Monday, July 10, 2017

Using ssh-agent for Remote Login Sessions

I work on a lot of remote systems via ssh logins.  It's very common for me to be remotely logged in to several systems throughout the day.  Not everything I do is from my workstation's login session.

A lot of the things I do, such as source code control with git, rely on ssh keys.  I have passphrases on my keys so every time I use git, ssh will prompt for the passphrase -or- will ask the running ssh-agent for the credentials.  On my graphical workstations, I have my X sessions set up to start ssh-agent upon log in and add all of the keys in ~/.ssh to the agent.  That way things like git push and git pull work quickly and without prompting me for the passphrase.  When I log out, the session goes away.

This does not work for remote sessions.  Once I log in to a system, I cannot use the agent running on my workstation.  I have added a block to my ~/.zshrc file to start an ssh-agent if one is not already running.  This handles interactive shell logins via ssh or at the console.  There are many ways to do this, but here's how I'm doing it (lines broken for posting here, anywhere there is a backslash, join it with the previous line and remove the backslash):

if [ -d "${HOME}/.ssh" ] && \
   [ ! -f "${HOME}/.noagent" ] && \
   [ -z "${TMUX}" ]; then
    # Start the SSH agent
    if [ -z "${SSH_AGENT_PID}" ] && \
       ( [ -z "${SSH_AUTH_SOCK}" ] || \
         [ ! -r "${SSH_AUTH_SOCK}" ] ); then
        eval $(ssh-agent)

        for pubkey in ${HOME}/.ssh/*.pub ; do
            privkey="$(basename ${pubkey} .pub)"
            [ -f ${HOME}/.ssh/${privkey} ] && \
                ssh-add ${HOME}/.ssh/${privkey}

Like I said, there are many ways to do this, but this is how I managed it.  I will walk through how this works:
  1. First, you need to have ~/.ssh.  I should modify this to make sure you have at least one public key, but I've made that assumption here because that will always be the case for.
  2. I also honor the ~/.noagent file in my home directory.  I can disable this entire block by touch ~/.noagent and it will skip right over it.  This file does not require anything in it, just that it exists.
  3. The test for ${TMUX} is important to ensure that each new pane I open in tmux does not start a new ssh-agent.  If you are using GNU screen, I am sure there's a similar test.
  4. The nested if will then check to see if an existing agent is running.  That's the test for SSH_AGENT_PID and SSH_AUTH_SOCK.  If those exist, I am going to assume ssh-agent is running because I otherwise do not use those environment variables,
  5. The eval line runs ssh-agent (which will background itself) and then sources in its stdout, which contains the SSH_AGENT_PID and SSH_AUTH_SOCK environment variables.
  6. The for loop iterates over all of your public keys and will add each one to the agent if there is a corresponding private key.  This part is interactive as ssh-add will prompt you for the passphrase for each key.
Another thing to note with zsh, which is the shell I'm using, is to avoid having this block in both ~/.zshrc and ~/.zprofile.  If you have both files and they are different, put this block in ~/.zprofile.  If you have one and the other is a symlink to the other, get rid of the symlink because the block will execute twice on interactive shell logins.

Lastly, you should make sure you kill the agent when you log out of your session.  For zsh, I add this to my ~/.zlogout file:

# Kill any running ssh-agent for this session
[ -z "${SSH_AGENT_PID}" ] || ssh-agent -k

Other shells have other mechanisms of running commands on logout.

So it's not perfect, but it does get me an agent running in my remote login sessions and that was my main goal.  I may make some tweaks to this over time, but for now this is what I'm using.

No comments: