mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
Fix TLS Routing jumphost flow (#11282)
This commit is contained in:
+54
-12
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user