diff --git a/lib/client/api.go b/lib/client/api.go index 0f6258edf40a1..d11f769d1ea0f 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -271,9 +271,12 @@ type Config struct { // (not currently implemented), or set to 'none' to suppress browser opening entirely. Browser string - // UseLocalSSHAgent will write user certificates to the local ssh-agent (or - // similar) socket at $SSH_AUTH_SOCK. - UseLocalSSHAgent bool + // AddKeysToAgent specifies how the client handles keys. + // auto - will attempt to add keys to agent if the agent supports it + // only - attempt to load keys into agent but don't write them to disk + // on - attempt to load keys into agent + // off - do not attempt to load keys into agent + AddKeysToAgent string // EnableEscapeSequences will scan Stdin for SSH escape sequences during // command/shell execution. This also requires Stdin to be an interactive @@ -298,7 +301,7 @@ func MakeDefaultConfig() *Config { Stdout: os.Stdout, Stderr: os.Stderr, Stdin: os.Stdin, - UseLocalSSHAgent: true, + AddKeysToAgent: "auto", EnableEscapeSequences: true, } } @@ -945,7 +948,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): webProxyHost, _ := tc.WebProxyHostPort() - tc.localAgent, err = NewLocalAgent(c.KeysDir, webProxyHost, c.Username, c.UseLocalSSHAgent) + tc.localAgent, err = NewLocalAgent(c.KeysDir, webProxyHost, c.Username, c.AddKeysToAgent) if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/client/keyagent.go b/lib/client/keyagent.go index a0f0569b3022f..553955e969dc7 100644 --- a/lib/client/keyagent.go +++ b/lib/client/keyagent.go @@ -64,6 +64,9 @@ type LocalKeyAgent struct { // proxyHost is the proxy for the cluster that his key agent holds keys for. proxyHost string + + // saveKeys dictates whether the agent will save added keys to disk or not. + saveKeys bool } // NewKeyStoreCertChecker returns a new certificate checker @@ -100,9 +103,14 @@ func NewKeyStoreCertChecker(keyStore LocalKeyStore) ssh.HostKeyCallback { } } +func agentSupportsSSHCertificates() bool { + agent := os.Getenv(teleport.SSHAuthSock) + return !strings.Contains(agent, "gpg-agent") +} + // 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) { +func NewLocalAgent(keyDir, proxyHost, username string, addKeysToAgent string) (a *LocalKeyAgent, err error) { keystore, err := NewFSLocalKeyStore(keyDir) if err != nil { return nil, trace.Wrap(err) @@ -117,9 +125,10 @@ func NewLocalAgent(keyDir, proxyHost, username string, useLocalSSHAgent bool) (a noHosts: make(map[string]bool), username: username, proxyHost: proxyHost, + saveKeys: addKeysToAgent != "only", } - if useLocalSSHAgent { + if (addKeysToAgent == "auto" || agentSupportsSSHCertificates()) || addKeysToAgent == "only" || addKeysToAgent == "yes" { a.sshAgent = connectToSSHAgent() } else { log.Debug("Skipping connection to the local ssh-agent.") @@ -403,10 +412,12 @@ func (a *LocalKeyAgent) defaultHostPromptFunc(host string, key ssh.PublicKey, wr // 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(key *Key) (*agent.AddedKey, error) { - // save it to disk (usually into ~/.tsh) - err := a.keyStore.AddKey(a.proxyHost, a.username, key) - if err != nil { - return nil, trace.Wrap(err) + if a.saveKeys { + // save it to disk (usually into ~/.tsh) + 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 diff --git a/lib/client/keyagent_test.go b/lib/client/keyagent_test.go index 74893849d2b17..942096751e31e 100644 --- a/lib/client/keyagent_test.go +++ b/lib/client/keyagent_test.go @@ -96,7 +96,7 @@ 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.hostname, s.username, true) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, "auto") c.Assert(err, check.IsNil) // add the key to the local agent, this should write the key @@ -155,7 +155,7 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { userdata := []byte("hello, world") // make a new local agent - lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, true) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, "auto") c.Assert(err, check.IsNil) // unload any keys that might be in the agent for this user @@ -213,7 +213,7 @@ func (s *KeyAgentTestSuite) TestLoadKey(c *check.C) { func (s *KeyAgentTestSuite) TestHostCertVerification(c *check.C) { // Make a new local agent. - lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, true) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, "auto") c.Assert(err, check.IsNil) // By default user has not refused any hosts. @@ -294,7 +294,7 @@ func (s *KeyAgentTestSuite) TestHostCertVerification(c *check.C) { func (s *KeyAgentTestSuite) TestHostKeyVerification(c *check.C) { // make a new local agent - lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, true) + lka, err := NewLocalAgent(s.keyDir, s.hostname, s.username, "auto") c.Assert(err, check.IsNil) // by default user has not refused any hosts: @@ -348,7 +348,7 @@ func (s *KeyAgentTestSuite) TestHostKeyVerification(c *check.C) { func (s *KeyAgentTestSuite) TestDefaultHostPromptFunc(c *check.C) { keygen := testauthority.New() - a, err := NewLocalAgent(s.keyDir, s.hostname, s.username, true) + a, err := NewLocalAgent(s.keyDir, s.hostname, s.username, "auto") c.Assert(err, check.IsNil) _, keyBytes, err := keygen.GenerateKeyPair("") diff --git a/tool/tsh/tsh.go b/tool/tsh/tsh.go index a9a18b07ccc3c..07f84b5eb7db2 100644 --- a/tool/tsh/tsh.go +++ b/tool/tsh/tsh.go @@ -196,6 +196,9 @@ type CLIConf struct { // connect to the local ssh-agent (or similar) socket at $SSH_AUTH_SOCK. UseLocalSSHAgent bool + // AddKeysToAgent specifies the behaviour of how certs are handled. + AddKeysToAgent string + // EnableEscapeSequences will scan stdin for SSH escape sequences during // command/shell execution. This also requires stdin to be an interactive // terminal. @@ -243,6 +246,7 @@ const ( // cluster. All new code should use TELEPORT_CLUSTER instead. siteEnvVar = "TELEPORT_SITE" userEnvVar = "TELEPORT_USER" + addKeysToAgentEnvVar = "TELEPORT_ADD_KEYS_TO_AGENT" useLocalSSHAgentEnvVar = "TELEPORT_USE_LOCAL_SSH_AGENT" clusterHelp = "Specify the cluster to connect" @@ -279,7 +283,9 @@ func Run(args []string, opts ...cliOption) error { app.Flag("gops-addr", "Specify gops addr to listen on").Hidden().StringVar(&cf.GopsAddr) app.Flag("skip-version-check", "Skip version checking between server and client.").BoolVar(&cf.SkipVersionCheck) app.Flag("debug", "Verbose logging to stdout").Short('d').BoolVar(&cf.Debug) + app.Flag("add-keys-to-agent", "Controls how keys are handled. Valid values are yes, no, auto or only").Short('k').Envar(addKeysToAgentEnvVar).Default("auto").StringVar(&cf.AddKeysToAgent) app.Flag("use-local-ssh-agent", fmt.Sprintf("Load generated SSH certificates into the local ssh-agent (specified via $SSH_AUTH_SOCK). You can also set %v environment variable. Default is true.", useLocalSSHAgentEnvVar)). + Hidden(). Envar(useLocalSSHAgentEnvVar). Default("true"). BoolVar(&cf.UseLocalSSHAgent) @@ -460,6 +466,20 @@ func Run(args []string, opts ...cliOption) error { return trace.Wrap(err) } + // parameter must be yes, no, auto or only + switch cf.AddKeysToAgent { + case "yes": + break + case "no": + break + case "auto": + break + case "only": + break + default: + return trace.BadParameter("valid values for parameter add-keys-to-agent are yes, no, auto or only, got %s", cf.AddKeysToAgent) + } + // Read in cluster flag from CLI or environment. readClusterFlag(&cf, os.Getenv) @@ -1634,11 +1654,10 @@ func makeClient(cf *CLIConf, useProfileLogin bool) (*client.TeleportClient, erro // (not currently implemented) or set to 'none' to suppress browser opening entirely. c.Browser = cf.Browser - // Do not write SSH certs into the local ssh-agent if user requested it. - // - // This is specifically for gpg-agent, which doesn't support SSH - // certificates (https://dev.gnupg.org/T1756) - c.UseLocalSSHAgent = cf.UseLocalSSHAgent + c.AddKeysToAgent = cf.AddKeysToAgent + if cf.UseLocalSSHAgent == false { + c.AddKeysToAgent = "off" + } c.EnableEscapeSequences = cf.EnableEscapeSequences