Google Play AAB 上傳採 keyless WIF(r0adkll/upload-google-play + google-github-actions/auth),不建 SA JSON key
Context
Keith 規劃調整 mobile 打包流程,補上 Android App Bundle(AAB)→ Google Play 這條發布路徑, 並提到需要「一個可以上傳到 Google Play 的 service account」。需先確認此設計與既有架構約束是否一致。
現狀(查證自 cd-mobile.yml、infra/terraform/modules/{iam,wif}、ADR-0017):
| 面向 | 現狀 |
|---|---|
| Android 產出 | cd-mobile.yml 的 android-beta job 只產 APK 丟 Firebase App Distribution;無 AAB、無 Google Play 對接、無 fastlane |
| CI → GCP 認證 | 一律 Workload Identity Federation(modules/wif/main.tf):GitHub OIDC 冒充 github-actions-deployer SA,無下載式金鑰 |
| SA JSON key | GCP org policy constraints/iam.disableServiceAccountKeyCreation 禁止建立(見 docs/runbook/deployment/stage-runbook-prereq.md) |
| 相關決策 | ADR-0017(Firebase 治理)Decision 表確立「Deployer SA / WIF → 無下載式金鑰」;但 ADR-0017 範圍是 Firebase project myyibi,未涵蓋 Google Play(Play Console 是與 Firebase 完全獨立的系統) |
關鍵約束:傳統 Play 上傳工具(fastlane supply、Gradle Play Publisher、r0adkll/upload-google-play)
預設要餵一把 SA JSON key——而本專案 org policy 禁止建 key。因此需決定:能否全程無 key 上傳 Play,
以及用哪個工具。此外,Google Play 的授權是兩層:GCP IAM(SA 存在 + 可換 token)與
Play Console 端(SA email 必須被邀進 Console 授權),後者活在 GCP IAM/Terraform 之外。
Decision
已裁決(accepted,2026-06-24):keith 於 PR #591 review、howie 裁決下方四項決定(原 Open Questions)。
結論:Google Play AAB 上傳採 keyless Workload Identity Federation,沿用既有
google-github-actions/auth + WIF 機制,不建立任何 SA JSON key;上傳工具採
r0adkll/upload-google-play。四項落地決定:
- SA:新建專用
play-publisherSA,由 Terraform 管 SA 本體(modules/iam/main.tf)+ WIF 綁定(modules/wif/main.tf);Play Console 邀請為手動步驟。 - Track:CI 自動上傳到
internaltrack;production 留人工 promote(對齊現行 beta 自動化到測試層、正式發布留人工關卡的精神)。 - App Signing:啟用 Google Play App Signing(發 AAB 的強制前提,2021-08 起新 app 預設且強制);現有
ANDROID_KEYSTORE_BASE64keystore 降格為 upload key,真正的發布金鑰由 Google 代管。注意:Play App Signing 與 GCP Secret Manager 無關(前者管 app 簽章金鑰,後者管後端 secret)。 - 工具:
r0adkll/upload-google-play(官方 README 將 keyless WIF 列為推薦路徑)。
機制要點(查證 2026-06-24,來源見 Links):
- keyless 的接法不是餵裸 access token,而是餵 ADC credential config 檔——
google-github-actions/auth@v2的credentials_file_path輸出(external_account 型別、無 key)。 三大工具都吃這條,但都不設計來吃token_format: access_token的裸 token,故 auth step 用預設的credentials_file_path、不要用token_format: access_token。 r0adkll/upload-google-play在官方 README 把此 keyless WIF 路徑列為「more secure, recommended」, 直接文件化serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }},與本專案現有 WIF 架構無縫對接、零 key、無版本不確定性。- workflow 四步:(1)
permissions: id-token: write;(2)google-github-actions/auth@v2用既有workload_identity_provider+service_account;(3)r0adkll/upload-google-play@v1設serviceAccountJson: ${{ steps.auth.outputs.credentials_file_path }};(4) 該 SA email 先在 Google Play Console「Users and permissions」邀請並授權。
落地需補五件事(其中第 3、4 件 Terraform 管不到):
關鍵澄清:Play 發布權限不是 GCP IAM role。Google Play 對 SA 的授權完全在 Play Console 授予(Users and permissions),沒有對應的
roles/androidpublisher.*GCP IAM role 可在 iam module 設定。Terraform 在 GCP 端只負責「API 啟用」與「WIF/impersonation 綁定」,不負責 Play 發布授權本身。
| # | 要做什麼 | 在哪裡 | Terraform 管得到? |
|---|---|---|---|
| 1 | 啟用 androidpublisher.googleapis.com API | infra/terraform/bootstrap/main.tf(google_project_service.apis 集合) | ✅ |
| 2 | (僅當 OQ#1 採專用 play-publisher SA 時) 在 iam module 建該 SA,並在 WIF module 授其 roles/iam.workloadIdentityUser(現只綁 deployer SA),否則 google-github-actions/auth 無法替新 SA 產 credential file | infra/terraform/modules/iam/main.tf + modules/wif/main.tf | ✅ |
| 3 | 把上傳 SA email 邀進 Google Play Console 並授 release 權限(這就是 Play 發布授權的唯一來源) | Play Console 網頁手動 | ❌(runbook 記錄,比照 ADR-0017 不可 IaC 項目) |
| 4 | (一次性,全新 package only — Keith 補充) 人工在 Play Console 建立 app 並上傳第一版 APK/AAB | Play Console 網頁手動 | ❌ |
| 5 | cd-mobile.yml 加 flutter build appbundle 產 AAB + 上傳 step | .github/workflows/cd-mobile.yml | — |
Google Play API 硬限制(bootstrap):Android Publisher API 不接受全新 package 的第一版—— 必須先人工在 Play Console 建立 app 並上傳一次 APK/AAB,API 才認得這個 package。第 4 步是 CI(第 5 步)能跑的前置條件,且僅全新 package 需要一次;既有 package 後續版本全走 API/CI。
若沿用既有
github-actions-deployerSA(OQ#1 的另一選項),第 2 步不需要——既有 WIF 綁定已涵蓋 deployer SA,僅需在 Play Console 邀請它(第 3 步)。
與 ADR-0017 的關係:本決策正交且可獨立執行。ADR-0017 處理 Firebase project myyibi
(google-beta provider、import app、config 改存 Secret Manager、per-env Firebase project),
本決策處理 Google Play Console + androidpublisher,兩者唯一交集是共用既有 WIF/deployer SA 機制與
cd-mobile.yml。ADR-0017 的 Firebase 治理工作不依賴本決策,可先行;本決策亦不需等 ADR-0017 落地。
Alternatives Considered
- 方案 A:SA JSON key + fastlane
supply(傳統做法) — 考慮過,因為是最普遍的範例; 但牴觸 org policyiam.disableServiceAccountKeyCreation(建不出 key)與 ADR-0017 「無下載式金鑰」原則,沒有選。 - 方案 B(推薦):keyless WIF +
r0adkll/upload-google-play— 唯一在官方 README 把 keyless WIF 列為推薦並文件化的工具,與既有 WIF 架構無縫、零 key、無版本風險。選為預設。 - 方案 C:keyless WIF + fastlane
supply(走 ADC) — 可行(fastlane 官方文件列出支援 WIF; 推測經 ADC(GOOGLE_APPLICATION_CREDENTIALS指向 config 檔)即可省略json_key,惟官方未逐字 載明此參數可完全省略,需 CI 實測確認);且舊版有missing client_emailbug(fastlane issue #29491,external_account 憑證被誤判為 service_account), 需確認 gem 版本已修。功能較全,列為備選。 - 方案 D:keyless WIF + Gradle Play Publisher(
useApplicationDefaultCredentials = true) — 可行(走 ADC、serviceAccountCredentials非必填);但官方 README 未明寫支援 WIF,需自行 CI 實測。備選。 - 方案 E:維持 Play Console 網頁手動上傳 — 考慮過(零 CI 改動);但不可重現、無版本紀錄、 不符 CI 自動化發布目標,沒有選。
關鍵事實:沒有任何工具「無論如何都需要 JSON key」——三者皆有 keyless 路徑,且全都依賴 「ADC credential config 檔」這條,而非裸 access token。
Consequences
好:
- 全程無 SA JSON key,符合 org policy 與 ADR-0017「無下載式金鑰」原則;CI 認證機制與既有部署完全一致。
- Play 發布 CI 可重現、可版本控制(workflow + AAB build step)。
- 與 ADR-0017 Firebase 治理工作正交,兩條可獨立並行推進。
壞 / 代償:
- Play Console 的 SA 授權是 Console 手動步驟,Terraform /
gcloud/ WIF 全碰不到—— 必須寫進docs/runbook/deployment/,比照 ADR-0017 對「不可 IaC 項目」的處理。 - 需新增
androidpublisherAPI 啟用 +(採專用 SA 時)WIF 綁定 + Play Console 授權 +cd-mobile.yml的 AAB build/上傳 step。 - 首次須完成 Google Play App Signing 註冊與 release track 設定(一次性)。
- 全新 package 的第一版必須人工在 Play Console 上傳一次(Android Publisher API 不接受全新 package 的首版),CI 自動化只能從第二版起接手——這是 Play API 的硬限制,無法繞過(見落地清單第 4 步)。
credentials_file_pathvsaccess_token是易錯點:用錯(餵裸 token)會認證失敗。
中性:
- 上傳工具最終選 B/C/D 皆為 keyless ADC 路徑;選型差異在維護成本與版本確定性,不影響「無 key」結論。
Resolved Decisions(原 Open Questions,2026-06-24 裁決)
四項皆已裁決,內容見上方 Decision 段落 1–4:
- ✅ 上傳 SA:新建專用
play-publisherSA(least-privilege,避免發布權限與部署權限混在同一身分),Terraform 管 SA + WIF 綁定。採專用 SA 必須在modules/wif/main.tf為其加roles/iam.workloadIdentityUser(見落地清單第 2 步),否則 keyless auth 換不到 credential file。 - ✅ 上傳 track:CI 自動上
internal,production 人工 promote。 - ✅ Play App Signing:啟用 Google 代管(發 AAB 強制前提);現有 keystore 降格為 upload key。與 GCP Secret Manager 無關。
- ✅ 工具:
r0adkll/upload-google-play(C fastlane 功能更全但有missing client_email版本不確定性;D GPP 官方未明寫 WIF 需實測——均未選)。
後續實作(非本 ADR 範圍)
本 ADR 只定方向;實際 cd-mobile.yml AAB build/upload step、Terraform SA + WIF 變更、Play Console 一次性 bootstrap(建 app + 首版手動上傳 + 邀請 SA)的執行,另開 PR / runbook 落地。
Links
- fastlane WIF 支援:https://docs.fastlane.tools/actions/upload_to_play_store/
- fastlane
missing client_emailbug:https://github.com/fastlane/fastlane/issues/29491 - r0adkll/upload-google-play(README,keyless WIF 推薦路徑):https://github.com/r0adkll/upload-google-play/blob/master/README.md
- Gradle Play Publisher(ADC):https://github.com/Triple-T/gradle-play-publisher/blob/master/README.md
- google-github-actions/auth:https://github.com/google-github-actions/auth
- GCP keyless WIF 指引:https://cloud.google.com/blog/products/identity-security/enable-keyless-access-to-gcp-with-workload-identity-federation
- 相關 ADR:ADR-0016(Firebase Auth identity broker)、ADR-0017(Firebase 治理:Terraform/CLI/config)