Unlocking SSH keys using pass
I use
pass as my
password manager. It stores passwords and other secrets as
GPG-encrypted files in a Git repository. I host that repo on GitHub
and have clones of it on my work and personal computers. I store
nearly all of my personal passwords in pass, including
passwords for my GitHub SSH keys. I’ve gone through three ways of
unlocking these SSH keys.
I started by running pass -c SSH/Ubuntu to copy my SSH
key’s password to the keyboard, running a Git command like
git push, then pasting my password into the prompt. I
wasn’t satisfied with how much typing this process required. Plus,
sometimes I’d forget to copy the password to my clipboard before
running the Git command. I’d have to Ctrl+C that command and rerun it
after copying the password. Even though I used
ssh-agent and wasn’t unlocking my SSH key that often, I
decided to automate this process.
My second method used a script I called sshpass. Here it
is in its entirety:
#!/usr/bin/expect -f
set password [exec pass SSH/Ubuntu]
spawn ssh-add
expect "passphrase"
send -- "$password\r"
interact
This is an expect script. According to
its man page,
expect is “a program that ‘talks’ to other interactive
programs according to a script”. In this case, the script instructs
expect to store the output of
pass SSH/Ubuntu in the variable password,
then run ssh-add, wait for it to prompt for a password,
and write the password to ssh-add’s stdin. For reasons I
don’t totally understand, the interact command, which
attaches the terminal’s standard streams to ssh-add’s, is
necessary to let ssh-add finish running.
Now, all I had to do was run sshpass before running a Git
command, but I’d still sometimes forget to run sshpass.
Finally, this week, I came across a third solution that addresses this
issue on Linux, mainly based on
this blog post.
First, I added AddKeysToAgent yes to my SSH configuration
at ~/.ssh/config. This causes ssh to add
keys to ssh-agent when they’re unlocked. This is
important because we’ll no longer be explicitly calling
ssh-add.
Then, I added the following to my .zshrc:
eval $(ssh-agent) > /dev/null
export SSH_ASKPASS=~/ssh-pass-passphrase.bash
alias ssh="setsid -w ssh"
alias git="setsid -w git"
This starts an instance of ssh-agent, then sets the
SSH_ASKPASS environment variable. If this variable is
set, when ssh needs a password to unlock an SSH key, it
runs the script pointed at by the variable and uses the output as the
password. Note that this only works if ssh isn’t run in a
terminal. That’s the purpose of the aliases for ssh and
git: setsid runs these programs in a new
shell session that isn’t associated with the terminal in which I’m
running ssh or git. The -w flag
causes setsid to wait until ssh or
git exits before exiting.
~/ssh-pass-passphrase.bash is a script that writes my SSH
key’s password to stdout:
#!/bin/bash
pass SSH/Ubuntu
Now, I can run Git commands without worrying if my SSH key is unlocked
beforehand. If it isn’t, ssh automatically calls
pass to get my SSH key’s password and unlocks the key
before Git runs the command.
So far, this setup only works on Ubuntu on my personal computer. My
next step is to get it working on Mac OS, so I can use it on my work
computer too. After that, I’d like to look into ways to avoid spawning
an instance of ssh-agent in every terminal I run this
command in.