From 9bdac2c82a698957f2de7fdfe1d844fc60fb8852 Mon Sep 17 00:00:00 2001 From: Sasha Klizhentas Date: Sun, 1 Nov 2020 18:23:11 -0800 Subject: [PATCH] Adds support for loading tctl creds from ~/.tsh profile This commit fixes #4439 --- e | 2 +- lib/client/interfaces.go | 21 ++ lib/client/keyagent.go | 34 ++++ lib/client/keystore_test.go | 379 ++++++++++++++++++++++-------------- tool/tctl/common/tctl.go | 79 +++++--- tool/tctl/main.go | 2 +- 6 files changed, 344 insertions(+), 173 deletions(-) diff --git a/e b/e index 441c6b110e13d..d0ea82ef2ce9a 160000 --- a/e +++ b/e @@ -1 +1 @@ -Subproject commit 441c6b110e13d71a34badd3d2e8cf774c19536d6 +Subproject commit d0ea82ef2ce9a35f0263d7ea240ef77d9ae8b57a diff --git a/lib/client/interfaces.go b/lib/client/interfaces.go index 3f112c3e18c63..5ee4184f63172 100644 --- a/lib/client/interfaces.go +++ b/lib/client/interfaces.go @@ -362,3 +362,24 @@ func (k *Key) HostKeyCallback() (ssh.HostKeyCallback, error) { return trace.AccessDenied("host %v is untrusted or Teleport CA has been rotated; try getting new credentials by logging in again ('tsh login') or re-exporting the identity file ('tctl auth sign' or 'tsh login -o'), depending on how you got them initially", host) }, nil } + +// ProxyClientSSHConfig returns an ssh.ClientConfig with SSH credentials from this +// Key and HostKeyCallback matching SSH CAs in the Key. +// +// The config is set up to authenticate to proxy with the first +// available principal and trust local SSH CAs without asking +// for public keys. +// +func ProxyClientSSHConfig(k *Key, keyStore LocalKeyStore) (*ssh.ClientConfig, error) { + sshConfig, err := k.ClientSSHConfig() + if err != nil { + return nil, trace.Wrap(err) + } + principals, err := k.CertPrincipals() + if err != nil { + return nil, trace.Wrap(err) + } + sshConfig.User = principals[0] + sshConfig.HostKeyCallback = NewKeyStoreCertChecker(keyStore) + return sshConfig, nil +} diff --git a/lib/client/keyagent.go b/lib/client/keyagent.go index 77c61d94baee4..320ba8e96a045 100644 --- a/lib/client/keyagent.go +++ b/lib/client/keyagent.go @@ -66,6 +66,40 @@ type LocalKeyAgent struct { proxyHost string } +// NewKeyStoreCertChecker returns a new certificate checker +// using trusted certs from key store +func NewKeyStoreCertChecker(keyStore LocalKeyStore) ssh.HostKeyCallback { + // CheckHostSignature checks if the given host key was signed by a Teleport + // certificate authority (CA) or a host certificate the user has seen before. + return func(addr string, remote net.Addr, key ssh.PublicKey) error { + certChecker := utils.CertChecker{ + CertChecker: ssh.CertChecker{ + IsHostAuthority: func(key ssh.PublicKey, addr string) bool { + keys, err := keyStore.GetKnownHostKeys("") + if err != nil { + log.Errorf("Unable to fetch certificate authorities: %v.", err) + return false + } + for i := range keys { + if sshutils.KeysEqual(key, keys[i]) { + return true + } + } + return false + }, + }, + FIPS: isFIPS(), + } + err := certChecker.CheckHostKey(addr, remote, key) + if err != nil { + log.Debugf("Host validation failed: %v.", err) + return trace.Wrap(err) + } + log.Debugf("Validated host %v.", addr) + return nil + } +} + // NewLocalAgent reads all Teleport certificates from disk (using FSLocalKeyStore), // creates a LocalKeyAgent, loads all certificates into it, and returns the agent. func NewLocalAgent(keyDir, proxyHost, username string, useLocalSSHAgent bool) (a *LocalKeyAgent, err error) { diff --git a/lib/client/keystore_test.go b/lib/client/keystore_test.go index bac99b82335d0..7a02a5cfd5a26 100644 --- a/lib/client/keystore_test.go +++ b/lib/client/keystore_test.go @@ -1,5 +1,5 @@ /* -Copyright 2016 Gravitational, Inc. +Copyright 2016-2020 Gravitational, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -17,12 +17,16 @@ limitations under the License. package client import ( + "context" "crypto/rsa" "crypto/x509/pkix" "fmt" + "io/ioutil" "os" + "testing" "time" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/defaults" @@ -33,184 +37,281 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" + "go.uber.org/atomic" "golang.org/x/crypto/ssh" - "gopkg.in/check.v1" ) -type KeyStoreTestSuite struct { - storeDir string - store *FSLocalKeyStore - keygen *testauthority.Keygen - tlsCA *tlsca.CertAuthority - tlsCACert auth.TrustedCerts -} - -var _ = fmt.Printf -var _ = check.Suite(&KeyStoreTestSuite{}) - -func newSelfSignedCA(privateKey []byte) (*tlsca.CertAuthority, auth.TrustedCerts, error) { - rsaKey, err := ssh.ParseRawPrivateKey(privateKey) - if err != nil { - return nil, auth.TrustedCerts{}, trace.Wrap(err) - } - key, cert, err := tlsca.GenerateSelfSignedCAWithPrivateKey(rsaKey.(*rsa.PrivateKey), pkix.Name{ - CommonName: "localhost", - Organization: []string{"localhost"}, - }, nil, defaults.CATTL) - if err != nil { - return nil, auth.TrustedCerts{}, trace.Wrap(err) - } - ca, err := tlsca.New(cert, key) - if err != nil { - return nil, auth.TrustedCerts{}, trace.Wrap(err) - } - return ca, auth.TrustedCerts{TLSCertificates: [][]byte{cert}}, nil -} - -func (s *KeyStoreTestSuite) SetUpSuite(c *check.C) { - utils.InitLoggerForTests() - var err error - s.keygen = testauthority.New() - s.storeDir = c.MkDir() - s.store, err = NewFSLocalKeyStore(s.storeDir) - c.Assert(err, check.IsNil) - c.Assert(s.store, check.NotNil) - c.Assert(utils.IsDir(s.store.KeyDir), check.Equals, true) - - s.tlsCA, s.tlsCACert, err = newSelfSignedCA(CAPriv) - c.Assert(err, check.IsNil) -} - -func (s *KeyStoreTestSuite) TearDownSuite(c *check.C) { - os.RemoveAll(s.storeDir) -} - -func (s *KeyStoreTestSuite) SetUpTest(c *check.C) { - os.RemoveAll(s.store.KeyDir) -} +func TestListKeys(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() -func (s *KeyStoreTestSuite) TestListKeys(c *check.C) { const keyNum = 5 // add 5 keys for "bob" keys := make([]Key, keyNum) for i := 0; i < keyNum; i++ { - key := s.makeSignedKey(c, false) + key := s.makeSignedKey(t, false) host := fmt.Sprintf("host-%v", i) - c.Assert(s.addKey(host, "bob", key), check.IsNil) + require.NoError(t, s.addKey(host, "bob", key)) key.ProxyHost = host keys[i] = *key } // add 1 key for "sam" - samKey := s.makeSignedKey(c, false) - c.Assert(s.addKey("sam.host", "sam", samKey), check.IsNil) + samKey := s.makeSignedKey(t, false) + require.NoError(t, s.addKey("sam.host", "sam", samKey)) // read all bob keys: for i := 0; i < keyNum; i++ { host := fmt.Sprintf("host-%v", i) keys2, err := s.store.GetKey(host, "bob") - c.Assert(err, check.IsNil) - c.Assert(*keys2, check.DeepEquals, keys[i]) + require.NoError(t, err) + require.Equal(t, *keys2, keys[i]) } // read sam's key and make sure it's the same: skey, err := s.store.GetKey("sam.host", "sam") - c.Assert(err, check.IsNil) - c.Assert(samKey.Cert, check.DeepEquals, skey.Cert) - c.Assert(samKey.Pub, check.DeepEquals, skey.Pub) + require.NoError(t, err) + require.Equal(t, samKey.Cert, skey.Cert) + require.Equal(t, samKey.Pub, skey.Pub) } -func (s *KeyStoreTestSuite) TestKeyCRUD(c *check.C) { - key := s.makeSignedKey(c, false) +func TestKeyCRUD(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + + key := s.makeSignedKey(t, false) // add key: err := s.addKey("host.a", "bob", key) - c.Assert(err, check.IsNil) + require.NoError(t, err) // load back and compare: keyCopy, err := s.store.GetKey("host.a", "bob") - c.Assert(err, check.IsNil) - c.Assert(key.EqualsTo(keyCopy), check.Equals, true) + require.NoError(t, err) + require.True(t, key.EqualsTo(keyCopy)) // Delete & verify that it's gone err = s.store.DeleteKey("host.a", "bob") - c.Assert(err, check.IsNil) + require.NoError(t, err) _, err = s.store.GetKey("host.a", "bob") - c.Assert(err, check.NotNil) - c.Assert(trace.IsNotFound(err), check.Equals, true) + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) // Delete non-existing err = s.store.DeleteKey("non-existing-host", "non-existing-user") - c.Assert(err, check.NotNil) - c.Assert(trace.IsNotFound(err), check.Equals, true) + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) } -func (s *KeyStoreTestSuite) TestDeleteAll(c *check.C) { - key := s.makeSignedKey(c, false) +func TestDeleteAll(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + + key := s.makeSignedKey(t, false) // add keys err := s.addKey("proxy.example.com", "foo", key) - c.Assert(err, check.IsNil) + require.NoError(t, err) err = s.addKey("proxy.example.com", "bar", key) - c.Assert(err, check.IsNil) + require.NoError(t, err) // check keys exist _, err = s.store.GetKey("proxy.example.com", "foo") - c.Assert(err, check.IsNil) + require.NoError(t, err) _, err = s.store.GetKey("proxy.example.com", "bar") - c.Assert(err, check.IsNil) + require.NoError(t, err) // delete all keys err = s.store.DeleteKeys() - c.Assert(err, check.IsNil) + require.NoError(t, err) - // verify keys gone + // verify keys are gone _, err = s.store.GetKey("proxy.example.com", "foo") - c.Assert(err, check.NotNil) + require.True(t, trace.IsNotFound(err)) _, err = s.store.GetKey("proxy.example.com", "bar") - c.Assert(err, check.NotNil) + require.Error(t, err) } -func (s *KeyStoreTestSuite) TestKnownHosts(c *check.C) { +func TestKnownHosts(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + err := os.MkdirAll(s.store.KeyDir, 0777) - c.Assert(err, check.IsNil) + require.NoError(t, err) pub, _, _, _, err := ssh.ParseAuthorizedKey(CAPub) - c.Assert(err, check.IsNil) + require.NoError(t, err) _, p2, _ := s.keygen.GenerateKeyPair("") pub2, _, _, _, _ := ssh.ParseAuthorizedKey(p2) err = s.store.AddKnownHostKeys("example.com", []ssh.PublicKey{pub}) - c.Assert(err, check.IsNil) + require.NoError(t, err) err = s.store.AddKnownHostKeys("example.com", []ssh.PublicKey{pub2}) - c.Assert(err, check.IsNil) + require.NoError(t, err) err = s.store.AddKnownHostKeys("example.org", []ssh.PublicKey{pub2}) - c.Assert(err, check.IsNil) + require.NoError(t, err) keys, err := s.store.GetKnownHostKeys("") - c.Assert(err, check.IsNil) - c.Assert(keys, check.HasLen, 3) - c.Assert(keys, check.DeepEquals, []ssh.PublicKey{pub, pub2, pub2}) + require.NoError(t, err) + require.Len(t, keys, 3) + require.Equal(t, keys, []ssh.PublicKey{pub, pub2, pub2}) // check against dupes: before, _ := s.store.GetKnownHostKeys("") err = s.store.AddKnownHostKeys("example.org", []ssh.PublicKey{pub2}) - c.Assert(err, check.IsNil) + require.NoError(t, err) err = s.store.AddKnownHostKeys("example.org", []ssh.PublicKey{pub2}) - c.Assert(err, check.IsNil) + require.NoError(t, err) after, _ := s.store.GetKnownHostKeys("") - c.Assert(len(before), check.Equals, len(after)) + require.Equal(t, len(before), len(after)) // check by hostname: keys, _ = s.store.GetKnownHostKeys("badhost") - c.Assert(len(keys), check.Equals, 0) + require.Equal(t, len(keys), 0) keys, _ = s.store.GetKnownHostKeys("example.org") - c.Assert(len(keys), check.Equals, 1) - c.Assert(sshutils.KeysEqual(keys[0], pub2), check.Equals, true) + require.Equal(t, len(keys), 1) + require.True(t, sshutils.KeysEqual(keys[0], pub2)) +} + +// TestCheckKey makes sure Teleport clients can load non-RSA algorithms in +// normal operating mode. +func TestCheckKey(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + + key := s.makeSignedKey(t, false) + + // Swap out the key with a ECDSA SSH key. + ellipticCertificate, _, err := utils.CreateEllipticCertificate("foo", ssh.UserCert) + require.NoError(t, err) + key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate) + + err = s.addKey("host.a", "bob", key) + require.NoError(t, err) + + _, err = s.store.GetKey("host.a", "bob") + require.NoError(t, err) +} + +// TestProxySSHConfig tests proxy client SSH config function +// that generates SSH client configuration for proxy tunnel connections +func TestProxySSHConfig(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + + key := s.makeSignedKey(t, false) + + caPub, _, _, _, err := ssh.ParseAuthorizedKey(CAPub) + require.NoError(t, err) + + err = s.store.AddKnownHostKeys("127.0.0.1", []ssh.PublicKey{caPub}) + require.NoError(t, err) + + clientConfig, err := ProxyClientSSHConfig(key, s.store) + require.NoError(t, err) + + called := atomic.NewInt32(0) + handler := sshutils.NewChanHandlerFunc(func(_ context.Context, _ *sshutils.ConnectionContext, nch ssh.NewChannel) { + called.Inc() + nch.Reject(ssh.Prohibited, "nothing to see here") + }) + + hostPriv, hostPub, err := s.keygen.GenerateKeyPair("") + require.NoError(t, err) + + hostCert, err := s.keygen.GenerateHostCert(services.HostCertParams{ + PrivateCASigningKey: CAPriv, + CASigningAlg: defaults.CASignatureAlgorithm, + PublicHostKey: hostPub, + HostID: "127.0.0.1", + NodeName: "127.0.0.1", + ClusterName: "host-cluster-name", + Roles: teleport.Roles{teleport.RoleNode}, + }) + require.NoError(t, err) + + hostSigner, err := sshutils.NewSigner(hostPriv, hostCert) + require.NoError(t, err) + + srv, err := sshutils.NewServer( + "test", + utils.NetAddr{AddrNetwork: "tcp", Addr: "127.0.0.1:0"}, + handler, + []ssh.Signer{hostSigner}, + sshutils.AuthMethods{ + PublicKey: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + certChecker := utils.CertChecker{ + CertChecker: ssh.CertChecker{ + IsUserAuthority: func(cert ssh.PublicKey) bool { + // Makes sure that user presented key signed by or with trusted authority. + return sshutils.KeysEqual(caPub, cert) + }, + }, + } + return certChecker.Authenticate(conn, key) + }, + }, + ) + require.NoError(t, err) + require.NoError(t, srv.Start()) + defer srv.Close() + + clt, err := ssh.Dial("tcp", srv.Addr(), clientConfig) + require.NoError(t, err) + defer clt.Close() + + // Call new session to initiate opening new channel. This should get + // rejected and fail. + _, err = clt.NewSession() + require.Error(t, err) + require.Equal(t, int(called.Load()), 1) +} + +// TestCheckKeyFIPS makes sure Teleport clients don't load invalid +// certificates while in FIPS mode. +func TestCheckKeyFIPS(t *testing.T) { + s, cleanup := newTest(t) + defer cleanup() + + // This test only runs in FIPS mode. + if !isFIPS() { + t.Skip("This test only runs in FIPS mode.") + } + + key := s.makeSignedKey(t, false) + + // Swap out the key with a ECDSA SSH key. + ellipticCertificate, _, err := utils.CreateEllipticCertificate("foo", ssh.UserCert) + require.NoError(t, err) + key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate) + + err = s.addKey("host.a", "bob", key) + require.NoError(t, err) + + // Should return trace.BadParameter error because only RSA keys are supported. + _, err = s.store.GetKey("host.a", "bob") + require.True(t, trace.IsBadParameter(err)) +} + +type keyStoreTest struct { + storeDir string + store *FSLocalKeyStore + keygen *testauthority.Keygen + tlsCA *tlsca.CertAuthority + tlsCACert auth.TrustedCerts } -// makeSIgnedKey helper returns all 3 components of a user key (signed by CAPriv key) -func (s *KeyStoreTestSuite) makeSignedKey(c *check.C, makeExpired bool) *Key { +func (s *keyStoreTest) addKey(host, user string, key *Key) error { + if err := s.store.AddKey(host, user, key); err != nil { + return err + } + // Also write the trusted CA certs for the host. + return s.store.SaveCerts(host, []auth.TrustedCerts{s.tlsCACert}) +} + +// makeSignedKey helper returns all 3 components of a user key (signed by CAPriv key) +func (s *keyStoreTest) makeSignedKey(t *testing.T, makeExpired bool) *Key { var ( err error priv, pub, cert []byte @@ -225,20 +326,20 @@ func (s *KeyStoreTestSuite) makeSignedKey(c *check.C, makeExpired bool) *Key { // reuse the same RSA keys for SSH and TLS keys cryptoPubKey, err := sshutils.CryptoPublicKey(pub) - c.Assert(err, check.IsNil) + require.NoError(t, err) clock := clockwork.NewRealClock() identity := tlsca.Identity{ Username: username, } subject, err := identity.Subject() - c.Assert(err, check.IsNil) + require.NoError(t, err) tlsCert, err := s.tlsCA.GenerateCertificate(tlsca.CertificateRequest{ Clock: clock, PublicKey: cryptoPubKey, Subject: subject, NotAfter: clock.Now().UTC().Add(ttl), }) - c.Assert(err, check.IsNil) + require.NoError(t, err) cert, err = s.keygen.GenerateUserCert(services.UserCertParams{ PrivateCASigningKey: CAPriv, @@ -250,7 +351,7 @@ func (s *KeyStoreTestSuite) makeSignedKey(c *check.C, makeExpired bool) *Key { PermitAgentForwarding: false, PermitPortForwarding: true, }) - c.Assert(err, check.IsNil) + require.NoError(t, err) return &Key{ Priv: priv, Pub: pub, @@ -260,51 +361,47 @@ func (s *KeyStoreTestSuite) makeSignedKey(c *check.C, makeExpired bool) *Key { } } -// TestCheckKey make sure Teleport clients can load non-RSA algorithms in -// normal operating mode. -func (s *KeyStoreTestSuite) TestCheckKey(c *check.C) { - key := s.makeSignedKey(c, false) - - // Swap out the key with a ECDSA SSH key. - ellipticCertificate, _, err := utils.CreateEllipticCertificate("foo", ssh.UserCert) - c.Assert(err, check.IsNil) - key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate) - - err = s.addKey("host.a", "bob", key) - c.Assert(err, check.IsNil) - - _, err = s.store.GetKey("host.a", "bob") - c.Assert(err, check.IsNil) +func newSelfSignedCA(privateKey []byte) (*tlsca.CertAuthority, auth.TrustedCerts, error) { + rsaKey, err := ssh.ParseRawPrivateKey(privateKey) + if err != nil { + return nil, auth.TrustedCerts{}, trace.Wrap(err) + } + key, cert, err := tlsca.GenerateSelfSignedCAWithPrivateKey(rsaKey.(*rsa.PrivateKey), pkix.Name{ + CommonName: "localhost", + Organization: []string{"localhost"}, + }, nil, defaults.CATTL) + if err != nil { + return nil, auth.TrustedCerts{}, trace.Wrap(err) + } + ca, err := tlsca.New(cert, key) + if err != nil { + return nil, auth.TrustedCerts{}, trace.Wrap(err) + } + return ca, auth.TrustedCerts{TLSCertificates: [][]byte{cert}}, nil } -// TestCheckKey make sure Teleport clients don't load invalid -// certificates while in FIPS mode. -func (s *KeyStoreTestSuite) TestCheckKeyFIPS(c *check.C) { - // This test only runs in FIPS mode. - if !isFIPS() { - return - } +func newTest(t *testing.T) (keyStoreTest, func()) { + utils.InitLoggerForTests(testing.Verbose()) - key := s.makeSignedKey(c, false) + dir, err := ioutil.TempDir("", "teleport-keystore") + require.NoError(t, err) - // Swap out the key with a ECDSA SSH key. - ellipticCertificate, _, err := utils.CreateEllipticCertificate("foo", ssh.UserCert) - c.Assert(err, check.IsNil) - key.Cert = ssh.MarshalAuthorizedKey(ellipticCertificate) + store, err := NewFSLocalKeyStore(dir) + require.NoError(t, err) - err = s.addKey("host.a", "bob", key) - c.Assert(err, check.IsNil) + s := keyStoreTest{ + keygen: testauthority.New(), + storeDir: dir, + store: store, + } + require.True(t, utils.IsDir(s.store.KeyDir)) - _, err = s.store.GetKey("host.a", "bob") - c.Assert(err, check.NotNil) -} + s.tlsCA, s.tlsCACert, err = newSelfSignedCA(CAPriv) + require.NoError(t, err) -func (s *KeyStoreTestSuite) addKey(host, user string, key *Key) error { - if err := s.store.AddKey(host, user, key); err != nil { - return err + return s, func() { + os.RemoveAll(dir) } - // Also write the trusted CA certs for the host. - return s.store.SaveCerts(host, []auth.TrustedCerts{s.tlsCACert}) } var ( diff --git a/tool/tctl/common/tctl.go b/tool/tctl/common/tctl.go index 01b12ed91e553..37759646395c4 100644 --- a/tool/tctl/common/tctl.go +++ b/tool/tctl/common/tctl.go @@ -38,7 +38,7 @@ import ( "github.com/gravitational/kingpin" "github.com/gravitational/trace" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" ) // GlobalCLIFlags keeps the CLI flags that apply to all tctl commands @@ -74,12 +74,12 @@ type CLICommand interface { TryRun(selectedCommand string, c auth.ClientI) (match bool, err error) } -// Run() is the same as 'make'. It helps to share the code between different +// Run is the same as 'make'. It helps to share the code between different // "distributions" like OSS or Enterprise // // distribution: name of the Teleport distribution -func Run(commands []CLICommand) { - utils.InitLogger(utils.LoggingForCLI, logrus.WarnLevel) +func Run(commands []CLICommand, loadConfigExt LoadConfigFn) { + utils.InitLogger(utils.LoggingForCLI, log.WarnLevel) // app is the command line parser app := utils.InitCLIParser("tctl", GlobalHelpString) @@ -129,13 +129,13 @@ func Run(commands []CLICommand) { } // configure all commands with Teleport configuration (they share 'cfg') - clientConfig, err := applyConfig(&ccf, cfg) + clientConfig, err := applyConfig(&ccf, cfg, loadConfigExt) if err != nil { utils.FatalError(err) } ctx := context.Background() - // connect to the auth sever: + client, err := connectToAuthService(ctx, cfg, clientConfig) if err != nil { utils.Consolef(os.Stderr, teleport.ComponentClient, @@ -157,14 +157,19 @@ func Run(commands []CLICommand) { } } -type authServiceClientConfig struct { - tlsConfig *tls.Config - sshConfig *ssh.ClientConfig +// LoadConfigFn is optional config loading function +type LoadConfigFn func(ccf *GlobalCLIFlags, cfg *service.Config) (*AuthServiceClientConfig, error) + +// AuthServiceClientConfig is a client config for auth service +type AuthServiceClientConfig struct { + // TLS holds credentials for mTLS + TLS *tls.Config + // SSH is client SSH config + SSH *ssh.ClientConfig } // connectToAuthService creates a valid client connection to the auth service -// -func connectToAuthService(ctx context.Context, cfg *service.Config, clientConfig *authServiceClientConfig) (auth.ClientI, error) { +func connectToAuthService(ctx context.Context, cfg *service.Config, clientConfig *AuthServiceClientConfig) (auth.ClientI, error) { // connect to the local auth server by default: cfg.Auth.Enabled = true if len(cfg.AuthServers) == 0 { @@ -173,10 +178,10 @@ func connectToAuthService(ctx context.Context, cfg *service.Config, clientConfig } } - logrus.Debugf("Connecting to auth servers: %v.", cfg.AuthServers) + log.Debugf("Connecting to auth servers: %v.", cfg.AuthServers) // Try connecting to the auth server directly over TLS. - client, err := auth.NewTLSClient(auth.ClientConfig{Addrs: cfg.AuthServers, TLS: clientConfig.tlsConfig}) + client, err := auth.NewTLSClient(auth.ClientConfig{Addrs: cfg.AuthServers, TLS: clientConfig.TLS}) if err != nil { return nil, trace.Wrap(err, "failed direct dial to auth server: %v", err) } @@ -185,7 +190,7 @@ func connectToAuthService(ctx context.Context, cfg *service.Config, clientConfig _, err = client.GetClusterName() if err != nil { err = trace.Wrap(err, "failed direct dial to auth server: %v", err) - if clientConfig.sshConfig == nil { + if clientConfig.SSH == nil { // No identity file was provided, don't try dialing via a reverse // tunnel on the proxy. return nil, trace.Wrap(err) @@ -201,19 +206,20 @@ func connectToAuthService(ctx context.Context, cfg *service.Config, clientConfig errs := []error{err} // Figure out the reverse tunnel address on the proxy first. - tunAddr, err := findReverseTunnel(ctx, cfg.AuthServers, clientConfig.tlsConfig.InsecureSkipVerify) + tunAddr, err := findReverseTunnel(ctx, cfg.AuthServers, clientConfig.TLS.InsecureSkipVerify) if err != nil { errs = append(errs, trace.Wrap(err, "failed lookup of proxy reverse tunnel address: %v", err)) return nil, trace.NewAggregate(errs...) } + log.Debugf("Attempting to connect using reverse tunnel address %v.", tunAddr) // reversetunnel.TunnelAuthDialer will take care of creating a net.Conn // within an SSH tunnel. client, err = auth.NewTLSClient(auth.ClientConfig{ Dialer: &reversetunnel.TunnelAuthDialer{ ProxyAddr: tunAddr, - ClientConfig: clientConfig.sshConfig, + ClientConfig: clientConfig.SSH, }, - TLS: clientConfig.tlsConfig, + TLS: clientConfig.TLS, }) if err != nil { errs = append(errs, trace.Wrap(err, "failed dial to auth server through reverse tunnel: %v", err)) @@ -289,8 +295,26 @@ func tunnelAddr(webAddr utils.NetAddr, settings client.ProxySettings) (string, e // // The returned authServiceClientConfig has the credentials needed to dial the // auth server. -func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) (*authServiceClientConfig, error) { - // load /etc/teleport.yaml and apply it's values: +func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config, loadConfigExt LoadConfigFn) (*AuthServiceClientConfig, error) { + // --debug flag + if ccf.Debug { + cfg.Debug = ccf.Debug + utils.InitLogger(utils.LoggingForCLI, log.DebugLevel) + log.Debugf("Debug logging has been enabled.") + } + + // Try the extension if the identity file has not been supplied. + if loadConfigExt != nil && ccf.IdentityFilePath == "" { + authConfig, err := loadConfigExt(ccf, cfg) + if err == nil { + return authConfig, nil + } + if !trace.IsNotFound(err) { + return nil, trace.Wrap(err) + } + } + + // load /etc/teleport.yaml and apply its values: fileConf, err := config.ReadConfigFile(ccf.ConfigFile) if err != nil { return nil, trace.Wrap(err) @@ -306,12 +330,7 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) (*authServiceClientCo if err = config.ApplyFileConfig(fileConf, cfg); err != nil { return nil, trace.Wrap(err) } - // --debug flag - if ccf.Debug { - cfg.Debug = ccf.Debug - utils.InitLogger(utils.LoggingForCLI, logrus.DebugLevel) - logrus.Debugf("DEBUG logging enabled") - } + // --auth-server flag(-s) if len(ccf.AuthServerAddr) != 0 { cfg.AuthServers, err = utils.ParseAddrs(ccf.AuthServerAddr) @@ -327,7 +346,7 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) (*authServiceClientCo return nil, trace.Wrap(err) } } - authConfig := new(authServiceClientConfig) + authConfig := new(AuthServiceClientConfig) // --identity flag if ccf.IdentityFilePath != "" { key, err := common.LoadIdentity(ccf.IdentityFilePath) @@ -335,11 +354,11 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) (*authServiceClientCo return nil, trace.Wrap(err) } - authConfig.tlsConfig, err = key.ClientTLSConfig(cfg.CipherSuites) + authConfig.TLS, err = key.ClientTLSConfig(cfg.CipherSuites) if err != nil { return nil, trace.Wrap(err) } - authConfig.sshConfig, err = key.ClientSSHConfig() + authConfig.SSH, err = key.ClientSSHConfig() if err != nil { return nil, trace.Wrap(err) } @@ -359,12 +378,12 @@ func applyConfig(ccf *GlobalCLIFlags, cfg *service.Config) (*authServiceClientCo } return nil, trace.Wrap(err) } - authConfig.tlsConfig, err = identity.TLSConfig(cfg.CipherSuites) + authConfig.TLS, err = identity.TLSConfig(cfg.CipherSuites) if err != nil { return nil, trace.Wrap(err) } } - authConfig.tlsConfig.InsecureSkipVerify = ccf.Insecure + authConfig.TLS.InsecureSkipVerify = ccf.Insecure return authConfig, nil } diff --git a/tool/tctl/main.go b/tool/tctl/main.go index 828a597079bd8..78770926b9ab8 100644 --- a/tool/tctl/main.go +++ b/tool/tctl/main.go @@ -32,5 +32,5 @@ func main() { &common.AccessRequestCommand{}, &common.AppsCommand{}, } - common.Run(commands) + common.Run(commands, nil) }