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インスタンスのいずれか)
- ワークロード内で使える
awsCLIまたはAWS SDK - Anthropic組織でサービスアカウント・連合発行者・連合ルールを作成できる権限
STS Web Identity Tokenを使う方式(推奨)
AWS STSのGetWebIdentityToken APIは、呼び出し元のIAMアイデンティティを主張するAWS署名済みのOIDCトークンを返します。ワークロードが持つアンビエントなAWS認証情報をそのまま使うため、Lambda・EC2・ECS・EKSのどれでも同じ実装で動きます。
AWS側の設定
まず、アカウントレベルで既定オフになっているOutbound web identity federationを有効化します。
python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"有効化していないと、GetWebIdentityTokenの呼び出しがOutboundWebIdentityFederationDisabledExceptionで失敗します。次に、ワークロードが動くIAMロールにsts:GetWebIdentityTokenを許可するポリシーをアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["sts:GetWebIdentityToken"],
"Resource": "*"
}
]
}最後に、アカウント固有のSTS発行者URLを控えます。IAM > Account settingsのGet Token Issuer URLにhttps://<uuid>.tokens.sts.global.api.awsという形式のURLが表示されます。
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も含まれるため、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は省略できます。ルールが複数ワークスペースにまたがる場合だけ、どのワークスペース宛てのトークンかを明示する必須フィールドに変わります。ワークスペースを跨いで同じIAMロールを使い回す構成では、この条件付き必須を見落として交換が失敗しがちなので、複数ワークスペース構成では常にworkspace_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クラスターとアクセスが要ります。
EKSクラスターの設定
クラスター固有のOIDC発行者URLをまず取得します。
aws eks describe-cluster \
--name <cluster-name> \
--query "cluster.identity.oidc.issuer" \
--output texthttps://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...のような形式のURLが返ります。次に、eks.amazonaws.com/role-arnアノテーションを付けたサービスアカウントを作成し、Anthropicのオーディエンス向けに専用のトークンをPodへ投影します。IRSA(IAM Roles for Service Accounts)のウェブフックが自動投影するaud: sts.amazonaws.comのトークンはAWS API呼び出し専用で、この交換には使えません。
apiVersion: v1
kind: ServiceAccount
metadata:
name: inference-worker
namespace: inference
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-workerapiVersion: 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_...
volumeMounts:
- name: anthropic-token
mountPath: /var/run/secrets/anthropic.com
readOnly: true投影されたトークンのsubクレームはKubernetesの慣習どおりsystem:serviceaccount:<namespace>:<service-account-name>の形式です。
Anthropic側の設定
発行者はEKSのクラスターごとに1つ登録します。公開JWKSエンドポイントを持つのでdiscoveryモードを使います。
{
"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仕様がこの環境変数と連合4変数をすでにセットしているため、SDKは引数なしでクライアントを構築するだけで自動的に環境変数から資格情報を解決します。
STS方式とEKS方式、どちらを選ぶか
| 観点 | STS Web Identity Token | EKS Projected Service Account Token |
|---|---|---|
| 動く場所 | STS Web Identity TokenLambda・EC2・ECS・EKSすべて | EKS Projected Service Account TokenEKSのPod内のみ |
| AWS側の設定 | STS Web Identity Token3手順(federation有効化・IAMポリシー・発行者URL取得) | EKS Projected Service Account Token1手順(クラスターOIDC発行者の取得) |
| ワークロード側の実装 | STS Web Identity TokenSTS呼び出し関数をトークンプロバイダーに渡す | EKS Projected Service Account Tokenファイルパスを渡すだけ(呼び出し不要) |
| 向く構成 | STS Web Identity Token複数サービス種別が混在する環境、Lambda・ECSを含む構成 | EKS Projected Service Account TokenEKS専業の環境、Pod数が多くシンプルさを優先したい構成 |
Lambda・ECS・EC2を含む混在環境ではSTS方式に統一したほうが運用がシンプルになります。EKSしか使わないなら、AWS側の設定が1手順少なく済むProjected Token方式のほうが素早く導入できます。
検証とよくあるつまずき
どちらの方式でも、ワークロード内でトークン交換を直接叩いて動作確認できます。成功するとsk-ant-oat01-で始まるaccess_tokenとexpires_in(秒)が返ります。
交換が401のauthentication_error(メッセージは常にAuthentication failed)で失敗する場合、認証履歴ページで却下理由を確認するのが最初の一手です。よくある原因は次のとおりです。
- STS方式で
issが一致しない: アカウント固有のSTS発行者URLと、登録したissuer_urlが完全一致しているかを確認します - EKS方式で
audが一致しない: IRSAの既定トークン(aud: sts.amazonaws.com)を渡していないか、専用オーディエンスで投影したトークンを使っているかを確認します GetWebIdentityTokenがリージョン未指定で失敗する:region_nameを明示し、AWS SDKのバージョンを確認します
ルールのスコープを絞る
ワークロードが許す範囲で、できるだけ狭くマッチ条件を絞ります。
- ロールARNを完全指定する: 末尾に
*を付けず、subject_prefix: "arn:aws:iam::<account>:role/<role-name>"のように完全一致させます - アカウントIDも固定する:
claimsマップやCEL条件でaws_accountをマッチさせ、subject_prefixの設定ミスに対する多層防御にします - EKSでは名前空間とサービスアカウント名を完全指定する:
system:serviceaccount:<namespace>:<name>の後ろに*を付けません - 環境ごとに別のルールを使う: 本番・ステージング・開発を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 \
--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コマンド自体には認証情報を渡す引数がありません。
まとめ
AWS上のワークロードをWIFでClaude APIに繋ぐ方法は2つあります。Lambda・EC2・ECS・EKSのどこでも動くSTS GetWebIdentityToken方式と、EKS限定で設定が2ステップ少ないProjected Service Account Token方式です。どちらもロールARNやサービスアカウント名を完全一致で絞り込み、環境ごとにルールを分けるのが安全な運用です。WIF自体の仕組みはWIFとは何か、環境変数やプロファイルの完全なリファレンスはWIFリファレンスにまとめています。