Background
As I mentioned in my previous post, I switched to using SSH key auth for GitHub and Azure DevOps Repos a long time ago and found it a positive experience. At first I was a bit lazy and didn’t use passphrases on my keys, and just kept a copy of my keys in the .ssh
folder in my User folder in Windows and another copy in ~/.ssh
in WSL.
For day-to-day working this worked okay, but I finally got round to adding passphrases to my keys a while back and was less happy with the setup at that point. My previously suppressed niggles around having the keys in multiple places re-surfaced once I had to add handle passphrases in multiple systems on the same machine!
As it happens, recent versions of Windows ship with the OpenSSH Agent and Server but I didn’t want to have two SSH Authentication services each with their own set of keys. My last post shows how to get git in Windows to use the OpenSSH Agent to retrieve keys. In this post, I’ll walk through the journey to get SSH in WSL using keys from the Windows OpenSSH Agent. For details on installing and setting up the Windows OpenSSH Agent see the docs.
If you just want to set up the WSL SSH forwarding then skip to the final solution! Otherwise, let me take you on a tour…
Initial investigation
As I mentioned, my most common usage of SSH keys day-to-day is as a way to authenticate to git remotes. This lead me to this page in the GitHub docs which discusses SSH Agent forwarding and mentions the SSH_AUTH_SOCK
environment variable.
After some reading it turns out that SSH_AUTH_SOCK
controls the path to the UNIX socket that is used by SSH tools to communicate with the SSH Agent. This seemed like an interesting start, and fortunately I’d previously stumbled across the npiperelay from John Starks. The docs and examples for npiperelay
have some examples for using npiperelay
with the socat
utility as a way of forwarding Linux sockets in WSL to named pipes in Windows - sounds perfect!
Get the tools
First off, I grabbed socat
:
# replace with the appropriate install for non-Debian/Ubuntu distros ;-)
sudo apt install socat
Next up, I built npiperelay
, but happily John merged a PR that added releases, so you can now grab the latest release from GitHub. Once downloaded, extract the npiperelay.exe
and place it somewhere in your PATH
.
Exploring npiperelay
and socat
The docker-relay example from npiperelay
shows how to forward the /var/run/docker.sock
socket to the //./pipe/docker_engine
named pipe.
# Example from https://github.com/jstarks/npiperelay/blob/master/scripts/docker-relay
exec socat UNIX-LISTEN:/var/run/docker.sock,fork,group=docker,umask=007 EXEC:"npiperelay.exe -ep -s //./pipe/docker_engine",nofork
I took a bit of time to read up on socat
. My understanding of the previous command is that it listens to the /var/run/docker.sock
UNIX socket and when it gets a connection it launches the EXEC
command. So in this case it starts an instance of npiperelay.exe
using the specified arguments and forwards the data received on the UNIX socket to the input stream for npiperelay
. On the other side, npiperelay
takes the data sent to its input stream and forwards it to the //./pipe/docker_engine
named pipe.
In this way, npiperelay
and socat
combine to be a way to forward from a UNIX socket to a Windows named pipe - pretty cool!
Testing it with SSH_AUTH_SOCK
Armed with new tools and knowledge, I deleted my SSH keys from ~/.ssh
in WSL and then ran the following commands to set SSH_AUTH_SOCK
and start socat.
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &
Once that is set up I gave it a test to authenticate with GitHub (note that I’ve already added the key to GitHub and the Windows OpenSSH Agent):
$ ssh -T git@github.com
Hi stuartleeks! You've successfully authenticated, but GitHub does not provide shell access.
Success!
Or almost :-)
It turns out that there are a few challenges with this, at least there are when you put it into your .bashrc
or .bash_profile
so that it runs automatically when you start your terminal.
Fixing up the script
In this section we’ll walk through the series of tweaks that I ended up making to the script. This will help to understand the script but feel free to skip ahead to the final solution if you are keen to get this set up.
Handling multiple instances of terminal
The first issue I hit was running multiple instances of the terminal. To overcome this I added a check via ps -aux
to look to see if the socat
/npiperelay
command was running.
My first attempt at this was:
ALREADY_RUNNING=$(ps -aux | grep -q "npiperelay.exe -ei -s //./pipe/openssh-ssh-agent"; echo $?)
Unfortunately, this always returned 0
. Dropping the -q
and running it interactively, I realised that the ps... | grep...
command was being listed in the ps
output, so it alwasy got a match. Fortunately, a colleague had pointed me to a nice way round this a while back:
ALREADY_RUNNING=$(ps -aux | grep -q "[n]piperelay.exe -ei -s //./pipe/openssh-ssh-agent"; echo $?)
Note the square brackets around the n
in [n]piperelay.exe
. That gives a regular expression that requires that character to be an n
, so we still match the original search string for the grep
stage. But, it changes the command so that it no longer matches itself!
With this in place, I still hit an issue where sometimes it wasn’t finding the existing process. This took me a little longer to track down, but the issue was that the ps
output gets truncated to try to fit in the terminal width. Adding the -ww
to the ps
command forced it to not truncate:
ALREADY_RUNNING=$(ps -auxww | grep -q "[n]piperelay.exe -ei -s //./pipe/openssh-ssh-agent"; echo $?)
Clearing out previous sockets
Unfortunately, my notes for this part are missing some details, but I hit a scenario where sometimes the socket already existed.
For this I added a test using the -S
condition (a list of condition expressions can be found here).
If our socat
command isn’t running then I added this check before starting it:
if [[ -S $SSH_AUTH_SOCK ]]; then
# not expecting the socket to exist as the forwarding command isn't running (http://www.tldp.org/LDP/abs/html/fto.html)
echo "removing previous socket..."
rm $SSH_AUTH_SOCK
fi
Keeping the forwarding running
I also wanted to keep the socat
command running when I closed the terminal (ready for the next session). After some searching, I found this StackOverflow question which pointed me to setsid.
Adding setsid
and suppressing the output resulted in
(setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &) > /dev/null 2>&1
At this point, it can all be put together…
Final solution
The end result is that once socat
is installed in WSL, and npiperelay
is (See how to get the tools), you can add this script to your ~/.bash_profile
or ~/.bashrc
.
Note that this also requires that you have set up the SSH Agent in Windows and added your keys to it (as mentioned in the previous post).
# Configure ssh forwarding
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
# need `ps -ww` to get non-truncated command for matching
# use square brackets to generate a regex match for the process we want but that doesn't match the grep command running it!
ALREADY_RUNNING=$(ps -auxww | grep -q "[n]piperelay.exe -ei -s //./pipe/openssh-ssh-agent"; echo $?)
if [[ $ALREADY_RUNNING != "0" ]]; then
if [[ -S $SSH_AUTH_SOCK ]]; then
# not expecting the socket to exist as the forwarding command isn't running (http://www.tldp.org/LDP/abs/html/fto.html)
echo "removing previous socket..."
rm $SSH_AUTH_SOCK
fi
echo "Starting SSH-Agent relay..."
# setsid to force new session to keep running
# set socat to listen on $SSH_AUTH_SOCK and forward to npiperelay which then forwards to openssh-ssh-agent on windows
(setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork &) >/dev/null 2>&1
fi
With all this in place your SSH keys will be handled by the Open SSH Agent in Windows and SSH in WSL will access them from there!
P.S. If you liked this, you may also like my book “WSL 2: Tips, Tricks and Techniques” which covers tips for working with WSL 2, Windows Terminal, VS Code dev containers and more https://wsl.tips/book :-)