[ポート番号].localhostでSSH内のlocalhostにアクセスする
やること
ssh user@ssh.server -L 9876:localhost:9876
に接続した上で、
クライアント側のブラウザで http://8000.127.0.0.1:9876/resource
をリクエストすると、
接続したSSHサーバー内のhttp://127.0.0.1:8000/resource
を返すようなプロキシサーバーを構築します。
モチベーション
SSHのローカル環境と、個人端末内でのローカル環境を同居させることが目的です。
SSHのダイナミックポートフォワードと組み合わせたSOCKSプロキシを用いる場合、ループバックはおろか全ネットワークがSSHプロセスによってプロキシされてしまい、その間は端末内のローカル環境は封鎖されてしまいます。
かと言って使用するであろうポートを事前にひとつひとつ-L
オプションで追加しているとキリがないわけで。
そこでChromeがlocalhostのサブドメインをDNS設定なしで巻き取ってくれる事を利用した、簡易的なトンネリングを行うプロキシサーバーをSSH側に構築します。
開発環境
Deno v2.0.2
注意点
このプロキシはあくまでもブラウザによるlocalhostのサブドメインの巻き取りを利用したものであり、追加のfetchやiframe、WebSocketを含むあらゆる接続要素はプロキシされることはないためご注意ください。(そのため VSCode-Server はフォルダが正常に表示されませんでした)
実装
ここでは事前にローカルポートフォワーディングを指定して接続します。
ssh user@ssh.server -L 9876:localhost:9876
サーバーでの実装に移ります。
const
urlPortCache = {},
urlObjCache = {}
;
Deno.serve({
port: Number(Deno.args[0])
}, req => {
const reqURL = urlObjCache[req.url] ||= new URL(req.url);
return fetch(Object.assign(reqURL, {
port: reqURL.hostname.split(".")[0],
host: "127.0.0.1"
}).href)
})
deno --allow-net ./main.js 9876
テスト
適宣ファイルサーバーを使用して適当なディレクトリをホストします。
ここではDeno 標準モジュールの file_server
を使用します。
file_server --port=8000
Chromeから8000.localhost:9876
にアクセスして、従来のlocalhostへのリクエストと同様の結果が得られれば成功です。
いかがでしたか?
この実装・およびこれを改変したものを実行して起こる不具合の責任は負いかねます。
追記・訂正(2024/10/27)
先述の例ではVSCodeのserve-webをプロキシ経由で表示しましたが、結果としてVSCodeは正常に動作できませんでした。
よってこのプロキシはプレビューのlocalhostにのみ適用することを推奨します。
原因はVSCode内部の仕様です。
WebSocket接続の解決がSSH側で8000番ポートからホストされた場合、クライアントではwss://localhost:8000
に接続するようになっており、結果として通常のHTTPリクエストは8000.localhost:9876
でプロキシできてもWebSocketはプロキシできないためです。
よってこの場合、VSCodeのserve-webコマンドは別途固定のローカルポートで直接クライアントに表示し、プレビュー用のlocalhostはVSCode用のものとは別の固定ポートからこのプロキシを経由することを推奨します。
以下の訂正例では、VSCode専用に8000番を、プレビュー用に9876番を開放しています。
ssh user@ssh.server -L 8000:localhost:8000 -L 9876:localhost:9876
Discussion