ProxyCommand芸
ssh_configというと,manpageの長さのわりに出来ることが貧弱で,何か工夫しようとするとシェルの設定とカップルせざるを得ない残念仕様というイメージがありますが,
ProxyCommand Specifies the command to use to connect to the server. The command string extends to the end of the line, and is executed with the user's shell. In the command string, ‘%h’ will be substituted by the host name to connect and ‘%p’ by the port. The command can be basically anything, and should read from its standard input and write to its standard output. It should eventually connect an sshd(8) server running on some machine, or execute sshd -i somewhere. Host key management will be done using the HostName of the host being connected (defaulting to the name typed by the user). Set‐ ting the command to “none” disables this option entirely. Note that CheckHostIP is not available for connects with a proxy command. This directive is useful in conjunction with nc(1) and its proxy support. For example, the following directive would connect via an HTTP proxy at 192.0.2.0: ProxyCommand /usr/bin/nc -X connect -x 192.0.2.0:8080 %h %p
考えてみると便利な抜け穴がありました.自分の権限で任意のコマンドを実行できるので,事実上何でもできそうです.
ぴんとこないかもしれませんので二つ例を挙げます.
例1:OpenSSHのバージョンを見て振る舞いを変える
OpenSSHはバージョン5.4から[-W host:port]というオプションで内蔵のnetcatのような機能を使えるようになりました.
Host gateway HostName gateway.fqdn Host target.gw HostName target.fqdn ProxyCommand ssh gateway -W %h:%p
これのおかげで,踏み台を必要とする多段ログインの際に,踏み台(gateway)の環境を気にすることなく,以下のコマンドでtargetにワンステップでログインできるようになりました.
$ ssh target.gw
しかし世にはOpenSSH 4.3を採用しているCentOS 5.xなど古い環境がまだまだ現存しているため,それらの環境をまたがってssh_configを使いまわそうと思うと,以下のように古いOpenSSHに対応するHostを別途書いておくか,-Wをあきらめて捨てるかするしかありません.
Host gateway HostName gateway.fqdn Host target.gw HostName target.fqdn ProxyCommand ssh gateway -W %h:%p Host target.gw-nc HostName target.fqdn ProxyCommand nohup ssh gateway nc %h %p
$ ssh target.gw # OpenSSH>=5.4環境 $ ssh target.gw-nc # OpenSSH<5.4環境
OpenSSHのバージョンによってエイリアスを使い分けるのは面倒です.これを勝手にやってもらうようにしてみます.
Host gateway HostName gateway.fqdn Host target.gw Hostname target.fqdn ProxyCommand $(PROXYHOST=gateway; MAJ=$(ssh -V 2>&1|cut -b9); MIN=$(ssh -V 2>&1|cut -b11); if [ $MAJ -gt 5 -o $MAJ -eq 5 -a $MIN -ge 4 ]; then echo "ssh $PROXYHOST -W %h:%p"; else echo "nohup ssh $PROXYHOST nc %h %p"; fi)
こうしておくと,以下のコマンド一つでOpenSSHのバージョンが新しければ-Wを,古ければgatewayのncを使うように自動的に選ばせることができます.
$ ssh target.gw
例2:どのネットワークにつながっているか見て振る舞いを変える
たとえば,次のようなケースを考えます.インターネット(WAN)とは切り離されたプライベートネットワーク(LAN)に,アクセスしたいホスト(Target)があるとします.プライベートネットワークにインターネットからアクセスするためには,ゲートウェイ(Gateway)を経由する必要があります.自分(たとえばラップトップPC)はインターネット(たとえば自宅)にいるときもプライベートネットワーク内(たとえば社内)にいるときもあります.
こんな時に想定される梅的な解はおそらく以下のような感じです.
Host *.gw ProxyCommand ssh gateway -W %h:%p Host gateway HostName gateway.fqdn Host target* HostName target.fqdn
インターネット,プライベートネットワーク内にいるときに,それぞれ
$ ssh target.gw # インターネットにいる場合 $ ssh target # プライベートネットワーク内にいる場合
などとアクセスするわけです.ただこの方法は微妙です.自分がどこにいるかによって実行するコマンドを変えなければなりません.
竹的な解はおそらく以下のような感じです.ここでは簡単のため,プライベートネットワークにいるかどうかを,target.fqdnからpingが返ってくるかどうかで判断できるとします.たとえば次のような感じでしょうか.
ssh-target() { if test ping -c 1 target.fqdn >/dev/null 2>&1; then ssh target else ssh target.gw fi }
先ののssh_configに加えてこのような関数を用意することで,どこにいるか意識せず以下のコマンドだけでアクセスできます.
$ ssh-target
ただこの方法も微妙です.sshの設定がシェルの設定とカップルしているからです.変な関数を作ってしまったので,autosshなどの便利なラッパを使おうとすると,その都度場当たり的な対応をすることになります.
さて,これを松と呼ぶのは気が引けますが,ProxyCommandでネットワークの判断まで行ってあげることで,ssh_configだけで完結するように書くことができます.
Host *.gw ProxyCommand $(if test ping -c 1 target.fqdn >/dev/null 2>&1; then PROXYHOST=localhost; else PROXYHOST=gateway; fi; echo "ssh $PROXYHOST -W %h:%p") Host gateway HostName gateway.fqdn Host target* HostName target.fqdn
こうしておくと,どこにいても
$ ssh target.gw
で接続できるようになります.ProxyCommandはsshのコマンドが発行されるタイミングで評価されるので,autosshにもそのまま食わせてあげることができます.