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"
}