termal.in

← ブログ

SSH ポート転送の仕組み:ローカル・リモート・ダイナミックの 3 つのトンネル

· Termalin Team sshport-forwardingtunnelingtutorial

SSH ポート転送は、Stack Overflow からコピーして一度は動かせたものの、結局よく理解しないまま終わる類いの機能です。だから次に必要になったとき、また -L・-R・-D のどれを使うか当てずっぽうに逆戻りします。3 つのフラグは難しくありません。ただ名前が下手なだけです。ここでは、それらを一目瞭然にする 1 つの考え方、それぞれの例、そしてトンネルが午後いっぱい生き延びるかどうかを左右するフラグと落とし穴を紹介します。

フラグを選ぶ、たった 1 つの問い

どの SSH トンネルも、片方のマシンでリッスンするポートを開き、そこに届いたものをもう一方のマシンから到達できるどこかへ静かに送り届けます。ですから問いはただ 1 つ。あなたが接続するポートは、どちら側に開くのか? です。

  • -L(ローカル)——ポートはあなたのマシンに開きます。ローカルに接続し、通信はサーバー側から出ていきます。
  • -R(リモート)——ポートはサーバーに開きます。誰かがそこに接続し、通信はあなたの側から出てきます。
  • -D(ダイナミック)——ポートはあなたのマシンに開きますが、これは SOCKS プロキシです。宛先は前もって固定せず、接続ごとに決まります。

あとはすべて細部です。「ポートがどちら側に開くか」を頭に入れておけば、二度と間違ったフラグに手を伸ばすことはありません。

ローカル転送(-L):リモートのサービスを localhost に引き込む

最も一般的なものです。データベース、社内のダッシュボード、メトリクスの UI など、サーバー上の 127.0.0.1 にバインドされた何か、あるいはサーバーだけが到達できるマシン上の何かです。それを、まるでローカルにあるかのようにノート PC で使いたいわけです。

ssh -L 5432:localhost:5432 deploy@db.example.com

左から右に読みます:5432 を自分のマシンに開き、そこに届いたものを(サーバーから見た)localhost:5432 へ転送する。これでノート PC 上の psql -h 127.0.0.1 がサーバーの Postgres と話します(英語)。暗号化された SSH 接続を通じて、しかもデータベースをネットワークに一切さらすことなく、です。

真ん中のホストはサーバー側で解決されます。これがこの仕組みを強力にしている点です:

# reach a database that only the bastion can see
ssh -L 5432:10.0.0.9:5432 deploy@bastion.example.com

ここで 10.0.0.9 は、ノート PC ではルーティングできないプライベートアドレスです。しかし踏み台(bastion)からは届きます。トンネルがその隔たりを橋渡しします。

リモート転送(-R):自分のマシンをサーバー側に公開する

鏡写しであり、多くの人が向きを取り違えるものです(これには専用の詳説があります:SSH リバーストンネル)。ポートはサーバー上に開き、そこへの接続はあなたの側から出てきます。定番の用途が 2 つあります:

# let the server (or its network) reach a dev server running on your laptop
ssh -R 8080:localhost:3000 deploy@app.example.com

これでサーバー上の localhost:8080 に当たったものは、ノート PC の localhost:3000 へ送られます。マシンにいる同僚に作りかけのものを見せたり、リモートのサービスから、あなただけが動かしている何かへコールバックさせたりするのに便利です。

これは、受信アクセスのない NAT の内側で動けないマシンに到達するための手でもあります。そのマシンに、あなたが管理するサーバーへ向けてリバーストンネルを張らせ、あなたはサーバー経由で接続します。接続を受け付けられないマシンが、代わりにアウトバウンドの接続を行うのです。

1 つ注意点があります。既定ではサーバーは転送ポートを localhost にバインドするので、サーバー自身しか使えません。サーバーのネットワーク上のほかのマシンから到達させるには、サーバーの sshd_config に GatewayPorts yes(または clientspecified)が必要です。これはサーバー側の設定で、クライアントから強制できるものではありません。

ダイナミック転送(-D):フラグ 1 つの SOCKS プロキシ

-L は 1 つの固定された宛先へ転送します。-D は代わりに SOCKS プロキシを開くので、宛先はリクエストごとに選ばれます:

ssh -D 1080 deploy@jump.example.com

ブラウザー(または curl --socks5 127.0.0.1:1080)を 127.0.0.1:1080 に向ければ、すべてのリクエストがサーバーからルーティングされます。社内ネットワークをざっと見て回る、データセンターの IP からサービスがどう見えるか試す、あるいは数台の社内ホストに、それぞれ -L を書かずに到達する、といったことの手早いやり方です。

使いものにするためのフラグ

素のトンネルコマンドは、要らないシェルまで開き、そのシェルを閉じた瞬間に死にます。実際には:

ssh -N -L 5432:localhost:5432 deploy@db.example.com
  • -N——リモートコマンドを実行しません。欲しいのはトンネルであって、シェルではありません。
  • -f——接続後にプロセスをバックグラウンドへ回します(-N と組み合わせます)。
  • -o ExitOnForwardFailure=yes——ポートをバインドできない(使用中、または権限がない)場合に、そのまま接続して何も転送しないのではなく、明示的に失敗します。
  • キープアライブ——ServerAliveInterval 30 がないと、長く張るトンネルはアイドルタイムアウトで死にます。入れておかないと、使うのをやめた瞬間にトンネルが落ちます。

ssh_config で恒久化する

-L/-R/-D で渡すものにはすべて設定ディレクティブがあるので、保存したホストが自分のトンネルを持ち運びます:

Host db-tunnel
    HostName db.example.com
    User deploy
    LocalForward 5432 localhost:5432
    ServerAliveInterval 30
    RequestTTY no

これで ssh -N db-tunnel が、覚えるべきフラグなしにデータベースを 127.0.0.1:5432 へ連れてきます。RemoteForward と DynamicForward も同じように動きます。(設定ファイルそのものについては ~/.ssh/config ガイド(英語)で詳しく。)

3 つの落とし穴

  • バインドアドレスが効く。 -L 5432:... は 127.0.0.1 だけでリッスンします。自分の LAN 上のほかのデバイスにトンネルを使わせるには、バインドアドレスを前に付けます:-L 0.0.0.0:5432:...。そして、それでローカルネットワークに公開したことを理解しておいてください。
  • 「Address already in use」 は、ローカルポートが使われている(多くは終了し損ねた前回のトンネル)という意味です。別のローカルポートを選ぶか、古いものを止めてください。ExitOnForwardFailure は、静かな失敗を、ちゃんと気づけるエラーに変えます。
  • 真ん中のホストは向こう側で解決される。 -L a:localhost:b の localhost は、あなたのではなくサーバーの localhost を指します。これを逆に取り違えるのが、ポート転送で最も多い間違いです。

トンネルがバインドできないとき

コマンドが bind: Address already in use や bind: Permission denied で失敗する場合、リスナーは一度も開いていません。短いリストを順にたどりましょう。「使用中(Already in use)」:何かがそのポートを握っています。多くは昨日の忘れられたトンネルです。ss -tlnp がプロセスを教えてくれるので、別のポートを選ぶか、古いものを止めます。ローカルで「権限拒否(Permission denied)」が出るのは、たいてい特権ポートです。1024 未満はすべて root が必要なので、443 ではなく 8443 でリッスンします。リモート側(-R)では、サーバーが拒否権を持ちます。sshd_config の AllowTcpForwarding no は転送を真っ向から禁止し、GatewayPorts がなければ、どんなバインドアドレスを要求してもリモートのリスナーは localhost だけにバインドします。さらに、堅牢化された RHEL 系のホストでは、設定が許していても SELinux が sshd のバインドを拒否することがあります。自分のコマンドを疑う前に、監査ログを確認してください。

手でトンネルをさばくのに疲れたら

転送は素晴らしいプリミティブで、どの開発者も 3 つのフラグをそらで言えるべきです。摩擦はあとから現れます。ssh -N -L ... のコマンド(英語)をメモ帳に溜め込み、ノート PC がスリープするたびに打ち直し、どのローカルポートがどのサービスに対応するか忘れ、そもそもトンネルがまだ生きているのかも一目では分からなくなります。

そこで、ちゃんとしたトンネルマネージャーを備えたクライアントが役に立ちます。Termalin は、ローカル・リモート・ダイナミックの各転送を、保存された設定としてホストにひも付けておきます。クリックで起動でき、どれが稼働中か分かり、コマンドを打ち直す代わりに接続と一緒に再確立されます。同じホスト一覧(鍵、踏み台ホスト、キープアライブ付き)は、複数のマシン間でエンドツーエンド暗号化で同期し、各サーバーは初回接続時に検証されます。コマンドラインはトンネルの仕組みを教えてくれます。クライアントは、毎朝それを手で組み直す手間から解放してくれます。

いずれにせよ、肝はこのモデルがすべてです。ポートがどちら側に開くかを決めれば、残りのフラグは自ずと決まります。


Termalin は、トンネル・SFTP・鍵マネージャーを内蔵した無料のクロスプラットフォーム SSH クライアントです。ダウンロードするか、鍵を安全に扱う仕組みをご覧ください。

まずは 1 台のホストで試してみてください。

Termalin は、あなたとあなたのエージェントのための高速な SSH クライアントです。

無料プラン · Pro を 14 日間お試し · 料金