踏み台サーバを2台経由して SSH ログイン・ポートフォワーディングする

通常、最もシンプルな構成で別のサーバに SSH 接続する際は、

  1. ローカルマシン
  2. 目的のサーバ

という登場人物で、ローカルマシンから

$ ssh -i 【ローカルマシンにある秘密鍵のパス】 【目的のサーバのユーザ名】@【目的のサーバのパブリック IP】

と叩けば SSH 接続できた。

踏み台サーバを用いる構成については以前も解説したことがある。

  1. ローカルマシン
  2. 踏み台サーバ
  3. 目的のサーバ

を順に繋いで、以下のようなコマンドで接続するやり方だ。

$ ssh -o ProxyCommand='ssh -W %h:%p -i 【ローカルマシンにある踏み台サーバ用の秘密鍵のパス】 【踏み台サーバのユーザ名】@【踏み台サーバのパブリック IP】' -i 【ローカルマシンにある目的のサーバ用の秘密鍵のパス】 【目的のサーバのユーザ名】@【目的のサーバのプライベート IP】

ローカルマシンから、ProxyCommand オプションで接続した踏み台サーバとの接続環境を利用して、目的のサーバに接続する形となる。

この構成での SSH ポートフォワーディングについても以前解説した。

今回は、間にさらに1台、別の踏み台サーバが登場する。

  1. ローカルマシン
  2. 踏み台サーバ A
  3. 踏み台サーバ B
  4. 目的のサーバ

いわゆる多段 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 は以下のようにテキトーに決めた。

$ 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

ポイントは以下のとおり。

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. ローカルマシン
  2. 踏み台サーバ
  3. 目的のサーバ

という、踏み台が1台の場合は、

$ ssh -t -i 【踏み台の秘密鍵】 【踏み台のユーザ名】@【踏み台の IP】  -L 【ローカルのポート】:目的のサーバの IP:【目的のサーバのポート】

$ curl http://localhost:【ローカルのポート】/

こんな感じでポートフォワードが設定できる。

それでは、この間にもう1台、踏み台サーバが増えた場合はどうするか。

  1. ローカルマシン
  2. 踏み台サーバ A
  3. 踏み台サーバ B
  4. 目的のサーバ (このサーバの 8080 ポートをフォワードしたい)

コマンドのみで実現 その1

まずは、ターミナルを2タブ使い、2コマンドで実現する方法。

コレをコマンドに直すと以下のとおり。

# 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 ログインできる状態になっているワケだ。ポートフォワードしているので、localhost10022 ポートを指定することで接続できるのであ。

で、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

自分で編み出しておきながらイマイチ分かっていないのだが、ProxyCommand10022 ポートとのトンネルを作っているが、このポート番号は以降指定することなく済んでしまう。実際に目的のサーバの 8080 ポートを流しているポートは、後半に出てくる 28080 ポートの方なので、先程と同様

$ curl http://localhost:28080/

でアクセスできる状態になる。ProxyCommand 内の -L オプションを消すと上手く動かなくなるし、でもどんなポート番号を使っていてもよくて、上手く繋がる。何だろコレ。

SSH Config ファイルに書く

続いてコレと同じモノを ~/.ssh/config に書く。bastion-abastion-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 部分が、何ポートでも良い理由が解明できていないのだが…。