Claude apps Gateway を Google Cloud+Cloudflare Zero Trust+Entra ID で試してみた

エヌデーデーの関口です。久しぶりの投稿になります。

多くの開発現場で Claude Code の導入が進む中、Anthropic からある新機能が発表されました。
本記事では、この新機能をいち早く利用できるようにチャレンジした検証内容をご紹介します。

Contents

Claude Code を組織で利用する際の課題

Claude Code を組織で利用する場合は次のようなパターンが考えられます。

  1. Claude Team / Enterprise プランを契約する
  2. Amazon Bedrock や Gemini Enterprise Agent Platform 経由で API を利用する

しかし、1 の場合は Team プランではサブスクリプション契約であるが故に開発者ごとの使用頻度のムラが問題になります。Enterprise プランだと最小契約シートが決まっているという課題があります。
そして 2 の場合は、個々の開発者へクラウドの認証情報を配布するため、少し扱いづらいという課題があります。さらに誰がどのモデルを使えるのか、どれくらい利用しているのか、設定をどこまで組織側で統制するのか。利用者が増えるほど、「各自で認証して使ってください」では運用しにくくなります。

これらの課題をいくつか解決できそうな機能が2026年7月に Anthropic から発表されました。
それが Claude apps Gateway というものです。

本検証では、この Claude apps Gateway をGoogle Cloud上へ構築し、社外へ持ち出したノート PC から安全に接続できるようにすることを目指します。

構築にあたっては、次の条件を満たす構成を目標に設定しました。

  • Claude apps Gateway は Google Cloud のプライベートネットワーク内で動かす
  • Gateway 自体にはパブリックIPを持たせない
  • 社外のノート PC から Cloudflare One Client 経由で接続する
  • 利用者認証には Microsoft Entra ID を使う
  • 開発者の端末へ Google Cloud の認証情報を配布しない
  • 利用可能なモデルや Claude Code の設定を Gateway 側で制御する

Cloudflare Zero Trust は Free プランを採用しています。検証に用いた Free プランは管理画面上 50 ユーザーまで利用でき、対象者を限定した PoC にはちょうどよい規模感でした。プラン条件は変更される可能性があるため、実際に利用する際は最新の情報をご確認ください。

検証時点

本文は2026年7月17日時点の公式ドキュメントを基にしています。Claude apps Gateway、Google Cloud、Cloudflare Zero Trustはいずれも更新が速いため、実施時には各公式ドキュメントも確認してください。

今回作る構成

全体像は次のとおりです。

ノート PC から見た Gateway の URL は、例えば次のようにします。

https://claude-gateway.private.example.co.jp

なお、このホスト名を一般公開するわけではありません。Cloudflare One Client を利用している端末だけが、Cloudflare の Private Hostname を通じて接続します。

この構成が完成した時の通信の流れは次のようになります。

Claude apps Gateway とは

Claude apps Gateway は、Claude Code とモデルプロバイダーの間に置くセルフホスト型のゲートウェイです。

Gateway が Google Cloud などの上流認証情報を持ち、開発者は Gateway から発行された短期トークンで Claude Code を利用します。開発者の端末へ Google Cloud のサービスアカウントキーや API キーを配る必要はありません。

組織側では、主に次のことができます。

  • OIDC 対応 IdP を使った組織 SSO
  • 利用者やグループ単位のモデル制御
  • Claude Code の Managed Settings 配布
  • 上流モデルの認証情報を Gateway へ集約
  • OTLP による利用メトリクスの転送
  • 日次・週次・月次の Spend Limits
  • 監査イベントの出力

公式ドキュメントでは、Claude Code v2.1.195 で Gateway サブコマンドとログインフローが導入されたと説明されています。Gateway サーバーには Linux 向けのネイティブバイナリが必要です。また、PostgreSQL 14 以降と HTTPS も必要です。

公式資料は、概要、設定リファレンス、Spend Limits、運用、Google Cloud 構築例に分かれています。最初は次の順で読むと全体像をつかみやすいです。

  1. Claude apps Gateway overview
  2. Deployment and operations
  3. Configuration reference
  4. Deploy on Google Cloud
  5. Spend limits

PostgreSQL は利用分析ダッシュボードではない

最初に少し勘違いしやすいのが、PostgreSQL の役割です。

PostgreSQL を用意すれば、利用状況のダッシュボードが自動的にできるわけではありません。

通常は、デバイスログインの一時状態やレート制限カウンターなどが保存されます。Spend Limits を有効にすると、利用者ごとの推定費用、上限設定、管理操作の監査履歴、最後に確認されたメールアドレスや IdP グループなども保存されます。Spend Limits を使わない場合は、主に kv テーブルだけが書き込まれます。

一方、モデル、トークン数、リクエスト数、レイテンシーなどの継続的な可視化は、Gateway から OTLP Collector などへメトリクスを転送して行います。

  • PostgreSQL: 認証状態、レート制限、Spend Limits、監査用データ
  • OTLP Collector / 監視基盤: モデル、トークン、リクエスト、レイテンシーなど

この二つは役割が別です。

Claude apps Gateway の重要な制約

接続先はプライベートアドレスでなければならない

今回の構成で一番重要なのが、この制約です。

Claude Code は Gateway へログインするとき、Gateway のホスト名を DNS で名前解決します。その結果に含まれるすべての IP アドレスが、次のいずれかでなければなりません。

  • RFC 1918 のプライベート IPv4
  • 100.64.0.0/10 の CGNAT アドレス
  • IPv6 ULA の fc00::/7
  • ローカル開発時の loopback

名前解決結果にパブリック IP が一つでも含まれると、Claude Code は接続を拒否します。単にパブリックなリバースプロキシの前段へ OIDC や WAF を置くだけでは、この条件を満たせません。

Cloudflare の Private Hostname では、端末へ 100.80.0.0/16 内の IP を返せます。この範囲は Claude Code が許可する 100.64.0.0/10 に含まれるため、今回の用途と相性がよい構成です。

HTTPS が必要

本番用途では、Gateway を HTTPS で公開します。

TLS 証明書の名前は、例えば次のように Claude Code が接続するホスト名と一致している必要があります。

  • 接続URL: https://claude-gateway.private.example.co.jp
  • 証明書のSAN: DNS:claude-gateway.private.example.co.jp

Gateway がプライベート IP であっても、自社が所有する公開ドメイン配下のホスト名なら、DNS 認証を使って公開 CA の証明書を発行できます。Gateway の A レコードをインターネットへ公開する必要はありません。

Gateway だけでは迂回利用を完全には防げない

Gateway は、Gateway を経由した利用について認証やモデル制御を行います。

一方で、開発者が別途取得した API キーや個人クラウドアカウントを使い、モデルプロバイダーへ直接アクセスすることまでは Gateway 単体では防げません。必要であれば、端末管理、IAM、ネットワークポリシーを組み合わせて直接接続経路を制限します。

現時点で利用できない機能もある

Claude apps Gateway は、Claude Code のすべての利用形態を置き換えるものではありません。主に次の制約が案内されています。(link1 / link2)

  • 無人 CI 向けの Service Token フローはない。ログインにはブラウザの Device Flow が必要
  • Gateway セッションでは Server-side WebSearch を利用できない
  • OTLP は HTTP 転送で、gRPC は未対応
  • 一つの Gateway で設定できる OIDC issuer は一つ
  • 管理画面はなく、基本設定は gateway.yaml と管理 API で行う

人が操作する Claude Code の組織利用には合いますが、CI ジョブまで同じ認証方式へ寄せる構成にはなっていません。

なぜこの構成にしたのか

Google Cloud を選んだ理由

Anthropic の公式ドキュメントには、Google Cloud 向けの比較的詳しい構築例があります。

公式例では、次の構成が紹介されています。

  • Cloud Run または GKE
  • Cloud SQL for PostgreSQL
  • Secret Manager
  • Google Cloud サービスアカウント
  • Internal Application Load Balancer

今回は、Gateway 自体が長時間動作する HTTP サービスであることと、PoC の運用負荷を抑えたいことから Cloud Run を選びました。Anthropic も Cloud Run を対応環境として案内しており、コールドスタートによる遅延を避けるため、最小インスタンス数を1以上にすることを推奨しています。

Google Cloud 上 の Claude へは、Cloud Run に付与したサービスアカウントの Application Default Credentials(ADC) で接続します。Google Cloud 上では Vertex AI の名称はなくなりましたが、Gateway の設定(gateway.yaml)では、現在も次のように provider: vertex を指定します。

upstreams:
  - provider: vertex
    region: <MODEL_REGION>
    project_id: <PROJECT_ID>
    auth: {}

Cloudflare Zero Trust を選んだ理由

社外へ持ち出したノート PC から Gateway へ接続するには、何らかのプライベート接続手段が必要です。

従来型のリモートアクセス VPN でも実現できますが、今回は Cloudflare Zero Trust を使いました。

理由は次のとおりです。

  • VPC 全体ではなく Gateway のホスト名と 443 番ポートだけを通せる
  • Cloudflare トンネルは Google Cloud 側からのアウトバウンド接続で構成できる
  • Gateway やロードバランサーへパブリック IP を持たせなくてよい
  • 社外でも Cloudflare One Client が DNS と通信経路を提供する
  • 既存の Microsoft Entra ID を IdP として利用できる
  • 小規模な PoC を無料枠で開始しやすい
  • Cloudflare が返す CGNAT アドレスが Claude Code のプライベートアドレス条件に合う

Cloudflare の Private Hostname を使う場合、Cloudflare One Client は トラフィックと DNS モード で動作させ、100.80.0.0/16 と対象ホスト名のDNS 問い合わせを Cloudflare へ送る必要があります。

SSO のための Entra ID へのアプリ登録は 2 つ

今回、Entra ID にはアプリを 2 つ登録します。

  • Cloudflare Zero Trust 用アプリ: Cloudflare One Client を利用してよい人かを確認
  • Claude apps Gateway 用アプリ: Claude Code を利用してよい人かを確認

Cloudflare One Client へ SSO したトークンが、そのまま Claude apps Gateway へ渡されるわけではありません。認証フローとしては別々です。

ただし、どちらも同じ Entra ID を使い、同じブラウザに Entra ID のセッションが残っていれば、Gateway へログインするときにパスワードや MFA を再入力せず進むことがあります。

構築前に決めておく値

作業途中でホスト名を変更すると、Entra IDのRedirect URI、TLS 証明書、Gateway 設定、Cloudflare Private Hostname、クライアント設定をまとめて変更することになります。

最初に、少なくとも次の値を決めます。

export PROJECT_ID="<Google Cloud プロジェクト ID>"

# Cloud Run、Cloud SQL、Internal LB を配置するリージョン
export INFRA_REGION="asia-northeast1"
export ZONE="${INFRA_REGION}-b"

# 利用モデルが提供されているリージョンを Model Garden で確認して設定(global / us / eu)
export MODEL_REGION="global"

export VPC_NETWORK="claude-gateway-vpc"
export SUBNET="claude-gateway-app"
export SUBNET_RANGE="10.50.0.0/24"
export PROXY_SUBNET="claude-gateway-proxy-only"
export PROXY_SUBNET_RANGE="10.50.10.0/23"
export CONNECTOR_SUBNET="claude-gateway-connector"
export CONNECTOR_SUBNET_RANGE="10.50.20.0/24"

export SERVICE_NAME="claude-gateway"
export DB_INSTANCE="claude-gateway-db"
export DB_NAME="claude_gateway"
export DB_USER="gateway"
export AR_REPO="claude-gateway"
export IMAGE_NAME="gateway"
export IMAGE_VERSION="poc-001"

export GATEWAY_HOST="claude-gateway.private.example.co.jp"
export GATEWAY_URL="https://${GATEWAY_HOST}"

export ENTRA_TENANT_ID="<Entra ID テナント ID>"
export ENTRA_GROUP_OBJECT_ID="<PoC 利用者グループのオブジェクト ID>"

Gateway のホスト名には、自社が所有する公開ドメイン配下の内部専用名を使うのがおすすめです。
例: claude-gateway.private.example.co.jp

なお、公開 DNS へ Gateway の A レコードを置く必要はありません。TLS 証明書の発行に必要な DNS 認証レコードだけを公開 DNS へ登録します。

手順1:Entra ID を準備する

さて、ここから実際の手順の説明です。

Entra ID の操作する権限があることが前提です。

1-1. PoC 利用者グループを作る

Entra ID へ、今回の利用者だけを含むセキュリティグループを作ります。

例: Claude-Gateway-PoC-Users

このグループの Object ID を控えておきます。

グループ表示名は後から変更できますが、オブジェクト ID は変わりません。Cloudflare や Gateway の設定ではオブジェクト ID を使います。

1-2. Cloudflare Zero Trust 用アプリを登録する

この段階ではまだ触れませんが、Cloudflare Zero Trust 管理画面では Microsoft Entra ID を IdP として追加すると、Callback URL が表示されます。

URL の形式は次のとおりです。

https://<Cloudflare のチーム名>.cloudflareaccess.com/cdn-cgi/access/callback

この情報とともに、Entra IDの「アプリの登録」から、Cloudflare 用のアプリを作成します。

例:

  • 表示名: Cloudflare Zero Trust
  • サポートされているアカウント: シングルテナントのみ
  • リダイレクト URI: Web を選択して https://{Cloudflare のチーム名}.cloudflareaccess.com/cdn-cgi/access/callback

登録後にアプリケーション(クライアント)IDディレクトリ(テナント)IDを控えておきます。

続いて「証明書とシークレット」に左メニューから遷移して「新しいクライアント シークレット」を作成します。
作成時に表示されたシークレットを控えておきます。(手順2 で使います)

次に「API のアクセス許可」に左メニューから遷移して「アクセス許可の追加」から次のものを追加します。

  • Mcrosoft Graph (以下は全て 委任されたアクセス許可)
  • email
  • offline_access
  • openid
  • profile
  • User.Read
  • Directory.Read.All
  • GroupMember.Read.All

利用するためには「管理者の同意」を処理することをお忘れ無く。

こうなっていればOKです。

次は Entra ID の「エンタープライズ アプリケーション」メニューへ遷移し、登録したアプリ名で検索してください。

アプリをクリックして表示されている「概要」画面で「1. ユーザーとグループの割り当て」をクリックし、1-1 で作成したグループ(Claude-Gateway-PoC-Users)を追加します。

「プロパティ」メニューに遷移して「割り当てが必要ですか?」のスイッチを「はい」にして保存してください。

これで、PoC 対象外の利用者が Cloudflare Zero Trust へログインするのを Entra ID 側でも防げます。

1-3. Claude apps Gateway 用アプリを登録する

Cloudflare 用アプリとは別に、Gateway 用アプリを作ります。

  • 表示名: Claude apps Gateway PoC
  • サポートされているアカウント: シングルテナントのみ
  • リダイレクト URI: Web を選択して https://claude-gateway.private.example.co.jp/oauth/callback

リダイレクト URI は、後でgateway.yaml へ設定する listen.public_url と完全に一致させます。
また、公開ドメイン配下の内部専用名にしておいてください。

こちらでも、1-2 と同じように次の事を実施してください。

  • クライアントシークレットを作成して控えておく(手順6 で使います)
  • エンタープライズ アプリケーションで、PoC 用のグループだけがアクセスできるように割り当てする

1-4. Gateway 用トークンへメールとグループを含める

Claude apps Gateway は、Entra IDの groups クレームではグループ名ではなくオブジェクト ID を受け取ります。Gateway の allowed_groups や Managed Settings の条件にもオブジェクト ID を指定します。

そこで、Gateway 用アプリの「トークン構成」で次を設定します。

  • オプション要求で emailを追加
  • グループ要求の追加で「アプリケーションに割り当てられたグループ」をチェックしてグループ ID を選択

手順2:Cloudflare Zero Trust の基本設定

Cloudflare Zero Trust の操作ができる権限があることを前提とします。

2-1. Entra ID を IdP として登録する

Cloudflare Zero Trust 管理画面で、認証用 IdP として Microsoft Entra ID を登録します。
ゼロトラスト > インテグレーション > ID プロバイダー と遷移して「ID プロバイダーを追加する」をクリックし、「Microsoft Entra ID」を選択して、次の値を入力します。

  • アプリケーション ID: 手順 1-2 で控えたアプリケーション(クライアント) ID
  • アプリケーション シークレット: 手順 1-2 で控えたクライアントシークレット
  • ディレクトリ ID: 手順 1-2 で控えたディレクトリ(テナント) ID
  • サポート グループ: オン

保存後、Cloudflare側のテストログインを実行し、PoCグループの利用者で認証できることを確認します。

2-2. デバイス登録ポリシーを設定する

Cloudflare One Clientへ端末を登録できる利用者を制限します。
ゼロトラスト > チームとリソース > デバイス と遷移し、管理 タブの デバイス登録権限管理 をクリックします。

デバイス登録権限画面が表示されるので 新しいポリシーを作成 をクリックします。

デバイス登録ポリシーには次のように入力します。

  • ポリシールール
    • セレクター: Azure Groups
    • 値: 手順 1-1 で控えた Claude-Gateway-PoC-Users の オブジェクト ID を直接入力
  • ポリシーの詳細
    • ポリシー名: 任意の名称
    • アクション: 許可

全てを入力したら ポリシーを保存 してください。

同画面の下部にある このアプリケーションで使用可能なIDプロバイダーを選択 に先ほど登録した Microsoft Entra ID だけを選びます。
さらに インスタンス認証を有効にすると、Cloudflare の IdP 選択画面を省略して直接 Entra ID へ遷移できます。

保存 をして手順を完了します。

2-3. Cloudflare One Client の デバイスプロファイル を設定する

Private Hostname では、DNS だけでなく通信自体も Cloudflare へ送る必要があります。
ゼロトラスト > チームとリソース > デバイス と遷移し、デバイスプロファイル タブの 一般プロファイルデフォルト の右端の三点リーダーから 設定 を選択します。
サービスモードが トラフィック と DNS モード であることを確認します。
これが選択されていれば、Claudflare Gateway が名前解決を行いつつ、100.80.x.x 宛ての通信をトンネルに流すということができるようになります。

2-4. Gateway プロキシを有効にする

Private Hostname を利用するには、Cloudflare Gateway のプロキシを有効にします。
ゼロトラスト > トラフィック ポリシー > トラフィックの設定 と遷移し、プロキシと検査の設定 で次が有効になっている事を確認します。

  • TCP
  • UDP

また、TLS 復号化による HTTPS リクエストの検査 がオフであることも確認します。

2-5. Cloudflare トンネルを作成する

ゼロトラスト > ネットワーク > コネクタ と遷移し、トンネルを作成 をクリックします。
Cloudflared を選択し、名前を付けて保存します。
例: claude-gateway-gcp-poc

次の画面ではインストールを指示されていますが、ここでは OS を Debian にしてインストールコマンドと動作させるコマンドをにコピーしておくだけにします。これは後で Google Cloud 側 の Compute Engine を作成した後で cloudflared をインストールする時に利用します。

その次の画面で表示される 公開アプリケーションルート タブには何も入力せず、ホスト名ルート タブで ホスト名 に Private Hostname でプライベート IP に解決される FQDN を入力をします。
例: claude-gateway.private.example.co.jp

最後に セットアップを完了 をクリックします。

2-6. スプリット トンネルを設定する

Cloudflare の Private Hostname が、Cloudflare One Client に 100.80.0.0/16 内の仮想IPを返すようにするために、スプリット トンネルを設定します。

Cloudflare 管理画面から ゼロトラスト > チームとリソース > デバイス と遷移し、デバイスプロファイル タブの 一般プロファイルデフォルト の右端の三点リーダーから 設定 を選択します。

画面下部にある スプリット トンネルIP とドメインを含める に変更します。警告が出ますが続行します。
スプリット トンネルの管理(含む) という画面に遷移しますので、次の情報を追加します。

  • セレクター: IP Address / 値: 100.80.0.0/16
  • セレクター: Domain / 値 claude-gateway-private.example.com

追加し終えたら、Return to profile settings を押して元の画面に戻り、プロファイルを保存します。

2-7. ファイアウォール ポリシーを作る

Cloudflare 管理画面から ゼロトラスト > トラフィック ポリシー > ファイアウォール ポリシー と遷移し、ネットワーク タブで ポリシーを追加するをクリックします。

トラフィックタイプを ネットワーク にして次の値を入れておきます。

  • かつトラフィックが一致する…
    • SNI Domain is claude-gateway.private.example.co.jp
    • Protocol is TCP
    • Destination Port is 443
  • かつ ID が一致する…
    • User Group IDs in
  • アクションと設定: 許可
  • ポリシー名: Allow Claude Gateway PoC users

もう一つデフォルトで拒否するポリシーも作っておきます。
こちらもトラフィックタイプを ネットワーク にして次の値を入れておきます。

  • かつトラフィックが一致する…
    • SNI Domain is claude-gateway.private.example.co.jp
    • Protocol is TCP
    • Destination Port is 443
  • アクションと設定: ブロック
  • ポリシー名: Block other access to Claude Gateway

手順3:Google Cloud の基盤を作る

ここから Google Cloud 側の構築です。


事前にプロジェクトを作成しているものとします。
以下のコマンドで登場する変数は前述の 「構築前に決めておく値」 で設定済みであるものとします。

3-1. APIを有効にする

次の API を有効にします。

  • Agent Platform API (aiplatform.googleapis.com)
  • Artifact Registry API (artifactregistry.googleapis.com)
  • Cloud SQL Admin API (sqladmin.googleapis.com)
  • Secret Manager API (secretmanager.googleapis.com)
  • IAM Service Account Credentials API (iamcredentials.googleapis.com)
  • Identity and Access Management (IAM) API (iam.googleapis.com)
  • Compute Engine API (compute.googleapis.com)
  • Cloud DNS API (dns.googleapis.com)
  • Service Networking API (servicenetworking.googleapis.com)
  • Cloud Run Admin API (run.googleapis.com)
  • Certificate Manager API (certificatemanager.googleapis.com)
gcloud config set project "$PROJECT_ID"

gcloud services enable \
  aiplatform.googleapis.com \
  artifactregistry.googleapis.com \
  sqladmin.googleapis.com \
  secretmanager.googleapis.com \
  iamcredentials.googleapis.com \
  iam.googleapis.com \
  compute.googleapis.com \
  dns.googleapis.com \
  servicenetworking.googleapis.com \
  run.googleapis.com \
  certificatemanager.googleapis.com

3-2. VPC とサブネットを作る

VPC に 3 つのサブネットを作成します。

各サブネットの役割は次のとおりです。

サブネット用途
SUBNETCloud Run の Direct VPC egress
CONNECTOR_SUBNETcloudflared を稼働させる VM 用
PROXY_SUBNETリージョナル内部アプリケーション ロードバランサーの Proxy-only サブネット
gcloud compute networks create "$VPC_NETWORK" \
  --subnet-mode=custom

# Claude apps Gateway の Cloud Run 向け VPC
gcloud compute networks subnets create "$SUBNET" \
  --network="$VPC_NETWORK" \
  --region="$INFRA_REGION" \
  --range="$SUBNET_RANGE" \
  --enable-private-ip-google-access

# Cloudflare Zero Trust の トンネルサービス GCE 用の VPC
gcloud compute networks subnets create "$CONNECTOR_SUBNET" \
  --network="$VPC_NETWORK" \
  --region="$INFRA_REGION" \
  --range="$CONNECTOR_SUBNET_RANGE" \
  --enable-private-ip-google-access

# 内部アプリケーション ロードバランサー用の VPC
gcloud compute networks subnets create "$PROXY_SUBNET" \
  --network="$VPC_NETWORK" \
  --region="$INFRA_REGION" \
  --range="$PROXY_SUBNET_RANGE" \
  --purpose=REGIONAL_MANAGED_PROXY \
  --role=ACTIVE

3-3. Cloud SQL 用の Private Services Access を作る

Cloud SQL に内部 IP でアクセスする為に作成します。

2つ目のコマンドは一つ目のコマンドの直後に実施しても失敗することがありますので、その場合は数分待って実行してください。

gcloud compute addresses create "google-managed-services-${VPC}" \
  --global \
  --purpose=VPC_PEERING \
  --prefix-length=16 \
  --network="$VPC_NETWORK"

gcloud services vpc-peerings connect \
  --service=servicenetworking.googleapis.com \
  --ranges="google-managed-services-${VPC}" \
  --network="$VPC_NETWORK"

3-4. Gateway 用 サービスアカウントを作る

サービスアカウントを作成して、roles/aiplatform.user のロールを与えます。Google Cloud 上の Claude へ接続するときは、Gateway がこのサービスアカウントの ADC(Application Default Credentials) を利用します。

gcloud iam service-accounts create claude-gateway \
  --display-name="Claude apps Gateway"

export GATEWAY_SA="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="serviceAccount:${GATEWAY_SA}" \
  --role="roles/aiplatform.user" \
  --condition=None

3-5. Google Cloud 上で Claude モデルを有効にする

Google Cloud コンソール中央上部の検索窓に Model Garden と入力して、Model Garden に遷移し、今回利用する Anthropic Claude モデルを対象プロジェクトで有効にします。

PoC 向けに Sonnet 5 を有効にしました。

手順4:Cloud SQL for PostgreSQL を作る

PoC 用に Private IP だけを持つ Cloud SQL for PostgreSQL を作ります。

本検証では 30 日間は無料で使用可能な Enterprise Plus エディションで作成しています。

export ROOT_PASSWORD="$(openssl rand -hex 24)"

gcloud sql instances create "$DB_INSTANCE" \
  --database-version=POSTGRES_16 \
  --edition=enterprise-plus \
  --tier=db-perf-optimized-N-2 \
  --region="$INFRA_REGION" \
  --network="projects/${PROJECT_ID}/global/networks/${VPC}" \
  --no-assign-ip \
  --availability-type=zonal \
  --root-password={$ROOT_PASSWORD}

作成が完了するまでしばらく(5分?)時間が掛かりますので、コーヒーでも飲みながら待ちましょう。

☕☕☕

インスタンスの作成が完了したら、データベースとユーザーを作ります。

なお、生成されたパスワードを含めた環境変数 GATEWAY_POSTGRES_URL は後ほど Secret Manager に登録します。
心配な場合は念のため値を控えておきましょう。

gcloud sql databases create "$DB_NAME" \
  --instance="$DB_INSTANCE"

export DB_PASSWORD="$(openssl rand -hex 24)"

gcloud sql users create "$DB_USER" \
  --instance="$DB_INSTANCE" \
  --password="$DB_PASSWORD"

export DB_PRIVATE_IP="$(gcloud sql instances describe "$DB_INSTANCE" \
  --format='value(ipAddresses[0].ipAddress)')"

export GATEWAY_POSTGRES_URL="postgres://${DB_USER}:${DB_PASSWORD}@${DB_PRIVATE_IP}:5432/${DB_NAME}?sslmode=require"

Gateway は起動時にスキーママイグレーションを実行します。PoC では DB ユーザーにテーブル作成権限を持たせておきます。
Google Cloud コンソールの検索窓に Cloud SQL を入力して、検索された Cloud SQL に遷移します。
作成済みのインスタンス(claude-gateway-db)を選択して、左のメニューから Cloud SQL Studio を選びます。
データベースに作成済みのデータベース(claude_gateway)を選び、ルートユーザーの postgres とパスワードには $ROOT_PASSWORD の値で認証します。

無題のクエリ タブに次の SQL を入力して実行します。

GRANT ALL ON SCHEMA public TO "gateway";

手順5:gateway.yaml を作る

gateway.yaml は Gateway を動作させるための設定が記載されたファイルです。
本検証のように Entra ID を使う構成例は次のようになります。なお、次の値は適宜置き換えてください。

  • <ENTRA_TENANT_ID>
  • <GATEWAY_OIDC_CLIENT_ID>
  • <ENTRA_GROUP_OBJECT_ID> 2箇所
  • <PROJECT_ID>
listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.private.example.co.jp
  trusted_proxies:
    - 169.254.0.0/16
    - 10.50.10.0/23

oidc:
  issuer: https://login.microsoftonline.com/<ENTRA_TENANT_ID>/v2.0
  client_id: <GATEWAY_OIDC_CLIENT_ID>
  client_secret: ${OIDC_CLIENT_SECRET}

  # Entra IDではグループ名ではなくObject IDが入る
  allowed_groups:
    - <ENTRA_GROUP_OBJECT_ID>

  # テナント構成によってemailではなくpreferred_usernameになる場合に備える
  email_claim:
    - email
    - preferred_username

  scopes:
    - openid
    - profile
    - email
    - offline_access

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}
  ttl_hours: 1

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}

upstreams:
  - name: google-cloud-primary
    provider: vertex
    region: global
    project_id: <PROJECT_ID>
    auth: {}

auto_include_builtin_models: false

# models は Anthropic が想定しているモデルの ID を正しく登録しておく必要がある。
# 例)Sonnet 5: claude-sonnet-5, Opus 4.8: claude-opus-4-8
models:
  - id: claude-sonnet-5
    label: Claude Sonnet 5
    upstream_model:
      vertex: claude-sonnet-5

managed:
  policies:
    - match:
        groups:
          - <ENTRA_GROUP_OBJECT_ID>
      cli:
        availableModels:
          - claude-sonnet-5
        enforceAvailableModels: true
        permissions:
          deny:
            - WebFetch
            - WebSearch

このとき trusted_proxies には、Cloud Run 前段と内部アプリケーション ロードバランサーの Proxy-only サブネットの CIDR を指定します。これを設定しないと、Gateway から見るすべてのアクセス元がロードバランサーの IP になり、サインイン時の IP 単位レート制限や監査ログが正しく扱えません。Anthropic の Google Cloud 向け公式例でも 169.254.0.0/16 と Proxy-only サブネットの両方を指定しています。

手順6:Secret Manager へ情報登録する

次の 4 つの情報を Secret Manager に登録します。

  • Gateway が利用する JWT のシークレット(この手順で生成)
  • Entra ID で確認した、Gateway 用の OIDC で利用するクライアントシークレット
  • 手順4 で作成したデータベースの接続文字列
  • 手順5 で作成した gateway.yaml ファイル
export GATEWAY_JWT_SECRET="$(openssl rand -base64 32)"

printf '%s' "$GATEWAY_JWT_SECRET" | \
  gcloud secrets create gateway-jwt-secret --data-file=-

export OIDC_CLIENT_SECRET="<Gateway用Entra ID Client Secret>"

printf '%s' "$OIDC_CLIENT_SECRET" | \
  gcloud secrets create gateway-oidc-client-secret --data-file=-

printf '%s' "$GATEWAY_POSTGRES_URL" | \
  gcloud secrets create gateway-postgres-url --data-file=-

gcloud secrets create gateway-config --data-file=gateway.yaml

登録したシークレットを参照できるように、Gateway 用サービスアカウントにシークレット アクセサー ロールを付与します。

for SECRET in \
  gateway-jwt-secret \
  gateway-oidc-client-secret \
  gateway-postgres-url \
  gateway-config
do
  gcloud secrets add-iam-policy-binding "$SECRET" \
    --member="serviceAccount:${GATEWAY_SA}" \
    --role="roles/secretmanager.secretAccessor"
done

手順7:Gateway コンテナを作る

Anthropic の公式リポジトリには、Google Cloud 向けのリファレンス資材があります。これを利用して Cloud Run コンテナを作成します。

まず、イメージファイルを登録するために Artifact Registry を作ります。

gcloud artifacts repositories create "$AR_REPO" \
  --repository-format=docker \
  --location="$INFRA_REGION"

export IMAGE_URI="${INFRA_REGION}-docker.pkg.dev/${PROJECT_ID}/${AR_REPO}/${IMAGE_NAME}:${IMAGE_VERSION}"

# docker push できる様に認証しておきます。
gcloud auth configure-docker "${INFRA_REGION}-docker.pkg.dev"

ここからイメージのビルドです。
まず、Anthropic の公式リポジトリからクローンして、Google Cloud 向けの Dockerfile があるディレクトリに移動します。

git clone https://github.com/anthropics/claude-code.git
cd claude-code/examples/gateway/gcp

ビルドの前にローカルに Claude の実行ファイルをダウンロードします。

# 最新バージョンの確認
export VERSION="$(
  curl -fsSL \
    https://downloads.claude.ai/claude-code-releases/latest |
    tr -d '[:space:]'
)"
echo "$VERSION"

# 最新バージョンをダウンロード
curl -fL \
  "https://downloads.claude.ai/claude-code-releases/${VERSION}/linux-x64/claude" \
  -o ./claude

docker build して、Artifact Registry に push します。

docker build \
  --platform=linux/amd64 \
  -t "$IMAGE_URI" \
  .

docker push "$IMAGE_URI"

手順8:Cloud Run へデプロイする

gcloud run deploy "$SERVICE_NAME" \
  --image="$IMAGE_URI" \
  --region="$INFRA_REGION" \
  --service-account="$GATEWAY_SA" \
  --min-instances=1 \
  --max-instances=3 \
  --timeout=3600 \
  --port=8080 \
  --ingress=internal-and-cloud-load-balancing \
  --network="$VPC_NETWORK" \
  --subnet="$SUBNET" \
  --vpc-egress=private-ranges-only \
  --set-secrets="/etc/claude/gateway.yaml=gateway-config:latest" \
  --set-secrets="GATEWAY_JWT_SECRET=gateway-jwt-secret:latest" \
  --set-secrets="OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest" \
  --set-secrets="GATEWAY_POSTGRES_URL=gateway-postgres-url:latest" \
  --no-invoker-iam-check

--no-invoker-iam-check のオプションをつけている理由について説明します。
Claude Code は Google Cloud の ID トークンを持っていません。Gateway 自身の OIDC 認証で利用者を確認するため、Cloud Run の Invoker IAM チェックを有効にしたままだと、Gateway へ到達する前に Cloud Run 側で 403 になってしまうためです。
つまり、Gateway が実行可能かどうかは、あくまで OIDC 認証によって確認するということです。

一方、--ingress=internal-and-cloud-load-balancing というオプションを付けて、ネットワークへの到達は内部ロードバランサーと Cloudflare にまかせることになっています。

起動ログを確認する

gcloud run services logs read "$SERVICE_NAME" \
  --region="$INFRA_REGION" \
  --limit=100

次のようなログが出力されていれば、正常に起動しています。
Cloud SQL にもテーブルが作成されているはずです。

2026-07-21 08:21:50 claude gateway: could not connect to Postgres: Connection timeout after 5s. Check store.postgres_url in /etc/claude/gateway.yaml
2026-07-21 08:28:45
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.751Z info waiting for migration lock (another replica may be migrating; check pg_locks for key 6775156 if this persists)
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.798Z info migration 1 applied
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.811Z info migration 2 applied
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.822Z info migration 3 applied
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.832Z info migration 4 applied
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.843Z info migration 5 applied
2026-07-21 08:28:45 [gateway] 2026-07-21T08:28:45.849Z info migration 6 applied
2026-07-21 08:28:46 ┌─────────────────────────────────────┐
2026-07-21 08:28:46 │  Claude Code Gateway                │
2026-07-21 08:28:46 └─────────────────────────────────────┘
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info claude gateway listening on http://0.0.0.0:8080
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info public_url https://claude-gateway.private.example.co.jp
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info oidc issuer https://login.microsoftonline.com/.........../v2.0
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info email domains (unrestricted)
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info allowed groups .........
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info upstreams 1: vertex(vertex)
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info telemetry relay: not configured
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z info managed settings: configured
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.026Z warn no managed policy carries a desktop: block — Claude Desktop clients will be rejected by /user/bootstrap until a policy opts in
2026-07-21 08:28:46 [gateway] 2026-07-21T08:28:46.028Z warn pod can reach cloud metadata endpoint (169.254.169.254); apply egress NetworkPolicy (see src/gateway/docs/docker/network-policy.yaml)

手順9:内部アプリケーション ロードバランサーと TLS を作る

9-1. Certificate Manager で DNS 認証を作る

Gateway 自体はインターネットへ公開しませんが、DNS 認証を使って公開 CA の証明書を発行します。
(コンソール画面では作成できません)

gcloud certificate-manager dns-authorizations create claude-gateway-dns-auth \
  --domain="$GATEWAY_HOST" \
  --location="$INFRA_REGION"

gcloud certificate-manager dns-authorizations describe claude-gateway-dns-auth \
  --location="$INFRA_REGION"

このコマンドで、次のような出力になります。

createTime: '2026-07-xxTHH:MM:DD.000000000Z'
dnsResourceRecord:
  data: <自動生成された値>.0.asia-northeast1.authorize.certificatemanager.goog.
  name: _acme-challenge_<自動生成された値>.claude-gateway.private.example.co.jp.
  type: CNAME
domain: claude-gateway.private.example.co.jp
name: projects/PROJECT_ID/locations/asia-northeast1/dnsAuthorizations/claude-gateway-dns-auth
type: PER_PROJECT_RECORD
updateTime: '2026-07-xxTHH:MM:DD.000000000Z'

example.co.jpの権威 DNS に CNAME を登録します。

  • dnsResourceRecord.name の値: レコード名
  • dnsResourceRecord.data の値: CNAME の値

なお、Cloudflare One Client によって内部 IP アドレスが解決できるため、権威 DNS には Gateway の公開 A レコードは作りません。

続いて TLS 証明書を作成します。

gcloud certificate-manager certificates create claude-gateway-cert \
  --domains="$GATEWAY_HOST" \
  --dns-authorizations=claude-gateway-dns-auth \
  --location="$INFRA_REGION"

状態が 有効 になるまで待ちます。

9-2. 内部アプリケーション ロードバランサーを作る

リージョナル内部アプリケーション ロードバランサーは、リージョン証明書と DNS 認証を同じリージョンにします。
Google Cloud コンソールで ロード バランシング を検索して作成します。

最初の画面では次の入力を行います。

  • ロードバランサのタイプ: アプリケーション ロードバランサ
  • インターネット接続または内部: 内部
  • クロスリージョンまたはシングル リージョンのデプロイ: リージョン ワークロードに最適

次の画面での入力は以下の通りにして、作成します。

  • ロードバランサの名前: claude-gateway-ilb
  • リージョン: asia-northeast1
  • ネットワーク: claude-gateway-vpc
  • バックエンドの構成
    • 名前: claude-gateway-backend
    • バックエンドタイプ: サーバレス ネットワーク エンドポイント グループ
    • 新しいバックエンドを作成します
      • 名前: claude-gateway-backend-neg
      • リージョン: asia-northeast1
      • サーバレス ネットワーク エンドポイント グループの種類: Cloud Run
      • サービスを選択: claude-gateway
  • ルーティング ルール: 単純なホストとパスのルール
  • フロントエンドの構成
    • プロトコル: HTTPS
    • サブネットワーク: claude-gateway-connector
    • ポート: 443
    • IP アドレスを作成します
      • 名前: claude-gateway-ilb-ip
      • 静的 IP アドレス: ユーザー指定
      • カスタム IP アドレス: 10.50.20.10
      • 目的: 共有しない
    • グローバルアクセス: 無効
    • Choose certificate repository: Certificates
    • 証明書を追加: claude-gateway-cert を選択

手順10:cloudflared を Compute Engine に置く

10-1. Cloud NATを作る

外部 IP を持たない Compute Engine(GCE) から Cloudflare へアウトバウンド接続するため、先に Cloud NAT を作ります。

gcloud compute routers create claude-gateway-nat-router \
  --network="$VPC_NETWORK" \
  --region="$INFRA_REGION"

gcloud compute routers nats create claude-gateway-nat \
  --router=claude-gateway-nat-router \
  --region="$INFRA_REGION" \
  --nat-all-subnet-ip-ranges \
  --auto-allocate-nat-external-ips

10-2. cloudflared 用 GCE を作る

次に外部 IP を持たない GCE を作成します。

gcloud iam service-accounts create cloudflared-connector \
  --display-name="Cloudflare Tunnel Connector"

export CONNECTOR_SA="cloudflared-connector@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud compute instances create claude-gateway-cloudflared-vm \
  --zone="$ZONE" \
  --machine-type=e2-micro \
  --subnet="$CONNECTOR_SUBNET" \
  --no-address \
  --service-account="$CONNECTOR_SA" \
  --scopes=cloud-platform \
  --image-family=debian-13 \
  --image-project=debian-cloud \
  --scopes="cloud-platform" \
  --tags="claude-gateway-connector"

IAP で SSH 接続するため、IAP を許可するファイアウォール ルールを作成します。

gcloud compute firewall-rules create allow-iap-ssh-to-connector \
  --project="$PROJECT_ID" \
  --network="$VPC_NETWORK" \
  --direction=INGRESS \
  --action=ALLOW \
  --rules=tcp:22 \
  --source-ranges="35.235.240.0/20" \
  --target-tags="claude-gateway-connector"

10-3. cloudflared をインストールする

Google Cloud コンソールの VM インスタンス画面で、作成した VM に SSH ログインします。
手順2-5 でコピーした Debian 用のインストールコマンドを実行します。

# Add cloudflare gpg key
sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-public-v2.gpg | \
sudo tee /usr/share/keyrings/cloudflare-public-v2.gpg >/dev/null

# Add this repo to your apt repositories
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-public-v2.gpg] https://pkg.cloudflare.com/cloudflared any main' | \
sudo tee /etc/apt/sources.list.d/cloudflared.list

# install cloudflared
sudo apt-get update && sudo apt-get install cloudflared

cloudflared をサービス起動します。

sudo cloudflared service install <トンネル用トークン>

#動作確認
sudo systemctl status cloudflared
sudo journalctl -u cloudflared -n 100

Cloudflare 管理画面でコネクターが接続済みなることも確認します。

10-4. cloudflared から Gateway 名を内部 IP へ解決する

Cloudflare の Private Hostname では、cloudflared 側が対象ホスト名を実際の接続先へ名前解決できる必要があります。

Cloud DNS のプライベート ゾーンを作ります。

gcloud dns managed-zones create claude-gateway-private-zone \
  --dns-name="private.example.co.jp." \
  --description="Claude Gateway private DNS" \
  --visibility=private \
  --networks="$VPC_NETWORK"

# 手順9-2 で作成したカスタム IP アドレスを --rrdatas に指定します。
gcloud dns record-sets create "${GATEWAY_HOST}." \
  --zone=claude-gateway-private-zone \
  --type=A \
  --ttl=300 \
  --rrdatas="10.50.20.10"

cloudflared 用 VM で確認します。

# $GATEWAY_HOST は VM 上で改めて設定しておいてください。
getent hosts "$GATEWAY_HOST"
curl -v "https://${GATEWAY_HOST}/healthz"

手順11:Cloudflare One Client を登録する

ノート PC へ Cloudflare One Client をインストールします。

Cloudflare Zero Trust のチーム名を入力し、Microsoft Entra ID でログインします。

PoC グループに所属していれば端末登録が完了します。対象外の利用者は、Entra ID のアプリ割り当てまたは Cloudflare のデバイス登録ポリシーで拒否されます。

Cloudflare One Client の 接続 を押して Cloudflare に接続します。

手順12:社外ネットワークから確認する

会社の LAN や既存 VPN から切断し、自宅回線やテザリングなどで確認します。

12-1. DNS を確認する

nslookup claude-gateway.private.example.co.jp

期待する結果は例えば以下のような 100.80.0.0/16 内のアドレスです。

Name:    claude-gateway.private.example.co.jp
Address: 100.80.200.48

この 100.80.x.x は、Google Cloud 上の内部アプリケーション ロードバランサーの IP ではありません。
Cloudflare が端末側へ返す IP です。Cloudflare One Client が通信を捕捉し、Cloudflare トンネルを通じて実際の Gateway へ運びます。

12-2. HTTPS を確認する

curl -v "https://${GATEWAY_HOST}/healthz"

次のような内容が表示されていれば成功です。

< HTTP/1.1 200 OK
< x-content-type-options: nosniff
< x-frame-options: DENY
< referrer-policy: no-referrer
< cross-origin-opener-policy: same-origin
< x-request-id: 9956438a-7558-448a-82bf-9b9ba23a08b8
< content-type: text/plain;charset=utf-8
< x-cloud-trace-context: 144df2186e443715201003f8f5c9ec70;o=1
< date: Wed, 22 Jul 2026 08:13:52 GMT
< server: Google Frontend
< content-length: 2
< via: 1.1 google
<
ok* Connection #0 to host claude-gateway.private.example.co.jp:443 left intact

OAuth ディスカバリも確認します。

curl -sS "${GATEWAY_URL}/.well-known/oauth-authorization-server" | jq

次のような結果が得られるはずです。

{
  "issuer": "https://claude-gateway.private.example.co.jp",
  "device_authorization_endpoint": "https://claude-gateway.private.example.co.jp/oauth/device_authorization",
  "token_endpoint": "https://claude-gateway.private.example.co.jp/oauth/token",
  "grant_types_supported": [
    "urn:ietf:params:oauth:grant-type:device_code",
    "refresh_token"
  ],
  "response_types_supported": [],
  "token_endpoint_auth_methods_supported": [
    "none"
  ],
  "scopes_supported": [
    "openid",
    "profile",
    "email"
  ],
  "gateway_protocol_version": 1
}

デバイス認証を直接確認する場合は、次を実行します。

curl -sS -X POST "${GATEWAY_URL}/oauth/device_authorization" | jq

次のような情報が返れば OK です。

{
  "device_code": "...",
  "user_code": "...",
  "verification_uri": "https://claude-gateway.private.example.co.jp/device",
  "verification_uri_complete": "https://claude-gateway.private.example.co.jp/device?user_code=...",
  "expires_in": 600,
  "interval": 5
}

手順13:Claude Codeからログインする

13-1. managed-settings.json へ Gateway の URL を配布する

Claude Code の managed-settings.json に、Gateway ログインを強制する設定を追加します。
managed-settings.json を設置する場所は、Anthropic の公式ドキュメントで確認してください。

{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.private.example.co.jp"
}

なお、この設定は通常では各ユーザーが変更することを想定しておらず、MDM や AD の GPO で配布することが想定されています。

13-2. /loginを実行する

claude

Claude Code内で次を実行します。

/login

まず Gateway の URL が表示されます。

次に信頼できるかどうかの確認が行われます。

Yes を選択すると、確認コードが表示されると共に、ブラウザにも確認コードが表示されます。
このときのブラウザの URL は claude-gateway.private.example.com になっているはずです。

This matches my devie -- continue をクリックしてログインが完了します。

すでに Cloudflare One Client へのログインで Entra ID セッションが残っていれば、Gateway 用の OIDC フローは実際は流れていますが、パスワードや MFA の再入力は求められないはずです。


手順14:推論を確認する

早速簡単な依頼を行います。

現在利用しているモデル名だけを答えてください。

使えましたね!

Claude Code の上部に Cloud gateway と表示されている点にも注目です。

今回はセットアップまで

結構複雑な手順になってしまいました。
今回はこのセットアップまでとして、次の記事で Spend Limits などを試してみたいと思います。

お付き合いいただきありがとうございました!