How I Manage SSH Identities

I manage a few servers these days, and I have several devices which access these servers. This is a description of how I manage all of these, and why, mainly as a reminder to myself.

What I Was Doing

I used to have only one SSH key, that I shared between all of my devices. Even better, it was a key without a passphrase, and I threw it all over the place: On my laptop, on my new laptop (I iMessaged it), on my phone, on my servers, etc. I used it as the sole authentication method between all of my servers. This was very easy, but it loses a few points in security.

SSH keys have a two pronged defense:

  1. The only authentication token stored anywhere visible is totally harmless: your public key. Your private key should be inaccessible to anyone
  2. Your SSH key has a passphrase known only to you, so that even if somebody maliciously gets your private key, it's still useless.

I disarmed both of these protections by having a key in more places than it needed to be, and having no passphrase on it. This was bad.

However, while important, passphrases can be hard to use. Typing in a long string of characters every time you wish to make an SSH connection can be an enormous pain, especially when pulling down a list of dependencies from GitHub. Fortunately, people thought of this and made a tool called SSH Agent. Unfortunately, SSH Agent only works with Bash compatible shells, and so it didn't work on mine, and so I left it.

What I do now

I shouldn't have left it. A quick bit of Googling got me most of the way to what I wanted. Reading the comments got me even further. So I amended my dotfiles to have these functions built in. Here's what I ended up with:


# ~/.config/fish/functions/start_agent.fish

# Start SSH Agent and set relevant variables
function start_agent
    echo "Initializing new SSH agent ..."
    ssh-agent -c | sed 's/^echo/#echo/' > $SSH_ENV
    echo "succeeded"
    chmod 600 $SSH_ENV 
    . $SSH_ENV > /dev/null
    ssh-add ~/.ssh/id_(nice_hostname)
end


# ~/.config/fish/functions/test_identities.fish

# Add my SSH identity
function test_identities
    ssh-add -l | grep "The agent has no identities" > /dev/null
    if [ $status -eq 0 ]
        ssh-add "~/.ssh/id_(nice_hostname)"
        if [ $status -eq 2 ]
            start_agent
        end
    end
end


# ~/.config/fish/config.fish

# ...

# SSH Agent
#
# Start SSH Agent if it's not already running, and add the
# id_(nice_hostname) identity.
setenv SSH_ENV "$HOME/.ssh/environment"
if [ -n "$SSH_AGENT_PID" ]
    ps -ef | grep $SSH_AGENT_PID | grep ssh-agent > /dev/null
    if [ $status -eq 0 ]
        test_identities
    end
else
    if [ -f $SSH_ENV ]
        . $SSH_ENV > /dev/null
    end
    ps -ef | grep $SSH_AGENT_PID | grep -v grep | grep ssh-agent > /dev/null
    if [ $status -eq 0 ]
        test_identities
    else
        start_agent
    end
end

# ...

What these all do is load SSH agent in a fish compatible way, setting all the right environment variables. This way, I can have a secure passphrase on my key, and only have to enter it at boot. Now, this may not look like much, but she's got it where it counts, kid. I've made a lot of special modifications myself. Well, I made one special modification: Rather than calling ssh-add with no arguments, I call it with one argument: id_(nice_hostname). This will load an SSH key that is unique to every computer I load these dotfiles on. Perfect, effortless security.

I don't just use SSH from the terminal, though. I like to use Panic's Transmit on my Mac. It's an FTP client. I log in via SFTP, of course. It's a little known fact that if you enter your SSH key's passphrase into the password field of Transmit's auth window, it will find any SSH key in your current session and use whichever one works with that passphrase. This is magical, and saved me from having to create a different SSH key with a different passphrase just for Transmit.

I also use Panic's Transmit and Prompt apps on my phone. For these, SSH key generation is handled in the apps. While it is possible to upload a key to the apps, I'm not eager to share my private keys with any third party, even one I trust. For these, I created keys in the app and exported the public key, adding it to all the servers that I need. Now I can SSH to places in my phone, and in theory do everything I need to do. Of course, the keyboard gets annoying, so it's best for smaller things.

Part 2: Managing this stuff

Even though I do not have to type in passphrases nearly as often, I still needed to keep track of them. For this I resort to my one stop shop for all vaguely sensitive information, 1Password.

Did you know 1Password even has a Server option? I have one of these for every computer I control (even my phone). At the very least, each of these contains the SSH passphrase and the public key, for easy sharing. Servers also have the password for maxbucknell, and their IP address.

The passphrases are 50 characters long, and I have no idea what they are.

This system has provided a great mix of security and convenience for me: it's almost totally secure, and it's almost totally convenient. I very rarely notice this working, which is nice.