Fix TLS Routing jumphost flow (#11282)

This commit is contained in:
Marek Smoliński
2022-03-28 12:31:05 +02:00
committed by GitHub
parent f00a4e2b66
commit 9e1b887d94
3 changed files with 130 additions and 19 deletions
+54 -12
View File
@@ -2268,14 +2268,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,
@@ -2289,26 +2287,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
}
return makeProxySSHClientDirect(tc, sshConfig)
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)
}
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) {
@@ -3270,8 +3308,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")
}
+62 -4
View File
@@ -55,10 +55,17 @@ 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
@@ -66,6 +73,7 @@ func TestTSHSSH(t *testing.T) {
}{
{"ssh root cluster access", testRootClusterSSHAccess},
{"ssh leaf cluster access", testLeafClusterSSHAccess},
{"ssh jump host access", testJumpHostSSHAccess},
}
for _, tc := range tests {
@@ -166,6 +174,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)
+14 -3
View File
@@ -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{
@@ -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)
@@ -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
}
@@ -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
@@ -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)