私はよく外出先から自宅のサーバに SSH で接続する。
自宅のサーバに「家にいるときはプライベート IP やローカルドメイン、外出先からだと自宅のグローバル IP やグローバルドメインなどで接続する」という場合、ホスト名別に設定を分けて書いていた。コンフィグに書くとこんな感じ。
Host rpi rpi-home User pi IdentityFile ~/.ssh/rpi_id_rsa Host rpi HostName 192.168.1.100 Host rpi-home HostName example.com Port 12345
ホスト名を変えれば家でも外出先でもサーバに簡単に接続できる。
# 自宅から接続する場合 $ ssh rpi # 外出先から接続する場合 $ ssh rpi-home
自分がいま自宅にいるか外出先かは問うまでもないのでこの設定でも特に問題は無い。しかし、やはり人間なのでたまに外にいるのにローカルのホスト名を叩いてしまったりすることがある。当然そんなホストは無いので SSH は沈黙し、そしてホスト名を間違ったことに気づく。
そこで、ssh_config の Match
ブロックを使うことで条件に応じて HostName
や Port
を変えるようにしてみる。Exec
の終了ステータスが 0
でない場合に True とする場合は !Exec
で反転する。if else が使えないので仕方がない。
Host rpi User pi IdentityFile ~/.ssh/rpi_id_rsa # ホスト名 rpi で ping が成功した場合 Match Host rpi Exec "ping -c1 -t1 raspberrypi.local" HostName raspberrypi.local # ホスト名 rpi で ping が失敗した場合 Match Host rpi !Exec "ping -c1 -t1 raspberrypi.local" HostName example.com Port 12345
ホスト名で一致する Match
が二つあるので ping も二回行われてしまうが、その結果によって HostName
が切り替わるので接続時のホスト名を気にする必要はなくなる。
$ ssh rpi
True になる Match
が複数存在する場合は先に出現した方が利用される。下記に雑な例を示すが、この状態で自宅で実行すると ping が両方とも成功して先に記述されている HostName 192.168.1.100
が有効になる。
Host rpi User pi IdentityFile ~/.ssh/rpi_id_rsa Match Host rpi Exec "ping -c1 -t1 192.168.1.100" HostName 192.168.1.100 Match Host rpi Exec "ping -c1 -t1 192.168.1.100" HostName example.com Port 12345
しかし、例えば公共の場所に自宅のサーバのプライベート IP アドレスと同じマシンが存在する場合、ping はいいとしても SSH のログイン試行履歴が残ってしまうかもしれない。これはとても恥ずかしいし、相手も不安に思うかもしれない。(通常は StrictHostKeyChecking yes
なはずなので接続先のフィンガープリントが違えばそもそも SSH が教えてくれるのだがそれは置いておく)
そこで、厳密にホストを識別するためにサーバのフィンガープリントを照合するようにしてみる。
Match
ブロックの結果が True になるものが複数ある場合は先の Match
が適用されるので自宅と外出先の両方を同時にチェックしてしまえばいいだろう。ちなみに例のフィンガープリントは github.com のもの。
Host rpi User pi IdentityFile ~/.ssh/rpi_id_rsa Match Host rpi Exec "ssh-keyscan -T1 -trsa 192.168.1.100 | grep -sq '192.168.1.100 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=='" HostName 192.168.1.100 Match Host rpi Exec "ssh-keyscan -T1 -p 12345 -trsa example.com | grep -sq 'example.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=='" HostName example.com Port 12345
正常な場合、フィンガープリント照会が三回ほど走ることになり無駄ではある。そう、Exec
行にどこまで書けるのかやってみたかっただけである。
ちなみに Exec
は複数書いてもいいっぽい。この場合、左から順に Exec
が実行され、すべての終了ステータスが 0
(True)であれば有効になる。途中で 0
以外(False)になった場合はそこで Exec
は終了する。何のシェルを使っているかまでは調べていないがシェルのビルトインコマンドも認識している様子。
Match Exec date Exec /usr/bin/false # 失敗 Match Exec /usr/bin/false Exec date # 失敗 Match Exec date Exec /usr/bin/true # 成功 Match Exec /usr/bin/true Exec date # 成功