多段 ssh するなら ProxyCommand じゃなくて ProxyJump を使おう
概要
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
の記事が出てくる。とにかくいっぱい出てくる(バリエーションはいくつかある)。
-
ProxyCommand
にssh -W %h:%p
みたいに書いとけ。 -
ProxyCommand
にssh 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)らしいので、それが原因の一つかもしれないが、そればかりでもない気はする。
と言う訳で、ProxyJump
と Control...
を使ってみなさんよい多段 ssh ライフを!
Discussion