Skip to content

Commit

Permalink
[v12] Machine ID trusted cluster enhancements (#23390)
Browse files Browse the repository at this point in the history
* Add leaf clusters into generated ssh_config

* Update tests for new output

* Update machine ID docs
  • Loading branch information
strideynet committed Mar 21, 2023
1 parent e68af89 commit 852bbb9
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 34 deletions.
13 changes: 2 additions & 11 deletions docs/pages/machine-id/faq.mdx
Expand Up @@ -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.
Expand Down
31 changes: 23 additions & 8 deletions lib/tbot/config/bot_test.go
Expand Up @@ -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) {
Expand All @@ -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{
Expand All @@ -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{
{
Expand Down Expand Up @@ -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,
}
}

Expand Down
57 changes: 42 additions & 15 deletions lib/tbot/config/configtemplate_ssh_client.go
Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand Down
@@ -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
Expand Up @@ -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
@@ -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
Expand Up @@ -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

0 comments on commit 852bbb9

Please sign in to comment.