Skip to content

Commit

Permalink
Fix TLS Routing jumphost flow (#11282)
Browse files Browse the repository at this point in the history
  • Loading branch information
smallinsky committed Mar 28, 2022
1 parent 7de7094 commit efd7142
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 19 deletions.
66 changes: 54 additions & 12 deletions lib/client/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2263,14 +2263,12 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err
HostKeyCallback: hostKeyCallback,
Auth: authMethods,
}
log.Infof("Connecting proxy=%v login=%q", sshProxyAddr, sshConfig.User)

sshClient, err := makeProxySSHClient(tc, sshConfig)
sshClient, err := makeProxySSHClient(ctx, tc, sshConfig)
if err != nil {
return nil, trace.Wrap(err)
}

log.Infof("Successful auth with proxy %v.", sshProxyAddr)
return &ProxyClient{
teleportClient: tc,
Client: sshClient,
Expand All @@ -2284,26 +2282,66 @@ func (tc *TeleportClient) connectToProxy(ctx context.Context) (*ProxyClient, err
}, nil
}

func makeProxySSHClient(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
if tc.Config.TLSRoutingEnabled {
return makeProxySSHClientWithTLSWrapper(tc, sshConfig)
// makeProxySSHClient creates an SSH client by following steps:
// 1) If the current proxy supports TLS Routing and JumpHost address was not provided use TLSWrapper.
// 2) Check JumpHost raw SSH port or Teleport proxy address.
// In case of proxy web address check if the proxy supports TLS Routing and connect to the proxy with TLSWrapper
// 3) Dial sshProxyAddr with raw SSH Dialer where sshProxyAddress is proxy ssh address or JumpHost address if
// JumpHost address was provided.
func makeProxySSHClient(ctx context.Context, tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
// Use TLS Routing dialer only if proxy support TLS Routing and JumpHost was not set.
if tc.Config.TLSRoutingEnabled && len(tc.JumpHosts) == 0 {
log.Infof("Connecting to proxy=%v login=%q using TLS Routing", tc.Config.WebProxyAddr, sshConfig.User)
c, err := makeProxySSHClientWithTLSWrapper(ctx, tc, sshConfig, tc.Config.WebProxyAddr)
if err != nil {
return nil, trace.Wrap(err)
}
log.Infof("Successful auth with proxy %v.", tc.Config.WebProxyAddr)
return c, nil
}

sshProxyAddr := tc.Config.SSHProxyAddr

// Handle situation where a Jump Host was set to proxy web address and Teleport supports TLS Routing.
if len(tc.JumpHosts) > 0 {
sshProxyAddr = tc.JumpHosts[0].Addr.Addr
// Check if JumpHost address is a proxy web address.
resp, err := webclient.Find(&webclient.Config{Context: ctx, ProxyAddr: sshProxyAddr, Insecure: tc.InsecureSkipVerify})
// If JumpHost address is a proxy web port and proxy supports TLSRouting dial proxy with TLSWrapper.
if err == nil && resp.Proxy.TLSRoutingEnabled {
log.Infof("Connecting to proxy=%v login=%q using TLS Routing JumpHost", sshProxyAddr, sshConfig.User)
c, err := makeProxySSHClientWithTLSWrapper(ctx, tc, sshConfig, sshProxyAddr)
if err != nil {
return nil, trace.Wrap(err)
}
log.Infof("Successful auth with proxy %v.", sshProxyAddr)
return c, nil
}
}

log.Infof("Connecting to proxy=%v login=%q", sshProxyAddr, sshConfig.User)
client, err := makeProxySSHClientDirect(tc, sshConfig, sshProxyAddr)
if err != nil {
return nil, trace.Wrap(err, "failed to authenticate with proxy %v", sshProxyAddr)
}
return makeProxySSHClientDirect(tc, sshConfig)
log.Infof("Successful auth with proxy %v.", sshProxyAddr)
return client, nil
}

func makeProxySSHClientDirect(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
func makeProxySSHClientDirect(tc *TeleportClient, sshConfig *ssh.ClientConfig, proxyAddr string) (*ssh.Client, error) {
dialer := proxy.DialerFromEnvironment(tc.Config.SSHProxyAddr)
return dialer.Dial("tcp", tc.Config.SSHProxyAddr, sshConfig)
return dialer.Dial("tcp", proxyAddr, sshConfig)
}

func makeProxySSHClientWithTLSWrapper(tc *TeleportClient, sshConfig *ssh.ClientConfig) (*ssh.Client, error) {
func makeProxySSHClientWithTLSWrapper(ctx context.Context, tc *TeleportClient, sshConfig *ssh.ClientConfig, proxyAddr string) (*ssh.Client, error) {
tlsConfig, err := tc.loadTLSConfig()
if err != nil {
return nil, trace.Wrap(err)
}

tlsConfig.NextProtos = []string{string(alpncommon.ProtocolProxySSH)}
dialer := proxy.DialerFromEnvironment(tc.Config.WebProxyAddr, proxy.WithALPNDialer(tlsConfig))
return dialer.Dial("tcp", tc.Config.WebProxyAddr, sshConfig)
return dialer.Dial("tcp", proxyAddr, sshConfig)
}

func (tc *TeleportClient) rootClusterName() (string, error) {
Expand Down Expand Up @@ -3176,8 +3214,12 @@ func (tc *TeleportClient) loadTLSConfig() (*tls.Config, error) {
if err != nil {
return nil, trace.Wrap(err)
}
clusters := []string{rootCluster}
if tc.SiteName != "" && rootCluster != tc.SiteName {
clusters = append(clusters, tc.SiteName)
}

tlsConfig, err := tlsKey.TeleportClientTLSConfig(nil, []string{rootCluster})
tlsConfig, err := tlsKey.TeleportClientTLSConfig(nil, clusters)
if err != nil {
return nil, trace.Wrap(err, "failed to generate client TLS config")
}
Expand Down
66 changes: 62 additions & 4 deletions tool/tsh/proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,25 @@ func TestTSHSSH(t *testing.T) {
os.RemoveAll(profile.FullProfilePath(""))
})

s := newTestSuite(t, withLeafCluster(), withRootConfigFunc(func(cfg *service.Config) {
cfg.Version = defaults.TeleportConfigVersionV2
cfg.Auth.NetworkingConfig.SetProxyListenerMode(types.ProxyListenerMode_Multiplex)
}))
s := newTestSuite(t,
withRootConfigFunc(func(cfg *service.Config) {
cfg.Version = defaults.TeleportConfigVersionV2
cfg.Auth.NetworkingConfig.SetProxyListenerMode(types.ProxyListenerMode_Multiplex)
}),
withLeafCluster(),
withLeafConfigFunc(func(cfg *service.Config) {
cfg.Version = defaults.TeleportConfigVersionV2
cfg.Proxy.SSHAddr.Addr = localListenerAddr()
}),
)

tests := []struct {
name string
fn func(t *testing.T, s *suite)
}{
{"ssh root cluster access", testRootClusterSSHAccess},
{"ssh leaf cluster access", testLeafClusterSSHAccess},
{"ssh jump host access", testJumpHostSSHAccess},
}

for _, tc := range tests {
Expand Down Expand Up @@ -167,6 +175,56 @@ func testLeafClusterSSHAccess(t *testing.T, s *suite) {
require.NoError(t, err)
}

func testJumpHostSSHAccess(t *testing.T, s *suite) {
err := Run([]string{
"login",
"--insecure",
"--auth", s.connector.GetName(),
"--proxy", s.root.Config.Proxy.WebAddr.String(),
s.root.Config.Auth.ClusterName.GetClusterName(),
}, func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user)
return nil
})
require.NoError(t, err)

err = Run([]string{
"login",
"--insecure",
s.leaf.Config.Auth.ClusterName.GetClusterName(),
}, func(cf *CLIConf) error {
cf.mockSSOLogin = mockSSOLogin(t, s.root.GetAuthServer(), s.user)
return nil
})
require.NoError(t, err)

// Connect to leaf node though jump host set to leaf proxy SSH port.
err = Run([]string{
"ssh",
"--insecure",
"-J", s.leaf.Config.Proxy.SSHAddr.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)

// 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)
}

// TestProxySSHDial verifies "tsh proxy ssh" command.
func TestProxySSHDial(t *testing.T) {
createAgent(t)
Expand Down
17 changes: 14 additions & 3 deletions tool/tsh/tsh_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (s *suite) setupRootCluster(t *testing.T, options testSuiteOptions) {
t.Cleanup(func() { require.NoError(t, s.root.Close()) })
}

func (s *suite) setupLeafCluster(t *testing.T) {
func (s *suite) setupLeafCluster(t *testing.T, options testSuiteOptions) {
fileConfig := &config.FileConfig{
Version: "v2",
Global: config.Global{
Expand Down Expand Up @@ -164,6 +164,9 @@ func (s *suite) setupLeafCluster(t *testing.T) {
})
require.NoError(t, err)
cfg.Auth.Resources = []types.Resource{sshLoginRole}
if options.leafConfigFunc != nil {
options.leafConfigFunc(cfg)
}
s.leaf = runTeleport(t, cfg)

_, err = s.leaf.GetAuthServer().UpsertTrustedCluster(s.leaf.ExitContext(), tc)
Expand All @@ -172,6 +175,7 @@ func (s *suite) setupLeafCluster(t *testing.T) {

type testSuiteOptions struct {
rootConfigFunc func(cfg *service.Config)
leafConfigFunc func(cfg *service.Config)
leafCluster bool
}

Expand All @@ -183,6 +187,12 @@ func withRootConfigFunc(fn func(cfg *service.Config)) testSuiteOptionFunc {
}
}

func withLeafConfigFunc(fn func(cfg *service.Config)) testSuiteOptionFunc {
return func(o *testSuiteOptions) {
o.leafConfigFunc = fn
}
}

func withLeafCluster() testSuiteOptionFunc {
return func(o *testSuiteOptions) {
o.leafCluster = true
Expand All @@ -197,8 +207,9 @@ func newTestSuite(t *testing.T, opts ...testSuiteOptionFunc) *suite {
s := &suite{}

s.setupRootCluster(t, options)
if options.leafCluster {
s.setupLeafCluster(t)

if options.leafCluster || options.leafConfigFunc != nil {
s.setupLeafCluster(t, options)
require.Eventually(t, func() bool {
rt, err := s.root.GetAuthServer().GetTunnelConnections(s.leaf.Config.Auth.ClusterName.GetClusterName())
require.NoError(t, err)
Expand Down

0 comments on commit efd7142

Please sign in to comment.