MCP tunnelsのセキュリティ — ベストプラクティスと侵害対応手順
MCP tunnelsの3層防御の仕組みと、侵害を疑ったときの対応手順・トンネル廃止手順をまとめています。
MCP tunnelsの3層防御はどこまでを守るか
MCP tunnelsは、プライベートネットワーク内のMCPサーバーへインバウンドポートを開けずに到達させるトンネルです。研究プレビューの機能で、稼働率・サポート・継続性の保証はなく、Anthropicはいつでも内容を変更したり提供を終えたりできる扱いです。トランスポートはCloudflareのネットワークで動いており、Cloudflare側も基盤の可用性を約束していません。
既定で効くのは、外向きのみの接続、エンドツーエンドの暗号化、IP検証の3つです。そのうえで、OAuthの設定、許可IP範囲、資格情報の扱いは利用者側の運用で決まります。既定の防御は次の3層に分かれ、守る相手がそれぞれ違います。
3層が守る相手
外側: mTLSとIP検証
AnthropicとCloudflareの間で相互TLSをかけ、認可されていないクライアントがトンネルに到達するのを防ぎます。
内側: インナーTLS
Anthropicのバックエンドとあなたのプロキシの間に、2回目のTLSを張ります。プロキシが提示する証明書はあなたが登録したCAで署名され、秘密鍵もあなたしか持ちません。そのためCloudflareや途中の機器はペイロードを検査できません。
各MCPサーバー: OAuth
認証済みのトンネル通信が相手でも、ツールの不正利用を防ぐ役割を担います。MCP認可仕様に沿って、サーバーごとに設定します。
Anthropicは、CA証明書がトンネルに登録されるまで接続しません。そのためペイロードはCloudflareのネットワークを渡る間、常に暗号化されています。
「外向きのみ」でも、リクエストは外から入ってくる
「アウトバウンドのみ」という説明が指しているのは、接続を張る向きです。リクエストの向きではありません。cloudflaredが外向きに張った接続を、Anthropicからのリクエストが逆向きに通ってきます。
リクエストが届くまでの道筋
- 1
cloudflaredが外向きに接続する
あなたのネットワークからトンネルエッジ(Cloudflareのネットワーク)へ、7844番ポートで接続します。接続は開いたままで、受信用のポートは開けません。
- 2
Anthropicがエッジ経由でリクエストを送る
外側のmTLSでエッジに入り、開いている接続を通ってcloudflaredに届きます。cloudflaredはlocalhost:8080のプロキシへ渡します。
- 3
プロキシがインナーTLSを終端する
ペイロードが平文になるのはここが最初です。cloudflaredとエッジが見るのは暗号文だけです。
- 4
ホスト名で上流のMCPサーバーを選ぶ
プロキシはホスト名から転送先を決め、接続先のIPが許可範囲に入っているかを確かめてから転送します。
プロキシはペイロードを最初に読める場所なので、プロキシのホストが侵害されると、ペイロードの露出に直結します。侵害対応の手順がトークンと秘密鍵、プロキシのホストの3点を起点にしているのは、このためです。
トンネルは通信を暗号化するが、MCPサーバーへの認証はしない
見落としやすい境界線がひとつあります。トンネルが保証するのは、Anthropicからあなたのプロキシまでの通信が暗号化され、途中経路から読めないことだけです。その先のアップストリームMCPサーバーに対する認証は別問題です。
MCPサーバーがOAuthやbearerトークンを要求するなら、トンネルを使っていない場合とまったく同じ方法で用意します。トンネルを使うのはClaude Managed AgentsとMessages APIで、Consoleで作ったトンネルはClaude.aiのコネクタとしては使えません。後述のベストプラクティスの筆頭にあるOAuthの必須化は、この境界線を埋める対策です。トンネルだけを設定して満足すると、経路は暗号化されていても、サーバー自体は無認証で誰でも呼べる構成になりかねません。
Cloudflareから何が見えるか
内側TLSがあるため、Cloudflareはリクエストやレスポンスの中身を読めません。ただし、次の接続メタデータはCloudflare側に渡ります。
- cloudflaredを動かすホストの送信元IPアドレス
- cloudflaredのホストフィンガープリント
- 接続のタイミングと通信量
- トンネルに割り当てられた
*.tunnel.anthropic.comのサブドメイン
このテレメトリの利用はAnthropicとの契約で制限されており、Cloudflareはこの研究プレビューにおけるサブプロセッサ(委託先の処理事業者)という位置づけです。
データ保持の観点では、APIのデータ保持ページの機能一覧で、MCP tunnelsはZero Data Retention(ZDR)もHIPAA readinessも対象外の「No」です。ペイロードが読めないことと、ZDRやHIPAA BAAの枠内で使えることは別の話になります。該当する業務データを流す予定のチームは、この表の行を先に見ておく価値があります。
侵害を防ぐためのベストプラクティス
認証・認可の層から見ます。最初の推奨は、すべてのMCPサーバーでOAuthを必須にすることです。MCP認可仕様に沿って設定すれば、トンネルのトランスポート認証に加えて、データ層でユーザー単位の認可が成立します。組織全体でSSOを有効にしておくと、トンネル・フェデレーションルール・サービスアカウントを変更できる管理者に、IdP側のセッション制御が及びます。
ネットワークの層では、upstream.allowed_ipsを最小限のCIDR範囲に絞るのが、プロキシのSSRF対策として最も効きます。既定値はRFC1918のプライベート範囲(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16)で、プロキシが接続できるのはIPv4だけです。プロキシとcloudflaredの出口も、次の宛先だけに絞ります。
| コンポーネント | 宛先 | ポート・プロトコル | 使う場面 |
|---|---|---|---|
| setupコンポーネント | 宛先api.anthropic.com | ポート・プロトコル443 TCP | 使う場面初期設定とトークンのローテーション |
| cloudflared | 宛先トンネルエッジ(198.41.192.0/19、2606:4700:a0::/44) | ポート・プロトコル7844 TCPとUDP | 使う場面通常運用 |
| プロキシ | 宛先上流のMCPサーバー | ポート・プロトコル設定どおり | 使う場面通常運用 |
Kubernetesならネットワークポリシー、Composeならホストのファイアウォールルールで絞ります。
allowed_ipsには、見落としやすい癖が2つあります。1つ目は、値を設定すると既定のRFC1918を拡張するのではなく置き換えることです。社内のMCPサーバーが複数あるなら、使っている範囲をすべて書き直す必要があります。2つ目は、0.0.0.0/0を入れるとSSRF対策が完全に外れることです。upstream.disable_ip_validationを有効にしても、IP検証は丸ごと無効になります。
# config/mcp-proxy.yaml(Helmでは gateway.config.upstream.allowed_ips)
upstream:
allowed_ips:
- 10.20.30.0/24 # 例: 対象のMCPサーバーが置かれたサブネットだけマネージドKubernetesには、ServiceのCIDRをRFC1918の外に割り当てるものがあります。クラスタ内のServiceへルーティングする構成では、プロキシのログにIP validation failed: <ip> is not a private addressが出ます。まずServiceのアドレスを調べ、該当する最小のCIDRだけを足します。
kubectl get svc kubernetes -n default -o jsonpath='{.spec.clusterIP}'運用の層では、ログの監視、証明書とトンネルトークンの定期ローテーション(侵害を疑った時点での即時ローテーションを含む)が挙げられています。あわせて、プロキシイメージの追随とSHA-256ダイジェストでのピン留め、秘密鍵とトークンを組織のシークレット管理に載せることも推奨です。資格情報の置き場所は構成で違い、Helmでは(プログラム経由のアクセスなら)リリース名のSecret1つ、Composeではdata/ディレクトリです。MCPサーバーが公開するツールとデータを用途に必要な範囲だけに絞ることも、独立した推奨事項です。1つのサーバーに何でも詰め込むと、OAuthで守っていても影響範囲が広がります。
侵害を疑ったときの対応手順
トンネルトークン、TLS秘密鍵、プロキシを動かすホストのいずれかが侵害された疑いがあるときは、次の順で対応します。
侵害対応の6手順
- 1
トンネルスタックを止める
コマンドは下のとおりです。Composeでは
--timeout 0を付け、待たずに即座に止めます。 - 2
アップストリームのMCPサーバーを切り離す
Managed Agentセッションの構成から該当サーバーを外します。Messages APIの
mcp_serversブロックへのURL指定もやめます。 - 3
トンネルをアーカイブする
アーカイブするとトンネルトークンが無効になり、ドメインも切り離されます。Consoleの「MCP tunnels」一覧から操作するか、APIで実行します。
- 4
Anthropicサポートへ報告する
疑わしい侵害は、サポートへ連絡します。
- 5
下流の資格情報をローテーションする
新しいトンネルを再発行し、影響を受けたMCPサーバーが発行していたOAuthトークンも入れ替えます。
- 6
ログを確認してから復旧する
新しいトンネルを稼働させる前に、疑わしい期間のプロキシ・cloudflared・MCPサーバーのログを確認します。
# Helm環境でのスタック停止
helm uninstall mcp-tunnel -n mcp-tunnel
# Docker Compose環境での即時停止
docker compose down --timeout 0トンネルを完全に廃止する手順
使わなくなったトンネルは、保存された資格情報ごと片付けます。侵害対応とほぼ同じ骨格ですが、緊急停止(--timeout 0)ではなく通常のシャットダウンで構いません。
- スタックを停止する。Helmなら
helm uninstall mcp-tunnel -n mcp-tunnel、Composeならdocker compose down。 - トンネルをアーカイブする。Consoleの「MCP tunnels」一覧から操作します。
- 保存済みの資格情報を削除する。
Helmでは、プログラム経由のアクセスならsetupコンポーネントがリリース名のSecretを1つ作っています。プログラム経由でない場合は、mcp-tunnel-tokenとmcp-tunnel-certを自分で作ったはずなので、該当するものを消します。
# Helm: 該当するSecretだけが消える(無いものは --ignore-not-found で読み飛ばす)
kubectl -n mcp-tunnel delete secret \
mcp-tunnel mcp-tunnel-token mcp-tunnel-cert \
--ignore-not-found
# Docker Compose: 秘密鍵・証明書・トークンが入るdataディレクトリを削除
sudo rm -rf dataComposeでは、秘密鍵と証明書がdata/にあります。トンネルトークンはプログラム経由のフローならdata/tunnel-token、手動フローならシェルの環境変数です。手動フローを取っていたなら、data/の削除に加えて環境変数も消します。config/ディレクトリとdocker-compose.yamlには秘密情報が含まれないため、同じ構成で再度立てる予定があれば残して構いません。
MCP tunnelsのセキュリティ責任分担は何を意味するか
公式の責任分担表では、Anthropicが持つ責任は3項目、利用者組織が持つ責任は7項目です。
| Anthropicが持つ責任(3項目) | 利用者組織が持つ責任(7項目) |
|---|---|
| トンネルへのアクセス制御 | 利用者組織が持つ責任(7項目)通信内容全般と第三者利用規約(Cloudflareの規約含む)への準拠 |
| プロキシへ接続する前のCA証明書検証 | 利用者組織が持つ責任(7項目)デプロイガイダンスの遵守 |
| 自組織が所有するトンネル以外へClaudeがリクエストを送らないことの保証 | 利用者組織が持つ責任(7項目)トンネルトークンとTLS秘密鍵の保護 |
| 利用者組織が持つ責任(7項目)サーバー証明書の管理と期限前の更新 | |
| 利用者組織が持つ責任(7項目)各MCPサーバーのOAuth設定 | |
| 利用者組織が持つ責任(7項目)プロキシとMCPサーバーのネットワークアクセス制限 | |
| 利用者組織が持つ責任(7項目)侵害を疑った場合のAnthropicへの通知 |
右列のうち、日々の作業として手が動くのは証明書の更新とトークンのローテーションです。setupコンポーネントが生成するサーバー証明書の有効期間は既定で90日(--cert-duration)で、CAは5年です。1つのトンネルには有効なCA証明書を同時に2枚まで登録でき、ダウンタイムなしで入れ替えられます。更新を誰が回すかは、デプロイ方法で決まります。
証明書の更新は誰が回すか
Helm + プログラム経由のアクセス
チャートが-cert-renewの名前のCronJobを置き、毎日(既定は0 0 * * * UTC)実行します。期限までserverCert.renewBefore(既定30日)を切ったときだけ更新します。API呼び出しはなく、プロキシはSecretから証明書を読み直すので再起動も要りません。期限の監視と更新完了の確認は、利用者の責任のままです。
Compose、またはプログラム経由でない構成
CronJobはありません。Composeでプログラム経由なら、setup renew-certを自分で定期実行します。プログラム経由でなければ、既存のCAでサーバー証明書を署名し直して差し替えます。CAは作り直しません。
Composeで定期実行するときは、--renew-before=720hを付けます。有効期限まで30日以上あれば何もしないコマンドになるため、毎日回しても安全です。
docker compose run --rm setup renew-cert --output=dir:/data --renew-before=720hトンネルトークンのローテーションは、構成によって性質が変わります。プログラム経由のHelmなら、tunnel.tokenVersionを上げて--set setup.force=true付きでアップグレードします。Composeなら、docker-compose.yamlの--token-versionを上げてsetupを再実行します。値が前回と同じだと、ローテーションは拒否されます。手動フローでは、ConsoleのRotate tokenが現在のトークンを即座に無効にします。HelmならSecretを更新してロールアウトを再起動し、Composeなら各ホストのTUNNEL_TOKENを更新してcloudflaredを再起動します。その間にPodやcloudflaredが再起動すると、古いトークンでは再接続できません。止められない用途では、プログラム経由のアクセスに寄せる構成が公式でも勧められています。
まとめ
MCP tunnelsを使うかどうかの分かれ目は、ZDRやHIPAAの枠内で流したいデータがあるかどうかと、OAuthを各サーバーに自前で組めるかどうかです。MCPサーバー単位の権限設計はMCPセキュリティガイドに、トンネルの利用先となるManaged Agentsの全体設計はそちらの記事にあります。