diff --git a/.github/workflows/kube-integration-tests-non-root.yaml b/.github/workflows/kube-integration-tests-non-root.yaml index a83ce1cb63927..2ca70b137d215 100644 --- a/.github/workflows/kube-integration-tests-non-root.yaml +++ b/.github/workflows/kube-integration-tests-non-root.yaml @@ -59,23 +59,6 @@ jobs: chown -Rf ci:ci ${GITHUB_WORKSPACE} $(go env GOMODCACHE) $(go env GOCACHE) continue-on-error: true - - name: Install Docker CLI - run: | - sudo apt-get update - sudo apt-get install -y \ - ca-certificates \ - curl \ - gnupg - sudo install -m 0755 -d /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg - sudo chmod a+r /etc/apt/keyrings/docker.gpg - echo \ - "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ - "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ - sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt-get update - sudo apt-get install -y docker-ce-cli - - name: Create KinD cluster uses: helm/kind-action@v1.5.0 with: diff --git a/go.mod b/go.mod index 58fb6afb41e9d..2053b992d6431 100644 --- a/go.mod +++ b/go.mod @@ -389,7 +389,7 @@ replace ( github.com/microsoft/go-mssqldb => github.com/gravitational/go-mssqldb v0.11.1-0.20230331180905-0f76f1751cd3 // replace module github.com/moby/spdystream until https://github.com/moby/spdystream/pull/91 merges and deps are updated // otherwise tests fail with a data race detection. - github.com/moby/spdystream => github.com/tigrato/spdystream v0.0.0-20230506141330-3473c0b0cd14 + github.com/moby/spdystream => github.com/gravitational/spdystream v0.0.0-20230511102044-2597ad437553 github.com/sirupsen/logrus => github.com/gravitational/logrus v1.4.4-0.20210817004754-047e20245621 github.com/vulcand/predicate => github.com/gravitational/predicate v1.3.0 // Use our internal crypto fork, to work around the issue with OpenSSH <= 7.6 mentioned here: https://github.com/golang/go/issues/53391 diff --git a/go.sum b/go.sum index 5dd10e2651b8f..117b5a980b4ec 100644 --- a/go.sum +++ b/go.sum @@ -772,6 +772,8 @@ github.com/gravitational/redis/v9 v9.0.0-teleport.3 h1:Eg/j3jiNUZ558KDXOqzF682EF github.com/gravitational/redis/v9 v9.0.0-teleport.3/go.mod h1:8et+z03j0l8N+DvsVnclzjf3Dl/pFHgRk+2Ct1qw66A= github.com/gravitational/roundtrip v1.0.2 h1:eOCY0NEKKaB0ksJmvhO6lPMFz1pIIef+vyPBTBROQ5c= github.com/gravitational/roundtrip v1.0.2/go.mod h1:fuI1booM2hLRA/B/m5MRAPOU6mBZNYcNycono2UuTw0= +github.com/gravitational/spdystream v0.0.0-20230511102044-2597ad437553 h1:C/2iznTqtvoa00hHwcqeYzgAS3tvaNBXWXIbdeGCxvM= +github.com/gravitational/spdystream v0.0.0-20230511102044-2597ad437553/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/gravitational/trace v1.2.1 h1:Iaf43aqbKV5H8bdiRs1qByjEHgAfADJ0lt0JwRyu+q8= github.com/gravitational/trace v1.2.1/go.mod h1:n0ijrq6psJY0sOI/NzLp+xdd8xl79jjwzVOFHDY6+kQ= github.com/gravitational/ttlmap v0.0.0-20171116003245-91fd36b9004c h1:C2iWDiod8vQ3YnOiCdMP9qYeg2UifQ8KSk36r0NswSE= @@ -1317,8 +1319,6 @@ github.com/thales-e-security/pool v0.0.2/go.mod h1:qtpMm2+thHtqhLzTwgDBj/OuNnMpu github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tigrato/spdystream v0.0.0-20230506141330-3473c0b0cd14 h1:N8tOYijlRgnmoTATYMODVD+QhlYN01fVALMxaQPnFTE= -github.com/tigrato/spdystream v0.0.0-20230506141330-3473c0b0cd14/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ucarion/urlpath v0.0.0-20200424170820-7ccc79b76bbb h1:Ywfo8sUltxogBpFuMOFRrrSifO788kAFxmvVw31PtQQ= diff --git a/integration/helpers/helpers.go b/integration/helpers/helpers.go index d2c14bdbfb28e..9a6a27686fbd2 100644 --- a/integration/helpers/helpers.go +++ b/integration/helpers/helpers.go @@ -448,3 +448,16 @@ func MakeTestDatabaseServer(t *testing.T, proxyAddr utils.NetAddr, token string, return db } + +// MustCreateListener creates a tcp listener at 127.0.0.1 with random port. +func MustCreateListener(t *testing.T) net.Listener { + t.Helper() + + listener, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + t.Cleanup(func() { + listener.Close() + }) + return listener +} diff --git a/integration/helpers/instance.go b/integration/helpers/instance.go index 2d9a36361a8cb..4245f19a98fc3 100644 --- a/integration/helpers/instance.go +++ b/integration/helpers/instance.go @@ -1017,6 +1017,8 @@ type ProxyConfig struct { SSHAddr string // WebAddr the address the web service should listen on WebAddr string + // KubeAddr is the kube proxy address. + KubeAddr string // ReverseTunnelAddr the address the reverse proxy service should listen on ReverseTunnelAddr string // Disable the web service @@ -1281,17 +1283,21 @@ func (i *TeleInstance) NewUnauthenticatedClient(cfg ClientConfig) (tc *client.Te var webProxyAddr string var sshProxyAddr string + var kubeProxyAddr string switch { case cfg.Proxy != nil: webProxyAddr = cfg.Proxy.WebAddr sshProxyAddr = cfg.Proxy.SSHAddr + kubeProxyAddr = cfg.Proxy.KubeAddr case cfg.ALBAddr != "": webProxyAddr = cfg.ALBAddr sshProxyAddr = cfg.ALBAddr + kubeProxyAddr = cfg.ALBAddr default: webProxyAddr = i.Web sshProxyAddr = i.SSHProxy + kubeProxyAddr = i.Config.Proxy.Kube.ListenAddr.Addr } fwdAgentMode := client.ForwardAgentNo @@ -1311,6 +1317,7 @@ func (i *TeleInstance) NewUnauthenticatedClient(cfg ClientConfig) (tc *client.Te Labels: cfg.Labels, WebProxyAddr: webProxyAddr, SSHProxyAddr: sshProxyAddr, + KubeProxyAddr: kubeProxyAddr, InteractiveCommand: cfg.Interactive, TLSRoutingEnabled: i.IsSinglePortSetup, TLSRoutingConnUpgradeRequired: cfg.ALBAddr != "", diff --git a/integration/helpers/proxy.go b/integration/helpers/proxy.go index e0a25822d4996..359f4caf1c0cd 100644 --- a/integration/helpers/proxy.go +++ b/integration/helpers/proxy.go @@ -15,15 +15,23 @@ package helpers import ( + "context" + "crypto/tls" "fmt" "io" "net" "net/http" "net/url" "sync" + "testing" "time" "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + + "github.com/gravitational/teleport/api/fixtures" + "github.com/gravitational/teleport/lib/utils" ) type ProxyHandler struct { @@ -201,3 +209,73 @@ func MakeProxyAddr(user, pass, host string) string { userPass := url.UserPassword(user, pass).String() return fmt.Sprintf("%v@%v", userPass, host) } + +// MockAWSALBProxy is a mock proxy server that simulates an AWS application +// load balancer where ALPN is not supported. Note that this mock does not +// actually balance traffic. +type MockAWSALBProxy struct { + net.Listener + proxyAddr string + cert tls.Certificate +} + +func (m *MockAWSALBProxy) serve(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + default: + } + + conn, err := m.Accept() + if err != nil { + logrus.WithError(err).Debugf("Failed to accept conn.") + return + } + + go func() { + defer conn.Close() + + // Handshake with incoming client and drops ALPN. + downstreamConn := tls.Server(conn, &tls.Config{ + Certificates: []tls.Certificate{m.cert}, + }) + + // api.Client may try different connection methods. Just close the + // connection when something goes wrong. + if err := downstreamConn.HandshakeContext(ctx); err != nil { + logrus.WithError(err).Debugf("Failed to handshake.") + return + } + + // Make a connection to the proxy server with ALPN protos. + upstreamConn, err := tls.Dial("tcp", m.proxyAddr, &tls.Config{ + InsecureSkipVerify: true, + }) + if err != nil { + logrus.WithError(err).Debugf("Failed to dial upstream.") + return + } + utils.ProxyConn(ctx, downstreamConn, upstreamConn) + }() + } +} + +// MustStartMockALBProxy creates and starts a MockAWSALBProxy. +func MustStartMockALBProxy(t *testing.T, proxyAddr string) *MockAWSALBProxy { + t.Helper() + + cert, err := tls.X509KeyPair([]byte(fixtures.TLSCACertPEM), []byte(fixtures.TLSCAKeyPEM)) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + m := &MockAWSALBProxy{ + proxyAddr: proxyAddr, + Listener: MustCreateListener(t), + cert: cert, + } + go m.serve(ctx) + return m +} diff --git a/integration/kube_integration_test.go b/integration/kube_integration_test.go index 9b8dd6760340a..dc18b1286a06a 100644 --- a/integration/kube_integration_test.go +++ b/integration/kube_integration_test.go @@ -50,6 +50,7 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/breaker" + "github.com/gravitational/teleport/api/constants" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/types" @@ -1641,15 +1642,27 @@ func testKubeJoin(t *testing.T, suite *KubeSuite) { return true }, 10*time.Second, time.Second) - stream, err := kubeJoin(kube.ProxyConfig{ - T: teleport, - Username: participantUsername, - KubeUsers: kubeUsers, - KubeGroups: kubeGroups, - }, tc, session) - require.NoError(t, err) + var stream *client.KubeSession + t.Run("join KubeProxyAddr", func(t *testing.T) { + stream, err = kubeJoin(kube.ProxyConfig{ + T: teleport, + Username: participantUsername, + KubeUsers: kubeUsers, + KubeGroups: kubeGroups, + CustomTLSServerName: "", + }, tc, session) + require.NoError(t, err) + }) defer stream.Close() + // Tests other connection methods. + t.Run("join WebProxyAddr", func(t *testing.T) { + testKubeJoinByWebAddr(t, teleport, participantUsername, kubeUsers, kubeGroups, session) + }) + t.Run("join WebProxyAddr with connection upgrade", func(t *testing.T) { + testKubeJoinByALBAddr(t, teleport, participantUsername, kubeUsers, kubeGroups, session) + }) + // We wait again for the second user to finish joining the session. // We allow a bit of time to pass here to give the session manager time to recognize the // new IO streams of the second client. @@ -1667,8 +1680,57 @@ func testKubeJoin(t *testing.T, suite *KubeSuite) { participantStdoutW.Close() }) - participantOutput, err := io.ReadAll(participantStdoutR) + t.Run("verify output", func(t *testing.T) { + participantOutput, err := io.ReadAll(participantStdoutR) + require.NoError(t, err) + require.Contains(t, string(participantOutput), "echo hi") + require.Contains(t, out.String(), "echo hi2") + }) +} + +func testKubeJoinByWebAddr(t *testing.T, teleport *helpers.TeleInstance, username string, kubeUsers, kubeGroups []string, session types.SessionTracker) { + t.Helper() + + tc, err := teleport.NewClient(helpers.ClientConfig{ + Login: username, + Cluster: helpers.Site, + Host: Host, + Proxy: &helpers.ProxyConfig{ + WebAddr: teleport.Config.Proxy.WebAddr.Addr, + KubeAddr: teleport.Config.Proxy.WebAddr.Addr, + }, + }) + require.NoError(t, err) + + stream, err := kubeJoin(kube.ProxyConfig{ + T: teleport, + Username: username, + KubeUsers: kubeUsers, + KubeGroups: kubeGroups, + CustomTLSServerName: constants.KubeTeleportProxyALPNPrefix + Host, + }, tc, session) + require.NoError(t, err) + stream.Close() +} +func testKubeJoinByALBAddr(t *testing.T, teleport *helpers.TeleInstance, username string, kubeUsers, kubeGroups []string, session types.SessionTracker) { + t.Helper() + + albProxy := helpers.MustStartMockALBProxy(t, teleport.Config.Proxy.WebAddr.Addr) + tc, err := teleport.NewClient(helpers.ClientConfig{ + Login: username, + Cluster: helpers.Site, + Host: Host, + ALBAddr: albProxy.Addr().String(), + }) + require.NoError(t, err) + + stream, err := kubeJoin(kube.ProxyConfig{ + T: teleport, + Username: username, + KubeUsers: kubeUsers, + KubeGroups: kubeGroups, + CustomTLSServerName: constants.KubeTeleportProxyALPNPrefix + Host, + }, tc, session) require.NoError(t, err) - require.Contains(t, string(participantOutput), "echo hi") - require.Contains(t, out.String(), "echo hi2") + stream.Close() } diff --git a/integration/proxy/proxy_helpers.go b/integration/proxy/proxy_helpers.go index 513417cd44534..979a53f886cfe 100644 --- a/integration/proxy/proxy_helpers.go +++ b/integration/proxy/proxy_helpers.go @@ -38,7 +38,6 @@ import ( "github.com/gravitational/trace" "github.com/jackc/pgconn" "github.com/jonboulle/clockwork" - "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -423,7 +422,7 @@ func withTrustedClusterBehindALB() proxySuiteOptionsFunc { } require.NotNil(t, options.trustedCluster) - albProxy := mustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) + albProxy := helpers.MustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) options.trustedCluster.SetProxyAddress(albProxy.Addr().String()) options.trustedCluster.SetReverseTunnelAddress(albProxy.Addr().String()) } @@ -503,18 +502,6 @@ func mustCreateKubeConfigFile(t *testing.T, config clientcmdapi.Config) string { return configPath } -func mustCreateListener(t *testing.T) net.Listener { - t.Helper() - - listener, err := net.Listen("tcp", "127.0.0.1:0") - require.NoError(t, err) - - t.Cleanup(func() { - listener.Close() - }) - return listener -} - func mustCreateKubeLocalProxyListener(t *testing.T, teleportCluster string, caCert, caKey []byte) net.Listener { t.Helper() @@ -542,7 +529,7 @@ func mustStartALPNLocalProxyWithConfig(t *testing.T, config alpnproxy.LocalProxy t.Helper() if config.Listener == nil { - config.Listener = mustCreateListener(t) + config.Listener = helpers.MustCreateListener(t) } if config.ParentContext == nil { config.ParentContext = context.TODO() @@ -620,77 +607,6 @@ func mustCreateSelfSignedCert(t *testing.T) tls.Certificate { return cert } -// mockAWSALBProxy is a mock proxy server that simulates an AWS application -// load balancer where ALPN is not supported. Note that this mock does not -// actually balance traffic. -type mockAWSALBProxy struct { - net.Listener - proxyAddr string - cert tls.Certificate -} - -func (m *mockAWSALBProxy) serve(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - default: - } - - conn, err := m.Accept() - if err != nil { - if utils.IsUseOfClosedNetworkError(err) { - continue - } - - logrus.WithError(err).Debugf("Failed to accept conn.") - return - } - - go func() { - defer conn.Close() - - // Handshake with incoming client and drops ALPN. - downstreamConn := tls.Server(conn, &tls.Config{ - Certificates: []tls.Certificate{m.cert}, - ClientAuth: tls.NoClientCert, - }) - - // api.Client may try different connection methods. Just close the - // connection when something goes wrong. - if err := downstreamConn.HandshakeContext(ctx); err != nil { - logrus.WithError(err).Debugf("Failed to handshake.") - return - } - - // Make a connection to the proxy server with ALPN protos. - upstreamConn, err := tls.Dial("tcp", m.proxyAddr, &tls.Config{ - InsecureSkipVerify: true, - }) - if err != nil { - logrus.WithError(err).Debugf("Failed to dial upstream.") - return - } - utils.ProxyConn(ctx, downstreamConn, upstreamConn) - }() - } -} - -func mustStartMockALBProxy(t *testing.T, proxyAddr string) *mockAWSALBProxy { - t.Helper() - - ctx, cancel := context.WithCancel(context.Background()) - t.Cleanup(cancel) - - m := &mockAWSALBProxy{ - proxyAddr: proxyAddr, - Listener: mustCreateListener(t), - cert: mustCreateSelfSignedCert(t), - } - go m.serve(ctx) - return m -} - // waitForActivePeerProxyConnections waits for remote cluster to report a minimum number of active proxy peer connections func waitForActivePeerProxyConnections(t *testing.T, tunnel reversetunnel.Server, expectedCount int) { //nolint:unused // Only used by skipped test TestProxyTunnelStrategyProxyPeering require.Eventually(t, func() bool { diff --git a/integration/proxy/proxy_test.go b/integration/proxy/proxy_test.go index d48229cb9edc3..8ada661a05eb2 100644 --- a/integration/proxy/proxy_test.go +++ b/integration/proxy/proxy_test.go @@ -141,7 +141,7 @@ func TestALPNSNIProxyMultiCluster(t *testing.T) { if tc.testALPNConnUpgrade { t.Run("ALPN conn upgrade", func(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. - albProxy := mustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) + albProxy := helpers.MustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) // Run command in root through ALB address. suite.mustConnectToClusterAndRunSSHCommand(t, helpers.ClientConfig{ @@ -400,7 +400,7 @@ func TestALPNSNIProxyKube(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. Then // ALPN local proxies will point to this ALB instead. - albProxy := mustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) + albProxy := helpers.MustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) // Generate a self-signed CA for kube local proxy. localCAKey, localCACert, err := tlsca.GenerateSelfSignedCA(pkix.Name{ @@ -888,7 +888,7 @@ func TestALPNSNIProxyDatabaseAccess(t *testing.T) { t.Run("ALPN connection upgrade", func(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. Then // ALPN local proxies will point to this ALB instead. - albProxy := mustStartMockALBProxy(t, pack.Root.Cluster.Web) + albProxy := helpers.MustStartMockALBProxy(t, pack.Root.Cluster.Web) // Test a protocol in the alpncommon.IsDBTLSProtocol list where // the database client will perform a native TLS handshake. @@ -1104,7 +1104,7 @@ func TestALPNSNIProxyAppAccess(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. Then // ALPN local proxies will point to this ALB instead. - albProxy := mustStartMockALBProxy(t, pack.RootWebAddr()) + albProxy := helpers.MustStartMockALBProxy(t, pack.RootWebAddr()) lp := mustStartALPNLocalProxyWithConfig(t, alpnproxy.LocalProxyConfig{ RemoteProxyAddr: albProxy.Addr().String(), @@ -1214,7 +1214,7 @@ func TestALPNProxyAuthClientConnectWithUserIdentity(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. Then // client can point to this ALB instead. - albProxy := mustStartMockALBProxy(t, rc.Web) + albProxy := helpers.MustStartMockALBProxy(t, rc.Web) tests := []struct { name string @@ -1535,7 +1535,7 @@ func TestALPNSNIProxyGRPCInsecure(t *testing.T) { // Test register through Proxy behind a L7 load balancer. t.Run("ALPN conn upgrade", func(t *testing.T) { - albProxy := mustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) + albProxy := helpers.MustStartMockALBProxy(t, suite.root.Config.Proxy.WebAddr.Addr) albAddr, err := utils.ParseAddr(albProxy.Addr().String()) require.NoError(t, err) diff --git a/integration/proxy/teleterm_test.go b/integration/proxy/teleterm_test.go index 94b1e9afe26cd..dd735bfa82110 100644 --- a/integration/proxy/teleterm_test.go +++ b/integration/proxy/teleterm_test.go @@ -66,7 +66,7 @@ func testTeletermGatewaysCertRenewal(t *testing.T, pack *dbhelpers.DatabasePack) t.Run("ALPN connection upgrade", func(t *testing.T) { // Make a mock ALB which points to the Teleport Proxy Service. Then // ALPN local proxies will point to this ALB instead. - albProxy := mustStartMockALBProxy(t, pack.Root.Cluster.Web) + albProxy := helpers.MustStartMockALBProxy(t, pack.Root.Cluster.Web) databaseURI := uri.NewClusterURI(rootClusterName). AppendDB(pack.Root.MysqlService.Name) diff --git a/lib/client/client_store.go b/lib/client/client_store.go index afa32e0e81938..8ffd2325552c6 100644 --- a/lib/client/client_store.go +++ b/lib/client/client_store.go @@ -231,26 +231,26 @@ func (s *Store) FullProfileStatus() (*ProfileStatus, []*ProfileStatus, error) { // - $TSH_HOME/$profile.yaml // - $TSH_HOME/keys/$PROXY/$USER-kube/$TELEPORT_CLUSTER/$KUBE_CLUSTER-x509.pem // - $TSH_HOME/keys/$PROXY/$USER -func LoadKeysToKubeFromStore(dirPath, proxy, teleportCluster, kubeCluster string) ([]byte, []byte, error) { +func LoadKeysToKubeFromStore(dirPath, proxy, teleportCluster, kubeCluster string) ([]byte, []byte, *profile.Profile, error) { dirPath = profile.FullProfilePath(dirPath) profileStore := NewFSProfileStore(dirPath) // tsh stores the profiles using the proxy host as the profile name. profileName, err := utils.Host(proxy) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, nil, nil, trace.Wrap(err) } if profileName == "" { // If no profile name is provided, default to the current profile. profileName, err = profileStore.CurrentProfile() if err != nil { - return nil, nil, trace.Wrap(err) + return nil, nil, nil, trace.Wrap(err) } } // Load the desired profile. profile, err := profileStore.GetProfile(profileName) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, nil, nil, trace.Wrap(err) } fsKeyStore := NewFSKeyStore(dirPath) @@ -258,17 +258,17 @@ func LoadKeysToKubeFromStore(dirPath, proxy, teleportCluster, kubeCluster string certPath := fsKeyStore.kubeCertPath(KeyIndex{ProxyHost: profile.SiteName, ClusterName: teleportCluster, Username: profile.Username}, kubeCluster) kubeCert, err := os.ReadFile(certPath) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, nil, nil, trace.Wrap(err) } privKeyPath := fsKeyStore.userKeyPath(KeyIndex{ProxyHost: profile.SiteName, Username: profile.Username}) privKey, err := os.ReadFile(privKeyPath) if err != nil { - return nil, nil, trace.Wrap(err) + return nil, nil, nil, trace.Wrap(err) } if ok := keys.IsRSAPrivateKey(privKey); !ok { - return nil, nil, trace.BadParameter("unsupported private key type") + return nil, nil, nil, trace.BadParameter("unsupported private key type") } - return kubeCert, privKey, nil + return kubeCert, privKey, profile, nil } diff --git a/lib/client/kubesession.go b/lib/client/kubesession.go index 0e260e286dae0..cd9050080ad91 100644 --- a/lib/client/kubesession.go +++ b/lib/client/kubesession.go @@ -28,6 +28,8 @@ import ( "github.com/gravitational/trace" "k8s.io/client-go/tools/remotecommand" + "github.com/gravitational/teleport/api/client" + "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/client/terminal" "github.com/gravitational/teleport/lib/kube/proxy/streamproto" @@ -45,6 +47,27 @@ type KubeSession struct { meta types.SessionTracker } +func kubeSessionNetDialer(ctx context.Context, tc *TeleportClient, kubeAddr string) client.ContextDialer { + dialOpts := []client.DialOption{ + client.WithInsecureSkipVerify(tc.InsecureSkipVerify), + } + + // Add options for ALPN connection upgrade if kube is served at Proxy web address. + if tc.WebProxyAddr == kubeAddr && tc.TLSRoutingConnUpgradeRequired { + dialOpts = append(dialOpts, + client.WithALPNConnUpgrade(tc.TLSRoutingConnUpgradeRequired), + client.WithALPNConnUpgradePing(true), + ) + } + + return client.NewDialer( + ctx, + defaults.DefaultIdleTimeout, + defaults.DefaultIOTimeout, + dialOpts..., + ) +} + // NewKubeSession joins a live kubernetes session. func NewKubeSession(ctx context.Context, tc *TeleportClient, meta types.SessionTracker, kubeAddr string, tlsServer string, mode types.SessionParticipantMode, tlsConfig *tls.Config) (*KubeSession, error) { ctx, cancel := context.WithCancel(ctx) @@ -55,6 +78,7 @@ func NewKubeSession(ctx context.Context, tc *TeleportClient, meta types.SessionT } dialer := &websocket.Dialer{ + NetDialContext: kubeSessionNetDialer(ctx, tc, kubeAddr).DialContext, TLSClientConfig: tlsConfig, } diff --git a/tool/tsh/kube.go b/tool/tsh/kube.go index e2a823dfc8e4f..5214cba95b4c2 100644 --- a/tool/tsh/kube.go +++ b/tool/tsh/kube.go @@ -591,12 +591,16 @@ func (c *kubeCredentialsCommand) run(cf *CLIConf) error { // - $TSH_HOME/$profile.yaml // - $TSH_HOME/keys/$PROXY/$USER-kube/$TELEPORT_CLUSTER/$KUBE_CLUSTER-x509.pem // - $TSH_HOME/keys/$PROXY/$USER - if kubeCert, privKey, err := client.LoadKeysToKubeFromStore( + if kubeCert, privKey, profile, err := client.LoadKeysToKubeFromStore( cf.HomePath, cf.Proxy, c.teleportCluster, c.kubeCluster, ); err == nil { + if profile.TLSRoutingConnUpgradeRequired { + return trace.BadParameter("Cannot connect Kubernetes clients to Teleport Proxy directly. Please use `tsh proxy kube` instead.") + } + crt, _ := tlsca.ParseCertificatePEM(kubeCert) if crt != nil && time.Until(crt.NotAfter) > time.Minute { log.Debugf("Re-using existing TLS cert for Kubernetes cluster %q", c.kubeCluster) @@ -608,6 +612,9 @@ func (c *kubeCredentialsCommand) run(cf *CLIConf) error { if err != nil { return trace.Wrap(err) } + if tc.TLSRoutingConnUpgradeRequired { + return trace.BadParameter("Cannot connect Kubernetes clients to Teleport Proxy directly. Please use `tsh proxy kube` instead.") + } _, span := tc.Tracer.Start(cf.Context, "tsh.kubeCredentials/GetKey") // Try loading existing keys. @@ -1062,12 +1069,39 @@ func (c *kubeLoginCommand) run(cf *CLIConf) error { if err := updateKubeConfig(cf, tc, profileKubeconfigPath, c.overrideContextName); err != nil { return trace.Wrap(err) } + + if tc.TLSRoutingConnUpgradeRequired { + c.printLocalProxyMessage() + } else { + c.printMessage() + } + return nil +} + +func (c *kubeLoginCommand) printMessage() { if c.kubeCluster != "" { fmt.Printf("Logged into Kubernetes cluster %q. Try 'kubectl version' to test the connection.\n", c.kubeCluster) } else { fmt.Printf("Created kubeconfig with every Kubernetes cluster available. Select a context and try 'kubectl version' to test the connection.\n") } - return nil +} + +func (c *kubeLoginCommand) printLocalProxyMessage() { + if c.kubeCluster != "" { + fmt.Printf(`Logged into Kubernetes cluster %q. Start the local proxy for it: + + tsh proxy kube %v -p 8443 + +Use the kubeconfig provided by the local proxy, and try 'kubectl version' to test the connection. +`, c.kubeCluster, c.kubeCluster) + } else { + fmt.Printf(`Logged into all Kubernetes clusters available. Start the local proxy: + + tsh proxy kube -p 8443 + +Use the kubeconfig provided by the local proxy, select a context, and try 'kubectl version' to test the connection. +`) + } } func fetchKubeClusters(ctx context.Context, tc *client.TeleportClient) (teleportCluster string, kubeClusters []types.KubeCluster, err error) {