As part of a not-so-secret project, I’m writing a python script to make git commits on my behalf. I’d like these commits to be made “from” a different user.
GitHub in fact has a page on multiple SSH key usage, which makes the entire process look deceptively simple. Just create an alternate domain name and specify the IdentityFile for the domain name. Sounds simple, right? Ha.
So I follow the instructions, use an alternate subdomain… and the commits are still appearing from my normal git account. To eliminate the ~/.ssh/config file as a source of error, I pass -i on the command line and manually specify a key file… still no effect. What’s going on here?
sauron:semaps drew$ ssh -i ~/.ssh/glados_key -Tv firstname.lastname@example.org OpenSSH_5.6p1, OpenSSL 0.9.8r 8 Feb 2011 debug1: Reading configuration data /Volumes/y/drew/.ssh/config debug1: Reading configuration data /etc/ssh_config debug1: Applying options for * debug1: Connecting to github.com [220.127.116.11] port 22. debug1: Connection established. debug1: identity file /Volumes/y/drew/.ssh/glados_key type 1 debug1: identity file /Volumes/y/drew/.ssh/glados_key-cert type -1 debug1: Remote protocol version 2.0, remote software version OpenSSH_5.1p1 Debian-5github2 debug1: match: OpenSSH_5.1p1 Debian-5github2 pat OpenSSH* debug1: Enabling compatibility mode for protocol 2.0 debug1: Local version string SSH-2.0-OpenSSH_5.6 debug1: SSH2_MSG_KEXINIT sent debug1: SSH2_MSG_KEXINIT received debug1: kex: server->client aes128-ctr hmac-md5 none debug1: kex: client->server aes128-ctr hmac-md5 none debug1: SSH2_MSG_KEX_DH_GEX_REQUEST(1024<1024<8192) sent debug1: expecting SSH2_MSG_KEX_DH_GEX_GROUP debug1: SSH2_MSG_KEX_DH_GEX_INIT sent debug1: expecting SSH2_MSG_KEX_DH_GEX_REPLY debug1: Host 'github.com' is known and matches the RSA host key. debug1: Found key in /Volumes/y/drew/.ssh/known_hosts:3 debug1: ssh_rsa_verify: signature correct debug1: SSH2_MSG_NEWKEYS sent debug1: expecting SSH2_MSG_NEWKEYS debug1: SSH2_MSG_NEWKEYS received debug1: Roaming not allowed by server debug1: SSH2_MSG_SERVICE_REQUEST sent debug1: SSH2_MSG_SERVICE_ACCEPT received debug1: Authentications that can continue: publickey debug1: Next authentication method: publickey <strong>debug1: Offering RSA public key: /Volumes/y/drew/Dropbox/keys/jeremyfletcher/jeremyfletcher.pem debug1: Authentications that can continue: publickey debug1: Offering RSA public key: /Volumes/y/drew/.ssh/id_rsa</strong> debug1: Remote: Forced command: gerve drewcrawford debug1: Remote: Port forwarding disabled. debug1: Remote: X11 forwarding disabled. debug1: Remote: Agent forwarding disabled. debug1: Remote: Pty allocation disabled. debug1: Server accepts key: pkalg ssh-rsa blen 279 debug1: Remote: Forced command: gerve drewcrawford debug1: Remote: Port forwarding disabled. debug1: Remote: X11 forwarding disabled. debug1: Remote: Agent forwarding disabled. debug1: Remote: Pty allocation disabled. debug1: Authentication succeeded (publickey). Authenticated to github.com ([18.104.22.168]:22). debug1: channel 0: new [client-session] debug1: Requesting email@example.com debug1: Entering interactive session. debug1: Sending environment. debug1: Sending env LANG = en_US.UTF-8 Hi drewcrawford! You've successfully authenticated, but GitHub does not provide shell access. debug1: client_input_channel_req: channel 0 rtype exit-status reply 0 debug1: client_input_channel_req: channel 0 rtype firstname.lastname@example.org reply 0 debug1: channel 0: free: client-session, nchannels 1 Transferred: sent 2736, received 2920 bytes, in 0.1 seconds Bytes per second: sent 27973.8, received 29855.0 debug1: Exit status 1 sauron:semaps drew$
Notice how no part of the key that is “offered” is in fact the key I specified on the command line. This is in contrast with the ssh manpage, which says:
Selects a file from which the identity (private key) for RSA or DSA authentication is read. The default is ~/.ssh/identity for protocol version 1, and ~/.ssh/id_rsa and ~/.ssh/id_dsa for protocol version 2. Identity files may also be specified on a perhost basis in the configuration file. It is possible to have multiple -i options (and multiple identities specified in configuration files). ssh will also try to load certificate information from the filename obtained by appending -cert.pub to identity filenames.
My interpretation of “selects” is that the file specified should be the file attempted, (or if multiple files are provided, the files). Not just “append some extra files to the list of things to try”, which is in fact the implementation. This is really bad documentation for what should by all rights be the most stable piece of software anywhere.
So where is this behavior documented? It’s not. There are lots of authoritatively-sounding blog posts like “Force SSH to use the given private key“, another article with the exact same title “Force SSH Client To Use Given Private Key ( identity file )“, an article called “Manage multiple SSH private keys with IdentityFile“, and even Joel Spolsky has failed us. As best as I can tell, all the available resources give the misleading impression that -i or IdentityFile actually do something other than vaguely suggest to SSH where to find the keys as a last resort.
There is one single place in my hour-long Google search that touched on the problem:
…identities in an agent take precedence over identities used manually. If an identity in the agent can successfully authenticate, there’s no way to override the agent manually with the -i command-line option or the IdentityFile keyword. So in the earlier example, there is literally no way to use the identity id-normal. The obvious attempt:
$ ssh -i id-normal server.example.com
still authenticates with id-backup s because it is loaded first into the agent. Even nonloaded identities can’t override the agent’s selection. For example, if you load only one identity into the agent and try authenticating with the other:
$ ssh-add id-normal $ ssh -i id-backups server.example.com
your ssh connection authenticates with the loaded identity, in this case id-normal, regardless of the -i option. This undocumented behavior drove us insane until we figured out what was happening.
Unfortunately “The Secure Shell: The Definitive Guide” is itself having authentication woes (oh, the irony!) and only Google’s Cache is available. The “definitive guide” moniker notwithstanding, there is absolutely no solution offered for the issue, only a discussion of how unfortunate the problem is.
What you actually do, it turns out, is use the IdentitiesOnly “yes” directive in ~/.ssh/config. This means that none of the ssh-agent magical keys will get in the priority queue, and only things explicitly specified either in config or on the commandline are allowed.
Once you know what to Google, you find that actually a lot of places mention IdentitiesOnly, for instance SuperUser’s excellent discussion, the manpage for ssh_config (I would never have thought to look up a manpage for a config file…), and in fact there’s even a pull request on GitHub’s documentation to advise users to do this. But it does appear to be one of the Internet’s better-kept secrets.