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

[v9] Fix TLS Routing jumphost flow #11496

Merged
merged 2 commits into from
Mar 28, 2022
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
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