SSH のリバースポートフォワード
はじめに
最近、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
コマンドを使ってリモートログインができます。
SSH の利用例
こういう環境があるとき、ssd001.internal でも ssh サーバーを起動してから、リバースポートフォワードの機能を使うと、ssh-gw.example.jp から ssd001.internal へ SSH ログインができるようにすることができます。
リバースポートフォワード
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 へアクセスしたいときに、この環境を利用します。
外出先からの利用
この図を見るとわかるように、外出先でノートパソコンの 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.internal
と host-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 から接続するポート番号 |
この -R
は ssh 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 を転送元として使うということになります。
次に、host
は ssh
コマンドを実行するマシンで名前解決することができるホスト名か IP アドレスを指定します。ここだと host-a.lan.internal
になります。
hostport
は、host
へ接続するときに使うポート番号になります。
改めての確認ですが、ssh user001@ssh-gw.guest.internal
で、ssh-gw.guest.internal
と host-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 ログインができるネットワーク環境が用意できたので、実際に使ってみましょう。
- host001.guest.internal から ssh-gw.guest.internal へ ssh ログイン
- ssh-gw.guest.internal 上で
ssh user001@127.0.0.1:9022
で ssh ログイン
こうすると、ssh-gw.guest.internal 上の 127.0.0.1:9022
は host-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
ProxyCommand
は Host
で指定したホスト名に ssh
ログインするときに実際に実行するコマンドになります。%h
と %p
は config 内の Host
に含まれる HostName
と Port
にそれぞれ対応しています。
そのため、ProxyCommand
の ssh 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