- SSHトンネリングは安全なSSH接続の上でTCPトラフィックを運び、レガシープロトコルの暗号化・管理パネルへのアクセス・NATの背後にあるサーバーへの接続のように、制限された経路を安全に開くために使われる
- サーバー側では
AllowTcpForwarding yesが必要で、ループバック以外のインターフェースでポートを開くには**GatewayPorts yes**の設定とSSHサーバーの再起動が必要
ssh -J, ssh -L, ssh -R, ssh -Dはそれぞれジャンプホスト、ローカルフォワーディング、リモートフォワーディング、SOCKS5ベースの動的フォワーディングを担当する
- トンネルをバックグラウンドで維持するには
ssh -fNを使い、切断の検知はClientAliveIntervalやClientAliveCountMaxのようなハートビート設定で調整する
- SSHトンネリングはUDPを直接サポートせず、TCP-over-TCPでは遅延やスループットの問題が発生しうるため、VPNの代替というよりは特定のTCP経路を開くためのツールに近い
SSHトンネリングを使う場面
- SSHトンネリングとポートフォワーディングは、SSH接続を通じてTCPトラフィックをクライアントからサーバーへ、またはサーバーからクライアントへ転送する
- TCPポートとUNIXソケットを使えるが、例はTCPポートを中心としている
- 代表的な活用方法はセキュリティ、トラブルシューティング、接続の迂回に分けられる
- セキュリティ
- FTPのような安全でない接続やレガシープロトコルを暗号化する
- 公開鍵認証ベースのSSHトンネルでWeb管理パネルにアクセスする
- 80/443のような追加ポートを公開せず、22番ポートだけを開けられる
- トラブルシューティング
- ファイアウォールやコンテンツフィルタを回避する
- 別のネットワーク経路を選択する
- 接続
- NATの背後にあるサーバーへアクセスする
- ジャンプホストを通じてインターネットから内部サーバーへ入る
- ローカルポートをインターネットに公開する
ポートフォワーディング前に確認すべき設定
- 例のオプションは環境に合わせて組み合わせて設定できる
bind_addressを指定しない場合、デフォルト値はlocalhost
- ローカルとリモートのユーザーは、それぞれのマシンでポートを開く権限が必要
0-1024番ポートは別途設定がなければroot権限が必要
- それ以外のポートは一般ユーザーでも設定できる
- クライアントとネットワークのファイアウォールも、フォワーディング経路に合わせて開いている必要がある
- SSHサーバーではポートフォワーディングが有効になっている必要がある
AllowTcpForwarding yes
- デフォルトで有効なことが多いが、サーバー設定の確認が必要
127.0.0.1以外のインターフェースへポートをフォワーディングするには、SSHサーバーでGatewayPortsを有効にする
GatewayPorts yes
- 設定後はSSHサーバーサービスを再起動する必要がある
SSHジャンプホストと多段トンネル
ssh -Jは、1台以上のホストを経由してリモートホストへ透過的に接続するときに使う
ssh -J user@REMOTE-MACHINE:22 -p 22 user@10.99.99.1
- デフォルトポート
22を使う場合はポート表記を省略できる
- REMOTE-MACHINEをジャンプホストとして使うと、接続は次のように確認できる
[user@REMOTE-MACHINE]$ ss | grep -i ssh
tcp ESTAB 0 0 167.135.173.108:ssh 192.160.140.207:45960
tcp ESTAB 0 0 10.99.99.2:49770 10.99.99.1:ssh
- 例のアドレスの役割は次のとおり
167.135.173.108: REMOTE-MACHINEの公開IP
192.160.140.207: LOCAL-MACHINEの公開IP
10.99.99.2: REMOTE-MACHINEの内部IP
10.99.99.1: REMOTE-WEBAPPの内部IP
- 複数のジャンプホストを使う場合はカンマで区切る
ssh -J user@REMOTE-MACHINE:22,user@ANOTHER-REMOTE-MACHINE:22 -p 22 user@10.99.99.1
ローカルポートフォワーディング
- ローカルポートフォワーディングは
ssh -Lオプションを使う
- 1つ目の例では、LOCAL-MACHINEの
10.10.10.1:8001をREMOTE-MACHINEのlocalhost:8000へ転送する
ssh -L 10.10.10.1:8001:localhost:8000 user@REMOTE-MACHINE
- REMOTE-MACHINEで
127.0.0.1にのみバインドされたWebサーバーのアクセスログは次のように見える
127.0.0.1 - - [30/Dec/2022 18:05:15] "GET / HTTP/1.1" 200
- このリクエストはLOCAL-MACHINEから開始される
- 2つ目の例では、ローカル
8001をREMOTE-MACHINE経由で10.99.99.1:8000へ転送する
ssh -L 8001:10.99.99.1:8000 user@REMOTE-MACHINE
- REMOTE-WEBAPPのWebサーバーアクセスログには、REMOTE-MACHINEの内部IPから来たリクエストとして記録される
10.99.99.2 - - [30/Dec/2022 21:28:42] "GET / HTTP/1.1" 200
リモートポートフォワーディング
- リモートポートフォワーディングは
ssh -Rオプションを使う
- 例ではREMOTE-MACHINEの
8000を、ローカル側のlocalhost:8001または10.10.10.2:8001へ転送する
ssh -R 8000:localhost:8001 user@REMOTE-MACHINE
ssh -R 8000:10.10.10.2:8001 user@REMOTE-MACHINE
- 特定のリモートインターフェースである
10.99.99.2:8000で受信するよう設定することもできる
ssh -R 10.99.99.2:8000:10.10.10.2:8001 user@REMOTE-MACHINE
- ループバックインターフェース以外で受信するには、SSHサーバーで**
GatewayPorts yes**が有効になっている必要がある
動的ポートフォワーディングとSOCKS5
- 複数のポートを転送するとき、SSHはSOCKSプロトコルを使う
- SOCKSは透過型プロキシプロトコルで、SSHは最新バージョンであるSOCKS5を使用する
- SOCKS5サーバーのデフォルトポートはRFC 1928で定義された
1080
- クライアントはアプリケーション層またはOS層でSOCKSプロキシを使うよう設定されている必要がある
ssh -Dの例では、10.10.10.1:5555にSOCKSプロキシを開く
ssh -D 10.10.10.1:5555 user@REMOTE-MACHINE
- LOCALクライアントでは
curlで接続経路をテストできる
curl -L -x socks5://10.10.10.1:5555 brrl.net/ip
- 正常に動作すればREMOTE-MACHINEの公開IPが返される
SSH TUN/TAPトンネリング
-wフラグで双方向トンネルを作成できる
- インターフェースは事前に作成されている必要がある
- この方式には、未テストであるという注意書きがある
-w local_tun[:remote_tun]
バックグラウンド実行と終了
- トンネルをネイティブな方法でバックグラウンド実行するには
-fNを使う
-f: バックグラウンド実行
-N: シェルを開かない
ssh -fN -L 8001:127.0.0.1:8000 user@REMOTE-MACHINE
- それ以外には
screenや別のツールを使える
- バックグラウンドで実行中のSSHはプロセスを見つけてPIDで終了する
user@pleasejustwork:~$ ps -ef | grep ssh
[...]
user 19255 1 0 11:40 ? 00:00:00 ssh -fN -L 8001:127.0.0.1:8000 user@REMOTE-MACHINE
[...]
kill 19255
SSH接続の維持と再接続
- SSH接続を維持する方法はいくつかある
- タイムアウト処理のためのハートビートオプションは、クライアント、サーバー、またはその両方に設定できる
ClientAliveIntervalは接続維持のため、n秒ごとにリクエストを送る
ClientAliveInterval 15
ClientAliveCountMaxは、相手から応答を受け取れなかったあと、接続を終了するまでに送るハートビート要求数
ClientAliveCountMax 3
3はデフォルト値で、0に設定すると接続終了を無効化する
- 例の設定では、応答がなければ約45秒後に接続が切れる
- 接続終了後の再接続には
autossh、スクリプト、cronjobなどを使える
制限とセキュリティ上の注意点
-
UDP
- SSHは正しい復号のために信頼性のある転送に依存している
- UDPは信頼性を提供しないため、SSHトンネルではサポートされず推奨もされない
- UDP over SSH tunnelingを扱った方法もあるが、未テストであるという注意書きがある
-
TCP-over-TCP
- オーバーヘッドのためスループットが低下し、遅延が増加する
- パケットロスや高遅延のある接続ではTCP meltdownが発生する可能性がある
- TCP over TCPに関する記事を参考にできる
- OpenVPN-over-TCPをしばらく使った際、スループットはUDPより低かったが安定して動作し、結果は構成に大きく依存した
-
VPNの代替として使う際の限界
- SSHトンネリングをVPNのように使うことはできるが、より良い性能を求めるならVPNのほうが適している
-
セキュリティリスク
- この機能が不要なら無効化することが推奨される
- 攻撃者がSSHトンネリングとポートフォワーディングを使ってファイアウォールや他のセキュリティ手段を回避できる
-
参考文書
1件のコメント
Hacker News の意見
2024年には、SSH コマンドを長く直接書くよりも、
~/.ssh/configに LocalForward、RemoteForward、ProxyJump を設定するほうがよい複数の中継 SSH 接続を経由しなければならないリモートサーバーに
ssh、scp、rsyncでアクセスするとき、時間を大幅に節約できる例えば
jump-host-1、jump-host-2、jump-host-3をProxyJumpでつないでおき、target-serverにエイリアスで接続すればよいLocalForward 0.0.0.0:8080 0.0.0.0:80はリモートの 80 番ポートをローカルの 8080 に転送し、RemoteForward 0.0.0.0:9022 0.0.0.0:22はリモートの 9022 に入ってきた SSH リクエストをローカルの 22 番へ転送するLocalForwardとRemoteForwardでは左側が対象サーバー、右側がローカルで、localhostや127.0.0.1の代わりに0.0.0.0を使うという Tips もあるssh_configの Tips を共有すると、自宅内やセルフホストの VPN 内では各機器に直接 SSH し、外からはジャンプホストを経由するよう自動分岐しておく構成が便利Match host *.example.org exec "getent ahosts router.example.org | grep -q ^10.0.0.1"のようにルーターのアドレスで自宅/VPN かどうかを先に検出し、arpで MAC アドレスが期待値と違えばProxyJump jump.example.orgを使うようにできる順序が重要で、VPN/自宅ネットワークの検出を先に行い、その後に外部の場合のジャンプホストを適用する必要がある
短期間使う VPS を作って SSH を SOCKS プロキシとして使い、地域制限を回避したり、別チームを少し手伝うために普段アクセスしないサーバーへ一度だけ入るような場合は、コマンドラインオプションのほうがよいと思う
0.0.0.0を使うのは危険な場合があるすべてのネットワークインターフェースでポートを開くことができ、特にリモートサーバーにファイアウォールがなければ、何かをインターネット全体に公開してしまう
よく使うトンネリングやポート転送なら設定ファイルがよいが、一回限りやまれな作業ならコマンドラインオプションのほうがよいと思う
単一ユーザー/複数マシンに合い、別サーバーを常時立てておく必要なく GitHub などから設定を取得する、GitOps に似た解決策を探している
bash/fish関数を作って、最小限の引数だけ覚えればよいようにしてある忘れたら、文書化しておいた関数定義を見ればよい
私が働いている、とんでもない企業環境では SSH トンネリングが必須
アクセス権を得たり、ポートを開けたり、ファイアウォール/VPN の例外を得たりするのに、官僚主義のせいで何週間もかかることがある
この記事は
-Dに言及しているが、その威力を十分には説明していないssh -D 8888 someserverを実行して、ブラウザの SOCKS プロキシをlocalhost:8888に設定すると、ブラウザのトラフィックがすべてsomeserver経由でルーティングされるFirefox は今でもシステムのデフォルト値を変えずにこの設定ができるので、とても便利
ところが会社に入ってみると、IP 許可リストのせいでインターネット上の任意のサーバーへ SSH 接続することすらできず、あまりにつらくて HTTP トンネルを作り、自分が制御するサーバーを許可リストに入れる方法まで調べたが、結局転職した
そのような制限には理由があり、迂回は組織の手続きやセキュリティに対する専門性不足や軽視と見なされかねない
アクセス権を得るのに何週間も何か月もかかるなら、そのように待つべきで、機密情報にバックドアを開けたり、セキュリティを損なったりした責任を負いたくはないはず
深夜3時にやった中で最も汚い SSH トンネリングのハックは、3か所のデータセンターをつなぐ作業だった
同じ都市圏にある3つの施設はインターネットの上位網には接続されていたが、妙なルーティングポリシーのせいで A は B にしか、B だけが C に接続できた
結局、A のデータを B 経由で C へ移すために、rsync + SSH トンネル + 鍵 + ルーティングの小細工を混ぜ合わせる必要があり、呪文のようなコマンドを何度か直して合わせたあと、全体が一度に動くのを見たときは魔法のようだった
今ならどうすればよいかはっきり分かるが、当時初心者だった身としては大きな達成で、同期が完了したのを確認したときの高揚感は今でも覚えている
~/.ssh/configと ProxyJump を使えば、A、B、C、D、E のように何段階でも実質的にジャンプして移動できる可視化の細部がよい
ネットワーキングには、とくにより低レベルの接続でトラフィックを視覚的に表現するツールがもっとたくさんあってほしい
もちろん静的な概念表現にすぎず、ほとんどのネットワークベンダーも何らかの形のトラフィック可視化は提供しているが、たいていは個別の指標やグラフのレベルにとどまる
Linux サーバーや IoT 機器がファイアウォールの背後にあり、固定 IP もないが SSH で接続したいなら、https://sshreach.me のようなトンネリングサービスを使える
何年もの間トンネリングをかなり使ってきたが、
-Jオプションは知らなかった数か月に一度トンネルが必要になるたびに30分かけて設定するより、トンネルを構成してくれる視覚的なツールがあればいいのに
TCP-over-TCP はオーバーヘッドを増やしてスループットを下げ、レイテンシを高め、パケットロスがある接続や衛星回線のような高遅延の接続では TCP メルトダウンを引き起こし得る、という説明がある
ただし SSH トンネルでは、TAP/TUN を使わない限り実際には問題にならない
SSH が TCP ストリームを解いて転送するため
ただし複数チャネルを使う場合は、ヘッドオブラインブロッキングのため性能が低下することがある
約15年前、大学ネットワークのファイアウォールを回避するために SSH トンネルを覚え、デフォルトポートを 443番 に変える必要があった
それ以来、ファイアウォール回避よりはるかに多様な用途で使い続けている
sshuttleを使ってみると、トンネリングがずっと楽になるsshuttle -r user@host 10.0.0.0/8のように使うと、10/8の範囲は自動的にトンネルされ、実質的に SSH 上の VPN のように動作するSSH 自体にリダイレクト機能があるのか気になる
たとえば A が B に SSH しようとすると、B が C に接続するよう伝え、A が透過的に C へ直接接続し、B はそれ以上コアのデータ経路に残らない、というような機能があるのか知りたい
自分のファイアウォール/ルーターは DHCP option 67 を正しく転送せず、必ず自分のアドレスを返すため、そのポートの PXE ブートトラフィックがルーター IP に向かったら実際の PXE ブートサーバーへルーティングされるよう、仮想 IP/ルールを設定する必要があった
そうでなければジャンプ機能を使えばよい:
ssh -J B CB が経路上にいる必要がなく、C に直接接続できるなら単に C へ直接接続すればよく、それができないならいずれにせよ B はホップとして入る必要がある
問題をもう少し説明すれば、より適した解決策があるかもしれない
ある程度組み込み寄りのホストなら DNS に「ルーティング」を担わせることもできるが、この場合 ホストキー指紋検証 は自分で処理する必要がある