エンジニアリングノート

SSHリバーストンネルでクラウドMacからローカルAPIを検証する

リモート Mac ·約 10 分

SSHリバーストンネルでクラウドMacからローカルAPIを検証する

ローカルの開発マシンでは未デプロイの API が動作している一方、Xcode プロジェクトやビルドスクリプトはクラウド Mac 上で実行されているとします。ルーターのポートを直接開放する方法は手間がかかるうえに攻撃対象領域を広げます。また、リポジトリ内の接続先を一時的に公開ドメインへ書き換えると、設定が意図せず残るおそれがあります。より安全なのは、開発マシンから SSH 接続を開始し、リバーストンネルでローカルポートをクラウド Mac のループバックアドレスへ転送する方法です。

まず通信方向を確認する

リバーストンネルが解決するのは、「クラウド側からローカルへアクセスする」ケースです。たとえば、開発マシンの 127.0.0.1:8080 で API が動作しており、クラウド Mac から 127.0.0.1:18080 経由で呼び出したいとします。SSH 接続は開発マシンからクラウド Mac に対して確立され、クラウド側で受け取った通信がローカルポートへ戻されます。

ローカルフォワーディングはその逆です。開発マシンから、クラウド Mac のループバックアドレスだけで待ち受けているデバッグサービスへアクセスする場合に使います。たとえば、クラウド側のサービスが 127.0.0.1:9000 にあるなら、開発マシンの 127.0.0.1:19000 へ転送できます。

目的 SSH オプション 接続を開始する場所 アクセス先
クラウド Mac からローカル API へアクセス -R 開発マシン クラウド側の 127.0.0.1:18080
ローカルからクラウド側のデバッグサービスへアクセス -L 開発マシン ローカルの 127.0.0.1:19000

最初からトンネルを疑うのではなく、まず実際のクライアントを使って転送元サービスが利用できることを確認します。

curl -i http://127.0.0.1:8080/health
lsof -nP -iTCP:8080 -sTCP:LISTEN

アプリケーションにヘルスチェック用のパスがない場合は、ローカルでのみ待ち受けるテスト用サーバーを一時的に起動できます。

python3 -m http.server 8080 --bind 127.0.0.1

公開範囲を最小限にしたリバーストンネルを作成する

開発マシンでクラウド側のアドレスを設定し、次のコマンドを実行します。

export CLOUD_MAC_IP="你的节点地址"
ssh -N \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=30 \
  -o ServerAliveCountMax=3 \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

-N はリモートコマンドを実行せず、接続をポート転送専用にするオプションです。ExitOnForwardFailure を指定すると、ポートのバインドに失敗したまま正常に見える SSH セッションだけが残る事態を防げます。2 つのキープアライブ設定は切断済みの接続を検出するためのものであり、ネットワーク切断後にトンネルが自動再確立されることを保証するものではありません。

クラウド Mac で別のターミナルを開き、動作を確認します。

lsof -nP -iTCP:18080 -sTCP:LISTEN
curl -i http://127.0.0.1:18080/health

lsof の出力では、待ち受けアドレスが *:18080 ではなく 127.0.0.1:18080 になっていることを確認します。結合テスト用の接続先はソースコードにハードコードせず、開発環境の環境変数としてアプリケーションへ渡します。

export DEV_API_BASE_URL="http://127.0.0.1:18080"
xcodebuild -scheme DemoApp -configuration Debug test

トンネルが担うのは通信の暗号化と入口の制限だけであり、API 自体の認証を代替するものではありません。ポートがループバックアドレスだけで待ち受けている場合でも、テスト用トークン、権限チェック、機密情報を除外したログは維持してください。

SSH 設定で操作ミスを減らす

長いコマンドを毎回入力すると、ローカルポート、リモートポート、転送先アドレスを取り違えやすくなります。開発マシンの ~/.ssh/config に専用エントリを作成しておくと安全です。

Host minid-api-tunnel
    HostName CLOUD_MAC_IP
    User dev
    RemoteForward 127.0.0.1:18080 127.0.0.1:8080
    ExitOnForwardFailure yes
    ServerAliveInterval 30
    ServerAliveCountMax 3

CLOUD_MAC_IP を実際のノードアドレスに置き換え、設定ファイルの権限が適切であることを確認します。

chmod 700 ~/.ssh
chmod 600 ~/.ssh/config
ssh -N minid-api-tunnel

1 つのエントリに無関係な転送設定を大量にまとめないようにします。プロジェクトごとに別のエイリアスを用意し、ポート番号も用途別に分けます。たとえば API には 18080、デバッグパネルには 19000 を割り当てます。これにより、待ち受けポートから対象プロジェクトを特定しやすくなり、複数人でノードを共有する場合の競合も減らせます。

ローカルからクラウド側のサービスへアクセスする場合は、ローカルフォワーディングを別途作成します。

ssh -N \
  -o ExitOnForwardFailure=yes \
  -L 127.0.0.1:19000:127.0.0.1:9000 \
  dev@"$CLOUD_MAC_IP"

その後、開発マシンからのみ http://127.0.0.1:19000 へアクセスします。どちらの転送方法でも、アプリケーションサービスをパブリックネットワークインターフェースで待ち受けさせる必要はありません。

接続失敗をレイヤーごとに切り分ける

トンネルの問題は、「転送元サービス」「SSH 転送」「呼び出し元」の 3 レイヤーに分けて確認します。ファイアウォール、アプリケーション設定、プロジェクトコードを同時に変更すると、原因を特定しにくくなります。

転送元サービスのレイヤー

開発マシンで curllsof を実行します。API が IPv6 の ::1 だけで待ち受けているのに、転送先として 127.0.0.1 を指定している場合、接続は拒否されます。待ち受けプロトコルを統一するか、転送先を実際のアドレスに変更してください。また、ローカルプロキシがループバック通信を処理していないことも確認します。一時的に次のコマンドを使用できます。

curl --noproxy '*' -i http://127.0.0.1:8080/health

SSH 転送のレイヤー

詳細ログを有効にして、ポート転送の要求結果を確認します。

ssh -vv -N \
  -o ExitOnForwardFailure=yes \
  -R 127.0.0.1:18080:127.0.0.1:8080 \
  dev@"$CLOUD_MAC_IP"

リモートポート転送の失敗が表示された場合は、まず未使用のポートへ変更します。それでも失敗するなら、クラウド側の SSH サービスで TCP 転送が許可されているか確認してください。手軽だからといってパブリックアドレスで待ち受ける設定に変更したり、すべての接続元を一括で許可したりしないでください。

呼び出し元のレイヤー

クラウド側のターミナルで curl が成功しても、すべての実行ターゲットが同じネットワーク条件を自動的に引き継ぐとは限りません。ビルドスクリプト、GUI アプリケーション、シミュレータでは、それぞれ異なる環境設定が読み込まれる可能性があります。また、HTTP リクエストがプロジェクトのセキュリティポリシーによって制限されることもあります。呼び出し元プロセス、接続先 URL、レスポンスコード、タイムアウト時間を個別に記録し、ネットワーク障害なのかアプリケーションレイヤーでの拒否なのかを切り分けます。

利用前の確認と終了後のクリーンアップ

結合テストが完了したら、先にトンネルへ依存しているテストタスクを停止し、その後 SSH セッションを終了します。lsof で転送ポートが消えていることを確認し、プロジェクトから一時的な接続先、テスト用トークン、デバッグログを削除します。

コードをコミットする前に、少なくとも次の項目を確認してください。

  • API が引き続き開発マシンのループバックアドレスだけで待ち受けている。
  • クラウド側の転送ポートが 127.0.0.1 だけで待ち受けている。
  • SSH 秘密鍵の権限が 600、設定ディレクトリの権限が 700 になっている。
  • プロジェクトが環境変数で結合テスト用アドレスを選択し、リリース設定では該当ポートを参照していない。
  • ログにリクエストトークン、鍵、完全なユーザーデータ、認証ヘッダーが含まれていない。
  • 複数人でプロジェクトを共有する場合、ポート番号、担当者、終了予定時刻が記録されている。

この方法は、一時的な API、コールバックの検証、リモートビルドとの結合テストに適しています。複数のノードから長期間にわたって呼び出されるサービスには、独立した認証、アクセス制御、監査ログを備えた正式なデプロイを使用し、一時的なトンネルを本番環境の入口として扱わないでください。

よくある質問

SSHリバーストンネルでローカルAPIが外部公開されることはありますか?

リモート側の待受を127.0.0.1に固定すれば、クラウドMac自身からのみ接続できます。0.0.0.0を指定せず、lsofで実際の待受アドレスを確認してください。

SSH接続後も転送先ポートへアクセスできない場合は何を確認しますか?

開発端末のAPIが正しいポートで待ち受けているかを先に確認し、次にクラウドMacでlsofを実行します。ポート競合とSSHサーバー側のTCP転送制限を分けて調べます。

Xcodeから実行したアプリでもトンネルを利用できますか?

クラウドMac上のビルドスクリプトは127.0.0.1の転送ポートを利用できます。シミュレータやアプリでは、ネットワーク範囲とHTTP通信ポリシーも個別に確認が必要です。

MiniD クラウド Mac

専用物理 Mac mini を日・週・月単位でレンタル

各プランは専用の物理 Mac mini で稼働し、リモートデスクトップと SSH の両方で接続できます。最新の機種・リージョン・期間は注文ページでご確認ください。

注文オプションを見る