diff --git a/integration/proxy_test.go b/integration/proxy_test.go index 5dd78f503a12b..833c95ad7ce5b 100644 --- a/integration/proxy_test.go +++ b/integration/proxy_test.go @@ -590,7 +590,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) @@ -599,7 +599,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 eea2657721660..9722acbff434c 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -37,6 +37,7 @@ import ( "github.com/gravitational/teleport" "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/types" "github.com/gravitational/teleport/lib/auth" @@ -378,7 +379,19 @@ func (proxy *ProxyClient) IssueUserCertsWithMFA(ctx context.Context, params Reis } if params.RouteToCluster != rootClusterName { clt.Close() - clt, err = proxy.ConnectToCluster(ctx, rootClusterName, true) + 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, false) if err != nil { return nil, trace.Wrap(err) } @@ -865,14 +878,19 @@ func (proxy *ProxyClient) loadTLS(clusterName string) (*tls.Config, error) { // ConnectToAuthServiceThroughALPNSNIProxy uses ALPN proxy service to connect to remove/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{ - Addrs: []string{proxy.teleportClient.WebProxyAddr}, + Addrs: []string{proxyAddr}, Credentials: []client.Credentials{ client.LoadTLS(tlsConfig), }, @@ -884,20 +902,41 @@ 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 -// -// if 'quiet' is set to true, no errors will be printed to stdout, otherwise -// any connection errors are visible to a user. func (proxy *ProxyClient) ConnectToCluster(ctx context.Context, clusterName string, quiet bool) (auth.ClientI, error) { - // 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 err != nil { - return nil, trace.Wrap(err) + if proxyAddr, ok := proxy.shouldDialWithTLSRouting(ctx); ok { + if proxy.teleportClient.TLSRoutingEnabled { + // 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) + } + return clt, nil } - return clt, nil } dialer := client.ContextDialerFunc(func(ctx context.Context, network, _ string) (net.Conn, error) { diff --git a/tool/tsh/proxy_test.go b/tool/tsh/proxy_test.go index 3b256cef8286a..fa5b7f450415c 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([]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([]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([]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.