From 07e90d0ad6810883a2b2100a7f5f197d9829fbd1 Mon Sep 17 00:00:00 2001 From: Russell Jones Date: Fri, 19 Jan 2018 02:30:02 +0000 Subject: [PATCH] LocalKeyAgent only loads keys for a user logged into a proxy. --- constants.go | 8 ++ lib/client/api.go | 12 +-- lib/client/client.go | 18 ++-- lib/client/keyagent.go | 129 +++++++++++++++++------------ lib/client/keyagent_test.go | 18 ++-- lib/client/keystore.go | 159 +++++++++++++++++++----------------- lib/client/keystore_test.go | 61 +++++++++++--- lib/client/profile.go | 38 --------- tool/tsh/tsh.go | 44 +++++++--- 9 files changed, 276 insertions(+), 211 deletions(-) diff --git a/constants.go b/constants.go index 6e3d529f595fc..a0040bf5a8810 100644 --- a/constants.go +++ b/constants.go @@ -100,6 +100,14 @@ const ( // ComponentAuditLog is audit log component ComponentAuditLog = "auditlog" + // ComponentKeyAgent is an agent that has loaded the sessions keys and + // certificates for a user connected to a proxy. + ComponentKeyAgent = "keyagent" + + // ComponentKeyStore is all sessions keys and certificates a user has on disk + // for all proxies. + ComponentKeyStore = "keystore" + // DebugEnvVar tells tests to use verbose debug output DebugEnvVar = "DEBUG" diff --git a/lib/client/api.go b/lib/client/api.go index 141e394530458..95edb36817176 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -389,7 +389,7 @@ func NewClient(c *Config) (tc *TeleportClient, err error) { } } else { // initialize the local agent (auth agent which uses local SSH keys signed by the CA): - tc.localAgent, err = NewLocalAgent(c.KeysDir, c.Username) + tc.localAgent, err = NewLocalAgent(c.KeysDir, tc.ProxyHost(), c.Username) if err != nil { return nil, trace.Wrap(err) } @@ -1027,7 +1027,7 @@ func (tc *TeleportClient) ConnectToProxy() (*ProxyClient, error) { // Logout locates a certificate stored for a given proxy and deletes it func (tc *TeleportClient) Logout() error { - return trace.Wrap(tc.localAgent.DeleteKey(tc.ProxyHost(), tc.Config.Username)) + return trace.Wrap(tc.localAgent.DeleteKey()) } // Login logs the user into a Teleport cluster by talking to a Teleport proxy. @@ -1098,13 +1098,13 @@ func (tc *TeleportClient) Login(activateKey bool) (*Key, error) { } // save the list of TLS CAs client trusts - err = tc.localAgent.SaveCerts(tc.ProxyHost(), response.HostSigners) + err = tc.localAgent.SaveCerts(response.HostSigners) if err != nil { return nil, trace.Wrap(err) } // save the cert to the local storage (~/.tsh usually): - _, err = tc.localAgent.AddKey(tc.ProxyHost(), tc.Config.Username, key) + _, err = tc.localAgent.AddKey(key) if err != nil { return nil, trace.Wrap(err) } @@ -1144,7 +1144,7 @@ func (tc *TeleportClient) AddTrustedCA(ca services.CertAuthority) error { // only host CA has TLS certificates, user CA will overwrite trusted certs // to empty file if called if ca.GetType() == services.HostCA { - err = tc.LocalAgent().SaveCerts(tc.ProxyHost(), auth.AuthoritiesToTrustedCerts([]services.CertAuthority{ca})) + err = tc.LocalAgent().SaveCerts(auth.AuthoritiesToTrustedCerts([]services.CertAuthority{ca})) if err != nil { return trace.Wrap(err) } @@ -1154,7 +1154,7 @@ func (tc *TeleportClient) AddTrustedCA(ca services.CertAuthority) error { } func (tc *TeleportClient) AddKey(host string, key *Key) (*agent.AddedKey, error) { - return tc.localAgent.AddKey(host, tc.Username, key) + return tc.localAgent.AddKey(key) } // directLogin asks for a password + HOTP token, makes a request to CA via proxy diff --git a/lib/client/client.go b/lib/client/client.go index 131e7d5eafb55..5c1dab46653ee 100644 --- a/lib/client/client.go +++ b/lib/client/client.go @@ -168,23 +168,21 @@ func (proxy *ProxyClient) ConnectToSite(ctx context.Context, quiet bool) (auth.C tlsConfig := utils.TLSConfig() localAgent := proxy.teleportClient.LocalAgent() - pool, err := localAgent.GetCerts(proxy.teleportClient.ProxyHost()) + pool, err := localAgent.GetCerts() if err != nil { return nil, trace.Wrap(err) } tlsConfig.RootCAs = pool - keys, err := localAgent.GetKeys(proxy.teleportClient.Username) + key, err := localAgent.GetKey() if err != nil { - return nil, trace.Wrap(err, "failed to fetch TLS keys for %v", proxy.teleportClient.Username) + return nil, trace.Wrap(err, "failed to fetch TLS key for %v", proxy.teleportClient.Username) } - for _, key := range keys { - if len(key.TLSCert) != 0 { - tlsCert, err := tls.X509KeyPair(key.TLSCert, key.Priv) - if err != nil { - return nil, trace.Wrap(err, "failed to parse TLS cert and key") - } - tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert) + if len(key.TLSCert) != 0 { + tlsCert, err := tls.X509KeyPair(key.TLSCert, key.Priv) + if err != nil { + return nil, trace.Wrap(err, "failed to parse TLS cert and key") } + tlsConfig.Certificates = append(tlsConfig.Certificates, tlsCert) } if len(tlsConfig.Certificates) == 0 { return nil, trace.BadParameter("no TLS keys found for user %v, please relogin to get new credentials", proxy.teleportClient.Username) diff --git a/lib/client/keyagent.go b/lib/client/keyagent.go index d49d380240afb..51127729dc185 100644 --- a/lib/client/keyagent.go +++ b/lib/client/keyagent.go @@ -26,17 +26,27 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) +// LocalKeyAgent holds Teleport certificates for a user connected to a cluster. type LocalKeyAgent struct { - agent.Agent // Agent is the teleport agent - keyStore LocalKeyStore // keyStore is the storage backend for certificates and keys - sshAgent agent.Agent // sshAgent is the system ssh agent + // log holds the structured logger. + log *logrus.Entry + + // Agent is the teleport agent + agent.Agent + + // keyStore is the storage backend for certificates and keys + keyStore LocalKeyStore + + // sshAgent is the system ssh agent + sshAgent agent.Agent // map of "no hosts". these are hosts that user manually (via keyboard // input) refused connecting to. @@ -44,21 +54,33 @@ type LocalKeyAgent struct { // function which asks a user to trust host/key combination (during host auth) hostPromptFunc func(host string, k ssh.PublicKey) error + + // username is the Teleport username for who the keys will be loaded in the + // local agent. + username string + + // proxyHost is the proxy for the cluster that his key agent holds keys for. + proxyHost string } // NewLocalAgent reads all Teleport certificates from disk (using FSLocalKeyStore), // creates a LocalKeyAgent, loads all certificates into it, and returns the agent. -func NewLocalAgent(keyDir, username string) (a *LocalKeyAgent, err error) { +func NewLocalAgent(keyDir string, proxyHost string, username string) (a *LocalKeyAgent, err error) { keystore, err := NewFSLocalKeyStore(keyDir) if err != nil { return nil, trace.Wrap(err) } a = &LocalKeyAgent{ - Agent: agent.NewKeyring(), - keyStore: keystore, - sshAgent: connectToSSHAgent(), - noHosts: make(map[string]bool), + log: logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentKeyAgent, + }), + Agent: agent.NewKeyring(), + keyStore: keystore, + sshAgent: connectToSSHAgent(), + noHosts: make(map[string]bool), + username: username, + proxyHost: proxyHost, } // unload all teleport keys from the agent first to ensure @@ -68,27 +90,29 @@ func NewLocalAgent(keyDir, username string) (a *LocalKeyAgent, err error) { return nil, trace.Wrap(err) } - // read all keys from disk (~/.tsh usually) - keys, err := a.GetKeys(username) + // read in key for this user in proxy + key, err := a.GetKey() if err != nil { + if trace.IsNotFound(err) { + return a, nil + } return nil, trace.Wrap(err) } - log.Infof("[KEY AGENT] Loading %v keys for %q", len(keys), username) + a.log.Infof("Loading key for %q", username) - // load all keys into the agent - for _, key := range keys { - _, err = a.LoadKey(username, key) - if err != nil { - return nil, trace.Wrap(err) - } + // load key into the agent + _, err = a.LoadKey(*key) + if err != nil { + return nil, trace.Wrap(err) } return a, nil } -// LoadKey adds a key into the teleport ssh agent as well as the system ssh agent. -func (a *LocalKeyAgent) LoadKey(username string, key Key) (*agent.AddedKey, error) { +// LoadKey adds a key into the Teleport ssh agent as well as the system ssh +// agent. +func (a *LocalKeyAgent) LoadKey(key Key) (*agent.AddedKey, error) { agents := []agent.Agent{a.Agent} if a.sshAgent != nil { agents = append(agents, a.sshAgent) @@ -101,7 +125,7 @@ func (a *LocalKeyAgent) LoadKey(username string, key Key) (*agent.AddedKey, erro } // remove any keys that the user may already have loaded - err = a.UnloadKey(username) + err = a.UnloadKey() if err != nil { return nil, trace.Wrap(err) } @@ -111,7 +135,7 @@ func (a *LocalKeyAgent) LoadKey(username string, key Key) (*agent.AddedKey, erro for _, agentKey := range agentKeys { err = agents[i].Add(*agentKey) if err != nil { - log.Warnf("[KEY AGENT] Unable to communicate with agent and add key: %v", err) + a.log.Warnf("Unable to communicate with agent and add key: %v", err) } } } @@ -121,8 +145,9 @@ func (a *LocalKeyAgent) LoadKey(username string, key Key) (*agent.AddedKey, erro return agentKeys[0], nil } -// UnloadKey will unload a key from the teleport ssh agent as well as the system agent. -func (a *LocalKeyAgent) UnloadKey(username string) error { +// UnloadKey will unload key for user from the teleport ssh agent as well as +// the system agent. +func (a *LocalKeyAgent) UnloadKey() error { agents := []agent.Agent{a.Agent} if a.sshAgent != nil { agents = append(agents, a.sshAgent) @@ -133,15 +158,15 @@ func (a *LocalKeyAgent) UnloadKey(username string) error { // get a list of all keys in the agent keyList, err := agents[i].List() if err != nil { - log.Warnf("Unable to communicate with agent and list keys: %v", err) + a.log.Warnf("Unable to communicate with agent and list keys: %v", err) } // remove any teleport keys we currently have loaded in the agent for this user for _, key := range keyList { - if key.Comment == fmt.Sprintf("teleport:%v", username) { + if key.Comment == fmt.Sprintf("teleport:%v", a.username) { err = agents[i].Remove(key) if err != nil { - log.Warnf("Unable to communicate with agent and remove key: %v", err) + a.log.Warnf("Unable to communicate with agent and remove key: %v", err) } } } @@ -150,7 +175,8 @@ func (a *LocalKeyAgent) UnloadKey(username string) error { return nil } -// UnloadKeys will unload all Teleport keys from the teleport agent as well as the system agent. +// UnloadKeys will unload all Teleport keys from the teleport agent as well as +// the system agent. func (a *LocalKeyAgent) UnloadKeys() error { agents := []agent.Agent{a.Agent} if a.sshAgent != nil { @@ -162,7 +188,7 @@ func (a *LocalKeyAgent) UnloadKeys() error { // get a list of all keys in the agent keyList, err := agents[i].List() if err != nil { - log.Warnf("Unable to communicate with agent and list keys: %v", err) + a.log.Warnf("Unable to communicate with agent and list keys: %v", err) } // remove any teleport keys we currently have loaded in the agent @@ -170,7 +196,7 @@ func (a *LocalKeyAgent) UnloadKeys() error { if strings.HasPrefix(key.Comment, "teleport:") { err = agents[i].Remove(key) if err != nil { - log.Warnf("Unable to communicate with agent and remove key: %v", err) + a.log.Warnf("Unable to communicate with agent and remove key: %v", err) } } } @@ -179,9 +205,10 @@ func (a *LocalKeyAgent) UnloadKeys() error { return nil } -// GetKeys returns a slice of keys that it has read in from the local keystore (~/.tsh) -func (a *LocalKeyAgent) GetKeys(username string) ([]Key, error) { - return a.keyStore.GetKeys(username) +// GetKey returns the key for this user in a proxy from the filesystem keystore +// at ~/.tsh. +func (a *LocalKeyAgent) GetKey() (*Key, error) { + return a.keyStore.GetKey(a.proxyHost, a.username) } // AddHostSignersToCache takes a list of CAs whom we trust. This list is added to a database @@ -196,10 +223,10 @@ func (a *LocalKeyAgent) AddHostSignersToCache(certAuthorities []auth.TrustedCert for _, ca := range certAuthorities { publicKeys, err := ca.SSHCertPublicKeys() if err != nil { - log.Error(err) + a.log.Error(err) return trace.Wrap(err) } - log.Debugf("[KEY AGENT] adding CA key for %s", ca.ClusterName) + a.log.Debugf("Adding CA key for %s", ca.ClusterName) err = a.keyStore.AddKnownHostKeys(ca.ClusterName, publicKeys) if err != nil { return trace.Wrap(err) @@ -208,12 +235,12 @@ func (a *LocalKeyAgent) AddHostSignersToCache(certAuthorities []auth.TrustedCert return nil } -func (a *LocalKeyAgent) SaveCerts(proxy string, certAuthorities []auth.TrustedCerts) error { - return a.keyStore.SaveCerts(proxy, certAuthorities) +func (a *LocalKeyAgent) SaveCerts(certAuthorities []auth.TrustedCerts) error { + return a.keyStore.SaveCerts(a.proxyHost, certAuthorities) } -func (a *LocalKeyAgent) GetCerts(proxy string) (*x509.CertPool, error) { - return a.keyStore.GetCerts(proxy) +func (a *LocalKeyAgent) GetCerts() (*x509.CertPool, error) { + return a.keyStore.GetCerts(a.proxyHost) } // UserRefusedHosts returns 'true' if a user refuses connecting to remote hosts @@ -252,7 +279,7 @@ func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh // sshd instead of teleport daemon keys, _ := a.keyStore.GetKnownHostKeys(host) if len(keys) > 0 && sshutils.KeysEqual(key, keys[0]) { - log.Debugf("[KEY AGENT] verified host %s", host) + a.log.Debugf("Verified host %s", host) return nil } // ask user: @@ -262,7 +289,7 @@ func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh } // remember the host key (put it into 'known_hosts') if err := a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key}); err != nil { - log.Warnf("error saving the host key: %v", err) + a.log.Warnf("Error saving the host key: %v", err) } return nil } @@ -270,13 +297,13 @@ func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh // we are given a certificate. see if it was signed by any of the known_host keys: keys, err := a.keyStore.GetKnownHostKeys("") if err != nil { - log.Error(err) + a.log.Error(err) return trace.Wrap(err) } - log.Debugf("[KEY AGENT] got %d known hosts", len(keys)) + a.log.Debugf("Got %d known hosts", len(keys)) for i := range keys { if sshutils.KeysEqual(cert.SignatureKey, keys[i]) { - log.Debugf("[KEY AGENT] verified host %s", host) + a.log.Debugf("Verified host %s", host) return nil } } @@ -290,34 +317,34 @@ func (a *LocalKeyAgent) CheckHostSignature(host string, remote net.Addr, key ssh // user said "yes" err = a.keyStore.AddKnownHostKeys(host, []ssh.PublicKey{key}) if err != nil { - log.Warn(err) + a.log.Warn(err) } return err } // AddKey activates a new signed session key by adding it into the keystore and also // by loading it into the SSH agent -func (a *LocalKeyAgent) AddKey(host string, username string, key *Key) (*agent.AddedKey, error) { +func (a *LocalKeyAgent) AddKey(key *Key) (*agent.AddedKey, error) { // save it to disk (usually into ~/.tsh) - err := a.keyStore.AddKey(host, username, key) + err := a.keyStore.AddKey(a.proxyHost, a.username, key) if err != nil { return nil, trace.Wrap(err) } // load key into the teleport agent and system agent - return a.LoadKey(username, *key) + return a.LoadKey(*key) } // DeleteKey removes the key from the key store as well as unloading the key from the agent. -func (a *LocalKeyAgent) DeleteKey(proxyHost string, username string) error { +func (a *LocalKeyAgent) DeleteKey() error { // remove key from key store - err := a.keyStore.DeleteKey(proxyHost, username) + err := a.keyStore.DeleteKey(a.proxyHost, a.username) if err != nil { return trace.Wrap(err) } // remove any keys that are loaded for this user from the teleport and system agents - err = a.UnloadKey(username) + err = a.UnloadKey() if err != nil { return trace.Wrap(err) } diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index cf000085cc6bd..076da9ae31323 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -95,12 +95,12 @@ func (s *KeyAgentTestSuite) SetUpTest(c *check.C) { // a teleport key with the teleport username. func (s *KeyAgentTestSuite) TestAddKey(c *check.C) { // make a new local agent - lka, err := NewLocalAgent(s.keyDir, s.username) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username) c.Assert(err, check.IsNil) // add the key to the local agent, this should write the key // to disk as well as load it in the agent - _, err = lka.AddKey(s.hostname, s.username, s.key) + _, err = lka.AddKey(s.key) c.Assert(err, check.IsNil) // check that the key has been written to disk @@ -140,7 +140,7 @@ func (s *KeyAgentTestSuite) TestAddKey(c *check.C) { c.Assert(true, check.Equals, found) // unload all keys for this user from the teleport agent and system agent - err = lka.UnloadKey(s.username) + err = lka.UnloadKey() c.Assert(err, check.IsNil) } @@ -154,11 +154,11 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { userdata := []byte("hello, world") // make a new local agent - lka, err := NewLocalAgent(s.keyDir, s.username) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username) c.Assert(err, check.IsNil) // unload any keys that might be in the agent for this user - err = lka.UnloadKey(s.username) + err = lka.UnloadKey() c.Assert(err, check.IsNil) // get all the keys in the teleport and system agent @@ -171,9 +171,9 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { // load the key to the twice, this should only // result in one key for this user in the agent - _, err = lka.LoadKey(s.username, *s.key) + _, err = lka.LoadKey(*s.key) c.Assert(err, check.IsNil) - _, err = lka.LoadKey(s.username, *s.key) + _, err = lka.LoadKey(*s.key) c.Assert(err, check.IsNil) // get all the keys in the teleport and system agent @@ -206,13 +206,13 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { c.Assert(err, check.IsNil) // unload all keys from the teleport agent and system agent - err = lka.UnloadKey(s.username) + err = lka.UnloadKey() c.Assert(err, check.IsNil) } func (s *KeyAgentTestSuite) TestHostVerification(c *check.C) { // make a new local agent - lka, err := NewLocalAgent(s.keyDir, s.username) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username) c.Assert(err, check.IsNil) // by default user has not refused any hosts: diff --git a/lib/client/keystore.go b/lib/client/keystore.go index fedfe398ecae4..5ee185572d8c4 100644 --- a/lib/client/keystore.go +++ b/lib/client/keystore.go @@ -29,14 +29,14 @@ import ( "strings" "time" + "golang.org/x/crypto/ssh" + + "github.com/gravitational/teleport" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/sshutils" - "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - - "golang.org/x/crypto/ssh" + "github.com/sirupsen/logrus" ) const ( @@ -57,89 +57,83 @@ const ( keyFilePerms os.FileMode = 0600 ) -// LocalKeyStore interface allows for different storage back-ends for TSH to -// load/save its keys +// LocalKeyStore interface allows for different storage backends for tsh to +// load/save its keys. // // The _only_ filesystem-based implementation of LocalKeyStore is declared // below (FSLocalKeyStore) type LocalKeyStore interface { - // client key management - GetKeys(username string) ([]Key, error) - AddKey(host string, username string, key *Key) error - GetKey(host string, username string) (*Key, error) - DeleteKey(host string, username string) error + // AddKey adds the given session key for the proxy and username to the + // storage backend. + AddKey(proxy string, username string, key *Key) error + + // GetKey returns the session key for the given username and proxy. + GetKey(proxy string, username string) (*Key, error) - // interface to known_hosts file: + // DeleteKey removes a specific session key from a proxy. + DeleteKey(proxyHost string, username string) error + + // DeleteKeys removes all session keys from disk. + DeleteKeys() error + + // AddKnownHostKeys adds the public key to the list of known hosts for + // a hostname. AddKnownHostKeys(hostname string, keys []ssh.PublicKey) error + + // GetKnownHostKeys returns all public keys for a hostname. GetKnownHostKeys(hostname string) ([]ssh.PublicKey, error) - // SaveCerts saves trusted TLS certificates of certificate authorities + // SaveCerts saves trusted TLS certificates of certificate authorities. SaveCerts(proxy string, cas []auth.TrustedCerts) error - // GetCerts gets trusted TLS certificates of certificate authorities + + // GetCerts gets trusted TLS certificates of certificate authorities. GetCerts(proxy string) (*x509.CertPool, error) } -// FSLocalKeyStore implements LocalKeyStore interface using the filesystem +// FSLocalKeyStore implements LocalKeyStore interface using the filesystem. // Here's the file layout for the FS store: +// // ~/.tsh/ -// ├── known_hosts --> trusted certificate authorities (their keys) in a format similar to known_hosts -// └── sessions --> server-signed session keys -// └── host-a -// | ├── cert -// | ├── key -// | └── pub -// └── host-b -// ├── cert -// ├── key -// └── pub +// ├── known_hosts --> trusted certificate authorities (their keys) in a format similar to known_hosts +// └── keys +//    ├── one.example.com +//    │   ├── certs.pem +//    │   ├── foo --> RSA Private Key +//    │   ├── foo-cert.pub --> SSH certificate for proxies and nodes +//    │   ├── foo.pub --> Public Key +//    │   └── foo-x509.pem --> TLS client certificate for Auth Server +//    └── two.example.com +//    ├── certs.pem +//    ├── bar +//    ├── bar-cert.pub +//    ├── bar.pub +//    └── bar-x509.pem type FSLocalKeyStore struct { - LocalKeyStore + // log holds the structured logger. + log *logrus.Entry - // KeyDir is the directory where all keys are stored + // KeyDir is the directory where all keys are stored. KeyDir string } // NewFSLocalKeyStore creates a new filesystem-based local keystore object // and initializes it. // -// if dirPath is empty, sets it to ~/.tsh +// If dirPath is empty, sets it to ~/.tsh. func NewFSLocalKeyStore(dirPath string) (s *FSLocalKeyStore, err error) { dirPath, err = initKeysDir(dirPath) if err != nil { return nil, trace.Wrap(err) } + return &FSLocalKeyStore{ + log: logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentKeyStore, + }), KeyDir: dirPath, }, nil } -// GetKeys returns all user session keys stored in the store -func (fs *FSLocalKeyStore) GetKeys(username string) (keys []Key, err error) { - dirPath := filepath.Join(fs.KeyDir, sessionKeyDir) - if !utils.IsDir(dirPath) { - return make([]Key, 0), nil - } - dirEntries, err := ioutil.ReadDir(dirPath) - if err != nil { - return nil, trace.Wrap(err) - } - for _, fi := range dirEntries { - if !fi.IsDir() { - continue - } - k, err := fs.GetKey(fi.Name(), username) - if err != nil { - // if a key is reported as 'not found' it's probably because it expired - if !trace.IsNotFound(err) { - return nil, trace.Wrap(err) - } - continue - } - keys = append(keys, *k) - } - return keys, nil -} - // AddKey adds a new key to the session store. If a key for the host is already // stored, overwrites it. func (fs *FSLocalKeyStore) AddKey(host, username string, key *Key) error { @@ -151,7 +145,7 @@ func (fs *FSLocalKeyStore) AddKey(host, username string, key *Key) error { fp := filepath.Join(dirPath, fname) err := ioutil.WriteFile(fp, data, keyFilePerms) if err != nil { - log.Error(err) + fs.log.Error(err) } return err } @@ -190,37 +184,54 @@ func (fs *FSLocalKeyStore) DeleteKey(host string, username string) error { return nil } +// DeleteKeys removes all session keys from disk. +func (fs *FSLocalKeyStore) DeleteKeys() error { + dirPath := filepath.Join(fs.KeyDir, sessionKeyDir) + + err := os.RemoveAll(dirPath) + if err != nil { + return trace.Wrap(err) + } + + return nil +} + // GetKey returns a key for a given host. If the key is not found, // returns trace.NotFound error. -func (fs *FSLocalKeyStore) GetKey(host, username string) (*Key, error) { - dirPath, err := fs.dirFor(host) +func (fs *FSLocalKeyStore) GetKey(proxyHost string, username string) (*Key, error) { + dirPath, err := fs.dirFor(proxyHost) if err != nil { return nil, trace.Wrap(err) } + _, err = ioutil.ReadDir(dirPath) + if err != nil { + return nil, trace.NotFound("no session keys for %v in %v", username, proxyHost) + } + certFile := filepath.Join(dirPath, username+fileExtCert) cert, err := ioutil.ReadFile(certFile) if err != nil { - log.Error(err) + fs.log.Error(err) return nil, trace.Wrap(err) } tlsCertFile := filepath.Join(dirPath, username+fileExtTLSCert) tlsCert, err := ioutil.ReadFile(tlsCertFile) if err != nil { - log.Error(err) + fs.log.Error(err) return nil, trace.Wrap(err) } pub, err := ioutil.ReadFile(filepath.Join(dirPath, username+fileExtPub)) if err != nil { - log.Error(err) + fs.log.Error(err) return nil, trace.Wrap(err) } priv, err := ioutil.ReadFile(filepath.Join(dirPath, username)) if err != nil { - log.Error(err) + fs.log.Error(err) return nil, trace.Wrap(err) } - key := &Key{Pub: pub, Priv: priv, Cert: cert, ProxyHost: host, TLSCert: tlsCert} + key := &Key{Pub: pub, Priv: priv, Cert: cert, ProxyHost: proxyHost, TLSCert: tlsCert} // expired certificate? this key won't be accepted anymore, lets delete it: certExpiration, err := key.CertValidBefore() @@ -231,11 +242,11 @@ func (fs *FSLocalKeyStore) GetKey(host, username string) (*Key, error) { if err != nil { return nil, trace.Wrap(err) } - log.Debugf("[KEYSTORE] Returning certificate %q valid until %q, TLS certificate %q valid until %q", certFile, certExpiration, tlsCertFile, tlsCertExpiration) + fs.log.Debugf("Returning certificate %q valid until %q, TLS certificate %q valid until %q", certFile, certExpiration, tlsCertFile, tlsCertExpiration) if certExpiration.Before(time.Now()) || tlsCertExpiration.Before(time.Now()) { - log.Infof("[KEYSTORE] TTL expired (%v) or (%v) for session key %v", certExpiration, tlsCertExpiration, dirPath) + fs.log.Infof("TTL expired (%v) or (%v) for session key %v", certExpiration, tlsCertExpiration, dirPath) os.RemoveAll(dirPath) - return nil, trace.NotFound("session keys for %s are not found", host) + return nil, trace.NotFound("session keys for %s are not found", proxyHost) } return key, nil } @@ -285,7 +296,7 @@ func (fs *FSLocalKeyStore) GetCerts(proxy string) (*x509.CertPool, error) { break } if block.Type != "CERTIFICATE" || len(block.Headers) != 0 { - log.Debugf("Skipping PEM block type=%v headers=%v.", block.Type, block.Headers) + fs.log.Debugf("Skipping PEM block type=%v headers=%v.", block.Type, block.Headers) continue } @@ -293,7 +304,7 @@ func (fs *FSLocalKeyStore) GetCerts(proxy string) (*x509.CertPool, error) { if err != nil { return nil, trace.BadParameter("failed to parse certificate: %v", err) } - log.Debugf("Adding trusted cluster certificate authority %q to trusted pool.", cert.Issuer) + fs.log.Debugf("Adding trusted cluster certificate authority %q to trusted pool.", cert.Issuer) pool.AddCert(cert) } return pool, nil @@ -320,7 +331,7 @@ func (fs *FSLocalKeyStore) AddKnownHostKeys(hostname string, hostKeys []ssh.Publ } // add every host key to the list of entries for i := range hostKeys { - log.Debugf("adding known host %s with key: %v", hostname, sshutils.Fingerprint(hostKeys[i])) + fs.log.Debugf("Adding known host %s with key: %v", hostname, sshutils.Fingerprint(hostKeys[i])) bytes := ssh.MarshalAuthorizedKey(hostKeys[i]) line := strings.TrimSpace(fmt.Sprintf("%s %s", hostname, bytes)) if _, exists := entries[line]; !exists { @@ -380,11 +391,13 @@ func (fs *FSLocalKeyStore) GetKnownHostKeys(hostname string) ([]ssh.PublicKey, e } // dirFor is a helper function. It returns a directory where session keys -// for a given host are stored -func (fs *FSLocalKeyStore) dirFor(hostname string) (string, error) { - dirPath := filepath.Join(fs.KeyDir, sessionKeyDir, hostname) +// for a given host are stored. fs.KeyDir is typically "~/.tsh", sessionKeyDir +// is typically "keys", and proxyHost is typically something like +// "proxy.example.com". +func (fs *FSLocalKeyStore) dirFor(proxyHost string) (string, error) { + dirPath := filepath.Join(fs.KeyDir, sessionKeyDir, proxyHost) if err := os.MkdirAll(dirPath, profileDirPerms); err != nil { - log.Error(err) + fs.log.Error(err) return "", trace.Wrap(err) } return dirPath, nil diff --git a/lib/client/keystore_test.go b/lib/client/keystore_test.go index 4abe7b722d6fc..38fe0d0b8ee9f 100644 --- a/lib/client/keystore_test.go +++ b/lib/client/keystore_test.go @@ -43,6 +43,7 @@ type KeyStoreTestSuite struct { tlsca *tlsca.CertAuthority } +var _ = fmt.Printf var _ = check.Suite(&KeyStoreTestSuite{}) func newSelfSignedCA(privateKey []byte) (*tlsca.CertAuthority, error) { @@ -81,6 +82,7 @@ func (s *KeyStoreTestSuite) SetUpTest(c *check.C) { 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++ { @@ -95,17 +97,18 @@ func (s *KeyStoreTestSuite) TestListKeys(c *check.C) { s.store.AddKey("sam.host", "sam", samKey) // read all bob keys: - keys2, err := s.store.GetKeys("bob") - c.Assert(err, check.IsNil) - c.Assert(keys2, check.HasLen, keyNum) - c.Assert(keys2, check.DeepEquals, 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]) + } // read sam's key and make sure it's the same: - keys, err = s.store.GetKeys("sam") + skey, err := s.store.GetKey("sam.host", "sam") c.Assert(err, check.IsNil) - c.Assert(keys, check.HasLen, 1) - c.Assert(samKey.Cert, check.DeepEquals, keys[0].Cert) - c.Assert(samKey.Pub, check.DeepEquals, keys[0].Pub) + c.Assert(samKey.Cert, check.DeepEquals, skey.Cert) + c.Assert(samKey.Pub, check.DeepEquals, skey.Pub) } func (s *KeyStoreTestSuite) TestKeyCRUD(c *check.C) { @@ -120,7 +123,7 @@ func (s *KeyStoreTestSuite) TestKeyCRUD(c *check.C) { c.Assert(err, check.IsNil) c.Assert(key.EqualsTo(keyCopy), check.Equals, true) - // Delete & verify that its' gone + // Delete & verify that it's gone err = s.store.DeleteKey("host.a", "bob") c.Assert(err, check.IsNil) keyCopy, err = s.store.GetKey("host.a", "bob") @@ -133,18 +136,50 @@ func (s *KeyStoreTestSuite) TestKeyCRUD(c *check.C) { c.Assert(trace.IsNotFound(err), check.Equals, true) } +func (s *KeyStoreTestSuite) TestDeleteAll(c *check.C) { + key := s.makeSignedKey(c, false) + + // add keys + err := s.store.AddKey("proxy.example.com", "foo", key) + c.Assert(err, check.IsNil) + err = s.store.AddKey("proxy.example.com", "bar", key) + c.Assert(err, check.IsNil) + + // check keys exist + _, err = s.store.GetKey("proxy.example.com", "foo") + c.Assert(err, check.IsNil) + _, err = s.store.GetKey("proxy.example.com", "bar") + c.Assert(err, check.IsNil) + + // delete all keys + err = s.store.DeleteKeys() + c.Assert(err, check.IsNil) + + // verify keys gone + _, err = s.store.GetKey("proxy.example.com", "foo") + c.Assert(err, check.NotNil) + _, err = s.store.GetKey("proxy.example.com", "bar") + c.Assert(err, check.NotNil) +} + func (s *KeyStoreTestSuite) TestKeyExpiration(c *check.C) { // make two keys: one is current, and the expire one good := s.makeSignedKey(c, false) + good.ProxyHost = "good.host" expired := s.makeSignedKey(c, true) + expired.ProxyHost = "expired.host" s.store.AddKey("good.host", "sam", good) s.store.AddKey("expired.host", "sam", expired) - // get all keys back. only "good" key should be returned: - keys, _ := s.store.GetKeys("sam") - c.Assert(keys, check.HasLen, 1) - c.Assert(keys[0].EqualsTo(good), check.Equals, true) + // only "good" key should be returned + goodKey, err := s.store.GetKey("good.host", "sam") + c.Assert(err, check.IsNil) + c.Assert(goodKey, check.DeepEquals, good) + + // expired key should not be returned + _, err = s.store.GetKey("expired.host", "sam") + c.Assert(err, check.NotNil) } func (s *KeyStoreTestSuite) TestKnownHosts(c *check.C) { diff --git a/lib/client/profile.go b/lib/client/profile.go index fd0789a1ee823..46efe80794620 100644 --- a/lib/client/profile.go +++ b/lib/client/profile.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "io/ioutil" "os" "os/user" @@ -121,40 +120,3 @@ func (cp *ClientProfile) SaveTo(filePath string, opts ProfileOptions) error { } return trace.Wrap(err) } - -// LogoutFromEverywhere looks at the list of proxy servers tsh is currently logged into -// by examining ~/.tsh and logs him out of them all -func LogoutFromEverywhere(username string) error { - // if no --user flag was passed, get the current OS user: - if username == "" { - me, err := user.Current() - if err != nil { - return trace.Wrap(err) - } - username = me.Username - } - // load all current keys: - agent, err := NewLocalAgent("", username) - if err != nil { - return trace.Wrap(err) - } - keys, err := agent.GetKeys(username) - if err != nil { - return trace.Wrap(err) - } - if len(keys) == 0 { - fmt.Printf("%s is not logged in\n", username) - return nil - } - // ... and delete them: - for _, key := range keys { - err = agent.DeleteKey(key.ProxyHost, username) - if err != nil { - fmt.Fprintf(os.Stderr, "error logging %s out of %s: %s\n", - username, key.ProxyHost, err) - } else { - fmt.Printf("logged %s out of %s\n", username, key.ProxyHost) - } - } - return nil -} diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index 5d9fe7e6c374f..fc7dd4d7dc705 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -343,21 +343,43 @@ func onLogin(cf *CLIConf) { func onLogout(cf *CLIConf) { client.UnlinkCurrentProfile() - // logout from all - if cf.Proxy == "" { - client.LogoutFromEverywhere(cf.Username) - } else { - tc, err := makeClient(cf, true) + // get access to the file system key store in ~/.tsh + flka, err := client.NewFSLocalKeyStore(client.FullProfilePath("")) + if err != nil { + fmt.Printf("Unable to logout user: %v\n", err) + return + } + + // extract the proxy name + proxyHost, _, err := net.SplitHostPort(cf.Proxy) + if err != nil { + proxyHost = cf.Proxy + } + + switch { + // proxy and username for key to remove + case proxyHost != "" && cf.Username != "": + err = flka.DeleteKey(proxyHost, cf.Username) if err != nil { - utils.FatalError(err) - } - if err = tc.Logout(); err != nil { if trace.IsNotFound(err) { - utils.FatalError(trace.Errorf("you are not logged into proxy '%s'", cf.Proxy)) + fmt.Printf("User %v already logged out from %v.\n", cf.Username, proxyHost) + return } - utils.FatalError(err) + fmt.Printf("Unable to logout %v from %v: %v\n", cf.Username, proxyHost, err) + return } - fmt.Printf("%s has logged out of %s\n", tc.Username, cf.SiteName) + fmt.Printf("Logged out %v from %v.\n", cf.Username, proxyHost) + // remove all keys + case proxyHost == "" && cf.Username == "": + err = flka.DeleteKeys() + if err != nil { + fmt.Printf("Unable to remove all keys: %v\n", err) + return + } + fmt.Printf("Logged out all users from all proxies.\n") + default: + fmt.Printf("Specify --proxy and --username to remove keys for specific user ") + fmt.Printf("from a proxy or neither to log out all users from all proxies.\n") } }