@kyanny's blog

My thoughts, my life. Views/opinions are my own.

SSH: ControlMaster, ControlPath, ControlPersist によってコネクションが永続化されていると ProxyCommand が無視される

SSH over HTTPS でプロキシを利用する場合の動作検証用に、ローカルで HTTP プロキシを立てて Connect with SSH through a proxy - Stack Overflow を試していたら、一向にうまくいかず苦戦した。

❯ ssh -o 'ProxyCommand nc -X connect -x localhost:8080 %h %p' -p 443 -T git@ssh.github.com

こんなやつ。なお nc は macOS の /usr/bin/nc である。

mitmproxy / mitmdump で HTTP プロキシを立てていると ssh 接続はエラーになってしまうとか(未解決)、proxy.py がデフォルトで IPv6 アドレスを bind するが nc はデフォルトで IPv4 アドレスを解決するためにプロキシサーバに接続できていなかったとか(解決済み)、他の要素も絡んでいて余計に苦労したが、最終的に表題の件にたどり着いた。

ControlMaster, ControlPath, ControlPersist については以下の記事が詳しい。要するに、ssh 接続を使い回すための設定が ControlMaster, ControlPath で、リモートホストからログアウトしたあともバックグラウンドで接続を維持するための設定が ControlPersist である。

今回ハマったのは、 ~/.ssh/config に、どこかからコピペしてきた設定が書いてあることに気付いていなかったのが敗因だった。

Host *
  ControlMaster auto
  ControlPath ~/.ssh/mux-%r@%h:%p
  ControlPersist yes  

ControlPersist yes が問題で、一度あるホストに ssh 接続すると、以後そのホストへの接続時には最初に確立したコネクションが(半永久的に)使いまわされる。ProxyCommand で HTTP プロキシを通すような ssh コマンドを発行しても、既存のコネクションの再利用が優先されるので、結果的に ProxyCommand が無視される。

-F /dev/null オプションで ssh config を読ませなくするか、 -o オプションでこれらのオプションの値を上書きしてやれば解決する。

❯ ssh -F /dev/null -o 'ProxyCommand nc -X connect -x localhost:8080 %h %p' -p 443 -T git@ssh.github.com

手元で試したところ、ControlMaster と ControlPersist を -o で上書きしても効果はなく、ControlPath を別のパスに上書きするのは効果があった。

# ダメ
❯ ssh -o 'ControlMaster no' -o 'ProxyCommand nc -X connect -x localhost:8080 %h %p' -p 443 -T git@ssh.github.com

# ダメ
❯ ssh -o 'ControlPersist no' -o 'ProxyCommand nc -X connect -x localhost:8080 %h %p' -p 443 -T git@ssh.github.com

# うまくいく
❯ ssh -o 'ControlPath /dev/null' -o 'ProxyCommand nc -X connect -x localhost:8080 %h %p' -p 443 -T git@ssh.github.com

なお、バックグラウンドで維持されている既存のコネクションを切断するには ssh -O オプションを使う。接続先のホスト・ポート・ユーザ名などを一致させる必要がある。

# コネクションが存在するか確認する
ssh -O check -p 443 -T git@ssh.github.com

# コネクションを切断する
ssh -O exit -p 443 -T git@ssh.github.com