IAM 與應用程式身分簡介
身分與存取權管理(IAM)是 Google Cloud 的安全基石,也是 Professional Cloud Developer 考試中權重最高的情境題類別之一。人類透過 [email protected] 這類 Google 身分存取 GCP,而應用程式則透過 service account(<name>@<project>.iam.gserviceaccount.com)或越來越常見的 聯邦身分(federated identity)來存取 GCP——後者把外部 OIDC/SAML token 換成短期的 Google access token。現代的 GCP 設計強烈推離長壽命的 JSON key 檔(type: service_account 金鑰),改朝向 IAM Credentials API 與 Security Token Service(STS)所核發的短期憑證。理解這整條鏈路——Compute Engine VM、GKE pod、GitHub Actions runner 如何取得 token、需要哪些角色、哪個 IAM condition 會擋下它——就是本章的核心。
這份研讀筆記把原本的精簡 stub 擴展成完整的 app-identity 生命週期,涵蓋:Application Default Credentials(ADC)的解析順序、驅動 VM/GKE/Cloud Run 身分的 GCE metadata server 流程、以 roles/iam.serviceAccountTokenCreator 進行 service account 模擬、GKE Workload Identity(GSA↔KSA 綁定)、跨 AWS / Azure / 一般 OIDC(含 GitHub Actions)的 Workload Identity Federation、以 CEL 撰寫的 IAM Conditions、橫向移動(lateral movement)防禦、iam.disableServiceAccountKeyCreation 組織政策、IAM Deny 政策、Resource Manager Tags 結合 IAM Conditions,以及透過 STS 取得短期憑證。每一節都附上具體的 gcloud 指令、API 欄位名稱與考試陷阱。
Application Default Credentials(ADC)解析
Client library 怎麼找憑證
google-auth 系列函式庫(Go、Python、Node、Java)都實作相同的 ADC 搜尋順序。它們依序檢查:(1)環境變數 GOOGLE_APPLICATION_CREDENTIALS 指向的 JSON 檔;(2)gcloud 使用者憑證 ~/.config/gcloud/application_default_credentials.json(由 gcloud auth application-default login 產生);(3)透過 metadata server 取得 Google Cloud 運算資源上附掛的 service account。函式庫在第一個命中後就停止。這就是為什麼一個在本機設了 GOOGLE_APPLICATION_CREDENTIALS=/tmp/key.json 的開發者,部署到 Cloud Run 之後會驚訝地發現 production 用的是 Cloud Run 服務身分——容器內沒有那個環境變數,ADC 就掉到 metadata server 那一層。
本機開發與 Production 一致性
本機開發推薦的做法是 gcloud auth application-default login --impersonate-service-account=<sa>@<proj>.iam.gserviceaccount.com。這讓開發者先用自己的人類 Google 身分證明身分,再去模擬部署環境的 runtime service account,使本機程式碼拿到的 scope 與 IAM 綁定與 production 完全相同。另一個替代方案——下載 production SA 的 JSON key——正是 iam.disableServiceAccountKeyCreation 與 IAM Deny 政策要阻擋的反模式。
Application Default Credentials(ADC): Google client library 的憑證查找慣例,依序從 GOOGLE_APPLICATION_CREDENTIALS、~/.config/gcloud/application_default_credentials.json、再到 GCE metadata server(169.254.169.254)找憑證,採用第一個找到的那一組。
GCE、GKE、Cloud Run 上的 Metadata Server 流程
169.254.169.254 端點
每一個 Google 託管的運算資源(GCE VM、GKE node、Cloud Run revision、Cloud Functions instance、App Engine instance)取得身分的方式都是呼叫 metadata server http://metadata.google.internal/(解析到 link-local 位址 169.254.169.254)。兩個關鍵路徑是:/computeMetadata/v1/instance/service-accounts/default/token(回傳短期 OAuth2 access token)與 /computeMetadata/v1/instance/service-accounts/default/identity?audience=...(回傳由 Google 簽章的 OIDC ID token)。所有請求都必須帶上 Metadata-Flavor: Google 標頭,以防止從 VM 外部對該端點發起簡易 SSRF 攻擊。
各 runtime 的差異
在 GCE VM 上,service account 是用 --service-account=<sa>@... 加上 --scopes=cloud-platform 在開機時指派的;access scopes 在 IAM 之上多疊一層過濾。Cloud Run 上的身分是在 service 或 revision 用 --service-account= 指派,access scopes 隱含為 cloud-platform。GKE 預設會把 node 的 service account 透過 metadata server 暴露給所有 pod,這就是為什麼 GKE Workload Identity 要用由 gke-metadata-server 提供的 pod 層級身分取代它。Cloud Functions Gen2 底層其實是 Cloud Run,所以 function 看到的是底層 Cloud Run service 的身分。
Metadata server 拿到的 token 由 client library 快取,效期最長 3600 秒。千萬不要寫成每個 request 都重新打 /token;函式庫會在到期前約 5 分鐘自動更新。對 /token 緊湊輪詢是經典反模式,考題會用這點設陷阱。
Service Account 模擬與 Token Creator
IAM Credentials API
模擬(impersonation)是由 IAM Credentials API(iamcredentials.googleapis.com)實作,提供 generateAccessToken、generateIdToken、signBlob、signJwt 等方法。要對目標 service account 呼叫這些方法,呼叫者 principal 必須在 目標 SA 這個資源上 持有 roles/iam.serviceAccountTokenCreator(不是 project 上)。這是最重要必背的模擬角色:底層權限是 iam.serviceAccounts.getAccessToken。
gcloud 旗標
--impersonate-service-account=<target-sa> 幾乎能用在所有 gcloud 指令以及 Terraform 的 google provider 上。底層 gcloud 先以呼叫者身分驗證,再呼叫 generateAccessToken 為目標 SA 取得一個 1 小時 token,並用該 token 去執行真正的 API 呼叫。generateAccessToken 的 --lifetime 預設最高 3600 秒;要拉到最高 43,200 秒(12 小時)必須有組織政策 iam.allowServiceAccountCredentialLifetimeExtension 把該 SA 列入白名單。
模擬鏈
--delegates 旗標可以把模擬串接成鏈:principal A → SA B → SA C。每一節都需要對下一個 SA 持有 Token Creator。Break-glass 與 just-in-time 提權就是這樣實作的,沒有人會永久持有最終目標角色。
把 CI 裡所有 gcloud iam service-accounts keys create 腳本通通換成 --impersonate-service-account。CI runner 透過 Workload Identity Federation 驗證後,再去模擬部署 SA——沒有任何 JSON key 離開 Google Cloud,目標 SA 的 audit log 還能精準記錄誰在何時模擬誰。
GKE Workload Identity
GSA ↔ KSA 綁定
GKE Workload Identity 是讓 pod 取得專屬 Google service account 身分、又不必掛載 JSON key 的官方做法。機制是一個雙向綁定:(1)叢集上啟用 Workload Identity(--workload-pool=PROJECT_ID.svc.id.goog)並把每個 node pool 的 workload metadata 模式設為 GKE_METADATA;(2)在 IAM 中把 Kubernetes ServiceAccount principal serviceAccount:PROJECT_ID.svc.id.goog[NAMESPACE/KSA_NAME] 授予 roles/iam.workloadIdentityUser 於目標 GSA 上;(3)在 KSA 加上註解 iam.gke.io/[email protected]。使用該 KSA 的 pod 就會透過叢集內的 gke-metadata-server proxy 拿到 GSA 的 token。
Node SA 為什麼會「消失」
Workload Identity 啟用前,node 上的任何 pod 都能從 metadata server 讀到 node 的 Compute Engine default service account。Workload Identity 在 node pool 啟用後,對 169.254.169.254 的請求會被攔截:pod 只看到綁到自己 KSA 的 GSA,底層 Compute Engine node SA 從 pod 內部完全不可達。這消除了一大類 GKE 提權路徑。
常見失敗模式
GKE 拿到 403 幾乎都來自三件事之一:KSA 註解漏掉或拼錯;workloadIdentityUser 綁定寫錯了 [namespace/ksa];或 node pool 建立時沒帶 --workload-metadata=GKE_METADATA。用 gcloud container node-pools describe 確認 workload metadata 模式。
已棄用的 Metadata Concealment 功能(--workload-metadata-from-node=SECURE)不等於 Workload Identity。Concealment 只是擋住 kube-system 看 instance metadata;pod 仍會繼承 node SA。只有 Workload Identity 才提供 pod 層級的 GSA 身分。任何「用 metadata concealment 修 GKE 權限問題」的答案在考試上都是錯的。
AWS、Azure 與 OIDC 的 Workload Identity Federation
Workload Identity Pool 與 Provider
Workload Identity Federation(WIF)讓外部工作負載(AWS role、Azure managed identity、on-prem OIDC、GitHub Actions、GitLab、Terraform Cloud、CircleCI)不必持有 service account key 也能呼叫 Google API。流程是建立一個 Workload Identity Pool(gcloud iam workload-identity-pools create),在其中建立 AWS、OIDC 或 SAML 類型的 Provider。Provider 會宣告 --issuer-uri、允許的 audience,以及 attribute mapping(例如 google.subject = assertion.sub)和一個 CEL 寫的 attribute condition(例如 assertion.repository == 'org/repo')。
透過 STS 進行 Token Exchange
外部工作負載把自家原生 token(AWS GetCallerIdentity request、Azure AD JWT、GitHub Actions OIDC JWT)送到 Google 的 Security Token Service sts.googleapis.com/v1/token,附上 grant_type=urn:ietf:params:oauth:grant-type:token-exchange。STS 依 Provider 設定驗證 token、套用 attribute mapping,回傳一個 federated access token。該 federated token 再透過 generateAccessToken 去模擬一個真實 GSA——所以外部工作負載仍需在目標 GSA 上持有 roles/iam.workloadIdentityUser,授予對象是聯邦 principal principal://iam.googleapis.com/projects/.../locations/global/workloadIdentityPools/POOL/subject/SUBJECT。
各 Provider 重點
AWS:Provider 用 --aws-account-id,可從該帳號內任何 IAM role/user 進行聯邦;attribute condition 通常釘住 assertion.arn。Azure:provider 用 --issuer-uri=https://sts.windows.net/<tenant>/,attribute condition 釘 assertion.aud 與 managed identity 的 sub。一般 OIDC:issuer URI 必須能被存取,並提供有效 JWKS 文件。
Workload Identity Federation 讓所有外部 CI/CD 或跨雲工作負載都不再需要下載 JSON service account key。這也是 PCD 考試會反覆考的「最大安全提升」答案——只要看到 JSON key 被存在 Jenkins、GitHub Secrets 或 CircleCI 環境變數,答案就要選 WIF。
GitHub Actions OIDC 串接 GCP
端到端串接
GitHub Actions 會對每個工作核發 OIDC JWT(前提是 job 設定 permissions: id-token: write)。token 的 iss 是 https://token.actions.githubusercontent.com、sub 形如 repo:org/repo:ref:refs/heads/main,aud 預設為要求的對象。串接到 GCP 的步驟:建立 WIF pool、加入 OIDC provider 並設 --issuer-uri=https://token.actions.githubusercontent.com、--attribute-mapping=google.subject=assertion.sub,attribute.repository=assertion.repository、--attribute-condition=assertion.repository=='myorg/myrepo'。然後把目標 GSA 上的 roles/iam.workloadIdentityUser 綁給 principalSet principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL/attribute.repository/myorg/myrepo。
Workflow 中的呼叫
官方 google-github-actions/auth@v2 action 會處理 STS 交換。傳入 workload_identity_provider(完整資源名稱)與 service_account(要模擬的 GSA),action 會把 GOOGLE_APPLICATION_CREDENTIALS 指向以聯邦 token 為基礎的短期憑證檔,後續 job 中所有 gcloud 與 SDK 呼叫都會以該 GSA 身分驗證。
釘住分支與環境
這是考試最愛考的點:泛用的 repo:org/repo:* subject 會允許任何分支(包含惡意 PR 分支)進行聯邦。production 部署一定要把 attribute condition 縮到 assertion.ref == 'refs/heads/main',或改用 GitHub Environments 並釘 assertion.environment == 'prod'。錯誤地只設 attribute.repository_owner == 'myorg' 已是多起真實外洩事件的根因。
橫向移動防禦
模擬圖(impersonation graph)問題
每一條 serviceAccountTokenCreator 綁定其實是有向圖上的一條邊:principal A 可變成 SA B。若 A 能變 B、B 對 C 有 Token Creator,A 就遞移地能變成 C。攻擊者拿到低權限身分後常常沿這張圖橫向擴張。防禦靠基本衛生:絕對不要在 project 層級授予 Token Creator(在 project 上授予 roles/iam.serviceAccountTokenCreator 等於讓 principal 可模擬該專案內的每一個 SA),永遠在特定目標 SA 上授予;用 Policy Analyzer(gcloud asset analyze-iam-policy --permissions=iam.serviceAccounts.getAccessToken)審視這張圖。
拆解傳統 Editor 反模式
舊式的 roles/editor primitive role 包含 iam.serviceAccounts.actAs,意味著在 project 層級任何 editor 都可以把 任何 SA 掛到新建的 GCE VM 或 Cloud Function 上——然後就從 metadata server 讀到該 SA 的 token。要把 Editor 換成只在 特定 SA 上授予 roles/serviceAccountUser。
roles/iam.serviceAccountUser(act-as)與 roles/iam.serviceAccountTokenCreator(impersonate)是不同的。ActAs 是在資源建立時把 SA 掛上去所需的權限(VM、Cloud Run service、Cloud Build job);TokenCreator 則是按需 核發 token。考試常把這兩個調包來測你是否真的懂——gcloud run deploy --service-account=... 需要的是目標 SA 上的 ActAs。
停用 Service Account Key 建立
組織政策約束
布林型組織政策約束 constraints/iam.disableServiceAccountKeyCreation 在組織或資料夾層級強制執行時,會擋下所有人(含 project owner)的 iam.serviceAccountKeys.create。在組織根節點套一次,整個 .json key 攻擊面就消失。配套要記的還有 constraints/iam.disableServiceAccountKeyUpload(擋自帶金鑰)、constraints/iam.disableServiceAccountCreation(避免 SA 氾濫)與 constraints/iam.allowedPolicyMemberDomains(擋外部 @gmail.com principal)。
改用什麼
一旦 key 被禁,每個工作流程都必須改用其中之一:附掛 service account(GCE、GKE、Cloud Run、Cloud Functions、Cloud Build)、Workload Identity(GKE)、Workload Identity Federation(外部 CI / 跨雲)、或透過 Token Creator 模擬。沒有第四種選項,考試也預期你能完整列出這個清單。
單一專案例外
如果真有單一遺留工作負載必須使用 key,做法是在組織層級設為 denyAll,再以單一專案 tag 範圍加上明確的 enforce: false 規則例外,而非把整個組織約束關掉。若無法淘汰 key,要搭配 Secret Manager + 90 天自動輪轉。
以 CEL 撰寫的 IAM Conditions
條件式綁定的結構
一條 IAM 政策綁定有 role、members、可選 condition 三個欄位。Condition 是物件 { title, description, expression },其中 expression 是存取時才求值的 Common Expression Language(CEL)片段。例如:把 roles/storage.objectViewer 綁給 serviceAccount:reports@...,附條件 resource.name.startsWith('projects/_/buckets/finance-reports/'),該角色只在符合的 bucket 前綴被存取時才生效。
常用屬性
App 開發者最常用的 CEL 函式與屬性:resource.name、resource.type、resource.service、request.time < timestamp('2026-12-31T23:59:59Z') 用於到期、request.time.getHours('Asia/Taipei') < 18 用於只在上班時段、'tagKeys/123' in resource.matchTag('env', 'prod') 用於依資源 tag 限縮。條件式綁定是逐請求求值;運算式回傳 true 時,該角色就針對該次呼叫生效。
不支援條件的場合
IAM Conditions 不是 每個資源型別都支援。Project 層級的 primitive Owner/Editor/Viewer 不能加條件,某些舊版服務(例如部分 App Engine API)會完全忽略條件。設計前一定要先查官方 "Conditional bindings supported resources" 表。
考試必認得的 CEL 寫法:request.time < timestamp(...) 用於時限授權、resource.name.startsWith(...) 用於前綴範圍、resource.matchTag(KEY, VALUE) 用於 tag 範圍、request.auth.access_levels 用於 VPC-SC context、has(...) 用於安全的屬性存在檢查。
IAM Deny 政策
Deny vs Allow
IAM Deny 政策是獨立、優先層級更高的層次,會明確禁止特定 principal 在資源層級節點(組織、資料夾、專案)上的特定權限。匹配到的 Deny 規則會覆蓋所有 Allow 綁定,連 primitive Owner 都擋得住。政策本體在 policies.googleapis.com,用 gcloud iam policies create --kind=denypolicies 管理。
常見 Deny 規則
教科書範例:對除少數核心引導群組以外的所有人 deny iam.serviceAccountKeys.create;對 finance folder 內 SRE 群組以外的 principal deny storage.buckets.delete;deny compute.instances.setServiceAccount 以防攻擊者把低權限 VM 換成高權限 SA。每條規則支援 deniedPrincipals 清單加上 exceptionPrincipals 清單與可選的 CEL condition。
Deny 的價值
當你想要組織或資料夾層級「不論底下誰怎麼授權都禁止」的保證時,Deny 就是對的工具。Allow 綁定仍需維持乾淨,但 Deny 提供一個可承受專案 owner 不小心過度授權的保險絲。
Resource Manager Tags + IAM Conditions
Tag 作為控制平面
Resource Manager Tags 是可掛在資源上的鍵值標記,可以參與 IAM 條件評估。流程:在組織層級建立 tag key env 與值 prod、staging、dev,把 env=prod 掛到特定資源(專案、VM、GCS bucket、BQ dataset)。IAM Condition 就能寫 resource.matchTag('123456789/env', 'prod'),並授予一個只在帶有 prod tag 的資源上才生效的角色。
Tag 綁定
這跟 label 本質不同:tag 會參與 IAM 政策評估,label 只是純粹的詮釋資料。常見模式是把 roles/run.invoker 授予某個 service account,條件 resource.matchTag('env', 'prod'),這樣綁定就跟著 tag 走——Cloud Run service 從 staging 重新貼成 prod 後,存取權自動翻轉,無須改 IAM 政策。
引導期警告
Tag 條件對於沒有貼上該 tag 的資源會評估為「不匹配」,因此依賴 env=prod 的權限對任何忘了貼標的 prod 資源都會回 403。請把貼 tag 動作做進 IaC(Terraform 的 google_tags_tag_binding),不要靠人工 gcloud resource-manager tags bindings create。
透過 STS 取得短期憑證
為何要 STS
Google 的 Security Token Service(sts.googleapis.com)是 OAuth2 token-exchange 端點,是 GCP 上每一條現代憑證流程的底層:WIF token exchange、Cloud Storage 的 downscoped 憑證、以及對第三方的憑證聯邦。STS 核發的 access token 會限定目標 principal 與生命週期——預設 3600 秒,可設定到更短,或在組織政策核准下最長 12 小時。
Cloud Storage 的 Downscoped 憑證
一個特殊的 STS 流程是 Cloud Storage 的 Credential Access Boundaries:持有廣泛 GCS token 的人,可以把一份邊界政策一起送到 STS——「產生的 token 只能對 bucket customer-123-data 做 storage.objects.get」——換得一個範圍極窄的子 token,交給不可信進程。這是多租戶 SaaS 要給某客戶瀏覽器一個一次性上傳網址、又不想暴露 parent SA 的官方建議模式。
為什麼短效期重要
外洩一個 1 小時 token 的事故規模遠小於外洩一個有效 10 年的 JSON key。配上 VPC-SC 邊界、條件式綁定與 Cloud Audit Logs,短期憑證讓任何洩漏的「炸開半徑」天生被時間框住。PCD 考試在「Secret Manager 存 key 然後 90 天輪轉」與「短期 token」之間,永遠選後者。
白話文解釋
類比 1:員工識別證
Service account 就像應用程式的員工識別證,它不是人,但代表這支應用的身分。應用想進某個房間(存取資源)時,把識別證在讀卡機上一刷,識別證自己就知道能開哪幾扇門。Workload Identity Federation 則像是讓另一家公司的外包工程師拿著 自家 公司的識別證進你家大廳;你不需要核發一張新的長期識別證給他,前台會即時跟對方公司確認他的身分,再印一張一日期限的訪客證。
類比 2:飯店總控房卡
Metadata server 就像飯店房間裡的內線電話。你不會把總控房卡帶在身上;客房清潔需要時,他用房內電話打回櫃台,櫃台核對身分後派一張只能用一小時的房卡到門口。那張短效期房卡就是 access token。飯店從不把總控卡複製給你,就算小偷偷到一張房卡,60 分鐘後也作廢。
類比 3:經公證的授權書
以 Token Creator 進行 service account 模擬,就像拿了一份經過公證的授權書。原始 principal(你)把一份「我授權此人在接下來一小時內擔任 CFO」的書面文件交給公證人(IAM Credentials API),公證人比對你的簽名(Token Creator 綁定)後核發一份時限代理書。下游所有看到的都是 CFO 權限,但稽核紀錄會清楚記下是你發起的代理。IAM Conditions 則是授權書上的小字注記:「僅限台北時間 9 點到 17 點之間有效、僅限新台幣 10 萬以下交易、僅限 prod 帳本」。
常見問題(FAQs)
Q1:使用者帳戶與服務帳戶有什麼差別?
使用者帳戶綁人類身分(@gmail.com 或 Workspace/Cloud Identity 網域),由 Google Workspace 或 Cloud Identity 管理。服務帳戶是 GCP 專案內部的資源(<name>@<project>.iam.gserviceaccount.com),代表非人類工作負載。服務帳戶可被掛到運算資源上、可被模擬、可參與聯邦;使用者帳戶不能掛到 VM 上、也沒有 JSON key 概念。
Q2:我什麼時候該用 Workload Identity Federation,而不是 service account key?
幾乎一律該用,除非該工作負載完全隔絕網路、無法對 sts.googleapis.com 發出 HTTPS。WIF 是 GitHub Actions、GitLab CI、CircleCI、AWS/Azure 上的 Jenkins、Terraform Cloud、on-prem Kubernetes 與任何第三方 SaaS 的官方答案。在 2026 年,service account key 已經是反模式,考試也是這樣考。
Q3:serviceAccountUser 與 serviceAccountTokenCreator 差在哪?
serviceAccountUser(iam.serviceAccounts.actAs)讓 principal 把目標 SA 掛到新資源(VM、Cloud Run service、Cloud Build trigger)。serviceAccountTokenCreator(iam.serviceAccounts.getAccessToken、signBlob、signJwt)讓 principal 透過 IAM Credentials API 核發 token。部署要 ActAs;臨時模擬要 TokenCreator。
Q4:可以全組織封鎖 JSON key 建立嗎?
可以。在組織節點強制 constraints/iam.disableServiceAccountKeyCreation,整個 iam.serviceAccountKeys.create 權限就會 denyAll,不論角色綁定怎麼設都無法越過。再搭配 constraints/iam.disableServiceAccountKeyUpload 連自帶金鑰也擋。
Q5:GitHub Actions pipeline 要存取 GCS 的正確做法?
設定 Workload Identity Pool 與 GitHub OIDC provider,把 attribute condition 釘到 assertion.repository == 'org/repo'(理想再加 assertion.ref == 'refs/heads/main'),把目標部署 SA 上的 roles/iam.workloadIdentityUser 綁到聯邦 principalSet,授予該 SA roles/storage.objectAdmin 於目標 bucket,並在 workflow 中使用 google-github-actions/auth@v2。整段流程沒有任何 JSON key 離開 Google。
Q6:Metadata server 拿到的 access token 效期多長?
預設最高 3600 秒。client library 會在到期前約 5 分鐘自動更新。透過 IAM Credentials API 自訂可更短,或在 SA 列入 iam.allowServiceAccountCredentialLifetimeExtension 組織政策時最長 12 小時。
Q7:IAM Conditions 與 IAM Deny 政策差在哪?
IAM Conditions 是把 CEL 表達式掛在 Allow 綁定上——只有條件為真時該角色才生效。IAM Deny 政策是完全獨立的頂層資源,會明確禁止某些 principal 的權限,覆蓋任何 Allow。精細的「只限於某個 bucket 前綴」用 Condition;組織層級的「SRE 以外永遠不能刪 finance bucket」用 Deny。