WSL SSH agent and Git

This article describes how you can say goodbye to the hassle of copying and pasting your SSH passphrase every time you want to connect to a machine via ssh, push/pull from a git repository via ssh or sign a commit/tag/push via ssh regardless of your shell being Powershell, Bash or anything else.

This is useful if you need to be able to use ssh/git fully in Windows and WSL. If you do all your work only in one of these environments, you probably do not need this.

What your current setup probably looks like

If you use both Windows and WSL for ssh/git, you probably either have a passphrase-less ssh key or you have to enter your passphrase every time. Another option is that you have two ssh agents running, one in Windows and one in WSL. This is not ideal, as you have to enter your passphrase twice. You probably also have to enter your passphrase every time you open a new WSL terminal.

Solution

Some solutions I found online get very complex (and usually are unstable). For example people try to map windows pipes to unix sockets or similar. We are going for a much simpler solution in this article.

We are going to setup WSL so that it uses the Windows ssh agent. This way we will have only a single shared ssh agent running on our machine, which will make our life much easier.

Initial setup

Firstly, we need to setup OpenSSH on Windows. You probably already have this working, but if not you can follow these instructions. This ssh agent will be used by both Windows and WSL. Git/ssh in Windows should work out of the box with this setup. In the rest of the article we will be configuring WSL to use the Windows ssh agent.

SSH

To access the Windows ssh agent in WSL, we are going to use the Linux ssh.exe command. This ssh client directly communicates the Windows ssh agent.

Let’s see if everyting works as expected. If you have your ssh key setup on Github, you can use the following command in WSL:

ssh.exe -T git@github.com # you can use any other ssh server

Cloning/fetching/… with git via ssh

What about git? Git on Linux by default uses the Linux ssh command to connect to remote repositories. To make git use the Windows ssh command, use the following command:

git config --global core.sshCommand ssh.exe

Now you can easily clone/pull/push etc. from git repositories. To test this, clone any repository via ssh in WSL:

git clone git@github.com:octocat/Hello-World.git # you can use any other git repository

Commit/tag/push signatures in Git

If you want to use your ssh key to sign commits/tags/pushes, you need to tell git in WSL to use the Windows ssh agent again. For that we are going to use the ssh-add.exe command. We also need to tell git to use the ssh-keygen.exe command, which is the counterpart to the ssh-keygen Linux command. For some reason, these programs are incompatible and since we are going to use the ssh-add.exe command to provide the keys, we also need to use the Windows variant ssh-keygen to make the signatures.

It might seem like we have everything ready and just need to run a few commands, but it is not that simple. There is one edge case, in which it does not work. Consider the following scenario. You make a git repository in the Windows file system. For some reason you need to open this git repository in WSL and make a signed commit. If you try this, you will get an error:

$ GIT_TRACE=1 git ci -m 'my great message'
15:45:35.924860 git.c:742               trace: exec: git-ci -m 'wsl ssh agent and git'
15:45:35.924930 run-command.c:668       trace: run_command: git-ci -m 'wsl ssh agent and git'
15:45:36.072419 git.c:396               trace: alias expansion: ci => commit
15:45:36.072466 git.c:803               trace: exec: git commit -m 'wsl ssh agent and git'
15:45:36.072495 run-command.c:668       trace: run_command: git commit -m 'wsl ssh agent and git'
15:45:36.091469 git.c:455               trace: built-in: git commit -m 'wsl ssh agent and git'
15:45:36.338328 run-command.c:668       trace: run_command: ssh-add.exe -L
15:45:36.409375 run-command.c:668       trace: run_command: ssh-keygen.exe -Y sign -n git -f /tmp/.git_signing_key_tmpbPSuUD /tmp/.git_signing_buffer_tmpX3z9UD
error: Couldn't load public key /tmp/.git_signing_key_tmpbPSuUD: No such file or directory?

fatal: failed to write commit object

What the hell? Why can our program not see the file? The reason is that since our working directory is in the Windows file system, the root of the file system, which is denoted by the backslask symbol /, links to the root of the Windows file system, but our Linux git created the temporary files in the Linux files system. To fix this, we are going to create a simple wrapper shell script, which is going to translate the paths for us. Since this is a common problem when working accross different file systems, we have a utility program wslpath, which solves this problem. Our shell script is going to look like this:

ssh-keygen.exe "$1" "$2" "$3" "$4" "$5" "$(wslpath -w "$6")" "$(wslpath -w "$7")"

Save this script as gpgSshProgram.sh and make it executable:

chmod +x gpgSshProgram.sh

Now we are ready to configure git to use our wrapper script instead of ssh-keygen.exe directly. To do this, run the following commands:

git config --global gpg.ssh.defaultKeyCommand "ssh-add.exe -L"
git config --global gpg.ssh.program "/absolute/path/to/gpgSshProgram.sh"

Note that the path must be absolute.

Now you can sign commits/tags/pushes using git in WSL. To try this out, you need to set git to use ssh for making signatures (default is openpgp). To accomplish this, use this command:

git config --global gpg.format ssh

Then you can make a signed commit using this command:

git commit -S

Conclusion

This solution is very simple and makes working with ssh and git super easy. All of this is possible thanks to WSL being awesome and git being highly customizable. The tooling available to developers is amazing, but to figure out the solution described in this article was not easy by any means. I hope this article helped you make your workflow more effecient and pleasant.