Claude apps gatewayのBedrockでassume_roleとguardrailを設定する
Claude apps gatewayのBedrockアップストリームに、STSのIAMロール引き受け(assume_role)とGuardrailを設定する手順と、全アップストリームで揃える制約をまとめます。
Claude apps gatewayのBedrockアップストリームには、v2.1.281で2つの設定キーが加わりました。assume_roleは、ゲートウェイが別のAWSアカウントのIAMロールをSTSで引き受けてBedrockを呼ぶ設定です。guardrailは、Amazon Bedrock Guardrailをゲートウェイ経由の全リクエストに適用する設定です。
どちらもgateway.yamlのアップストリーム1件に数行足すだけで済みます。落とし穴は書き方より、複数のアップストリームを並べたときの組み合わせにあります。この記事では、設定の書き方、必要なIAM権限、失敗時の挙動、そして「全部に付けるか、1つも付けないか」の制約を順に扱います。
v2.1.281で加わった2つのキーは何を変えるのか
Claude apps gatewayは、開発者のClaude Codeからのリクエストを受け、設定したアップストリームへ転送するサーバーです。Bedrockをアップストリームにすると、ゲートウェイ自身のAWS認証情報でBedrockを呼びます。
従来はこの認証情報が1つで、別アカウントのBedrockを使うには静的なアクセスキーを渡す必要がありました。v2.1.281のchangelogでは、次の2点が追加されたと記されています。
assume_role: STSで引き受けたIAMロールとしてBedrockを呼ぶ。別アカウントも可。開発者ごとに1セッションにもできるguardrail: {id, version}: Bedrock Guardrailを、そのアップストリーム経由の全リクエストに適用する(全Bedrockアップストリームに付けるか、1つも付けないかのどちらか)
どちらもゲートウェイサーバー側がv2.1.281以降である必要があります。古いゲートウェイはassume_roleのキーを見つけると起動を拒否します。レプリカを複数並べている場合は、全台を上げてから設定を入れる順序になります。キー全体の一覧はgateway.yaml設定リファレンスにあり、リリース全体の変更はv2.1.281のリリースノートで追えます。
assume_roleで別アカウントのBedrockを呼ぶ
設定の書き方
assume_roleを置くと、ゲートウェイは自分のAWS IDをsts:AssumeRoleの呼び出しにだけ使います。Bedrockへの全リクエストは、STSが返す1時間有効の認証情報で署名されます。長期のアクセスキーがアカウントをまたいで流れません。
upstreams:
- name: bedrock-isolated
provider: bedrock
region: us-east-1
auth: {} # ゲートウェイ自身のロール。STSを呼ぶだけ
assume_role:
role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
# external_id: ${BEDROCK_ROLE_EXTERNAL_ID} # 信頼ポリシーが要求するとき上の例は公式ドキュメントの記載に沿った形です。auth: {}はAWS SDKの既定の認証情報チェーンを使う指定で、ECSのタスクロールやEKSのIRSAがそのまま拾われます。
assume_roleブロックのキーは3つです。
| キー | 意味 |
|---|---|
role_arn | 意味引き受けるIAMロール。arn:aws:iam::またはarn:aws-us-gov:iam::の形式 |
external_id | 意味任意。毎回のsts:AssumeRoleに外部IDとして送る。数字だけの値は引用符で囲む |
session_name | 意味任意。emailかsubで開発者ごとのセッションになる。未設定なら全リクエストがclaude-apps-gatewayという1つのセッションを使う |
2つのアカウントに必要なIAM権限
権限は両側に分かれます。ゲートウェイ側のプリンシパル(IRSAやECSタスクロール)に必要なのは、対象ロールへのsts:AssumeRoleだけです。Bedrockの権限は持たせません。
引き受けられるロール側には、そのアップストリームが必要とするBedrock権限を付けます。bedrock:InvokeModelとbedrock:InvokeModelWithResponseStreamに加え、bedrock:CountTokensも要ります。ゲートウェイはクライアントが中断したリクエストの入力トークン数を、このアクションで数えているためです。支出上限の見積もりを狂わせないための呼び出しで、付けない場合は1トークンのBedrockリクエストで代替します。
ロールの信頼ポリシーは、ゲートウェイ自身のプリンシパルを名指しします。ドキュメントの例は次の形です。external_idを設定しないならConditionは外します。
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::111111111111:role/claude-gateway" },
"Action": "sts:AssumeRole",
"Condition": { "StringEquals": { "sts:ExternalId": "your-external-id" } }
}]
}開発者ごとにセッションを分ける
session_name: email(またはsub)を足すと、ゲートウェイは開発者ごとに1時間に1回sts:AssumeRoleを呼び、開発者のメールをセッション名にします。AWS側には、開発者ごとの引き受けロールセッションとして現れます。この場合、ロールはゲートウェイと同じアカウントにあっても構いません。
セッション名に使えない文字は=XXの16進に置き換えられ、64文字を超える分は接頭辞とハッシュに縮められます。トークンに該当クレームが無い開発者のリクエストは、このアップストリームを通りません。その場合はsubに切り替えるか、oidc.email_claimを設定します。コストの目安は、アクティブな開発者1人につきゲートウェイのレプリカごとに1時間1回のSTS呼び出しです。支出上限との関係は支出上限の解説に詳しく書かれています。
厳密な開発者単位の帰属が必要なら、列挙したすべてのBedrockアップストリームにsession_name付きのassume_roleを置きます。付いていないアップストリームは、自分の認証情報で署名します。中断リクエストのトークン数確認(と1トークンの代替リクエスト)も共有セッションclaude-apps-gatewayで署名されるので、AWS上では開発者ではなくこのセッションの利用として見えます。
STSが失敗したとき、どこへ流れるか
ここが最も見落としやすい点です。STSが拒否したり到達できなかったりしても、ゲートウェイはアップストリーム自身の認証情報で代わりに送ることはしません。STSのエラーをログに残し、リストの次のアップストリームを試します。
次のアップストリームがassume_roleを持たなければ、そちらは自分の認証情報でリクエストを処理します。つまり、リストの並び方で「どのアカウントが請求を受けるか」が変わります。ドキュメントは、assume_roleのない後続アップストリームは、そうなっていてよい場合だけ置くようにと注意しています。
どのアップストリームも成功しなかったときに開発者が受け取る内容は、アップストリームの種類で変わります。Bedrockの400や413は、Anthropic標準のエラー形式ならその文面、AWS独自の形式ならcapability_rejected:トークンに置き換わります。たとえばBedrockのInput is too long for requested model.はcapability_rejected: prompt_too_longになり、Claude Code側で自動コンパクトが走ります。ロールARNやアカウントIDはBedrockのエラー文に含まれうるため、全文はゲートウェイの運用ログにだけ残り、開発者には出ません。
そのほかの動作条件は3つです。
- ゲートウェイは地域別のSTSエンドポイント
sts.<region>.amazonaws.comを呼ぶので、ネットワークから届く必要がある - FIPSエンドポイントを使うときは、AWS設定ファイルの
use_fips_endpointではなく、ゲートウェイの環境変数AWS_USE_FIPS_ENDPOINT=trueで指定する assume_roleはprovider: bedrock専用で、SigV4の元認証情報が前提。aws_bearer_tokenと併用するとゲートウェイは起動を拒否する
別アカウントのモデルを他へ流さない
引き受けたロール経由で配信するモデルを、別アカウントからは配信させたくない場合があります。ドキュメントの方法は、組み込みモデル名ではないカスタムIDを作り、upstream_modelにそのアップストリームの名前だけを書くことです。
models:
- id: claude-opus-restricted # 組み込みモデル名ではないカスタムID
upstream_model:
bedrock-isolated: us.anthropic.claude-opus-4-8 # このアップストリームだけが配信するこのIDのリクエストは他のアップストリームをスキップするので、本体も中断時のトークン数確認も別アカウントへフェイルオーバーしません。一方、組み込みモデル名のリクエストは、同じロールで署名されてこのアップストリームに届きえます。そのアカウントにも組み込みモデルを配信させたくないなら、リストの最後に置きます。管理ポリシーのmanagedが制御するのはどの開発者がどのモデルを使えるかであり、ゲートウェイが受け入れた開発者は誰でもこのアップストリームを使えます。
guardrailは「全部か、なしか」
書き方と権限
Bedrock Guardrailを適用するには、アップストリームにguardrailブロックを足します。
upstreams:
- provider: bedrock
region: us-east-1
auth: {}
guardrail:
id: gr-abc123 # GuardrailのIDまたは完全なARN
version: "1" # 発行済みバージョン番号、またはDRAFT
# 引用符は必須。裸の 1 は起動時に失敗するversionは文字列で書きます。YAMLの数値として1と書くと、起動時に失敗します。
署名するプリンシパルには、そのGuardrailへのbedrock:ApplyGuardrailが必要です。ゲートウェイ自身のAWSプリンシパルか、assume_roleを使うならrole_arnのロールです。ここをassume_roleのロールに付け忘れると、別アカウント構成でGuardrailだけが通らなくなります。
一部だけには付けられない
guardrailは、すべてのbedrockアップストリームに設定するか、1つにも設定しないかのどちらかです。混在させるとゲートウェイは起動しません。理由はフェイルオーバーにあります。Guardrailなしのアップストリームが混ざっていると、障害時にリクエストがフィルタを通らない経路へ流れるからです。
この制約は、Claude Code本体でGuardrailのヘッダーを配る方式との大きな違いです。本体の方式では、ANTHROPIC_CUSTOM_HEADERSで渡すGuardrail設定が開発者ごとの設定に依存します。ゲートウェイ方式なら、サーバー側の1か所で強制できます。
効かない範囲
Guardrailが及ぶのはBedrockアップストリームだけです。他のプロバイダーをリストに並べると、ゲートウェイはそのプロバイダーへGuardrailなしで転送します。ただしmantleプロバイダーは例外で、Bedrockアップストリームにguardrailが設定されている間は、mantleを並べるとゲートウェイが起動を拒否します。Mantleへ送るリクエストにはGuardrailを適用できないからです。
mantleアップストリームはassume_roleも受け付けません。Mantleが処理するリクエストはmantle自身のauthで署名され、開発者ごとの帰属にもなりません。
覚えておきたい制限が2つあります。
- ゲートウェイはGuardrailの入力タグを扱わない。プロンプトにガードコンテンツタグを付けないため、タグ付きの入力にだけ働くフィルタは、ゲートウェイ経由では動かない
/v1/messagesのボディにamazon-bedrock-guardrailConfigのようなamazon-bedrock-*フィールドがあり、guardrail設定済みのアップストリームに届くと、ゲートウェイは転送せず400を返す
2つ目は、クライアントがリクエスト側で独自のGuardrail設定を差し込む抜け道を塞ぐ挙動です。
2つを組み合わせた設定例
別アカウントのBedrockを開発者ごとのセッションで呼び、そのうえでGuardrailを強制する構成は次のようになります。例として、アカウントIDとGuardrail IDは仮の値です。
upstreams:
- name: bedrock-main
provider: bedrock
region: us-east-1
auth: {}
assume_role:
role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
session_name: email
guardrail:
id: gr-abc123
version: "3"
- name: bedrock-west
provider: bedrock
region: us-west-2
auth: {}
assume_role:
role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
session_name: email
guardrail:
id: gr-abc123
version: "3"2件のアップストリームに同じassume_roleとguardrailを並べています。片方だけから外すと、guardrailの混在でゲートウェイが起動しません。assume_roleのほうは起動エラーにならないため、外したことに気づきにくい点に注意が要ります。外した側は自分の認証情報で署名し、そのアップストリームだけ開発者単位の帰属から外れます。
IAM側の最終チェックは次のとおりです。
- ゲートウェイのプリンシパルに、対象ロールへの
sts:AssumeRoleがある - 対象ロールの信頼ポリシーが、ゲートウェイのプリンシパルを名指ししている
- 対象ロールに、
InvokeModel系・CountTokens・ApplyGuardrailの3種が揃っている - Guardrailの
versionが文字列で、発行済みの番号になっている
よくあるつまずき
- 起動時に
assume_roleで落ちる: ゲートウェイがv2.1.281未満の可能性があります。レプリカの一部だけ古い構成でも同様です - 起動時にGuardrailの混在で落ちる:
bedrockアップストリームのうち、guardrailを持たないものがあります。mantleを並べている場合も、同じ理由で拒否されます - STSエラーのあと別アカウントで処理された: 後続のアップストリームが
assume_roleなしで、自分の認証情報で処理しています。並び順を見直します - Guardrailが特定の入力に反応しない: 入力タグが前提のフィルタは、ゲートウェイ経由では動きません。Guardrail側のフィルタ種別を確認します
- メールを使うと一部の開発者だけ通らない: トークンにメールのクレームがありません。
session_name: subかoidc.email_claimを検討します
まとめ
assume_roleは、アカウントをまたぐBedrock利用から長期のアクセスキーを消し、必要なら開発者単位の帰属まで進められる設定です。guardrailは、サーバー側でフィルタを強制する代わりに、全Bedrockアップストリームへ付けることを求めます。
2つに共通する運用上の要点は、アップストリームの並び順と数が設定の意味を決めることです。guardrailは混在すると起動エラーになりますが、assume_roleは付け忘れても黙って通ります。複数アップストリームを持つ構成では、全件を見比べておくと設定の食い違いに気づけます。AWSへの全体の構築手順はECS FargateとRDSの構成例が出発点になります。