From 20b63e071e183c7d3569f341f134fd30a871df74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Smoli=C5=84ski?= Date: Tue, 28 Jun 2022 14:09:37 +0200 Subject: [PATCH] Fix JumpHost TLSRouting flow when root cluster is offline (#13791) --- integration/proxy_test.go | 8 +++--- lib/client/client.go | 54 ++++++++++++++++++++++++++++++++++----- tool/tsh/proxy_test.go | 45 +++++++++++++++++++++++--------- 3 files changed, 84 insertions(+), 23 deletions(-) diff --git a/integration/proxy_test.go b/integration/proxy_test.go index c3e2544659b2e..37443d240b1ff 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -28,15 +28,15 @@ import ( "time" "github.com/google/uuid" - "github.com/gravitational/teleport/api/breaker" - "github.com/gravitational/teleport/integration/helpers" "github.com/gravitational/trace" "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/gravitational/teleport/api/breaker" "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/integration/helpers" "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/defaults" @@ -601,7 +601,7 @@ func TestALPNProxyRootLeafAuthDial(t *testing.T) { require.NoError(t, err) // Dial root auth service. - rootAuthClient, err := proxyClient.ConnectToAuthServiceThroughALPNSNIProxy(ctx, "root.example.com") + rootAuthClient, err := proxyClient.ConnectToAuthServiceThroughALPNSNIProxy(ctx, "root.example.com", "") require.NoError(t, err) pr, err := rootAuthClient.Ping(ctx) require.NoError(t, err) @@ -610,7 +610,7 @@ func TestALPNProxyRootLeafAuthDial(t *testing.T) { require.NoError(t, err) // Dial leaf auth service. - leafAuthClient, err := proxyClient.ConnectToAuthServiceThroughALPNSNIProxy(ctx, "leaf.example.com") + leafAuthClient, err := proxyClient.ConnectToAuthServiceThroughALPNSNIProxy(ctx, "leaf.example.com", "") require.NoError(t, err) pr, err = leafAuthClient.Ping(ctx) require.NoError(t, err) diff --git a/lib/client/client.go b/lib/client/client.go index 4489b9200da4c..45e03770f33d9 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -34,6 +34,7 @@ import ( "github.com/gravitational/teleport/api/breaker" "github.com/gravitational/teleport/api/client" "github.com/gravitational/teleport/api/client/proto" + "github.com/gravitational/teleport/api/client/webclient" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/observability/tracing" tracessh "github.com/gravitational/teleport/api/observability/tracing/ssh" @@ -424,7 +425,19 @@ func (proxy *ProxyClient) IssueUserCertsWithMFA(ctx context.Context, params Reis } if params.RouteToCluster != rootClusterName { clt.Close() - clt, err = proxy.ConnectToCluster(ctx, rootClusterName) + rootClusterProxy := proxy + if jumpHost := proxy.teleportClient.JumpHosts; jumpHost != nil { + // In case of MFA connect to root teleport proxy instead of JumpHost to request + // MFA certificates. + proxy.teleportClient.JumpHosts = nil + rootClusterProxy, err = proxy.teleportClient.ConnectToProxy(ctx) + proxy.teleportClient.JumpHosts = jumpHost + if err != nil { + return nil, trace.Wrap(err) + } + defer rootClusterProxy.Close() + } + clt, err = rootClusterProxy.ConnectToCluster(ctx, rootClusterName) if err != nil { return nil, trace.Wrap(err) } @@ -1081,15 +1094,20 @@ func (proxy *ProxyClient) loadTLS(clusterName string) (*tls.Config, error) { // ConnectToAuthServiceThroughALPNSNIProxy uses ALPN proxy service to connect to remote/local auth // service and returns auth client. For routing purposes, TLS ServerName is set to destination auth service // cluster name with ALPN values set to teleport-auth protocol. -func (proxy *ProxyClient) ConnectToAuthServiceThroughALPNSNIProxy(ctx context.Context, clusterName string) (auth.ClientI, error) { +func (proxy *ProxyClient) ConnectToAuthServiceThroughALPNSNIProxy(ctx context.Context, clusterName, proxyAddr string) (auth.ClientI, error) { tlsConfig, err := proxy.loadTLS(clusterName) if err != nil { return nil, trace.Wrap(err) } + + if proxyAddr == "" { + proxyAddr = proxy.teleportClient.WebProxyAddr + } + tlsConfig.InsecureSkipVerify = proxy.teleportClient.InsecureSkipVerify clt, err := auth.NewClient(client.Config{ Context: ctx, - Addrs: []string{proxy.teleportClient.WebProxyAddr}, + Addrs: []string{proxyAddr}, Credentials: []client.Credentials{ client.LoadTLS(tlsConfig), }, @@ -1102,6 +1120,28 @@ func (proxy *ProxyClient) ConnectToAuthServiceThroughALPNSNIProxy(ctx context.Co return clt, nil } +func (proxy *ProxyClient) shouldDialWithTLSRouting(ctx context.Context) (string, bool) { + if len(proxy.teleportClient.JumpHosts) > 0 { + // Check if the provided JumpHost address is a Teleport Proxy. + // This is needed to distinguish if the JumpHost address from Teleport Proxy Web address + // or Teleport Proxy SSH address. + jumpHostAddr := proxy.teleportClient.JumpHosts[0].Addr.String() + resp, err := webclient.Find( + &webclient.Config{ + Context: ctx, + ProxyAddr: jumpHostAddr, + Insecure: proxy.teleportClient.InsecureSkipVerify, + }, + ) + if err != nil { + // HTTP ping call failed. The JumpHost address is not a Teleport proxy address + return "", false + } + return jumpHostAddr, resp.Proxy.TLSRoutingEnabled + } + return proxy.teleportClient.WebProxyAddr, proxy.teleportClient.TLSRoutingEnabled +} + // ConnectToCluster connects to the auth server of the given cluster via proxy. // It returns connected and authenticated auth server client func (proxy *ProxyClient) ConnectToCluster(ctx context.Context, clusterName string) (auth.ClientI, error) { @@ -1115,10 +1155,10 @@ func (proxy *ProxyClient) ConnectToCluster(ctx context.Context, clusterName stri ) defer span.End() - // If proxy supports multiplex listener mode dial root/leaf cluster auth service via ALPN Proxy - // directly without using SSH tunnels. - if proxy.teleportClient.TLSRoutingEnabled { - clt, err := proxy.ConnectToAuthServiceThroughALPNSNIProxy(ctx, clusterName) + if proxyAddr, ok := proxy.shouldDialWithTLSRouting(ctx); ok { + // If proxy supports multiplex listener mode dial root/leaf cluster auth service via ALPN Proxy + // directly without using SSH tunnels. + clt, err := proxy.ConnectToAuthServiceThroughALPNSNIProxy(ctx, clusterName, proxyAddr) if err != nil { return nil, trace.Wrap(err) } diff --git a/tool/tsh/proxy_test.go b/tool/tsh/proxy_test.go index 37c0178cc9689..b1a2608110487 100644 --- a/tool/tsh/proxy_test.go +++ b/tool/tsh/proxy_test.go @@ -29,11 +29,11 @@ import ( "testing" "time" - "github.com/gravitational/teleport" "github.com/gravitational/trace" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh/agent" + "github.com/gravitational/teleport" apidefaults "github.com/gravitational/teleport/api/defaults" "github.com/gravitational/teleport/api/types" apievents "github.com/gravitational/teleport/api/types/events" @@ -210,18 +210,39 @@ func testJumpHostSSHAccess(t *testing.T, s *suite) { }) require.NoError(t, err) - // Connect to leaf node though jump host set to proxy web port where TLS Routing is enabled. - err = Run(context.Background(), []string{ - "ssh", - "--insecure", - "-J", s.leaf.Config.Proxy.WebAddr.Addr, - s.leaf.Config.Hostname, - "echo", "hello", - }, func(cf *CLIConf) error { - cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user) - return nil + t.Run("root cluster online", func(t *testing.T) { + // Connect to leaf node though jump host set to proxy web port where TLS Routing is enabled. + err = Run(context.Background(), []string{ + "ssh", + "--insecure", + "-J", s.leaf.Config.Proxy.WebAddr.Addr, + s.leaf.Config.Hostname, + "echo", "hello", + }, func(cf *CLIConf) error { + cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user) + return nil + }) + require.NoError(t, err) + }) + + t.Run("root cluster offline", func(t *testing.T) { + // Terminate root cluster. + err = s.root.Close() + require.NoError(t, err) + + // Check JumpHost flow when root cluster is offline. + err = Run(context.Background(), []string{ + "ssh", + "--insecure", + "-J", s.leaf.Config.Proxy.WebAddr.Addr, + s.leaf.Config.Hostname, + "echo", "hello", + }, func(cf *CLIConf) error { + cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user) + return nil + }) + require.NoError(t, err) }) - require.NoError(t, err) } // TestProxySSHDial verifies "tsh proxy ssh" command.