diff --git a/docs/pages/machine-id/faq.mdx b/docs/pages/machine-id/faq.mdx index 84cf8711b9e10..2a85843c9ea6d 100644 --- a/docs/pages/machine-id/faq.mdx +++ b/docs/pages/machine-id/faq.mdx @@ -18,17 +18,8 @@ following situations: ## Can Machine ID be used with Trusted Clusters ? -Partially. We currently support SSH access to leaf clusters when using `tsh` to -make the connection. To do this, you will need to provide `tsh` with the generated identity file -output in the destination directory to `tsh`, for example: - -```code -$ tsh -i /opt/machine-id/identity --proxy root-cluster.example.com ssh --cluster leaf-cluster.example.com root@node.leaf-cluster.example.com -``` - -We hope to introduce support for generating a `ssh_config` that is compatible -with leaf clusters for use with OpenSSH. You can track support for this on the -[GitHub Machine ID Trusted Cluster Support issue](https://github.com/gravitational/teleport/issues/13792). +From Teleport 12.2, Trusted Cluster support for SSH Access has been included in +Machine ID. We currently do not support Application Access, Database Access or Kubernetes Access to resources in leaf clusters. diff --git a/lib/tbot/config/bot_test.go b/lib/tbot/config/bot_test.go index b3be89a5fb1c9..87c2d92d7bd8c 100644 --- a/lib/tbot/config/bot_test.go +++ b/lib/tbot/config/bot_test.go @@ -45,15 +45,19 @@ const ( // mockClusterName is the cluster name for the mock auth client, used in // tests mockClusterName = "tele.blackmesa.gov" + // mockRemoteClusterName is the remote cluster name used for the mock auth + // client + mockRemoteClusterName = "tele.aperture.labs" ) // mockAuth is a minimal fake auth client, used in tests type mockAuth struct { auth.ClientI - clusterName string - proxyAddr string - t *testing.T + clusterName string + remoteClusterName string + proxyAddr string + t *testing.T } func (m *mockAuth) GetDomainName(ctx context.Context) (string, error) { @@ -69,6 +73,12 @@ func (m *mockAuth) GetClusterName(opts ...services.MarshalOption) (types.Cluster return cn, nil } +func (m *mockAuth) GetRemoteClusters(opts ...services.MarshalOption) ([]types.RemoteCluster, error) { + rc, err := types.NewRemoteCluster(m.remoteClusterName) + require.NoError(m.t, err) + return []types.RemoteCluster{rc}, nil +} + func (m *mockAuth) Ping(ctx context.Context) (proto.PingResponse, error) { require.NotNil(m.t, ctx) return proto.PingResponse{ @@ -78,13 +88,17 @@ func (m *mockAuth) Ping(ctx context.Context) (proto.PingResponse, error) { func (m *mockAuth) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) { require.NotNil(m.t, ctx) - require.Equal(m.t, m.clusterName, id.DomainName) + require.Contains( + m.t, + []string{m.clusterName, m.remoteClusterName}, + id.DomainName, + ) require.False(m.t, loadKeys) ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{ // Pretend to be the correct type. Type: id.Type, - ClusterName: m.clusterName, + ClusterName: id.DomainName, ActiveKeys: types.CAKeySet{ TLS: []*types.TLSKeyPair{ { @@ -126,9 +140,10 @@ func (m *mockAuth) GetCertAuthorities(ctx context.Context, caType types.CertAuth func newMockAuth(t *testing.T) *mockAuth { return &mockAuth{ - t: t, - clusterName: mockClusterName, - proxyAddr: mockProxyAddr, + t: t, + clusterName: mockClusterName, + proxyAddr: mockProxyAddr, + remoteClusterName: mockRemoteClusterName, } } diff --git a/lib/tbot/config/configtemplate_ssh_client.go b/lib/tbot/config/configtemplate_ssh_client.go index 2cf12a9d7cfa5..822fe8df710c7 100644 --- a/lib/tbot/config/configtemplate_ssh_client.go +++ b/lib/tbot/config/configtemplate_ssh_client.go @@ -91,18 +91,31 @@ func (c *TemplateSSHClient) Describe(destination bot.Destination) []FileDescript // using non-filesystem backends. var sshConfigUnsupportedWarning sync.Once -func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Identity, destination *DestinationConfig) error { - dest, err := destination.GetDestination() +func getClusterNames(client auth.ClientI) ([]string, error) { + cn, err := client.GetClusterName() if err != nil { - return trace.Wrap(err) + return nil, trace.Wrap(err) } + allClusterNames := []string{cn.GetClusterName()} - authClient := bot.Client() - clusterName, err := authClient.GetClusterName() + leafClusters, err := client.GetRemoteClusters() + if err != nil { + return nil, trace.Wrap(err) + } + for _, lc := range leafClusters { + allClusterNames = append(allClusterNames, lc.GetName()) + } + + return allClusterNames, nil +} + +func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Identity, destination *DestinationConfig) error { + dest, err := destination.GetDestination() if err != nil { return trace.Wrap(err) } + authClient := bot.Client() ping, err := bot.AuthPing(ctx) if err != nil { return trace.Wrap(err) @@ -113,6 +126,11 @@ func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Ide return trace.BadParameter("proxy %+v has no usable public address: %v", ping.ProxyPublicAddr, err) } + clusterNames, err := getClusterNames(authClient) + if err != nil { + return trace.Wrap(err) + } + // Backend note: Prefer to use absolute paths for filesystem backends. // If the backend is something else, use "". ssh_config will generate with // paths relative to the destination. This doesn't work with ssh in @@ -130,7 +148,12 @@ func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Ide // We'll write known_hosts regardless of destination type, it's still // useful alongside a manually-written ssh_config. - knownHosts, err := fetchKnownHosts(ctx, authClient, clusterName.GetClusterName(), proxyHost) + knownHosts, err := fetchKnownHosts( + ctx, + authClient, + clusterNames, + proxyHost, + ) if err != nil { return trace.Wrap(err) } @@ -164,7 +187,7 @@ func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Ide sshConf := openssh.NewSSHConfig(c.getSSHVersion, log) if err := sshConf.GetSSHConfig(&sshConfigBuilder, &openssh.SSHConfigParameters{ AppName: openssh.TbotApp, - ClusterNames: []string{clusterName.GetClusterName()}, + ClusterNames: clusterNames, KnownHostsPath: knownHostsPath, IdentityFilePath: identityFilePath, CertificateFilePath: certificateFilePath, @@ -182,17 +205,21 @@ func (c *TemplateSSHClient) Render(ctx context.Context, bot Bot, _ *identity.Ide return nil } -func fetchKnownHosts(ctx context.Context, client auth.ClientI, clusterName, proxyHosts string) (string, error) { - ca, err := client.GetCertAuthority(ctx, types.CertAuthID{ - Type: types.HostCA, - DomainName: clusterName, - }, false) - if err != nil { - return "", trace.Wrap(err) +func fetchKnownHosts(ctx context.Context, client auth.ClientI, clusterNames []string, proxyHosts string) (string, error) { + certAuthorities := make([]types.CertAuthority, 0, len(clusterNames)) + for _, cn := range clusterNames { + ca, err := client.GetCertAuthority(ctx, types.CertAuthID{ + Type: types.HostCA, + DomainName: cn, + }, false) + if err != nil { + return "", trace.Wrap(err) + } + certAuthorities = append(certAuthorities, ca) } var sb strings.Builder - for _, auth := range auth.AuthoritiesToTrustedCerts([]types.CertAuthority{ca}) { + for _, auth := range auth.AuthoritiesToTrustedCerts(certAuthorities) { pubKeys, err := auth.SSHCertPublicKeys() if err != nil { return "", trace.Wrap(err) diff --git a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/known_hosts.golden b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/known_hosts.golden index ce2267be63210..8b414dc2bc188 100644 --- a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/known_hosts.golden +++ b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/known_hosts.golden @@ -1,2 +1,4 @@ @cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host @cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host +@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host +@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host diff --git a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/ssh_config.golden b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/ssh_config.golden index 87eedbc20e11c..bf9342d2f363c 100644 --- a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/ssh_config.golden +++ b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/latest_OpenSSH/ssh_config.golden @@ -11,5 +11,16 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov Host *.tele.blackmesa.gov !tele.blackmesa.gov Port 3022 ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy=tele.blackmesa.gov ssh --cluster=tele.blackmesa.gov %r@%h:%p +# Common flags for all tele.aperture.labs hosts +Host *.tele.aperture.labs tele.blackmesa.gov + UserKnownHostsFile "/test/dir/known_hosts" + IdentityFile "/test/dir/key" + CertificateFile "/test/dir/key-cert.pub" + HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com + +# Flags for all tele.aperture.labs hosts except the proxy +Host *.tele.aperture.labs !tele.blackmesa.gov + Port 3022 + ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy=tele.blackmesa.gov ssh --cluster=tele.aperture.labs %r@%h:%p # End generated Teleport configuration diff --git a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/known_hosts.golden b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/known_hosts.golden index ce2267be63210..8b414dc2bc188 100644 --- a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/known_hosts.golden +++ b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/known_hosts.golden @@ -1,2 +1,4 @@ @cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host @cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host +@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host +@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host diff --git a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/ssh_config.golden b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/ssh_config.golden index 347c718781f85..60c651ee0676f 100644 --- a/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/ssh_config.golden +++ b/lib/tbot/config/testdata/TestTemplateSSHClient_Render/legacy_OpenSSH/ssh_config.golden @@ -11,5 +11,16 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov Host *.tele.blackmesa.gov !tele.blackmesa.gov Port 3022 ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy=tele.blackmesa.gov ssh --cluster=tele.blackmesa.gov %r@%h:%p +# Common flags for all tele.aperture.labs hosts +Host *.tele.aperture.labs tele.blackmesa.gov + UserKnownHostsFile "/test/dir/known_hosts" + IdentityFile "/test/dir/key" + CertificateFile "/test/dir/key-cert.pub" + HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com + +# Flags for all tele.aperture.labs hosts except the proxy +Host *.tele.aperture.labs !tele.blackmesa.gov + Port 3022 + ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy=tele.blackmesa.gov ssh --cluster=tele.aperture.labs %r@%h:%p # End generated Teleport configuration