🌉

wsl2-ssh-agent: WSL2からssh-agent.exeサービスへのブリッジ

2023/02/12に公開

なに?

WSL2からWindowsサービスのssh-agent.exeにアクセスするためのかんたんなツールです。

https://github.com/mame/wsl2-ssh-agent

使い方

ssh-agent.exeサービスがセットアップ済みなら、次の2ステップだけで動くはず。

  1. WSL2でwsl2-ssh-agentの実行ファイルを取得して、好きなところに置く
curl -L -O https://github.com/mame/wsl2-ssh-agent/releases/latest/download/wsl2-ssh-agent
chmod 755 wsl2-ssh-agent
  1. .bashrcなどに次の一行を足す
eval $(/path/to/wsl2-ssh-agent)

新しい端末を開いてどこかにsshしたら、ssh-agent.exeと通信してくれるはずです。

うまく動かなかったら、eval $(/path/to/wsl2-ssh-agent -log /tmp/wsl2-ssh-agent.log) などとして、ログを見てみてください。

仕組み

何がむずかしいか

以下、WSL2のsshを単に「ssh」、Windowsのssh-agent.exeサービスを「ssh-agent.exe」と呼びます。

sshとssh-agent.exeを通信させるのがなんでむずかしいかというと、通信手段が違うからです。sshはUNIX domain socketで通信を行おうとしますが、ssh-agent.exeはWindowsの名前付きパイプを待ち受けています。このあたりの事情は次の記事が詳しいです。

https://qiita.com/slotport/items/e1d5a5dbd3aa7c6a2a24

既存手法

socatとnpiperelay.exeの組み合わせで解決している人が多いと思います。socatがUNIX domain socketを待ち受け、npiperelay.exeが名前付きパイプにアクセスします。socatとnpiperelay.exeは標準入出力を通じて通信します。

図にするとこんな感じ。

しかしこの手法は、最近のLinuxで動きません。OpenSSHがバージョン8.9からssh-agentプロトコルを少し拡張したためです(OpenSSHのドキュメント)。ssh-agent.exeは少し古い(手元では8.6ベースだった)ので、拡張メッセージを受け取った瞬間に通信を切断してしまいます。この問題については次の記事が詳しいです。

https://zenn.dev/qnighy/articles/8b992970b86653

対策として、sshにパッチをあてて非互換を止めるとか、ssh-agent.exeを手動更新するなどがありますが、個人的にはどちらもやりたくありませんでした。

あと個人的には、「Windows側のどこかにnpiperelay.exeを置く」みたいな設定がなんとなく死ぬほど大嫌いなので、そもそもこの既存手法を使ってませんでした [1]

wsl2-ssh-agentの手法

基本的にはsocat + npiperelay.exeと同じです。socatがwsl2-ssh-agentに、npiperelay.exeがPowerShell.exeになっただけです。

しかしwsl2-ssh-agentは単純なリレーではなく、ssh-agentプロトコルを少しだけ理解します。sshが拡張メッセージを送ってきたら、ssh-agent.exeに転送せず、ダミーのSUCCESSを返答してしまいます [2] 。これにより、OpenSSHの非互換問題を回避できます。

npiperelay.exeの代わりにPowerShell.exeを使いました。PowerShell.exeは最近のWindowsなら標準装備のはずで、WSL2からも普通に起動できます。PowerShellのソースコードも標準入力から流し込むので、「Windows側のどこかにファイルを置く」みたいなダサい設定が不要になっています。

苦労した点

PowerShell.exeの起動はちょっと遅い(自分の環境で2秒弱ほどかかる)ので、いちいち起動するとストレスがたまりました。なので、wsl2-ssh-agentの起動時にPowerShell.exeをあらかじめ起動するようにしています。

しかし、PowerShell.exeを起動した端末を閉じるとなぜかプロセス終了します。Windowsプロセスに対しては、setsidが無力なようです。しかも、SIGCHLDが来ないので殺されたことが即座にはわからず、通信しようとしてはじめて死んだことがわかります。たぶんWSL2 Interopに少し問題があるんだと思いますが、しょうがないので、通信してみてPowerShell.exeが死んでいたときは自動で再起動するようにしました。

PoCはRubyで作りましたが、バイナリ配布しやすいようにGo言語で仕上げてみました。行数が10倍以上になった。Goの感想は別記事で。

まとめ

WSL2からssh-agent.exeにアクセスするためのかんたんなツール、wsl2-ssh-agentを紹介しました。数日ほど使ってみていますが、いまのところ安定して動いている気がします。なんかバグってると思ったらご報告ください。

自分はあらゆる設定作業が大嫌いなのですが、WSL2はほとんど設定不要でいい感じに動くところがすばらしいと思ってます。その中で、WSL2 ssh + ssh-agent.exeごときで設定が必要なのは最大の不満のひとつです。そのうちssh-agent.exeとのブリッジも組み込んでくれないかなあ。それまでの一時しのぎとしてwsl2-ssh-agentをお使いください。

脚注
  1. じゃあどうしていたかというと、WSL2からssh.exeを直接使ってました。細かいことを除けば意外と動きます。しかしXの転送ができないのが痛恨でした。 ↩︎

  2. このせいで若干セキュリティ強度が低下しているかもしれません。少し古いssh-agent.exeを使う以上、どうしようもないと思いますが、一応自己責任でお願いします。ssh.exe のバージョンを見て、古いときだけダミー応答をするようにしているので、将来Windowsのssh-agent.exeが更新されたらこの仕組みは自動で止まる予定です。 ↩︎

Discussion