Skip to content

Instantly share code, notes, and snippets.

@Jaykul
Last active May 6, 2025 11:12
Show Gist options
  • Save Jaykul/19e9f18b8a68f6ab854e338f9b38ca7b to your computer and use it in GitHub Desktop.
Save Jaykul/19e9f18b8a68f6ab854e338f9b38ca7b to your computer and use it in GitHub Desktop.
SSH Agent passthru to WSL 2 (working, in Windows 11, in May 2023)

For awhile now, each time I got a new Windows laptop I would dig up strasis gist on how to set up agent forwarding for SSH in WSL2 -- but recently I tried to point someone else at it and they were very confused by it, so this is my attempt at simpler instructions.

Installation

With Chocolatey, you must use an elevated PowerShell session. If there's no choco command found, it will fall back to winget for the npiperelay install. To force using Winget even if you have choco installed, you need to download it, so you can pass parameters to it.

Easy mode: just run this in PowerShell:

iex (irm https://gist.githubusercontent.com/Jaykul/19e9f18b8a68f6ab854e338f9b38ca7b/raw/Install.ps1)

To be more cautious, click the Download zip button in the top right, unzip everything, read it through carefully, to make sure I'm not tricking you. Then run the Install.ps1 script from the unzipped files.

What does it do?

It's all designed to be run from PowerShell (5+) on Windows. When commands need to be run in WSL, it will use the wsl command to do so (as root and as you). Before you start, make sure you have wsl 2 and a distro installed (run wsl --list -v and pick a distro that has VERSION 2).

  1. Install npiperelay in Windows using chocolatey or winget
  2. Install socat in WSL using apt (sorry, if your distro isn't apt, please fork and comment 😉)
  3. Copy an ssh-agent-pipe script that starts npiperelay for you whenever you open bash
  4. Make that script executable
  5. Add that script to your .bashrc (note: better not to run this install multiple times 😝)
<#
.SYNOPSIS
Install npiperelay and socat and configure SSH_AUTH_SOCK forwarding
#>
[CmdletBinding()]
param(
# The distribution to connect the pipe to
[Parameter(Position = 0)]
$Distribution = "Ubuntu",
# The user for whom .bashrc should be modified
# Defaults to your username all lowercase
[Parameter(ParameterSetName = "Insecure")]
$Username = $Env:USERNAME.ToLower(),
# Ingore chocolatey for install (winget must be available).
[switch]$NoChocolate
)
# Install npiperelay
if (!(Get-Command npiperelay.exe -ErrorAction Ignore)) {
if (-not $NoChocolate -and (Get-Command choco -ErrorAction Ignore)) {
choco upgrade npiperelay -y
} elseif (Get-Command winget -ErrorAction Ignore) {
winget install --id=jstarks.npiperelay -e --accept-source-agreements
} else {
throw "Unable to install. Please download https://github.com/jstarks/npiperelay/releases/latest/download/npiperelay_windows_amd64.zip and extract it somewhere in your PATH"
}
}
# install socat in WSL
wsl -d $Distribution -u root apt install socat
if ($LASTEXITCODE) {
throw "Unable to install socat. I give up."
}
# create the ssh-agent-pipe script in WSL
# Ensure the carriage returns are correct (and fetch the script, if necessary):
$script = if (Test-Path $PSScriptRoot\ssh-agent-pipe.sh) {
(Get-Content $PSScriptRoot\ssh-agent-pipe.sh) -join "`n"
} else {
Invoke-RestMethod https://gist.githubusercontent.com/Jaykul/19e9f18b8a68f6ab854e338f9b38ca7b/raw/ssh-agent-pipe.sh
}
# escape $ and " so we can pass this through bash
$script = $script -replace "\$", "\$" -replace '"', '\"'
wsl -d $Distribution -u root -- bash -c "cat > /usr/local/bin/ssh-agent-pipe <<'EOF'`n${script}`nEOF"
# Make it executable
wsl -d $Distribution -u root chmod +x /usr/local/bin/ssh-agent-pipe
# Add to .bashrc for the specified user
wsl -d $Distribution -u $Username -- bash -c "echo \`"source /usr/local/bin/ssh-agent-pipe\`" >> ~/.bashrc"
#!/bin/bash
# Usage: ssh-agent-pipe [ -k | -r ]
# Options:
# -k Kill the current process (if exists) and do not restart it.
# -r Kill the current process (if exists) and restart it.
# Default operation is to start a process only if it does not exist.
export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
sshpid=$(ss -ap | grep "$SSH_AUTH_SOCK")
if [ "$1" = "-k" ] || [ "$1" = "-r" ]; then
sshpid=${sshpid//*pid=/}
sshpid=${sshpid%%,*}
if [ -n "${sshpid}" ]; then
kill "${sshpid}"
else
echo "socat not found or PID not found"
fi
if [ "$1" = "-k" ]; then
exit
fi
unset sshpid
fi
if [ -z "${sshpid}" ]; then
rm -f $SSH_AUTH_SOCK
( setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork & ) >/dev/null 2>&1
fi
@theeternalrat
Copy link

Thank you! This worked perfectly!

I did have to follow these instructions as well: https://superuser.com/questions/1726204/get-agent-identities-ssh-agent-bind-hostkey-communication-with-agent-failed

I was able to use ssh-add -l in wsl after running the script, but trying to ssh gave communication with agent failed. Following that SO and restarting seems to have fix it. I've done this one two different computers now, both working.

@oliverw
Copy link

oliverw commented Jan 4, 2025

Thanks a lot! This is pretty much the only solution out there that actually works.

@sdktr
Copy link

sdktr commented Jan 9, 2025

After some tweaks in the bash script to detect the PID of running process it worked for me. ChatGPT verbose version below:

#!/bin/bash
# ssh-agent-pipe: Manage an SSH agent socket relayed via socat and npiperelay.
# Usage: ssh-agent-pipe [ -k | -r ]
# Options:
#    -k    Kill the current process (if exists) and do not restart it.
#    -r    Kill the current process (if exists) and restart it.
# Default: Start the process only if it does not exist.

export SSH_AUTH_SOCK=$HOME/.ssh/agent.sock
LOG_FILE=$HOME/.ssh-agent-pipe.log
> "$LOG_FILE"

log_message() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}

log_message "Script started. SSH_AUTH_SOCK=$SSH_AUTH_SOCK"

if [ -z "$SSH_AUTH_SOCK" ]; then
    log_message "Error: SSH_AUTH_SOCK is not set."
    exit 1
fi

sshpid=$(ss -ap | grep -oP "(?<=users:\(\(\"socat\",pid=)\d+" | head -n 1)
log_message "Raw sshpid: $sshpid"

if [ "$1" = "-k" ] || [ "$1" = "-r" ]; then
    if [ -n "$sshpid" ]; then
        log_message "Killing existing socat process with PID: $sshpid"
        kill "$sshpid"
    else
        log_message "No socat process found for $SSH_AUTH_SOCK."
    fi

    if [ "$1" = "-k" ]; then
        log_message "Exiting due to -k flag."
        exit
    fi
    unset sshpid
fi

if [ -z "$sshpid" ]; then
    log_message "Starting socat process for SSH_AUTH_SOCK=$SSH_AUTH_SOCK."
    rm -f "$SSH_AUTH_SOCK"
    ( setsid socat UNIX-LISTEN:$SSH_AUTH_SOCK,fork EXEC:"npiperelay.exe -ei -s //./pipe/openssh-ssh-agent",nofork & ) >> "$LOG_FILE" 2>&1
    if [ $? -eq 0 ]; then
        log_message "Socat started successfully."
    else
        log_message "Error: Failed to start socat."
    fi
else
    log_message "Socat process already running for $SSH_AUTH_SOCK."
fi

log_message "Script completed."

Logging of all the options:

sdktr@WSL:~$ /usr/local/bin/ssh-agent-pipe -k
sdktr@WSL:~$ cat $HOME/.ssh-agent-pipe.log
2025-01-09 11:02:13 - Script started. SSH_AUTH_SOCK=/home/sdktr/.ssh/agent.sock
2025-01-09 11:02:13 - Raw sshpid: 5123
2025-01-09 11:02:13 - Killing existing socat process with PID: 5123
2025/01/09 11:02:13 socat[5123] W exiting on signal 15
2025-01-09 11:02:13 - Exiting due to -k flag.

sdktr@WSL:~$ /usr/local/bin/ssh-agent-pipe
sdktr@WSL:~$ cat $HOME/.ssh-agent-pipe.log
2025-01-09 11:02:33 - Script started. SSH_AUTH_SOCK=/home/sdktr/.ssh/agent.sock
2025-01-09 11:02:33 - Raw sshpid:
2025-01-09 11:02:33 - Starting socat process for SSH_AUTH_SOCK=/home/sdktr/.ssh/agent.sock.
2025-01-09 11:02:33 - Socat started successfully.
2025-01-09 11:02:33 - Script completed.

sdktr@WSL:~$ /usr/local/bin/ssh-agent-pipe -r
sdktr@WSL:~$ cat $HOME/.ssh-agent-pipe.log
2025-01-09 11:02:40 - Script started. SSH_AUTH_SOCK=/home/sdktr/.ssh/agent.sock
2025-01-09 11:02:40 - Raw sshpid: 6548
2025-01-09 11:02:40 - Killing existing socat process with PID: 6548
2025/01/09 11:02:40 socat[6548] W exiting on signal 15
2025-01-09 11:02:40 - Starting socat process for SSH_AUTH_SOCK=/home/sdktr/.ssh/agent.sock.
2025-01-09 11:02:40 - Socat started successfully.
2025-01-09 11:02:40 - Script completed.

@sdktr
Copy link

sdktr commented Apr 10, 2025

Additional method implemented to filter the keys that are offered as part of the SSH login, to avoid lockout due to too much keys offered.

# Windows (NON WORKING WITH ALL SSH clients, needs checking!)
PS C:\Users\myuser> cat .ssh/config
Host *
  IdentityAgent \\.\pipe\ssh-agent
  IdentitiesOnly yes
  IdentityFile c:/Users/myuser/.ssh/mykey_key.pub

# WSL2:
myuser@WSL:~$ cat ~/.ssh/config
Host *
    IdentityAgent $SSH_AUTH_SOCK
    IdentitiesOnly yes
    IdentityFile /mnt/c/Users/myuser/.ssh/mykey_key.pub

# points to my PUBLIC key in OpenSSH format, so that only that key is offered upon ssh negotiation

sdktr@PROX3-NB156637:~$ cat /mnt/c/Users/myuser/.ssh/mykey_key.pub
ssh-rsa AAAAB.....
 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment