ClaudeのWIFをSPIFFE/SPIREと連携する手順
SPIREが発行するJWT-SVIDをClaude APIの認証に使う設定を、OIDC Discovery ProviderとKubernetesの構成で説明する。
SPIFFE対応が持つ意味
Workload Identity Federation(WIF)が対応するIdPは、AWS・Google Cloud・GitHub Actionsのような単一クラウドのネイティブIDだけではありません。SPIFFEはCNCFが定めるワークロードID発行の標準規格で、SPIREはそのオープンソース実装です。ChatGPTやGeminiなど他社の主要なLLM APIでSPIFFE連携を明示的に文書化しているものは見当たらず、この対応はマルチクラウド・オンプレ混在環境を持つ組織にとって珍しい選択肢になります。
SPIFFEはワークロードごとにspiffe://<trust-domain>/<path>という安定したID URIを割り当て、SPIREはそれをJWT-SVID(JWT形式のSVID)として必要な時に発行します。JWT-SVIDは普通の署名付きJWTで、subクレームがSPIFFE ID、audクレームは取得時にワークロード側が指定します。AnthropicはOIDC互換のJWT-SVIDを発行するSPIFFE実装であれば、SPIREに限らずどれでも連携できます。
前提条件
- ワークロードIDが発行済みのSPIFFE環境(この記事の例はSPIRE Server/Agent)と、Claude APIを呼ぶワークロード向けの登録エントリ
- trust domain用のOIDC discoveryエンドポイント(SPIREではOIDC Discovery Provider)が公開HTTPSで到達可能であること、または登録用にエクスポート済みのJWKS
- JWT-SVIDの
issクレームを、federation issuerのissuer_urlとして登録する値に一致させる設定(discoveryモードならdiscoveryエンドポイントの公開URL、SPIREではjwt_issuerサーバー設定) - JWT-SVID(X.509-SVIDではない)がワークロードから取得可能であること
- Claude Consoleでサービスアカウント・federation issuer・federation ruleを作成できる権限
JWT-SVID取得時にリクエストするaudience値は常にhttps://api.anthropic.comです。spiffe-helperのjwt_audience、Workload APIのFetchJWTSVID呼び出し、federation ruleのaudienceマッチャーのすべてでこの値を使います。
SPIREを構成する
このセクションはSPIRE固有です。別のSPIFFE実装を使う場合は、そのプロバイダーのOIDC discoveryとJWT-SVID取得の手順に従い、Anthropic側の設定セクションから読み進めてください。
JWT issuerを揃える
AnthropicはJWT-SVIDのissクレームを登録済みのfederation issuerと突き合わせ、そのissuerのdiscoveryドキュメントからJWKSを取得して署名を検証します。ここで2つのSPIRE設定が同じURLを指している必要があります。SPIRE Serverのjwt_issuer(発行されるすべてのJWT-SVIDのissクレームになる値)と、OIDC Discovery Providerのdomains(discoveryドキュメントとJWKSを配信するホスト)です。
server {
trust_domain = "prod.example.com"
jwt_issuer = "https://oidc-discovery.prod.example.com"
default_jwt_svid_ttl = "5m"
# ...
}SPIREの既定のJWT-SVID寿命は5分と短く、継続的なローテーションが前提です。Anthropicのトークン交換エンドポイントは、identity tokenの寿命がfederation issuerに設定した上限(既定1時間)を超えると拒否します。これはSPIRE固有ではなく、すべてのSPIFFE実装に共通するルールなので、default_jwt_svid_ttlやエントリ単位の上書き値をこの上限以下に保ちます。
OIDC Discovery Provider側の設定にも同じホスト名をdomainsに含め、SPIRE ServerのAPIソケットに到達できるようにします。
domains = ["oidc-discovery.prod.example.com"]
server_api {
address = "unix:///run/spire/sockets/private/api.sock"
}ワークロードを登録する
Claude APIを呼ぶ各ワークロードには、実行時のセレクタをSPIFFE IDにマッピングするSPIRE登録エントリが必要です。すでに登録済みならそのSPIFFE IDをfederation ruleのsubject_prefixに使い、未登録なら新規作成します。Kubernetes Podの場合、セレクタは通常namespaceとKubernetesサービスアカウントです。
# NODE_UIDはノードのUIDに置き換える
# kubectl get node <node-name> -o jsonpath='{.metadata.uid}'
spire-server entry create \
-spiffeID spiffe://prod.example.com/ns/inference/sa/worker \
-parentID spiffe://prod.example.com/spire/agent/k8s_psat/prod-cluster/NODE_UID \
-selector k8s:ns:inference \
-selector k8s:sa:workerspiffe-helperを動かす
spiffe-helperは、SPIRE Agentのソケットに接続してJWT-SVIDを取得し、ファイルに書き出して期限前に再取得するサイドカーです。
agent_address = "/run/spire/sockets/agent.sock"
cert_dir = "/var/run/secrets/anthropic.com"
daemon_mode = true
jwt_svids = [{
jwt_audience = "https://api.anthropic.com"
jwt_svid_file_name = "token"
}]Kubernetesでは、spiffe-helperをアプリケーションコンテナとemptyDir(medium: Memory)を共有するサイドカーとして動かし、ANTHROPIC_IDENTITY_TOKEN_FILE=/var/run/secrets/anthropic.com/tokenをアプリ側にセットします。SVIDがノードのディスクに書かれないよう、共有ボリュームはメモリ上に置きます。
Anthropic側でissuerとruleを作る
Claude ConsoleでSettings → Workload identityからConnect workload → Custom OIDCを選びます。ウィザードが作るリソースは、Admin APIにコードから送っても同じ結果になります。
federation issuer: discoveryモードでOIDC Discovery Providerの公開URLを登録します。
{
"name": "spire-prod",
"issuer_url": "https://oidc-discovery.prod.example.com",
"jwks": { "type": "discovery" }
}discovery providerが外部から到達できない場合は、自分でJWKSを取得し(curl https://oidc-discovery.prod.example.com/keys)、inlineモードで登録します。inlineモードではissuer_urlはJWT-SVIDのissクレームとの文字列比較にのみ使われ、Anthropicはそこへ接続しに行きません。
federation rule: JWT-SVIDのsub(SPIFFE ID)とspiffe-helperに要求させたaudを照合します。
{
"name": "spire-inference-worker",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "spiffe://prod.example.com/ns/inference/sa/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
}token_lifetime_secondsはAnthropicアクセストークンの寿命であり、JWT-SVID自体の寿命ではありません。アクセストークンの更新はSDKが自動で処理します。
トークンを取得してClaude APIを呼ぶ
Anthropic SDKは、spiffe-helperが維持するファイルからJWT-SVIDを読む方法と、SPIFFE Workload APIを直接呼ぶcallableを渡す方法のどちらにも対応します。ファイル経由が最もシンプルで、どのSDK言語でも動きます。
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")
ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
-H "content-type: application/json" \
--data @- <<JSON | jq -r .access_token
{
"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
)
curl https://api.anthropic.com/v1/messages \
-H "authorization: Bearer $ACCESS_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model": "claude-opus-5", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello, Claude"}]}' \
| jq -r '.content[] | select(.type == "text") | .text'SPIRE Agentのソケットに直接つなぐSPIFFE Workload APIクライアントを持つ言語(Pythonのpy-spiffe、Goのgo-spiffe)では、spiffe-helperを省いてSDKにcallableを渡す構成も選べます。SDKはトークン交換のたびにこのcallableを呼ぶため、常に有効期限内のSVIDを提示できます。
検証方法
SDKを組み込む前に、SPIRE AgentからJWT-SVIDを直接取得し、federation ruleが期待するクレームと一致しているかを確認します。
spire-agent api fetch jwt \
-audience https://api.anthropic.com \
-socketPath /run/spire/sockets/agent.sock \
-output json \
| jq -r '.[0].svids[0].svid' \
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'Workload APIは呼び出し元のプロセスをattestationで確認するため、Kubernetesの登録エントリなら該当Podの中(kubectl exec経由など)で、VM・ベアメタルならunix:セレクタに一致するユーザー・プロセスとして実行します。attestation対象外のホストシェルから実行するとno identity issuedになるのが、検証時に最も多い失敗です。issがdiscovery providerのURL、subがワークロードのSPIFFE ID、audにhttps://api.anthropic.comが含まれることを確認したら、上のcURL例を実行します。成功時はsk-ant-oat01-で始まるaccess_tokenが返り、失敗時は401のauthentication_error(Authentication failed)が返るので、Claude Consoleの認証履歴ページで拒否理由を確認します。SPIRE側で最も多い原因は、SPIRE Serverのjwt_issuerと登録したfederation issuerのURLの不一致です。
ルールのスコープを絞る
SPIFFE IDのパス規則は運用者が自由に決めるため、subject_prefixは登録エントリで使っているパス構成に合わせます。よくある構成は、Kubernetesのspire-controller-managerがClusterSPIFFEIDリソースで自動生成するspiffe://<trust-domain>/ns/<namespace>/sa/<service-account>と、VM・ベアメタル向けのspiffe://<trust-domain>/host/<hostname>/<service>です。
- 1ワークロードに固定:
subject_prefixを末尾*なしの完全なSPIFFE IDにする - audienceを必ず設定: ルールに
audienceを必須にし、spiffe-helper(またはWorkload API呼び出し)にも同じ値を要求させ、別の依拠者向けに発行されたSVIDを拒否する - パスセグメントで絞る:
spiffe://prod.example.com/ns/inference/*のようにnamespace単位で許可し、1つのルールを広げるのではなくnamespaceごとに別ルール・別サービスアカウントを作る - trust domainごとに1 issuer: SPIREのtrust domainごとに署名鍵とOIDC Discovery Providerが異なるため、それぞれ別のfederation issuerとして登録し、対応するSPIFFE IDにルールを紐づける
まとめ
SPIFFE/SPIRE連携の要点は、SPIRE Serverのjwt_issuerとOIDC Discovery Providerの公開URLを一致させること、JWT-SVIDの短い寿命に合わせてspiffe-helperで継続的にローテーションすること、そしてsubject_prefixをSPIFFE IDのパス構成に合わせて絞ることの3つです。マルチクラウド・オンプレ混在の環境で単一のワークロードID基盤をすでに運用しているなら、クラウドごとに別々のWIF設定を組むよりSPIFFE経由で統一した方が、federation issuerの数を減らせます。OktaのようなIdPと役割を切り分けたい場合は、ClaudeのWIFをOktaと連携する手順も合わせて確認してください。