mattintosh note

どこかのエンジニアモドキの備忘録

SSH の Match Exec で接続元の環境が異なっても同じホスト名を使いたい

私はよく外出先から自宅のサーバに SSH で接続する。

自宅のサーバに「家にいるときはプライベート IP やローカルドメイン、外出先からだと自宅のグローバル IP やグローバルドメインなどで接続する」という場合、ホスト名別に設定を分けて書いていた。コンフィグに書くとこんな感じ。

~/.ssh/config

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

ホスト名を変えれば家でも外出先でもサーバに簡単に接続できる。

Terminal

# 自宅から接続する場合
$ ssh rpi

# 外出先から接続する場合
$ ssh rpi-home

自分がいま自宅にいるか外出先かは問うまでもないのでこの設定でも特に問題は無い。しかし、やはり人間なのでたまに外にいるのにローカルのホスト名を叩いてしまったりすることがある。当然そんなホストは無いので SSH は沈黙し、そしてホスト名を間違ったことに気づく。

そこで、ssh_config の Match ブロックを使うことで条件に応じて HostNamePort を変えるようにしてみる。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
    # 成功