踏み台サーバを2台経由して SSH ログイン・ポートフォワーディングする
通常、最もシンプルな構成で別のサーバに SSH 接続する際は、
- ローカルマシン
- 目的のサーバ
という登場人物で、ローカルマシンから
$ ssh -i 【ローカルマシンにある秘密鍵のパス】 【目的のサーバのユーザ名】@【目的のサーバのパブリック IP】
と叩けば SSH 接続できた。
踏み台サーバを用いる構成については以前も解説したことがある。
- ローカルマシン
- 踏み台サーバ
- 目的のサーバ
を順に繋いで、以下のようなコマンドで接続するやり方だ。
$ ssh -o ProxyCommand='ssh -W %h:%p -i 【ローカルマシンにある踏み台サーバ用の秘密鍵のパス】 【踏み台サーバのユーザ名】@【踏み台サーバのパブリック IP】' -i 【ローカルマシンにある目的のサーバ用の秘密鍵のパス】 【目的のサーバのユーザ名】@【目的のサーバのプライベート IP】
ローカルマシンから、ProxyCommand
オプションで接続した踏み台サーバとの接続環境を利用して、目的のサーバに接続する形となる。
この構成での SSH ポートフォワーディングについても以前解説した。
今回は、間にさらに1台、別の踏み台サーバが登場する。
- ローカルマシン
- 踏み台サーバ A
- 踏み台サーバ B
- 目的のサーバ
いわゆる多段 SSH を行うワケだが、この場合の SSH ログインの方法と、SSH ポートフォワーディングの方法をまとめる。
目次
3段 SSH ログイン
コマンドのみで実現
先に答えから。
$ ssh -o ProxyCommand="ssh -o ProxyCommand='ssh -i 【ローカルにある踏み台 A の秘密鍵パス】 【踏み台 A のユーザ名】@【踏み台 A の IP】 -W 【踏み台 B の IP】:【踏み台 B のポート】' -i 【ローカルにある踏み台 B の秘密鍵パス】 【踏み台 B のユーザ名】@【踏み台 B の IP】 -W %h:%p " -i 【ローカルにある目的のサーバの秘密鍵パス】 【目的のサーバのユーザ名】@【目的のサーバのプライベート IP】
随分長ったらしくなったが、コマンド内で擬似的に改行してみると少し読み取りやすくなるだろうか。
$ ssh \
-o ProxyCommand=" \
ssh -o ProxyCommand=' \
ssh -i 【ローカルにある踏み台 A の秘密鍵パス】 【踏み台 A のユーザ名】@【踏み台 A の IP】 -W 【踏み台 B の IP】:【踏み台 B のポート】 \
' \
-i 【ローカルにある踏み台 B の秘密鍵パス】 【踏み台 B のユーザ名】@【踏み台 B の IP】 -W %h:%p \
" \
-i 【ローカルにある目的のサーバの秘密鍵パス】 【目的のサーバのユーザ名】@【目的のサーバのプライベート IP】
実際に IP などを当てはめてみるとこんな感じ。IP は以下のようにテキトーに決めた。
- 踏み台サーバ A = 100.11.1.1 (パブリック IP なイメージ)
- 踏み台サーバ B = 10.22.2.2 (プライベート IP なイメージ)
- 目的のサーバ = 10.33.3.3 (プライベート IP なイメージ)
$ ssh -o ProxyCommand="ssh -o ProxyCommand='ssh -i ~/.ssh/key-bastion-A user-A@100.11.1.1 -W 10.22.2.2:22' -i ~/.ssh/key-bastion-B user-B@10.22.2.2 -W %h:%p " -i ~/.ssh/key-target user-target@10.33.3.3
# 複数行に分けるとこんな感じ
$ ssh \
-o ProxyCommand=" \
ssh -o ProxyCommand=' \
ssh -i ~/.ssh/key-bastion-A user-A@100.11.1.1 -W 10.22.2.2:22 \
' \
-i ~/.ssh/key-bastion-B user-B@10.22.2.2 -W %h:%p \
" \
-i ~/.ssh/key-target user-target@10.33.3.3
ポイントは以下のとおり。
- いずれのサーバも、ポートはデフォルトの22番で接続しているテイなので、
-p 22
オプションの表記がない。-W 10.22.2.2:22
と、踏み台サーバ B の IP とポートを指定している場所は、ポート番号を省略できないので書いてある。 -W %h:%p
というイディオムは、ProxyCommand
使用時に標準入力と標準出力を中継するためのオプション。%h:%p
部分は目的のサーバのホストとポートに置換される。- 3段以上の SSH となると、
ProxyCommand
も入れ子になる。間にいるサーバでは、標準入出力の中継先を%h:%p
で指定できないので、踏み台サーバ B の IP とポートをベタ書きしている。 -i
オプションで指定する SSH 秘密鍵のファイルパスは、全て、ローカルマシン上にあるファイルのパスを指定する。踏み台サーバ A・B には秘密鍵ファイルを置いておく必要はない。
ssh
コマンドはオプションの位置に特段制限がないので、-o ProxyCommand
を先に書いて、なるべくコマンドの見た目が「(ローカル →) 踏み台 A → 踏み台 B → 目的のサーバ」と読めるように並べてやると良いと思う。
SSH Config ファイルに書く
とはいえ、毎回こんな長ったらしいコマンドを書くのもしんどいので、~/.ssh/config
に書くことにする。
# 踏み台サーバ A
Host bastion-a
HostName 100.11.1.1
User user-A
IdentityFile ~/.ssh/key-bastion-A
# 踏み台サーバ B
Host bastion-b
HostName 10.22.2.2
User user-B
IdentityFile ~/.ssh/key-bastion-B
ProxyCommand ssh bastion-a -W %h:%p
# 目的のサーバ
Host target-server
HostName 10.33.3.3
User user-target
IdentityFile ~/.ssh/key-target
ProxyCommand ssh bastion-b -W %h:%p
やはり設定ファイルに書くとシンプル。コレで、
$ ssh target-server
と入力するだけで、踏み台 A → 踏み台 B を経由して目的のサーバに SSH ログインできるようになった。
ちなみに、この設定ファイルのまま、
$ ssh bastion-b
と実行すれば、踏み台 A を経由して踏み台 B に SSH ログインできる。
3段 SSH ポートフォワーディング
次はポートフォワーディング。
- ローカルマシン
- 踏み台サーバ
- 目的のサーバ
という、踏み台が1台の場合は、
$ ssh -t -i 【踏み台の秘密鍵】 【踏み台のユーザ名】@【踏み台の IP】 -L 【ローカルのポート】:目的のサーバの IP:【目的のサーバのポート】
$ curl http://localhost:【ローカルのポート】/
こんな感じでポートフォワードが設定できる。
それでは、この間にもう1台、踏み台サーバが増えた場合はどうするか。
- ローカルマシン
- 踏み台サーバ A
- 踏み台サーバ B
- 目的のサーバ (このサーバの 8080 ポートをフォワードしたい)
コマンドのみで実現 その1
まずは、ターミナルを2タブ使い、2コマンドで実現する方法。
- 1つ目のタブで、ローカルマシンから、踏み台サーバ A を経由して踏み台サーバ B をトンネリングする。
- 2つ目のタブで、1つ目のタブでトンネリングしたローカルのポート (= 踏み台サーバ B) から、目的のサーバをトンネリングする。
コレをコマンドに直すと以下のとおり。
# 1つ目のタブ
$ ssh -t -i 【踏み台 A の秘密鍵】 -p 22 【踏み台 A のユーザ名】@【踏み台 A の IP】 -L 10022:【踏み台 B の IP】:22
# 2つ目のタブ
$ ssh -t -i 【踏み台 B の秘密鍵】 -p 10022 【踏み台 B のユーザ名】@localhost -L 28080:【目的のサーバの IP】:8080
1つ目のタブで、踏み台 B の22番ポートとのトンネルを作る。ココでは適当に、ローカルの 10022
ポートに当たるようにした。この時点で、
$ ssh -i ~/.ssh/key-bastion-B -p 10022 user-B@localhost
とコマンドを指定すれば、踏み台 B に SSH ログインできる状態になっているワケだ。ポートフォワードしているので、localhost
の 10022
ポートを指定することで接続できるのであ。
で、2つ目のタブは、このトンネルを利用して踏み台 B に入り、目的のサーバの 8080 ポートとのトンネルを作る。ココでは 28080
ポートに割り当てたので、最終的には
$ curl http://localhost:28080/
と叩けば、目的のサーバの内容にアクセスできる。
コマンドのみで実現 その2
続いて、コレを1つのコマンドにまとめてみる。長いので2行に改行したが、1行で実行できる。
$ ssh -o ProxyCommand='ssh -i 【踏み台 A の秘密鍵】 -p 22 【踏み台 A のユーザ名】@【踏み台 A の IP】 -L 10022:【目的のサーバの IP】:8080 -W %h:%p' \
-i 【踏み台 B の秘密鍵】 -p 22 【踏み台 B のユーザ名】@【踏み台 B の IP】 -L 28080:【目的のサーバの IP】:8080
自分で編み出しておきながらイマイチ分かっていないのだが、ProxyCommand
で 10022
ポートとのトンネルを作っているが、このポート番号は以降指定することなく済んでしまう。実際に目的のサーバの 8080 ポートを流しているポートは、後半に出てくる 28080
ポートの方なので、先程と同様
$ curl http://localhost:28080/
でアクセスできる状態になる。ProxyCommand
内の -L
オプションを消すと上手く動かなくなるし、でもどんなポート番号を使っていてもよくて、上手く繋がる。何だろコレ。
SSH Config ファイルに書く
続いてコレと同じモノを ~/.ssh/config
に書く。bastion-a
と bastion-b
の接続情報は先程掲載したモノと同じ。目的のサーバの IP は 10.33.3.3 とする。
# 踏み台サーバ A
Host bastion-a
HostName 100.11.1.1
User user-A
IdentityFile ~/.ssh/key-bastion-A
# 踏み台サーバ B
Host bastion-b
HostName 10.22.2.2
User user-B
IdentityFile ~/.ssh/key-bastion-B
ProxyCommand ssh bastion-a -W %h:%p
# 目的のサーバの 8080 ポートを 28080 ポートにフォワーディングする
Host target-server-forwarding
HostName 10.22.2.2
User user-B
IdentityFile ~/.ssh/key-bastion-B
# ↑ ココまでは「踏み台サーバ B」に接続するための情報を書く
ProxyCommand ssh bastion-a -W %h:%p -L 10022:10.33.3.3:8080
# ↑ 「Host bastion-b」の ProxyCommand に追加してローカルポートフォワードの設定を追加する
LocalForward 28080 10.33.3.3:8080
# ↑ 実際にローカルマシンで割り当てたいポートを指定する
Host target-server-forwarding
の主な接続情報は、踏み台 B のモノになる点に注意。目的のサーバの情報は、2箇所に書いた 10.33.3.3.:8080
部分にしか出てこない。
ProxyCommand
内で指定している 10022
ポートは、先程も書いた用途がよく分からないポート。ポート番号が何でも良い理由が分かっていない。実際にトンネリングができているのは、LocalForward
で指定した 28080
ポートなので、繰り返しになるが接続 URL は
$ curl http://localhost:28080/
となる。
以上
SSH コマンドの仕様はなかなか覚えられない…。
ただ、Windows ユーザは PuTTY とか Tera Term とかで設定してしまって、原理的なところを知らないまま使っていることが多いように見受けられる。自分はそうはなりたくないので、仕組みを理解して使えるようになりたい。とはいえ今回も、最後に出てきた 10022
部分が、何ポートでも良い理由が解明できていないのだが…。