API key 的進化版 Workload Identity Federation





昨天 Day 243 講的四件事——一個用途一把 key、權限縮小、設花費上限、信用卡那道防線——都建立在同一個前提上:你手上有一把長期有效的金鑰,而它有一天可能會外洩。
還有另一條路,是讓那把長期金鑰根本不存在。Workload Identity Federation(以下簡稱 WIF)做的就是這件事:機器不再隨身帶一把永久鑰匙,而是拿一張幾分鐘就過期的身分證明去換臨時通行證。
今天要分享的是:它到底在幹嘛、為什麼外洩的損失會變小、原理上誰在信任誰、哪些情況它幫不上忙,還有什麼時候值得換。
先說為什麼現在講這個。WIF 原本是雲端部署的東西,但 Anthropic 在 2026 年 6 月 17 日把它正式開放,OpenAI 也有同一套機制——Day 243 那把 AI API key,只要是跑在 CI、Kubernetes、雲端函式這種執行環境上,現在也走得了這條路;跑在你自己筆電上的那把,還是得留著。
它到底在幹嘛:先想像外送員要進大樓
外送員要進大樓送餐,警衛室得發他一張通行證。
舊做法是發一張永久通行證。那張卡本身就是憑證,誰拿到誰就能進,警衛不會再問你是誰。所以卡一旦弄丟、或被人從外送員身上偷走,小偷就能一直進出這棟大樓——而且他可以先不動手,潛伏著,挑未來某一天才進來。你不主動去掛失,它永遠有效。
WIF 是另一種發卡方式。外送員身上什麼卡都沒有。他到大樓的時候,是 foodpanda 出面跟警衛說「這一單是我派的」;警衛驗的是 foodpanda 的簽章,不是外送員本人。確認過了,才發一張十分鐘後失效的臨時訪客證。
餐送完,訪客證自己過期,沒有人需要去掛失。就算它被偷走,撿到的人最多只有那十分鐘可以用,而且他再也拿不到新的——要 foodpanda 出面,警衛才會再發一張。
對應回技術上:外送員是你的服務或 CI job,foodpanda 是它跑在上面的執行環境(GitHub Actions、AWS、GCP),警衛室是 API 供應商或雲端服務,永久通行證是長期 API key,臨時訪客證是短期權杖。
講白一點,長期 API key 是「持有即通行」:那串 sk-... 本身就是憑證,從哪台機器用都行,一直用到你去後台撤銷為止。WIF 把它換成「先證明你是誰,再拿一張快過期的票」。你的工作不再隨身帶鑰匙,而是先跟執行環境要一張 JWT——一份有數位簽章、幾分鐘就過期的身分證明,上面寫著它是誰、從哪個 repo、哪個分支、哪個事件跑起來的。拿這張去換,才換得到一個只能用幾分鐘的存取權杖。
外洩的東西因此從「一把可以一直用的鑰匙」變成「一張快過期的票」。撿到票的人還換不到新的票,因為能幫你簽身分證明的私鑰在 GitHub(或 AWS、GCP、你的 Kubernetes 叢集)那邊,不在你的 repo 裡。
為什麼外洩的損失會變小
| 長期 API key | 短期 federated token | |
|---|---|---|
| 效期 | 到你撤銷為止 | 只有幾分鐘。Anthropic 的設定預設 600 秒,直接打 API 不指定的話是 3600 秒(可設 60 到 86400 秒);但實際還會被下一段「上游 JWT 剩餘時間兩倍」的上限壓下來 |
| 誰能用 | 任何人、任何機器、任何 IP | 一樣是任何人——只是剩下那幾分鐘 |
| 撿到之後 | 一直用,直到有人發現 | 用到過期,然後換不到新的 |
| 要再拿一把 | 複製那串字就好 | 要有辦法讓 GitHub/AWS/叢集幫你簽一張新的 JWT |
Anthropic 還多壓了一層:換到的權杖效期是「規則設的秒數」跟「上游 JWT 剩餘時間的兩倍」取小的那個,最短 60 秒。GitHub 發的身分權杖大約五分鐘就過期,所以就算你把規則設成 3600 秒,實際拿到的也是十分鐘上下的東西。上游身分活多久,下游權限最多就跟著活那麼久。
還有一件事跟直覺不太一樣:那組設定值本身不是秘密。federation rule ID、organization ID、service account ID 這些,在官方的 workflow 範例裡是直接寫在 env: 裡的明文,不需要放進 repository secrets。CI 裡跟這條認證路徑有關的部分,真的沒有秘密要保管了。
原理:誰發、誰驗、誰在信任誰
三個角色:
- 身分提供者(也就是 OIDC 發證方)——你本來就在用的執行環境:GitHub Actions、AWS、GCP、Kubernetes。它握著一組私鑰,會幫在它上面跑的工作簽 JWT
- 工作本身——你的 CI job、你的 pod。它跟身分提供者要那張 JWT
- 資源那一方——雲端服務或 API 供應商。它拿身分提供者「公開」的公鑰驗簽章,再比對你事先設好的規則
信任是這樣接起來的:你在資源那一方登記兩樣東西。
第一樣是 issuer——那個身分提供者的 iss 值,還有去哪裡抓公鑰。像 GitHub Actions 就是 https://token.actions.githubusercontent.com,公鑰走 /.well-known/openid-configuration 自動發現,對方會在 GitHub 換金鑰的時候自己更新。
第二樣是規則——什麼樣的 JWT 可以換成什麼身分。GitHub 簽出來的 JWT 解碼後長這樣:
{
"iss": "https://token.actions.githubusercontent.com",
"sub": "repo:your-org/your-repo:ref:refs/heads/main",
"aud": "https://api.anthropic.com",
"repository": "your-org/your-repo",
"repository_owner": "your-org",
"ref": "refs/heads/main",
"event_name": "push"
}
注意 GitHub 從 2026 年 7 月 15 日起改用 immutable subject:之後新建、改名或轉移的 repo,sub 會變成 repo:your-org@1234567/your-repo@8901234:ref:refs/heads/main(名稱後面多了不可變的數字 ID)。7/15 前的舊 repo 維持原格式,除非你主動 opt in。設規則前先把實際的 JWT 解碼出來看一眼。
規則就寫成:iss 要是這個、sub 要對上 repo:your-org/your-repo:ref:refs/heads/main(要涵蓋多種事件才加結尾的 *,並搭配 claims.ref 限制)、aud 要對——三項都過,才換一個指定身分的短期權杖給它。GitHub 那邊也要在 workflow 裡明確開權限,加上 permissions: 區塊,底下放 id-token: write(Anthropic 文件的範例還會一起帶 contents: read),沒開的 job 拿不到 JWT。
整條路上沒有任何一段是「兩邊共用同一組密鑰」。GitHub 用自己的私鑰簽,對方用公開的公鑰驗,你設定的是一段公開的比對條件。所以中間才沒有東西可以外洩。
各家名字不一樣,這兩個步驟是一樣的:
| 註冊發證方 | 設定比對規則 | |
|---|---|---|
| GCP | workload identity pool + provider | attribute condition(CEL) |
| AWS | IAM OIDC identity provider | 信任政策 + AssumeRoleWithWebIdentity |
| Anthropic | federation issuer | federation rule → service account |
| OpenAI | workload identity provider | service account mapping |
這條路做不到的事
- 沒有 OIDC 發證方的地方就沒有這條路。 你的筆電沒有,本機開發還是得靠一把 key。自己租的 VM、跑在自家機器上的 cron、不少 PaaS 也都沒有。這些地方 Day 243 那四招照樣是主力
- 規則寫太鬆,比長期 key 還危險。 Anthropic 文件裡直接標了警告:
subject_prefix寫成repo:your-org/*,整個組織的每一個 repo 都會對上,而且沒有加ref條件的話,連從 fork 發起的 pull request 都算——任何人只要能對你的 repo 開一個 PR,就換得到權杖。以為自己更安全了,其實是把門開得更大 - 信任鏈只到你的身分提供者為止。 對方驗的是「GitHub 說這個 job 是誰」。GitHub 帳號被接管,或有人能在你的 main 分支上跑任意 workflow,WIF 一點都擋不住。它換掉的是秘密怎麼保管,不是誰能執行
- 舊 key 沒清掉的話,可能根本沒生效。 Anthropic SDK 的憑證優先順序裡,
ANTHROPIC_API_KEY排在 federation 前面。環境裡留著一把舊 key,它會默默蓋過 federation——你以為換好了,其實還在燒那把 key - 出錯的時候不好查。 換權杖失敗回的是一個沒有細節的 401
Authentication failed,真正的原因要去 Console 的 authentication history 頁面看。最常見的是sub沒對上:新舊 subject 格式不同,或者 push、pull_request、environment 這三種事件的sub尾巴長得不一樣
什麼時候值得換
- 跑在 CI 上的自動化:這是 WIF 最順的情境,本來就有 OIDC,改完之後 repository secrets 可以少一項
- 跑在 Kubernetes、雲端函式上的服務:同理,執行環境本來就會發 JWT
- 本機開發、一次性腳本、朋友的 demo:維持 Day 243 那四招,一個用途一把 key、權限縮小、設花費上限、信用卡那道防線
一句話總結:Day 243 是讓 key 外洩的那一刻值比較少錢,這篇是讓撿到的人手上那張票,十分鐘後變成一串沒用的字。
- 昨天的 Day 243:事前把爆炸半徑縮小
- Anthropic WIF 總覽:https://platform.claude.com/docs/en/manage-claude/workload-identity-federation
- Anthropic WIF × GitHub Actions:https://platform.claude.com/docs/en/manage-claude/wif-providers/github-actions
- Anthropic WIF 正式開放公告(2026-06-17):https://claude.com/blog/workload-identity-federation
- OpenAI Workload Identity Federation:https://developers.openai.com/api/docs/guides/workload-identity-federation
- GCP Workload Identity Federation:https://docs.cloud.google.com/iam/docs/workload-identity-federation
- GitHub OIDC 的 sub claim 格式:https://docs.github.com/en/actions/reference/security/oidc
- GitHub Actions OIDC immutable subject 公告(2026-04-23):https://github.blog/changelog/2026-04-23-immutable-subject-claims-for-github-actions-oidc-tokens/