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・リンクローカル・CGNAT100.64.0.0/10・IPv6 ULAfc00::/7・ループバック。自前でホストするゲートウェイでは、宣言したブロックの外の公開アドレスは拒否される - 開発者のマシンが HTTPS を社内プロキシ経由にしているなら、プロキシのホストも私設に解決される必要がある。無理ならゲートウェイのホストを
NO_PROXYに足す - 社内網を自社所有の公開 IPv4 で組んでいるなら、
gatewayInternalNetworksで宣言する(下の「公開アドレス空間の社内ネットワークを許可する」)
手順#
- IdP に OAuth クライアントを登録する。先にゲートウェイのホスト名を決め、リダイレクト URI を
https://claude-gateway.<自社ドメイン>/oauth/callback(listen.public_urlと同じホスト)にする。client_idとclient_secretを控える - PostgreSQL を用意する(マネージドの最小の階層でよい)。起動時にスキーマを移行するので、ロールにテーブルの作成と変更の権限を与える
- 下の最小の
gateway.yamlを書く。秘密は${ENV_VAR}で展開させれば、ファイルはバージョン管理に置ける。public_urlは、ネットワーク内でプライベート IP に解決されるホスト名にする(/loginが公開アドレスを拒否するため) claudeのバイナリを入れたコンテナイメージを作り、Postgres と並べて起動する(glibc ベース、書き込めるディレクトリをCLAUDE_CONFIG_DIRで指す、コマンドはclaude gateway --config /etc/claude/gateway.yaml)- 下の「サインインの面の確認」の3つを順に通す
- 開発者のマシンの managed settings に
forceLoginMethod: "gateway"とforceLoginGatewayUrlを書き、/loginの「Cloud gateway」の画面で Enter を押してブラウザでサインインする
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つ目のリダイレクト URIhttp://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 で無効にされて更新が失敗すると、再ログインを促す
証明書ファイルからフィンガープリント全体を出すコマンドです。
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 のセッションへ渡せる
{
"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 以降(古い版はキーを無視し、私設アドレスの規則のまま)
{
"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つのブロックの中にある(ブロックの外のレコード・私設や IPv6 のアドレスが混ざれば拒否)
- 開発者のマシンが同じブロックの中から接続している(NAT の内側・コンテナや WSL2・アドレスのプールがブロックの外にある VPN は拒否し、接続元のアドレスを示す)
- 直接の接続である(
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 の手順です。
- managed settings のファイルで、上のスニペット(
parentSettingsBehavior: "merge"を含む)を配る - ファイルより優先されるクライアント側の源があれば、そこにもスニペット全体を写す。Claude Code は
parentSettingsBehaviorを選ばれた源からだけ読み、源にポリシーのキーを足すとその源が選ばれうるため。優先の順は、ゲートウェイ自身のリモート managed settings、macOS の managed-preferences の plist と Windows の HKLM のポリシー、managed-settings.json。ゲートウェイにサインインするマシンのために、ゲートウェイのポリシーのcliブロックにもparentSettingsBehaviorを書く - 選ばれている源を確かめる。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 のロックと、それぞれが受け持つ許可リストを置きます。
{
"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 が、登録された証明書をサムプリントで探せる必要があります。
-
鍵と証明書を作る。暗号化していない 2048 ビット以上の RSA 秘密鍵(PKCS#8 か PKCS#1 の PEM)と、その証明書を用意する。条件を満たさない鍵ではゲートウェイが起動を拒む。次の
opensslで、1年有効の自己署名の証明書つきの鍵ができる。できたidp-client.keyとidp-client.crtを、ゲートウェイが読める場所へ置く(手順3の例は/etc/gateway/)bashopenssl req -x509 -newkey rsa:2048 -nodes -keyout idp-client.key -out idp-client.crt -days 365 -subj "/CN=claude-gateway" -
証明書を IdP へ上げる。上げるのは証明書で、秘密鍵ではない。ゲートウェイのアプリ登録に上げる
-
gateway.yamlにclient_assertionブロックで秘密鍵と証明書を渡す。client_secretは書かない(private_key_jwtと一緒に書くと、ゲートウェイは起動を拒む)。次は Microsoft Entra のテナントへ証明書で認証する例yamloidc: 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と合っていなければ、ゲートウェイは起動を拒む -
ゲートウェイを再起動し、起動ログの次の行を探す
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 GMTSHA-1 のサムプリントを、IdP が上げた証明書について示すものと突き合わせる。証明書が期限切れか、まだ有効でないときは、ゲートウェイは起動するが、置き換えるまでサインインと更新が失敗するという警告を出す。IdP が証明書を受け入れるかは、開発者1人にゲートウェイ経由でサインインしてもらって確かめる
クライアント証明書のローテーション
ゲートウェイは鍵と証明書を起動時に1回だけ読むので、ファイルを変えても再起動するまで効きません。IdP が持たない証明書でトークンを要求しないよう、次の順で回します。
- 新しい証明書を、古いものと並べて IdP へ上げる
gateway.yamlが読む鍵と証明書のファイルを置き換え、ゲートウェイを再起動する- 古い証明書を 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行出る
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つがそろうまで、プロキシ経由だけの外向きはオフのままです。そろわなければ、止めている変数を名指しする警告を起動時に出し、既定の挙動を保ちます。
HTTPS_PROXYかHTTP_PROXYがあるNO_PROXYとno_proxyが空。プラットフォームが注入するなら、ゲートウェイのコンテナで両方を空にする(テレメトリのコレクターをNO_PROXYに載せると、オフのまま)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.comanthropicの 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)の中でリージョンをまたぐ
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 ではほかの上流をすべて飛ばすので、リクエストも、中断したリクエストのトークン数えも、別のアカウントへフェイルオーバーしない - 内蔵のモデル名のリクエストも、この上流へ届きうる。届けば同じロールで署名される。このアカウントでも内蔵のモデルを出してよいのでなければ、この上流を最後に並べる
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 の上流に任せます。
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 以降が要る
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)の上流#
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 の上流#
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のサーバー(未設定ならプロバイダのエンドポイント)。プロキシが外さなければ、プロバイダにも届く
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時間待つ
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 での開発者ごとの支出の強制を有効にします。上限の決め方と強制のしくみは、下の「支出上限」にあります。
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:ブロックが要る(どちらも無いと読む側がいないので、起動を拒否する)
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 のデプロイメント名では必須です。
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 のキャッシュつき)
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 でセッションを始めます。
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 以降 |
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 のウィンドウを設定します。
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 を返す)
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 で出せるモデルにだけ足す
次の例は、アプリケーション推論プロファイルへ向けたカスタムのエイリアスに、選択肢を手で出し、新しいユーザーをそれで始めさせます。
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 以降
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=1OTEL_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 以降。古いゲートウェイはキーがあると起動を拒否するので、全レプリカを上げてからキーを足し、戻す前に外す
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 以降。古いゲートウェイはキーがあると起動を拒否するので、全レプリカを上げてからブロックを足し、戻す前に外す
- オンのあいだ、各プロバイダへのリクエストはいつもどおり作って署名するが、送らずに捨て、通常の応答の経路で定型のダミーの返信をストリームする(返信は、定型であることを述べる文で始まる埋め草のテキスト)
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 のクレーム名も出ます(監査イベントはこれに関係なく常に出る)。
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 を替えるだけでゲートウェイに使える
# 組織全体の既定として、全開発者に月 $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 の見積りで、請求書ではなく遮断器(請求は、提供元の使用量のレポートと突き合わせる)
料率は次の順で選びます。
- そのリクエストに応えた上流の、一致する
pricing.overridesの行(v2.1.227 以降) - 上流のモデル ID(ゲートウェイが提供元へ送る文字列)の定価。Claude Code のコスト表が知っている ID のとき(表は Anthropic・Bedrock・Vertex AI・Foundry の ID の形を受ける)
- その上流の ID に対応づけた
models[].idの定価。Bedrock のアプリケーション推論プロファイルの ARN や Foundry のデプロイメント名のような、モデル名を含まない上流の文字列向け(v2.1.218 以降) - 不明なモデルの階層。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 のsubactor.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の行は仮名の OIDCsubしか持たず、それぞれの期間で消える
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 が満たす条件です。
/.well-known/openid-configurationを出す(本番は HTTPS。http://の issuer も受けるが、ループバックの issuer にはCLAUDE_GATEWAY_ALLOW_LOOPBACK=1も要る)- authorization-code フローに対応する。PKCE は既定でオン(対応しない IdP では
oidc.use_pkce: false) - 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 バイナリを元に、イメージを自分で作ります。
- 固定したリリースから、イメージのアーキテクチャに合う Linux ビルドを取る(URL は、特定のバージョンのインストールの項)
- リリースの GPG 署名つきの
manifest.jsonで検証する(バイナリの完全性とコード署名の項) - ビルドのコンテキストに置く
ビルドからリリースのホストに届かないなら、リリースを社内のレジストリに写し、全台で動かすバージョンを固定します。バイナリのほかにイメージに要るものです。
- 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つの
window_seconds(既定10分)にサインインする人数を見積もる - 再試行と、Claude Code と Claude Desktop の両方にサインインする人を見込んで2倍にする
たとえば、4つの出口アドレスの後ろの10,000人が1時間かけて均等にサインインすると、1アドレスに2,500人で10分あたり約420人。2倍して切り上げ、1,000 にします。
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)とユーザーの IDadmin.denied:拒否された管理者 API の認証の試み。クライアント IP・メソッド・パス・理由(出されたキーの中身は載せない)。理由は、x-api-keyが設定のキーに一致しないinvalid_key、Authorizationヘッダーだけがありadmin.admin_groupsのゲートウェイのセッションとして通らなかったbearer_rejected、どちらのヘッダーも無いno_credentials
- 成功した mint と refresh:
- 運用ログ:起動・警告・上流のエラーの、人が読む
[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 シークレットのローテーション#
既存のセッションを生かしたまま、段階的に回します。
- 新しいシークレットを作り、
session.jwt_secretの配列の先頭に足す - 配備を入れ替える。新しいトークンは新しいシークレットで署名され、古いトークンも検証が通る
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 の接頭辞も合わせる)
手順です。
- セキュリティグループを3つ作る
- IAM のロールを作り、Anthropic の一度きりのユースケースのフォームを送る
- RDS for PostgreSQL を作る
gateway.yamlを書く- 3つのシークレットを Secrets Manager に作る
- イメージを作って ECR へ押す
- ECS Fargate か EKS に配備する
- Route 53 のプライベートホストゾーンで内部 DNS 名を ALB のエイリアスにし、
listen.public_urlと IdP のリダイレクト URI をそろえる - 開発者のマシンへ 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 も呼べる必要がある
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 |
手順です。
- プロジェクトとリージョンを決め、必要な API を有効にする
- サービスアカウントを作って
roles/aiplatform.userを付け、プロジェクトの Model Garden で Claude モデルを有効にする(モデルは特定のリージョンで公開される) - イメージを作って Artifact Registry へ押す
- Cloud SQL を作る
gateway.yamlを書く- シークレットを Secret Manager に作る
- 配備する(下の Cloud Run・GKE の要点)
- 開発者のマシンへ 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 ゲートウェイ、上流のクラウド側の設定はクラウドプロバイダを見てください
公式ドキュメント(英語)
- Claude apps gateway for Amazon Bedrock, Claude Platform on AWS, Google Cloud, and Microsoft Foundry
- Claude apps gateway configuration
- Claude apps gateway spend limits
- Claude apps gateway deployment and operations
- Deploy Claude apps gateway on AWS
- Deploy Claude apps gateway on Google Cloud
2026年10月5日時点の内容をもとに、日本語でまとめています。