SSH agent forwarding versus GNU screen

Problem

If you

  1. start ssh-agent on your local machine
  2. import your private key into it
  3. SSH into another remote machine with agent forwarding (-A)
  4. start a screen session
  5. SSH into a another machine just works fine
  6. disconnect from SSH
  7. login again
  8. resume the screen session
  9. try to login into that other machine again
  10. you get Permission Denied

I solved it, so it worked in all my desired situations well. (Tested connecting from a Mac to an Ubuntu Lucid machine.)

There were couple of solutions dangling around the net but none of them worked for properly for each situations I met... 

Gory details for the curious

In my system administrator mode I have to connect to shitloads of servers from where I'm connecting further to other ones. Managing account password just doesn't scale, so I'm using public key authentication. It's good until I have to login from my local machine to a remote one.

To login further from these remote machine to "even more remote" ones, these machines somehow need my private key. SSH agent forwarding has been invented to solve this exact situation. It uses a UNIX domain socket file to tunnel back key challenges and their solutions from the remote machine to the local one which actually holds the private key unencrypted in the memory. There is such a socket file created each and every new SSH connection. The name of these files are quite random, just as the assigned client TCP ports used for each connections for example.

There is an environment variable (SSH_AUTH_SOCK) which keeps track of the SSH agent's socket. Each process inherits this environment variable. Once... Which sounds obvious, right? But what happens if I disconnect from SSH and reconnect again? A new process is started remotely which will have a new socket. If a screen is started at that point, it will just pull out the previous processes running in its windows. Those processes will remember the socket file name allocated for the previous connection. There is no regular or easy way to send a message to them to update an environment variable.

The solution is to use a symlink pointing to the latest agent file and have the environment variable pointing to this symlink. It means though we can only have 1 socket per user (per remote machine). In other words you can only connect to a remote machine once to make this work. It can be a problem if you are connecting to this remote machine via other commands which use SSH as their transport layer, such as rsync, darcs, git, etc... To overcome this, we have to check what is the case. A good approximation is to check whether are we in an "interactive" session and only do this linking trick in that case.

Solution

So, here is the actual implementation with some commented out debug statements:

#echo $SSH_AUTH_SOCK >> ~/debug
# in case of rsync for example, don't modify the socket link
# also don't do a link if it has been done already
if [ "$SSH_TTY" -a "$SSH_AUTH_SOCK" != ~/.screen.sock ]; then
    ln -sfn $SSH_AUTH_SOCK ~/.screen.sock
    export SSH_AUTH_SOCK=~/.screen.sock
fi
#echo === result: $SSH_AUTH_SOCK >> ~/debug

Put it into your ~/.bashrc first. If it worked there, you can install it globally in /etc/bash.bashrc too.

2423 views and 1 response

  • Oct 8 2010, 12:45 AM
    Tamas Herman responded:
    actually, it just worked with my ssh+screen script:

    #!/bin/bash
    SCRIPT=`basename $0`; HOST=$1; REMOTE=$HOST; TITLE=`cut -d. -f1 <<< ${HOST}`
    if grep -qv 'u$' <<< $SCRIPT; then
    REMOTE=root@$HOST
    TITLE=`tr a-z A-Z <<< $TITLE`
    fi
    shift; CMD=$@
    if grep -q '^sshs' <<< $SCRIPT; then
    INTERACTIVE=-t; CMD="$@ bash -l -c 'screen -xRU -S main'"; fi
    # INTERACTIVE=-t; CMD="$@ bash -l -c 'screen -xRU -S main'"; fi
    exec screen -t $TITLE ssh -A $INTERACTIVE $REMOTE $CMD

    probably it's better to do this linking mechanism only when the ssh command is "screen..."