前言
最近在搭建 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):