ProxyCommand芸

例2

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にもそのまま食わせてあげることができます.