Skip to content

Instantly share code, notes, and snippets.

@QNimbus
Last active April 22, 2025 22:47
Show Gist options
  • Save QNimbus/4a731a9f76fadf444ddf6dc889bd81ff to your computer and use it in GitHub Desktop.
Save QNimbus/4a731a9f76fadf444ddf6dc889bd81ff to your computer and use it in GitHub Desktop.
SSH Agent forwarding WSL2

SSH Agent forwarding in WSL2

Download npiperelay

To communicate between WSL and the 1Password SSH agent, we'd need to use npiperelay. This tool allows WSL to communicate with Windows' named pipes.

To install it, we need to open the GitHub repository and download the latest release. At the time of writing this post the latest release is v0.1.0 from July 2, 2020.

Unzip it and paste the npiperelay.exe file in any folder that's configured in your system's PATH. For example %USERPROFILE%/.wsl/mpiperelay.exe and then add %USERPROFILE%/.wsl to your PATH.

Connect WSL with 1Password's SSH agent

Now that we have the prerequisites fulfilled, we can connect our WSL distro with the SSH agent.

I'm using Ubuntu as my WSL distro, this may change if you're using another distro.

We need to install socat which is a utility to transfer data between channels, this tool will use npiperelay to then communicate with the named pipes.

sudo apt install socat

In your WSL terminal create the wsl-ssh-agent-relay script, ensure it's executable and copy/paste the contents of wsl-ssh-agent-relay script.

mkdir -p ~/.local/bin
touch ~/.local/bin/wsl-ssh-agent-relay

Add following lines to your shell rc file: (e.g. .zshrc of .bashrc)

${HOME}/.local/bin/wsl-ssh-agent-relay start
export SSH_AUTH_SOCK=${HOME}/.ssh/wsl-ssh-agent.sock

SSH config

Ensure that at least the following is added to your SSH client config (except windows):

# ~/.ssh/config

Host *
  IdentityAgent ~/.ssh/wsl-ssh-agent.sock

Testing

Useful links

#!/bin/bash
#### Add following lines to your shell rc file (.zshrc .bashrc)
# ${HOME}/.local/bin/wsl-ssh-agent-relay start
# export SSH_AUTH_SOCK=${HOME}/.ssh/wsl-ssh-agent.sock
# If you do not want the ssh agent relay require your ssh agent
# to be running at the time relay is started add the option -s
# to wsl-ssh-agent-relay.
# For debugging startup problems uncomment next line
# exec 2> >(tee -a -i "$HOME/error.log")
#### Assuming ~/winhome links to %USERPROFILE on Windows side
RELAY_BIN="${HOME}/winhome/.wsl/npiperelay.exe"
PIDFILE="${HOME}/.ssh/wsl-ssh-agent-relay.pid"
WSL_AGENT_SSH_SOCK="${HOME}/.ssh/wsl-ssh-agent.sock"
log() {
echo >&2 "$@"
}
is_pid_running() {
if [[ -z "$1" ]]; then
return 1
fi
ps -p "$1" >/dev/null
return $?
}
_cleanup() {
log "Cleaning up relay to ${WSL_AGENT_SSH_SOCK}..."
if is_pid_running "${SOCAT_WSL_AGENT_SSH_PID}"; then
kill -SIGTERM "${SOCAT_WSL_AGENT_SSH_PID}" || log "Failed."
fi
}
die() {
if [[ -n "$1" ]]; then
log "$1"
fi
log "Exiting."
exit 1
}
usage() {
log "Usage: wsl-ssh-agent-relay [OPTIONS] COMMAND"
log ""
log " SUMMARY: Relay Windows openssh named pipe to local SSH socket in order to integrate WSL2 and host."
log " To debug use foreground command"
log ""
log " OPTIONS:"
log " -h|--help this page"
log ""
log " -v|--verbose verbose mode"
log ""
log " -s|--skip-test skip ssh-agent communication test"
log ""
log " COMMAND: start, stop, foreground"
}
fg_opts() {
FG_OPTS=()
# Generate opts for passing it to foreground version
if [[ -n "$VERBOSE" ]]; then
FG_OPTS+=("-v")
fi
if [[ -n "$NO_COM_TEST" ]]; then
FG_OPTS+=("-c")
fi
}
main() {
POSITIONAL=()
VERBOSE=""
SKIP_SSH_TEST=""
while (($# > 0)); do
case "$1" in
-v | --verbose)
VERBOSE="ENABLED"
shift # shift once since flags have no values
;;
-s | --skip-test)
SKIP_SSH_TEST="TRUE"
shift
;;
-h | --help)
usage
exit 0
;;
*) # unknown flag/switch
POSITIONAL+=("$1")
shift
if [[ "${#POSITIONAL[@]}" -gt 1 ]]; then
usage
die
fi
;;
esac
done
set -- "${POSITIONAL[@]}" # restore positional params
if [[ -z "$VERBOSE" ]]; then
QUIET="QUIET"
fi
case "${POSITIONAL[0]}" in
start)
fg_opts
start-stop-daemon --start --oknodo --pidfile "${PIDFILE}" --name wsl-ssh-agent-r --make-pidfile --background --startas "$0" ${VERBOSE:+--verbose} ${QUIET:+--quiet} -- foreground "${FG_OPTS[@]}"
;;
stop)
start-stop-daemon --pidfile "${PIDFILE}" --stop --remove-pidfile ${VERBOSE:+--verbose} ${QUIET:+--quiet}
;;
status)
start-stop-daemon --pidfile "${PIDFILE}" --status ${VERBOSE:+--verbose} ${QUIET:+--quiet}
local result=$?
case $result in
0) log "$0 is running" ;;
1 | 3) log "$0 is not running" ;;
4) log "$0 unable to determine status" ;;
esac
return $result
;;
foreground)
relay
;;
*)
usage
die
;;
esac
}
relay() {
trap _cleanup EXIT
[[ -f "${RELAY_BIN}" ]] || die "Unable to access ${RELAY_BIN}"
if pgrep -fx "^ssh-agent\s.+" >/dev/null; then
log "Killing previously started local ssh-agent..."
SSH_AGENT_PID="$(pidof ssh-agent)" ssh-agent -k >/dev/null 2>&1
fi
if [ -e "${WSL_AGENT_SSH_SOCK}" ]; then
log "WSL has been shutdown ungracefully, leaving garbage behind"
rm "${WSL_AGENT_SSH_SOCK}"
fi
socat UNIX-LISTEN:"\"${WSL_AGENT_SSH_SOCK}\"",fork EXEC:"\"\'${RELAY_BIN}\' -ei -s \'//./pipe/openssh-ssh-agent\'\"",nofork 1>/dev/null 2>&1 &
SOCAT_WSL_AGENT_SSH_PID="$!"
if ! is_pid_running "${SOCAT_WSL_AGENT_SSH_PID}"; then
log "Relay for ${SOCAT_WSL_AGENT_SSH_PID} failed"
return 1
fi
log "Relay is running with PID: ${SOCAT_WSL_AGENT_SSH_PID}"
if [[ -z "$SKIP_SSH_TEST" ]]; then
log -n "Polling remote ssh-agent..."
SSH_AUTH_SOCK="${WSL_AGENT_SSH_SOCK}" ssh-add -L >/dev/null 2>&1 || die "[$?] Failure communicating with ssh-agent"
log "OK"
fi
# Everything necessary checks, we are ready for actions
log "Entering wait..."
wait ${SOCAT_WSL_AGENT_SSH_PID}
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment