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側の設定

まず、アカウントレベルで既定オフになっている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 settingsGet Token Issuer URLhttps://<uuid>.tokens.sts.global.api.awsという形式のURLが表示されます。

Anthropic側の設定

Claude ConsoleのSettings → Workload identityConnect 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_accountorg_idprincipal_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なら、WorkloadIdentityCredentialsidentity_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 text

https://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-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_...
      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 TokenEKS 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_tokenexpires_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_IDANTHROPIC_ORGANIZATION_IDANTHROPIC_SERVICE_ACCOUNT_IDANTHROPIC_WORKSPACE_IDは環境変数からそのまま読まれるため、CLIコマンド自体には認証情報を渡す引数がありません。

まとめ

AWS上のワークロードをWIFでClaude APIに繋ぐ方法は2つあります。Lambda・EC2・ECS・EKSのどこでも動くSTS GetWebIdentityToken方式と、EKS限定で設定が2ステップ少ないProjected Service Account Token方式です。どちらもロールARNやサービスアカウント名を完全一致で絞り込み、環境ごとにルールを分けるのが安全な運用です。WIF自体の仕組みはWIFとは何か、環境変数やプロファイルの完全なリファレンスはWIFリファレンスにまとめています。

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