Claude Media
WIFをAdmin APIで管理する — サービスアカウント/issuer/ruleを操作

WIFをAdmin APIで管理する — サービスアカウント/issuer/ruleを操作

Workload Identity Federationのサービスアカウントとfederationイシュアー・ルールをAdmin APIから作成・一覧・アーカイブする手順。

WIFをAdmin APIで管理するとは

Workload Identity Federation(WIF)は、Claude ConsoleのConnect workloadウィザードから手動で設定するのが基本の入り口です。ただしサービスアカウント・federation issuer・federation ruleの3リソースは、Admin APIから直接作成・一覧・アーカイブもできます。これらのエンドポイントは、Admin APIの残り全体と同じ/v1/organizationsパスプレフィックスを共有します。組織を横断してfederation設定をInfrastructure as Codeとして持ちたい、CIから配線したい、複数組織に同じ構成を再現したいといった用途に向きます。

Oktaやgithub-actionsといった個別プロバイダーごとの連携手順はClaudeのWIFをOktaと連携する手順やClaudeのWIFをSPIFFE/SPIREと連携する手順で扱っており、本記事はプロバイダーを問わず「issuer・ruleそのものをどう操作するか」に絞ります。

前提条件: どのトークンで叩くか

ここで扱うエンドポイントはすべて、org:adminスコープを持つOAuthベアラートークンで認証します。このスコープは組織のadmin・owner・primary ownerロールを持つメンバーにのみ付与され、組織全体へのアクセス権になります(ワークスペースの紐づけは無視されます)。Admin APIキー(x-api-key)は受け付けられません。Admin APIのページにあるx-api-keyの例は、ここでは使えません。

くらべる

トークンの取り方は2通り

自分の端末

対話的に取得

自分のログインから得たトークンは人間として動作し、組織のadmin権限でここで扱う操作を実行できます。

CIと自動化

ワークロードから取得

federationされたトークンはサービスアカウントとして動作し、ここで扱う操作の一部を実行できません。

対話的に取得する

ant CLIでスコープを要求して専用プロファイルにログインし、ベアラートークンをexportします。

ant auth login --profile admin --scope "org:admin"
export ANTHROPIC_AUTH_TOKEN=$(ant auth print-credentials --profile admin --access-token)

--profile adminでのログインはorg:admin資格情報を専用のプロファイル名で保存すると同時にCLIのアクティブプロファイルにもしてしまい、exportした環境変数はそのシェル内のすべてのSDK・CLI呼び出しに適用されます。管理作業専用のシェルを用意し、作業が終わったら変数をunsetし、ant profile activate defaultでCLIを戻してください。対話的トークンは短命で、401が返り始めたらexportコマンドを再実行します(自動でリフレッシュされます)。

SDKとant CLIはANTHROPIC_AUTH_TOKENを自動で読みます。同じシェルではANTHROPIC_API_KEYを未設定にしておきます。これらのエンドポイントはAPIキーを拒否し、両方セットされているとクライアントによってはAPIキーが選ばれるためです。

ワークロードから取得する

CIと自動化では、organization_roleがadminのサービスアカウントを対象に、oauth_scope: org:adminのfederation ruleを作ります。このルール自体はClaude Console上でしか作れません。ワークロードに組織admin権限を与えるのは意図的な人間の操作であるべきで、自動化が自分自身にその権限を発行できてはならないためです。

ワークロードにWIF管理を任せる準備

Console側で作るorg:adminルールは1つで足ります。信頼できる1つのワークロード(たとえばインフラ用リポジトリのGitHub Actionsワークフロー)にorg:adminスコープを付与すれば、残りのfederation設定(issuerとワークスペース単位のすべてのrule)をこのAPI経由でInfrastructure as Codeに載せられます。

手順

ブートストラップの3手順

  1. 1

    ConsoleでOrg-adminルールを作る

    Settings → Workload identity → Connect workloadで自動化ワークロード用のfederation ruleを1つ作ります。Advanced rule optionsでOAuth scopeをorg:adminに設定すると、ウィザードがAdmin組織ロールを持つ新しいサービスアカウントを作成します。既存のadminサービスアカウントを選ぶこともできます。

  2. 2

    ワークロードのidentity tokenを交換する

    SDKやant CLIを使うワークロードは、交換処理自体を自分で書く必要はありません。federation用の環境変数をセットしてクライアントを引数無しで構築するだけで、推論用途と同じ仕組みが働きます。

  3. 3

    APIからissuerとruleを管理する

    クライアントが構成できたら、curlの場合は交換で得たトークンをANTHROPIC_AUTH_TOKENに入れて、以降のエンドポイントでfederation設定を作成・管理します。

手順2の環境変数は次の4つが必須です。

export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...        # 手順1で作ったorg:adminルール
export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
export ANTHROPIC_SERVICE_ACCOUNT_ID=svac_...       # ルールのターゲットサービスアカウント
export ANTHROPIC_IDENTITY_TOKEN_FILE=/path/to/jwt  # またはANTHROPIC_IDENTITY_TOKEN
unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN       # どちらもfederationより優先されるため

ANTHROPIC_WORKSPACE_IDは、ルールが複数のワークスペースで有効なときだけ必要です。org:adminのエンドポイントはワークスペースの紐づけを無視します。ただしSDKと違い、ant CLIはANTHROPIC_WORKSPACE_IDも--workspace-idも交換に渡しません。複数ワークスペースで有効なルールにCLIからworkspace_idを渡せるのは、federation profileだけです。

複数のantコマンドを実行するワークロードでは、フラグや環境変数ではなくfederation profileを使います。federation profileはANTHROPIC_FEDERATION_RULE_IDなどの設定をJSONの設定ファイルにまとめる仕組みです。identity tokenはファイルのパスで指定でき、省略すると環境変数が使われます。フラグや環境変数のままだと、antはプロセスごとにidentity tokenを再交換してしまいます。jtiクレームを含むトークン(GitHub Actionsのトークンはこれに該当)は1回しか使えないため、2回目のコマンドが拒否されます。curlで直接叩くワークロードは、通常のfederationワークロードと同じトークン交換を自分で行い、authorization: Bearerヘッダーに載せます。

すでにConnect workloadウィザードでissuer・サービスアカウント・ruleを作成済みなら、一覧エンドポイントで取得してInfrastructure as Codeの状態に取り込み、作り直さないようにします。

サービスアカウント

サービスアカウント(svac_...)は、federationされたトークンが代わりに動作する非人間のIDです。organization_roleはdeveloperに設定します。

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/service_accounts" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "inference-worker",
    "organization_role": "developer"
  }'

ant CLIなら同じ操作を次のように書けます。SDKではclient.beta.organization.service_accountsが対応し、CLIのコマンド名はSDKのリソース名をコロンでつないだ形です。

ant beta:organization:service-accounts create \
  --name inference-worker \
  --organization-role developer
ant beta:organization:service-accounts list --limit 20
ant beta:organization:service-accounts archive svac_01ABCDEFabcdef0123456789XY

作成するとsvac_...から始まるIDとname・organization_role・created_at・typeを含むオブジェクトが返ります。単体の参照・更新は/service_accounts/{service_account_id}へのGET/POSTです。アーカイブはPOST /service_accounts/{service_account_id}/archiveで、レスポンスにはarchived_atが入ります。

サービスアカウントは、federationされたトークンがそのワークスペースで動作する前に、そのワークスペースのメンバーである必要があります。組織の既定ワークスペースには暗黙にメンバーとして扱われます。他のワークスペースは/service_accounts/{service_account_id}/workspacesへのGET・POST・DELETEで明示的にメンバーシップを追加します(DELETEの宛先は.../workspaces/{workspace_id})。

Federation issuers

federation issuer(fdis_...)は、OIDCアイデンティティプロバイダーを組織に登録するリソースです。jwksフィールドは判別可能なユニオン型で、Anthropicが署名鍵をどう取得するかを決めます。

jwksの値使う場面
{"type": "discovery"}使う場面プロバイダーがissuer URLで/.well-known/openid-configurationを配信している
{"type": "explicit_url", "url": "..."}使う場面JWKSエンドポイントを直接指定する
{"type": "inline", "keys": [...]}使う場面公開インターネットから到達できないプロバイダー向けに鍵セットをアップロードする

GitHub Actionsをdiscoveryモードで登録する例:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_issuers" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "github-actions",
    "issuer_url": "https://token.actions.githubusercontent.com",
    "jwks": {"type": "discovery"}
  }'

CLIではjwksをフローマッピングの形で渡します。

ant beta:organization:federation:issuers create \
  --name github-actions \
  --issuer-url https://token.actions.githubusercontent.com \
  --jwks '{type: discovery}'

一覧・単体参照・更新のパスはサービスアカウントと同じ構造(/federation_issuers、/federation_issuers/{issuer_id})です。アーカイブはPOST /federation_issuers/{issuer_id}/archiveで、CLIではant beta:organization:federation:issuers archive --federation-issuer-id fdis_...と書きます。

issuer_urlとjwks.urlはAnthropic側が実際にアクセスするため、スキームはhttps、ポートは443、ホストは公開DNSに解決できる名前に限られます。IPアドレスの直書きは通りません。違反すると400 invalid_request_errorが返り、メッセージの先頭にフィールド名が付きます(例: issuer_url: url must use https scheme)。explicit_urlやinlineのモード、またはdiscoveryでjwks.discovery_baseを指定した場合、issuer_urlはJWTのissクレームと文字列で比較されるだけで取得されません。この場合は社内ホスト名や標準外のポートも書けます。

Federation rules

federation rule(fdrl_...)は、issuerとサービスアカウントを結びつけます。issuerからのJWTがruleのmatch条件を満たせば、ruleのtargetとして動作するトークンを発行します。作成リクエストのworkspace_idが、そのワークスペースでruleを有効にします。workspace_idかapplies_to_all_workspaces: trueのどちらかが作成時に必須です。

GitHub Actionsのmainブランチからのデプロイを、サービスアカウントとして動作させる例:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_rules" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "gha-deploy",
    "issuer_id": "fdis_01ABCDEFabcdef0123456789XY",
    "match": {
      "subject_prefix": "repo:my-org/my-repo:ref:refs/heads/main",
      "claims": {"repository_owner": "my-org"}
    },
    "target": {
      "type": "service_account",
      "service_account_id": "svac_01ABCDEFabcdef0123456789XY"
    },
    "workspace_id": "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
    "oauth_scope": "workspace:developer",
    "token_lifetime_seconds": 600
  }'

CLIではJSONの代わりにYAMLを標準入力から渡せます。

ant beta:organization:federation:rules create <<'YAML'
name: gha-deploy
issuer_id: fdis_01ABCDEFabcdef0123456789XY
match:
  subject_prefix: "repo:my-org/my-repo:ref:refs/heads/main"
  claims:
    repository_owner: my-org
target:
  type: service_account
  service_account_id: svac_01ABCDEFabcdef0123456789XY
workspace_id: wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ
oauth_scope: "workspace:developer"
token_lifetime_seconds: 600
YAML

一覧はissuer_idでフィルタできます(GET /federation_rules?issuer_id=...、CLIでは--issuer-id)。レスポンスはdata配列と次ページ用のnext_pageカーソルです。ruleが動作できるワークスペースの一覧と追加は/federation_rules/{rule_id}/workspacesへのGET・POST、削除はDELETE .../workspaces/{workspace_id}です。アーカイブはPOST /federation_rules/{rule_id}/archiveです。

ruleのworkspace_idは同じ組織のワークスペースで、かつターゲットのサービスアカウントがそのメンバーでなければなりません。課金・クォータ・レート制限は、このruleで発行したトークンについてそのワークスペースのものが適用されます。

ruleのscopeとサービスアカウントのroleの関係

ruleのoauth_scopeは発行トークンの権限の上限です。実際の権限は、ruleのscopeとターゲットサービスアカウントのorganization_role(developerかadmin)の積集合になります。たとえばorganization_roleがadminのサービスアカウントでも、ruleがworkspace:developerなら、権限はそのワークスペースのClaude API利用に収まります。org:adminのruleが成立するのは、ターゲットがadminのサービスアカウントのときだけです。

権限と制約

  • OAuth認証した呼び出し元は、oauth_scopeがworkspace:developerかworkspace:inferenceのruleしか作成・変更できません。org:adminやworkspace:manage_tunnelsのような他のscopeを持つruleを作る・変えるにはConsoleを使います
  • oauth_scopeがworkspace:developerかworkspace:inference以外のruleが参照しているissuerは、OAuth呼び出し元からは更新できません。bootstrap用のルールには専用のissuerを登録しておくと、ワークスペーススコープのrule側が使うissuerをAPI経由で更新できる状態を保てます

値の範囲とページング

数字

リソースと一覧の数値制限

  • 名前の長さ

    1〜255字

    `^[a-z0-9-]+$`に一致。リソース種別ごとに組織内で一意

  • limit

    1〜100

    一覧の1ページの件数。既定は20

  • token_lifetime_seconds

    60〜86400

    ruleの発行トークンの有効秒数。既定は3600

サービスアカウント・federation issuer・federation ruleの一覧エンドポイントは、limitと前回レスポンスから取ったpageカーソルを受け付けます。レスポンスのnext_page値を次のリクエストのpageクエリパラメータに渡します。SDKの一覧メソッドは必要に応じて次のページを取りに行くので、SDKではlimitがページサイズになります。rule-workspacesサブリソースの一覧だけはページングなしで全件を返します。

アーカイブ済みリソースは既定で一覧から除外され、include_archived=trueを付けると含まれます。アーカイブはソフトデリートで冪等です。すでにアーカイブ済みのリソースをアーカイブしても成功します。一方、稼働中のfederation ruleがまだ参照しているissuerやサービスアカウントをアーカイブしようとすると400が返ります。

失敗したときの切り分け

APIの操作がうまくいかないときと、ワークロードのトークン交換が通らないときでは、見る場所が違います。

  • アーカイブが400になる: 稼働中のruleがそのissuerやサービスアカウントを参照しています。GET /federation_rules?issuer_id=...で参照元のruleを探し、先にアーカイブします
  • トークン交換が401になる: メッセージは常にAuthentication failedで、理由はレスポンスに出ません。どのチェックで落ちても同じ応答になるのは、ルール構成を探られないようにするためです。理由はConsoleの認証履歴に記録され、たとえばsubject_prefixの不一致はmatch_subject_prefix、複数ワークスペースで有効なruleにworkspace_idを渡し忘れた場合はworkspace_id_requiredと出ます
  • 2回目のコマンドだけ401になる: jtiクレームを持つJWTは、issuerごとに1回しか交換できません。同じJWTの再送はjti_reusedとして認証履歴に残るので、交換のたびに新しいJWTを用意します。issuerのcheck_jtiは既定で有効で、jtiを持たないJWTには適用されません
  • federationしているのにAPIキーで動く: ANTHROPIC_API_KEYやANTHROPIC_AUTH_TOKENが設定されていると、federationより先に選ばれます。空文字でexportしただけでも優先順位の枠を占めるので、VAR=""ではなくunset VARを使います
  • JWTのissが合わない: 登録したissuer_urlとissクレームは一字一句の一致が必要で、末尾スラッシュやスキームの違いも不一致になります

まとめ

単発の管理作業なら、対話的に取ったトークンで足ります。issuerとruleを継続的にコードで回すなら、Consoleでorg:adminのruleを1つだけ作ってCIに持たせます。

そのruleが使うissuerは専用に分けておくと、ワークスペース側のruleのissuerをAPIから更新できる状態が保てます。issuerとruleの具体的な設定値は、Okta連携やSPIFFE/SPIRE連携のようなプロバイダー別ガイドで組み立てます。

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