Claude Media
WIFをAWSと連携する — STS Web Identity TokenとEKSの投影トークン

WIFをAWSと連携する — STS Web Identity TokenとEKSの投影トークン

AWS上のワークロードをAPIキーなしでClaude APIに認証させる2つの方式、STS GetWebIdentityTokenとEKSのProjected Service Account Tokenを設定します。

対象ワークロードと前提条件

AWS上のLambda・EC2・ECS・EKSで動くワークロードを、Workload Identity Federation(WIF)を使って静的なAPIキーなしでClaude APIに認証させる方法を扱います。AWSにはSTSのGetWebIdentityTokenを使う方式と、EKS限定でKubernetesのProjected Service Account Tokenを直接使う方式の2つがあり、どちらも最終的にはAnthropicのPOST /v1/oauth/tokenでJWTを短命アクセストークンに交換します。

前提条件:

  • WIFの基本概念(サービスアカウント・連合発行者・連合ルール)への理解
  • IAMロールを付与済みのAWSワークロード(EKS Pod・ECSタスク・Lambda関数・EC2インスタンスのいずれか)
  • ワークロード内で使えるaws CLIまたはAWS SDK
  • Anthropic組織でサービスアカウント・連合発行者・連合ルールを作成できる権限

STS Web Identity Tokenを使う方式(推奨)

AWS STSのGetWebIdentityToken APIは、呼び出し元のIAMアイデンティティを主張するAWS署名済みのOIDCトークンを返します。ワークロードが持つアンビエントなAWS認証情報をそのまま使うため、Lambda・EC2・ECS・EKSのどれでも同じ実装で動きます。

AWS側の設定

AWS側の作業は3つで、順番どおりに進めます。

手順

AWS側の3手順

  1. 1

    Outbound web identity federationを有効にする

    アカウント単位の設定で、既定ではオフです。コンソールならIAM > Account settingsから有効にでき、コードからは次の1行で済みます。

    python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"

    有効化していないと、GetWebIdentityTokenの呼び出しがOutboundWebIdentityFederationDisabledExceptionで失敗します。

  2. 2

    IAMロールにsts:GetWebIdentityTokenを許可する

    Lambda関数・EC2インスタンス・ECSタスクが使うIAMロールに、次のポリシーをアタッチします。

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["sts:GetWebIdentityToken"],
          "Resource": "*"
        }
      ]
    }
  3. 3

    アカウント固有のSTS発行者URLを控える

    有効化の後、IAM > Account settingsのGet Token Issuer URLにhttps://<uuid>.tokens.sts.global.api.awsの形式でURLが表示されます。このURLはAWSアカウントごとに固有です。コードで取るなら、get_outbound_web_identity_federation_info()を呼びます。

    python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"

Anthropic側の設定

Claude ConsoleのSettings → Workload identityでConnect workloadを選び、AWSタイルからウィザードを進めます。ウィザードが発行者・サービスアカウント・連合ルールの3つをまとめて作成しますが、Admin APIから直接組む場合の値は次のとおりです。

連合発行者は、控えたSTS発行者URLをdiscoveryモードで登録します。

{
  "name": "aws-sts",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "jwks": { "type": "discovery" }
}

連合ルールは、GetWebIdentityTokenに渡すオーディエンスと、呼び出し元IAMロールのARN(subクレーム)をマッチさせます。トークンにはhttps://sts.amazonaws.com/のクレームとしてaws_account・org_id・principal_idと、呼び出し時に渡したrequest_tagsも入ります。claimsマップやCEL条件で、これらを使ってさらに絞り込めます。

{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "arn:aws:iam::123456789012:role/inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}

連合ルールが有効なワークスペースが1つだけなら、トークン交換リクエストのworkspace_idは省略できます。サーバー側がそのワークスペースを選びます。

ルールが複数のワークスペースで有効な場合は、workspace_idが必須に変わります。省くと401のauthentication_errorになり、認証履歴にはworkspace_id_requiredという理由が残ります。複数ワークスペース向けのルールでは、ANTHROPIC_WORKSPACE_IDにwrkspc_...のIDを渡します。発行されるトークンはそのワークスペースに固定されるため、別のワークスペースを使うときは交換をやり直します。

トークンを取得して使う

GetWebIdentityTokenのオーディエンスをhttps://api.anthropic.comに指定して呼び出し、結果をSDKの連合クレデンシャルへ渡します。トークンプロバイダーは呼び出し可能な関数として渡すため、SDKは更新のたびにSTSを再呼び出しします。

JWT=$(aws sts get-web-identity-token \
  --region us-east-1 \
  --audience "https://api.anthropic.com" \
  --signing-algorithm RS256 \
  --duration-seconds 900 \
  --query WebIdentityToken --output text)
 
RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
  -H "content-type: application/json" \
  --data @- <<JSON
{
  "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  "assertion": "$JWT",
  "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
  "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
  "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
  "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
}
JSON
)
 
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

Python SDKなら、WorkloadIdentityCredentialsにidentity_token_providerとしてSTS呼び出し関数を渡すだけです。

import boto3
from anthropic import Anthropic, WorkloadIdentityCredentials
 
 
def get_sts_web_identity_token() -> str:
    sts = boto3.client("sts", region_name="us-east-1")
    resp = sts.get_web_identity_token(
        Audience=["https://api.anthropic.com"],
        SigningAlgorithm="RS256",
        DurationSeconds=900,
    )
    return resp["WebIdentityToken"]
 
 
client = Anthropic(
    credentials=WorkloadIdentityCredentials(
        identity_token_provider=get_sts_web_identity_token,
        federation_rule_id="fdrl_...",
        organization_id="00000000-0000-0000-0000-000000000000",
        service_account_id="svac_...",
    ),
)

EKS Projected Service Account Tokenを使う方式

EKSのPod内で動くワークロードなら、STS呼び出しを省いてKubernetesがPodに直接投影するProjected Service Account Tokenをそのまま使えます。SDKがファイルパスから読めるため、呼び出し可能なトークンプロバイダーすら不要です。AWS側の設定手順がSTS方式より2つ少ない代わりに、Pod内でしか動きません。基盤の仕組みはKubernetesとの汎用連携と同じです。この方式にはさらに、IAM OIDCプロバイダーを有効化済みのEKSクラスターと、そのクラスターへのkubectlアクセスが要ります。

EKSクラスターの設定

クラスター固有のOIDC発行者URLをまず取得します。

aws eks describe-cluster \
  --name <cluster-name> \
  --query "cluster.identity.oidc.issuer" \
  --output text

https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...のような形式のURLが返ります。次に、eks.amazonaws.com/role-arnアノテーションを付けたサービスアカウントを作成し、Anthropicのオーディエンス向けに専用のトークンをPodへ投影します。EKSのPod identity webhookは、eks.amazonaws.com/role-arnアノテーションを見つけるとaud: sts.amazonaws.comのトークンを自動で投影し、パスをAWS_WEB_IDENTITY_TOKEN_FILEに入れます。IRSA(IAM Roles for Service Accounts)が使うこのトークンはAWSのロール引き受け用で、Anthropicとの交換には使えません。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: inference-worker
  namespace: inference
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-worker
apiVersion: v1
kind: Pod
metadata:
  name: inference-worker
  namespace: inference
spec:
  serviceAccountName: inference-worker
  volumes:
    - name: anthropic-token
      projected:
        sources:
          - serviceAccountToken:
              audience: https://api.anthropic.com
              expirationSeconds: 3600
              path: token
  containers:
    - name: app
      image: your-registry/inference-worker:latest
      env:
        - name: ANTHROPIC_IDENTITY_TOKEN_FILE
          value: /var/run/secrets/anthropic.com/token
        - name: ANTHROPIC_FEDERATION_RULE_ID
          value: fdrl_...
        - name: ANTHROPIC_ORGANIZATION_ID
          value: 00000000-0000-0000-0000-000000000000
        - name: ANTHROPIC_SERVICE_ACCOUNT_ID
          value: svac_...
        - name: ANTHROPIC_WORKSPACE_ID  # ルールが複数ワークスペースで有効なときは必須
          value: wrkspc_...
      volumeMounts:
        - name: anthropic-token
          mountPath: /var/run/secrets/anthropic.com
          readOnly: true

投影されたトークンはクラスターのOIDC発行者が署名したJWTで、subクレームはKubernetesの慣習どおりsystem:serviceaccount:<namespace>:<service-account-name>の形式です。中身は次のようになります。

{
  "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "sub": "system:serviceaccount:inference:inference-worker",
  "aud": ["https://api.anthropic.com"],
  "kubernetes.io": {
    "namespace": "inference",
    "serviceaccount": { "name": "inference-worker", "uid": "..." }
  }
}

Anthropic側の設定

発行者はEKSのクラスターごとに1つ登録します。公開JWKSエンドポイントを持つのでdiscoveryモードを使い、issuer_urlはトークンのissクレームと完全に一致させます。

{
  "name": "prod-eks-uswest2",
  "issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "jwks": { "type": "discovery" }
}

連合ルールはKubernetesのsubクレームとAnthropicのオーディエンスをマッチさせます。IRSAが自動投影する既定トークン(aud: sts.amazonaws.com)を再利用せず、専用のオーディエンスで投影したトークンを使う点が肝心です。

{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "system:serviceaccount:inference:inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}

Pod内では、投影されたトークンがANTHROPIC_IDENTITY_TOKEN_FILEが指すパスにあります。Pod仕様がANTHROPIC_IDENTITY_TOKEN_FILEと連合用の4変数をすでにセットしているため、SDKは引数なしでクライアントを構築するだけで自動的に環境変数から資格情報を解決します。

STS方式とEKS方式、どちらを選ぶか

くらべる

STS方式とEKS方式の違い

どこでも動く

STS Web Identity Token

Lambda・EC2・ECS・EKSのすべてで動きます。ワークロード側はSTSを呼ぶ関数をトークンプロバイダーに渡す実装で、AWS側は上の3手順が要ります。Lambda・ECSを含む構成や、複数のサービス種別が混在する環境でも、同じ実装で使えます。

EKSのPod内だけ

EKS Projected Token

STS方式よりAWS側の設定が2手順少なく、ワークロード側はファイルパスを渡すだけで呼び出しが要りません。IAM OIDCプロバイダーを有効にしたEKSクラスターとkubectlが前提で、EKSのPod内だけで動かす環境向きです。

検証とよくあるつまずき

どちらの方式でも、ワークロード内でトークン交換を直接叩いて動作確認できます。成功するとsk-ant-oat01-で始まるaccess_tokenとexpires_in(秒)が返ります。

交換が401のauthentication_error(メッセージは常にAuthentication failed)で失敗したら、認証履歴ページを開きます。レスポンスには理由が出ず、却下理由は履歴にだけ残るためです。原因ごとの見分け方は次のとおりです。

症状・履歴の理由原因対処
STS方式でissが合わない原因登録したissuer_urlがアカウント固有のSTS発行者URLと違う対処1文字、末尾のスラッシュまで含めて完全一致させる
EKS方式でaudが合わない原因IRSAの既定トークン(aud: sts.amazonaws.com)を渡している対処audience: https://api.anthropic.comで投影したトークンを使う
jti_reused原因jtiを持つトークンは既定で一度しか交換できず、同じJWTを再送した対処交換のたびに新しいトークンを取り直す
workspace_id_required原因複数ワークスペースで有効なルールにworkspace_idを付けていない対処ANTHROPIC_WORKSPACE_IDを設定する
exp・nbf・iatの検証失敗原因ホストの時計がずれている。許容は30秒対処時刻同期を確認する

ここまでで原因が絞れないときは、送ったJWTをデコードしてクレームを見比べます。iss・aud・sub・expを、登録した発行者とルールの値に突き合わせてください。

jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' <<< "$JWT"

発行者の署名鍵が入れ替わった直後は、Anthropic側の鍵キャッシュが更新されるまで最大1分、署名エラーで交換が失敗することがあります。

交換したトークンはいつまで使えるか

発行されるAnthropicのアクセストークンの寿命は、連合ルールのtoken_lifetime_secondsと、提示したJWTの残り寿命の2倍のうち短いほうです。ただし60秒を下回りません。ルールの値は60秒から86,400秒までで、既定は3,600秒です。

この記事の例に当てはめると次のとおりです。

数字

この記事の設定での寿命

  • ルールの上限

    600秒

    token_lifetime_seconds

  • STSのJWT

    900秒

    --duration-seconds 900

  • 発行トークン

    600秒

    min(600, 900×2)

SDKはトークンを保持し、期限の120秒前に更新を試みます。このとき通信に失敗しても、残り約90秒は保持したトークンで動き続けます。期限の30秒前の更新に失敗すると、エラーになります。STS方式ではこの更新のたびにプロバイダー関数が呼ばれ、新しいJWTを取り直します。

ルールのスコープを絞る

マッチ条件はワークロードが許す範囲で、できるだけ狭く絞ります。subject_prefixを広げてよいのは、複数のIAMロールを同じAnthropicサービスアカウントに対応させたいときだけです。

  • ロールARNを完全指定する: 末尾に*を付けず、subject_prefix: "arn:aws:iam::<account>:role/<role-name>"のように完全一致させます
  • アカウントIDも固定する: claimsマップやCEL条件でaws_accountをマッチさせ、subject_prefixの設定ミスに対する多層防御にします
  • EKSでは名前空間とサービスアカウント名を完全指定する: system:serviceaccount:<namespace>:<name>の後ろに*を付けません。system:serviceaccount:inference:*のように末尾に*を置くと、名前空間内の全サービスアカウントが同じAnthropicのサービスアカウントに対応します
  • 環境ごとに別のルールを使う: 本番・ステージング・開発を1つのプレフィックスでまとめず、ルールを分けます

SDKを使わずCLIで呼ぶ場合

言語SDKを組み込めないシェルスクリプトからは、取得したトークンを一時ファイルに書き出し、ANTHROPIC_IDENTITY_TOKEN_FILE環境変数でantコマンドに読ませる方法が使えます。

TOKEN_FILE=$(mktemp)
aws sts get-web-identity-token \
  --region us-east-1 \
  --audience "https://api.anthropic.com" \
  --signing-algorithm RS256 \
  --duration-seconds 900 \
  --query WebIdentityToken --output text > "$TOKEN_FILE"
 
export ANTHROPIC_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
ant messages create \
  --model claude-opus-5-5 \
  --max-tokens 1024 \
  --message '{role: user, content: "Hello from AWS"}'

ANTHROPIC_FEDERATION_RULE_ID・ANTHROPIC_ORGANIZATION_ID・ANTHROPIC_SERVICE_ACCOUNT_ID・ANTHROPIC_WORKSPACE_IDは環境変数からそのまま読まれるため、CLIコマンド自体には認証情報を渡す引数がありません。

まとめ

STS方式が推奨経路で、EKSのPod内に限れば投影トークンでAWS側の手順を2つ省けます。仕組みの全体像はWIFとは何か、環境変数やプロファイルの詳細はWIFリファレンスにあります。

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