GitHub Actions + GCP WIF: 用 Branch、Workflow 與 Service Account 切出最小權限
如果要把 GitHub Actions 接到 GCP, 我現在最不想再做的就是:
- one shared deploy service account
- one broad trust rule
- one workflow that can hit every environment
這樣一開始很省事, 但後面只要開始切 dev / prod, 風險和 debug 成本都會一起上升。
這篇整理我最後收斂出來的做法: 用 branch、workflow、service account 三層一起切最小權限。
想解的不是登入問題, 是信任邊界
很多人第一次做 OIDC / WIF, 會把問題理解成:
- how do I let GitHub Actions authenticate to GCP?
這只答到一半。
真正要答的是:
- 哪條 workflow 可以模擬哪個身分?
- from which branch?
- against which environment?
- 對應哪些權限?
如果這些沒寫清楚, 你只是把 long-lived credential 換成 short-lived credential, 不是把邊界收乾淨。
我最後的身分模型
我會把這三種角色分開看:
|
|
這樣的核心原則是:
- CI should not get cloud credentials if it does not need them
- infra 不該和應用部署共用同一個身分
- dev deploy 和 prod deploy 不該共用同一個身分
對應到 workflow 的切法
workflow 層我最後收斂成:
|
|
再往下綁 branch boundary:
|
|
這樣 branch policy 和 environment policy 就能互相對齊。
WIF provider 層應該管什麼
provider 層最適合管的是「哪些 workflow + branch 組合能進來」。
概念上像這樣:
|
|
這樣 token 連 exchange 都過不了, 會先被擋在第一層。
為什麼只靠 provider 還不夠
provider 知道的只有 token claim。它不知道 workflow 實際想做什麼。
例如 infra.yml 會接一個 target input:
shareddev-platformprod-platform
provider 不會知道這個 input 是什麼, 所以它沒辦法單靠自己判斷:
sharedmust be main onlydev-platformmust be dev only
這種事情還是要在 workflow 本身顯式驗證。
|
|
這就是我最後採用的 defense-in-depth:
- workflow validation
- provider condition
- service account binding
workflow_ref 和 caller_workflow 這個坑
這是整個 WIF 設計裡最容易踩的坑之一。
如果 workflow 裡又呼叫 reusable workflow, job_workflow_ref 會指向 reusable workflow 本身, 不是最外層入口 workflow。
這代表你如果用錯 claim 做 binding, 很可能會出現這種狀況:
- trust rule technically passes
- but the wrong workflow identity is bound
我最後比較穩的做法是:
|
|
然後 service account binding 綁這個 caller workflow path。
這樣 reusable 那層和入口 workflow 那層才不會混在一起。
最後為什麼我又把 reusable workflows 拿掉
雖然這篇主題是 WIF, 但這兩件事其實有關。
reusable workflows 的問題不是不能用, 而是它會讓這些東西變得比較難看懂:
- who owns permissions
- where
id-token: writeis actually needed - 哪條 workflow 才是真正的操作者入口
- what shows up in the GitHub Actions UI
所以我最後寧可接受一點重複, 也把 deploy workflows 扁平化回:
deploy-dev.ymldeploy-prod.yml
shared steps 再留在 composite actions。
這樣做完之後, 最小權限審查反而更直觀。
id-token: write 不要開太大
另一個我會特別記住的點是: 不要在 workflow scope 就直接給 id-token: write, 除非真的整條 workflow 都需要。
比較乾淨的做法是:
- CI: no id-token
- validate job: no id-token
- only build/migrate/deploy jobs that need auth get id-token
|
|
這樣 branch validation job 就不會莫名其妙取得 OIDC 能力。
GitHub environment-scoped vars 也屬於信任邊界
這一點很常被忽略。
如果某個 job 需要 environment-scoped variables, job 必須真的宣告對應 environment:
|
|
這不只是取值問題, 也是一種 boundary。因為 GitHub environment 本身還可以掛 approval rule。
所以 environment-scoped variables、deploy service account、branch guard, 其實是同一條安全模型上的不同層。
我現在會怎麼檢查這套設計有沒有真的最小權限
我會問這幾題:
- Can PR CI run without any cloud credential?
- Can dev deploy impersonate only the dev deploy SA?
- Can prod deploy impersonate only the prod deploy SA?
- Can infra impersonate only the infra SA?
- Does the wrong branch fail before real work begins?
- Does the provider reject disallowed workflow+branch combinations?
如果其中有一題答不出來, 就還不夠乾淨。
結論
GitHub Actions 接 GCP 的難點從來不是把登入做通而已。
真正難的是把信任邊界切得夠清楚:
- correct workflow
- correct branch
- 正確的 service account
- 正確的 environment
- 正確的權限
這幾層一起成立, 才算真的做到最小權限。