本文へ移動
Claude Tips

Claude apps gateway

社内の IdP でサインインさせ、Bedrock・Vertex AI・Foundry・Anthropic API へ中継する自前のゲートウェイ(Claude apps gateway)の導入・gateway.yaml の全設定項目・支出上限・運用をまとめます。

Claude apps gateway は、開発者の Claude Code とモデルの提供元のあいだに置く、セルフホストのサービスです。開発者は社内の IdP(ID プロバイダ)でサインインするだけで、上流の資格情報・グループ別のモデルと managed settings・テレメトリの中継はゲートウェイが受け持ちます。claude のバイナリに入っていて、claude gateway --config gateway.yaml で起動します。

要点#

  • 対象:組織の管理者。データ所在地などの理由で、推論を自社のクラウド経由にしたい組織向け。その必要がなく、SCIM やクラウド(Web)・モバイルの Claude Code が欲しいなら Claude Enterprise のほうが向く
  • 資格情報:上流の API キーやクラウドの資格情報はゲートウェイの内側だけに置く。開発者は SSO で短命のベアラートークンを受け取る。退職の処理は IdP で済む(無効にしたユーザーは、既定でセッションの寿命の1時間以内に切れる)
  • アクセス制御:IdP のグループに、モデルの許可リストと managed settings を割り当てる。モデルの許可はサーバー側でも強制する
  • 設定の配布:サインイン済みのクライアントへ、ゲートウェイが managed settings を配る(claude.ai の管理画面のサーバー管理設定の代わり)
  • テレメトリ:OTLP のメトリクスを、設定した宛先へ中継する。ログとトレースは宛先ごとに選ぶ
  • 上流の振り分け:クライアントは Anthropic Messages API で話す。ゲートウェイが上流(Bedrock・Claude Platform on AWS・Vertex AI・Foundry・Anthropic API)に合わせて変換し、順にフェイルオーバーする
  • 必要なもの:Claude Code v2.1.195 以降、OIDC の IdP、PostgreSQL 14 以降、HTTPS、プライベートネットワークのアドレス、Linux

ゲートウェイの考え方と他製品との選び方はネットワークと LLM ゲートウェイ、上流のクラウド側の設定はクラウドプロバイダにあります。

補足

上流に Anthropic API を選ばない限り、ゲートウェイは Anthropic のインフラへ何も送りません。テレメトリ・監査ログ・managed settings・開発者の IdP の ID は、設定した宛先だけへ行きます。Anthropic API を上流にすると、その推論の経路にだけ既存のデータ取り扱いの契約が適用されます。

注意

Claude Code は、プライベートなアドレスのゲートウェイにしか接続しません。信頼したゲートウェイは、開発者のマシンでコマンドを実行する設定を配れるためです。内部のロードバランサーか VPN の内側に置き、プライベート IP にだけ解決される名前を付けます。

他の実装との関係#

  • すでに運用している LLM ゲートウェイがあれば、そのまま使える(ネットワークと LLM ゲートウェイの「ほかの LLM ゲートウェイ」)
  • 動いているゲートウェイは、Claude Code に見せるエンドポイント(SSO サインイン・推論・managed settings の配布・モデルの検出・テレメトリ)の仕様を GET /protocol で返す。例:curl https://claude-gateway.internal.example.com/protocol
  • プロトコルの破壊的な変更は事前に告知される。ただし後方互換がずっと続く保証はない

クイックスタート#

IdP への OAuth クライアントの登録から、Docker Compose で Postgres と並べて起動し、サインインを通すまでの最小の流れです。例の上流は Bedrock ですが、ほかの上流でも upstreams を替えるだけです。

前提#

必要なもの 内容
Claude Code v2.1.195 以降 サーバーと各開発者のマシンの両方。Claude Platform on AWS を上流にするなら、サーバーは v2.1.198 以降
OIDC の IdP 標準の OIDC discovery と authorization-code フローに対応するもの(Okta・Microsoft Entra ID・Google Workspace・Keycloak・Dex・PingFederate など)。SAML・LDAP は非対応
PostgreSQL 14 以降 デバイスのサインインとレート制限のカウンターを置く。支出上限を使うと、支出・監査・ID の表も入る
モデルの上流 Bedrock・Claude Platform on AWS・Google Cloud の資格情報、Foundry のリソース、Anthropic の API キーのどれか。複数を並べてフェイルオーバーできる
HTTPS 開発者のノート PC と、サインインに使うブラウザから https:// で届くこと
プライベートネットワークのアドレス ゲートウェイのホスト名か IP が、私設のアドレスにだけ解決されること
Linux の実行環境 サーバーはネイティブの Linux バイナリだけ。macOS はローカル開発用、Windows のサーバーは非対応
  • PostgreSQL:サインインの受け渡しは、ブラウザのコールバックが書き、ポーリングする CLI が読む。支出上限を使うならバックアップが要る。TLS は ?sslmode=require を推奨
  • HTTPS:listen.tls で証明書を渡すか、TLS を終端するイングレスの後ろに置く。どちらの場合も listen.public_url に外から見えるオリジンを書く。/login が平文の http:// を受けるのは、ゲートウェイのホストがループバックのときだけ
  • 私設のアドレス:/login が認めるのは RFC 1918・リンクローカル・CGNAT 100.64.0.0/10・IPv6 ULA fc00::/7・ループバック。自前でホストするゲートウェイでは、宣言したブロックの外の公開アドレスは拒否される
  • 開発者のマシンが HTTPS を社内プロキシ経由にしているなら、プロキシのホストも私設に解決される必要がある。無理ならゲートウェイのホストを NO_PROXY に足す
  • 社内網を自社所有の公開 IPv4 で組んでいるなら、gatewayInternalNetworks で宣言する(下の「公開アドレス空間の社内ネットワークを許可する」)

手順#

  1. IdP に OAuth クライアントを登録する。先にゲートウェイのホスト名を決め、リダイレクト URI を https://claude-gateway.<自社ドメイン>/oauth/callback(listen.public_url と同じホスト)にする。client_id と client_secret を控える
  2. PostgreSQL を用意する(マネージドの最小の階層でよい)。起動時にスキーマを移行するので、ロールにテーブルの作成と変更の権限を与える
  3. 下の最小の gateway.yaml を書く。秘密は ${ENV_VAR} で展開させれば、ファイルはバージョン管理に置ける。public_url は、ネットワーク内でプライベート IP に解決されるホスト名にする(/login が公開アドレスを拒否するため)
  4. claude のバイナリを入れたコンテナイメージを作り、Postgres と並べて起動する(glibc ベース、書き込めるディレクトリを CLAUDE_CONFIG_DIR で指す、コマンドは claude gateway --config /etc/claude/gateway.yaml)
  5. 下の「サインインの面の確認」の3つを順に通す
  6. 開発者のマシンの managed settings に forceLoginMethod: "gateway" と forceLoginGatewayUrl を書き、/login の「Cloud gateway」の画面で Enter を押してブラウザでサインインする
yaml
listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.internal.example.com

oidc:
  issuer: https://login.example.com
  client_id: 0oa1example2
  client_secret: ${OIDC_CLIENT_SECRET}
  allowed_email_domains: [example.com]
  userinfo_fallback: true

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}
  ttl_hours: 1

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}

upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}

auto_include_builtin_models: true

これでサインインが動き、Bedrock の既定のモデルカタログが使えます。そのあと必要に応じて足します。

  • グループごとの RBAC と managed settings:managed.policies
  • テレメトリの宛先:telemetry
  • 複数の上流のフェイルオーバー・プロビジョンドスループットの ARN・米国以外のリージョン:models

Bedrock を上流にするときの準備です。

  • ゲートウェイの主体に、inference-profile/us.anthropic.* と元の foundation-model/anthropic.* の両方への bedrock:InvokeModel と bedrock:InvokeModelWithResponseStream を付ける
  • Anthropic の一度きりのユースケースのフォームを送る
  • 資格情報は静的なキーでなく、EKS の IRSA・ECS のタスクロール・EC2 のインスタンスプロファイルで渡す

起動の確認#

  • 設定・Postgres への接続・OIDC の discovery・上流のクライアントの生成のどれかが失敗すると、劣化運転をせずエラーで終了する(フェイルクローズ)
  • 起動できても推論の経路は確かめていない(Bedrock と Vertex AI のインスタンスの資格情報は、最初のリクエストで解決されるため)
  • 標準エラーには、[gateway] <時刻> <レベル> <メッセージ> の形のログと、evt フィールドを持つ1行 JSON の監査イベントが出る
  • claude gateway listening on http://0.0.0.0:8080 が出れば起動している。その前に終了したら、標準エラーの最後の行が原因(Postgres に届かない・ロールに DDL の権限が無い・OIDC の discovery が届かないか無効・設定のスキーマ違反とそのフィールドのパス)
  • access_control.allow_cidrs が空だという警告は想定どおり(許可リストを設定するまで、どのクライアントのアドレスも受けるため)

サインインの面の確認#

確認 コマンド・操作 失敗したとき
1. discovery の文書 curl -s https://claude-gateway.internal.example.com/.well-known/oauth-authorization-server 起動が終わっていない。標準エラーを見る
2. デバイス認可 curl -s -X POST https://claude-gateway.internal.example.com/oauth/device_authorization Postgres に届かないか書き込めない。接続文字列と権限を見る
3. ブラウザの区間 verification_uri_complete をブラウザで開いてコードを確定する IdP の設定かゲートウェイの監査ログを見る(下)
  • 1 が返れば、起動・設定・起動時の確認がすべて通っている
  • 2 で device_code・user_code・verification_uri・verification_uri_complete・expires_in・interval が返れば、デバイスの流れと Postgres への書き込みが動いている
  • 3 は IdP のサインインへ飛び、終わるとゲートウェイへ戻って確認が出れば成功。IdP に届かないなら、IdP のリダイレクト URI が https://<gateway>/oauth/callback と完全に一致するかを見る。IdP から戻ってエラーなら、監査ログの拒否の理由(例 email domain not allowed)を読む
  • イングレスなしのローカルの Compose では、1・2 に http://localhost:8080 を使う。3 のために public_url: http://localhost:8080 とし、OAuth クライアントに2つ目のリダイレクト URI http://localhost:8080/oauth/callback を足す
  • Windows の PowerShell では curl.exe を使う(素の curl は Invoke-WebRequest の別名)

開発者を接続する#

開発者がすることは、会社のアカウントでブラウザから1回サインインするだけです。claude.ai のアカウント・API キー・サブスクリプションは要りません。接続は MDM などで配るクライアント側の managed settings が決めるので、開発者の手作業もありません。

  • TLS の固定:初回の接続でゲートウェイの TLS のリーフ証明書のフィンガープリントを記録し、ホスト名ごとに固定する。サインイン・セッションのサイレントな更新・managed settings の取得のたびに照合する
  • 推論リクエストは固定を使わず、標準の TLS 検証だけ。HTTPS プロキシを通るリクエストは照合を省くので、ゲートウェイのホストを NO_PROXY に足して直接つなぐ
  • 期待する SHA-256 のフィンガープリントは、ゲートウェイの URL と一緒に社内へ公開しておく。/login の画面には先頭16文字(小文字の16進、コロンなし)が出る
  • 証明書を更新すると、全員に信頼の確認がもう一度出る。計画した作業として扱い、フィンガープリントを公開し直す。承認が要る設定を配っているなら、承認の記憶は固定した証明書に紐づくので、承認ダイアログも再び出る
  • ゲートウェイがトークンの応答に省略可の email を入れると、開発者は認証情報を保存する前に、どのアカウントでサインインしたかを確かめられる(開発者のマシンで v2.1.275 以降)。claude のバイナリのゲートウェイはこの項目を返さない
  • サインイン後のモデルの選択肢は、availableModels の許可リストのモデル。managed settings は起動時に適用し、1時間ごとに取り直す
  • セッションは ttl_hours の前にサイレントに更新される。IdP で無効にされて更新が失敗すると、再ログインを促す

証明書ファイルからフィンガープリント全体を出すコマンドです。

bash
openssl x509 -noout -fingerprint -sha256 -in cert.pem | cut -d= -f2 | tr -d : | tr 'A-F' 'a-f'

ゲートウェイの URL を配る#

OS ごとの managed settings に次の3つのキーを入れ、MDM などで配ります。

  • forceLoginMethod と forceLoginGatewayUrl:/login が、URL を入れた状態の「Cloud gateway」の画面で開く
  • parentSettingsBehavior: "merge":Claude Desktop が、ゲートウェイの外向きの許可リストを、自分が起動する Claude Code のセッションへ渡せる
json
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com",
  "parentSettingsBehavior": "merge"
}
  • 開発者は Enter で接続する。初回の TLS の確認の画面はそのまま出る
  • ファイルを置いたマシンでゲートウェイのサインインがまだだと、Administrator policy requires a Cloud gateway sign-in が出る(エラー一覧)。CLAUDE_CODE_USE_BEDROCK などの環境変数でクラウドのプロバイダを選ぶセッションは、ゲートウェイのサインインが要らない
  • 開発者が自分で設定する道は無い。ログインの選択肢にゲートウェイの項目は無く、forceLoginGatewayUrl は開発者自身の設定ファイルでは無視される。URL なしで forceLoginMethod だけを書くと、「IT 管理者に連絡」のメッセージで止まる
  • ログインのキーは、マシンへ配るファイルに書く。ゲートウェイの managed.policies[].cli ブロックに書いても、接続済みのクライアントにしか届かない
  • クラウドのプロバイダ変数や独自の ANTHROPIC_BASE_URL でゲートウェイを迂回させないなら、同じファイルに "allowedProviders": ["gateway"] を足す(v2.1.285 以降)。受け入れるのは、forceLoginGatewayUrl が指すゲートウェイか、ファイルの env が ANTHROPIC_BASE_URL に書いたゲートウェイだけ
  • allowedProviders があるマシンでは claude gateway が動かない。ゲートウェイのホストにはこのキーを置かない

公開アドレス空間の社内ネットワークを許可する#

  • 使う場面:自社が持つ公開 IPv4 のブロック(通信事業者自身のアドレス空間や古い /8 など)で社内網を組んでいて、ゲートウェイに私設のアドレスを付けられないとき
  • 書き方:managed settings の gatewayInternalNetworks にブロックを並べる
  • 効果:開発者のマシンが同じブロックの中から接続しているときだけ、/login がブロックの中のゲートウェイを受け入れる
  • 版:開発者のマシンで v2.1.268 以降(古い版はキーを無視し、私設アドレスの規則のまま)
json
{
  "gatewayInternalNetworks": ["203.0.113.0/24"]
}

注意

gatewayInternalNetworks は、たまたま公開アドレスで番号付けされた社内網のためのもので、ゲートウェイをインターネットへ出してよくなるわけではありません。ファイアウォールやロードバランサーの規則で外から届かないようにし、access_control.allow_cidrs にも同じブロックを入れます。ロードバランサーの後ろなら、listen.trusted_proxies もそのフロントに合わせます(でないと、allow_cidrs が開発者ではなくフロントのアドレスと照合される)。

  • 書く場所:ログインのキーと同じ managed settings(ファイル・MDM のプロファイル・レジストリのポリシー)。ユーザー・プロジェクト・サーバー管理の設定に書いても無視される
  • 例の 203.0.113.0/24 は文書用の範囲で、Claude Code は拒否する。自社のブロックに替える
  • managed-settings.json と managed-settings.d/ のドロップインのブロックは1つの一覧に結合され、下の規則は結合後の一覧に当たる。ブロックを狭めるときは、重なる項目を足さずに元の項目を置き換える
  • 規則に反する項目があるか、値が文字列の一覧でないと、そのマシンでの新しいゲートウェイのサインインがすべて拒否される(私設アドレスのゲートウェイも。既存のサインインは動き続ける)。メッセージが原因を示す。配る前に1台で試す
  • 宣言は、サインインできる人を狭めるだけで、マシンの居場所の証明にはならない。宣言するのは自社が管理するアドレス空間だけにする(クラウド事業者の公開の範囲のように他のテナントと共有するブロックだと、その中の誰でも同じ確認を通れる)

値の規則(/login がゲートウェイへつなぐ前に検証します)。

  • 各項目は IPv4 のブロック(先頭のアドレスと /8〜/32 の接頭辞)
  • 最大4つで、互いに重ならない
  • 私設の空間と重ならない:10.0.0.0/8・172.16.0.0/12・192.168.0.0/16・127.0.0.0/8・169.254.0.0/16・100.64.0.0/10
  • 組織のネットワークになりえない空間と重ならない:198.18.0.0/15・192.0.0.0/24・文書用の 192.0.2.0/24・198.51.100.0/24・203.0.113.0/24・予約の 0.0.0.0/8・192.88.99.0/24・マルチキャストの 224.0.0.0/4
  • 240.0.0.0/4 の内側は宣言できる

一覧が有効なとき、ブロックの中のゲートウェイについて /login は3つを確かめます。

  1. ゲートウェイのホスト名が解決する全アドレスが、1つのブロックの中にある(ブロックの外のレコード・私設や IPv6 のアドレスが混ざれば拒否)
  2. 開発者のマシンが同じブロックの中から接続している(NAT の内側・コンテナや WSL2・アドレスのプールがブロックの外にある VPN は拒否し、接続元のアドレスを示す)
  3. 直接の接続である(HTTPS_PROXY が当たるなら拒否し、足すべき NO_PROXY の項目を示す)

3つを通ると、信頼の確認の画面に、マシンのアドレス・ゲートウェイのアドレス・両方を含むブロックの行が足されます。

Claude Desktop のセッションへポリシーを渡す#

Claude Desktop は、Cowork と Code のタブ(有効にすれば Chat も)を埋め込みの Claude Code セッションで動かし、モデルのリクエストをゲートウェイへ通します。各セッションへ渡すポリシーは、ゲートウェイが /user/bootstrap で返す設定(一致したポリシーの cli ブロックから導いたモデルの許可リスト・無効にするツール・外向きの許可リストと、desktop の上書き)から作られます。

  • 届かない cli のキー:hooks・env・Bash(npm *) のようなスコープ付きの権限ルールなどは、/login でサインインするクライアントにしか届かない。Desktop はゲートウェイの URL を自分の managed 設定から読み、forceLoginMethod・forceLoginGatewayUrl とは別の流れでサインインする
  • 親の設定:起動元のプロセスが渡す設定のこと。管理者が配った managed の源があるマシンでは、その源が parentSettingsBehavior: "merge" を書いていない限り、Claude Code は親の設定を無視する
  • Desktop だけを動かすマシンには、この opt-in が要る。モデルの一覧と無効なツールの一覧は Desktop 自身が当てるが、外向きの許可リスト(WebFetch のドメイン規則とサンドボックスのネットワーク規則)は親の設定でしか届かないため。opt-in が無いと外向きの制限なしで動き、警告も出ない(許可されないモデルの推論は、ゲートウェイが拒否する)
  • プラグインのマーケットプレイスの許可リストも、親の設定でしか届かない。Desktop の managed 設定でユーザー追加のマーケットプレイスをオフにすると、Claude Desktop 2.16120.0 以降は、組織が用意していないマーケットプレイスを隠してインストールを拒否し、入れ済みのプラグインの読み込みを止めるために、strictKnownMarketplaces の一覧を親の設定として送る。opt-in が無いと Claude Code はこの一覧を無視し、プラグインは読み込まれ続ける
  • 要らないマシン:/login でサインインする開発者のマシン
  • 使えない組織:policyHelper で managed settings を出す組織(Claude Code はヘルパーの出力だけを読み、親の設定を合成しない)

opt-in の手順です。

  1. managed settings のファイルで、上のスニペット(parentSettingsBehavior: "merge" を含む)を配る
  2. ファイルより優先されるクライアント側の源があれば、そこにもスニペット全体を写す。Claude Code は parentSettingsBehavior を選ばれた源からだけ読み、源にポリシーのキーを足すとその源が選ばれうるため。優先の順は、ゲートウェイ自身のリモート managed settings、macOS の managed-preferences の plist と Windows の HKLM のポリシー、managed-settings.json。ゲートウェイにサインインするマシンのために、ゲートウェイのポリシーの cli ブロックにも parentSettingsBehavior を書く
  3. 選ばれている源を確かめる。Desktop だけのマシンで Agent SDK の resolveSettings() を呼び、sources の managed の項目の policyOrigin(plist・hklm・file)を読む。それがスニペットを持つべき源。埋め込みセッションはゲートウェイのポリシーを取りに行かないので、そこでゲートウェイの cli ブロックが選ばれることはない

親の設定を制限する#

  • parentSettingsBehavior: "merge" を配ると、Desktop に限らず、Claude Code を起動するどのホストのプロセス(Agent SDK のアプリ・IDE 拡張など)も親の設定を渡せる
  • 親の設定は、制限向きのキーの許可リストで絞られる。ただし許されるキーの中にも、制限でなく許可を与えうるものがある
  • allowManaged*Only のロックを掛けない限り、ホストが渡す権限の allow ルールとサンドボックスの許可リストは効く(組織のポリシーの deny と ask の規則はどちらの場合も効き、allow より先に評価される)

sandbox.credentials は、親から渡されたものを削ってから転送します。

項目 転送のされ方
deny の項目 path か name とモードだけ
mode: mask のファイル項目 ファイル全体をマスクするセンチネルだけ(injectHosts は空の一覧)
mode: mask の envVars の項目 転送しない(親の経路で envVars が表せる制限は deny だけ)
awsPairs と sigv4 制限の部分だけ(下)
  • mode: mask のファイル項目:プロキシが親の項目に本物の値を差し込むことはなく、構造化マスクのフィールドも落とす。親の抽出パターンが、他の源の厳しいマスクを置き換えられないようにするため
  • sigv4 は deny の値だけを残す。sigv4 ブロックを書いた親では、3つの形(streaming・presigned・sigv4a)が deny に固定される。awsPairs の組は、再署名できる形では転送されない

親の設定をなるべく制限の向きだけにするには、merge の opt-in と同じ源に、5つの allowManaged*Only のロックと、それぞれが受け持つ許可リストを置きます。

json
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com",
  "parentSettingsBehavior": "merge",
  "allowManagedPermissionRulesOnly": true,
  "allowManagedMcpServersOnly": true,
  "allowManagedHooksOnly": true,
  "allowedMcpServers": [{ "serverUrl": "https://mcp.internal.example.com/*" }],
  "sandbox": {
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "filesystem": {
      "allowManagedReadPathsOnly": true,
      "denyRead": ["~/"],
      "allowRead": ["~/projects"]
    }
  }
}
  • OS のポリシー(HKLM のレジストリ・managed-preferences の plist)はこのファイルより優先される。使っているなら、スニペット全体をそちらで配る
  • ゲートウェイのリモート managed settings は OS のポリシーとファイルより優先されるが、接続したクライアントにしか届かない。ロック・許可リスト・merge の opt-in をポリシーの cli ブロックにも写し、ファイルも配り続ける(接続しないマシンと Desktop だけのマシンは、ファイルからしかポリシーを得ない)
  • ロックはそれぞれ独立で、1つ掛けてもほかは制限されない(各キーは設定キー一覧)
  • 勝った源より下の管理者の源に書いても効くもの:サンドボックスの2つのロック、allowManagedPermissionRulesOnly(親から渡された allow ルールと additionalDirectories を止める)、v2.1.273 以降は MCP サーバーのロック(有効なあいだ、managed の allowedMcpServers は、それを書いている最も優先度の高い管理者の源から取る)
  • 既定で勝った源に書く必要があるもの:フックのロックと、開発者自身のルールに対する allowManagedPermissionRulesOnly の効果。managedSourcesBehavior の merge の opt-in では、どのロックも、いずれかの源が書く最も厳しい値になる。policyHelper の組織は、ヘルパーの出力だけからロックを読む
  • ロックを掛けると、その設定の開発者自身の項目は無視される。組織の許可リストを横に置く
  • ネットワークのドメインを空の managed の一覧でロックすると、サンドボックス内の外向きがすべて止まる
  • MCP サーバーを、管理者の源にも親の設定にも allowedMcpServers が無い状態でロックすると、deniedMcpServers が止めないサーバーをすべて読み込む
  • 読み取りパスは、allowRead が denyRead の領域の中だけを許可し直す仕組みなので、managed の denyRead と組にする

ロックでは覆えない、親から渡される設定が7つあります。既定の first-wins では、親の値を止められるのは最も優先度の高い管理者の源にある値だけです(MCP のロックが有効なときの allowedMcpServers は例外)。

設定 扱い
forceLoginOrgUUID 最優先の管理者の源に組織の UUID が無ければ、親の値が通る。ゲートウェイのサインインはこのキーを見ない
allowedMcpServers 管理者の一覧が効いていなければ、親の許可リストが通る。allowManagedMcpServersOnly では止まらないので、ロックと並べて最優先の管理者の源に書く(v2.1.223 より前は、どの管理者の源の値でも親の値を止めた)
availableModels 勝った managed の源に無ければ、親のモデル一覧が通る。絞るなら勝った源に書く
allowedProviders 勝った managed の源に無ければ、親の API プロバイダの許可リストが通る。開発者が使える API プロバイダを絞るなら勝った源に書く(v2.1.285 以降)
strictKnownMarketplaces 勝った源に無ければ、親のマーケットプレイスの許可リストが通る(v2.1.282 以降)。絞るなら勝った源に書く
blockedMarketplaces 親のブロックリストが、管理者の源のブロックリストに足される(制限を強めるだけなので。v2.1.282 以降)
strictPluginOnlyCustomization ロックに関係なく通り、開発者自身のカスタマイズ(保護のフックを含む)を無視させる。止めるロックは無い

Claude Desktop をつなぐ#

  • Desktop は、別の MDM のキーで同じゲートウェイにつなぐ。Desktop の managed 設定の bootstrapUrl を <listen.public_url>/user/bootstrap にし、ユーザーのポリシーで desktop キーを使って opt-in させる(ゲートウェイのサーバーで v2.1.203 以降)
  • サインインは CLI と同じブラウザの SSO。設定は Anthropic ではなくゲートウェイから取る。モデルへのアクセスとポリシーも、CLI と同じグループ単位の規則
  • CLI と Desktop の両方を使う開発者は、それぞれでサインインする(ゲートウェイのセッションは共有されない)
  • 接続後は、有効なすべてのタブのモデルのリクエストがゲートウェイを通る。既定で見えるのは Cowork と Code のタブ。Chat のタブも出すには、Desktop の managed 設定で chatTabEnabled を true にする(v2.1.227 以降のゲートウェイなら、ポリシーの desktop ブロックでも書ける)

CI とリモートのマシン#

  • 無人のパイプライン向けのサービストークンは無い。サインインは必ずブラウザのデバイスフローなので、承認する開発者のいない CI のジョブは認証できない(CI は提供元に直接つなぐ)
  • 開発者がサインイン済みのマシンでは、非対話の claude -p や Agent SDK が起動したものを含め、すべての Claude Code のセッションがゲートウェイのセッションを使い、そのポリシーが当たる
  • デバイスフローでは、ポーリングする CLI と承認するブラウザが別のマシンでよい。画面の無いリモートの開発機でも、SSH 先で /login を実行し、手元のノート PC のブラウザで確認のリンクを開けばサインインできる

開発者に強制されること#

/login でサインインしたすべてのセッションに当たります(Desktop が起動する埋め込みセッションへのポリシーは、上の「Claude Desktop のセッションへポリシーを渡す」)。

項目 内容
モデルへのアクセス 許可されないモデルのリクエストは 400。/model は availableModels に絞られる
テレメトリの宛先 OTLP/HTTP の出力はゲートウェイへ行き、telemetry.forward_to の宛先へ中継される
資格情報 ゲートウェイのトークンだけを使う。ほかの API キーが残っていると止まる
managed settings ロックされたキーはローカルで上書きできない。起動時に適用し、1時間ごとのポーリングで変更を当てる(次の起動でだけ効く変更を除く)
ゲートウェイに届かない起動 設定なしでは始まらず、約10秒後にエラーで終了する
退職の処理 IdP で無効にしたユーザーは、次の更新に失敗して ttl_hours 以内に切れる
サインアウト /logout で、開発者のマシンからゲートウェイの資格情報が消える
  • モデル:ポリシーが許可しないモデルのリクエストは 400 を返し、/model の選択肢も availableModels の許可リストに絞られる。セッションが最初に使うモデル(開発者が選ぶ前のもの)も対象。詳しくは「ポリシーが許すモデルでセッションを始める」の節
  • テレメトリ:ローカルの OTEL_EXPORTER_OTLP_ENDPOINT ではなくゲートウェイへ送る(ポリシーが自社のコレクターをエンドポイントに指名した場合を除く)。Desktop が起動する埋め込みセッションは設定済みの OTEL_EXPORTER_OTLP_ENDPOINT へ出し、そこがゲートウェイ自身のときだけセッショントークンを付ける。宛先の無い信号は、ゲートウェイが受けて捨てる
  • 資格情報:サインイン中は、Anthropic のプロファイルと以前の claude.ai のログインを無視する。ANTHROPIC_API_KEY・ANTHROPIC_AUTH_TOKEN・apiKeyHelper・以前の Console のログインで保存された API キーのどれかがあると、Administrator policy requires a Cloud gateway sign-in で止まる
  • サインアウト:ゲートウェイの discovery の文書が、ゲートウェイの URL と同じスキーム・ホスト・ポートの revocation_endpoint を載せていれば、保存したトークンをそこへ送り、ゲートウェイ側でもセッションを終えさせる(ベストエフォート。開発者のマシンで v2.1.275 以降)。claude のバイナリのゲートウェイは載せないので、サーバー側では終わらない。サーバー側で強制的に切るには JWT シークレットを回す

組織から見えるのは、開発者の ID・トークン数・モデル・遅延を含む使用量のテレメトリです。プロンプトや応答の中身は、ゲートウェイがログにも保存にも残しません。ログとトレースはコマンドやファイルパスを含みうるので、宛先ごとに選ぶ形になっています。

機能の対応状況#

機能 状態 備考
推論の転送(Bedrock・Claude Platform on AWS・Vertex AI・Foundry・Anthropic) 使える 上流ごとのモデル変換とフェイルオーバーつき。下の補足も参照
IdP のグループ別のモデル許可と managed settings 使える モデルはサーバー側で強制、managed settings は CLI が managed の階層で適用
Claude Desktop opt-in で使える ポリシーの desktop キーで opt-in すると /user/bootstrap が設定を返す。サーバーで v2.1.203 以降
テレメトリの中継(OTLP/HTTP) 使える 出力ごとに ID を付ける。protobuf と JSON の両方
OIDC の IdP 使える OIDC 準拠なら何でも
ユーザー・グループ単位の支出上限 使える 下の「支出上限」
サーバー側のウェブ検索 使えない ゲートウェイのセッションでは WebSearch を無効にする
リモートコントロール 使えない ゲートウェイを名指しするエラーになる
/design-sync と /design-login 使えない コマンド自体が出ない(どちらも claude.ai が要る)
機能フラグの取得が要る機能(/import・claude import など) 使えない ゲートウェイのセッションではフラグを取りに行かない
標準のプロンプトキャッシュ 使える cache_control を全上流へ転送する
1時間のキャッシュ TTL 使えない extended-cache-ttl のベータを省くので、5分の TTL になる
auto モード 使える サードパーティのプロバイダの規則どおり、対応するモデルだけ
ファーストパーティ専用の最適化(グローバルなキャッシュのスコープ・トークン効率のよいツールなど) 使えない ゲートウェイのセッションでは有効にしない
OTLP/gRPC 非対応 OTLP は HTTP だけ
SAML・LDAP など OIDC 以外の認証 非対応 OIDC だけ。要るなら OIDC のブリッジを前に置く
マルチテナント(複数の OIDC の発行者) 非対応 1つのゲートウェイに発行者は1つ。別のインスタンスを動かす
Windows のサーバー 非対応 Linux に配備する。macOS はローカル開発だけ
Helm チャート 無し ふつうのステートレスな Deployment として動かす
管理画面 無し 設定は YAML ファイル。変えたら再デプロイ
  • 推論の転送:Bedrock の上流は bedrock-runtime と AWS の既定の資格情報チェーンを使う。Amazon Bedrock の Mantle の上流はサーバーで v2.1.283 以降、Claude Platform on AWS はサーバーで v2.1.198 以降
  • ウェブ検索:ゲートウェイがどの上流へ振るかが CLI から見えず、ウェブ検索に対応するかを確かめられないため
  • /design-sync・/design-login:ゲートウェイのセッションでは CLI が claude.ai に接続しないため
  • 1時間の TTL:ゲートウェイが振る上流のすべてが1時間の TTL に対応するとは限らないため
  • auto モード:v2.1.207 より前は CLAUDE_CODE_ENABLE_AUTO_MODE=1 が必要だった(ポリシーの env で配れる)
  • anthropic-beta:CLI が送る値をゲートウェイが全上流へ届けるので、運用側でベータの許可リストを持たなくてよい。ヘッダーを読まない Bedrock には本文の anthropic_beta へ移して渡し、ほかの上流にはヘッダーのまま渡す

gateway.yaml の設定#

  • 設定は1つの YAML ファイル(慣例で gateway.yaml)。起動時に1回だけ読む
  • 全項目をスキーマで検証し、不正ならフィールドを名指しするエラーで起動が止まる
  • 未知のキーも起動を止める。 タイプミスが黙って無視されることはない
セクション 区分 内容
listen 必須 バインドアドレス・公開 URL・TLS の終端
oidc 必須 IdP(発行者・クライアント・クレームの対応・サインインできる人)
session 必須 ゲートウェイが発行するベアラートークンの秘密と寿命
store 必須 PostgreSQL(デバイスの許可とレート制限のカウンター)
upstreams 必須 推論の行き先
admin 省略可 支出上限の Admin API の認証と保持期間
enforcement 省略可 ストアの障害時に、支出上限を通す側か止める側か
pricing 省略可 契約料金と倍率
models と auto_include_builtin_models 省略可 管理者が整えるモデル一覧と、上流ごとの ID
managed 省略可 IdP のグループごとの managed settings のポリシー
telemetry 省略可 OTLP の中継
access_control・limits・timeouts・rate_limits 省略可 HTTP の調整
load_test_mode 省略可 モデルの提供元を呼ばない負荷試験

秘密の展開#

client_secret・jwt_secret・postgres_url などの秘密は gateway.yaml に直接書かず、次の形で参照します。起動時に環境変数かファイルから解決されます。

形 解決先 用途
${VAR} 環境変数 VAR。未定義なら起動に失敗する コンテナの環境変数、環境変数へ注入する AWS Secrets Manager
${file:/path} その絶対パスのファイルの中身(前後の空白を除く) Kubernetes の Secret のボリュームマウント、Vault Agent、SOPS

${file:...} はフィールドの値全体でなければならず、${VAR} と違って長い文字列の途中では展開されません。データベースのパスワードは postgres_url に埋めず、store.password に書きます。

listen#

フィールド 必須 内容
host いいえ バインドアドレス。既定 0.0.0.0
port いいえ バインドポート。既定 8080
public_url host がループバックでなければ必須 外から見える https:// のオリジン
tls.cert / tls.key いいえ ゲートウェイ自身が TLS を終端するときの PEM のパス
trusted_proxies いいえ 前段のロードバランサーの CIDR か IP。ここからの X-Forwarded-For だけを信頼する
  • public_url:IdP の redirect_uri と discovery のメタデータを作るのに使う。X-Forwarded-* から自分のオリジンを導くことはしない(クライアントが偽れるため)ので、無いと起動に失敗する。テレメトリを有効にするのにも必須(クライアントへ押す OTLP エンドポイントをこの URL から作る)
  • trusted_proxies:本当のクライアント IP をレート制限と監査に記録できるようになる(nginx の set_real_ip_from と同じ役)。ipv4:port・[ipv6]:port の形の X-Forwarded-For はポートを除いて読む。角括弧なしでポートを付けた IPv6 は、別のアドレスとして読まれたり読まれなかったりするので、その形を書くプロキシのポートのオプションはオフにする

oidc#

フィールド 必須 内容
issuer はい OIDC の discovery の基底(/.well-known/openid-configuration を出す)。本番は HTTPS
client_id はい OAuth クライアントの登録で得た値
client_secret token_endpoint_auth_method が private_key_jwt でなければ必須 OAuth クライアントの登録で得た値。証明書によるクライアント認証(下)を使うときは書かない
allowed_email_domains いいえ email クレームがこのドメインに無い id_token を拒否する(大文字小文字は区別しない)
allowed_groups いいえ サインインを、groups_claim に一致する IdP のグループのメンバーに限る
groups_claim いいえ グループを運ぶ id_token のクレーム。既定 groups
google_groups いいえ Google Workspace の Admin SDK Directory API で、ユーザーのグループを引く
email_claim いいえ メールを運ぶ id_token のクレーム。既定 email
scopes いいえ 要求する OIDC スコープの丸ごとの上書き。既定 [openid, profile, email, offline_access]
scope_on_refresh いいえ リフレッシュのときもサインインと同じ scope を送る。既定 false。サーバーで v2.1.260 以降
extra_auth_params いいえ IdP の認可リクエストにそのまま足すクエリパラメータ
userinfo_fallback いいえ id_token にメールやグループが無いとき /userinfo から取る。既定 false
use_pkce いいえ 認可リクエストに PKCE(S256)のチャレンジを付ける。既定 true
clock_skew_seconds いいえ id_token の時刻のクレームの検証で許す時計のずれ。既定 0(厳格)
token_endpoint_auth_method いいえ ゲートウェイが IdP のトークンエンドポイントへ認証する方式。client_secret_basic・client_secret_post・証明書によるクライアント認証の private_key_jwt。既定では、IdP が示す内容から2つの client_secret 方式のどちらかを選ぶ
client_assertion private_key_jwt のとき private_key_pem と certificate_pem を持つブロック。証明書によるクライアント認証に使う秘密鍵と証明書。サーバーで v2.1.284 以降
id_token_signed_response_alg いいえ 期待する id_token の署名アルゴリズム。既定 RS256(ES256・PS256・EdDSA の IdP 向け)
additional_authorized_parties いいえ client_id のほかに受け入れる azp の値(Keycloak のブローカーとトークン交換のフロー向け)
discovery_url いいえ issuer から導かずに discovery の文書を取る URL(パスに /.well-known/ を含む)
use_proxy いいえ IdP へのリクエストを HTTPS_PROXY・HTTP_PROXY のフォワードプロキシ経由にする。v2.1.227 以降
form_action_origins いいえ /device のページの Content-Security-Policy: form-action に足すオリジン
ca_cert_pem いいえ IdP へのリクエストだけに使う CA 証明書(PEM の中身そのもの)
  • issuer:http:// の issuer も受ける。ただし http://localhost:8081 のようなループバックの issuer は、環境変数 CLAUDE_GATEWAY_ALLOW_LOOPBACK=1 が無いと SSRF ガードが拒否する
  • allowed_email_domains:マルチテナントの IdP の設定ミスへの多層防御。これとは別に、email_verified が明示的に false の id_token は常に拒否する
  • allowed_groups:許可されたドメインでも、どのグループにも居ないユーザーは拒否する。IdP がグループのクレームを出す必要がある。大文字小文字を区別する完全一致で、入れ子のグループは展開しない(サブグループのメンバーも通すなら、サブグループを並べるか、IdP を平らなメンバーシップを出す設定にする)
  • groups_claim:平らなキーか、/resource_access/gateway/roles のような入れ子のクレームを指す RFC 6901 の JSON Pointer。Microsoft Entra はアプリのロールを roles に出す
  • google_groups:Google の id_token にはグループのクレームが無いために使う。service_account_json_path(https://www.googleapis.com/auth/admin.directory.group.readonly のスコープでドメイン全体の委任を持つサービスアカウントのキーファイル)と、サービスアカウントが成り代わる Workspace 管理者の admin_email(Directory API は本物の管理者の主体を求める)を書く。グループのメールアドレスがグループのクレームになるので、allowed_groups と managed.policies.match.groups はグループのメールで書く
  • email_claim:ADFS や Entra B2C のように、upn や preferred_username でメールを出す IdP 向け。平らなキー・JSON Pointer・フォールバックのキーの一覧(最初に在るものを使う)のどれかで書く
  • scopes:IdP が知らないスコープを拒否するときや、グループ・メールを出すのにカスタムのスコープが要るときに書く。openid は必ず含める。offline_access を外すとリフレッシュトークンが無くなり、開発者は session.ttl_hours ごとにブラウザのログインをやり直す
  • scope_on_refresh:既定では、リフレッシュのリクエストは scope を省く。多くの IdP は更新のたびに id_token を返すので不要。更新で openid を求め直されたときだけ id_token を返す IdP(Okta がそう文書化している)なら true にする
  • id_token が返らない更新は、更新後のアクセストークンを userinfo が受け付けるかに懸かる。グループでサインインを制限するかポリシーを一致させていて、更新時の id_token がグループを省く IdP なら、userinfo_fallback: true も書く
  • 要求より少ないスコープしか付与していない IdP は、invalid_scope で更新を拒否することがある。scope_on_refresh をオンにしたまま scopes に項目を足すと、既存のセッションでも起きる。設定後に更新が token_endpoint で失敗し始めたら外す
  • extra_auth_params:IdP 固有の挙動を上書きする口(Google のリフレッシュトークンの access_type: offline、一部の Entra テナントの domain_hint、ステップアップの acr_values)。ゲートウェイが管理するパラメータ(state・nonce・redirect_uri・PKCE・scope・response_type・response_mode・client_id)は上書きできない
  • userinfo_fallback:Keycloak の軽量アクセストークン・Okta の org サーバー・ADFS の最小トークンで要る。正は id_token で、userinfo は欠けを埋めるだけ
  • use_pkce:IdP がこの機密クライアントの PKCE を拒否するときだけ false にする
  • clock_skew_seconds:サインイン直後に「token expired / not yet valid」が出るなら上げる
  • discovery_url:issuer のホストを書き換えるプロキシの後ろにある IdP 向け
  • use_proxy:NO_PROXY に従う。false で直接(下の項)
  • form_action_origins:'self' と、discovery した authorization_endpoint のオリジンは許可済み。ただし Chrome はリダイレクトの連鎖全体に form-action を強制する。IdP が2つ目のホストを経由する(Azure AD から ADFS へのフェデレーション・ハブ&スポークの Okta・社内の SSO インターセプターなど)なら、認可リクエストが経由しうるオリジンをすべて並べる
  • ca_cert_pem:ファイルのパスではない。IdP へのリクエストでだけ、システムのトラストストアの代わりに使う。マウントしたファイルなら ${file:/etc/gateway/idp-ca.pem}。社内の PKI の後ろの Keycloak や Dex 向け

証明書によるクライアント認証#

IdP が OAuth クライアントをクライアントシークレットではなく証明書で認証する場合(Microsoft Entra の証明書の資格情報など)は、token_endpoint_auth_method: private_key_jwt にします。ゲートウェイのサーバーで Claude Code v2.1.284 以降が要ります。

この設定ではシークレットを送りません。開発者のサインインのときと、ゲートウェイがセッションを更新するたびに、証明書の秘密鍵で署名した短命の JWT で IdP のトークンエンドポイントへ認証します。署名は RS256 で、証明書は kid ではなく x5t と x5t#S256 のサムプリントのヘッダーで示します。IdP が、登録された証明書をサムプリントで探せる必要があります。

  1. 鍵と証明書を作る。暗号化していない 2048 ビット以上の RSA 秘密鍵(PKCS#8 か PKCS#1 の PEM)と、その証明書を用意する。条件を満たさない鍵ではゲートウェイが起動を拒む。次の openssl で、1年有効の自己署名の証明書つきの鍵ができる。できた idp-client.key と idp-client.crt を、ゲートウェイが読める場所へ置く(手順3の例は /etc/gateway/)

    bash
    openssl req -x509 -newkey rsa:2048 -nodes -keyout idp-client.key -out idp-client.crt -days 365 -subj "/CN=claude-gateway"
    
  2. 証明書を IdP へ上げる。上げるのは証明書で、秘密鍵ではない。ゲートウェイのアプリ登録に上げる

  3. gateway.yaml に client_assertion ブロックで秘密鍵と証明書を渡す。client_secret は書かない(private_key_jwt と一緒に書くと、ゲートウェイは起動を拒む)。次は Microsoft Entra のテナントへ証明書で認証する例

    yaml
    oidc:
      issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
      client_id: <application-id>
      token_endpoint_auth_method: private_key_jwt
      client_assertion:
        private_key_pem: ${file:/etc/gateway/idp-client.key}
        certificate_pem: ${file:/etc/gateway/idp-client.crt}
    

    値は PEM の中身で、ファイルのパスではないので、例のように ${file:/path} で読み込む。certificate_pem は、チェーンの残りを含まない1枚の PEM 証明書で、公開鍵が private_key_pem と合っていなければ、ゲートウェイは起動を拒む

  4. ゲートウェイを再起動し、起動ログの次の行を探す

    text
    [gateway] 2026-10-01T23:07:40.512Z info oidc: client authentication private_key_jwt; certificate CN=claude-gateway, SHA-1 thumbprint DE92821854EE8BAA1D98C758FAA04AABE80B9F57, expires Oct  1 23:07:31 2027 GMT
    

    SHA-1 のサムプリントを、IdP が上げた証明書について示すものと突き合わせる。証明書が期限切れか、まだ有効でないときは、ゲートウェイは起動するが、置き換えるまでサインインと更新が失敗するという警告を出す。IdP が証明書を受け入れるかは、開発者1人にゲートウェイ経由でサインインしてもらって確かめる

クライアント証明書のローテーション

ゲートウェイは鍵と証明書を起動時に1回だけ読むので、ファイルを変えても再起動するまで効きません。IdP が持たない証明書でトークンを要求しないよう、次の順で回します。

  1. 新しい証明書を、古いものと並べて IdP へ上げる
  2. gateway.yaml が読む鍵と証明書のファイルを置き換え、ゲートウェイを再起動する
  3. 古い証明書を IdP から外す

IdP へのリクエストをフォワードプロキシ経由にする#

  • 推論の上流は、どの版でも HTTPS_PROXY・HTTP_PROXY に従う
  • IdP へのリクエスト(discovery・JWKS・トークン・userinfo)は、oidc.use_proxy: true(v2.1.227 以降)にしない限り直接行く
  • プロキシの変数があり、use_proxy が未設定で、issuer が NO_PROXY に入っていなければ、直接のまま起動時にどちらかを選ぶよう通知する(use_proxy: false で通知が止まる)
  • use_proxy: true では、ポッドが各 IdP エンドポイントのホスト名を自分で解決し、その IP への CONNECT をプロキシに頼む。プロキシは、discovery の文書が名指しするすべてのホストの IP への CONNECT を通す必要がある(issuer だけでは足りない)
  • プロキシの URL は http:// で書く。ca_cert_pem と SSRF ガードは、プロキシ経由の経路にも当たる

プロキシ経由だけの外向き通信#

  • 使う場面:ポッドがフォワードプロキシ経由でしか外へ出られず、公開 DNS 名を自分で解決できないとき。または、プロキシが IP アドレスへの CONNECT を拒否するとき
  • 設定:ゲートウェイの環境に、HTTPS_PROXY と並べて CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1 を書く(v2.1.277 以降)。設定ファイルのキーでなく環境変数なのは、設定ファイルの中身でゲートウェイのアドレスの確認を緩められないようにするため
  • 有効になると、起動時に network: の行が1行出る
bash
export HTTPS_PROXY=http://proxy.corp.example.com:3128
export NO_PROXY=
export no_proxy=
export CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1

HTTPS_PROXY があるゲートウェイでの、外向きリクエストの種類ごとの扱いです。

外向きのリクエスト 既定 プロキシ経由だけの外向きが有効なとき
provider: anthropic の上流・Workload Identity Federation のトークン交換・telemetry.forward_to の出力 ローカルで解決して確かめ、その IP へプロキシ経由で CONNECT。NO_PROXY に載せたテレメトリのコレクターは直接 ホスト名をプロキシへ渡す
IdP の discovery・JWKS・トークン・userinfo oidc.use_proxy: true でなければ直接。true なら確かめた IP へ CONNECT ホスト名をプロキシへ渡す(oidc.use_proxy: false なら、社内の IdP は直接のまま)
Bedrock・Claude Platform on AWS・Vertex AI・Foundry の上流、Google のグループ検索 ホスト名をプロキシへ渡す 変わらない

次の3つがそろうまで、プロキシ経由だけの外向きはオフのままです。そろわなければ、止めている変数を名指しする警告を起動時に出し、既定の挙動を保ちます。

  1. HTTPS_PROXY か HTTP_PROXY がある
  2. NO_PROXY と no_proxy が空。プラットフォームが注入するなら、ゲートウェイのコンテナで両方を空にする(テレメトリのコレクターを NO_PROXY に載せると、オフのまま)
  3. CLAUDE_GATEWAY_ALLOW_LOOPBACK がオンでない。プロキシへ渡したループバックのアドレスはプロキシのホスト自身を指すので、ポッド自身のループバックのコレクターや IdP とは併用できない(有効なあいだは localhost 風の名前を拒否する)

有効にしたら、内部のコレクターや IP で書いたホストも含め、すべての宛先をプロキシ側で許可します。

注意

オンにするのは、プロキシの許可リストがゲートウェイ自身の確認と同じか、それより厳しいときだけです。プロキシは、クラウドのメタデータのエンドポイント(169.254.169.254・metadata.google.internal)・リンクローカルのアドレス・プロキシのホスト自身のループバックを、名前だけでなく解決先のアドレスでも拒否する必要があります。頼まれればどこへでもつなぐプロキシだと、これらのリクエストについてゲートウェイの SSRF ガードが無いのと同じになります。

session#

フィールド 必須 内容
jwt_secret はい ゲートウェイの HS256 のベアラートークンに署名する秘密。32バイト以上のエントロピー(openssl rand -base64 32 など)
ttl_hours いいえ ゲートウェイのベアラートークンの寿命。既定 1
  • jwt_secret は1つの文字列か、ローテーション用の配列(先頭の項目で署名し、全項目で検証する)。回すときは新しいものを先頭に足し、ttl_hours 待ってから古いものを外す
  • ttl_hours:IdP がリフレッシュトークンを出すなら、CLI が期限前にサイレントに更新する。短いほど退職の処理が速く、長いほど IdP への往復が減る
  • offline_access が使えず IdP がリフレッシュトークンを出せないなら、サイレントな更新が無い。ttl_hours を 8 か 12 に上げて、毎時のブラウザのログインを避ける

store#

フィールド 必須 内容
postgres_url はい postgres:// か postgresql:// の URL
username いいえ postgres_url のユーザーを上書きする
password いいえ データベースのパスワード。URL の資格情報より優先される
max_connections いいえ レプリカごとの Postgres の接続プールの大きさ。既定 5
connect_timeout_seconds いいえ 接続を開くときの待ち秒数。1〜60 の整数、既定 5。サーバーで v2.1.274 以降
readiness_grace_seconds いいえ Postgres が応答しなくなってからも /readyz が ready を返し続ける秒数。0〜3600 の整数、既定 0。サーバーで v2.1.282 以降
  • postgres_url が必須なのは、デバイスの許可の受け渡し(ブラウザのコールバックが書き、ポーリングする CLI が読む)にレプリカをまたぐ状態が要るため。起動時とアップグレード時にスキーマを自分で移行するので、ロールにテーブルの作成と変更の権限が要る
  • password:URL に入れずにここへ書く。どんな文字も受ける
  • max_connections:既定は控えめで、共有のデータベースに優しい値。支出上限を使うと推論リクエストごとに数回の操作をするので、専用のデータベースで負荷があるなら上げる。レプリカ数 × この値を、データベースの max_connections より小さく保つ
  • connect_timeout_seconds:新しいゲートウェイのインスタンスが起動時に接続でタイムアウトするなら上げる
  • connect_timeout_seconds と readiness_grace_seconds は、対応する版より古いサーバーだと、キーがあるだけで起動を拒否する

upstreams#

  • 順序つきの一覧。要求されたモデルを解決できる最初の上流へ推論を転送する
  • 次の上流へフェイルオーバーするのは 5xx・429・401・403・404・タイムアウト。401・403 はその上流がゲートウェイの資格情報を拒んだか、要求のモデルなどへのアクセスを拒んだこと、404 はその上流が要求のモデルを出していないこと(後ろの上流は出しうる)を表す
  • 404 でのフェイルオーバーはサーバー v2.1.198 以降(それより前は、最初の 404 をクライアントへ返した)
  • ほかの 4xx は上流でなくリクエスト側の問題なので、フェイルオーバーしない
  • 同じプロバイダを複数置くなら、それぞれに別の name: を付ける
  • Bedrock・Claude Platform on AWS・Vertex AI・Foundry のクライアントは起動時に1回作られ、SDK が中で資格情報を更新する。クラウドの資格情報を回しても再起動は要らない。静的な Anthropic の API キーとベアラーは起動時に読む
provider 主なキー
anthropic auth.api_key・auth.oauth_token・base_url、または Workload Identity Federation の auth.*
bedrock region・auth・base_url・guardrail・assume_role
mantle region(必須)・models(必須)・auth・base_url。サーバーで v2.1.283 以降
anthropicAws region(必須)・workspace_id(必須)・auth・base_url。サーバーで v2.1.198 以降
vertex region(global も可)・project_id・auth・base_url
foundry resource・auth・base_url
  • anthropic:auth.api_key は x-api-key で送る。auth.oauth_token は Authorization: Bearer で送り、起動時に1回読むだけなので、更新はシークレットの再マウントと再起動。base_url の既定は https://api.anthropic.com
  • anthropic の Workload Identity Federation:auth.federation_rule_id・auth.organization_id・auth.identity_token_file。workspace_id はルールが複数のワークスペースにかかるとき必須。service_account_id は省略可で、宛先の確認に使う。トークンのファイルは交換のたびに読み直すので、回したトークンも再起動なしで拾う
  • bedrock の auth:空の {} は AWS の既定の資格情報チェーン。ほかに aws_access_key_id と aws_secret_access_key(aws_session_token は省略可)、または aws_bearer_token。base_url は FIPS や VPC エンドポイント向けに bedrock-runtime を上書きする
  • anthropicAws:workspace_id は毎回のヘッダーで送る。auth は api_key か SigV4(空 {} か明示の資格情報)
  • vertex の auth:空の {} は Application Default Credentials、service_account_json はキーファイルのパス。base_url は Private Service Connect 向け
  • foundry:resource から https://<resource>.services.ai.azure.com を導く。auth は use_azure_ad: true(DefaultAzureCredential か Managed Identity)か api_key。base_url は Azure Government などのソブリンクラウド向け

上流のエラーメッセージ#

  • フェイルオーバーしないステータスが返ったら、その上流の応答を返す(後ろの上流は試さない)
  • 試した上流がすべてフェイルオーバーの形で失敗したら、次の順で最初に当てはまるものを返す:最後の 429、最後の 401 か 403、最後の 404、最後の 501、ゲートウェイ自身の 502(all upstreams failed (N attempted)。N は、モデルを出さないので飛ばした項目も含めた upstreams の全項目数)
  • 上流の応答を返すとき、ステータスコードは保つ。メッセージを保つかはプロバイダ次第。Anthropic API の上流のエラー本文は、そのまま開発者に届く
  • クラウドの上流(Bedrock・Claude Platform on AWS・Vertex AI・Foundry)のエラー文には、アカウント ID・ロールの ARN・プロジェクト ID が入りうる。全文は運用ログに残し、開発者には次を見せる
  • 400・413 で Anthropic 標準のエラーの形なら、上流自身のメッセージ(prompt is too long など)
  • 400・413 でプロバイダ独自の形なら、capability_rejected: のトークン。分類できなければ、400 は upstream rejected the request、413 は request too large for this upstream
  • ほかのステータスは、upstream rate limit exceeded のようなステータスごとの定型文
  • 例:Bedrock の Input is too long for requested model. は capability_rejected: prompt_too_long になり、Claude Code は prompt is too long と同じく自動で圧縮する
  • クラウドの上流の 400・413 のメッセージを保つ・capability_rejected: に置き換える動きは、ゲートウェイ v2.1.233 以降

forward_user_identity:自前のプロキシへユーザーの ID を渡す#

  • 使う場面:provider: anthropic の上流の base_url を自前のプロキシに向けていて、どの開発者のリクエストかをプロキシに伝えたいとき。プロキシ側で開発者ごとの支出を付けられる
  • 設定:その上流に forward_user_identity: true(既定 false。ゲートウェイで v2.1.233 以降)。その上流へ転送するすべてのリクエストに、次のヘッダーが付く
ヘッダー 値
x-litellm-end-user-id IdP がメールを出していれば、開発者のメール
x-claude-gateway-user-id トークンの sub クレームの、開発者の IdP の主体
x-claude-gateway-user-email IdP がメールを出していれば、開発者のメール
  • IdP のトークンにメールが無ければ、x-claude-gateway-user-id だけを送る。メールが別のクレームにあるなら、oidc.email_claim をそのクレームにする
  • 開発者のメールつきのリクエストにプロキシが 429 を返すと、フェイルオーバーせず、その応答を開発者へそのまま返す(プロキシのユーザー単位の予算やレート制限を生かすため)。プロキシのほかの応答は、通常のフェイルオーバーの規則どおり
  • メールの無い開発者のリクエストはメールのヘッダーなしで届くので、その 429 は上流の容量として扱われ、フェイルオーバーする(サーバー v2.1.267 より前は、すべての 429 がフェイルオーバーした)
  • 自分で運用するプロキシを base_url にした上流にだけ書く。base_url が Anthropic API(既定)のままだと、起動を拒否する

Bedrock の上流#

設定 内容
IAM の権限 推論プロファイルと元の foundation-model の両方の ARN に bedrock:InvokeModel・bedrock:InvokeModelWithResponseStream。foundation-model には bedrock:CountTokens も
モデルの利用許可 商用リージョンでは既定で有効。残る関門は、Anthropic の一度きりのユースケースのフォーム(コンソールの Model catalog から送る)
EKS(IRSA) 上のポリシーを持つ IAM ロールを、ゲートウェイのサービスアカウントに結びつける。auth: {} が拾う
ECS / EC2 IAM ロールをタスク定義かインスタンスプロファイルに付ける
それ以外 環境変数 AWS_ACCESS_KEY_ID・AWS_SECRET_ACCESS_KEY・AWS_SESSION_TOKEN か、auth: に ${VAR} で明示する
リージョン region: は API のエンドポイントのリージョン。米国以外のリージョンとプロビジョンドスループットの ARN には models: を足す
  • ARN の例(内蔵カタログ・米国のリージョン):arn:aws:bedrock:<region>:<account>:inference-profile/us.anthropic.* と arn:aws:bedrock:*::foundation-model/anthropic.*
  • bedrock:CountTokens:クライアントが途中でやめたリクエストの入力トークンを無料で数え、支出上限を正確に保つため。無いと、数えるのに1トークンの Bedrock リクエストを使う
  • EKS:信頼ポリシーはクラスターの OIDC プロバイダに対して、ゲートウェイのサービスアカウントに絞る。サービスアカウントに eks.amazonaws.com/role-arn の注釈を付ける
  • 明示する資格情報はそろえる。aws_access_key_id と aws_secret_access_key の片方だけ、または aws_session_token だけだと起動に失敗する(v2.1.207 より前は、一部だけの auth: も検証を通った)
  • クロスリージョン推論プロファイルは、どれを選んでも地域(US・EU・APAC)の中でリージョンをまたぐ
yaml
upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}
    guardrail:
      id: gr-abc123
      version: "1"

guardrail は、その上流のすべての推論リクエストに Bedrock の guardrail を当てます(サーバーで v2.1.281 以降)。

  • id は guardrail の ID か完全な ARN。version は公開済みのバージョン番号か DRAFT で、引用符で囲む(裸の 1 は起動に失敗する)
  • 署名する主体(ゲートウェイの AWS 主体、assume_role があれば role_arn のロール)に、guardrail への bedrock:ApplyGuardrail も付ける
  • 入力タグには対応しない。ガードのコンテンツタグをプロンプトに足さないので、タグ付きの入力だけに当たる guardrail のフィルタは、ゲートウェイ経由では動かない
  • すべての bedrock の上流に書くか、1つも書かないか。混ぜるとフェイルオーバーで guardrail の無い上流へ送りうるので、起動を拒否する
  • 対象は Bedrock の上流だけ。別のプロバイダを upstreams に並べると、ゲートウェイは guardrail なしでそこへ送る。ただしそれが mantle なら、起動を拒む。本文に amazon-bedrock-guardrailConfig のような amazon-bedrock-* フィールドを持つ /v1/messages リクエストが、guardrail を書いた Bedrock の上流に届くと、転送せず 400 を返す
assume_role:別の AWS アカウントの Bedrock
  • Bedrock の上流に assume_role を書くと、ゲートウェイは自分の AWS の ID を、名指ししたロール(別アカウントでもよい)への sts:AssumeRole にだけ使う
  • その上流の Bedrock リクエストは、STS が返す1時間の資格情報で署名する。長期のアクセスキーがアカウントをまたがない
  • サーバーで v2.1.281 以降。古いゲートウェイは、キーがあると起動を拒否する
キー 内容
role_arn 引き受ける IAM ロール(arn:aws:iam:: か arn:aws-us-gov:iam:: の ARN)。この上流に要る Bedrock の権限を持たせる
external_id 省略可。毎回の sts:AssumeRole に送る外部 ID(数字だけなら引用符で囲む)
session_name 省略可。email か sub で、開発者ごとのセッションにする。未設定なら全リクエストが claude-apps-gateway という1つのセッション
  • role_arn のロールの権限には bedrock:CountTokens を含める。上流が guardrail を書くなら bedrock:ApplyGuardrail も
  • external_id は、ロールの信頼ポリシーが求めるときに書く
  • 信頼ポリシーはゲートウェイ自身の主体(IRSA や ECS のタスクロール)を名指しする。その主体に要るのはロールへの sts:AssumeRole だけで、自身の Bedrock の権限は要らない。external_id を使わないなら Condition を外す
  • STS が拒否するか届かないとき、上流自身の資格情報で代わりに送ることはしない。STS のエラーと確認点をログに出し、次の上流を試す。後ろの assume_role の無い上流は自分の資格情報で応えるので、それを望むときだけ並べる
  • STS のエンドポイントは sts.<region>.amazonaws.com(ネットワークが届く必要がある)。FIPS のエンドポイントにするには、AWS の設定ファイルの use_fips_endpoint ではなく、ゲートウェイの環境に AWS_USE_FIPS_ENDPOINT=true を書く
  • 使えるのは provider: bedrock だけ。SigV4 の元の資格情報が要るので、aws_bearer_token と一緒に書くと起動を拒否する
  • ゲートウェイが受け入れた開発者は誰でもこの上流を使える。誰がどのモデルを使えるかは managed が決める
  • ロール経由のモデルを別のアカウントからも出させたくないなら、そのモデルにカスタム ID を付け、upstream_model のマップにこの上流の名前だけを書く。その ID ではほかの上流をすべて飛ばすので、リクエストも、中断したリクエストのトークン数えも、別のアカウントへフェイルオーバーしない
  • 内蔵のモデル名のリクエストも、この上流へ届きうる。届けば同じロールで署名される。このアカウントでも内蔵のモデルを出してよいのでなければ、この上流を最後に並べる
yaml
upstreams:
  - name: bedrock-isolated
    provider: bedrock
    region: us-east-1
    auth: {}
    assume_role:
      role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
models:
  - id: claude-opus-restricted
    upstream_model:
      bedrock-isolated: us.anthropic.claude-opus-4-8
開発者ごとの AWS のコストの帰属
  • 既定では全 Bedrock リクエストを1つの資格情報で署名するので、AWS からは全開発者が1つの IAM 主体に見える
  • assume_role に session_name: email を足すと、開発者ごとに1時間に1回 sts:AssumeRole を呼び、セッション名をその開発者のメールにする。各開発者のリクエストは、その人用に引き受けたロールのセッションで AWS に届く(ロールはゲートウェイと同じアカウントでもよい。v2.1.281 以降)
  • session_name は、検証済みのどのクレームを AWS の RoleSessionName にするかを選ぶ(email か sub)
  • ASCII の英数字と _+,.@- 以外の文字は、UTF-8 のバイトごとに =XX(16進)にする。64文字を超えたら、接頭辞とハッシュに縮める
  • そのクレームが無いトークンの開発者のリクエストは、この上流を通らない。運用ログが、sub への切り替えか oidc.email_claim の設定を案内する
  • STS の呼び出しは、アクティブな開発者1人につき、レプリカごとに1時間1回
  • ゲートウェイ自身が行う、中断したリクエストのトークン数え(と1トークンの代わりのリクエスト)は、共有の claude-apps-gateway セッションで署名される。AWS ではそのぶんが開発者でなく claude-apps-gateway に付く。厳密に開発者ごとに分けるなら、並べるすべての Bedrock の上流に session_name つきの assume_role を書く

Amazon Bedrock の Mantle エンドポイント#

mantle プロバイダは、推論を Amazon Bedrock の Mantle エンドポイントへ送ります。ゲートウェイのサーバーで Claude Code v2.1.283 以降が要ります。それより前のゲートウェイは起動時に拒むので、足す前にすべてのレプリカを上げます。

次の例は、Mantle を先頭に置き、models に無いモデルは後ろの Amazon Bedrock の上流に任せます。

yaml
upstreams:
  - provider: mantle
    region: us-east-1
    models: [claude-opus-4-7, claude-haiku-4-5]   # 必須
    auth: {}                           # AWS の既定の資格情報チェーン
  - provider: bedrock
    region: us-east-1
    auth: {}

mantle の上流に固有のフィールドです。

フィールド 必須 内容
region はい AWS のリージョン。エンドポイントは https://bedrock-mantle.<region>.api.aws/anthropic と導かれる
models はい AWS アカウントが Mantle で許可されたモデル。クライアントが送る名前(claude-haiku-4-5 など)で書く。ここにあるモデルだけがこの上流へ行き、ほかは次の上流へ飛ばす
auth いいえ Amazon Bedrock の上流の auth ブロックと同じキーで、同じ規則
base_url いいえ 導かれたエンドポイントの上書き。末尾の /anthropic のパスは残す

上流の AWS の ID に、推論とトークン数えのための Mantle 固有の IAM アクションを許可します(公式の Amazon Bedrock のドキュメントの「Mantle エンドポイントを使う」にある)。

ゲートウェイが知らない Mantle のモデル ID は、トップレベルの models: に項目を足し、その upstream_model にこの上流の名前とその ID を対応づけます。そのうえで、その項目の id をこの上流の models にも書きます。

bedrock の上流の guardrail と assume_role の設定は、Mantle が受けるリクエストには及びません。

  • guardrail:Mantle へ送るリクエストにゲートウェイは Bedrock の guardrail を当てない。そのため、mantle の上流を並べたうえで、いずれかの bedrock の上流が guardrail を書いていると、起動を拒む
  • assume_role:mantle の上流は assume_role を取らない。Mantle が受けるリクエストは mantle の上流自身の auth の資格情報で送られ、開発者ごとの AWS のコスト帰属の対象にならない

Mantle 自身のエラー応答の意味は、公式の Amazon Bedrock のドキュメントの「Mantle エンドポイントのエラー」にあります。

Claude Platform on AWS の上流#

  • Anthropic が AWS のインフラで運営するファーストパーティの API(aws-external-anthropic.<region>.api.aws)
  • ファーストパーティのモデル ID を使い、anthropic-beta をそのまま通し、count_tokens もあるので、Bedrock 向けの変換は要らない
  • anthropicAws プロバイダには Claude Code v2.1.198 以降が要る
yaml
upstreams:
  - provider: anthropicAws
    region: us-east-1
    workspace_id: wrkspc_...
    auth:
      api_key: ${ANTHROPIC_AWS_API_KEY}
フィールド 必須 内容
region はい AWS のリージョン(小文字・数字・ハイフン)。エンドポイント https://aws-external-anthropic.<region>.api.aws を導く
workspace_id はい 毎回のリクエストにヘッダーで送る(プラットフォームの要件)
auth.api_key いいえ プラットフォームの API キー。x-api-key で送る
auth.aws_access_key_id / auth.aws_secret_access_key いいえ 明示の SigV4 の資格情報。片方だけだと起動に失敗する。auth.aws_session_token も受ける
base_url いいえ 導いたエンドポイントを上書きする
  • 認証は API キーか SigV4 のどちらか(ベアラートークンではない)。両方あれば auth.api_key が優先
  • 空の auth ブロックは、AWS SDK の既定の資格情報チェーンを使う
  • プラットフォームは Bedrock とは別の AWS アカウントで動き、自分のサービス名 aws-external-anthropic で SigV4 に署名するので、Bedrock 用の IAM ロールでは認可されない
  • ファーストパーティのモデル ID を解決するので、内蔵カタログは models: なしでここへ振れる。models: を整えるなら、項目のキーを anthropicAws:、値をファーストパーティの ID にする

Vertex AI(Google Cloud Agent Platform)の上流#

yaml
upstreams:
  - provider: vertex
    region: us-east5
    project_id: example-prod
    auth: {}
設定 内容
認証 空の auth は Application Default Credentials。サービスアカウントの JSON キーも使えるが非推奨
region: global グローバルエンドポイントを使い、Google が使えるリージョンへ振る
IAM サービスアカウントに、プロジェクトの roles/aiplatform.user(か aiplatform.endpoints.predict を持つカスタムロール)。aiplatform.googleapis.com を有効にする
モデルの利用許可 Model Garden でプロジェクトの Claude モデルを有効にする(特定のリージョンで公開される)
GKE(Workload Identity) GCP のサービスアカウントをゲートウェイの Kubernetes のサービスアカウントに結びつけ、iam.gke.io/gcp-service-account を注釈する
Cloud Run / GCE サービスのサービスアカウントを、roles/aiplatform.user を持つものにする
base_url Private Service Connect 向けに aiplatform のエンドポイントを上書きする
  • ADC の出どころ:GOOGLE_APPLICATION_CREDENTIALS・GCE のメタデータ・GKE の Workload Identity
  • JSON キーは auth: { service_account_json: /secrets/sa.json } と書く(中身ではなくファイルのパス)
  • region: global なら、リージョンごとのモデルの提供状況を追わずに済む。個別のリージョンを書くと、全リクエストがそこに固定される

Foundry の上流#

yaml
upstreams:
  - provider: foundry
    resource: example-foundry
    auth: { use_azure_ad: true }
  • use_azure_ad: true は DefaultAzureCredential(AKS・ACI・App Service の Managed Identity、Azure CLI、環境の資格情報)で解決する
  • API キーも使えるが、プロジェクト全体のキーで、自動では回らない
  • エンドポイントは resource: から導く
設定 内容
RBAC ゲートウェイの ID に、Foundry のリソースの Azure AI User か Cognitive Services User を付ける
デプロイメント 管理者が付けたデプロイメント名で呼ぶので、標準のモデル ID をデプロイメント名に対応づける models: を足す
AKS(Workload Identity) User-Assigned Managed Identity をクラスターの OIDC の発行者にフェデレーションし、ゲートウェイのサービスアカウントに結びつける。use_azure_ad: true が WorkloadIdentityCredential で拾う
ACI / App Service リソースで、システム割り当てかユーザー割り当ての Managed Identity を有効にする
それ以外 auth: { api_key: "${FOUNDRY_API_KEY}" }({ } の中の ${…} は引用符で囲む)

上流へのリクエストに固定のヘッダーを付ける#

  • 使う場面:プロバイダの前のプロキシが、ヘッダーで振り分けや帰属をするとき。上流に headers: を書くと、その上流へのリクエストに固定のヘッダーが付く
  • 版:サーバーで v2.1.277 以降。古いゲートウェイはキーがあると起動を拒否するので、全レプリカを上げてからキーを足し、戻す前にキーを外す
  • 届く先:base_url のサーバー(未設定ならプロバイダのエンドポイント)。プロキシが外さなければ、プロバイダにも届く
yaml
upstreams:
  - provider: vertex
    region: us-east5
    project_id: example-prod
    base_url: https://upstream-proxy.internal.example.com
    auth: {}
    headers:
      x-source: claude-apps-gateway
      x-proxy-token: ${PROXY_TOKEN}
  • 値は、前後に空白の無い印字できる ASCII。数字・true・false は、YAML が文字列として読むよう引用符で囲む。秘密は ${VAR} か ${file:/path} で読む(${VAR} が空に解決されると起動しない)
  • どのプロバイダにも使える。各上流は自分の headers: だけを送る
  • 付くリクエスト:/v1/messages(ストリームかどうかを問わない)・/v1/messages/count_tokens・ほかの上流からフェイルオーバーしてきたリクエスト(この上流の headers: だけ)
  • 付かないリクエスト:Bedrock の、中断したリクエストのための CountTokens・Workload Identity Federation のトークン交換
  • SigV4 で署名する Bedrock と Claude Platform on AWS では、ヘッダーが署名に含まれる。プロキシは変えずに通す
  • 予約名を使うと起動を拒否し、エラーがヘッダーを名指しする。予約名は authorization・x-api-key・host・content-type・user-agent と、anthropic-・x-goog-・x-amz-・x-amzn- で始まる名前

複数の上流#

  • 同じプロバイダを、別の name: で複数並べられる(別のリージョン・別の認証チェーンによる別アカウント・プロビジョンドスループットとオンデマンド・クラウドをまたぐ予備)
  • どのリクエストも最初の上流から試す。後ろへ進むのは、前の上流がすべて失敗したか、要求のモデルを出さないときだけ
  • 429 は上流ごとの容量なので、プロビジョンドスループットが尽きるとオンデマンドへ移る
  • 404 も上流ごとのモデルの有無なので、モデルを有効にしていない上流があっても後ろの上流が応えられる。要求のモデルを解決できない上流は、通信せずに飛ばす
  • 失敗した上流を覚えないので、上流が落ちているあいだは、届くリクエストごとに失敗を待ってから次へ進む。待ちを抑えるのは Anthropic API の上流の timeouts.upstream_ttfb_ms だけで、ほかのプロバイダでは応答が始まるまで最大1時間待つ
yaml
upstreams:
  - name: bedrock-pt
    provider: bedrock
    region: us-east-1
    auth: {}
  - name: bedrock-od
    provider: bedrock
    region: us-west-2
    auth: {}
  - name: anthropic-fallback
    provider: anthropic
    auth:
      api_key: ${ANTHROPIC_API_KEY}

models:
  - id: claude-opus-4-8
    label: Claude Opus 4.8
    upstream_model:
      bedrock-pt: arn:aws:bedrock:us-east-1:111111111111:provisioned-model/abcdef
      bedrock-od: us.anthropic.claude-opus-4-8
      anthropic-fallback: claude-opus-4-8
やりたいこと 方法
別のリージョン リージョンごとに Bedrock の上流を1つ(それぞれの region:)
別のアカウント アカウントごとに Bedrock の上流を1つ
プロビジョンドスループット models: で、その上流の名前にプロビジョンドスループットの ARN を対応づける
VPC / FIPS のエンドポイント 上流の base_url: を、VPC エンドポイントか FIPS エンドポイントの URL にする
モデル単位の振り分け 内蔵でないカスタムのモデル id だけが、upstream_model: のマップに無い上流を飛ばす
  • 別のリージョン:auto_include_builtin_models: true なら、クロスリージョン推論プロファイルへ自動で振られる。リージョンを固定した配備には models: を使う
  • 別のアカウント:既定のチェーン(auth: {})はポッドの ID を使う。2つ目のアカウントには、短命の資格情報で届く assume_role を足すか、auth: に明示の資格情報かベアラートークンを書く
  • プロビジョンドスループット:ほかの上流はオンデマンドの ID のままなので、プロビジョンドの容量を使い切ってからフェイルオーバーする
  • モデル単位の振り分け:カスタムのモデル id(内蔵の Claude のモデルでないもの)だけが、upstream_model: のマップに無い上流を飛ばす。mantle の上流は、その models フィールドに書いたモデルでだけ試す。ほかの上流では、内蔵のモデルを順に試し、マップに項目が無ければプロバイダの既定の ID を使う。内蔵のモデルでは、マップは「試すかどうか」ではなく「渡す ID」を変える。ID を拒否した上流は、ほかのエラーと同じフェイルオーバーの規則に従う
  • クラウドのプロバイダ間や直接の Anthropic API へのフェイルオーバーでは、リクエストに適用される契約・地域・その他の条件が変わる
  • CLI は、どの上流が応えるかに関係なく同じ機能の絞り込みを当ててゲートウェイに送る。フェイルオーバー先が受け付けないフィールドが送られることはない

admin#

省略可。Anthropic の公開 Admin API と同じ形の /v1/organizations/spend_limits と、/v1/messages での開発者ごとの支出の強制を有効にします。上限の決め方と強制のしくみは、下の「支出上限」にあります。

yaml
admin:
  write_keys:
    - { id: terraform, key: "${GATEWAY_ADMIN_WRITE_KEY_TF}" }
  read_keys:
    - { id: reporting, key: "${GATEWAY_ADMIN_READ_KEY}" }
  admin_groups: [platform-finops]
  blocked_message: request an increase at https://go.example.com/claude-limits
フィールド 必須 内容
write_keys いいえ {id, key} の配列。一致する x-api-key で、支出上限の一覧・設定・削除ができる
read_keys いいえ {id, key} の配列。読み取り専用(一覧・ID での取得・/effective と /audit を含むすべての GET)
admin_groups いいえ IdP のグループ名。groups クレームにこれを含むゲートウェイの JWT に、読み書きの完全な管理者アクセスを与える
blocked_message いいえ ブロックされた開発者が見る 429 billing_error の末尾に、そのまま足す文。未設定なら既定のメッセージだけ
audit_retention_days いいえ admin_audit の行の保持日数。既定 365
spend_retention_months いいえ spend のカウンターの行の保持月数。既定 13
identity_retention_days いいえ principal_emails の行(個人情報)を、最後に見た時点から残す日数。既定 90
group_limit_mode いいえ min(既定)か max。上限のある複数のグループに属するとき、最も厳しいものか最も緩いものかを選ぶ
  • キーの値は32文字以上。id は read_keys と write_keys を通して一意で、監査ログに admin-key:<id> と残る。回すときは新しいキーを足し、クライアントを替えてから古いものを外す
  • admin_groups は人間の管理者向け(機械には API キー)。監査では oidc:<sub> になる。空の項目があると起動時に止まる
  • blocked_message には、URL や Slack のチャンネルなど、上限の引き上げの頼み方を全文で書く
  • spend_retention_months の既定 13 は、前年同月と比べられるよう、1年に今月を足した値
  • principal_emails は開発者のメール・表示名・グループを持つ。identity_retention_days は支出の保持より意図して短く、無効にした ID は匿名の支出のカウンターより先に消える
  • group_limit_mode は、強制と /effective の両方で使う

enforcement#

フィールド 必須 内容
fail_closed_on_error いいえ Postgres の障害時に、支出の強制をどちらへ倒すか。既定 false(通す側)
  • false:推論は止まらない
  • true:止める側。上限を超えた開発者に加え、ストアに届かないあいだは全員がブロックされる
  • admin: ブロックが要る。支出の強制は admin があるときだけ動き、admin なしで true にすると起動を拒否する

pricing#

  • 支出のメーターに、USD の定価でなく契約の料金を使わせる。上限と /effective も契約の料金になる
  • 金額は USD のまま。請求書ではなく見積り
  • サーバーで Claude Code v2.1.227 以降(古い版は未知のキーとして起動を拒否する)
  • admin: ブロックか、v2.1.268 以降ならポリシーを1つ以上持つ managed: ブロックが要る(どちらも無いと読む側がいないので、起動を拒否する)
yaml
pricing:
  multiplier: 0.85
  overrides:
    - upstream: bedrock-eu
      model: claude-sonnet-4-6
      input: 3.30
      output: 16.50
      cache_read: 0.33
      cache_write: 4.125
フィールド 必須 内容
multiplier いいえ メーターが課金するすべての金額にかける倍率。既定 1。0より大きく10以下
overrides いいえ {upstream, model, input, output, cache_read, cache_write} の行。単位は100万トークンあたりの USD
  • multiplier は定価にも上書きの料金にもかかる。0.85 なら価格の85%
  • 1より大きい値は割り増し(v2.1.271 以降のサーバー。社内チャージバックの料率など)。上流の請求は変わらない。admin: があれば支出上限にも効き、開発者は早く上限に届く。起動時に警告が出る
  • overrides の4つの料率はすべて必須で、それぞれ0より大きく10000以下

行の当たり方です。

  • 行は、upstream(upstreams[].name)が model を出すときの定価を置き換える。fast mode の高い料金も置き換わるので、fast と標準は同じ4つの料率になる
  • claude-sonnet-4-6 のような内蔵の ID は、models[].id と同じ照合で、そのモデルとして値が付く形(日付つき・リージョン付きの Bedrock の形・Vertex AI の形)をすべて覆う
  • 別名や推論プロファイルの ARN のようなほかの文字列は、クライアントが送った ID か上流へ送る文字列に、大文字小文字を区別せず一致する
  • 重なるときは、最初の行ではなく最も具体的な行を使う。上流へ送る正確なモデル文字列の行、クライアントが送った正確な ID の行、内蔵のモデルを名指しする行の順
  • 未知の上流名は起動に失敗する。1つの上流に同じモデルを名指しする2行(1つの内蔵モデルの2つの綴りも含む)も同じ。要求できるどのモデルにも使われない行は、起動時に警告する
  • ウェブ検索のリクエストは定価の1件 $0.01 のまま。倍率はそれにもかかる
  • リージョンごとの料率にするなら、リージョンごとに名前を付けた上流を置き、上流ごとに1行書く

署名済みのクライアントへ料率を送る#

  • v2.1.268 以降のサーバーは、pricing の料率を、配る managed のポリシーに modelPricing の managed 設定として入れる
  • ポリシーに一致した開発者は、/usage・ステータスライン・OpenTelemetry で、各モデル ID を出す最初の上流の料率を見る(クライアントは Claude Code v2.1.242 以降が当てる)。一致しない開発者は managed settings を受けないので定価のまま
  • 割り増しの multiplier が見えるのは、開発者側が v2.1.271 以降のとき。古いクライアントは1より大きい multiplier を無視し、割り増し抜きのコストを出す
  • ゲートウェイが足すもの:ポリシーの cli ブロックに modelPricing がまだ無ければ、multiplier と、クライアントが要求できる各モデル ID について、それを出す最初の上流の上書きの行。フェイルオーバー先の上流だけが課金する料率は、ゲートウェイの中にだけ残る
  • 1つのポリシーだけ外す:そのポリシーの cli ブロックで modelPricing を {} にすると、その開発者は定価のまま
  • ポリシー自身の料率を使う:cli ブロックが自分の multiplier か overrides を持つ modelPricing を書いていれば、それが丸ごと保たれ、ゲートウェイは料率を足さない

models と auto_include_builtin_models#

/v1/models で見せ、上流ごとにモデル ID を変換するための、管理者が整えるモデル一覧です。米国以外の Bedrock のリージョン・Bedrock のプロビジョンドスループットの ARN・Foundry のデプロイメント名では必須です。

yaml
auto_include_builtin_models: true   # false なら下の一覧のモデルだけを出す
models:
  - id: claude-opus-4-8
    label: Claude Opus 4.8
    upstream_model:
      anthropic: claude-opus-4-8
      bedrock: us.anthropic.claude-opus-4-8
      foundry: your-opus-deployment-name
  • upstream_model の各キーは、設定した上流の name(既定はプロバイダ名)と一致させる。どの上流にも一致しないキーは起動に失敗するので、使わないプロバイダの行は書かない
  • description(省略可)は、表示するクライアントに出す説明

managed#

  • IdP のグループかメールのドメインで引く、ロールベースのポリシー
  • ポリシーは上から評価して最初に一致したものを使い、それを match: {} のキャッチオールの基底に重ねる
  • ユーザーごとに GET /managed/settings で配る(ETag/304 のキャッシュつき)
yaml
managed:
  policies:
    - match: { groups: [eng-contractors] }
      cli:
        availableModels: [claude-sonnet-4-6]
        permissions: { deny: ["WebFetch", "WebSearch"] }
    - match: {}
      cli:
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        # /model の Default を、各ポリシーの一覧の中で解決させる。
        # eng-contractors のポリシーは enforceAvailableModels を継承する
        enforceAvailableModels: true

最後に置く match: {} は基底の層で、ほかのポリシーは自分で書かないキーをここから継承します。重ね方はキーの型で決まります。

キーの型 該当するキー 合成
許可リスト availableModels・permissions.allow 個別のポリシーの一覧が、基底を丸ごと置き換える
拒否リストとフックの配列 permissions.deny・permissions.ask・disabledMcpjsonServers・deniedMcpServers・blockedMarketplaces・すべての hooks のイベント型の配列 基底とポリシーの和集合(組織全体の deny や監査のフックが、ロール別の上書きで落ちない)
レコード型のキー env・modelOverrides・skillOverrides 浅く合成する(ロール別の env は書いたキーだけを上書きし、残りは基底から継承)
  • availableModels は /v1/messages でもサーバー側で強制する。クライアントが何を送っても、許可しないモデルは 400。空の一覧は全モデルを拒否する。確認は、開発者が選ぶ前のセッションの開始モデルにも及ぶ(下の「ポリシーが許すモデルでセッションを始める」)
  • 中継の前に model の値そのものを検証し、不正な値は上流へ届かない。無いか空なら 400 の model is required(サーバー v2.1.228 以降)、文字列でなければ 400 の model must be a string(サーバー v2.1.221 以降)

一致のしかたです。

マッチャ 挙動
match: {} 認証されたすべてのユーザーに一致する。まずこれを置き、グループ別のポリシーはあとで上に足す
match: { groups: [a, b] } JWT の groups クレームがどれかのグループを含めば一致する。大文字小文字を区別するので、IdP の綴りと正確に合わせる
match: { email_domain: example.com } JWT の email クレームの最後の @ より後ろに、大文字小文字を区別せず一致する。1つのポリシーに1つのドメイン
match: { groups: [a], email_domain: example.com } 両方の条件が一致したときだけ

どのポリシーにも一致しない認証済みのユーザーは、ゲートウェイの既定(カタログの全モデル・managed settings なし)になります。確実な既定が欲しければ、最後に match: {} を置きます。

補足

ゲートウェイは自分のユーザーのディレクトリを持ちません。リクエストごとに IdP のトークンで認可し、groups クレームからグループを読んでポリシーを当てます。名簿も事前のアカウント作成も無いので、SCIM のエンドポイントもありません。ユーザーとグループのライフサイクルは、IdP 自身の SCIM のプロビジョニングか、専用の ID ガバナンスの製品で管理します(Claude のアカウント自体の SCIM のプロビジョニングは、Claude for Enterprise の機能)。

変更が届くまでの時間は2通りあります。

  • ポリシーの中身:編集して再デプロイすると、接続中のクライアントには次の managed settings のポーリング(1時間以内)で届く(次の起動でだけ効く変更を除く)
  • グループのメンバーシップ:ユーザーのグループを変えると一致するポリシーが変わり、次のセッションの再発行(次のサイレントな更新)で効く。最長で session.ttl_hours

ポリシーが許すモデルでセッションを始める#

availableModels が Claude Code の既定のモデルを含まないと、開発者が一覧のモデルを選ぶ(/model など)まで、セッションは 400 を返します。ゲートウェイのセッションでの既定は、opus のエイリアスが指す Opus のモデルで、availableModels だけでは変わりません。

直すには、同じ cli ブロックに enforceAvailableModels: true を足し、一覧の中身を確かめます。

  • sonnet のようなエイリアスか、claude-sonnet-4-6 のような内蔵の ID がある:セッションはそのどれかで始まり、/model の Default の選択もそれになる
  • エイリアスも内蔵の ID も無い:セッションは内蔵の既定で始まりうるので、そのポリシーの cli ブロックで model も一覧の ID のどれかにする

次のポリシーは、models が定義するカスタム ID を1つだけ許可し、その ID でセッションを始めます。

yaml
managed:
  policies:
    - match: { groups: [restricted-projects] }
      cli:
        availableModels: [claude-opus-restricted]
        enforceAvailableModels: true
        model: claude-opus-restricted

起動時に止める matcher の値#

起動時に、すべてのポリシーの match ブロックと admin_groups の一覧を確かめ、次の値があればフィールドを名指しするエラーで止まります。

  • 空の groups の一覧
  • groups か admin_groups の空の項目
  • 空の email_domain
  • @・空白・カンマを含む email_domain(値を trim し、先頭の @ を1つ除いてから確かめる。example.com のように裸のドメインで書く)

v2.1.232 より前は、これらの値でも起動し、次のように動いていました。

  • 空の email_domain:ドメインの確認を省き、groups の無いポリシーが認証済みの全員に一致した
  • 空の groups:誰にも一致しなかった
  • @・空白・カンマを含む email_domain:誰にも一致しなかった
  • groups か admin_groups の空の項目:IdP の groups クレームにも空の項目があるユーザーにだけ一致した(admin_groups なら管理者アクセスを与えた。admin_groups に空の項目が無かったなら、この経路で管理者になった人はいない)

cli に入れるもの#

  • 各 cli は、MDM や /etc/claude-code/managed-settings.json で配るのと同じスキーマの managed-settings.json の文書を、YAML で書いたもの
  • CLI はこれを managed の階層(ユーザーやプロジェクトの設定より上)に当てる(サーバー管理設定の代わり)。そのため、OS レベルのポリシーの源でしか効かない設定(policyHelper・wslInheritsWindowsSettings)は無視する
  • 起動時に各文書を CLI の設定スキーマで検証し、未知のトップレベルのキーがあれば、すべて挙げて起動に失敗する
  • スキーマの意図的に開いた部分(env・pluginConfigs・permissions の下のキー)は、どんな値も受ける(新しいクライアントが知る項目が、ゲートウェイのスキーマに無いことがあるため)
  • 検証はゲートウェイに入っている版のスキーマで行う。新しい Claude Code で入ったトップレベルのキーを使うなら、先にゲートウェイを上げる
  • 新しいポリシーは、広げる前に1台のクライアントで試す
  • cli キーの旧名 settings も別名として受けるが、新しい配備では cli を使う
キー 強制する側 効果
availableModels ゲートウェイと CLI モデルの許可リスト。/v1/messages でも確かめるので、改造したクライアントでも迂回できない
permissions.allow / .deny CLI ツールとコマンドの規則(権限ルール)
permissions.disableBypassPermissionsMode CLI disable で、権限の確認を飛ばす bypassPermissions モードと --dangerously-skip-permissions を止める
allowManagedPermissionRulesOnly CLI true で、権限ルールの源を managed settings だけにする
env CLI CLI のプロセスに足す環境変数(テレメトリ・自動更新・モデル名の上書き)
hooks CLI 組織全体のフック(フックのリファレンス)
managedMcpServers CLI 一致する開発者全員に、自分で足すサーバーとは別に配るリモートの MCP サーバー(http と sse だけ)。サーバーとクライアントで v2.1.259 以降
yaml
managed:
  policies:
    - match: {}
      cli:
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        enforceAvailableModels: true
        permissions:
          deny:
            - "WebFetch"
            - "Read(./.env)"
            - "Read(./secrets/**)"
          disableBypassPermissionsMode: disable
        allowManagedPermissionRulesOnly: true
        env:
          DISABLE_UPDATES: "1"
        hooks:
          PostToolUse:
            - matcher: "Edit|Write"
              hooks:
                - { type: command, command: /usr/local/bin/audit-edit.sh }
  • DISABLE_UPDATES はバックグラウンドと手動の更新を止め、DISABLE_AUTOUPDATER はバックグラウンドの更新だけを止める
  • フックのコマンドは、ゲートウェイではなく開発者のマシンで動く。ポリシーに含まれるすべてのクライアントの OS に、そのパスが要る
  • managedMcpServers は、古いクライアントでは無視される

ネットワーク越しに届く設定なので、次のものを当てる前に、CLI は開発者ごとにセキュリティの承認ダイアログを出します。

  • hooks
  • 開発者の承認が要る env の変数(プロキシやベース URL の変数など)
  • apiKeyHelper や statusLine など、シェルを実行する設定
  • サンドボックスのバイナリの設定 sandbox.bwrapPath・sandbox.socatPath・sandbox.ripgrep
  • 通信の傍受・資格情報の注入・分離を弱めるサンドボックスの設定(sandbox.network.tlsTerminate やプロキシのポートの設定など)

ダイアログの決まりです。

  • env のうち、モデルの選択の設定や数値の上限などはダイアログなしで当てる。それ以外は開発者の承認が要る(v2.1.218 より前は、承認なしで当たる変数が少なかった)
  • 空でないプロキシ・ベース URL・OTEL_EXPORTER_OTLP_ENDPOINT の値は、常に承認が要る。telemetry.forward_to を書くとゲートウェイが OTEL_EXPORTER_OTLP_ENDPOINT を押すので、各対話クライアントにダイアログが出る
  • 目的は、侵害された・悪意のあるゲートウェイから開発者のマシンを守ること。開発者から組織を守るためのものではない
  • 非対話の実行(claude -p・Agent SDK)はダイアログを出せないので、押された設定をその実行にだけ当て、承認済みとは記録しない(v2.1.207 より前は承認済みとして保存し、後の対話セッションでダイアログが出なかった)
  • 開発者が拒否すると、ポリシーを当てずにそのセッションを終える
  • 広く当たるポリシーに、新しいフックやダイアログの要る env を足すと、一致する全開発者の対話セッションでダイアログが出る(動いている対話セッションは次の毎時のポーリングで、それ以外は次の対話の起動で)

ターミナルのセッションのコンテキストウィンドウ#

/login でサインインしたターミナルのセッションは、Opus 4.7 以降・Sonnet 5 以降・Fable のモデルで 1M のコンテキストウィンドウを使います。モデル ID に [1m] の接尾辞は要らず、セッションは約 967K トークンで compact します。開発者のマシンの Claude Code が v2.1.287 より前だと、Opus と Fable のモデルは、モデル ID が [1m] で終わらない限り 200K のウィンドウとみなされます。

ターミナルのセッションを 200K の境界で compact させたいときは、ポリシーの env に auto-compact のウィンドウを設定します。

yaml
managed:
  policies:
    - match: {}
      cli:
        env:
          CLAUDE_CODE_AUTO_COMPACT_WINDOW: "200000"

Claude Code はこの変数を、承認のダイアログを開発者に見せずに適用します。変数は、[1m] で終わるモデル ID を含むすべてのモデルに効きます。

1M のコンテキストそのものを止めたいときは、同じ env に CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" を書きます。Claude Code はすべてのモデルを 200K のウィンドウとして扱います。対話のセッションでは、この変数が効く前に、開発者がそれぞれ承認のダイアログで承認します。

ポリシーの中の MCP サーバー#

  • 一致するクライアントへ MCP サーバーを配るには、そのポリシーの cli ブロックに managedMcpServers を書く(サーバーとクライアントで v2.1.259 以降)
  • 起動時に、クライアントと同じ規則で各項目を検証し、通らない項目があれば名指しして起動を拒否する
  • gateway.yaml の ${VAR} は、起動時(項目の検証より前)に環境から解決される。一致するすべてのクライアントが展開後の値そのものを受け取り、読める
  • .mcp.json の書き方の mcpServers は cli ブロックでは拒否され、起動のエラーが managedMcpServers を使うよう示す(v2.1.259 より前は、cli ブロックの MCP サーバーの定義はすべて拒否された)

Claude Desktop の上書き(desktop)#

  • 組織が Claude Desktop も配るなら、同じゲートウェイが両方に応える。Desktop の managed 設定の bootstrapUrl を <listen.public_url>/user/bootstrap に向けると、Desktop は同じデバイスコードのサインインをこのゲートウェイで行い、応答から設定を取る
  • サーバーで v2.1.203 以降。明示の opt-in が要り、ユーザーに一致するポリシーに desktop キーが無ければ /user/bootstrap は 404 を返す
  • 空の desktop: {} で、そのポリシーが opt-in になる。match: {} の基底に desktop を書くと、それを継承するすべてのポリシーが opt-in になる
  • 監査ログには、リクエストごとに desktop_bootstrap.serve か desktop_bootstrap.denied が残る

応答の多くは、一致したポリシーの cli ブロックとトップレベルの設定から導かれます。

項目 導き方
モデル一覧 availableModels から。各モデルの 1M のコンテキストの選択肢は下の「Claude Desktop の拡張コンテキスト」
無効にするツール ツール名だけの permissions.deny の項目から
外向きの許可リスト sandbox.network.allowedDomains から
OTLP エンドポイントと、サインイン中のユーザーの ID 属性 ゲートウェイ自身を指すエンドポイント。telemetry.forward_to と listen.public_url の両方があるときに入る
  • 無効にするツール:desktop ブロックに disabledBuiltinTools を書くと、それと導いた一覧の和集合になる。さらに無効にはできるが、permissions.deny で無効にしたものを有効に戻すことはできない
  • 外向きの許可リスト:desktop ブロックに coworkEgressAllowedHosts を書くと、導いた一覧の代わりにその値を使う
  • OTLP:ゲートウェイは受けた出力を forward_to の宛先へ中継する。Desktop は信号を1つのエンコーディングで出す(既定は http/protobuf。ポリシーの env で OTEL_EXPORTER_OTLP_PROTOCOL かその信号別の変数を http/json にしたときは http/json)。サーバー v2.1.261 より前は応答が常に http/json を設定していたので、protobuf しか受けないコレクターは Desktop の出力を拒否した
  • desktop ブロックに disabledBuiltinTools・coworkEgressAllowedHosts・Desktop 自身の managedMcpServers を書くには、サーバーで v2.1.232 以降(Desktop の managedMcpServers はオブジェクトでなく配列)
  • Desktop に相当するものが無いキー(hooks、Bash(npm *) のようなスコープ付きの権限ルールなど)は、bootstrap の応答から省く

cli の隣に desktop ブロック(省略可)を書くと、Desktop の設定を直接書けます。

  • 書くのは Claude Desktop の managed 設定のリファレンスにある設定で、平らなキー名で書く。bootstrapUrl のように Desktop が MDM やローカルのファイルからしか読まないキーは書かない(起動時に拒否される)
  • v2.1.232 より前は、chatTabEnabled・disableAutoUpdates など11個の機能ゲートのキーの固定リストだけを受け、ほかは起動時に拒否した。v2.1.227 より前は chatTabEnabled と chatAdvancedFileAnalysisEnabled も拒否した
  • キーはすべて省略可で、省いたキーには Desktop 自身の既定が当たる
  • 各 desktop ブロックは Desktop 自身と同じ設定のスキーマで検証され、間違いはキーを名指しするエラーとして起動時に出る

起動に失敗する desktop ブロックです。

  • 未知のキーがある
  • Desktop が拒否するか黙って落とす値を持つキーがある(空の値や、入れ子の項目の中の綴りミスなど。v2.1.260 より前は、managedMcpServers や orgPluginSettings の項目の入れ子のオブジェクトの中の綴りミスを、起動を止めずに黙って落としていた)
  • ゲートウェイ自身が計算するキー(推論の接続・モデル一覧・OTLP の中継)がある。これらは upstreams・models・telemetry の forward_to で書く
  • 現行のキーの旧名がある(エラーが正しいキー名を示す)

非推奨の値や項目の形(transport の無い managedMcpServers の項目など)は、起動したうえで置き換えを示す警告を出します。

版の決まりです。

  • 検証はゲートウェイに入っている版のスキーマで行う。新しい Claude Desktop で入った設定を配るには、先にゲートウェイを上げる
  • 例:userPluginMarketplacesEnabled と userPluginUploadsEnabled は、サーバーで v2.1.260 以降、メンバーのマシンで Claude Desktop 1.37937.0 以降
  • blockReadsOutsideWorkingDirectories・disableBypassPermissionsMode・configRecheckIntervalMinutes・sshClientPath、microsoftAuthBroker の required の値、Microsoft 365 の managedMcpServers の項目の continuousAccessEvaluation フィールドは、サーバーで v2.1.281 以降
  • required の値は、それを知らない古い Desktop が disabled と読む。全メンバーの Desktop が対応してから書く
  • 各キーを最初に読む Desktop の版は、Claude Desktop の managed 設定のリファレンスにある
  • orgPluginSettings は、Claude Desktop 1.15200.0 以降が読む配列の形で配られる(古い Desktop は配列を無視し、プラグインのツールのポリシーを強制しない)

継承の決まりです。

  • desktop ブロックで書かないキーは、match: {} の desktop ブロックから埋める(cli を基底から埋めるのと同じ)
  • 基底とロールの両方に disabledBuiltinTools か builtinToolPolicy を書くと、基底の制限が残る。disabledBuiltinTools は両方の和集合。builtinToolPolicy は、基底でツールを allow 以外にしていれば、ロールが同じツールを allow にしても基底の値のまま
  • ほかのキーは、ロールのポリシーで書けばその値になる。配列と、banner のような入れ子のオブジェクトは丸ごと置き換わる(ロールで banner.text を書くと、基底の banner.backgroundColor は落ちる)
  • Desktop を配らないなら、ポリシーから desktop を外す(全ユーザーに /user/bootstrap が 404 を返す)
yaml
managed:
  policies:
    - match: { groups: [eng-contractors] }
      cli:
        availableModels: [claude-sonnet-4-6]
      desktop:
        isLocalDevMcpEnabled: false
        disableAutoUpdates: true
        banner: { text: "Contractor build: internal use only" }

Claude Desktop の拡張コンテキスト#

Claude Desktop をゲートウェイから配るとき、Desktop のモデルピッカーは、1M のコンテキストウィンドウで動かせる一覧のモデルごとに 1M の選択肢を出します。対象は Claude Opus 4.6 以降・Claude Sonnet 4.6 以降・Fable のモデルです。選択肢は、そのモデルの [1m] の変種です(モデルの「拡張コンテキスト」を参照)。ゲートウェイのサーバーで Claude Code v2.1.284 以降が要ります。

models の項目に 1M の選択肢が付かないのは、次のときです。

  • その項目を出せる上流のどれかが、1M に対応しないモデルへ対応づけている(フェイルオーバーでだけ届く上流を含む)
  • id も upstream_model の値も、Claude のモデルを指していない(アプリケーション推論プロファイルの ARN へ向けたカスタムのエイリアスなど)

ピッカーの出し方は、次のどちらかで変えます。

  • 1M の選択肢で始めさせる:ポリシーの desktop ブロックに modelPrefer1mContext: true を書く。まだモデルを選んでいないユーザーは、一覧の先頭のモデルに 1M の選択肢があれば、それで始める。すでに選んだユーザーは選択を保つ
  • 選択肢を手で出す:ゲートウェイのサーバーが v2.1.284 より古いか、項目が Claude のモデルを指していないときにする。models に同じモデルを2回、素の ID と末尾に [1m] を付けた ID で書き、どちらにも同じ upstream_model のマップを付ける。Claude Desktop は、この組を 1M の選択肢つきの1つのモデルとして出す。ゲートウェイは [1m] の項目を検証せずに配るので、上流が 1M で出せるモデルにだけ足す

次の例は、アプリケーション推論プロファイルへ向けたカスタムのエイリアスに、選択肢を手で出し、新しいユーザーをそれで始めさせます。

yaml
models:
  - id: corp-sonnet
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-2:123456789012:application-inference-profile/sonnet-5-prod
  - id: corp-sonnet[1m]
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-2:123456789012:application-inference-profile/sonnet-5-prod

managed:
  policies:
    - match: {}
      desktop:
        modelPrefer1mContext: true
1M の選択肢を外す

選択肢をピッカーから外すには、ポリシーの cli の下の env に CLAUDE_CODE_DISABLE_1M_CONTEXT: "1" を書きます。id が [1m] で終わる項目も並べているなら、ゲートウェイはそれを配り続けるので、その項目も消します。

この変数は、ポリシーに一致する開発者のターミナルのセッションにも届きます。そちらで何が変わるかは、モデルの「拡張コンテキスト」を参照してください。

他の managed の源との優先順位#

  • デバイスに MDM のポリシーやローカルの managed-settings.json もあるなら、ゲートウェイが配る設定が最優先
  • ローカルの源がいつ当たるかと、どの源が選ばれても全管理者の源から読むキー(サンドボックスのロックのキー・forceRemoteSettingsRefresh・変数ごとの env の合成など)は、managed settings のドキュメントの該当の節にある
  • MDM のプロファイルか managed settings のファイルに書いた policyHelper は、ゲートウェイが設定を配らないときだけ動く
  • Claude Desktop のような組み込みのホストは、SDK の managedSettings オプションでポリシーを渡せる(親の設定。上の「親の設定を制限する」)
  • ゲートウェイのポリシーは、claude -p と Agent SDK が起動したセッションを含め、マシン上のすべての Claude Code の呼び出しに当たる
  • 起動時にゲートウェイに届かないと、サインイン済みのセッションはポリシーなしで動かず、エラーで終了する

telemetry#

  • CLI はメトリクス・ログ・(有効なら)トレースをゲートウェイへ送り、ゲートウェイが設定した各宛先へそのまま中継する。形式は OTLP over HTTP
  • 中継せず、セッションからコレクターへ直接出すなら、ポリシーでコレクターを指名する(下の「コレクターへ直接出力する」)
  • CLI が出すメトリクスとイベントは利用状況の計測
  • /login でサインインしたセッションでは、ゲートウェイが発行した JWT にある認証済みユーザーの ID(user.id・user.email・user.groups)を、CLI が各出力に付ける。開発者側の設定なしで、開発者ごとにコストと使用量を分けられる

ID の属性の補足です。

  • Claude Desktop と Cowork のセッションも、user.email と user.groups を enduser.id と一緒に付ける。user.email か user.groups で問い合わせれば、ターミナル・Desktop・Cowork をまとめて見られる。user.groups は IdP のグループのカンマ区切り
  • Desktop と Cowork には enduser.sub(IdP が発行する sub クレーム。メールが変わっても同じ)も付く。ターミナルは同じ値を user.id に付けるので、enduser.sub とターミナルの user.id を突き合わせれば、1人のターミナル・Desktop・Cowork をまとめて覆える(Desktop と Cowork の user.id は主体ではなく匿名の識別子)
  • これらの属性も、ほかの OpenTelemetry のデータと同じく、組織が設定した宛先だけへ行き、Anthropic へは行かない
  • グループの一覧がパーセントエンコード後に255文字を超えるか、グループ名にカンマか等号があると、切り詰めずに Desktop と Cowork のテレメトリから user.groups を外す(ターミナルは全体の一覧を持つ)
  • enduser.sub は、主体がパーセントエンコード後に255文字を超えるか、空白・印字できる ASCII の外の文字・, ; = \ " % のどれかを含むと外す(ほかの属性は残る)
  • 版:Desktop と Cowork の user.email・user.groups はサーバーで v2.1.265 以降(user.groups は開発者のマシンの Claude Desktop 1.24012 以降も)。enduser.sub はサーバーで v2.1.274 以降
yaml
telemetry:
  forward_to:
    - url: https://otel-collector.internal.example.com
      headers:
        Authorization: ${OTLP_TOKEN}
      metrics: true
      logs: false
      traces: false
    - url: https://api.datadoghq.com/api/v2/otlp
      headers:
        DD-API-KEY: ${DD_API_KEY}

注意

宛先ごとに metrics・logs・traces を個別に選び、既定はメトリクスだけです。メトリクスはトークン数・リクエスト数・遅延などの集計値ですが、ログとトレースは、Bash のコマンドの全文・ツールの入力・ファイルのパスなど、Claude Code が開発者のマシンでしたことを何でも含みえます。ログとトレースは、その中身に見合うアクセス制御と保持の方針を持つ宛先でだけ有効にします。

項目 内容
forward_to[].url 宛先。https:// が必須(例外はゲートウェイ自身のループバックのコレクター。下)
forward_to[].headers 宛先へ付ける認証などのヘッダー
forward_to[].metrics / logs / traces 宛先ごとの信号の選択。既定はメトリクスだけ
resource_attributes 固定のラベル(下の「自分のラベルを足す」)
HTTPS_PROXY 環境にあれば、出力はそのプロキシ経由
  • ループバックのコレクター:http://localhost:<port> は設定の検証を通るが、ゲートウェイの環境に CLAUDE_GATEWAY_ALLOW_LOOPBACK=1 が無いと、SSRF ガードが全出力を ECONNREFUSED_SSRF で止める。http://127.0.0.1:<port> と http://[::1]:<port> は、その変数が無いと起動に失敗する
  • クラスター内のコレクターは、自分の内部アドレスの HTTPS で公開するか、変数を書いてサイドカーで動かす
  • プロキシがあるとき内部のコレクターへ直接行かせるには、NO_PROXY にホスト名か、先頭にドットの付くドメイン(.internal.example.com。サーバーで v2.1.277 以降)を載せ、プロキシなしで届くことを確かめる。ドットの無い項目はその名前にだけ一致し、CIDR は一致しない
  • プロキシ経由だけの外向きが有効なら、NO_PROXY の項目があるとオフのままになる。代わりにプロキシでコレクターを許可する

CLI のテレメトリは既定でオフです。telemetry.forward_to と listen.public_url の両方を書くと、ゲートウェイが /managed/settings で次の6つの環境変数を押し、接続したクライアントでオンにします。

  • CLAUDE_CODE_ENABLE_TELEMETRY=1
  • OTEL_METRICS_EXPORTER・OTEL_LOGS_EXPORTER・OTEL_TRACES_EXPORTER:その信号を有効にする forward_to の宛先が1つでもあれば otlp、なければ none(サーバー v2.1.265 より前は、宛先が選ばない信号も含めて3つとも otlp を押した)
  • OTEL_EXPORTER_OTLP_ENDPOINT=<public_url>
  • OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf

押された変数の扱いです。

  • ラベルを足すと、OTEL_RESOURCE_ATTRIBUTES も押される
  • エンドポイントは公開 URL から作られるので、メトリクスとログには、開発者側にもポリシーにも OTEL の設定は要らない
  • 押した変数は managed の階層で当たり、開発者がローカルで書いた値を上書きする。/login でサインインした開発者は、自分の OTEL の設定で出力先を変えられない
  • OTLP/HTTP の出力が有効なら、CLI はローカルに書かれたエンドポイントを無視してゲートウェイへ出す(ゲートウェイがテレメトリの変数を押したかどうかに関係なく。ポリシーが自社のコレクターを指名した場合を除く)
  • 信号に forward_to の宛先が無ければ、ゲートウェイは受けて捨てる
  • すでに自社のコレクターへ Claude Code のテレメトリを出していたなら、サインイン後も届くよう、そのコレクターを forward_to に足す(ログやトレースも出していたなら、それも有効にする)
  • protobuf と JSON の OTLP の両方を中継する。OpenTelemetry 互換のバックエンドなら宛先にできる

トレースの決まりです。

  • 各クライアントで CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 も要る。ゲートウェイは押さないので、managed のポリシーの env に書く(押されたエンドポイントと同じセキュリティの承認ダイアログで承認される)
  • トレースを取りたいグループのポリシーにだけ 1 を書く。書かないポリシーは、match: {} のキャッチオールが書いていればその値を継承する
  • 開発者がローカルで書いてもトレースを送らせないグループは、そのポリシーで 0 にする

自分のラベルを足す#

  • service.namespace や deployment.environment.name のような固定のラベル(OpenTelemetry のリソース属性)を付けるには、telemetry.resource_attributes を書く。すべての宛先が同じラベルを受ける
  • ラベルが付くのは、telemetry.forward_to と listen.public_url も書いたときだけ
  • サーバーで v2.1.281 以降。古いゲートウェイはキーがあると起動を拒否するので、全レプリカを上げてからキーを足し、戻す前に外す
yaml
telemetry:
  forward_to:
    - url: https://otel-collector.internal.example.com
  resource_attributes:
    service.namespace: claude
    deployment.environment.name: prod

次の規則を破ると、ラベルを名指しするエラーで起動を拒否します。

  • 名前は英字・数字・.・_・- だけ
  • 予約された名前は使えない:大文字小文字を区別せず user.・enduser.・identity. で始まるものすべてと、service.name・service.version・claude.deployment_mode・host.arch・os.type・os.version・wsl.version
  • 値は空でない印字できる ASCII で、空白と , ; = \ " % を含まない
  • 値はパーセントエンコード後に数えて255文字以下(/・:・@ は1つにつき3文字)
  • 値は文字列なので、数字・true・false は引用符で囲む

ラベルの届き方です。

  • ターミナルのセッションは、ほかのテレメトリの変数と一緒に押される OTEL_RESOURCE_ATTRIBUTES でラベルを受ける。ポリシーの env に OTEL_RESOURCE_ATTRIBUTES を書くと、一致するターミナルのセッションはラベルの代わりにその値を受ける
  • Claude Desktop は、user.email などの ID の属性と並べて、ゲートウェイからラベルを受ける
  • Claude Code は各ラベルをメトリクスのすべてのデータ点にも写すので、リソース属性を索引しないバックエンドでも絞り込める(写すのを止める方法は、利用状況の計測のメトリクスのカーディナリティの制御)

コレクターへ直接出力する#

  • 設定:managed のポリシーの env に、OTEL_EXPORTER_OTLP_ENDPOINT をコレクターの https:// の基底 URL(https://otel-collector.example.com:4318 など)で書く
  • Claude Code はその URL に /v1/metrics・/v1/logs・/v1/traces を付け、各信号を OTLP/HTTP で出す(各開発者のマシンで v2.1.265 以降。古いクライアントは中継へ出す)
  • コレクターの認証は、同じ env の OTEL_EXPORTER_OTLP_HEADERS で渡す
  • この方法で指名したコレクターには、ゲートウェイのセッショントークンを送らない
  • ポリシーでこのエンドポイントを足すか変えると、対話セッションでは当てる前にセキュリティの承認ダイアログが出る

Claude Code は直接出す前にエンドポイントを確かめ、次のどれかに外れた信号は中継に残します。

  • エンドポイントがゲートウェイから来ている(MDM のプロファイルやローカルの managed-settings.json に同じ変数を書いても、中継のまま)
  • URL が https://、またはループバックへの http://
  • URL が /v1/<signal> で終わるパスになり、クエリとフラグメントが無い(一般の変数からは Claude Code がそのパスを作る。OTEL_EXPORTER_OTLP_METRICS_ENDPOINT のような信号別の変数は書いたまま使うので、完全なパスを書く)
  • URL がゲートウェイ自身のホストでない(ゲートウェイ宛てなら、中継の経路とセッショントークンのまま)
  • 開発者のものも管理者のものも、どの設定の源にも otelHeadersHelper が無い(あると全信号が中継に残る)

出力をオンにする変数との関係です。

  • エンドポイントが変えるのは出力先だけ。どの信号を出すかは OTEL_*_EXPORTER の選択子が決め、エンドポイントだけでは出力はオンにならない
  • ゲートウェイがテレメトリの変数をすでに押しているなら、有効化・選択子・プロトコルはそれが受け持ち、書いたエンドポイントは押された <public_url> を上書きする。forward_to のどの宛先も有効にしない信号だけ、自分で OTEL_*_EXPORTER を otlp にする
  • 押していないなら、CLAUDE_CODE_ENABLE_TELEMETRY=1・OTEL_*_EXPORTER の選択子・OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf も書く
  • 開発者がサインアウトするか別のゲートウェイにサインインすると、コレクターへの出力は止まり、残りのバッチは送らずに捨てる

宛先が失敗したとき#

  • テレメトリをバッファも再試行も保存もしない。宛先に届かなかった出力は、あとから送らずに捨てる
  • 成否は宛先ごとに独立。出力したクライアントはどちらでも成功の応答を受けるので、配信の失敗はゲートウェイのログにだけ出る
  • 宛先への配信が5回続けて失敗すると、成功するまで30秒ずつその宛先への転送を止め、止めるたびにログを出す
  • 失敗に数えるのはエラー応答・タイムアウト・接続エラー。ただし 400・413・415・422・431(コレクターがペイロードを不正・大きすぎるとして拒否)は数えない
  • 拒否されたペイロードは、失敗の回数を進めも戻しもしない。その宛先への転送は続け、最初の拒否と以後100回ごとに、宛先とステータスを名指しする警告を出す

HTTP の調整#

access_control・limits・timeouts・rate_limits の4つの省略可のブロックで、HTTP の面を調整します。たいていの配備は既定のままで足ります。

ブロック キー 既定 内容
access_control allow_cidrs / deny_cidrs 空 クライアントのアドレス(trusted_proxies の解決後)による受信の許可と拒否
limits max_request_bytes 32 MiB 受けるリクエスト本文の上限。超えると、本文をバッファする前に 413
limits max_request_header_bytes 未設定 ゲートウェイの、リクエストのヘッダー合計 256 KiB という上限を下げる。超えると 431。256 KiB を超える値は効かない。サインイン後に開発者が 431 を受けるなら、下の「サインイン後にリクエストのヘッダーが大きすぎる」を見る
limits max_url_length 未設定 書くと、長すぎる URL に 414
timeouts upstream_ttfb_ms 120000 上流の応答ヘッダーを待つ上限(最初のバイトまで)。直接の Anthropic の上流だけに当たる
rate_limits device_authorization.max / .window_seconds 30 / 600 認証なしのデバイス認可のエンドポイントの、IP ごとのレート制限
rate_limits device_verify.max / .window_seconds 10 / 600 /device での user_code の送信の、IP ごとのレート制限
  • access_control:deny_cidrs を先に見て、一致すれば allow_cidrs にも一致していても拒否する。allow_cidrs が空でなければ、既定は拒否。/healthz と /readyz は allow_cidrs の対象外
  • 信頼したプロキシが IP アドレスでない X-Forwarded-For の項目を送ると、本当のクライアントが分からないので、確認点を名指しする警告を1回出す。どちらかの一覧がそのリクエストに当たるなら 403(監査の理由 xff_unparseable)で拒否する。どちらも当たらなければ受けて、プロキシのアドレスをクライアント IP として、IP ごとのレート制限と監査に使う
  • max_request_bytes:大きなファイルや画像を送るなら上げる
  • upstream_ttfb_ms:そのあとの本文のストリームに、壁時計の上限は無い。ほかのプロバイダでは、応答が始まるまで最大1時間待つ
  • device_authorization:共有の出口 IP や NAT の後ろの大きな組織なら上げる。サインインの流れだけに当たり、/v1/messages の推論には当たらない
  • device_verify:他の開発者のコードを推測されるのを防ぐためのもの

両方の access_control の一覧が空のまま(既定)だと、どのクライアントのアドレスも受けるので、届く相手を絞るのはネットワークだけになります。配る managed settings は開発者のマシンでコマンドを実行できるので、ここは大事です。allow_cidrs が空のあいだは、応答は変えずに2か所で警告します。

  • 起動時:運用ログに、プライベートの範囲(10.0.0.0/8・172.16.0.0/12・192.168.0.0/16・100.64.0.0/10・127.0.0.0/8・::1/128・fc00::/7)と、開発者が接続する社内のほかの範囲だけを許可するよう勧める警告が出る。ループバックにバインドし、trusted_proxies も public_url も書かない(ローカル開発など)なら出ない
  • 実行時:その範囲の外のアドレスから最初のリクエストが来たとき、警告をログに出し、クライアント IP を載せた access.public_client の監査イベントを出す(どちらもプロセスごとに1回)。リンクローカルの 169.254.0.0/16 と fe80::/10 は公開に数えない。/healthz と /readyz にはこの確認より前に応えるので、公開の範囲からのヘルスプローブは引き金にならない

警告が効かない場合です。

  • どちらの警告も、ゲートウェイが解決したクライアントのアドレスで判断する。ロードバランサー・ポートフォワード・トンネルが中継していて listen.trusted_proxies に載っていないと、中継のアドレス(たいてい私設)しか見えず、実行時の警告もプライベートの許可リストも、中継された通信を捉えない
  • そうしたフロントの後ろでは、まず listen.trusted_proxies を書いて本当のクライアントのアドレスを見せる。そのうえで、ゲートウェイとその前段を公開のインターネットから届かないようにしておく

load_test_mode#

  • モデルのプロバイダを呼ばずに、ゲートウェイを負荷試験するブロック
  • 版:サーバーで v2.1.282 以降。古いゲートウェイはキーがあると起動を拒否するので、全レプリカを上げてからブロックを足し、戻す前に外す
  • オンのあいだ、各プロバイダへのリクエストはいつもどおり作って署名するが、送らずに捨て、通常の応答の経路で定型のダミーの返信をストリームする(返信は、定型であることを述べる文で始まる埋め草のテキスト)
yaml
load_test_mode:
  enabled: true
  reply_tokens: 750
  reply_seconds: 9.5
フィールド 必須 内容
enabled はい true でオン。false なら数値をファイルに残したままオフ。ブロックがあって enabled が無いと起動を拒否する
reply_tokens いいえ ダミーの返信のおおよそのトークン数。1〜100000 の整数、既定 750
reply_seconds いいえ ストリームの返信にかける時間。0〜600、既定 9.5。0 は一度に送る(非ストリームへの返信は常に一度に返る)
  • 試験で覆えるのは、ゲートウェイ・自分の Postgres・ゲートウェイの前段だけ。プロバイダの上限・速度・ネットワーク経路は覆えない
  • プロバイダへ送らないので、レプリカのリクエストあたりの CPU は、通信を暗号化する本番より低く出る見積り(v2.1.283 より前は、さらにずっと低く出た)。レプリカ数は、本物のプロバイダへの小さなパイロットで確かめる
  • オンのあいだ、リクエストに最大7桁の整数の x-load-test-user ヘッダーを付けられる。番号ごとに別の開発者として数え、メールとグループはそのリクエストのトークンのものを使う
  • 専用の空のデータベースを用意する(すでに開発者の支出があるデータベースでは、このモードをオンにすると起動を拒否する)

注意

開発者が使うゲートウェイでは、このモードをオンにしないでください。すべてのリクエストがダミーの返信になり、モデルは呼ばれません。オンのあいだは、起動時に load_test_mode is on の警告が出て、各 inference の監査イベントに load_test: true が付きます。

完全な設定の例の要点#

主なセクションをすべて使った設定の骨格です(HTTP の調整のブロックは既定のまま)。ログの詳しさは環境変数 CLAUDE_GATEWAY_LOG_LEVEL(debug・info・warn・error。既定 info)で決めます。debug では、groups_claim の診断用に各 id_token のクレーム名も出ます(監査イベントはこれに関係なく常に出る)。

yaml
listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.internal.example.com

oidc:
  issuer: https://example.okta.com
  client_id: 0oa1example2
  client_secret: ${OIDC_CLIENT_SECRET}
  allowed_email_domains: [example.com]
  userinfo_fallback: true
  scopes: [openid, profile, email, offline_access, groups]

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}

upstreams:
  - provider: anthropic
    auth:
      api_key: ${ANTHROPIC_API_KEY}

  # - provider: mantle
  #   region: us-east-1
  #   models: [claude-opus-4-8, claude-opus-4-7, claude-haiku-4-5]
  #   auth: {}

auto_include_builtin_models: true

managed:
  policies:
    - match: { groups: [contractors] }
      cli:
        availableModels: [claude-haiku-4-5]
        permissions: { allow: [Read, Grep] }
    - match: {}
      cli:
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        # Default の選択を、内蔵の既定ではなく各ポリシーの availableModels に絞る。
        # contractors のポリシーはこのキーを継承する
        enforceAvailableModels: true
        permissions:
          allow: [Read, Grep, Bash, Edit]
          deny: ["WebFetch"]

telemetry:
  forward_to:
    - url: https://otel.internal.example.com:4318
      headers:
        Authorization: Bearer ${OTEL_TOKEN}
  • allow はそのツールを自動で承認するだけで、ほかを止めはしない。絞るには deny の規則を足す
  • Okta は groups のスコープを求め、アプリのグループのクレームのフィルタが通したときだけグループを出す。グループで一致させるポリシーを使うなら、scopes に groups を足す

支出上限#

  • 各開発者がゲートウェイ経由で使える額を、1日・1週間・1か月ごとに制限する
  • 上限を超えると次のリクエストから 429 を返し、期間がリセットされるか管理者が上限を上げるまでブロックする
  • 既定では全推論が1つの共有の上流の資格情報を通るので、提供元の請求はその資格情報に付き、個々の開発者には付かない(Bedrock では、上の「開発者ごとの AWS のコストの帰属」でこれを変えられる)
  • 支出上限は、その共有の請求の上に載せる、開発者ごとの見える化と遮断器。開発者・グループ・組織全体に天井を付けられる

上限を設定する#

  • gateway.yaml に admin: ブロックを書くと、/v1/organizations/spend_limits で Admin API が開き、すべての推論リクエストで上限をリアルタイムに強制する
  • 上限そのものは gateway.yaml ではなく API で決める。POST /v1/organizations/spend_limits が、{scope, amount, period} から上限を1つ作るか置き換える
  • API は、Anthropic の公開 Admin API の支出上限のエンドポイントと同じワイヤー形式。その契約向けに書いた HTTP クライアントは、ベース URL を替えるだけでゲートウェイに使える
bash
# 組織全体の既定として、全開発者に月 $500
curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "organization"}, "amount": "50000", "period": "monthly"}'

# contractors グループの各メンバーに、日 $100 の、より厳しい上限
curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "rbac_group", "rbac_group_id": "contractors"}, "amount": "10000", "period": "daily"}'
フィールド 値 内容
scope.type user・rbac_group・organization 上限をかける相手。1人の開発者・IdP のグループ・組織全体の既定
amount USD セントの整数の文字列、または null null は無制限。"0" はゼロの上限で、すべてのリクエストをブロックする
period daily・weekly・monthly 1つのスコープは期間ごとに1つの上限を持ち、それぞれ独立に強制する(どれかを超えるとブロック)
  • user は OIDC の sub(IdP が割り当てる安定したユーザー ID)で指し、scope.user_id に渡す。rbac_group は IdP のグループ名を scope.rbac_group_id に渡す
  • ゲートウェイは3つとも受けるが、Anthropic の公開の POST はいまのところ user だけ
  • グループや組織の上限は共有のプールではなく、各メンバーが受け継ぐ1人あたりの既定
  • 期間ごとに、開発者の実効の上限は次の順で決まる:ユーザーごとの上書き、所属するグループの上限のうち最も厳しいもの、組織の既定、無制限
  • admin.group_limit_mode: max にすると、複数グループのときは最も緩いものを選ぶ

Admin API の認証#

  • admin.write_keys のキーに一致する x-api-key ヘッダー(完全なアクセス)、または admin.read_keys のキー(GET だけ)。各キーの id が監査ログに admin-key:<id> と出るので、Terraform・CI・自動化ごとに別のキーを渡す
  • groups クレームに admin.admin_groups のどれかを含む、ゲートウェイのベアラートークン。完全なアクセスで、監査では oidc:<sub> になる。人間の管理者向け

強制のしくみ#

  • /v1/messages のリクエストごとに、1回の Postgres のクエリで開発者の上限と期間内の支出を調べる
  • どれかの上限を超えた開発者には、error.type: billing_error で、x-should-retry: false のヘッダー付きの 429 を返す
  • メッセージは期間とリセット時刻(spend limit reached (daily; resets 2026-08-08 00:00 UTC) など)のあとに、書いていれば admin.blocked_message が続く。複数の上限を同時に超えたら、最後にリセットされる上限を示す
  • 応答には、そのリセットまでの秒数を持つ retry-after ヘッダーも付く(サーバー v2.1.225 より前は、期間・リセット時刻・retry-after の無い spend limit reached だけだった)
  • v2.1.227 以降は、<public_url>/protocol のプロトコルの文書に、使用量の上限の応答ヘッダーと 429 の本文が載る
  • リセットは UTC の暦の境界。日次は 00:00 UTC、週次は月曜、月次は1日
  • /v1/messages/count_tokens はトークンを数えるだけで無料なので、決してブロックしない

リクエストの価格の付け方#

  • 各応答のあとで、使用量のメーターがトークン数を読み、日・週・月のカウンターに費用を足す。クライアントへ送るバイトには触れないので、メーターが失敗しても応答は壊れない
  • 金額は USD の見積りで、請求書ではなく遮断器(請求は、提供元の使用量のレポートと突き合わせる)

料率は次の順で選びます。

  1. そのリクエストに応えた上流の、一致する pricing.overrides の行(v2.1.227 以降)
  2. 上流のモデル ID(ゲートウェイが提供元へ送る文字列)の定価。Claude Code のコスト表が知っている ID のとき(表は Anthropic・Bedrock・Vertex AI・Foundry の ID の形を受ける)
  3. その上流の ID に対応づけた models[].id の定価。Bedrock のアプリケーション推論プロファイルの ARN や Foundry のデプロイメント名のような、モデル名を含まない上流の文字列向け(v2.1.218 以降)
  4. 不明なモデルの階層。100万トークンあたり入力 $5・出力 $25(値の分からない ID を無料にしないため)。この階層を使うと、起動時と、ID ごとに実行時に1回警告する

課金の決まりです。

  • どの料率にも、そのあと pricing.multiplier(既定 1)をかける
  • クライアントが中断したリクエストも課金する。上流の最後の使用量のフレームなしにストリームが終わると、クライアントへ送り済みのテキストを、出力トークンあたり約4文字とする下限の見積りで課金する。早めに中断しても上限は逃れられない

Postgres が使えないとき#

  • 事前の確認は、2秒のタイムアウトで Postgres に問い合わせる
  • 既定(通す側):ストアに届かないかタイムアウトすると、リクエストは進む。ゲートウェイは警告をログに出し、応答に anthropic-ratelimit-unified-* ヘッダーは付かない
  • enforcement.fail_closed_on_error: true(止める側):同じ 429 billing_error を、メッセージ spend limit unavailable(期間・リセット時刻・retry-after なし)で返す
  • 通す側はストアの障害を推論の障害にしない。止める側は、計測されない支出を出さない
  • 通す側が役に立つのは、ロードバランサーやオーケストレーターがまだゲートウェイへ通信を流しているあいだだけ。store.readiness_grace_seconds で、短い障害のあいだレプリカを readiness の確認に通し続けられる(下の「ストアが落ちたときの挙動」)

Claude Code の使用量の警告#

  • 開発者が上限に近づくと、Claude Code が警告する。使用率が75%を超えたときと、最も消費している上限の95%を超えたとき
  • ブロックされると、admin.blocked_message を含むゲートウェイの 429 のメッセージを、そのまま表示する。あわせて上限を /usage に出し、開発者のステータスラインのスクリプトにも渡す

表示ごとに、開発者のマシンとゲートウェイのサーバーで最低限の版が要ります。

開発者が見るもの 開発者のマシン ゲートウェイのサーバー
75% と 95% の使用量の警告 v2.1.225 以降 v2.1.225 以降
/usage に、使用した割合とリセット時刻の「Spend limit」のバー、ステータスラインの入力に rate_limits.spend_limit オブジェクト v2.1.251 以降 v2.1.225 以降
「Spend limit」のバーに、推定の支出と上限の米ドル(「$271.40 / $500.00 spent this month」のような表示)、ステータスラインの入力に同じ金額と上限の期間 v2.1.284 以降 v2.1.284 以降

警告と割合は、ゲートウェイが上限のある開発者への成功した /v1/messages の応答に足す anthropic-ratelimit-unified-* ヘッダーから来ます。ヘッダーは常に開発者自身の上限を表します(共有の割り当てを表す上流の提供元のレート制限ヘッダーは、ゲートウェイが外して転送しません)。

開発者が見る支出は、ゲートウェイ自身の見積りで、上限の強制に使う値と同じです。提供元の請求書の金額ではありません。Claude Code は、金額を別のリクエストでゲートウェイから読みます。開発者のマシンに CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC を設定していると(コンプライアンスの姿勢で勧めている)、Claude Code はそのリクエストを省きます。この変数があるとき、またはゲートウェイのサーバーが v2.1.284 より古いときは、バーとステータスラインは割合だけです。

Admin API のリファレンス#

メソッドとパス 内容
GET /v1/organizations/spend_limits 設定された上限の一覧。scope_type(organization・rbac_group・user)で絞れる。クエリ:?limit=&after_id=&before_id=&scope_type=
POST /v1/organizations/spend_limits {scope, period} の上限を作るか置き換える
GET /v1/organizations/spend_limits/{id} spl_ で始まる ID で上限を1つ取る
DELETE /v1/organizations/spend_limits/{id} 上限を1つ消す。{type: "spend_limit_deleted", id} を返す
GET /v1/organizations/spend_limits/effective 主体ごと・期間ごとの、解決した上限と期間内の支出
GET /v1/organizations/spend_limits/audit 管理者の変更の履歴(新しい順)。クエリ:?limit=&after_id=

規約は Anthropic の Admin API に合わせてあります。

  • すべてのオブジェクトに type がある。ID は spl_ で始まる
  • 金額は USD セントの整数の文字列(POST はほかの currency を 400 で拒否)
  • エラーの形は {type: "error", error: {type, message}, request_id}
  • 成功・エラーを問わず、管理者の応答にはすべて request-id ヘッダーが付く(エラーの本文にも request_id で入る)
  • 変更はすべて、同じトランザクションで admin_audit に前後の行を書き、admin-key:<id> か oidc:<sub> に帰属させる
  • 提供するのは支出上限のエンドポイントだけ。spend_limit_increase_requests のキューなど、ほかの Admin API の面は無い

/effective#

Anthropic の SpendSummary スキーマで返します。各行は1つの主体の1つの期間で、解決した上限・期間内の支出・actor オブジェクトを持ちます。ゲートウェイでの違いは次のとおりです。

  • user_id は OIDC の sub
  • actor.name と actor.email_address は、その主体がゲートウェイ経由で最初の推論リクエストをするまで null(ユーザーのディレクトリを持たず、各ユーザー自身のセッションの JWT から最後に見た値を記録するため)
  • 各行に groups の配列(主体の最後に見た IdP のグループ)も付く。管理画面が当たっている上限の階層をすべて示せるようにするための拡張で、Anthropic 形式のクライアントは無視する
  • user_ids[] で絞らないと、組織の全メンバーは列挙できないので、支出が記録された主体を並べる。グループ由来の上限は、強制と同じ group_limit_mode で最後に見たグループに対して解決するので、実際に当たる上限が出る
クエリパラメータ 内容
user_ids[] 繰り返せる。OIDC の sub で特定の主体に絞る
period[] 繰り返せる。daily・weekly・monthly の行に絞る
sort spend_desc で支出の多い順。period[] をちょうど1つ指定する
q OIDC の sub・最後に見たメール・最後に見た表示名への、大文字小文字を区別しない部分一致
limit / page ページの大きさ(1〜1000、既定20)と、前の応答の next_page の不透明なカーソル

注意

q= と user_ids[]= は GET のクエリ文字列に載るので、前段のプロキシやロードバランサーのアクセスログに残ります。個人情報のログの方針が厳しいなら、そこでこれらのパラメータを消します。

/audit#

支出上限の変更の履歴(誰が・どの上限を・どう変えたか、前後のスナップショット、新しい順)を返します。has_more は正確です。ファーストパーティのワイヤー形式ではなく、ローカルの Admin API の規約に従います。

ページング#

  • 素の一覧:after_id と before_id(どちらか一方。spl_… の ID)で送る。作成順で、has_more は走査の向きを表す
  • /effective:応答の next_page(不透明なトークン)を ?page= に渡す。支出が記録されている最中もページがずれないよう、主体を昇順に並べる
  • この2つの limit は 1〜1000(既定20)
  • /audit:前のページの最後のイベントの数値の id を after_id に渡す。limit の既定は100

データのライフサイクル#

支出まわりの表は4つあり、毎時の掃除が保持期間を守らせます。

表 内容 保持
spend 主体ごとの期間内のカウンター(セント) admin.spend_retention_months(既定13)
spend_limits 設定された上限 API で消すまで
admin_audit 変更の履歴 admin.audit_retention_days(既定365)
principal_emails 各主体の最後に見たメール・表示名・IdP のグループ(個人情報) admin.identity_retention_days(既定90。最後の活動から)
  • 開発者が退職したら、ユーザーごとの上限を DELETE /v1/organizations/spend_limits/{id} で消す。支出と ID の行は、上の保持期間で消える
  • 1人をすぐ消す(退職・データ主体のアクセス請求)なら、ゲートウェイのデータベースで下の SQL を実行する。メール・名前・グループを持つのはこの表だけ
  • spend と admin_audit の行は仮名の OIDC sub しか持たず、それぞれの期間で消える
sql
DELETE FROM principal_emails WHERE principal = '<sub>';

配備と運用#

  • ゲートウェイは、Postgres で協調する、状態を持たない単一の Linux バイナリ。ほかのステートレスなサービスと同じ要領で配備する
  • 開発者と IdP から HTTPS で届く社内ネットワークに置き、本番の資格情報を持つサービスとして扱う
  • 本番の構成は4段:IdP の設定・配備・運用・セキュリティの確認

IdP の設定#

  • 登録するもの:機密の OAuth/OpenID Connect(OIDC)の Web アプリケーション。リダイレクト URI は https://<gateway>/oauth/callback の1つだけ。ゲートウェイを使わせるユーザーかグループに割り当てる。ゲートウェイは、登録のクライアントシークレットで IdP へ認証する。IdP が証明書の資格情報を使うなら、登録へ上げた証明書で認証する
  • 動く IdP:OIDC 準拠のもの(Okta・Microsoft Entra ID・Google Workspace・Keycloak・Dex・PingFederate など)

IdP が満たす条件です。

  1. /.well-known/openid-configuration を出す(本番は HTTPS。http:// の issuer も受けるが、ループバックの issuer には CLAUDE_GATEWAY_ALLOW_LOOPBACK=1 も要る)
  2. authorization-code フローに対応する。PKCE は既定でオン(対応しない IdP では oidc.use_pkce: false)
  3. id_token で email(と任意で groups)を返すか、oidc.userinfo_fallback: true で userinfo から出せる

社内の PKI なら oidc.ca_cert_pem を書きます。IdP ごとの違いです。

IdP 注意点
Okta org の認可サーバーは id_token が薄いので oidc.userinfo_fallback: true が要る。カスタムの認可サーバーなら不要
Microsoft Entra ID issuer は https://login.microsoftonline.com/<tenant-id>/v2.0。グループは名前でなくオブジェクト ID で出る
Google Workspace issuer は https://accounts.google.com。id_token にグループが無く、標準の offline_access も無視される
  • Okta:https://example.okta.com の org の認可サーバーは、email と groups を省いた薄い id_token を返す。これを issuer にするなら常に oidc.userinfo_fallback: true。https://example.okta.com/oauth2/default のようなカスタムの認可サーバーは email(と任意で groups)を id_token に載せるので、フォールバックは要らない
  • Okta は oidc.scopes に groups のスコープを求め、アプリのグループのクレームのフィルタが通したときだけ groups を出す(userinfo_fallback は、IdP に求めていないクレームまでは埋められない)
  • Entra ID:managed.policies.match.groups には GUID を書くか、人間が読めるアプリのロールを使う。テナントがロールを groups でなく roles に出すなら oidc.groups_claim: roles
  • Google Workspace:グループで allowed_groups や managed.policies を使うなら oidc.google_groups を書く。無ければ、メンバーの制限は oidc.allowed_email_domains、ポリシーの割り当ては managed.policies.match.email_domain で行う
  • Google でリフレッシュトークンを得るには、oidc.scopes: [openid, profile, email] と oidc.extra_auth_params: { access_type: offline, prompt: consent } を書く

注意

リフレッシュトークンは、セッションをブラウザに戻さずサイレントに更新するためのもので、退職の処理も担います(IdP がユーザーを無効にすると次の更新が失敗し、セッションは ttl_hours 以内に終わる)。ゲートウェイは既定で offline_access を求めるので、IdP がオフラインアクセスに明示の同意を求めるなら、OAuth クライアントで許可しておきます。リフレッシュトークンを出せない IdP でも動きますが、サイレントな更新が無く、期限が切れるたびにブラウザのログインをやり直します。毎時になるのを避けて session.ttl_hours を 8 か 12 に上げると、リフレッシュトークンが無いぶん、無効にしたユーザーが長い TTL のあいだアクセスを保つ、という代償があります。

配備#

先に決めておくことです。

項目 内容
コスト 別のライセンスも1人あたりの料金も無い。推論は既存の契約で払い、かかるのは動かす計算資源の費用だけ
迂回 ゲートウェイはモデルへの唯一の経路を強制しない。閉じるのはネットワークの方針
複数のゲートウェイ それぞれ別の配備・別の設定。CLI は信頼と資格情報をホスト名ごとに持つので衝突しない
サーバーレス Cloud Run は min-instances: 1 で動く。Lambda や Cloud Functions は動かない
  • コスト:ゲートウェイは claude のバイナリの一部
  • 迂回:自分の資格情報を持つ開発者は、提供元を直接呼べる。api.anthropic.com への外向きをゲートウェイ以外から止めるなどで閉じる。止めると、各開発者のマシンが api.anthropic.com を呼ぶ WebFetch のドメイン安全確認も壊れるので、managed のポリシーで skipWebFetchPreflight: true にして無効にする
  • 複数のゲートウェイ:チームごとに別のゲートウェイを使える。複数の OIDC の発行者に応えるには、別のインスタンスを動かす
  • サーバーレス:Cloud Run の min-instances: 1 は、コールドな OIDC の discovery を避けるため。Lambda や Cloud Functions は、ゲートウェイが長く動く HTTP サーバーなので動かない

本番の構成ではどれも、平文の HTTP のレプリカの前に L7 のプロキシ(Ingress・Cloud Run のフロント・ALB など)を置きます。

  • listen.trusted_proxies をそのプロキシの送信元の範囲にする。ゲートウェイは、TCP のピアが信頼済みのときだけ X-Forwarded-For からクライアント IP を読む
  • 書かないと、全リクエストがプロキシの IP から来たことになる。IP ごとのレート制限が1つの共有のバケットに潰れ、監査イベントにもプロキシの IP が残る
  • デバイス認可とトークンのエンドポイントへのリクエストを、リダイレクトしない(HTTP から HTTPS への書き換えやホスト名の正規化も含む)。Claude Code はこれらでリダイレクトに従わないので、サインインとトークンの更新が壊れる
  • プロキシのアイドルタイムアウトは、ゲートウェイの keepalive の間隔より長くする
  • keepalive:provider: anthropic 以外の上流では、ストリームが約15秒静かになると SSE の ping を書く。provider: anthropic では、Anthropic API 自身の ping を含め、応答をそのまま通す
  • ALB の既定の60秒でも静かなストリームは保てるが、AWS の例は念のため1時間に上げている

コンテナイメージ#

標準の Claude Code のリリースのネイティブな claude バイナリを元に、イメージを自分で作ります。

  1. 固定したリリースから、イメージのアーキテクチャに合う Linux ビルドを取る(URL は、特定のバージョンのインストールの項)
  2. リリースの GPG 署名つきの manifest.json で検証する(バイナリの完全性とコード署名の項)
  3. ビルドのコンテキストに置く

ビルドからリリースのホストに届かないなら、リリースを社内のレジストリに写し、全台で動かすバージョンを固定します。バイナリのほかにイメージに要るものです。

  • glibc ベースのイメージ(glibc ビルドの動的な依存は glibc のライブラリだけ。musl ベースのイメージには、linux-x64-musl か linux-arm64-musl のビルドと追加のパッケージが要る)
  • 書き込める状態のディレクトリ:ゲートウェイはどのユーザーでも動くが、最小のイメージには書き込めるホームが無い。CLAUDE_CONFIG_DIR を /tmp/.claude のような書き込めるパスにする
  • コンテナのコマンド:claude gateway --config /etc/claude/gateway.yaml。設定ファイルは読み取り専用でマウントし、秘密は環境変数で渡す。待ち受けは listen.port(既定 8080)

Kubernetes#

ほかのステートレスなサービスと同じく、Deployment として動かします。

  • 設定は ConfigMap から、秘密は Secret からマウントする(YAML では ${file:/path/to/secret} か環境変数で参照)
  • Ingress で TLS を終端し、listen.public_url を Ingress のホスト名にする
  • readiness のプローブを GET /readyz、liveness を GET /healthz にする
  • 静的なキーより、プラットフォームの Workload Identity を使う。クラウドをまたぐ組み合わせ(GKE 上で Bedrock の上流など)なら、上流の auth ブロックに明示の資格情報を書く

Cloud Run#

  • listen.port は既定の 8080(Cloud Run の既定の PORT と同じ)のままにするか、port: ${PORT}
  • public_url は外から届くオリジンにする。本番では、/login が公開アドレスを拒否し *.run.app の URL も公開に解決されるので、ふつうは内部ロードバランサーのホスト名
  • Cloud Run の URL だけで済むのは、curl やブラウザの簡単な確認だけ。例外は、Private Service Connect と Cloud DNS のプライベートゾーンで *.run.app が私設に解決されるネットワークで、そこでは Cloud Run の URL を public_url にできる
  • 設定はシークレットのボリュームとしてマウントする
  • コールドな OIDC の discovery を避けるため min-instances: 1

大規模な展開#

  • サインインは、クライアントの IP アドレスごとにレート制限される。既定は小さなチーム向けで、1アドレスにつき10分あたり、サインインの開始30回とコードの送信10回
  • 数千人へ展開すると、初日の朝にこの上限に届くことがある

原因は次の2つのどちらかです。

  • ロードバランサーの先が見えていない:listen.trusted_proxies が無いと、全開発者がロードバランサーのアドレスから来たことになり、1つの上限を分け合う。何より先に書く(ゲートウェイは、最初に X-Forwarded-For ヘッダーを無視したときに警告をログに出す)
  • 多数の開発者が、少数の NAT や VPN の出口アドレスを共有している:trusted_proxies が正しくても、そのアドレスの上限を分け合う。rate_limits を上げる

max の見積り方です。

  1. 開発者の数を、共有する出口アドレスの数で割る
  2. そのうち1つの window_seconds(既定10分)にサインインする人数を見積もる
  3. 再試行と、Claude Code と Claude Desktop の両方にサインインする人を見込んで2倍にする

たとえば、4つの出口アドレスの後ろの10,000人が1時間かけて均等にサインインすると、1アドレスに2,500人で10分あたり約420人。2倍して切り上げ、1,000 にします。

yaml
rate_limits:
  device_authorization: { max: 1000, window_seconds: 600 }
  device_verify: { max: 1000, window_seconds: 600 }
  • device_verify は他人のサインインのコードの推測を防ぐものなので、見積りで要るぶんだけ上げる(上げても、コードは20文字の文字集合から選ぶ8文字で、10分で期限切れになるので、推測は現実的でない)
  • IdP がリフレッシュトークンを出すなら、Claude Code がセッションをサイレントに更新するので、展開が済んだら上限を戻せる
  • リフレッシュトークンが無いと、開発者は session.ttl_hours ごとにサインインし直す。その定常の頻度でも両方の上限を見積もり、上げたままにする
  • 上限に達すると、v2.1.274 以降の Claude Code は The gateway is limiting sign-in attempts right now を出し、v2.1.274 以降のゲートウェイは検証ページに Too many attempts came from your network address と見直す設定を出す。変える設定を示す sign-in refused のログも書く

運用#

ログ#

標準エラーに2つのストリームが出ます。

  • 監査イベント:セキュリティに関わるイベントごとの1行 JSON。標準エラーをログの集約先へ流す。イベントは config.load・session.mint・session.refresh・device.authorize・device.verify・device.callback・auth.denied・access.denied・access.public_client・inference・managed.serve・desktop_bootstrap.serve・desktop_bootstrap.denied・spend.blocked・admin.denied・admin.limit.upsert・admin.limit.delete。載る項目はイベントごとに違う
    • 成功した mint と refresh:sub・email・client_ip・結果
    • auth.denied と access.denied:理由とクライアント IP(auth.denied は、拒否の時点でユーザーの ID が無いので、リクエストのパスも載せる)。access.denied は理由で載るものが違う。xff_unparseable は読めなかった X-Forwarded-For の項目も載せ、client_ip_unknown(access_control の一覧があるのに接続にピアのアドレスが無かった)はクライアント IP を載せない
    • access.public_client:access_control.allow_cidrs が空のあいだに、プロセスで最初に公開アドレスから来たリクエストのクライアント IP。リクエストはふつうに処理する。ゲートウェイが公開のインターネットから届く状態かもしれない合図
    • inference:どの上流が応えたかと、応答のステータス
    • desktop_bootstrap.denied:拒否された Claude Desktop の bootstrap の取得の理由(not_configured・policy_not_opted_in・no_policy_matched)とユーザーの ID
    • admin.denied:拒否された管理者 API の認証の試み。クライアント IP・メソッド・パス・理由(出されたキーの中身は載せない)。理由は、x-api-key が設定のキーに一致しない invalid_key、Authorization ヘッダーだけがあり admin.admin_groups のゲートウェイのセッションとして通らなかった bearer_rejected、どちらのヘッダーも無い no_credentials
  • 運用ログ:起動・警告・上流のエラーの、人が読む [gateway] で始まる行。環境変数 CLAUDE_GATEWAY_LOG_LEVEL(debug・info・warn・error、既定 info)で詳しさを決める。debug では、サインインと更新のたびに id_token のクレームの名前(値ではない)と、userinfo_fallback が出した userinfo のクレームの名前も出るので、個人情報をログに出さずに email_claim と groups_claim の設定を診断できる。監査イベントはこれに関係なく常に出る

ヘルス#

  • liveness は GET /healthz、readiness は GET /readyz
  • /readyz はストアに届くかを見る。store.readiness_grace_seconds を書くと、ストアが応答しなくなってもその秒数まで ready を返し続ける
  • どちらも access_control.allow_cidrs の対象外なので、絞ったリスナーでもプローブは動き続ける
  • OAuth の discovery の文書(/.well-known/oauth-authorization-server)も、設定の読み込み・OIDC の discovery・上流のクライアントの生成・Postgres の移行がすべて成功して初めて 200 を返す。起動の通しの確認に使える

上流への同時リクエスト#

  • 既定で、各レプリカが上流へ同時に送るのは最大256リクエスト(ストリームの応答は、終わるまで数に入る)
  • 上限にあるレプリカに来たリクエストは、ゲートウェイの中で空きを待つ。開発者からは、応答の始まりが遅いか止まったように見える
  • provider: anthropic の上流では、timeouts.upstream_ttfb_ms より長く待ったリクエストはその上流を諦め、後ろの上流が応えなければ 502 で失敗する
  • 起動ログの upstream requests: を含む行が、有効な上限を示す。上限より多くのリクエストを開いているあいだは、client requests are open を含む警告を最大1分に1回出す

もっと多くを同時に捌くには、次のようにします。

  • レプリカを足すか、各レプリカの上限を上げる(ゲートウェイのコンテナの環境変数 BUN_CONFIG_MAX_HTTP_REQUESTS に1〜65535の整数を書いて再起動)
  • 上限が埋まる速さは、上限 ÷ リクエストが開いている平均の秒数(平均10秒なら、既定の256は約26リクエスト/秒で埋まる)
  • CPU で自動スケールするなら、上限にあるレプリカはスケールアウトを起こさずにリクエストを待たせる。目標の CPU を、client requests are open の警告が出るときの水準より低くする

注意

開いているリクエストは、ストリーム中も空き待ちのあいだも、ゲートウェイのプロセスのメモリを使います。待つリクエストも本文を持つので、上限を256のままにしても過負荷のレプリカのメモリは増えます。コンテナのメモリはピーク時に開いているリクエスト数に合わせて見積もり、上限を変えたらメモリを見ます。メモリが尽きたレプリカは強制終了され、持っていたストリームはすべて落ちます。

ストアが落ちたときの挙動#

Postgres が落ちても、サインイン済みの開発者にはゲートウェイが応え続け、新しいサインインは失敗します。

対象 挙動
既存のセッション 続く。ベアラートークンは JWT シークレットでローカルに検証でき、セッションの更新もストアを使わない
新しいサインイン 復旧まで失敗する(デバイスフローとレート制限のカウンターが Postgres にあるため)
支出上限の強制 既定は通す側で、推論は流れる。計測されない実行より止めたいなら止める側にする
readiness 既定では Postgres に届かなくなるとすぐ /readyz が not ready を返し、全レプリカが一斉に確認に落ちる
  • readiness:確認に通ったレプリカにだけ通信を流す環境では、ゲートウェイが応えられる推論も含め、Postgres の復旧まですべての通信が失敗する。/healthz の liveness は通り続ける
  • IdP が落ちたら:既存のセッションは ttl_hours まで動き、新しいログインは失敗する。セッションの更新は再試行の応答を受け、IdP が戻れば成功する。IdP のメンテナンスが多いなら ttl_hours を長くする
  • 短い Postgres の障害(データベースのフェイルオーバーなど)でもサインイン済みの開発者が働けるようにするには、store.readiness_grace_seconds をフェイルオーバーより長く(たとえば 300)する。サーバーで v2.1.282 以降(古いゲートウェイはキーがあると起動を拒否するので、足す前に全レプリカを上げる)
  • 支出上限が有効で既定の通す側なら、readiness を保ったレプリカ経由のリクエストは Postgres の復旧まで計測されない。値はフェイルオーバーを覆う範囲で低めにする
  • enforcement.fail_closed_on_error: true なら、レプリカが readiness の確認を通っていても、Postgres の復旧までサインイン済みの開発者の推論を 429 の spend limit unavailable で拒否する
  • readiness のプローブを /healthz に向ければ障害中もレプリカは通るが、/healthz は not ready を返さないので、Postgres の接続が戻らないレプリカまで通ってしまう

JWT シークレットのローテーション#

既存のセッションを生かしたまま、段階的に回します。

  1. 新しいシークレットを作り、session.jwt_secret の配列の先頭に足す
  2. 配備を入れ替える。新しいトークンは新しいシークレットで署名され、古いトークンも検証が通る
  3. ttl_hours に余裕を足したぶん待ってから古いシークレットを外し、もう一度入れ替える

ローテーションの補足です。

  • 期限前にセッションを強制的に切る唯一の方法でもある(ベアラートークンは JWT シークレットでローカルに検証するので、セッションごとの失効は無い)
  • 古いものを配列に残さずに置き換えると、発行済みのすべてのセッションが一度に無効になる
  • 個々の退職は、IdP でユーザーを無効にすれば ttl_hours 以内に終わる

Postgres#

5つのデータの表と _migrations 表があり、すべて起動時の移行が作ります。

表 内容 保持
kv デバイスの許可(TTL 10分)とレート制限のカウンター 行ごとの TTL
spend 主体ごとの期間内の支出のカウンター(セント) admin.spend_retention_months(既定13)
spend_limits 設定された支出上限 API で消すまで
admin_audit Admin API の変更の履歴 admin.audit_retention_days(既定365)
principal_emails 各主体の最後に見たメール・表示名・IdP のグループ(個人情報) admin.identity_retention_days(既定90。最後の活動から)
  • 30秒ごとのループが TTL を過ぎた kv の行を消し、毎時の掃除が支出の表の保持期間を守らせるので、際限なく増えはしない
  • 支出上限を使わなければ、書かれるのは kv だけ
  • 起動時とアップグレードのたびにスキーマを自分で移行するので、データベースのロールにテーブルの作成と変更の権限が要る。権限を狭く保つため、ゲートウェイ専用のデータベースかスキーマに向ける
  • 支出上限を使うなら、データベースを失うと、開発者の再ログインだけでなく支出の記録と上限も失う。定期的にバックアップする

アップグレード#

  • レプリカはステートレスなので、ローリングで再起動しても状態は失われない
  • スキーマの移行は起動時に走るので、新しいバイナリを配ればデータベースは自分で移行する。並行するレプリカは Postgres の advisory lock で順番になり、各移行を当てるのは1つだけ
  • オーケストレーターが SIGTERM(ローリングの再起動や縮小)で止めると、新しい接続を受けるのをやめ、進行中のリクエストとストリームを終えてから終了する(ドレイン)。待つのは最大25秒(ドレインのウィンドウ)で、過ぎたら開いているものを閉じる
  • ターミナルの Ctrl+C のような SIGINT も同じドレインを始める。ドレイン中に2回目のシグナルを受けると、開いているリクエストを閉じてすぐ終了する
  • ドレインはゲートウェイ v2.1.274 以降

長い生成は数分ストリームしうるので、Kubernetes と Amazon ECS では次の2つを一緒に上げ、ストリームに使える時間を延ばします。

  • ドレインのウィンドウ:ゲートウェイのコンテナの環境変数 CLAUDE_GATEWAY_DRAIN_TIMEOUT_MS に、ミリ秒の正の整数(120000 など)を書く。120s のような別の形の値は無視され、既定の25秒のまま
  • オーケストレーターの猶予期間:Kubernetes の terminationGracePeriodSeconds、Amazon ECS の stopTimeout

猶予期間と、前の版へ戻すときの決まりです。

  • 猶予期間の既定は、どちらのプラットフォームも30秒。ドレインのウィンドウより5秒以上長く保たないと、ドレインが終わる前に強制終了される
  • Kubernetes では preStop フックの時間も足す(猶予期間は、ゲートウェイが SIGTERM を受けた時点ではなく、フックが走る前から数える)
  • プラットフォームの上限:Fargate 上の Amazon ECS は stopTimeout が最大120秒。Cloud Run は SIGTERM の10秒後にインスタンスを止めるので、ドレインのウィンドウに関係なく、開いているストリームには最大10秒しかない
  • ドレインのウィンドウが終わっても開いているリクエストがあると、drain window over after を含む警告をログに出し、切ったリクエストの数と、上げるべき2つの設定を示す
  • 移行は追記だけなので、知っている移行が少ない前のバイナリへ戻しても安全(余分な行は無視される)
  • ただし戻すと、YAML を古いバイナリのスキーマで検証し直す。新しいリリースで入ったキーを使った設定は起動に失敗するので、戻す前にそのキーを外す
  • ゲートウェイの版は自分のイメージで固定しているので、新しい Claude Code のリリースの修正(セキュリティの修正を含む)は、固定を上げて再デプロイしたときだけ届く。本番の資格情報を持つほかのサービスと同じパッチの周期に、ゲートウェイも入れる

セキュリティ#

データの流れ#

データ 経路 ゲートウェイが Anthropic へ送るか
推論(プロンプトと応答) CLI → ゲートウェイ → 自分の上流 Anthropic API を上流に設定したときだけ
テレメトリ(OTLP のメトリクスと、有効にしたログとトレース) CLI → ゲートウェイ → 自分のコレクター 送らない
ID(メール・グループ・sub) IdP → ゲートウェイ → CLI(CLI が OTLP の出力に付ける)。forward_user_identity がオンなら、開発者のメールと IdP の主体を自前のプロキシへヘッダーで送る 送らない
managed settings ゲートウェイの YAML → CLI 送らない
監査ログ ゲートウェイの標準エラー → 自分の集約先 送らない

脅威モデルの要約#

ゲートウェイはネットワークの境界の内側に置きますが、個々の開発者のノート PC は信頼しない前提で作られています。

  • 開発者が持つのは、上流の生のキーではなく短命の JWT
  • CLI とゲートウェイの間は RFC 8628 のデバイスグラント。ゲートウェイと IdP の間の authorization-code の交換は既定で PKCE を使うので、横取りした IdP の認可コードは役に立たない
  • デバイス検証のページは、同一オリジンの POST と、RFC 8628 §5.1 に沿った IP ごとのレート制限を強制する
  • SSRF ガード:ゲートウェイから IdP・OTLP のコレクター・provider: anthropic の上流へのリクエストは、DNS を解決し、リンクローカル・クラウドのメタデータのアドレス・ループバックを既定で拒否し、解決した IP に接続を固定する。運用者が書けるこれらの URL を、クラウドのメタデータのエンドポイントへ向けることはできない
  • RFC 1918 のプライベートの範囲は、IdP や OTLP のコレクターがよく私設 IP にあるので、意図して許可している
  • ほかのプロバイダでは、設定の読み込み時に、それらのアドレスやメタデータのホスト名を名指しする base_url を拒否する。プロバイダの SDK は DNS の確認なしに接続する

CLAUDE_GATEWAY_ALLOW_LOOPBACK=1 の扱いです。

  • 書くのは、ゲートウェイが正当に届く必要のあるものがループバックにあるとき(ローカル開発の IdP や localhost のサイドカーの OTLP コレクターなど)だけ
  • この変数は、運用者が書いたすべての URL のループバックの拒否を緩め、ポッドがクラウドのメタデータのエンドポイントへ届くかを見る起動時の警告も飛ばす。コレクターには自分の内部アドレスを与えるほうがよい
  • プロキシ経由だけの外向きをオンにすると、アドレスの確認はフォワードプロキシへ移る。ゲートウェイはホスト名を渡すので、プロキシの許可リストがそれらの宛先を拒否する必要がある
  • 自分で外向きの制御を足す場合、ワークロード ID などインスタンスのメタデータの資格情報を使うなら、ゲートウェイがメタデータのサーバーに届くようにしておく

範囲外の脅威が2つあり、自社のインフラで守ります。

  • 侵害されたゲートウェイのホスト:ホストは上流の資格情報を持ち、接続中の全開発者へ managed settings を配る。ゲートウェイの設定を握られるのは、MDM を握られるのに等しい。シェルを実行できる設定への CLI の承認ダイアログは、気づかれない変更を抑えるが、ホストのセキュリティの代わりにはならない
  • 悪意のある OIDC のプロバイダ:ゲートウェイが信頼する id_token にはプロバイダが署名するので、プロバイダはどんな ID でも名乗れる。IdP の審査と保護は自社の責任

ユーザーコードの総当たりへの耐性#

  • /device の検証ページで入力する user_code は、20文字の文字集合から選ぶ8文字で 20⁸(約2.56×10¹⁰)通り。10分で期限切れになる
  • デバイスグラントのエンドポイントには、IP ごとのレート制限(rate_limits)がかかる
  • 共有の社内の NAT のアドレスから多数がサインインするなら上限を上げる(サインインの流れだけに当たり、推論には当たらない)

コンプライアンスの姿勢#

項目 内容
データの所在地 上流に Anthropic API を書かない限り、ゲートウェイのデータプレーンは Anthropic に何も送らない
ホストプロセスの通信 claude gateway は Bedrock や Vertex AI の配備と同じサードパーティの規則で動き、Anthropic に何も送らない
クライアントの分析 ゲートウェイにサインインしているあいだ、CLI は自分の使用量の分析とエラー報告を止める
エラー報告 モデルのリクエストが Anthropic のファーストパーティ API 以外(Bedrock や独自の ANTHROPIC_BASE_URL)へ行くときはオフ
クライアントのマシン CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 と skipWebFetchPreflight: true を書かない限り、WebFetch のホスト名の確認とバージョン確認を Anthropic へ送る。プラグインのマーケットプレイスへの要求には、別の止め方がある(下の「プラグインのマーケットプレイスへの要求」)
アンケートの評価 サインイン中は分析のストリームと一緒に止まり、Anthropic へ送らない
文字起こしの共有 アンケートで Yes を選ぶと、Anthropic へ上げずに ~/.claude/feedback-bundles/ へローカルのファイルを書く
クライアントの更新 更新の確認は、ゲートウェイの通信とは別。止めるなら DISABLE_UPDATES
TLS 本番では public_url を HTTPS で出す。ゲートウェイ自身は平文の HTTP を拒否しない
  • データの所在地:Anthropic API を上流にすると、その推論の経路に既存のデータ取り扱いの契約が当たる。テレメトリ・監査・ID・設定は、設定した宛先だけへ行く
  • ホストプロセス:ホストのプロセスは Claude Code の CLI。v2.1.227 より前は起動時のテレメトリ(製品のバージョンやプラットフォーム)を送り、コンテナの環境に CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 を書けば止まった
  • 同じく v2.1.227 より前のリリースは、起動時に https://api.anthropic.com(環境に ANTHROPIC_BASE_URL があればそれ)の /api/hello へ、本文も資格情報も無い HEAD を1回送った(HTTPS_PROXY などのプロキシの変数か mTLS のクライアント証明書がある環境を除く)。応答は無視されるので、外向きのファイアウォールで止めても影響は無かった
  • クライアントの分析:最初のサインインの前は、ゲートウェイのサインインを強制する managed settings のマシンでも、起動時のイベントを Anthropic へ送る。これも止めるなら、サインインを強制するクライアント側の managed settings に DISABLE_TELEMETRY を一緒に配る
  • クライアントの更新:バージョンは自分の配布で固定し、ノート PC にリリースを取らせたくなければ DISABLE_UPDATES を書く(DISABLE_AUTOUPDATER はバックグラウンドの更新だけを止め、claude update は動く)
  • TLS:ゲートウェイ自身のリスナーの listen.tls か、TLS を終端するイングレスの後ろに平文 HTTP のレプリカを置く。どちらでも listen.public_url を書く。IdP は本番では HTTPS、Postgres は ?sslmode=require に対応する。Strict-Transport-Security はイングレスで付ける

プラグインのマーケットプレイスへの要求#

Claude Code は、プラグインのマーケットプレイスを、ゲートウェイを通さず開発者のマシンから直接取りに行きます。ホストの一覧は、公式の「ネットワークのアクセス要件」にあります。

開発者が対話のターミナルのセッションを初めて始めるとき、Claude Code は公式のマーケットプレイス claude-plugins-official を登録します。カタログを downloads.claude.ai から取り、失敗したら github.com から clone します。そのあとの更新はプラグインを使うに書いてあります。

CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 は、この最初の登録を止めません。止めるのは、次のどちらかの managed settings です。

  • マーケットプレイスの一覧:そのマーケットプレイスを含まない strictKnownMarketplaces の許可リスト、またはそれを名指しする blockedMarketplaces の項目
  • 環境変数:managed の env ブロックに CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL を "1" で書く

最初の登録は、開発者がゲートウェイにサインインする前、ゲートウェイのポリシーがまだ届いていないときに走りうります。最初の起動も覆うには、選んだ設定を、ゲートウェイのポリシーの cli ブロックだけでなく、クライアント側の managed settings にも置きます。

トラブルシューティング#

問題を報告するときに添えるものです。

  • ゲートウェイの問題:その時間帯のゲートウェイの標準エラー・秘密を伏せた gateway.yaml・ゲートウェイのバージョン(/ のページと、/managed/settings の応答ヘッダー x-cc-gateway-version に出る)・最近変えたこと
  • ログインの問題:開発者に claude --debug-file ./claude-debug.txt で再現してもらい、そのファイルと、同じ時間帯のゲートウェイの監査ログ
  • 推論の問題:要求したモデル・設定した上流・そのリクエストの監査ログ(どの上流が応えたかと応答のステータスが残る)
  • 標準エラーには監査イベント(開発者の ID)が、デバッグのファイルには開発者のマシンのフックと MCP サーバーの出力が入る。公開の issue に貼る前に見直して伏せる
症状 原因 対処
/login が「Cloud gateway」の画面ではなく標準のアカウント選択を出す そのマシンの managed settings に forceLoginMethod か forceLoginGatewayUrl が無い managed settings のファイルをデバイスへ配る(/login はゲートウェイの URL をそこから読む)
リクエストが Not signed in to the Cloud gateway — run /login. で失敗する managed settings が forceLoginMethod: "gateway" か forceLoginGatewayUrl を書いていて、セッションにゲートウェイのサインインが無い(残っている claude.ai のログインでは足りない) /login でゲートウェイのサインインを済ませる
Claude Desktop が bootstrap の設定を取れないと報告する /user/bootstrap が 404:一致するポリシーに desktop キーが無いか、どのポリシーにも一致していない。監査ログに desktop_bootstrap.denied と理由が残る 一致するポリシーか match: {} の基底に desktop ブロックを足す(空の desktop: {} でよい)
起動時に Gateway login is configured in managed settings, but this Claude Code build does not include Cloud gateway support. 入っている Claude Code がゲートウェイ対応より前の版 Cloud gateway に対応するリリースへ上げる
起動時に Administrator policy requires a Cloud gateway sign-in on this machine で終了する 環境に ANTHROPIC_API_KEY か ANTHROPIC_AUTH_TOKEN がある、設定に apiKeyHelper がある、または以前の Console のログインの API キーが保存されている 変数を unset する・apiKeyHelper の項目を消す・claude auth logout で保存されたキーを消す。CLAUDE_CODE_USE_* でクラウドのプロバイダを選ぶセッションはサインインなしで始まり、ほかは claude を起動して /login でサインインする
managed settings の読み込みが 403 のあと Claude Code may not be enabled for your organization と出る ゲートウェイかその前段が /managed/settings に 403 を返した。ゲートウェイの設定のルート自身は 403 を返さないので、access_control の IP の確認か、前段のプロキシや WAF が出どころ。IP の確認の拒否は理由つきの access.denied で監査ログに残る。開発者はサインインしたまま 失敗した時刻の access.denied を監査ログで探し、access_control の一覧かフロントを直して、開発者に claude を再起動してもらう
/login で The gateway is limiting sign-in attempts right now(古い版は Request failed with status code 429)。/device のページは、初めての開発者にも Too many attempts を出すことがある IP ごとのサインインのレート制限に達した。listen.trusted_proxies がロードバランサーを覆わず全員がそのアドレスを共有しているか、多数が NAT や VPN の出口アドレスを共有している。result: rate_limited の監査イベントが、同じ1つか少数の client_ip を示す まず listen.trusted_proxies をロードバランサーの送信元の範囲にし、それでもアドレスを共有するなら rate_limits を上げる
/login で Gateway hosts must be on your organization's private network; <host> resolves to the public (or unrecognized) address <ip> ゲートウェイの名前が、公開 IP に1つでも解決される(解決した全アドレスが私設である必要がある)。よくあるのはデュアルスタックの名前で、AWS の内部のデュアルスタックのロードバランサーは公開の範囲の AAAA を返す 開発者のマシンで、ゲートウェイの名前が私設のアドレスにだけ解決されるようにする(公開の範囲のレコードを外すか、内部専用の DNS 名を別に用意する)。自社が持つ公開空間を社内で使っているなら、そのブロックを gatewayInternalNetworks で宣言する
/login で Gateway login would go through proxy <proxy>, which is not on a private network HTTPS_PROXY か HTTP_PROXY がゲートウェイのホストに当たり、プロキシのホスト名が公開アドレスに解決される(私設だけに解決されるプロキシなら起きない) 開発者のマシンでゲートウェイのホストを NO_PROXY に足して直接つなぐか、私設に解決されるプロキシを使う。足す NO_PROXY の項目はメッセージに出る
/login で Claude Code only signs in to <host> from inside its declared network <block> (managed settings), and this machine is connecting from <ip>, outside it ゲートウェイは gatewayInternalNetworks のブロックにあるが、開発者のマシンがブロックの外のアドレス(VPN のアドレスのプール・コンテナや WSL2 の NAT のセグメント・自社のものでないネットワーク)から来た 自社のネットワークにいるホスト OS から /login を実行してもらう。表示されたアドレスも自社の公開空間なら、ゲートウェイの項目を、両方を覆う /8 までのブロックに置き換える(重なる2つ目の項目は拒否される)
/login で Every address for gateway host <host> must be inside its declared network <block>, and it also resolves to <ip> ゲートウェイの名前が、宣言したブロックの外のアドレス(2つ目のサイトや、デュアルスタックの IPv6 のレコード)にも解決される。宣言したブロックのもとでは、私設や IPv6 も含め、全レコードが1つの IPv4 のブロックの中にある必要がある 開発者のマシンで、ゲートウェイの名前にブロックの中のレコードだけを出すか、内部専用の名前を別に用意する
/login で <host> is on the declared network <block>, which Claude Code checks over a direct connection, not through an HTTP proxy HTTPS_PROXY か HTTP_PROXY が、宣言したブロックのゲートウェイに当たっている 開発者のマシンで、メッセージが示す NO_PROXY の項目を足す
/login で gatewayInternalNetworks in managed settings で始まるメッセージ 値が検証の規則に反している(どれかはメッセージに出る)。直すまで、そのマシンでの新しいゲートウェイの /login はすべて拒否される(私設アドレスのゲートウェイも。既存のサインインは動く) 配っている managed settings の源で示された項目を直し、/login をやり直す
/login で Could not resolve the configured HTTP proxy HTTPS_PROXY か HTTP_PROXY のホスト名が、開発者のマシンから解決できない(社内のネットワークにつないでいないことが多い) 社内のネットワークか VPN につないで再試行するか、プロキシの URL を直す
/login で Could not resolve gateway host <host> ゲートウェイの内部の DNS 名を解決できない(社内のネットワークにつないでいないことが多い) 社内のネットワークか VPN につないで、/login を再試行する
起動が store.postgres_url を名指しする設定の検証エラーで終了する Postgres が設定されていない(必須) store.postgres_url を書く。ローカル開発なら使い捨てのコンテナ:docker run --rm -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres
起動が requires the native binary で終了する ネイティブのバイナリではなく Node で動かしている スタンドアロンのインストール方法のどれかで Claude Code を入れる
config.load のあと、OIDC の discovery のエラーで起動が終了する oidc.issuer に届かないか、TLS のチェーンが信頼されていない issuer がポッドから届き、/.well-known/openid-configuration を出しているかを見る。社内の PKI なら ca_cert_pem。IdP へフォワードプロキシ経由でしか届かないなら oidc.use_proxy: true(v2.1.227 より前は、IdP の各エンドポイントへの直接の経路を用意する)。IdP のホスト名も解決できないか、プロキシが IP への CONNECT を拒否するなら、プロキシ経由だけの外向き(v2.1.277 以降)
起動が Postgres の権限エラーで終了する データベースのロールに、スキーマの DDL の権限が無い 起動時にテーブルを作成・変更できるよう、ゲートウェイのスキーマの CREATE をロールに付ける
ログ:could not connect to Postgres at boot, attempt 1 of 3 起動時に、まだデータベースに届かなかった(ネットワークが立ち上がる前のコールドなインスタンスなど) その後に起動し終えるなら対処は要らない。届かないときは2秒おきに3回試してから終了する。could not connect to Postgres で終了したら、store.postgres_url とデータベースまでの経路を見る。拒否でなくタイムアウトなら、store.connect_timeout_seconds を上げて1回ごとの待ちを延ばす
/oauth/callback が「Sign-in could not be completed」を出す メールのドメインが拒否された、id_token の検証に失敗した、または email_verified が明示的に false(常に拒否し、上書きは無い) allowed_email_domains と、IdP が検証済みの email クレームを返すかを見る。email_verified: false なら IdP 側の検証を直す。メールが別のクレーム名なら oidc.email_claim
ログ:token exchange failed request_id=<id>: id_token missing email claim IdP が既定で id_token に email を入れない。この拒否は allowed_email_domains を書いたときだけ起き、無ければメールの無いセッションを発行する IdP が id_token に email を出すよう設定する(Okta:カスタム認可サーバーの ID トークンのクレームに email を足す。Entra:アプリの登録に email を任意のクレームとして足す。PingFederate:email を出す OpenID Connect ポリシーを有効にする)。userinfo には email があるが id_token に無い IdP(Okta の org の認可サーバーなど)なら oidc.userinfo_fallback: true
ログ:refresh failed request_id=<id>: invalid_token (…) (at userinfo_no_id_token, …)。開発者が session.ttl_hours ごとに Cloud gateway session expired を見る IdP がリフレッシュトークンを受けたが id_token を返さず、ゲートウェイが userinfo にクレームを求めたら、更新後のアクセストークンを IdP が拒否した。ゲートウェイは temporarily_unavailable で応え、Claude Code はリフレッシュトークンを保つが更新できない(v2.1.260 より前のゲートウェイは、(at …) の詳細なしで同じ行を出す) oidc.scope_on_refresh: true(ゲートウェイ v2.1.260 以降)で、更新時に openid を求め直させる(Okta のように、求められたときだけ更新で id_token を返す IdP がある)。PingFederate では、Applications > OAuth > OpenID Connect Policy Management の「Return ID Token On Refresh Grant」を有効にする(このキーでは PingFederate の挙動は変わらない)。ほかの IdP なら、更新で発行されたアクセストークンを userinfo が受けるかを見る。暫定には session.ttl_hours を上げる
開発者がサインインしたあと、そのセッションのすべてのリクエストが 431 で失敗する 全リクエストの Authorization ヘッダーのセッショントークンが開発者の IdP のグループを並べるので、グループの多い開発者ではヘッダーの合計がゲートウェイの受け入れる量を超えうる どの上限が効くかと直し方は、下の「サインイン後にリクエストのヘッダーが大きすぎる」を見る
すべての Bedrock リクエストが 502、ログに Could not load credentials from any providers EC2 で、IMDSv2 の既定のホップ制限1が、コンテナの中からのインスタンスメタデータのリクエストを塞いでいる。AWS SDK はインスタンスの資格情報をクライアントの構築時でなく最初のリクエストで解決するので、起動と /readyz は通る aws ec2 modify-instance-metadata-options --instance-id <id> --http-put-response-hop-limit 2 でホップ制限を上げる(起動テンプレートでもよい。インスタンスのすべてのコンテナに効く)。ECS のタスクロール(コンテナ資格情報のエンドポイントから読むので変更が要らない)を優先するか、ゲートウェイ専用のインスタンスにして影響を限る
ピーク時に応答の始まりが遅い・止まる、または上流が健全なのに 502 all upstreams failed で失敗する レプリカが上流へ同時に送れる数を超えてリクエストを開いていて、余りがゲートウェイの中で待っている。provider: anthropic の上流では timeouts.upstream_ttfb_ms を超えて待つとその上流を諦め、後ろが応えなければ 502。ログに client requests are open の警告が出る レプリカを足すか、各レプリカの上限を上げる(上の「上流への同時リクエスト」)
IdP のエラー:unknown or unsupported scope IdP が知らないスコープを拒否する oidc.scopes を IdP が受ける一覧にそろえる(openid を含める。既定は openid profile email offline_access)
oidc.scopes を書いたら、セッションがサイレントに更新されない 上書きで offline_access が抜けた IdP が対応するなら offline_access を戻す。リフレッシュトークンが無いと、session.ttl_hours ごとにブラウザのログインをやり直す
ブラウザが「This request came from another site and was blocked」を出す クロスサイトのフォーム POST を、CSRF の防御として止めた。埋め込みやプロキシ経由のページでは想定どおり 検証のリンクを直接開く
Chrome が form-action の CSP で「Approve」を止めるが、Safari と Firefox では動く Chrome は form-action をリダイレクトの連鎖全体に強制する。IdP が、許可リストに無い2つ目のホストへ転送している 連鎖で増えるオリジンをすべて oidc.form_action_origins に足す。止められたオリジンは、Approve のページの Chrome の DevTools → Console で分かる
IdP でのサインインは済むが、コールバックが Chrome の CSP エラーや Safari の「this sign-in link has expired」で失敗する IdP が response_mode=form_post でコードを返し、/oauth/callback へクロスオリジンの POST を自動送信している。Chrome は厳格な CSP で止め、Safari は送信を許すが、コールバックはクエリ文字列しか読まない IdP が response_mode=query(コールバックが単純なリダイレクトになるよう、ゲートウェイが明示的に求めている)を守るようにする
ローカルでは動くが、ALB の後ろでログインが失敗する public_url がローカルか内側の http:// のオリジンのままで、IdP が違う redirect_uri を受けている listen.public_url を外向きの https:// のオリジンにし、<public_url>/oauth/callback を IdP に登録する
開発者に信頼の確認が何度も出る TLS の証明書が、レプリカごとやリクエストごとに変わっている イングレスで安定した証明書を使うか、TLS を1回だけ終端して内側のレプリカは平文 HTTP で動かす
/login で「Could not verify the gateway's TLS certificate」か SELF_SIGNED_CERT_IN_CHAIN ゲートウェイの TLS のチェーンが、CLI のホストのトラストストアに無い私設の CA で署名されている ネイティブのバイナリと Node 22.15 以降では、OS のトラストストアを既定で読む(CLAUDE_CODE_CERT_STORE で制御)。CA が OS のトラストストアにあるなら、開発者の実行環境が新しいかを見る。無いなら、起動前に NODE_EXTRA_CA_CERTS を CA 証明書の PEM にする。初回の接続のフィンガープリントの確認はそのまま出る
ブラウザのサインインは済んだのに、セッションが Cloud gateway sign-in was not completed と TLS の証明書の不一致で終わる サインイン後の最初のリクエストで、固定したフィンガープリントと違う証明書が出たので、資格情報を保存しなかった。よくあるのは、1つのアドレスの後ろのレプリカが別々の証明書を出している、または経路のどこかが TLS を傍受している ホスト名に1つの証明書を出す(イングレスで TLS を1回だけ終端するなど)。開発者に /login をやり直してもらう。証明書が固定したものと違えば、証明書が変わった警告つきで信頼の確認がまた出る
/login が The gateway's TLS certificate changed during sign-in: it no longer matches the one you trusted で止まる サインインのリクエストが、/login の開始時に受け入れた証明書と違うサーバーに届いた(1つのアドレスの後ろのレプリカが別々の証明書を出す・経路の TLS の傍受・サインイン中の証明書の更新) ホスト名に1つの証明書を出し、サインインをやり直して、新しい証明書を信頼の確認で確かめてもらう
  • Cloud gateway sign-in was not completed のメッセージには、ゲートウェイのホスト名が出る。固定したフィンガープリントと提示されたものの両方があれば、それぞれの先頭16文字も出る
  • サインイン後に couldn't load your organization's managed settings と出たら、Claude Code は理由を示してその場で再起動し、会話を再開する。再起動できない(バックグラウンドのセッションなど)ときは、サインインを保ったままセッションを終える

サインイン後にリクエストのヘッダーが大きすぎる#

開発者が多くの IdP のグループに属していると、サインイン後のリクエストが 431 で失敗することがあります。

ゲートウェイは、リクエストのヘッダーの合計が 256 KiB を超えると、または limits.max_request_header_bytes を書いていればそれを超えると 431 を返します。この場合、ログの行も監査イベントも書きません。v2.1.284 より前のゲートウェイは、16 KiB を超えると 431 を返します。

当てはまるものを上から順に試します。

  • ゲートウェイが v2.1.284 より古い:ゲートウェイを上げる
  • limits.max_request_header_bytes を書いている:値を上げるか、キーを消す
  • どちらでもない、またはそのあとも 431 が続く:IdP が出すグループを減らす。Okta・Microsoft Entra ID・Google Workspace のグループの出し方は、IdP の設定の節にある

groups のクレームを削るときは、開発者のアクセス・ポリシー・支出上限を決める、次の設定で名指ししたグループを残します。

  • oidc.allowed_groups:サインインできる人を決める
  • admin.admin_groups:ゲートウェイのセッションで管理 API を呼べる人を決める
  • managed.policies の match.groups:開発者にどのポリシーが当たるかを決める
  • rbac_group の支出上限:開発者にどのグループの上限が当たるかを決める

AWS と Google Cloud での配備の例#

  • どちらも顧客が管理するインフラで動かす例で、サポートされる本番の配備ではない。部品の組み合わせ方を見るための見本なので、自分の環境に合わせて直す
  • Terraform と setup.sh を含む参考の一式が、公式のリポジトリの examples/gateway/aws と examples/gateway/gcp にある

AWS(Bedrock を上流に、ECS Fargate か EKS)#

構成です。IdP は Okta の例ですが、OIDC 準拠なら何でも使えます。

部品 内容
計算資源 Amazon ECS on AWS Fargate のサービス、または Amazon EKS の Deployment
イメージ Amazon ECR のリポジトリ(イミュータブルなタグにして、固定したタグが黙って別のイメージを指さないようにする。scanOnPush を有効にする)
ストア プライベートサブネットの Amazon RDS for PostgreSQL(公開アクセスなし・ストレージ暗号化・rds.force_ssl=1 のパラメータグループ。エンジンのバージョンを固定する)
秘密 JWT の署名キー・OIDC のクライアントシークレット・Postgres の URL を、AWS Secrets Manager のシークレットに置く
IAM bedrock:InvokeModel・bedrock:InvokeModelWithResponseStream・bedrock:CountTokens を持つロール(ECS ではタスクロール、EKS では IRSA)
フロント HTTPS の内部の Application Load Balancer

前提です。

  • 上の構成のリソースを作る権限を持つ AWS アカウント
  • AWS CLI v2 と Docker
  • 2つ以上のアベイラビリティゾーンにプライベートサブネットを持つ VPC(NAT ゲートウェイ経由の外向きつき)
  • リダイレクト URI https://<gateway-host>/oauth/callback の Okta の OIDC アプリケーション
  • Route 53 のプライベートホストゾーンの内部 DNS 名と、ACM の証明書
  • Bedrock がモデルを出している米国のリージョン(内蔵カタログは us.anthropic.* の推論プロファイルに解決され、IAM のポリシーもその ARN を許可する。米国以外なら models: ブロックを足し、IAM のポリシーの ARN の接頭辞も合わせる)

手順です。

  1. セキュリティグループを3つ作る
  2. IAM のロールを作り、Anthropic の一度きりのユースケースのフォームを送る
  3. RDS for PostgreSQL を作る
  4. gateway.yaml を書く
  5. 3つのシークレットを Secrets Manager に作る
  6. イメージを作って ECR へ押す
  7. ECS Fargate か EKS に配備する
  8. Route 53 のプライベートホストゾーンで内部 DNS 名を ALB のエイリアスにし、listen.public_url と IdP のリダイレクト URI をそろえる
  9. 開発者のマシンへ URL を配る(上の「ゲートウェイの URL を配る」)

手順ごとの要点です。

  • 1:企業のネットワークから ALB へ443、ALB からゲートウェイへ8080、ゲートウェイから Postgres へ5432
  • 2:ゲートウェイのタスクロールの権限は、Bedrock のモデルの呼び出しだけ。ECS には、ECS エージェント自身がイメージを引き、Secrets Manager の値を注入するための実行ロールも別に要る(シークレットごとに ARN を挙げ、末尾を -?????? にして6文字のランダムな接尾辞に一致させる)
  • 3:接続文字列は sslmode=verify-full。信頼の起点として、AWS の RDS の証明書バンドルをイメージにコピーし、NODE_EXTRA_CA_CERTS で信頼させる。libpq 風の sslrootcert= は URL に付けない(ドライバーは sslmode だけを読み、sslrootcert を起動パラメータとして Postgres へ渡してしまい、サーバーが拒否する)
  • 4:upstreams は provider: bedrock と auth: {}。trusted_proxies は、ALB がつながるサブネットの CIDR。そのサブネットのホストはすべてプロキシとして信頼されるので、ALB の入口の送信元(企業の CIDR)と重ねず、X-Forwarded-For を偽りうる信頼できないワークロードとサブネットを共有しない。ALB のクライアントポートを残す属性(routing.http.xff_client_port.enabled)はどちらでもよい
  • 5:コマンドの引数はプロセス表に見えるので、監視されたホストでは 0600 のファイルを file:// で渡す。ECS では gateway.yaml をイメージに埋め込み、タスク定義の secrets でシークレットを環境変数に注入する。EKS では gateway.yaml を ConfigMap から、シークレットを /secrets のファイルとしてマウントし、${file:/secrets/...} で参照する
  • 6:Fargate の ARM64 なら、linux/arm64 のイメージと linux-arm64 のバイナリ
  • 7(ECS Fargate):クラスターと、標準エラー(監査イベントと運用ログ)用のロググループを作る(保持期間は別の呼び出しで、監査の保持の方針に合わせて90日など)。タスク定義を登録し、内部 ALB・ターゲットグループ・HTTPS のリスナーを作り、サービスを作る
  • 7(ECS Fargate)の設定値:ALB は --ip-address-type ipv4(デュアルスタックの内部 ALB は公開の範囲の AAAA を出し、/login のプライベートネットワークの確認に拒否される)。ターゲットグループのヘルスチェックは /readyz。リスナーは --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 で TLS の下限を固定する(省くと TLS 1.0/1.1 を受ける古い既定に戻る)。アイドルタイムアウトは3600秒。サービスにはデプロイメントのサーキットブレーカー(deploymentCircuitBreaker={enable=true,rollback=true})と60秒のヘルスチェックの猶予期間
  • 7(EKS):IRSA のロール(eksctl create iamserviceaccount)を作り、serviceAccountName: gateway の Deployment と Service、AWS Load Balancer Controller が管理する Ingress(scheme: internal・target-type: ip・ip-address-type: ipv4・inbound-cidrs・certificate-arn・ssl-policy・load-balancer-attributes: idle_timeout.timeout_seconds=3600)を置く
  • 8:OAuth クライアントの許可するリダイレクト URI を <public_url>/oauth/callback にする。ECS では設定がイメージに埋め込まれ、ゲートウェイは公開オリジンをその設定だけから作るので、public_url を変えたら新しいタグでイメージを作り直して押し、新しいタスク定義のリビジョンを登録して再デプロイする

運用の注意です。

  • プライベートサブネットのタスクは公開 IP を持たないので、外向き(Bedrock・IdP・Secrets Manager・ECR・CloudWatch Logs)はすべて NAT ゲートウェイを通る。Bedrock の通信を公開の経路から外すには、bedrock-runtime のインターフェース VPC エンドポイントを作り、上流の base_url をそこへ向ける
  • EKS の IRSA の経路では、AWS SDK がサービスアカウントのトークンを STS と交換するので、ポッドは EC2 のインスタンスメタデータのサービスを使わない。ゲートウェイのポッドに外向きの NetworkPolicy を当てて 169.254.169.254 を塞げる
  • Claude Platform on AWS も上流にできる(Bedrock の代わりにも併用にも)。上流の項目・資格情報・IAM の権限は Bedrock と違うので、上の「Claude Platform on AWS の上流」を見る。それ以外はそのまま当てはまる

AWS の配備に固有のトラブルです。

症状 原因 対処
/login で Gateway hosts must be on your organization's private network … デュアルスタックの内部 ALB が、公開の範囲の AAAA レコードを出している ALB を --ip-address-type ipv4 で作るか、公開の AAAA の無い内部専用の DNS 名を別に用意する
すべての Bedrock リクエストが 502、ログに Could not load credentials from any providers タスクロールなしの ECS の EC2 起動タイプ、または IRSA なしの EKS ノードで動いていて、資格情報がインスタンスメタデータから来る。IMDSv2 の既定のホップ制限1がコンテナの中でそれを止める(Fargate のタスクロールと IRSA は影響を受けない) タスクロールか IRSA を使う。インスタンスの資格情報しか使えないなら、ホップ制限を2に上げる
Bedrock のリクエストが 403 AccessDeniedException ユースケースのフォームが未送信、アカウントの最初の呼び出しで自動で始まる AWS Marketplace の購読が未完了、またはタスクロールのポリシーに推論プロファイルか foundation-model の ARN が無い Bedrock のコンソールの Model catalog からフォームを送る(送った直後か最初の呼び出しなら、数分後に再試行)。両方の ARN の系統に bedrock:InvokeModel と bedrock:InvokeModelWithResponseStream を付ける
Bedrock が、オンデマンドのスループットに対応しないという ValidationException を返す カスタムの models: の項目が、そのリージョンでは推論プロファイル経由でしか出ない素の foundation-model の ID を指している クロスリージョン推論プロファイルの ID(us.anthropic.*)に対応づける(内蔵カタログはそうなっている)
ゲートウェイが何もログに出す前に、ECS のタスクが ResourceInitializationError で止まる 実行ロールが Secrets Manager のシークレットを読めない、またはプライベートサブネットから Secrets Manager か ECR に届かない 3つの gateway- のシークレットの ARN への secretsmanager:GetSecretValue を実行ロールに付け、NAT ゲートウェイで外向きを用意する(無ければ、Secrets Manager・ECR・CloudWatch Logs(awslogs ドライバーが同じ段階で使う)のインターフェースエンドポイントと、S3 のゲートウェイエンドポイント)
起動が Postgres の接続タイムアウトのエラーで終了する データベースのセキュリティグループが、ゲートウェイのセキュリティグループからの5432を許可していない、またはサービスがデータベースの VPC の外で動いている データベース側で、ゲートウェイのセキュリティグループからの5432を許可し、DB のサブネットグループと同じ VPC でサービスを動かす
起動が Postgres の TLS の証明書の検証エラーで終了する 接続文字列は sslmode=verify-full だが、イメージが RDS の CA バンドルを信頼していない(コピーしていないか、NODE_EXTRA_CA_CERTS がそれを指していない) ビルドの手順の2行の Dockerfile(バンドルのコピーと NODE_EXTRA_CA_CERTS の設定)を足し、新しいタグで作り直して押し、再デプロイする
静かな時間のあと、ストリームの応答が途中で切れる v2.1.229 より前のゲートウェイは、Bedrock か Claude Platform on AWS の上流が静かなあいだ(ストリームされる出力の無い拡張思考など)何も送らず、ALB は既定でデータの無い接続を60秒で閉じる。v2.1.229 以降は、約15秒データが無いと SSE の ping を出し(Anthropic API の上流では API 自身の ping を中継し)、静かなストリームを60秒の内側に保つ ゲートウェイを v2.1.229 以降へ上げるか、modify-load-balancer-attributes か EKS の Ingress の load-balancer-attributes の注釈で idle_timeout.timeout_seconds を 3600 にする

AWS でのテレメトリとコスト帰属#

  • クライアントのメトリクス・ログ・トレース:telemetry.forward_to を、AWS Distro for OpenTelemetry(ADOT)のコレクターのような OpenTelemetry のコレクターへ向け、そこから Amazon CloudWatch・Amazon Managed Service for Prometheus・任意の OTLP のバックエンドへ出す。コレクターは https:// で届く自前の内部サービスとして動かす
  • ゲートウェイのログ:ECS Fargate では追加の設定は要らず、awslogs ドライバーが標準エラー(監査イベントと運用ログ)を /ecs/claude-gateway のロググループへ届ける。EKS ではポッドのログは既定で CloudWatch に届かず、ログ収集(Amazon CloudWatch Observability の add-on でコンテナのログの取得を有効にするか、Fluent Bit の DaemonSet)を入れるまで、監査の記録が失われる。どちらも、CloudWatch Logs Insights で問い合わせ、メトリクスフィルターでアラームを動かす
  • コンテナのメトリクス:ECS はクラスターで Container Insights を有効にし(aws ecs update-cluster-settings --cluster claude-gateway --settings name=containerInsights,value=enabled)、EKS は Amazon CloudWatch Observability の add-on を入れる
  • コストの帰属:既定では全 Bedrock リクエストをゲートウェイの主体(ECS のタスクロールか EKS の IRSA のロール)で署名するので、AWS からは支出がすべて1つの IAM 主体に見える。AWS 自身の請求のデータで分ける方法が2つあり、組み合わせられる
方法 内容
開発者ごと(assume_role) 開発者ごとに引き受けたロールのセッションで署名させ、IAM 主体ごとに支出を分ける(サーバーで v2.1.281 以降)
チームごと(アプリケーション推論プロファイル) チームとモデルごとの推論プロファイルにタグを付け、コスト配分タグで分ける

開発者ごと(assume_role)の要点です。

  • Bedrock の権限を持ち、ゲートウェイの主体を信頼する2つ目の IAM ロールを作り、その主体にロールへの sts:AssumeRole を付ける
  • Bedrock の上流に、session_name: email つきの assume_role を書く
  • 開発者ごとに1時間に1回ロールを引き受け、各開発者のリクエストは arn:aws:sts::<account>:assumed-role/<role>/<email> として AWS に届く。ロールは別の AWS アカウントでもよい
  • assume_role があると、無料の CountTokens も含めて全 Bedrock の呼び出しを引き受けたロールで署名する。ゲートウェイの主体に自分の Bedrock のポリシーが要るのは、assume_role の無い上流があるときだけ
  • 実行時に STS を呼ぶので、プライベートサブネットから sts.<region>.amazonaws.com へ届く必要がある(NAT ゲートウェイか、その名前に応える STS のインターフェース VPC エンドポイント)
  • 主体ごとの支出は、IAM 主体のデータを含む請求のエクスポートで見る

チームごと(アプリケーション推論プロファイル)の要点です。

  • 使うのは models と managed の2つのセクションだけ
  • チームとモデルごとに Bedrock のアプリケーション推論プロファイルを1つ作り、チームのタグを付けて、そのタグをコスト配分タグとして有効にする
  • gateway.yaml でチームごとのモデル ID を持たせ、IdP のグループをそのチームの ID に固定する(下の例)
  • 固定されたチームの開発者は、--model platform-claude-opus-4-8 のようにチームの ID で Claude Code を起動する(付けないと、ゲートウェイが拒否する既定のモデルで動く)
  • match: {} のキャッチオールが無いと、どのポリシーにも一致しない開発者がカタログの全モデルを持ち、どちらのチームのプロファイルにも課金できてしまう
  • 代償:設定がチームの数 × モデルの数で増える。また、この上流の Bedrock のリクエストに署名するロール(ゲートウェイの主体、assume_role があれば引き受けるロール)が、application-inference-profile/* の ARN も呼べる必要がある
yaml
models:
  - id: platform-claude-opus-4-8
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123
  - id: data-claude-opus-4-8
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/def456
managed:
  policies:
    - match: {groups: [team-platform]}
      cli: {availableModels: [platform-claude-opus-4-8], enforceAvailableModels: true}
    - match: {groups: [team-data]}
      cli: {availableModels: [data-claude-opus-4-8], enforceAvailableModels: true}
    - match: {}
      cli: {availableModels: [claude-opus-4-8, claude-sonnet-4-6], enforceAvailableModels: true}

Google Cloud(Vertex AI を上流に、Cloud Run か GKE)#

構成です。IdP は Google Workspace の例ですが、ほかの IdP にしても変わるのは oidc ブロックだけです。

部品 内容
計算資源 Cloud Run のサービス、または GKE の Deployment
イメージ Artifact Registry のリポジトリ。Cloud Run には linux/amd64 が要り、--provenance=false で buildx の OCI のイメージインデックス(Cloud Run が拒否する)を避ける
ストア プライベート IP だけの Cloud SQL for PostgreSQL(Private Services Access で、公開 IP なし)
秘密 gateway.yaml・JWT の署名キー・OIDC のクライアントシークレット・Postgres の URL を、Secret Manager のシークレット(gateway-config など)に置く。サービスアカウントに roles/secretmanager.secretAccessor
IAM roles/aiplatform.user を持つサービスアカウント(Cloud Run では直接付け、GKE では Workload Identity で結びつける)
フロント 自分で用意する HTTPS のフロント。Cloud Run の前の内部 Application Load Balancer(この手順は設定するが作らない)か、GKE の gce-internal クラスの内部 Ingress

手順です。

  1. プロジェクトとリージョンを決め、必要な API を有効にする
  2. サービスアカウントを作って roles/aiplatform.user を付け、プロジェクトの Model Garden で Claude モデルを有効にする(モデルは特定のリージョンで公開される)
  3. イメージを作って Artifact Registry へ押す
  4. Cloud SQL を作る
  5. gateway.yaml を書く
  6. シークレットを Secret Manager に作る
  7. 配備する(下の Cloud Run・GKE の要点)
  8. 開発者のマシンへ URL を配る(上の「ゲートウェイの URL を配る」。parentSettingsBehavior: "merge" の opt-in を含むスニペット全体)

手順ごとの要点です。

  • 1:API は aiplatform・artifactregistry・sqladmin・secretmanager・iamcredentials・iam・compute・servicenetworking・run・container。compute と servicenetworking はプライベート IP の Cloud SQL 用、run は Cloud Run だけ、container は GKE だけで要る
  • 4:VPC は Private Services Access で、公開 IP の無い Cloud SQL にする(constraints/sql.restrictPublicIp が強制されるプロジェクトでも通る)。Cloud Run や GKE の実行環境は、この VPC の中か、そこへの経路を持つ必要がある
  • 5:upstreams は provider: vertex・region・project_id・auth: {}。Google の id_token にはグループが無い。リフレッシュトークンのために scopes: [openid, profile, email] と extra_auth_params: { access_type: offline, prompt: consent } を書き(Google は offline_access を無視する)、グループでポリシーを分けるなら oidc.google_groups を使う
  • 6:GKE では Secret Manager の CSI ドライバーでファイルとしてマウントし、${file:/secrets/...} で参照する。Cloud Run は複数のシークレットを1つのディレクトリにマウントできないので、gateway.yaml を1つのファイルとしてマウントし、ほかの3つ(${GATEWAY_JWT_SECRET}・${OIDC_CLIENT_SECRET}・${GATEWAY_POSTGRES_URL})は環境変数に注入する

trusted_proxies はフロントに合わせて書きます。gce クラスの外部 GKE Ingress は公開のフォワーディングルールのアドレスを作り、/login のプライベートネットワークの確認に拒否されるので、対象にしません。

フロント trusted_proxies
ロードバランサーなしで直接届く Cloud Run [169.254.0.0/16]
Cloud Run の前の内部 Application Load Balancer 169.254.0.0/16 と、プロキシ専用サブネットの CIDR
GKE の内部 Ingress(gce-internal クラス) プロキシ専用サブネットの CIDR

Cloud Run の配備の要点です。

  • 本番のフラグ:内部ロードバランサーの後ろに置くため --ingress=internal。ほかに --min-instances=1・--max-instances=8・--timeout=3600・VPC への直接の外向き(--network・--subnet・--vpc-egress=private-ranges-only)
  • --max-instances:各インスタンスが最大 store.max_connections(既定5)の Postgres 接続を持つので、最大インスタンス数 × store.max_connections を、Cloud SQL の階層の接続の上限より小さく保つ
  • Vertex AI のエンドポイントと accounts.google.com への公開の外向きは、VPC を通らずインターネットへ直接出るので、Cloud NAT は要らない
  • 設定は gateway-config のシークレットとして /etc/claude/gateway.yaml にマウントし、ほかの3つのシークレットは環境変数に注入する
  • invoker の IAM の確認は開けるか無効にする。ゲートウェイは自分で OIDC を動かし、クライアントは GCP のトークンを持たないので、認証なしのリクエストを通す必要がある(コンテナに届いたあと、ゲートウェイの OIDC のサインインが認証し、allowed_email_domains がサインインできるドメインを絞る)
  • 方法は --no-invoker-iam-check(allUsers の束縛を扱わず、Domain Restricted Sharing でも動く)か、組織が --no-invoker-iam-check を許さないときの --allow-unauthenticated(allUsers に run.invoker を付ける)。--ingress の制限はこれとは別の層なので、サービスを社内ネットワークに限るために付けたままにする
  • *.run.app の URL は既定で公開アドレスに解決され、/login のプライベートネットワークの確認に拒否される。私設に解決できるホスト名を開発者に渡す形は2つあり、どちらも Cloud Run 自体は用意しない
  • 1つ目は内部 Application Load Balancer(この gateway.yaml が前提とする形)。内部の DNS 名と証明書を持つ内部ロードバランサーをサービスの前に置き、listen.public_url をそのホスト名にする。--ingress=internal は内部 Application Load Balancer からの通信をすでに許しているので、internal-and-cloud-load-balancing(外部 Application Load Balancer も許すが、その公開アドレスは /login が拒否する)はどちらの形でも要らない
  • 2つ目はロードバランサーなしの内部専用のイングレスで、listen.public_url は *.run.app の URL のまま。*.run.app が私設に解決されるよう、Google API 向けの Private Service Connect のエンドポイント・*.run.app をそこへ解決する Cloud DNS のプライベートゾーン・そのエンドポイントへのオンプレミスからの経路を、ネットワークチームがすでに運用している必要がある
  • 最初のサインインの前に、OAuth クライアントの許可するリダイレクト URI を <public_url>/oauth/callback にする。public_url を変えたら再デプロイする(公開オリジンはこの設定だけから作り、X-Forwarded-Host と X-Forwarded-Proto は無視する。X-Forwarded-For は、listen.trusted_proxies を書いたときだけクライアント IP に使う)

GKE の配備の要点です。

  • クラスターは、Cloud SQL の手順で作った VPC の上に置く。VPC ピアリングだけでは動かない(Cloud SQL のプライベート IP 自体がピアリングされたネットワークにあり、ピアリングは推移しないため)
  • クラスターとノードプールで Workload Identity を有効にする(Standard クラスターでは既存のノードプールにも GKE_METADATA が要る。Autopilot は既定で有効)。Google のサービスアカウントを Kubernetes のサービスアカウントに結びつけ(roles/iam.workloadIdentityUser、iam.gke.io/gcp-service-account の注釈)、ポッドに資格情報を継がせる
  • 置くもの:serviceAccountName: gateway の Deployment と Service、gce-internal クラスの内部 Ingress、Secret Manager の CSI ドライバーが /secrets にマウントするシークレット、GET /readyz を指す readiness のプローブ
  • GKE の Ingress の後ろのロードバランサーのバックエンドサービスは、既定で30秒のタイムアウトで、長いストリームの応答を切る。ゲートウェイの Service に、timeoutSec を上げた BackendConfig を付ける
  • Workload Identity のクラスターでは、169.254.169.254 を塞ぐ外向きの NetworkPolicy を当てない(ポッドは資格情報のためにメタデータのサーバーへ届く必要があり、そこではゲートウェイの組み込みの SSRF ガードが守りになる)
  • メタデータのエンドポイントに届くと、ゲートウェイは起動時に警告して外向きの NetworkPolicy を勧める。Workload Identity ではポッドがそのエンドポイントを使うので、想定どおりの警告

Google Cloud の配備に固有のトラブルです。

症状 原因 対処
Cloud Run が、コンテナに届く前に 403 Forbidden を返す invoker の IAM の確認がまだ有効 --no-invoker-iam-check で配備するか、--allow-unauthenticated で allUsers に run.invoker を付ける
--no-invoker-iam-check が invoker_iam_disabled is not currently available で拒否される constraints/run.managed.requireInvokerIam が止めている --allow-unauthenticated を使う。constraints/iam.allowedPolicyMemberDomains の Domain Restricted Sharing がそれも止めるなら、allUsers の束縛なしにネットワークの層でゲートウェイを出す GKE の経路を使う
配備時に Container manifest type … must support amd64/linux イメージを amd64 でないホストでビルドした、または buildx が OCI のイメージインデックスを出した --platform=linux/amd64 --provenance=false でビルドする
Cloud Run で、起動が Postgres の接続タイムアウトのエラーで終了する サービスが VPC につながっていない、または Cloud SQL にその VPC のプライベート IP が無い 直接の VPC の外向きのために --network と --subnet を付けて配備し、Cloud SQL を同じ VPC を指す --no-assign-ip と --network で作る
Vertex AI のリクエストが 403 PERMISSION_DENIED を返す 実行環境が claude-gateway のサービスアカウントを使っていない、またはプロジェクトの Model Garden でモデルが有効でない Cloud Run では --service-account を書くか、GKE では Workload Identity を結びつけ、対象のリージョンの各 Claude モデルを Model Garden で有効にする
ストリームの応答が一定の時間で切れる フロントのリクエストのタイムアウト。GKE の Ingress の後ろのロードバランサーのバックエンドサービスは既定30秒、Cloud Run は300秒 GKE では timeoutSec を上げた BackendConfig を付けるか、Cloud Run では --timeout=3600 で配備する

次に進む#

  • 設定を最小から広げる:グループごとの RBAC・複数の上流のフェイルオーバー・テレメトリの宛先を足す(全項目はこのページの「gateway.yaml の設定」)
  • Compose から、Kubernetes や Cloud Run の本番の配備へ移し、IdP を整え、セキュリティのモデルを確かめる(上の「配備と運用」)
  • 開発者やグループに上限を付け、暴走した作業が契約の枠を使い切れないようにする(上の「支出上限」)
  • 通信先の許可リストやプロキシ・mTLS はネットワークと LLM ゲートウェイ、上流のクラウド側の設定はクラウドプロバイダを見てください

公式ドキュメント(英語)

2026年10月5日時点の内容をもとに、日本語でまとめています。

ページの一覧