🐔

多段 ssh するなら ProxyCommand じゃなくて ProxyJump を使おう

2022/07/04に公開

概要

AWS とかで踏み台ホスト経由(ここでは AWS っぽく bastion と呼ぶ)で ssh する必要があるなら ~/.ssh/config は↓みたいにしとくのが良いんじゃないかな?

Host bastion
    Hostname bastionのIPアドレス
    User bastionのユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile bastion接続用の秘密鍵ファイル名
    # ↓の3つはWindowsでは使えないので諦めて
    ControlMaster auto
    ControlPath ~/.ssh/cp-%r@%h:%p
    ControlPersist 10m

Host 好きな接続先名
    Hostname 接続先のIPアドレス
    User 接続先のユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile 接続先用の秘密鍵ファイル名
    ProxyJump bastion

ポイントは接続先の設定にある ProxyJump と bastion の設定にある Control... シリーズの 2 つ。

以下にちょっとした解説と最後により良いと思ってるウチの設定例があるので、是非最後まで見て欲しい。

いいから ProxyJump 使っとけ

多段 ssh でググると↓のような ProxyCommand の記事が出てくる。とにかくいっぱい出てくる(バリエーションはいくつかある)。

  1. ProxyCommandssh -W %h:%p みたいに書いとけ。
  2. ProxyCommandssh bastion nc %h %p みたいに書いとけ。

それはそれでどちらも間違いと言う訳ではないんだが、書き方が古い。特に 2 はとっても古い。
イマドキの ssh 使ってるんであればそんな面倒な事書かなくても ProxyJump 使えばもっと簡単に書ける。

    ProxyJump 踏み台ホストのユーザ名@踏み台ホストのホスト名:踏み台ホストのポート番号

いや思ったよりなげぇな。

でも大丈夫。
踏み台ホストのユーザ名@ の部分と :踏み台ホストのポート番号 の部分は省略できる。その場合、ユーザ名は現在のユーザ名、ポート番号は 22 になる。それに、踏み台ホストがちゃんと ~/.ssh/config に定義されてれば

    ProxyJump bastion

みたいに書ける(最初の例はこれ)。スッキリ。

ちなみに、ProxyJump は踏み台ホストをカンマ区切りで複数書けるので、ホントに多段な場合でもわりと簡単に書ける。書けはするんだが、ホントに多段なのであればそれぞれの踏み台ホストのエントリに ProxyJump を書いとけばいいので、カンマ区切りはあんまり使うことは無いんじゃないかな?

コマンドラインなら -J オプション

ProxyJump は便利だが、ちょっとしたときにはわざわざ ~/.ssh/config にエントリを追加せずにコマンドラインで指定したい。その場合、-o ProxyJump=bastion とやるのはどうにも長ったらしい。いや、-o ProxyCommand ssh -W %h:%p bastion とか書くのに比べたら断然短いんだが、長いものは長い。

と言う訳で -J オプションである。

ssh -J 踏み台ホストのユーザ名@踏み台ホストのホスト名:踏み台ホストのポート番号 接続先IPアドレス

例によって 踏み台ホストのユーザ名@ の部分と :踏み台ホストのポート番号 の部分は省略できる。
もちろん 踏み台ホストのホスト名~/.ssh/config で定義されたホストでも良いので、例えば最初の例のように bastion を定義しておけば

ssh -J bastion 接続先IPアドレス

でいいわけだ(ポート番号とかユーザ名は必要に応じて -p とか -l とかで別途指定してくれ)。

あ、ちなみにここで出てきた 接続先IPアドレス ってのは普通プライベート IP アドレスになると思う。ssh にプライベート IP アドレスを直接指定するのはちょっと不思議な気がするかもしれないが、多段 ssh 接続というのはそういうものなので気にしてはいけない(ちゃんと知りたければ多段 ssh の詳しい解説を見て欲しいが、ここの指定は踏み台ホストが解釈するモノだと分かれば納得できるかな?)。

-J オプションを指定する事すらダルい

上記のように、あらかじめ ~/.ssh/config に bastion さえ定義しておけば、接続先ホストの定義はなくとも

ssh -J bastion 接続先IPアドレス

で多段 ssh 接続できるわけだが、接続のたびに毎回 -J bastion と指定する事すらダル過ぎると言う向きもいると思う(オレの事だ)。
そんな向きにとっておきの裏ワザを授けよう(上から目線)。

Host bastion
    Hostname bastionのIPアドレス
    User bastionのユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile bastion接続用の秘密鍵ファイル名
    # ↓の3つはWindowsでは使えないので諦めて
    ControlMaster auto
    ControlPath ~/.ssh/cp-%r@%h:%p
    ControlPersist 10m

Match host 172.16.*,172.17.*
    ProxyJump bastion

こう書いておけば、

ssh 接続先IPアドレス

とだけ指定すれば普通に bastion 経由で接続できる(ただし、接続先IPアドレス172.16.*172.17.* の範囲である必要はある)。接続先としてプライベート IP アドレスだけを指定する事になるのでだいぶキモい。キモいがとても楽だ。

~/.ssh/config の方は見ての通り bastion の定義部分は変わらない。違うのは Match host 172.16.*,172.17.* と言う部分が追加されている事だ。この Match host は何かといえば、指定されたアドレスに接続に行く場合はこの後に指定されているもの(上記の場合は ProxyJump)を適用しろよ、と言う事だ。

つまり、この場合 IP アドレスが 172.16.* とか 172.17.* とかのホストに接続に行く場合には自動的に ProxyJump bastio が適用されると言う訳だ。

これは、AWS のインスタンスなんてすぐ終了(Terminate)するから IP アドレスとかカジュアルに変わって毎回 ~/.ssh/config に登録とかウザくてやってられない、と言うオレのような貧乏性にピッタリだ(IP アドレス固定化しとけよ、と言う話は無くは無い)。IP アドレスは毎回変わったとしても割り当てる IP アドレスの範囲は変わらない(変えない)よね(それともそこまで一般的ではない?)。

ちなみに、上記の場合は別に Match host じゃなくて普通に Host でもいける。いけるんだが後述するように、設定をまとめるためには Match host の方が便利だったりするので Match host を使っている。

Match は結構便利

上記では Match-J オプション省略のために使ったが、そうじゃなくても踏み台ホストの先に接続したいサーバが複数ある場合には便利だ。
どういうことかと言えば、↓のような感じだ。

Host bastion
    Hostname bastionのIPアドレス
    User bastionのユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile bastion接続用の秘密鍵ファイル名
    # ↓の3つはWindowsでは使えないので諦めて
    ControlMaster auto
    ControlPath ~/.ssh/cp-%r@%h:%p
    ControlPersist 10m

Host 好きな接続先名1
    Hostname 接続先1のIPアドレス
    User 接続先1のユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile 接続先1用の秘密鍵ファイル名

Host 好きな接続先名2
    Hostname 接続先2のIPアドレス
    User 接続先2のユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile 接続先2用の秘密鍵ファイル名

あと好きなだけ接続先の設定を、ここに!

Match host 172.16.*
    ProxyJump bastion

ここで、接続先1と接続先2は共に 172.16.* と言う IP アドレス範囲であるとする。
要は、ProxyJump bastion と言う記載をそれぞれのホストの設定に書く必要が無い。
なお、ここでは Match host エントリには ProxyJump だけを書いたが、共通する設定は何だって書ける。

こういった記載方法は知ってる人間は知っているだろうが、ネット上であんまり見かけない気がするので縁起物として書いておく(ググり方が悪いだけか?)。

ちなみにここの記載は Host ではダメで Match host を使う必要がある。と言うのも Host はコマンドラインで指定したそのものだけにマッチさせようとするからだ。つまり、接続先として解決した IP アドレスでマッチしようとしてはくれない。逆に言えばコマンドラインでも単なる IP アドレスしか指定しないのであれば Match host を使わずとも Host だけで事足りる。が、せっかく設定を ~/.ssh/config に書くのであればナゾの IP アドレスじゃなくてもっと分かりやすいシンボリックな名前を使いたくなると思う(ならん?)。前述で設定をまとめるためには Match host の方が便利だったりすると言ったのはこういうわけだ。

Match は記載順序に気を付ける

上記において Match の記載順はとても重要なので注意して欲しい。共通設定だからと言って各接続先のエントリよりも Match host エントリを先に書くと効かなくなってしまう。

これは、ssh が設定ファイルを上から順に舐めて行くから、Match host のエントリに差し掛かった時に接続先ホストの IP アドレスが分かってないと Match host にマッチしないからだ。(もちろん接続先名がまんま IP アドレスだったり IP アドレスに解決できるホスト名であればちゃんとマッチする)

いや、オレは共通設定は死んでも上に書きたいんだ、と言うワガママさん向けの書き方も一応あるにはある。

Host bastion
    Hostname bastionのIPアドレス
    User bastionのユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile bastion接続用の秘密鍵ファイル名
    # ↓の3つはWindowsでは使えないので諦めて
    ControlMaster auto
    ControlPath ~/.ssh/cp-%r@%h:%p
    ControlPersist 10m

Match final host 172.16.*
    ProxyJump bastion

Host 好きな接続先名1
    Hostname 接続先1のIPアドレス
    User 接続先1のユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile 接続先1用の秘密鍵ファイル名

Host 好きな接続先名2
    Hostname 接続先2のIPアドレス
    User 接続先2のユーザ名
    # ↓は規定のファイルだったり ssh-agent 使ってれば不要
    IdentityFile 接続先2用の秘密鍵ファイル名

あと好きなだけ接続先の設定を、ここに!

記載順序以外は一緒じゃね~か、と思うかもしれないが、よく見て欲しい。Match host ではなくて Match final host になっている。

どうも Match の後に final があると、~/.ssh/config を 2 回読んでくれるらしく、上記の場合であれば最初に 好きな接続先名2とかの Hostname で接続先の IP アドレスが判明した後に、 Match final host で設定を読んでくれるらしい。

個人的には記述順序にそこまで思い入れは無いので Match host を最後に書いているが、どうしても最後は許せんと言う人はこれを使ってもいいかもしれない。

Control... って何なのよ

さて、もう一つの Control... シリーズだが、こいつらは接続の多重化(マルチプレクサ)を制御するものだ。接続の多重化って何?と言えば、一つの TCP 接続で複数の ssh セッションを張る事が出来る機能だ。

皆さんの環境では 1 台の踏み台ホストの先には接続したいサーバが複数台無いだろうか?そういった時に、接続の多重化を行わなければ踏み台ホストには接続先サーバ台数分、より正確に言えば接続する ssh セッション数分の TCP 接続が作成される。

だが、接続の多重化を行えば、ローカルマシンから踏み台ホストまでは TCP 接続は 1 つだけで、複数台の接続先サーバへの接続を賄うことが出来る。

と言う訳で、それを制御するための設定が Control... シリーズである。詳しくはマニュアル ssh_config(5) を見て欲しいが、

  • ControlMaster auto TCP 接続が無ければ新規に接続し、あれば使いまわす。
  • ControlPath ~/.ssh/cp-%r@%h:%p 既存接続を使いまわすための Unix ドメインソケットのパス名。(%r は接続先ユーザ名、%h は接続先ホスト名、%p は接続先ポート番号)
  • ControlPersist 10m ssh セッションが無くなっても 10 分は TCP 接続を保持しておく。

となっている。

接続の多重化なんてやって何かいいことあるの?と言えば、2 つめ以降の接続時の待ち時間が短くなる。それはもう短くなる。あと、サーバ目線で言えば、TCP 接続の本数が減るので負荷が軽くなる。

なので、特に困らなければ使った方がいいんじゃないかな?

あ、Windows の場合は使えないのであしからず(Windows 標準の ssh は Unix ドメインソケットに対応していないので。でも WSL2 なら使えるよ。WSL1 は確認してない)。

ちなみに、パス名を ~/.ssh/cp-%r@%h:%p にしているが、これだと接続先のホスト名とかユーザ名とかがパスで丸見えになる。オレは今どこに繋がってるのかを覚えてられない鳥頭なので、見えた方が好きでこうしているが、もしそれがイヤなのであれば ~/.ssh/cp-%C とかにするといいかもしれない。詳しくはマニュアル ssh_config(5) を見てくれ。

接続の多重化って踏み台ホスト以外でも使えるんでは?

はい。
と言う訳で、実はウチの設定は↓のような感じになっている。

Host *
    ControlMaster auto
    ControlPath ~/.ssh/cp-%r@%h:%p
    ControlPersist 10m
    HashKnownHosts no
    HostKeyAlgorithms ssh-ed25519
    ServerAliveCountMax 6
    ServerAliveInterval 10

Host bastion
    Hostname bastionのIPアドレス
    User bastionのユーザ名

Host 接続先名1
    Hostname 接続先1のIPアドレス
    User 接続先1のユーザ名

あと沢山の接続先設定

Match host 172.16.*,その他のIPアドレス達…
    ProxyJump bastion

Host * であらゆる接続先の接続多重化を設定している。

なお、Host * のその他の設定は単なる趣味なので今回の記事とは無関係(known_hosts がハッシュ化されると読めなくなって嫌い。ホストキーは ssh-ed25519 が好き。ServerAlive... シリーズは指定すると接続が切れづらい気がする(気のせいかも))。

あと、IdentityFile が無いのは標準のファイル名だからじゃなくて ssh-agent、てか正確には gpg-agent 使ってるから。gpg はいいぞ!

最後に

偉そうに書いてはいるが、実はオレも最近までこの設定にはなっていなかった。ssh なんて接続さえできればいいわけだし、普段から ssh_config(5) を熟読するような輩はそうそういないと思う(おる?)。

が、せっかく良い設定があるのにググると古い設定ばかり出てくるのはちょっと勿体ない。実は Windows 標準の ssh は ProxyJump に対応したのが比較的最近(Windows 10 21H1)らしいので、それが原因の一つかもしれないが、そればかりでもない気はする。

と言う訳で、ProxyJumpControl... を使ってみなさんよい多段 ssh ライフを!

Discussion