WIFをGoogle Cloudと連携する — Claude APIをAPIキーレスにする手順
Cloud Run・GCE・GKEからGoogle署名のIDトークンでClaude APIを呼ぶ、Workload Identity Federationの設定手順を追います。
WIFでGoogle CloudからClaude APIを呼ぶとは何か
Google Cloud上のワークロード(Cloud Run・Cloud Functions・App Engine・GCE・GKE)は、インスタンスのメタデータサーバーから自分のサービスアカウントに紐づくGoogle署名のIDトークンを取得できます。Workload Identity Federation(WIF)は、このIDトークンをAnthropicのPOST /v1/oauth/tokenエンドポイントに渡し、短命のClaude APIアクセストークンと交換する仕組みです。
長期間有効なsk-ant-...のAPIキーをリポジトリや環境変数に置く必要がなくなります。トークンの発行元はhttps://accounts.google.comで、AnthropicはこのIssuerを標準のOIDC discoveryでそのまま検証できます。Google Cloud側に追加設定は不要で、サービスアカウントを正しく紐づけるだけです。
前提として、WIFの基本概念(サービスアカウント・連合発行者・連合ルール)はWIFリファレンスを先に読んでおくと設定の意味が追いやすくなります。
Google Cloud側で必要な設定
標準のコンピュート環境(Cloud Run・Cloud Functions・App Engine・GCE)とGKEでは、IDトークンの取得手順がわずかに異なります。
Cloud Run・Cloud Functions・App Engine・GCEの場合
まず、Compute Engineのデフォルトサービスアカウントではなく、専用のユーザー管理サービスアカウントをワークロードに紐づけます。
gcloud run deploy my-service \
--service-account inference-worker@my-project.iam.gserviceaccount.comワークロード内部では、メタデータサーバーへのリクエストでIDトークンをオンデマンド取得できます。audienceにはAnthropic側で登録する値(https://api.anthropic.com)を指定し、format=fullを付けてemailクレームを含めます。
curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full"gcloudコマンドでも同じトークンを取得できます。
gcloud auth print-identity-token \
--audiences="https://api.anthropic.com" \
--include-emailデコードしたトークンのペイロードは次の形です。
{
"iss": "https://accounts.google.com",
"aud": "https://api.anthropic.com",
"sub": "104892...",
"azp": "104892...",
"email": "inference-worker@my-project.iam.gserviceaccount.com",
"email_verified": true,
"exp": 1775527120
}subはサービスアカウントの数値の一意IDで、emailは人が読めるサービスアカウントのアドレスです。連合ルールでは両方をマッチ条件に含めます。
GKE(Workload Identity)の場合
GKEでは、クラスターでWorkload Identityを有効化し、KubernetesのサービスアカウントにアノテーションでGoogleサービスアカウントを紐づけます。クラスター側は--workload-pool=PROJECT_ID.svc.id.googでプールを有効にします。
Googleサービスアカウントを紐づける方式では、IAMの許可ポリシーとアノテーションの両方が必要です。許可ポリシーは、Namespace/ServiceAccountの組ごとにroles/iam.workloadIdentityUserを付与して作ります。
gcloud iam service-accounts add-iam-policy-binding \
inference-worker@my-project.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:my-project.svc.id.goog[prod/inference-worker]"アノテーションはKubernetesのサービスアカウントに次のように付けます。
apiVersion: v1
kind: ServiceAccount
metadata:
name: inference-worker
namespace: prod
annotations:
iam.gke.io/gcp-service-account: inference-worker@my-project.iam.gserviceaccount.comこの紐づけができていれば、GKEのメタデータサーバーもCloud Run・GCEと同じhttps://accounts.google.com発行・同じemailクレームのトークンを返します。Anthropic側の設定も共通です。
format=fullのGKEトークンには、google.compute_engine.project_id・google.compute_engine.zone・google.compute_engine.instance_nameのクレームが追加されます。これらは連合ルールのCEL条件式(claims.google.compute_engine.project_id == "my-project")で、特定のプロジェクトやノードプールにアクセスを絞り込むのに使えます。
GKEでAnthropicに連合する方法は2通りあります。
GKEでの2つの連合方法
Workload Identity
上で設定したIAMの許可ポリシーとアノテーションを使う方式です。Googleサービスアカウント単位でIAMを管理できます。
クラスター自身のOIDC発行者
Googleサービスアカウントへの紐づけが要りません。発行者はhttps://container.googleapis.com/v1/projects/PROJECT/locations/REGION/clusters/CLUSTERで、projected serviceAccountTokenボリュームからトークンを読みます。
後者の手順はKubernetesクラスター自体のWIF連携で扱う仕組みと同じです。
Anthropic側で必要な設定
Claude ConsoleのSettings → Workload identityを開き、Connect workloadからGoogle Cloudタイルを選ぶと、発行者の登録・サービスアカウントの作成・連合ルールの作成をウィザードが順に案内します。
ウィザードが作るリソースは、Admin APIからも作成・更新できます。発行者はPOST /v1/organizations/federation_issuers、連合ルールはPOST /v1/organizations/federation_rulesです。認証にはorg:adminのOAuthトークンを使い、Admin APIキーは受け付けられません。複数プロジェクト分のルールをコードで管理したいときの入口になります。
連合発行者: GoogleはOIDC discoveryドキュメントを公開しているため、discoveryモードを使います。この1つの発行者だけで、Cloud Run・GCE・Cloud Functions・App Engine・GKE(Workload Identity)のすべてのGoogle Cloudサーフェスをカバーできます。ワークロードの違いは発行者ではなくルールで区別します。
{
"name": "gcp",
"issuer_url": "https://accounts.google.com",
"jwks": { "type": "discovery" }
}連合ルール: subとemailの両方のクレームをマッチ条件にします。emailは読みやすいサービスアカウントのアドレスで、subはサービスアカウントの数値の一意IDです。Googleはsubを再利用しないため、これを固定しておくと、サービスアカウントが削除されて同じメールアドレスで新しいアカウントが作られても、ルールが誤って一致することを防げます。一意IDは次のコマンドで確認できます。
gcloud iam service-accounts describe SA_EMAIL --format='value(uniqueId)'{
"name": "gcp-inference-worker",
"issuer_id": "fdis_...",
"match": {
"audience": "https://api.anthropic.com",
"claims": {
"sub": "104892101234567890123",
"email": "inference-worker@my-project.iam.gserviceaccount.com"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}workspace_idは、交換で得たトークンの権限が及ぶワークスペースです。ルールが複数のワークスペースで有効なら、交換リクエストにもworkspace_idが必須になります。省くと401になり、認証履歴の理由はworkspace_id_requiredです。ルールが1つのワークスペースにだけ紐づくなら省略でき、そのワークスペースが選ばれます。
oauth_scopeのworkspace:developerは、ワークスペースのAPIキーと同じ範囲を許すスコープです。Messages・Models・Files・Skillsなど管理系以外のエンドポイントを呼べます。推論しか使わないワークロードはworkspace:inferenceに絞れ、Files・Skillsは呼べなくなります。範囲外のエンドポイントは403です。
token_lifetime_secondsは60〜86400の範囲で、省略時は3600秒です(ウィザードの初期値は600)。実際の有効期間は、この値と、提示したIDトークンの残り寿命の2倍のうち短いほうで決まります。60秒を下回ることはありません。
トークンを取得してClaude APIを呼ぶ
ワークロード内でメタデータサーバーからIDトークンを取得し、POST /v1/oauth/tokenで交換して、返ってきたベアラートークンでClaude APIを呼びます。各Anthropic SDKは、メタデータサーバーから新しいIDトークンを返すトークンプロバイダー関数を渡すだけで、交換と更新のループを自動でこなします。
# メタデータサーバーからGoogle署名のIDトークンを取得
JWT=$(curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full")
# Anthropicのアクセストークンに交換
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)
curl -sS 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-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello from Cloud Run"}]}'Python SDKではWorkloadIdentityCredentialsにトークン取得関数を渡すだけで済みます。
import os
import anthropic
import google.auth.transport.requests
import google.oauth2.id_token
from anthropic import WorkloadIdentityCredentials
AUDIENCE = "https://api.anthropic.com"
def fetch_google_identity_token() -> str:
request = google.auth.transport.requests.Request()
return google.oauth2.id_token.fetch_id_token(request, AUDIENCE)
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_google_identity_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)Google発行のIDトークンはおよそ1時間で失効します。SDKは失効前にトークンプロバイダーを再実行し、自動で交換をやり直します。アクセストークンのexpires_inより長く動くシェルスクリプトでは、タイマーで定期的に更新処理を回します。
設定を検証する
ワークロード内からIDトークンをデコードし、クレームがルールと一致しているか確認します。
curl -sS -H "Metadata-Flavor: Google" \
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=https://api.anthropic.com&format=full" \
| jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson'issがhttps://accounts.google.com、audがhttps://api.anthropic.com、emailがルールの値と一致していることを確認したら、トークン交換を実行します。成功時はsk-ant-oat01-で始まるaccess_tokenとexpires_inが返ります。401のauthentication_error(メッセージはAuthentication failedという不透明な固定文言)で失敗する場合、認証履歴ページで拒否理由を確認します。Google Cloud側で最も多い原因はemailクレームの欠落で、IDトークンのリクエストにformat=fullを付ければ解決します。
リクエストの形が不正なときだけは400で、workspace_idがwrkspc_...の形式でない場合などがこれに当たります。400は認証履歴に残らず、レスポンスのメッセージに原因が書かれます。401は履歴の拒否理由から次のように当たりをつけます。
401の切り分け
iss が合わない
デコードした
issは、登録したissuer_urlとスキームや末尾スラッシュまで1文字単位で一致する必要があります。Googleの値はhttps://accounts.google.comです。aud が合わない
トークンの
audには、ルールのaudienceが完全一致で含まれている必要があります。メタデータサーバーに渡したaudienceと、ルールの値を見比べます。sub・email が合わない
ルールの
claimsの各キーは、トークンの同名クレームと完全一致で比べられます。subは数値の一意IDで、メールアドレスではありません。同じトークンの再送
jtiクレームを持つトークンは1回しか交換できません。リトライループで再送するとjti_reusedで拒否されるため、交換のたびにメタデータサーバーから取り直します。履歴に何も残らない
履歴のない401は、
federation_rule_idが認識されていないことが多いとされています。ルールIDがアーカイブされていないかも確かめます。
expとnbf・iatは30秒の許容幅で検証されるため、ワークロードの時計がずれていると有効なトークンでも落ちます。
ルールを絞り込むときの注意点
Googleのsubクレームは安定したプレフィックスを持たない不透明な数値IDです。末尾に*を付けたsubject_prefixは、すべてのGoogle Cloudプロジェクトの任意のサービスアカウントにマッチしてしまい、そのどれもが連合済みのAnthropicトークンを取得できてしまいます。
ルールのmatchブロックは、用途に合う最も狭いスコープに絞ります。
| 絞り込み | やり方 |
|---|---|
subを完全一致させる | やり方完全な数値の一意IDをclaims.subに設定し、Googleトークンにはsubject_prefixを使わない |
emailクレームも固定 | やり方claims.emailをsubと併記し、安定したIDと読みやすいアドレスの両方を一致条件にする |
| audienceを固定 | やり方メタデータサーバーへリクエストする値と同じ値をaudienceに設定し、他の用途向けトークンを拒否する |
| GKEのプロジェクトを固定 | やり方format=fullトークンにclaims.google.compute_engine.project_id == "my-project"のような条件を追加し、特定プロジェクトのノードだけに絞る |
本番と検証環境で連合ルールを分けておくと、片方を無効化しても他方に影響しません。環境ごとに専用の連合ルールを置く設計にします。
APIキー運用からの移行
環境にANTHROPIC_API_KEYが残っていると、連合ルールを足しても呼び出しはAPIキーのままです。SDKの資格情報は、コンストラクタ引数、ANTHROPIC_API_KEYとANTHROPIC_AUTH_TOKEN、ANTHROPIC_PROFILE、連合用の環境変数、暗黙のアクティブプロファイルの順に探され、最初に見つかったものが使われます。APIキーは連合より上位です。
APIキーから連合へ切り替える順序
- 1
連合を並行して設定する
ルールがワークロードのトークンに一致することを確かめます。既存の
ANTHROPIC_API_KEYはまだ残します。 - 2
どの資格情報が使われているか見る
ワークロード内で
ant auth statusを実行します。この段階ではAPIキーが使われています。 - 3
APIキーの注入元を外す
CIのシークレット、コンテナの環境変数、Secret Managerからの参照、シェルのプロファイルから外します。再度
ant auth statusを実行し、連合に切り替わったことを確かめます。 - 4
APIキーを削除する
連合で動いていることを確認してから、Claude ConsoleのSettings → API keysでキーを削除します。
GitHub ActionsやKubernetesとの違い
IdPが変わると、トークンの発行元とクレームの形が変わります。GitHub Actionsのワークフローから同じ仕組みでAPIキーレス化する手順はWIFをGitHub Actionsと連携するで扱っています。GKEのPodがクラスター自身の発行者を使う構成は、WIFをKubernetesと連携するの手順が当てはまります。
連合ルールの粒度設計とトークン交換のリクエスト・レスポンス仕様は、どの構成でも共通です。エラーコードの逆引きやCEL条件式の書き方はWIFリファレンスにあります。
まとめ
Google Cloud側の作業は専用サービスアカウントの紐づけだけで、GKEではそれにWorkload Identityの許可ポリシーが加わります。ルールはsubとemailの両方で固定し、移行時はant auth statusで、APIキーではなく連合が使われていることを確かめてからキーを削除します。