Packet Disarray

Despatches from longb4

Setting Up Passphrase-protected SSH Keys Without Repetitive Typing

I’ve recently begun exploring Ansible, which is a Python-based configuration management and orchestration system that uses ssh (when in unix land). As one can imagine, typing your password for every connection to each server gets tedious. The solution to this problem is not to use simpler passwords!

Setting up keys

The canonical way of resolving this is to set up ssh keys with ssh-keygen. By default, this deposits your keys in ~/.ssh, with your public key at id_rsa.pub and your private key (keep this secret!) at id_rsa. Then, if you append the contents of id_rsa.pub to /home/someuser/.ssh/authorized_keys on some other machine, you can do ssh someuser@someothermachine to log in as that user without typing a password. (This is assuming your system is set up similar to the vast majority of Unix machines with ssh daemons running.)

In comparison to an attacker trying to guess a human-memorable password, brute forcing a 2048-bit key is outside the realm of possibility. So, systems that only allow ssh access with keys are considerably more secure. Some people configure their machines this way, but it’s also very common for central git systems like GitHub and gitolite to require ssh keys for authentication.

Keys with passphrases

One issue raised by taking the default options in ssh-keygen is that if a key without a passphrase is authorized by remote machines but that key gets compromised, an attacker could get access to any of those machines without even needing a password. The easy way to mitigate this is to choose a passphrase when generating ssh keys.

Securely storing key passphrases

Of course, it may seem like putting a passphrase on your key gets you back where you started from the perspective of convenience. This is where a tool called ssh-agent comes in. Running ssh-agent starts a process that lets you add ssh private keys — only typing your passphrase once, when you add the key — and supplies the key when you initiate an ssh connection. This prevents you from needing to type the passphrase each time you connect.

When you run ssh-agent, it spits out some information about the process it started:

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-vyRGU0IGInTm/agent.28010; export SSH_AUTH_SOCK;
SSH_AGENT_PID=28011; export SSH_AGENT_PID;
echo Agent pid 28011;

These are just commands that (for example) a script could use to set up ssh-agent for you by putting the SSH_AUTH_SOCK and SSH_AGENT_PID variables in your environment. We’ll do it by hand.

$ SSH_AUTH_SOCK=/tmp/ssh-vyRGU0IGInTm/agent.28010; export SSH_AUTH_SOCK;
$ SSH_AGENT_PID=28011; export SSH_AGENT_PID;
$ env | grep SSH_A
SSH_AGENT_PID=28011
SSH_AUTH_SOCK=/tmp/ssh-vyRGU0IGInTm/agent.28010

(If you want to use this ssh-agent in multiple sessions/terminals on this machine, you’ll need to add those variables to your environment again. You can’t use ssh-agent without those environment variables set to valid values.)

Once your session is set up to use ssh-agent, you can add your private key with ssh-add:

$ ssh-add
Enter passphrase for /home/longb4/.ssh/id_rsa:
Identity added: /home/longb4/.ssh/id_rsa (/home/longb4/.ssh/id_rsa)

Once your key is added, you can ssh to a user and machine that trusts your public key without needing to type a passphrase.

Easily transferring public keys

A simple way to get your local id_rsa.pub into a remote authorized_keys file is with scp and shell redirection:

$ scp ~/.ssh/id_rsa.pub user@otherhost:
$ ssh user@otherhost
user@otherhost's password:
otherhost$ cat id_rsa.pub >> .ssh/authorized_keys
otherhost$ logout
Connection to otherhost closed.
$ ssh user@otherhost
otherhost$

However, this requires more interactivity than I’d like. To improve the process, we can use ssh-copy-id, which does the same thing:

$ ssh-copy-id user@otherhost
user@otherhost's password:
Now try logging into the machine, with "ssh 'user@otherhost'", and check in:

  ~/.ssh/authorized_keys

to make sure we haven't added extra keys that you weren't expecting.

Transferring public keys even more easily

I mentioned Ansible at the beginning because even though I already had a few of my servers set up to trust my key, not all of them did, and I really didn’t feel like typing my password a few dozen times to transfer my key to the rest of them. An issue with many tools like sshpass that let you non-interactively give ssh your password is that they expect your password on the command line (which exposes your password to anyone running ps) or in a file (which hits disk in plaintext if you didn’t bother setting up a ramdisk).

Thankfully, there is a delightful tool called ssh-deploy-key, available on PyPI, with documentation available. Assuming your password is the same on all remote hosts (because you’re using central authentication, right?), it takes your password once without exposing it in ps or on disk:

(virtualenv) $ pip install ssh-deploy-key
[...]
(virtualenv) $ ssh-deploy-key otherhost1 otherhost2
Enter common password for remote hosts:
Distributing key '/home/longb4/.ssh/id_rsa.pub' to remote hosts in overwrite mode.
  copying key to longb4@otherhost1:~/.ssh/authorized_keys                   SUCCESS
  copying key to longb4@otherhost2:~/.ssh/authorized_keys                   SUCCESS

Then, finally, once your public key is distributed to your remote hosts and you have ssh-agent working happily, you can play with Ansible on remote hosts without typing any passwords to log in:

$ ansible -i inventory.ini -e 'ansible_python_interpreter="/usr/bin/env python"' all -m ping
otherhost1 | success >> {
    "changed": false,
    "ping": "pong"
}

otherhost2 | success >> {
    "changed": false,
    "ping": "pong"
}