Microsoft は Windows Server 2019/Windows 10 1809 に Win32-OpenSSH の名で OpenSSH を移植したのだが、この時 ssh-agent などで必要になるプロセス間通信を Unix ドメインソケットから Windows 名前付きパイプに変更している。

> Get-ChildItem -Path \\.\pipe\ -Name openssh-ssh-agent
openssh-ssh-agent

Windows Socket 2(Winsock) は長年 Unix ドメインソケットをサポートしてこなかったので、絶滅寸前の Unix おじいちゃんは同様の変更を MySQL なんかの Unix アプリケーション移植の度に目にしてきたかと思う。

だがしかし今は違う(ギュッ) Winsock にも Windows 10 1803(Redstone 4) の時点で Unix ドメインソケットはめでたく 実装された はずなのだ、なのに

  • なぜ使わない?
  • 使えないのか?
  • 使いたくないのか?
  • 使う度胸も無いのか?

だいたい相川もおかしいよ!(まさかの監督就任で 椎野四段活用 )。

答えは簡単、Unix ドメインソケットを利用した双方向プロセス間通信 API つまり socketpair が実装されていないからである。

風の噂では Winsock の Unix ドメインソケット実装はいまだバグだらけと聞くので socketpair のシムは Windows 名前付きパイプで実装したんだろうなって。 そもそも Cygwin/MSYS2/WSL1/WSL2 どいつもこいつも Unix ドメインソケットの実装に互換性がないので、Win32-OpenSSH が仮に Winsock の Unix ドメインソケットを使ったとしてもこいつらとの間ではプロセス間通信できない問題もある、もうグッチャグチャ。

ただまあ一般的に Win32-OpenSSH と Cygwin/MSYS2 そして WSL1/WSL2 をチャンポンで使うことは無いので困ることは無い。 せいぜい鍵の場所だけでも環境変数で共有しておけばいい。

混乱しそうなのは Git for Windows だろうかね、あれ MSYS2 ベースの OpenSSH も同梱してるから Windows 名前付きパイプでなく MSYS2 の Unix ドメインソケットエミュレーションを使うから。 まさか自分が使ってるのが Win32-OpenSSH ではないとは思わんだろうて、とはいえ GitBash という名の MSYS2 上で ssh-agent と ssh-add を操作してれば問題は起きないはずである。

しかしですな、ここからようやく本題なんだが Bitwarden を使って SSH 鍵管理をクラウドにしようとするとだいぶ困ったことになる。 Bitwarden の SSH-Agent 機能は冒頭に書いた Win32-OpenSSH 互換の Windows 名前付きパイプに限定されるため、Cygwin/MSYS2/WSL1/WSL2 に含まれる OpenSSH と通信できないのですわ。 これじゃ SSH 鍵管理をパスワードマネージャで一元化する意味が全く無いのである。

ちなみに PuTTY の SSH-Agent 実装である pageant は

pageant --unix C:\cygwin64\home\tnozaki\.pageant-ssh-agent

のようにオプション指定すれば Unix ドメインソケットで通信できるのだけどこいつも WSL1 形式なので Cygwin からは使えねえのだ。 繰り返すけど Cygwin/MSYS2/WSL1/WSL2 いずれも Unix ドメインソケットはエミュレーションでそれぞれ全く互換性が無いのである。

まあどうせ誰か Windows 名前付きパイプと Unix ドメインソケットのプロキシくらい作ってるだろうと探したら この記事 によると socat と PuTTY の plink の組み合わせで実現できるもよう。

ちょいと手直ししたのがこちら。

SSH_AUTH_SOCK=$HOME/.bitwarden-ssh-agent.sock; export SSH_AUTH_SOCK
socat UNIX-LISTEN:$SSH_AUTH_SOCK,unlink-early,fork EXEC:"plink -serial //./pipe/openssh-ssh-agent",pipes &

ポイントは

  • socat は Cygwin アプリ
  • plink は Windows ネイティブアプリ

という壁があるので

  • 既定は socketpair によるプロセス間通信だけど前述の通り互換性が無いので、オプションに pipes を指定し無名パイプによるプロセス通信を行う
  • nofork 使うとバックグラウンドプロセスごと終了してしまうので fork を指定すること

ちゅうあたり。

毎回手入力するのもバカバカしいので .bashrc に入れておきたいのだが多重起動チェックがめんどくさい。

Unix ドメインソケットの状態確認は BSD なら netstat そして Linux ならiproute2 の ss コマンドそして lsof などがある。 しかし Cygwin で netstat を実行してもこいつは Windows 付属のもので Cygwin の Unix ドメインソケットの状態は確認できないし ss も lsof も移植されてない。 かろうじて psmisc パッケージに fuser コマンドがあるけどこいつじゃ役に立たんしな。

しょうがないので問答無用で ssh-add -l を実行し ~/.bitwarden-ssh-agent.sock 経由で鍵一覧が取得できたら OK と判定しよう考えた。

SSH_AUTH_SOCK=$HOME/.bitwarden-ssh-agent.sock; export SSH_AUTH_SOCK
ssh-add -l >/dev/null 2>&1
if [ $? -ne 0 ]; then
  socat UNIX-LISTEN:$SSH_AUTH_SOCK,unlink-early,fork EXEC:"plink -serial //./pipe/openssh-ssh-agent",pipes &
fi

ところがこの方法にも問題があり Bitwarden がログアウトあるいはロック状態の間は永遠に結果が帰ってこないのである。 しかも ssh-add は ssh-agent からの応答を待つ時間を指定する方法がマニュアル読む限り無いのだよな。 なので迂闊に .bashrc に入れるとハングアップしてしまう可能性があるのだ。

ということで地道に起動してるプロセスを調べるしかねえ。

SSH_AUTH_SOCK=$HOME/.bitwarden-ssh-agent.sock; export SSH_AUTH_SOCK
ssh_proxy=$(ps -aef | awk '{for(i=6;i<NF;++i){printf("%s%s",$i,OFS=" ")}print $NF}' | \
  grep -E -c "^socat UNIX-LISTEN:$SSH_AUTH_SOCK,unlink-early,fork EXEC:plink -serial //./pipe/openssh-ssh-agent,pipes")
if [ $ssh_proxy -eq 0 ]; then
  socat UNIX-LISTEN:$SSH_AUTH_SOCK,unlink-early,fork EXEC:"plink -serial //./pipe/openssh-ssh-agent",pipes &
fi

みたいにやるしかなさそうね、もうちょっとスッキリ書けんのかおい。 目的果たしたからもうこれでいいや。

これで Cygwin の ssh を実行すると Bitwarden が起動し認可ボタンを押すだけでパスフレーズ無しで認証が可能になる。

Bitwarden を SSH-Agent として動かすには設定からチェックボックスを有効にするだけなので詳しくは これを読め。

ちなみに plink ではなく Microsoft Dev Blog の WSL Interoperability with Docker という記事にあった npiperelay という WSL から Windows 名前付きパイプを読むツールでも OK である。 こいつは WSL 専用というわけではなく Go で書かれてるので当然 Windows ネイティブアプリで WSL との連携は socat 任せである。 よって Cygwin/MSYS2/WSL1/WSL2 お好みの環境の socat と組み合わせて使えば WSL 以外でも plink と同様に動くのだ。

SSH_AUTH_SOCK=$HOME/.bitwarden-ssh-agent.sock; export SSH_AUTH_SOCK
socat UNIX-LISTEN:$SSH_AUTH_SOCK,unlink-early,fork EXEC:"npiperelay -ep -s //./pipe/openssh-ssh-agent",pipes &

Cygwin とはそれこそ b19 あたりの頃からの長いつきあいだったけど、Windows 11 があまりにクソ過ぎて日常マシンを openSUSE Tumbleweed に移行しつつあるので今後は Cygwin ネタは減ると思う。 そいや mintty (a.k.a Cygwin Terminal) 3.6.5 以降は日本語入力がまともに動作しない 問題がいつの間にか解消されてるっぽいね。 おそらく #1226 に関連する commit で解決したようである。