🖥️

WSL2のOpenSSH Agent Relayが動かなくなったので調査した

2022/11/19に公開
1

症状

WSL2におけるOpenSSHのAgentについては以下の記事を参照。

手元のPCでは npiperelay + socat を使ったAgent Relay構成にしているが、最近になって動かなくなった。Agentから結果が得られず、毎回ストレージの秘密鍵を開くためにパスワードが要求されるようになっていた。

何を調査したか

  • 秘密鍵を移動。パスワードが聞かれなくなり、エラーになることを確認
    • → Agentから毎回パスワードを聞かれているのではなく、そもそもAgentがうまく動いていない
  • ssh-add -T ~/.ssh/id_rsa.pub は正しく動作することを確認
    • → 少なくとも基本的な通信経路は生きている
  • openssh-portable を自分でビルドしても再現
    • → Ubuntu固有の問題ではない
    • 処理を差し込んでデバッグ可能に
  • debug("...")debug_f("...") を各所に差し込む → SSH_AUTH_SOCK に対する read(2) の戻り値が0になっている箇所がある (get_agent_identities → ssh_agent_bind_hostkey → ssh_request_reply_decode → ssh_request_reply → atomicio(read))
    • → リクエストを送信してからレスポンスを受信するまでのどこかに問題がある (受信後のssh側の問題ではない)
  • sshssh-add -T をそれぞれstraceして、SSH_AUTH_SOCK への書き込みコールを見てみる → リクエストの中身は異なるが、書き込みパターンは同じ
    • → 相手側処理からレスポンスを受信するまでのどこかに問題がある (送信自体は成功している)
  • straceのログからメッセージタイプを特定する → ssh は SSH_AGENTC_EXTENSION (extension name = session-bind@openssh.com)、 ssh-add -T は SSH_AGENTC_SIGN_REQUEST を送っている
    • 仮説: 現状のWindows側のOpenSSH Serviceが session-bind@openssh.com に対応していない?
  • ssh_agent_bind_hostkey の呼び出し部を削除してみる → 動いた
  • client bindingが導入されたコミット
  • 仮説: この前Ubuntuをfocal (20.04) からjammy (22.04) に上げたときに導入されて壊れた?

対応

以下のパッチを作成した。 SSH_AUTH_NOBIND=1 とすることでclient hostkey bindingを無効にできる。

diff --git a/authfd.c b/authfd.c
index 76e48aab..0d94c5e3 100644
--- a/authfd.c
+++ b/authfd.c
@@ -732,6 +732,11 @@ ssh_agent_bind_hostkey(int sock, const struct sshkey *key,
 {
        struct sshbuf *msg;
        int r;
+       char *nobind;
+
+       nobind = getenv(SSH_AUTH_NOBIND_ENV_NAME);
+       if (nobind != NULL && *nobind != '\0')
+               return 0;

        if (key == NULL || session_id == NULL || signature == NULL)
                return SSH_ERR_INVALID_ARGUMENT;
diff --git a/ssh.h b/ssh.h
index 8110c060..29a1dbf4 100644
--- a/ssh.h
+++ b/ssh.h
@@ -62,6 +62,8 @@
  */
 #define SSH_AUTHSOCKET_ENV_NAME "SSH_AUTH_SOCK"

+#define SSH_AUTH_NOBIND_ENV_NAME "SSH_AUTH_NOBIND"
+
 /*
  * Environment variable for overwriting the default location of askpass
  */

Debianパッケージを作るためにまず元となるメンテナリポジトリをクローン

git clone https://salsa.debian.org/ssh-team/openssh.git debian-openssh
cd debian-openssh
git switch -d debian/1%8.9p1-3

ブランチを切る

git switch -c nobind

上記パッチを適用

git-dpm checkout-patched
patch -p1 < nobind.patch
git add authfd.c ssh.h
git commit -m "Allow skipping client hostkey binding"
git-dpm update-patches

更新時にオリジナルのパッケージで上書きされないように、changelogを足してバージョンを大きくする。たとえば今回ソースバージョンは 8.9p1-3 でUbuntuで配布されているバージョンは 8.9p1-3ubuntu0.1 なので、 u に勝てるように 8.9p1-3znobind とかにしておけばよいはず。

dch

ビルド

git-dpm prepare && dpkg-buildpackage -rfakeroot -us -uc

インストール

dpkg -i ../openssh-client_8.9p1-3_amd64.deb

あとはSSH_AUTH_SOCKをセットアップしている箇所で export SSH_AUTH_SOCK=1 をするようにすればOK。

Discussion

Yusuke EndohYusuke Endoh

この記事をみて、WSL2とssh-agent.exeの独自ブリッジを作ってみました。

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

基本的にはsocat + npiperelayを自力で再実装したものですが、OpenSSHの拡張メッセージはssh-agent.exeに転送しないようにしたので、パッチをあてない素のsshでも使えます。もしよければお試しくださいー