🐧

SSH のリバースポートフォワード

2024/10/28に公開

はじめに

最近、SSH のリバースポートフォワードの設定をしたので、備忘録として説明を書いておきます。

SSH のリバースポートフォワード

ラズパイなどをモニタレスで使いたいときに、ssh ログインができるように設定して、リモートから管理できるようにしています。

このとき、ラズパイは LAN 環境に置いてあるとします。これをゲスト用のネットワークに接続しているユーザーが使えるようにしたいとします。

LAN 環境からはゲスト用のネットワークへはアクセスできますが、ゲスト用のネットワークからは LAN 環境へは基本的には接続ができないようになっているとします。完全には遮断していませんが、ネットワーク的には LAN 側はファイアウォールでゲスト側から守られているとします。

こういうときに、SSH のリバースポートフォワードを使います。

一般的な SSH のリバースポートフォワード

今回、筆者が構築したいと考えた事例は少し特殊なので、一般的な SSH のリバースポートフォワード利用について先に説明しておきます。

ここで使用するマシンは次のとおりです。全体の仕組みを説明することを優先して、認証については、きちんと説明していません。ここでは認証については、エージェントフォワード機能を有効にするなどして、各段階で通過できる設定になっているとします。

ホスト名 役割
sshd001.internal mobile001.internal から SSH ログインして使うマシン
ssh-gw.example.jp sshd001.internal へ SSH ログインするときに経由するゲートウェイマシン
mobile001.internal 外出先などから sshd001.internalへ SSH ログインするときに使うマシン

まず、SSH を使うと安全な通信路を経由して、リモートにあるマシンへログインすることができます。

ここで、ssh-gw.example.jp では、ポート番号 22 で待機する ssh サーバー(sshd のプログラム)が動作していて、インターネットでアクセスできるようになっているとします。

また、sshd001.internal は LAN で動かしているマシンで、ファイアウォールで守られていて、通常の方法ではインターネットから接続はできないとします。

ssh-gw.example.jp はインターネットで公開されているマシンなので、sshd001.internal から ssh クライアントである ssh コマンドを使ってリモートログインができます。

/images/ssh/img0.png
SSH の利用例

こういう環境があるとき、ssd001.internal でも ssh サーバーを起動してから、リバースポートフォワードの機能を使うと、ssh-gw.example.jp から ssd001.internal へ SSH ログインができるようにすることができます。

/images/ssh/img1.png
リバースポートフォワード

sshd001.internal で起動した sshd サーバーは ssh-gw.example.jp から ssd001.internal へ SSH ログインするときに使います。

sshd001.internal では、-R オプションを使って、ssh-gw.example.jp の ssh サーバーへ SSH ログインすると同時にポートフォワード用の通信路を作成します。

図の例では、ssh-gw.example.jp 上で有効な IP アドレス 127.0.0.1 のポート番号 1022 から、sshd001.internal から接続可能な localhost:22 へのポートフォワード用の通信路が作成されます。

ここで、sshd001.internal から localhost:22 へアクセスすると、それは、sshd001.internal で稼働する sshd サーバーに接続します。

この結果、ssh-gw.example.jp 上で 127.0.0.1:1022 へアクセスすると、sshd001.internal で稼働する sshd サーバーに接続する環境が用意されます。

LAN 環境にいる限りでは、これを利用する必要はありませんが、ノートパソコンなどを使って外出先から LAN 環境の sshd001.internal へアクセスしたいときに、この環境を利用します。

/images/ssh/img2.png
外出先からの利用

この図を見るとわかるように、外出先でノートパソコンの mobile001.internal から自宅の LAN 環境で動作する sshd001.internal へアクセスしたいときは、ssh-gw.example.jp の SSH サーバーへリモートログインできれば良いです。

ssh-gw.example.jp の SSH サーバーへリモートログインできたら、そこで ssh 127.0.0.1:1022 コマンドを実行することで、sshd001.internal へ SSH ログインができます。このコマンドを実行することで、あらかじめリバースポートフォワードの機能を使って用意しておいた「127.0.0.1:1022 からの localhost:22 へのポートフォワード」経由で SSH ログインができるからです。

この仕組みを使って、「LAN 環境に置いてあるラズパイをゲスト用のネットワークに接続しているユーザーが使えるようにすること」を実現するわけです。なお、この後の説明では、ここでの説明した環境の応用例なので、ここの図で使ったホスト名やポート番号とは変えてあります。その点に注意して読んでください。

SSH ゲートウェイマシン

まず、SSH ゲートウェイマシンとなるマシンをゲスト用のネットワークへ用意します。ssh-gw.guest.internal というホストだとします。sshd を起動して、user001 で ssh ログインできるようにしてあるとします。

このマシンへはゲスト用ネットワーク内の他のマシン host001.guest.internal から ssh ログインできるとします。

host001.guest.internal(ゲストネットワーク)
↓
user001@ssh-gw.guest.internal:22(ゲストネットワーク)

LAN 内の SSH マシン

LAN 内の SSH マシンとして host-a.lan.internal を用意します。こちらは ssh-gw.guest.internal:22 へ ssh ログインできるようになっています。

user001@ssh-gw.guest.internal:22 (ゲストネットワーク)
↑
host-a.lan.internal (ローカルエリアネットワーク(LAN))

リバースポートフォワード

host-a.lan.internal から ssh-gw.guest.internal:22 へ ssh 接続するときに、リバースポートフォワード機能を使うと、ssh-gw.guest.internal の 127.0.0.1:9022 を host-a.lan.internal の localhost:22 へポートフォワードすることができます。

ssh-gw.guest.internal の 127.0.0.1:9022
  ↓
host-a.lan.internal の localhost:22

実際に host-a.lan.internal 上で user001 ユーザーが実行するコマンドは次のようになります。localhost は実際は 127.0.0.1 なのですが、ここでは ssh-gw.guest.internalhost-a.lan.internal の 127.0.0.1 を区別したいので、host-a.lan.internal 側は localhost と指定することにして説明します。

ssh -N -f -R 127.0.0.1:9022:localhost:22 user001@ssh-gw.guest.internal

リバースポートフォワードのためのオプションは -R で、指定するものは次の通りです。

[bind_address:]port:host:hostport

ここでは -N -f のオプションはリバースポートフォワードと直接は関係ないので説明を省略します。

リバースポートフォワードで指定する値については勘違いしやすいので、表にして説明します。

変数 説明
bind_address ssh-gw.guest.internal 上で使用する IP アドレス
port ssh-gw.guest.internal 上で使用するポート番号
host host-a.lan.internal から接続する IP アドレス
hostport host-a.lan.internal から接続するポート番号

この -Rssh user001@ssh-gw.guest.internal のコマンドで ssh 接続するときに指定するオプションだということを忘れないでください。

この表での bind_address は、ssh ログインした ssh-gw.guest.internal 上で利用できる IP のどの IP アドレスをバインドして使うかを指定するためのものです。省略すると、IP アドレスについてはバインド可能なすべてのものが使われます。

この表での port は、 bind_address で指定した IP アドレスの、どのポートを使うかを指定するためのものです。

そのため、127.0.0.1:9022:localhost:22 とした場合は、ssh-gw.guest.internal の 127.0.0.1 のポート番号 9022 を転送元として使うということになります。

次に、hostssh コマンドを実行するマシンで名前解決することができるホスト名か IP アドレスを指定します。ここだと host-a.lan.internal になります。

hostport は、host へ接続するときに使うポート番号になります。

改めての確認ですが、ssh user001@ssh-gw.guest.internal で、ssh-gw.guest.internalhost-a.lan.internal は ssh 接続ができています。この接続を使って、ssh-gw.guest.internal 側のネットワークから host-a.lan.internal 側のネットワークへポートフォワードするための通信を追加するためのオプションが、リバースポートフォワードのオプションなのです。

今回は、host-a.lan.internal の sshd サービスについて、localhost と対応する IP アドレスのポート番号 22 でポートフォワードされたパケットを受け取るので、127.0.0.1:9022:localhost:22 という指定になるわけです。

以上で用意されるネットワークは次のようになります。

ssh-gw.guest.internal ... 127.0.0.1:9022 (ゲストネットワーク)
                                ↓
host-a.lan.internal ... localhost:22 (ローカルエリアネットワーク(LAN))

host-a.lan.internal への ssh ログイン

ssh-gw.guest.internal から host-a.lan.internal への ssh ログインができるネットワーク環境が用意できたので、実際に使ってみましょう。

  1. host001.guest.internal から ssh-gw.guest.internal へ ssh ログイン
  2. ssh-gw.guest.internal 上で ssh user001@127.0.0.1:9022 で ssh ログイン

こうすると、ssh-gw.guest.internal 上の 127.0.0.1:9022host-a.lan.internal:22 へポートフォワードされているので、host-a.lan.internal へ ssh ログインができます。

ワンライナーでコマンド実行する場合は -t オプション(Force pseudo-terminal allocation.)を使い、ssh ログイン後にターミナルを用意して標準入出力の転送をします。具体的には、host001.guest.internal で次のコマンドを実行します。

ssh user001@ssh-gw.guest.internal -t ssh user001@127.0.0.1:9022

実際のところ、よく使う場合は config ファイルで指定をするのが楽です。VS Code などでも利用できるようにするには、オプション指定はすべて ~/.ssh/config に含めておくのが良いです。

例えば host001.guest.internal:/home/user001/.ssh/config に次のように書いておきます。

Host host-a.lan.internal
    HostName localhost
    Port 9022
    IdentityFile   ~/.ssh/id_ed25519
    ProxyCommand   ssh ssh-gw.guest.internal -CW %h:%p

このように書いておくと、下記コマンドで host-a.lan.internal へ ssh ログインできます。

ssh host-a.lan.internal

ProxyCommandHost で指定したホスト名に ssh ログインするときに実際に実行するコマンドになります。%h%p は config 内の Host に含まれる HostNamePort にそれぞれ対応しています。

そのため、ProxyCommandssh ssh-gw.guest.internal -CW %h:%p という指定により、実際は ssh ssh-gw.guest.internal -CW localhost:9022 のコマンドが実行されます。

ちなみに -C は Requests compression of all data. のオプション、-W は Requests that standard input and output on the client be forwarded to host on port over the secure channel. のオプションになります。-CW で、通信データを圧縮して、標準入出力の転送もするということになります。

なお、~/.ssh/config ファイルでは ProxyCommand と似たような指定として ProxyJump というものも使えます。ProxyJump でホストを指定すると、そのホストを経由した SSH 接続になります。これについては、経由する SSH サーバーが多段となる場合などに使うと便利です。内部的には ProxyCommand へ置き換えられるようなので、わかりやすい方を使えば良いでしょう。複雑な設定をするときは、指定できるオプションが豊富な ProxyCommand の方を使います。

Discussion