diff --git a/lib/srv/alpnproxy/dialer.go b/api/client/alpn.go similarity index 85% rename from lib/srv/alpnproxy/dialer.go rename to api/client/alpn.go index f4b3e183bb97a..15ac2451194cc 100644 --- a/lib/srv/alpnproxy/dialer.go +++ b/api/client/alpn.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "context" @@ -23,16 +23,8 @@ import ( "time" "github.com/gravitational/trace" - - apiclient "github.com/gravitational/teleport/api/client" ) -// ContextDialer represents network dialer interface that uses context -type ContextDialer interface { - // DialContext is a function that dials the specified address - DialContext(ctx context.Context, network, addr string) (net.Conn, error) -} - // ALPNDialerConfig is the config for ALPNDialer. type ALPNDialerConfig struct { // KeepAlivePeriod defines period between keep alives. @@ -66,7 +58,7 @@ func (d ALPNDialer) DialContext(ctx context.Context, network, addr string) (net. return nil, trace.BadParameter("missing TLS config") } - dialer := apiclient.NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, apiclient.WithTLSConfig(d.cfg.TLSConfig)) + dialer := NewDialer(ctx, d.cfg.DialTimeout, d.cfg.DialTimeout, WithTLSConfig(d.cfg.TLSConfig)) if d.cfg.ALPNConnUpgradeRequired { dialer = newALPNConnUpgradeDialer(dialer, &tls.Config{ InsecureSkipVerify: d.cfg.TLSConfig.InsecureSkipVerify, diff --git a/lib/srv/alpnproxy/conn_upgrade.go b/api/client/alpn_conn_upgrade.go similarity index 92% rename from lib/srv/alpnproxy/conn_upgrade.go rename to api/client/alpn_conn_upgrade.go index 1425ae70ee274..c82f91dd7ff63 100644 --- a/lib/srv/alpnproxy/conn_upgrade.go +++ b/api/client/alpn_conn_upgrade.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "bufio" @@ -30,11 +30,9 @@ import ( "github.com/gravitational/trace" "github.com/sirupsen/logrus" - "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/utils" - "github.com/gravitational/teleport/lib/srv/alpnproxy/common" ) // IsALPNConnUpgradeRequired returns true if a tunnel is required through a HTTP @@ -57,7 +55,7 @@ func IsALPNConnUpgradeRequired(addr string, insecure bool) bool { Timeout: defaults.DefaultIOTimeout, } tlsConfig := &tls.Config{ - NextProtos: []string{string(common.ProtocolReverseTunnel)}, + NextProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, InsecureSkipVerify: insecure, } testConn, err := tls.DialWithDialer(netDialer, "tcp", addr, tlsConfig) @@ -145,12 +143,12 @@ func isALPNConnUpgradeRequiredByEnv(addr, envValue string) bool { // alpnConnUpgradeDialer makes an "HTTP" upgrade call to the Proxy Service then // tunnels the connection with this connection upgrade. type alpnConnUpgradeDialer struct { - dialer apiclient.ContextDialer + dialer ContextDialer tlsConfig *tls.Config } // newALPNConnUpgradeDialer creates a new alpnConnUpgradeDialer. -func newALPNConnUpgradeDialer(dialer apiclient.ContextDialer, tlsConfig *tls.Config) ContextDialer { +func newALPNConnUpgradeDialer(dialer ContextDialer, tlsConfig *tls.Config) ContextDialer { return &alpnConnUpgradeDialer{ dialer: dialer, tlsConfig: tlsConfig, @@ -187,7 +185,7 @@ func (d alpnConnUpgradeDialer) DialContext(ctx context.Context, network, addr st err = upgradeConnThroughWebAPI(tlsConn, url.URL{ Host: addr, Scheme: "https", - Path: teleport.WebAPIConnUpgrade, + Path: constants.WebAPIConnUpgrade, }) if err != nil { defer tlsConn.Close() @@ -203,7 +201,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error { } // For now, only "alpn" is supported. - req.Header.Add(teleport.WebAPIConnUpgradeHeader, teleport.WebAPIConnUpgradeTypeALPN) + req.Header.Add(constants.WebAPIConnUpgradeHeader, constants.WebAPIConnUpgradeTypeALPN) // Send the request and check if upgrade is successful. if err = req.Write(conn); err != nil { @@ -219,7 +217,7 @@ func upgradeConnThroughWebAPI(conn net.Conn, api url.URL) error { if http.StatusNotFound == resp.StatusCode { return trace.NotImplemented( "connection upgrade call to %q failed with status code %v. Please upgrade the server and try again.", - teleport.WebAPIConnUpgrade, + constants.WebAPIConnUpgrade, resp.StatusCode, ) } diff --git a/lib/srv/alpnproxy/conn_upgrade_test.go b/api/client/alpn_conn_upgrade_test.go similarity index 85% rename from lib/srv/alpnproxy/conn_upgrade_test.go rename to api/client/alpn_conn_upgrade_test.go index c01d01eb4d131..4c02b3ebfebb6 100644 --- a/lib/srv/alpnproxy/conn_upgrade_test.go +++ b/api/client/alpn_conn_upgrade_test.go @@ -14,13 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package alpnproxy +package client import ( "context" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" "errors" "net" "net/http" @@ -31,11 +30,8 @@ import ( "github.com/stretchr/testify/require" - "github.com/gravitational/teleport" - apiclient "github.com/gravitational/teleport/api/client" - "github.com/gravitational/teleport/lib/defaults" - "github.com/gravitational/teleport/lib/srv/alpnproxy/common" - "github.com/gravitational/teleport/lib/tlsca" + "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/fixtures" ) func TestIsALPNConnUpgradeRequired(t *testing.T) { @@ -55,7 +51,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) { }, { name: "upgrade not required (proto negotiated)", - serverProtos: []string{string(common.ProtocolReverseTunnel)}, + serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, insecure: true, expectedResult: false, }, @@ -67,7 +63,7 @@ func TestIsALPNConnUpgradeRequired(t *testing.T) { }, { name: "upgrade not required (other handshake error)", - serverProtos: []string{string(common.ProtocolReverseTunnel)}, + serverProtos: []string{string(constants.ALPNSNIProtocolReverseTunnel)}, insecure: false, // to cause handshake error expectedResult: false, }, @@ -138,7 +134,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) { pool.AddCert(server.Certificate()) tlsConfig := &tls.Config{RootCAs: pool} - preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig)) + preDialer := NewDialer(ctx, 0, 5*time.Second) dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig) conn, err := dialer.DialContext(ctx, "tcp", addr.Host) require.NoError(t, err) @@ -158,7 +154,7 @@ func TestALPNConnUpgradeDialer(t *testing.T) { require.NoError(t, err) tlsConfig := &tls.Config{InsecureSkipVerify: true} - preDialer := apiclient.NewDialer(ctx, 0, 5*time.Second, apiclient.WithTLSConfig(tlsConfig)) + preDialer := NewDialer(ctx, 0, 5*time.Second) dialer := newALPNConnUpgradeDialer(preDialer, tlsConfig) _, err = dialer.DialContext(ctx, "tcp", addr.Host) require.Error(t, err) @@ -207,12 +203,7 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe listener.Close() }) - caKey, caCert, err := tlsca.GenerateSelfSignedCA(pkix.Name{ - CommonName: "localhost", - }, []string{"localhost"}, defaults.CATTL) - require.NoError(t, err) - - cert, err := tls.X509KeyPair(caCert, caKey) + cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM)) require.NoError(t, err) m := &mockALPNServer{ @@ -228,8 +219,8 @@ func mustStartMockALPNServer(t *testing.T, supportedProtos []string) *mockALPNSe // upgrade request and sends back some data inside the tunnel. func mockConnUpgradeHandler(t *testing.T, upgradeType string, write []byte) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, teleport.WebAPIConnUpgrade, r.URL.Path) - require.Equal(t, upgradeType, r.Header.Get(teleport.WebAPIConnUpgradeHeader)) + require.Equal(t, constants.WebAPIConnUpgrade, r.URL.Path) + require.Equal(t, upgradeType, r.Header.Get(constants.WebAPIConnUpgradeHeader)) hj, ok := w.(http.Hijacker) require.True(t, ok) diff --git a/api/constants/constants.go b/api/constants/constants.go index 50c409cd6aca1..47b3b6c513c29 100644 --- a/api/constants/constants.go +++ b/api/constants/constants.go @@ -402,3 +402,15 @@ const ( // TimeoutGetClusterAlerts is the timeout for grabbing cluster alerts from tctl and tsh TimeoutGetClusterAlerts = time.Millisecond * 500 ) + +const ( + // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade + // call. + WebAPIConnUpgrade = "/webapi/connectionupgrade" + // WebAPIConnUpgradeHeader is the header used to indicate the requested + // connection upgrade types in the connection upgrade API. + WebAPIConnUpgradeHeader = "Upgrade" + // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies + // the upgraded connection should be handled by the ALPN handler. + WebAPIConnUpgradeTypeALPN = "alpn" +) diff --git a/api/fixtures/fixtures.go b/api/fixtures/fixtures.go new file mode 100644 index 0000000000000..573e4d3c9f651 --- /dev/null +++ b/api/fixtures/fixtures.go @@ -0,0 +1,64 @@ +// Copyright 2023 Gravitational, Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fixtures + +const ( + TLSCACertPEM = `-----BEGIN CERTIFICATE----- +MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA +MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs +aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa +MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j +YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI +gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi +fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4 +PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp +1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB +sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD +VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ +o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1 +IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m +NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx +QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu +vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ== +-----END CERTIFICATE-----` + TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ +m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6 +m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq +qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG +saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu +F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9 +k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD +ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ +fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ +jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE +7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz +Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e +WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA +d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb +hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU +ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1 +y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI +ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs +F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE +FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY +rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z +jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh +k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA +LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8 ++ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA +-----END RSA PRIVATE KEY-----` +) diff --git a/api/utils/pingconn/pingconn.go b/api/utils/pingconn/pingconn.go new file mode 100644 index 0000000000000..1578a1110b90c --- /dev/null +++ b/api/utils/pingconn/pingconn.go @@ -0,0 +1,132 @@ +/* +Copyright 2023 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package pingconn + +import ( + "crypto/tls" + "encoding/binary" + "math" + "sync" + + "github.com/gravitational/trace" +) + +// New returns a ping connection wrapping the provided net.Conn. +func New(conn *tls.Conn) *PingConn { + return &PingConn{Conn: conn} +} + +// PingConn wraps a *tls.Conn and add ping capabilities to it, including the +// `WritePing` function and `Read` (which excludes ping packets). +// +// When using this connection, the packets written will contain an initial data: +// the packet size. When reading, this information is taken into account, but it +// is not returned to the caller. +// +// Ping messages have a packet size of zero and are produced only when +// `WritePing` is called. On `Read`, any Ping packet is discarded. +type PingConn struct { + //net.Conn + *tls.Conn + + muRead sync.Mutex + muWrite sync.Mutex + + // currentSize size of bytes of the current packet. + currentSize uint32 +} + +// Read reads content from the underlaying connection, discarding any ping +// messages it finds. +func (c *PingConn) Read(p []byte) (int, error) { + c.muRead.Lock() + defer c.muRead.Unlock() + + err := c.discardPingReads() + if err != nil { + return 0, err + } + + // Check if the current size is larger than the provided buffer. + readSize := c.currentSize + if c.currentSize > uint32(len(p)) { + readSize = uint32(len(p)) + } + + n, err := c.Conn.Read(p[:readSize]) + c.currentSize -= uint32(n) + + return n, err +} + +// WritePing writes the ping packet to the connection. +func (c *PingConn) WritePing() error { + c.muWrite.Lock() + defer c.muWrite.Unlock() + + return binary.Write(c.Conn, binary.BigEndian, uint32(0)) +} + +// discardPingReads reads from the wrapped net.Conn until it encounters a +// non-ping packet. +func (c *PingConn) discardPingReads() error { + for c.currentSize == 0 { + err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize) + if err != nil { + return err + } + } + + return nil +} + +// Write writes provided content to the underlying connection with proper +// protocol fields. +func (c *PingConn) Write(p []byte) (int, error) { + c.muWrite.Lock() + defer c.muWrite.Unlock() + + // Avoid overflow when casting data length. It is only present to avoid + // panicking if the size cannot be cast. Callers should handle packet length + // limits, such as protocol implementations and audits. + if uint64(len(p)) > math.MaxUint32 { + return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32)) + } + + size := uint32(len(p)) + if size == 0 { + return 0, nil + } + + // Write packet size. + if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil { + return 0, trace.Wrap(err) + } + + // Iterate until everything is written. + var written int + for written < len(p) { + n, err := c.Conn.Write(p) + written += n + + if err != nil { + return written, trace.Wrap(err) + } + } + + return written, nil +} diff --git a/lib/srv/alpnproxy/conn_test.go b/api/utils/pingconn/pingconn_test.go similarity index 96% rename from lib/srv/alpnproxy/conn_test.go rename to api/utils/pingconn/pingconn_test.go index d91175d628a22..69a6a32cc5c47 100644 --- a/lib/srv/alpnproxy/conn_test.go +++ b/api/utils/pingconn/pingconn_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package alpnproxy +package pingconn import ( "bytes" @@ -26,6 +26,8 @@ import ( "time" "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/fixtures" ) func TestPingConnection(t *testing.T) { @@ -273,7 +275,7 @@ func makePingConn(t *testing.T) (*PingConn, *PingConn) { writer, reader := net.Pipe() tlsWriter, tlsReader := makeTLSConn(t, writer, reader) - return NewPingConn(tlsWriter), NewPingConn(tlsReader) + return New(tlsWriter), New(tlsReader) } // makeBufferedPingConn creates connections to have asynchronous writes. @@ -321,7 +323,7 @@ func makeBufferedPingConn(t *testing.T) (*PingConn, *PingConn) { } tlsConnA, tlsConnB := makeTLSConn(t, connSlice[0], connSlice[1]) - return NewPingConn(tlsConnA), NewPingConn(tlsConnB) + return New(tlsConnA), New(tlsConnB) } // makeTLSConn take two connections (client and server) and wrap them into TLS @@ -334,10 +336,13 @@ func makeTLSConn(t *testing.T, server, client net.Conn) (*tls.Conn, *tls.Conn) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM)) + require.NoError(t, err) + // Server go func() { tlsConn := tls.Server(server, &tls.Config{ - Certificates: []tls.Certificate{mustGenCertSignedWithCA(t, mustGenSelfSignedCert(t))}, + Certificates: []tls.Certificate{cert}, }) tlsConnChan <- struct { *tls.Conn diff --git a/constants.go b/constants.go index 4e79e5636a0a5..e21f46bda3423 100644 --- a/constants.go +++ b/constants.go @@ -803,18 +803,6 @@ const UserSingleUseCertTTL = time.Minute // cf. RFC 7230 ยง 2.7.2. const StandardHTTPSPort = 443 -const ( - // WebAPIConnUpgrade is the HTTP web API to make the connection upgrade - // call. - WebAPIConnUpgrade = "/webapi/connectionupgrade" - // WebAPIConnUpgradeHeader is the header used to indicate the requested - // connection upgrade types in the connection upgrade API. - WebAPIConnUpgradeHeader = "Upgrade" - // WebAPIConnUpgradeTypeALPN is a connection upgrade type that specifies - // the upgraded connection should be handled by the ALPN handler. - WebAPIConnUpgradeTypeALPN = "alpn" -) - const ( // KubeSessionDisplayParticipantRequirementsQueryParam is the query parameter used to // indicate that the client wants to display the participant requirements diff --git a/lib/client/api.go b/lib/client/api.go index c93d0809e5ac0..43112e98d1e00 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -75,7 +75,6 @@ import ( "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" "github.com/gravitational/teleport/lib/shell" - "github.com/gravitational/teleport/lib/srv/alpnproxy" alpncommon "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/sshutils/scp" @@ -623,7 +622,7 @@ func (c *Config) LoadProfile(ps ProfileStore, proxyAddr string) error { log.Warnf("Unable to parse dynamic port forwarding in user profile: %v.", err) } - if required, ok := alpnproxy.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok { + if required, ok := client.OverwriteALPNConnUpgradeRequirementByEnv(c.WebProxyAddr); ok { c.TLSRoutingConnUpgradeRequired = required } log.Infof("ALPN connection upgrade required for %q: %v.", c.WebProxyAddr, c.TLSRoutingConnUpgradeRequired) @@ -3095,7 +3094,7 @@ func (tc *TeleportClient) Login(ctx context.Context) (*Key, error) { } // Perform the ALPN test once at login. - tc.TLSRoutingConnUpgradeRequired = alpnproxy.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify) + tc.TLSRoutingConnUpgradeRequired = client.IsALPNConnUpgradeRequired(tc.WebProxyAddr, tc.InsecureSkipVerify) // Get the SSHLoginFunc that matches client and cluster settings. sshLoginFunc, err := tc.getSSHLoginFunc(pr) diff --git a/lib/fixtures/fixtures.go b/lib/fixtures/fixtures.go index 9d6ce39dc1a4b..cf86dd07d1959 100644 --- a/lib/fixtures/fixtures.go +++ b/lib/fixtures/fixtures.go @@ -19,6 +19,8 @@ import ( "testing" "github.com/gravitational/trace" + + apifixtures "github.com/gravitational/teleport/api/fixtures" ) // AssertNotFound expects not found error @@ -149,52 +151,8 @@ spec: pHM7WKwFyW1dvEDax3BGj9/cbKvpvcwRurn:oasis:names:tc:SAML:1.1:nameid-format:emailAddressurn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` const ( - TLSCACertPEM = `-----BEGIN CERTIFICATE----- -MIIDKjCCAhKgAwIBAgIQJtJDJZZBkg/afM8d2ZJCTjANBgkqhkiG9w0BAQsFADBA -MRUwEwYDVQQKEwxUZWxlcG9ydCBPU1MxJzAlBgNVBAMTHnRlbGVwb3J0LmxvY2Fs -aG9zdC5sb2NhbGRvbWFpbjAeFw0xNzA1MDkxOTQwMzZaFw0yNzA1MDcxOTQwMzZa -MEAxFTATBgNVBAoTDFRlbGVwb3J0IE9TUzEnMCUGA1UEAxMedGVsZXBvcnQubG9j -YWxob3N0LmxvY2FsZG9tYWluMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZm4Ml80SCiBgI -gTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6m34s6hwEr8Fi -fts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igqqLnuaY9eqGi4 -PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bGsaMmptVbsSEp -1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzuF9d9NubTLWgB -sK28YItcmWHdHXD/ODxVaehRjwIDAQABoyAwHjAOBgNVHQ8BAf8EBAMCB4AwDAYD -VR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAAVU6sNBdj76saHwOxGSdnEqQ -o2tMuR3msSM4F6wFK2UkKepsD7CYIf/PzNSNUqA5JIEUVeMqGyiHuAbU4C655nT1 -IyJX1D/+r73sSp5jbIpQm2xoQGZnj6g/Kltw8OSOAw+DsMF/PLVqoWJp07u6ew/m -NxWsJKcZ5k+q4eMxci9mKRHHqsquWKXzQlURMNFI+mGaFwrKM4dmzaR0BEc+ilSx -QqUvQ74smsLK+zhNikmgjlGC5ob9g8XkhVAkJMAh2rb9onDNiRl68iAgczP88mXu -vN/o98dypzsPxXmw6tkDqIRPUAUbh465rlY5sKMmRgXi2rUfl/QV5nbozUo/HQ== ------END CERTIFICATE-----` - TLSCAKeyPEM = `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAuKFLaf2iII/xDR+m2Yj6PnUEa+qzqwxsdLUjnunFZaAXG+hZ -m4Ml80SCiBgIgTHQlJyLIkTtuRoH5aeMyz1ERUCtii4ZsTqDrjjUybxP4r+4HVX6 -m34s6hwEr8Fifts9pMp4iS3tQguRc28gPdDo/T6VrJTVYUfUUsNDRtIrlB5O9igq -qLnuaY9eqGi4PUx0G0wRYJpRywoj8G0IkpfQTiX+CAC7dt5ws7ZrnGqCNBLGi5bG -saMmptVbsSEp1TenntF54V1iR49IV5JqDhm1S0HmkleoJzKdc+6sP/xNepz9PJzu -F9d9NubTLWgBsK28YItcmWHdHXD/ODxVaehRjwIDAQABAoIBABy4orWrShRMsA/9 -k4QVpfAfXf+3tBlwxlJld1QaQ6XqgI3L2FyzyyyLxM6NBo2qhSsJKy+6j0yTOxVD -ukhHkJ5BUH3FbCPA2Yk5uAhl7ft1HZwaqvCTcUM99pCswbjAPFetU5DrfxQeHpNZ -fyd+ny/+E2SUhpkqhmIVlBqpSTQyOywbiEvZ6ZiFmncdHhXaCy3YZsylrKUGPzsJ -jfU2iOE167eTOIjPStsaoCPv9jLSyy2OvuNNudS+Y1qkFz8ZGvPp+HB+Iig+AlAE -7KMzNrIW7PlHTDgUly1cRCl3+84yE2mJ97+hHiEy//HIwVDUpI529i2hMYM/u4qz -Wso/2tkCgYEA2FdE4bmCrZiA9eS8qobwGLE1+MJME4YwfJkynZUHHX93xORPQ66e -WYpN7/xbMvBDa8LZZYVTNVtZ/SkEUaTb5NQW2zXKoIutk1PFBb8NbA0m8Ss/mOJA -d5nUYGr987O9fRh1yP9TksBshHB/5A8U2UG8MFFCNvJTZDPRkuSlMiUCgYEA2nnb -hAJrhY7PaF6jdfimGvvponkUiEbWLppg7/SjgPg+QgqIwuLybryXyOAp+TEnNzgU -ujAjhNtIiyB/B13TDxOgUgWUWPbPvUAWGEvwI9h+RLie1umGHd48G1NR76fwqSf1 -y7z3YRnq8vCdz8ywB3o5GO6SH6QkMJBIxfIMlKMCgYA55akOi7oYQT8KD4waSwCI -ayyZhU4cz4W8Yrd0CsUbtNhVvhAked/w8J2JA01Y5Yn1lfDeRX8OQYNkyAxa2Tbs -F4KCafPvYVIzonCQ6B9sclygoEVl4e8E0wtOPnP2O30TtG8ZOpOgK5UfIIhpfUvE -FN6LQ8PntpRwtZl5qW04bQKBgGnHhFxHG64fthZPdA9jY3E/NSCgRSuyOHN59aNY -rG1+RA6PsSXC4iRxlYAB4PCxNs6KjaaUNi5WSaprAnYbnFv5Ya802l20qmJ0C/6Z -jdydLo2xYd6mVHRTrICCd/J0OpZ8LYsGpDPUa6hSjeYVscj9CXYj1IYTYB5PTZzh -k+vHAoGBAJyA+RtBF5m64/TqhZFcesTtnpWaRhQ50xXnNVF3W1eKGPtdTDKOaENA -LJxgC1GdoEz2ilXW802H9QrdKf9GPqxwi2TVzfO6pzWkdZcmbItu+QCCFz+co+r8 -+ki49FmlfbR5YVPN+8X40aLQB4xDkCHwRwTkrigzWQhIOv8NAhDA ------END RSA PRIVATE KEY-----` + TLSCACertPEM = apifixtures.TLSCACertPEM + TLSCAKeyPEM = apifixtures.TLSCAKeyPEM // Backwards-compatibility alias for teleport.e SigningCertPEM = TLSCACertPEM ) diff --git a/lib/srv/alpnproxy/conn.go b/lib/srv/alpnproxy/conn.go index ef566dbb9fd81..c8bed17e08112 100644 --- a/lib/srv/alpnproxy/conn.go +++ b/lib/srv/alpnproxy/conn.go @@ -17,16 +17,10 @@ limitations under the License. package alpnproxy import ( - "crypto/tls" - "encoding/binary" "io" - "math" "net" - "sync" "time" - "github.com/gravitational/trace" - "github.com/gravitational/teleport/lib/utils" ) @@ -103,109 +97,3 @@ func (conn readOnlyConn) RemoteAddr() net.Addr { return &utils.Net func (conn readOnlyConn) SetDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetReadDeadline(t time.Time) error { return nil } func (conn readOnlyConn) SetWriteDeadline(t time.Time) error { return nil } - -// NewPingConn returns a ping connection wrapping the provided net.Conn. -func NewPingConn(conn *tls.Conn) *PingConn { - return &PingConn{Conn: conn} -} - -// PingConn wraps a *tls.Conn and add ping capabilities to it, including the -// `WritePing` function and `Read` (which excludes ping packets). -// -// When using this connection, the packets written will contain an initial data: -// the packet size. When reading, this information is taken into account, but it -// is not returned to the caller. -// -// Ping messages have a packet size of zero and are produced only when -// `WritePing` is called. On `Read`, any Ping packet is discarded. -type PingConn struct { - //net.Conn - *tls.Conn - - muRead sync.Mutex - muWrite sync.Mutex - - // currentSize size of bytes of the current packet. - currentSize uint32 -} - -// Read reads content from the underlaying connection, discarding any ping -// messages it finds. -func (c *PingConn) Read(p []byte) (int, error) { - c.muRead.Lock() - defer c.muRead.Unlock() - - err := c.discardPingReads() - if err != nil { - return 0, err - } - - // Check if the current size is larger than the provided buffer. - readSize := c.currentSize - if c.currentSize > uint32(len(p)) { - readSize = uint32(len(p)) - } - - n, err := c.Conn.Read(p[:readSize]) - c.currentSize -= uint32(n) - - return n, err -} - -// WritePing writes the ping packet to the connection. -func (c *PingConn) WritePing() error { - c.muWrite.Lock() - defer c.muWrite.Unlock() - - return binary.Write(c.Conn, binary.BigEndian, uint32(0)) -} - -// discardPingReads reads from the wrapped net.Conn until it encounters a -// non-ping packet. -func (c *PingConn) discardPingReads() error { - for c.currentSize == 0 { - err := binary.Read(c.Conn, binary.BigEndian, &c.currentSize) - if err != nil { - return err - } - } - - return nil -} - -// Write writes provided content to the underlying connection with proper -// protocol fields. -func (c *PingConn) Write(p []byte) (int, error) { - c.muWrite.Lock() - defer c.muWrite.Unlock() - - // Avoid overflow when casting data length. It is only present to avoid - // panicking if the size cannot be cast. Callers should handle packet length - // limits, such as protocol implementations and audits. - if uint64(len(p)) > math.MaxUint32 { - return 0, trace.BadParameter("invalid content size, max size permitted is %d", uint64(math.MaxUint32)) - } - - size := uint32(len(p)) - if size == 0 { - return 0, nil - } - - // Write packet size. - if err := binary.Write(c.Conn, binary.BigEndian, size); err != nil { - return 0, trace.Wrap(err) - } - - // Iterate until everything is written. - var written int - for written < len(p) { - n, err := c.Conn.Write(p) - written += n - - if err != nil { - return written, trace.Wrap(err) - } - } - - return written, nil -} diff --git a/lib/srv/alpnproxy/local_proxy.go b/lib/srv/alpnproxy/local_proxy.go index 103a45af5e60d..d914002faf7d9 100644 --- a/lib/srv/alpnproxy/local_proxy.go +++ b/lib/srv/alpnproxy/local_proxy.go @@ -34,6 +34,8 @@ import ( "github.com/sirupsen/logrus" "golang.org/x/exp/slices" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" commonApp "github.com/gravitational/teleport/lib/srv/app/common" "github.com/gravitational/teleport/lib/tlsca" @@ -218,7 +220,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC return trace.Wrap(err) } - tlsConn, err := DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs)) + tlsConn, err := client.DialALPN(ctx, l.cfg.RemoteProxyAddr, l.getALPNDialerConfig(certs)) if err != nil { return trace.Wrap(err) } @@ -227,7 +229,7 @@ func (l *LocalProxy) handleDownstreamConnection(ctx context.Context, downstreamC var upstreamConn net.Conn = tlsConn if common.IsPingProtocol(common.Protocol(tlsConn.ConnectionState().NegotiatedProtocol)) { l.cfg.Log.Debug("Using ping connection") - upstreamConn = NewPingConn(tlsConn) + upstreamConn = pingconn.New(tlsConn) } return trace.Wrap(utils.ProxyConn(ctx, downstreamConn, upstreamConn)) @@ -243,8 +245,8 @@ func (l *LocalProxy) Close() error { return nil } -func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) ALPNDialerConfig { - return ALPNDialerConfig{ +func (l *LocalProxy) getALPNDialerConfig(certs []tls.Certificate) client.ALPNDialerConfig { + return client.ALPNDialerConfig{ ALPNConnUpgradeRequired: l.cfg.ALPNConnUpgradeRequired, TLSConfig: &tls.Config{ NextProtos: common.ProtocolsToString(l.cfg.Protocols), @@ -285,7 +287,7 @@ func (l *LocalProxy) makeHTTPReverseProxy(certs []tls.Certificate) *httputil.Rev http.Error(w, http.StatusText(code), code) }, Transport: &http.Transport{ - DialTLSContext: NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext, + DialTLSContext: client.NewALPNDialer(l.getALPNDialerConfig(certs)).DialContext, }, } } diff --git a/lib/srv/alpnproxy/local_proxy_config_opt.go b/lib/srv/alpnproxy/local_proxy_config_opt.go index 20b5e61e23dde..f2af0b5ed81ae 100644 --- a/lib/srv/alpnproxy/local_proxy_config_opt.go +++ b/lib/srv/alpnproxy/local_proxy_config_opt.go @@ -24,6 +24,7 @@ import ( "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" ) @@ -41,7 +42,7 @@ type GetClusterCACertPoolFunc func(ctx context.Context) (*x509.CertPool, error) // already been set. func WithALPNConnUpgradeTest(ctx context.Context, getClusterCertPool GetClusterCACertPoolFunc) LocalProxyConfigOpt { return func(config *LocalProxyConfig) error { - config.ALPNConnUpgradeRequired = IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify) + config.ALPNConnUpgradeRequired = client.IsALPNConnUpgradeRequired(config.RemoteProxyAddr, config.InsecureSkipVerify) return trace.Wrap(WithClusterCAsIfConnUpgrade(ctx, getClusterCertPool)(config)) } } diff --git a/lib/srv/alpnproxy/proxy.go b/lib/srv/alpnproxy/proxy.go index 4ac5613fb538d..2c0595cf7f8cf 100644 --- a/lib/srv/alpnproxy/proxy.go +++ b/lib/srv/alpnproxy/proxy.go @@ -33,6 +33,7 @@ import ( "github.com/sirupsen/logrus" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" @@ -410,7 +411,7 @@ func (p *Proxy) handleConn(ctx context.Context, clientConn net.Conn, defaultOver // handlePingConnection starts the server ping routine and returns `pingConn`. func (p *Proxy) handlePingConnection(ctx context.Context, conn *tls.Conn) net.Conn { - pingConn := NewPingConn(conn) + pingConn := pingconn.New(conn) // Start ping routine. It will continuously send pings in a defined // interval. diff --git a/lib/srv/alpnproxy/proxy_test.go b/lib/srv/alpnproxy/proxy_test.go index 6c81f62696de1..7e7e9451c8132 100644 --- a/lib/srv/alpnproxy/proxy_test.go +++ b/lib/srv/alpnproxy/proxy_test.go @@ -32,6 +32,7 @@ import ( "github.com/stretchr/testify/require" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/utils/pingconn" "github.com/gravitational/teleport/lib/srv/alpnproxy/common" "github.com/gravitational/teleport/lib/srv/db/dbutils" "github.com/gravitational/teleport/lib/tlsca" @@ -191,7 +192,7 @@ func TestProxyTLSDatabaseHandler(t *testing.T) { }) require.NoError(t, err) - conn := NewPingConn(baseConn) + conn := pingconn.New(baseConn) tlsConn := tls.Client(conn, &tls.Config{ Certificates: []tls.Certificate{ clientCert, diff --git a/lib/web/conn_upgrade.go b/lib/web/conn_upgrade.go index 9f1638a74a2d2..cdfe3dae0556a 100644 --- a/lib/web/conn_upgrade.go +++ b/lib/web/conn_upgrade.go @@ -25,17 +25,17 @@ import ( "github.com/gravitational/trace" "github.com/julienschmidt/httprouter" - "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/lib/utils" ) // selectConnectionUpgrade selects the requested upgrade type and returns the // corresponding handler. func (h *Handler) selectConnectionUpgrade(r *http.Request) (string, ConnectionHandler, error) { - upgrades := r.Header.Values(teleport.WebAPIConnUpgradeHeader) + upgrades := r.Header.Values(constants.WebAPIConnUpgradeHeader) for _, upgradeType := range upgrades { switch upgradeType { - case teleport.WebAPIConnUpgradeTypeALPN: + case constants.WebAPIConnUpgradeTypeALPN: return upgradeType, h.upgradeALPN, nil } } @@ -90,7 +90,7 @@ func (h *Handler) upgradeALPN(ctx context.Context, conn net.Conn) error { func writeUpgradeResponse(w io.Writer, upgradeType string) error { header := make(http.Header) - header.Add(teleport.WebAPIConnUpgradeHeader, upgradeType) + header.Add(constants.WebAPIConnUpgradeHeader, upgradeType) response := &http.Response{ Status: http.StatusText(http.StatusSwitchingProtocols), StatusCode: http.StatusSwitchingProtocols,