多段 ssh / rsync するために ProxyCommand を使ってみる (2)
さーて,どんどんこんらんさせていきますよ。
高度な例 (3) - 多段 ssh
要件は*1
- host:gw1 に user:foo というアカウントがある
- host:gw2 に user:bar というアカウントがある
- host:target に user:baz というアカウントがある
- user:dayflower が host:local から host:gw1, host:gw2 を経由して host:target に user:baz で アクセスしたい
ProxyCommand
をカスケードして指定していけばなんとでもなります。実行例は下記のとおり((コマンドラインから実行する場合,実用上の理由から %h
や %p
などの展開は利用していません))。
local% ssh -o "ProxyCommand ssh -o 'ProxyCommand ssh -l foo -i foo.id_dsa gw1 nc gw2 22' -l bar -i bar.id_dsa gw2 nc target 22" -l baz -i baz.id_dsa target Enter passphrase for key 'foo.id_dsa': ******** Enter passphrase for key 'bar.id_dsa': ******** Enter passphrase for key 'baz.id_dsa': ******** Last login: Fri Feb 8 12:01:55 2008 from gw2 baz@target% ■
以下のようなフローになっています。
- (最終的に)target につなぎたい ssh クライアントが立ち上がる
- [1]
ssh
[totarget
]
- [1]
- target に ssh でつなぐためには以下のサブプロセスが必要となる
- [2]
ssh -o 'ProxyCommand ssh gw1 nc gw2 22' gw2 nc target 22
- [2]
- [2] の ssh プロセスは gw2 につなぐ必要がある
- [2]
ssh
[togw2
]
- [2]
- gw2 に ssh でつなぐために以下のサブプロセスが必要になる
- [3]
ssh gw1 nc gw2 22
- [3]
実際にプロセスがどのようになっているか確認してみます。
local% ps -efww PID PPID CMD 31448 600 [1] ssh -o ProxyCommand ssh -o 'ProxyCommand ssh -l foo -i foo.id_dsa gw1 nc gw2 22' -l bar -i bar.id_dsa gw2 nc target 22 -l baz -i baz.id_dsa target 31449 31448 [2] ssh -o ProxyCommand ssh -l foo -i foo.id_dsa gw1 nc gw2 22 -l bar -i bar.id_dsa gw2 nc target 22 31450 31449 [3] ssh -l foo -i foo.id_dsa gw1 nc gw2 22
ProxyCommand
で指定されたプロセスが全て host:local で立ち上がっているのが直感的に間違っている気もしますが,問題ありません。なぜなら
- [3] は [2] の(local が gw2 に繋ぐ)ためのトランスポートレイヤとして働く
- したがって [2] で local ⇒ gw2 にダイレクトに繋がる
- [2] は [1] の(local が target に繋ぐ)ためのトランスポートレイヤとして働く
- したがって [1] で local ⇒ target にダイレクトに繋がる
だからです。(下位トランスポートがどのようになっているかに関わらず)[1] ssh は host:target にダイレクトに接続しているつもりなので,よいのです。
コネクションを見てみる
まずは host:local におけるコネクションの様子から。
local% netstat -atn | grep ':22' Active Internet connections (servers and established) Proto Local Address Foreign Address State tcp local:57530 gw1:22 ESTABLISHED
ネットワークコネクションとしては,あくまで,host:local から host:gw1 にのみ張られています。
次に,host:gw1 におけるプロセスとネットワークコネクションの様子をみてみます。
gw1% ps -efw | grep foo UID PID PPID C STIME TTY TIME CMD root 8061 1909 0 12:04 ? 00:00:00 sshd: foo [priv] foo 8063 8061 0 12:04 ? 00:00:00 sshd: foo@notty foo 8064 8063 0 12:04 ? 00:00:00 nc gw2 22 gw1% netstat -atn | grep ':22' Active Internet connections (servers and established) Proto Local Address Foreign Address State tcp gw1:43157 gw2:22 ESTABLISHED tcp gw1:22 local:57530 ESTABLISHED
host:gw2 の 22 ポートに対するコネクションが nc
コマンドによって(user:foo の権限で)張られています。また,先ほどの host:local ⇒ host:gw1 のコネクションの受け口も表示されています(ポート番号に着目)。
次に,host:gw2 におけるプロセスとネットワークコネクションの様子です。
gw2% ps -efw | grep bar UID PID PPID C STIME TTY TIME CMD root 2247 1921 0 12:04 ? 00:00:00 sshd: bar [priv] bar 2264 2247 0 12:05 ? 00:00:00 sshd: bar@notty bar 2265 2264 0 12:05 ? 00:00:00 nc target 22 gw2% netstat -atn | grep ':22' Active Internet connections (servers and established) Proto Local Address Foreign Address State tcp gw2:38172 target:22 ESTABLISHED tcp gw2:22 gw1:43157 ESTABLISHED
host:target の 22 ポートに対するコネクションが nc
コマンドによって張られています。host:gw1 ⇒ host:gw2 のコネクションの受け口も表示されています。
最後に,host:target でのネットワークコネクションの様子です。
target% netstat -atn | grep ':22' Active Internet connections (servers and established) Proto Local Address Foreign Address State tcp target:22 gw2:38172 ESTABLISHED
あくまで explicit なネットワークコネクションとしては host:gw2 から張られた ssh コネクションのみです。
直感とは裏腹に,うまくいってますね。
ようやく ssh_config
を書いてみる
$HOME/.ssh/config
ではなく,カレントディレクトリに ssh_config
という別設定ファイルを置いた場合((このため設定ファイル中で -F ssh_config
というオプションが指定されています。通常の設定ではこれらは必要ありません。))の,設定例です。
Host gw1 User foo IdentityFile foo.id_dsa Host gw2 ProxyCommand ssh -F ssh_config gw1 nc %h %p User bar IdentityFile bar.id_dsa Host target ProxyCommand ssh -F ssh_config gw2 nc %h %p User baz IdentityFile baz.id_dsa
あーーーーー,最初っから ssh_config
を書けば一目瞭然でした。
- host:gw1 に繋ぐためには user:foo で繋ぐ必要があることを示している(通常必要ない設定)
- host:gw2 に繋ぐためには host:gw1 で nc (on ssh) する必要があることを示している
- host:target に繋ぐためには host:gw2 で nc (on ssh) する必要があることを示している
これらがカスケード実行されているだけです。
上記の例ではわざわざ gateway で異なるユーザを指定しました。が,同じユーザを使うことが多いでしょうし,実際の環境では %h
の展開が理想どおりの値となることは少ないと思います。なので,より現実的な例を。
Host gw2 ProxyCommand ssh gw1 nc gw2.dmz.network %p Host target ProxyCommand ssh gw2 nc target.inside.network %p
- host:gw2 は host:gw1 にとって gw2.dmz.network という名前で見えている
- host:target は host:gw2 にとって target.inside.network という名前で見えている
という例ですが,このように %h
等の展開マクロは使わずに実名で書いた方が混乱は少ないと思います。
*1:ほんとにこんな凝ったシチュエーションがあるかどうかはかなり疑問ですが