Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v8] Fix JumpHost TLSRouting flow when root cluster is offline (#13791) #13929

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions integration/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,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)
Expand All @@ -590,7 +590,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)
Expand Down
65 changes: 52 additions & 13 deletions lib/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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"
Expand Down Expand Up @@ -368,7 +369,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)
}
Expand Down Expand Up @@ -718,14 +731,19 @@ func (proxy *ProxyClient) loadTLS() (*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()
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),
},
Expand All @@ -737,20 +755,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) {
Expand Down
45 changes: 33 additions & 12 deletions tool/tsh/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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/profile"
"github.com/gravitational/teleport/api/types"
Expand Down Expand Up @@ -212,18 +212,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.
Expand Down