SSHトンネリング:あらゆるネットワーク経由でリモートサービスに安全にアクセスする方法

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

SSHでトンネリングする3つの方法

ファイアウォールの裏に隠されたサービス――データベース、社内Webアプリ、Redisキャッシュなど――にアクセスしたい場合、いくつかの方法があります:ファイアウォールのポートを開ける、VPNを構築する、あるいはSSHトンネリングを使う。それぞれ適した場面があります。SSHの詳細に入る前に、それぞれを比較してみましょう。

方法1:ファイアウォールのポートを開ける

最もシンプルな方法:穴を開けてトラフィックを通す。機能はしますが、サービスがインターネットに直接さらされてしまいます。パスワード保護があっても、公開されたデータベースのポートは、24時間稼働しているブルートフォース攻撃や脆弱性スキャナーへの招待状と同じです。

方法2:VPNを構築する

VPNは、暗号化されたトンネルを通じてマシンをリモートネットワーク内に仮想的に配置します。チームや企業環境には最適ですが、VPNサーバー、クライアントソフトウェア、証明書の管理、継続的なメンテナンスが必要です。ステージングデータベースにちょっとアクセスしたいだけのソロ開発者には、明らかにやりすぎです。

方法3:SSHトンネリング

SSHトンネリングは、既存のSSH接続を通じて、あるマシンから別のマシンへポートを転送します。追加ソフトウェアも、認証局も、新しいファイアウォールルールも不要です。サーバーにSSH接続できれば、トンネルを通せます。

トンネルの種類は3つあり、それぞれ異なる問題を解決します:

  • ローカル転送 — リモートサービスをローカルマシン上にあるかのようにアクセスする
  • リモート転送 — ローカルのポートをリモートサーバーに公開する
  • ダイナミック転送(SOCKSプロキシ) — 任意のトラフィックをSSHサーバー経由でルーティングする

各方法のメリットとデメリット

ローカルポート転送

最もよく使うトンネルの種類です。たとえばステージングサーバーがポート5432でPostgreSQLを実行しているが、そのポートは公開されていないとします。次のようにローカルマシンに転送できます:

ssh -L 5433:localhost:5432 [email protected]

データベースクライアントをlocalhost:5433に向ければ、リモートのPostgreSQLインスタンスとローカルで動いているかのように通信できます。DBeaver、DataGrip、あるいは普通のpsqlなど、特別な設定なしに使えます。

  • メリット: 非常にシンプル――一時的なアクセスに設定ファイルは不要
  • メリット: SSH(ポート22)を許可しているほとんどのファイアウォールを通過できる
  • デメリット: SSHセッションが終了するとトンネルも切断される
  • デメリット: コマンド1つにつきポート1つの転送――4つのサービスを扱うには4つのターミナルタブが必要

リモートポート転送

リモート転送は方向が逆になります。ローカルマシン上のサービスをリモートサーバーに公開します。典型的なユースケース:公開URLが必要なWebhookレシーバーのテスト、またはNATの裏にあってインバウンド接続を受け付けられない開発マシンへのアクセスです。

ssh -R 8080:localhost:3000 [email protected]

これにより、public-server.com:8080に来るトラフィックがローカルのポート3000に届きます。

  • メリット: ルーターのポート転送を変更せずに、ローカルサービスをインターネットから到達可能にする
  • デメリット: localhost外からの接続を受け付けるには、サーバーのsshd_configGatewayPorts yesの設定が必要
  • デメリット: 適切に制限しないとセキュリティリスクになる――リモートサーバー上の誰でもトンネル経由でマシンにアクセスできる

ダイナミック転送(SOCKSプロキシ)

ダイナミック転送はSSH接続をSOCKS5プロキシに変えます。経由したトラフィックはSSHサーバーのネットワークから出ていくため、社内リソースへのアクセスや地域制限のある動作のテストに便利です。

ssh -D 1080 [email protected]
  • メリット: コマンド1つでSOCKS対応のすべてのトラフィックをプロキシ――ブラウザ、curl、git、プロキシ設定に対応するものなら何でも
  • メリット: フルVPNなしで社内リソースを閲覧するのに便利
  • デメリット: すべてのアプリケーションがSOCKS5をネイティブにサポートしているわけではない――proxychainsのようなラッパーが必要な場合がある
  • デメリット: 各アプリケーションのプロキシ設定を個別に設定する必要がある

推奨セットアップ

日常の開発作業では、2つを組み合わせましょう:よく使うトンネルのSSH設定エントリと、インタラクティブシェルを開かずにバックグラウンドでトンネルを実行する-Nフラグです。

~/.ssh/configにトンネル定義を追加する

長いコマンドを何度も入力するのはやめましょう。~/.ssh/configにトンネルを一度定義します:

Host staging-db-tunnel
    HostName staging-server.com
    User deploy
    LocalForward 5433 localhost:5432
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host prod-redis-tunnel
    HostName prod-server.com
    User deploy
    LocalForward 6380 localhost:6379
    ServerAliveInterval 60
    ServerAliveCountMax 3

次に、短いコマンドでどちらかのトンネルを起動します:

ssh -N staging-db-tunnel &
ssh -N prod-redis-tunnel &

-Nフラグは、SSHにリモートコマンドを実行しないよう指示します――接続を開いたまま保ち、ポート転送を維持するだけです。&でプロセスをバックグラウンドにします。

ServerAliveIntervalServerAliveCountMaxは、多くの人が思っている以上に重要です。これらがないと、アイドル状態のトンネルは数分後に静かに切断され、データベースクエリがタイムアウトするまで気づきません。これらの設定があると、SSHは60秒ごとにキープアライブを送信し、切断する前に3回の応答失敗を許容します。これで、クラウドインフラでよくある一時的な障害のほとんどをカバーできる、約3分間の耐性が確保されます。

長期稼働トンネルにはautosshを使う

無期限に稼働し続けるトンネルが必要ですか?autosshは接続を監視し、切断されたときに自動的に再起動します:

# autosshをインストール
sudo apt install autossh   # Debian/Ubuntu
brew install autossh       # macOS

# 永続的なトンネルを開始
autossh -M 0 -N -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" \
  -L 5433:localhost:5432 [email protected]

-M 0フラグはautossh独自の監視ポートを無効にし、代わりにSSHの組み込みキープアライブに依存します――監視ポートの方法は不安定で不必要な複雑さを加えるため、現在推奨されているアプローチです。

実装ガイド

ステップ1:基本的なローカルトンネル

シンプルに始めましょう。SSHアクセスが機能することを確認し、その上に構築する前にトンネルが接続されることを確認します:

# リモートポート5432をローカルポート5433に転送
ssh -L 5433:localhost:5432 -N [email protected]

# 別のターミナルで接続をテスト
psql -h localhost -p 5433 -U dbuser mydb

ステップ2:マルチホップトンネリング

対象サービスがSSHサーバー自体にない?問題ありません。ジャンプサーバーが到達できるなら、あなたも到達できます――転送ルールで内部ホスト名を指定するだけです:

# 書式:-L ローカルポート:対象ホスト:対象ポート
ssh -L 5433:db-internal.private:5432 -N [email protected]

db-internal.privateはローカルマシンではなく、jump-server.comの視点から解決されます。これにより、パブリックDNSエントリを持たないホストにもアクセスできます。

ステップ3:ProxyJumpを使ったジャンプホスト

OpenSSH 7.3(2016年リリース)はProxyJumpを導入しました――多くのチュートリアルがいまだに紹介している古いProxyCommand netcatパターンよりスッキリした代替方法です。SSHの設定に一度チェーンを定義します:

Host internal-app
    HostName 10.0.1.50
    User appuser
    ProxyJump [email protected]
    LocalForward 8080 localhost:8080

あとはこれを実行するだけです:

ssh -N internal-app

SSHは2ホップの接続を自動的にネゴシエートします。手動のnetcatも、シェルのトリックも不要です。

ステップ4:永続的なトンネルのためのSystemdサービス

このセットアップは、1年以上手間なしで本番環境で稼働しています。再起動、ネットワーク切断、SSH切断――autosshがすべて処理します。カギはすべてをsystemdサービスでラップすることです:

# /etc/systemd/system/ssh-tunnel-db.service
[Unit]
Description=ステージングDBへのSSHトンネル
After=network.target

[Service]
User=deploy
ExecStart=/usr/bin/autossh -M 0 -N \
  -o "ServerAliveInterval=60" \
  -o "ServerAliveCountMax=3" \
  -o "ExitOnForwardFailure=yes" \
  -L 5433:localhost:5432 \
  [email protected]
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel-db
sudo systemctl start ssh-tunnel-db
sudo systemctl status ssh-tunnel-db

ExitOnForwardFailure=yesを省略しないでください。これがないと、トンネルのバインドが静かに失敗しても(たとえば別のプロセスがそのローカルポートを既に使用しているため)、autosshは問題なく動き続けます。サービスステータスは緑色に見えても、実際の接続はゼロです。このオプションがあると、autosshが明確に失敗するため、systemdがクリーンに再起動できます。

クイックセキュリティチェックリスト

  • SSH鍵認証を使用する――サーバーでパスワード認証を無効化する(sshd_configPasswordAuthentication no
  • AllowTcpForwardingでポート転送の権限を制御する――sshd_configMatch Userブロックを使ってユーザーごとに制限する
  • リモート転送の場合、外部アクセスが意図的でない限りlocalhostにバインドする:-R 127.0.0.1:8080:localhost:3000
  • サーバー上のアクティブな転送ポートを確認する:ss -tlnp | grep ssh

SSHトンネリングは50人チームのVPNの代替にはなりません。しかし、ソロ開発者や少数のサービスを管理する小規模チームには、通常これで十分です。設定エントリを一度整えれば、リモートデータベースや社内サービスへのアクセスがコマンド1つになります――暗号化チャネル付きで、追加インフラは不要です。

Share: