使用 npiperelay 和 socat 在 WSL 中复用 Windows SSH 密钥

前言

最近在搭建 WSL2 环境,我使用 Bitwarden 作为我的 SSH 密钥管理器,为了能在 WSL 中也使用而不是直接将私钥复制过去,所以尝试做了一个转接工作。这里我跳过了 Windows 上设置 Bitwarden 的 ssh-agent 的步骤,如有需要请前往 Bitwarden 官网查看。


步骤

先在 Windows 上安装 npiperelay

winget install --id albertony.npiperelay --exact

安装完成后需要重启 bash 或者可能需要重启电脑。之后确认一下

Get-Command npiperelay.exe
C:\Users\<你的用户名>\AppData\Local\Microsoft\WinGet\Links\npiperelay.exe

在 WSL 安装 socat

sudo apt update
sudo apt install -y socat

确认

socat -V

同时确认你使用的是 WSL2

wsl.exe -l -v
NAME      STATE    VERSION
Debian    Running  2

创建一个脚本

nano "$HOME/.local/bin/start-bitwarden-ssh-agent"

在里面写入脚本。
注意修改 NPIPERELAY 变量中的用户名,整体替换成你上面 Get-Command npiperelay.exe 命令出现的地址,但要使用在 WSL 中的地址来写。
注意脚本会修改 .ssh 文件夹的权限为 700 ,如果你有特别的需求,自行删除。

#!/usr/bin/env bash

set -u

SOCKET="$HOME/.ssh/bitwarden-agent.sock"
PIDFILE="$HOME/.ssh/bitwarden-agent.pid"
LOGFILE="$HOME/.ssh/bitwarden-agent.log"

NPIPERELAY="/mnt/c/Users/你的用户名/AppData/Local/Microsoft/WinGet/Links/npiperelay.exe"

mkdir -p "$HOME/.ssh"
chmod 700 "$HOME/.ssh"

# 检查 npiperelay.exe 是否存在
if [[ ! -x "$NPIPERELAY" ]]; then
    echo "找不到或无法执行 npiperelay.exe:" >&2
    echo "$NPIPERELAY" >&2
    exit 1
fi

# 如果 PID 文件存在,检查对应进程是否仍然有效
if [[ -f "$PIDFILE" ]]; then
    PID="$(cat "$PIDFILE" 2>/dev/null || true)"

    if [[ -n "$PID" ]] &&
       kill -0 "$PID" 2>/dev/null &&
       [[ -S "$SOCKET" ]]; then
        exit 0
    fi

    # PID 已失效
    rm -f "$PIDFILE"
fi

# 如果 socket 存在但没有有效进程,它就是残留文件
if [[ -e "$SOCKET" ]]; then
    rm -f "$SOCKET"
fi

# 启动桥接
nohup socat \
    UNIX-LISTEN:"$SOCKET",fork,mode=600 \
    EXEC:"$NPIPERELAY -ei -s //./pipe/openssh-ssh-agent",nofork \
    >>"$LOGFILE" 2>&1 &

PID=$!
echo "$PID" > "$PIDFILE"

# 等待 socket 建立
for _ in {1..30}; do
    if [[ -S "$SOCKET" ]]; then
        exit 0
    fi

    # socat 如果已经异常退出,不必继续傻等
    if ! kill -0 "$PID" 2>/dev/null; then
        rm -f "$PIDFILE"
        echo "Bitwarden SSH Agent 桥接进程启动失败。" >&2
        echo "日志位置:$LOGFILE" >&2
        exit 1
    fi

    sleep 0.1
done

# 超时则终止异常进程
kill "$PID" 2>/dev/null || true
rm -f "$PIDFILE" "$SOCKET"

echo "Bitwarden SSH Agent socket 创建超时。" >&2
echo "日志位置:$LOGFILE" >&2
exit 1

之后添加权限

chmod +x "$HOME/.local/bin/start-bitwarden-ssh-agent"

编辑 .bashrc 文件

nano "$HOME/.bashrc"

在 .bashrc 文件末尾添加

# Windows Bitwarden SSH Agent bridge
export SSH_AUTH_SOCK="$HOME/.ssh/bitwarden-agent.sock"

if [[ -x "$HOME/.local/bin/start-bitwarden-ssh-agent" ]]; then
    "$HOME/.local/bin/start-bitwarden-ssh-agent"
fi

重新加载

source "$HOME/.bashrc"

测试

echo "$SSH_AUTH_SOCK"
/home/你的用户名/.ssh/bitwarden-agent.sock

检查 socket

ls -l "$SSH_AUTH_SOCK"

检查进程,一般来说当没有发起 SSH 请求时,只有一个监听进程

pgrep -af 'socat.*bitwarden-agent.sock'

最后测试

ssh-add -l
ssh-add -L

应该可以看到和你在 Windows 上一样的结果,会输出 Bitwarden 中已经存储了的公钥。


npiperelay 和 socat

这里需要两个工具,Windows 上的 npiperelay 和 Linux 上的 socat 。

npiperelay 允许你从 WSL 侧访问 Windows 命名管道,他连接 Windows 命名管道,并把数据转成标准输入 / 标准输出。

socat 全名可以理解为 SOcket CAT ,它是一个通用的数据转发工具,可以在两个“数据通道”之间做双向转发。它也可以连接很多类型的通道,比如 TCP 端口、UDP 端口、Unix Socket、文件、管道、标准输入输出、子进程等。在这个方案中, socat 的作用是:在 WSL 里创建一个 Unix Socket ,然后把访问这个 Socket 的数据转交给 npiperelay.exe 。
socat 的基础语法是:

socat 地址1 地址2

我们以脚本为例:

nohup socat \
    UNIX-LISTEN:"$SOCKET",fork,mode=600 \
    EXEC:"$NPIPERELAY -ei -s //./pipe/openssh-ssh-agent",nofork \
    >>"$LOGFILE" 2>&1 &

nohup:忽略 hangup 信号,用于当当前 shell 退出时,尽量让这个后台进程继续运行。
socat :

地址1 = UNIX-LISTEN:"$SOCKET",fork,mode=600
地址2 = EXEC:"$NPIPERELAY -ei -s //./pipe/openssh-ssh-agent",nofork

也就是:

一边监听 WSL Unix Socket
另一边执行 npiperelay 去连接 Windows 管道
UNIX-LISTEN:"$SOCKET"

表示:

创建并监听 Unix Domain Socket

fork:

每当有客户端连接进来,就 fork 一个子进程处理这个连接

否则 socat 可能在同一时间只能处理一个连接。

mode=600

表示创建出来的 socket 权限为 600 。

EXEC:"$NPIPERELAY -ei -s //./pipe/openssh-ssh-agent"

表示:

当 socket 有连接时,执行这个命令
并把 socket 数据转给这个命令的 stdin/stdout
-ei

这是 npiperelay 的参数组合。

在常见 WSL 桥接用法里,-ei 用于让它适配 WSL/stdio 场景,处理标准输入输出和 EOF 行为。

你可以把它理解为:

让 npiperelay 以适合被 socat EXEC 调用的方式运行
-s

表示连接指定的 Windows Named Pipe 服务端。也就是

\\.\pipe\openssh-ssh-agent
nofork

这里是 socat 的 EXEC 侧参数。表示:

执行子命令时不要再额外 fork 一层
>>"$LOGFILE" 2>&1

日志内容输出

&

后台运行


方案设计

Windows 中很多服务是通过命名管道来进行的,比如我们要用的 ssh-agent 就是通过下面这个命名通道来传输的。

\\.\pipe\openssh-ssh-agent

但同一时间只能由一个应用作为服务端占用,所以 Bitwarden 要求关闭 Windows 自带的 OpenSSH Authentication Agent 然后改为让 Bitwarden 来监听接管这个通道实现的服务,再配合上我们上面的两个工具。
所以我们实现大致路径是:

WSL 程序 → Unix Socket → socat → npiperelay.exe → Windows Named Pipe → Bitwarden

具体结构如下:

WSL 中的 ssh-add / ssh / git
        │
        │ 读取环境变量 SSH_AUTH_SOCK
        ▼
/home/你的用户名/.ssh/bitwarden-agent.sock
        │
        │ Unix Socket
        ▼
socat
        │
        │ 启动并连接子进程
        ▼
npiperelay.exe
        │
        │ Windows Named Pipe
        ▼
\\.\pipe\openssh-ssh-agent
        │
        ▼
Bitwarden Desktop SSH Agent

1、因为在 .bashrc 中配置了 SSH_AUTH_SOCK 所以 WSL 中的这些都会去找你写的那个 bitwarden-agent.sock
2、在脚本中 socat 监听了这个 Unix Socket ,这里面的变量 $SOCKET 填写的就是 bitwarden-agent.sock 的路径。

socat \
    UNIX-LISTEN:"$SOCKET",fork,mode=600 \
    EXEC:"$NPIPERELAY -ei -s //./pipe/openssh-ssh-agent",nofork

3、当有连接进来时,socat 启动 npiperelay ,当 ssh-add -l 连接到这个 socket 时, socat 会执行:

npiperelay.exe -ei -s //./pipe/openssh-ssh-agent

也就是说:

每次 WSL 里的 SSH 客户端连接 socket
socat 就把这次连接转给 npiperelay
npiperelay 再去连接 Windows 的 openssh-ssh-agent 管道

4、Bitwarden 在 Windows 端监听

\\.\pipe\openssh-ssh-agent

当 npiperelay 连接到这个管道后,SSH Agent 的请求协议数据被发送到 Bitwarden ,Bitwarden 根据请求返回。


文章封面(AI):使用 npiperelay 和 socat 在 WSL 中复用 Windows SSH 密钥

订阅评论
提醒
用户头像

0 评论
最旧
最新 最多投票