Claude Media
Claude apps gatewayのBedrockでassume_roleとguardrailを設定する

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側の最終チェックは次のとおりです。

  1. ゲートウェイのプリンシパルに、対象ロールへのsts:AssumeRoleがある
  2. 対象ロールの信頼ポリシーが、ゲートウェイのプリンシパルを名指ししている
  3. 対象ロールに、InvokeModel系・CountTokens・ApplyGuardrailの3種が揃っている
  4. 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の構成例が出発点になります。

この記事を共有:XはてブLinkedIn