【PowerShell】SSHポートフォワーディングを設定する方法|ローカル・リモート転送

PowerShellからOpenSSHを使うと、アプリやDBに直接穴を開けずに「手元⇄サーバー」間へ安全なトンネルを張れます。
ここではローカル転送(-L)、リモート転送(-R)、SOCKSプロキシ(-D)の違いと具体例、設定ファイルでの自動化、動作確認とトラブル対処、セキュリティ上の注意点までを実務目線で解説します。

前提と基本構文

Windows 10 以降はOpenSSHクライアントが同梱されており、PowerShellからそのまま利用できます。
ポートフォワーディングの基本は「どこで待ち受け、どこへ中継するか」を3要素で指定することです。
ローカル転送は ssh -L [bind_addr:]local_port:dest_host:dest_port user@bastion、リモート転送は ssh -R [bind_addr:]remote_port:dest_host:dest_port user@bastion、SOCKSは ssh -D [bind_addr:]local_port user@bastion の形を取ります。

ローカル転送:手元から遠隔リソースへ安全に到達する

手元のTCPポートに来た通信を、SSHトンネル経由でリモートの任意ホストへ中継します。
たとえば社内だけで開放されているDBの5432番へ、手元の15432番をトンネルで対応付けると、クライアントは localhost:15432 へ接続するだけで済みます。

# リモートの db.internal:5432 を手元の 127.0.0.1:15432 へ転送
ssh -L 127.0.0.1:15432:db.internal:5432 user@bastion.example.com

PowerShellやツールからは localhost:15432 を指定します。外部に晒したくないときは必ず 127.0.0.1 へバインドします。全インターフェースで受けたい場合のみ 0.0.0.0:15432 を指定します。

リモート転送:遠隔側に入り口を作り手元のサービスへ到達させる

開発中のローカルWebを一時的に公開したい、または踏み台からあなたの端末へメンテナンス接続させたい場面では、リモート転送を使います。
リモート側(接続先)のポートに来た通信を、トンネル経由であなたの端末の任意ポートへ流します。

# リモートの 9000 を、手元の 127.0.0.1:8000 へ中継
ssh -R 9000:127.0.0.1:8000 user@bastion.example.com

リモートでの公開範囲を広げる場合は -R 0.0.0.0:9000:127.0.0.1:8000 のようにしますが、サーバー側の sshd_configGatewayPorts yes が必要です。
管理ポリシーに従い、不要に外部へ晒さない構成を心がけます。

SOCKSプロキシ:任意のTCP先を動的に中継する

先の宛先を固定せず、手元アプリの要求ごとに任意のホストへ到達したいなら、SOCKSプロキシを立てます。
ブラウザーや各種クライアントのプロキシ設定に localhost:1080 を指定し、DNS解決もプロキシ側で行うと社内名にも到達できます。

# SOCKS5 を 127.0.0.1:1080 に起動(-D)。必要なら -N でコマンド実行無し
ssh -D 127.0.0.1:1080 -N user@bastion.example.com

ブラウザーは手動でSOCKS5を指定し、可能なら「DNSをプロキシ経由にする」オプションを有効化します。curlは --socks5-hostname を用いるとDNSも踏み台側で解決します。

複数転送と持続運用:設定ファイルで自動化する

毎回長い引数を打つ代わりに ~/.ssh/config に定義して短いコマンドで起動できるようにします。
PowerShellからも同じ設定が読み込まれるため、作業の再現性が上がります。

# C:\Users\<ユーザー>\.ssh\config
Host tunnel-db
  HostName bastion.example.com
  User ubuntu
  LocalForward 127.0.0.1:15432 db.internal:5432
  LocalForward 127.0.0.1:18080 app.internal:8080
  ServerAliveInterval 30
  ServerAliveCountMax 3
  IdentitiesOnly yes
  StrictHostKeyChecking accept-new

Host tunnel-socks
  HostName bastion.example.com
  User ubuntu
  DynamicForward 127.0.0.1:1080
  ServerAliveInterval 30
  ServerAliveCountMax 3
# 起動は短く
ssh tunnel-db
# または
ssh tunnel-socks

サービス化したい場合はターミナルを占有しない -f(Windowsでは未サポート)や端末マルチプレクサtmux/screen、タスクスケジューラでの自動起動を検討します。
Windowsでは別ウィンドウで起動するか、バックグラウンド常駐に近い運用としてタスクスケジューラに登録します。

動作確認の勘所

ローカル転送は手元からの到達性を確認します。DBなら Test-NetConnection、Webなら Invoke-WebRequest を使います。

# ポートの疎通確認
Test-NetConnection -ComputerName 127.0.0.1 -Port 15432

# HTTPの簡易確認
Invoke-WebRequest http://127.0.0.1:18080/ -UseBasicParsing

SOCKSはcurlでの確認が手軽です。DNS解決も踏み台側に委ねるなら --socks5-hostname を使います。

curl --socks5-hostname 127.0.0.1:1080 http://app.internal:8080/ -I

よくあるエラーと対処

既に同じポートが使われていると「Cannot allocate requested address」等で失敗します。別ポートに変えるか待ち受けプロセスを停止します。
リモート転送が拒否される場合はサーバー側 /etc/ssh/sshd_configAllowTcpForwardingGatewayPortsPermitOpen を確認します。
社内DNS名へ届かない時はSOCKSで --socks5-hostname を使うか、トンネル先でDNS解決させます。
Windowsファイアウォールがローカル待ち受けをブロックすることもあるため、必要に応じて例外を追加します。

セキュリティ上の注意点

不要に 0.0.0.0 へバインドすると同じネットワークの端末からも接続できてしまいます。原則は 127.0.0.1 へ限定します。
長時間放置するSOCKSは認証のない内部プロキシになり得るため、必要な時だけ起動し、強固な鍵と ssh-agent を使い、ServerAliveInterval で切断検知を有効化します。
サーバー側では最小権限原則に従い、不要なリモート転送や外部公開を禁止する設定にしておきます。

実用レシピ:踏み台越しに内側WebとDBへ同時に張る

複数のLocalForwardを同時に指定して開発効率を上げます。WebアプリとDBの双方を安全に手元へ引き込み、IDEやブラウザーから通常のローカル接続として扱えます。

ssh -L 127.0.0.1:18080:app.internal:8080 `
    -L 127.0.0.1:15432:db.internal:5432 `
    user@bastion.example.com

まとめ

ローカル転送は「手元から内側へ」、リモート転送は「内側から手元へ」、SOCKSは「任意宛先の動的中継」という役割分担です。
PowerShellからは単発実行でも設定ファイルでも同じ要領で扱えます。用途に応じてバインド先と公開範囲を慎重に選び、KeepAliveと厳格な鍵運用を組み合わせれば、安全で快適なトンネリング環境が構築できます。