Skip to content

Commit

Permalink
Demonstrate usage of golden for tbot template generation tests. (#1…
Browse files Browse the repository at this point in the history
  • Loading branch information
strideynet committed Jun 1, 2022
1 parent 889ecb4 commit a33903a
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 5 deletions.
18 changes: 13 additions & 5 deletions tool/tbot/config/configtemplate_ssh_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ import (
// TemplateSSHClient contains parameters for the ssh_config config
// template
type TemplateSSHClient struct {
ProxyPort uint16 `yaml:"proxy_port"`
ProxyPort uint16 `yaml:"proxy_port"`
getSSHVersion func() (*semver.Version, error)
getExecutablePath func() (string, error)
}

// openSSHVersionRegex is a regex used to parse OpenSSH version strings.
Expand Down Expand Up @@ -99,8 +101,8 @@ func parseSSHVersion(versionString string) (*semver.Version, error) {
}, nil
}

// getSSHVersion attempts to query the system SSH for its current version.
func getSSHVersion() (*semver.Version, error) {
// getSystemSSHVersion attempts to query the system SSH for its current version.
func getSystemSSHVersion() (*semver.Version, error) {
var out bytes.Buffer

cmd := exec.Command("ssh", "-V")
Expand All @@ -118,6 +120,12 @@ func (c *TemplateSSHClient) CheckAndSetDefaults() error {
if c.ProxyPort != 0 {
log.Warn("ssh_client's proxy_port parameter is deprecated and will be removed in a future release.")
}
if c.getSSHVersion == nil {
c.getSSHVersion = getSystemSSHVersion
}
if c.getExecutablePath == nil {
c.getExecutablePath = os.Executable
}
return nil
}

Expand Down Expand Up @@ -208,7 +216,7 @@ func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI,

// Default to including the RSA deprecation workaround.
rsaWorkaround := true
version, err := getSSHVersion()
version, err := c.getSSHVersion()
if err != nil {
log.WithError(err).Debugf("Could not determine SSH version, will include RSA workaround.")
} else if version.LessThan(*openSSHMinVersionForRSAWorkaround) {
Expand All @@ -218,7 +226,7 @@ func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI,
log.Debugf("OpenSSH version %s will use workaround for RSA deprecation", version)
}

executablePath, err := os.Executable()
executablePath, err := c.getExecutablePath()
if err != nil {
return trace.Wrap(err)
}
Expand Down
134 changes: 134 additions & 0 deletions tool/tbot/config/configtemplate_ssh_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"bytes"
"context"
"os"
"path/filepath"
"testing"

"github.com/coreos/go-semver/semver"
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/lib/auth"
"github.com/gravitational/teleport/lib/fixtures"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/utils/golden"
"github.com/gravitational/teleport/tool/tbot/botfs"
"github.com/gravitational/teleport/tool/tbot/identity"
"github.com/stretchr/testify/require"
)

type templateSSHClientAuthMock struct {
auth.ClientI
clusterName string
t *testing.T
}

func (m *templateSSHClientAuthMock) GetClusterName(opts ...services.MarshalOption) (types.ClusterName, error) {
cn, err := types.NewClusterName(types.ClusterNameSpecV2{
ClusterName: m.clusterName,
ClusterID: "aa-bb-cc",
})
require.NoError(m.t, err)
return cn, nil
}

func (m *templateSSHClientAuthMock) Ping(ctx context.Context) (proto.PingResponse, error) {
require.NotNil(m.t, ctx)
return proto.PingResponse{
ProxyPublicAddr: "tele.blackmesa.gov:443",
}, nil
}

func (m *templateSSHClientAuthMock) GetCertAuthority(ctx context.Context, id types.CertAuthID, loadKeys bool, opts ...services.MarshalOption) (types.CertAuthority, error) {
require.NotNil(m.t, ctx)
require.Equal(m.t, types.CertAuthID{
Type: types.HostCA,
DomainName: m.clusterName,
}, id)
require.False(m.t, loadKeys)

ca, err := types.NewCertAuthority(types.CertAuthoritySpecV2{
Type: types.HostCA,
ClusterName: m.clusterName,
ActiveKeys: types.CAKeySet{
SSH: []*types.SSHKeyPair{
{
PrivateKey: []byte(fixtures.SSHCAPrivateKey),
PublicKey: []byte(fixtures.SSHCAPublicKey),
},
},
},
})
require.NoError(m.t, err)
return ca, nil
}

func TestTemplateSSHClient_Render(t *testing.T) {
dir := t.TempDir()
mockAuth := &templateSSHClientAuthMock{
t: t,
clusterName: "black-mesa",
}
template := TemplateSSHClient{
ProxyPort: 1337,
getSSHVersion: func() (*semver.Version, error) {
return openSSHMinVersionForRSAWorkaround, nil
},
getExecutablePath: func() (string, error) {
return "/path/to/tbot", nil
},
}
// ident is passed in, but not used.
var ident *identity.Identity
dest := &DestinationConfig{
DestinationMixin: DestinationMixin{
Directory: &DestinationDirectory{
Path: dir,
Symlinks: botfs.SymlinksInsecure,
ACLs: botfs.ACLOff,
},
},
}

err := template.Render(context.Background(), mockAuth, ident, dest)
require.NoError(t, err)

replaceTestDir := func(b []byte) []byte {
return bytes.ReplaceAll(b, []byte(dir), []byte("/test/dir"))
}

knownHostBytes, err := os.ReadFile(filepath.Join(dir, knownHostsName))
require.NoError(t, err)
knownHostBytes = replaceTestDir(knownHostBytes)
sshConfigBytes, err := os.ReadFile(filepath.Join(dir, sshConfigName))
require.NoError(t, err)
sshConfigBytes = replaceTestDir(sshConfigBytes)
if golden.ShouldSet() {
golden.SetNamed(t, "known_hosts", knownHostBytes)
golden.SetNamed(t, "ssh_config", sshConfigBytes)
}
require.Equal(
t, string(golden.GetNamed(t, "known_hosts")), string(knownHostBytes),
)
require.Equal(
t, string(golden.GetNamed(t, "ssh_config")), string(sshConfigBytes),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@cert-authority tele.blackmesa.gov,black-mesa,*.black-mesa ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@

# Begin generated Teleport configuration for tele.blackmesa.gov by tbot

# Common flags for all black-mesa hosts
Host *.black-mesa tele.blackmesa.gov
UserKnownHostsFile "/test/dir/known_hosts"
IdentityFile "/test/dir/key"
CertificateFile "/test/dir/sshcert"
HostKeyAlgorithms [email protected]
PubkeyAcceptedAlgorithms [email protected]

# Flags for all black-mesa hosts except the proxy
Host *.black-mesa !tele.blackmesa.gov
Port 3022
ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy=tele.blackmesa.gov ssh --cluster=black-mesa %r@%h:%p

# End generated Teleport configuration

0 comments on commit a33903a

Please sign in to comment.