跳至主要內容
GCP 入門基礎 — 第 5/5 篇

GCP IAM 入門:看懂身分、角色與權限繼承

內文字級

GCP IAM 入門:看懂身分、角色與權限繼承
更新:2026-10-22

VM 和 VPC 建好後,很快就會碰到權限問題:誰可以查看 VM?應用程式要怎麼讀取 Cloud Storage?成員離開團隊後,又該從哪裡移除他的存取權?

Cloud IAM(Identity and Access Management)處理的就是這些問題。它把身分、角色和資源串在一起,決定某個人或工作負載能對哪些資源執行哪些動作。

這篇先拆解 IAM 的資源層級與政策繼承,再比較角色與服務帳號。完整實作集中在第六節:使用一個私有 Bucket 和新的服務帳號,取得授權前、授權後、撤回後的對照,不下載長效金鑰。

選擇閱讀路線與實作範圍
  • 觀念閱讀:先讀第一到五節,再看 Conditions、政策限制及文末四題;不需要建立資源。7 天計畫 Day 4 的只讀觀察/方案推演也走這條路線。
  • 完整實作:第六節準備約 60–75 分鐘,包含短期模擬、正反驗證、撤回與清理;權限等待與排錯另計。
  • 提前中止:沿用保存的設定,跳到撤回物件讀取授權或清理本次 IAM 資源,不為排錯擴大專案角色。

教材核對:2026-10-22。前半段的角色、JSON 與組織政策是觀念示意;需要變更雲端資源的指令集中在完整實作中。

GCP IAM 存取模型:使用者、群組或服務帳號等 Principal 透過 Policy 取得由多個 Permission 組成的 Role,並作用在 Resource;Organization、Folder、Project、Resource 的政策向下繼承,右側比較最小權限的預定義角色、過大的基本角色與工作負載免下載金鑰的服務帳號

一筆 IAM Binding 會記錄「哪個 Principal,在什麼 Resource 上取得哪個 Role」。Role 是一組權限,政策則會從 Organization、Folder、Project 往下繼承。授權時除了選角色,也要確認綁定層級;工作負載若能使用附加身分或聯邦身分,就不必另外保存長效服務帳號金鑰。


一、先看懂 IAM 的四個元素

IAM 的三個核心問題

每一次存取 GCP 資源,系統都要回答三個問題:

Who          can do What                 on Which resource?
誰            可以做哪些動作               在哪個資源上?
Principal  + Role(包含 Permissions)  + Resource

Principal 是發出請求的身分,Role 是一組 Permission,而 Resource 是權限生效的範圍。IAM Policy 負責把三者綁在一起。

用門禁系統來理解

把 GCP 想像成一棟辦公大樓:

  • 主體(Principal):拿著識別證的人,例如員工、訪客或清潔人員
  • 角色(Role):識別證對應的一組通行權限
  • 資源(Resource):辦公室、會議室或機房
  • IAM 政策(Policy):記錄誰能進入哪些區域的門禁規則

當你說「小明可以管理 Project A 裡的 VM」,你就是在建立一個 IAM Binding:

小明(Principal)+ Compute Instance Admin(Role)+ Project A(Resource)

二、政策會從哪一層生效?

GCP 資源的四層結構

常見容器層級如下。個人專案可能沒有 Organization,Folder 可以省略;各服務也可能有更細的子資源,並非每一種資源都能直接綁定 IAM 政策:

Organization(組織,若有)
    └── Folder(資料夾,可省略)
        └── Project(專案)
            └── Resource(資源)
                例如:VM、Cloud Storage、Cloud SQL

政策繼承規則

IAM 政策具有向下繼承的特性:

Organization 層級授予的權限
    ↓ 自動繼承到所有下層
    Folder 層級授予的權限
        ↓ 自動繼承到所有下層
        Project 層級授予的權限
            ↓ 自動繼承到所有下層
            Resource 層級授予的權限(僅限該資源)

Allow Policy 會向下繼承;某個資源的有效允許政策是直接與繼承 Allow 的聯集,不能直接把它當成最終存取結果。舉例來說,如果在 Organization 層授予某人 Viewer,他在下方的 Project 也會取得 Viewer;只在 Project 層刪除另一筆 Binding,並不會抵消這項繼承權限。

實際請求還會評估相關的 Deny 與 Principal Access Boundary(PAB)政策,以及授權條件。Deny 可以阻擋其支援的權限;PAB 決定主體可存取的資源範圍。Organization Policy 則限制支援的資源行為,不能代替所有存取控制。排錯時應分清「哪裡授予了 Allow」與「什麼限制阻擋了請求」。

綁在哪一層比較合適?

場景建議做法
全組織安全基線依控制目標選 IAM Deny/Organization Policy,核對支援範圍與繼承
部門共用存取在 Folder 層授予開發部門讀取權限
日常工作授權在 Project 層管理個別成員權限
特定資源存取在 Resource 層做細部控制

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。


三、哪些身分可以取得權限?

六種主體(Principal)

官方另外還列有 Service agents、Workforce identity pool 等主體類型(見官方 Principal types),本文先介紹其中六種:

主體類型說明使用場景
Google Account個人 Google 帳號(@gmail.com 或公司帳號)開發者、管理員
Service Account代表應用程式或工作負載的特殊帳號VM、Cloud Run、GKE
Google Group一組 Google 帳號的集合部門授權、批次管理
Cloud Identity Domain代表整個 Workspace/Cloud Identity 網域的虛擬群組企業帳號管理
allAuthenticatedUsers經 Google 帳號驗證的廣泛使用者及服務帳號,不限公司網域需先確認服務支援與公開範圍
allUsers任何人(含匿名使用者)公開資源(如公開網站)

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

團隊權限可以交給 Google Group 管理

成員人數一多,逐一綁定個人帳號很容易漏改。比較好維護的做法是:

  1. 建立 Google Group(例如:dev-team@yourcompany.com)
  2. 將群組成員加入/移除群組
  3. IAM 只授權給群組,而不是個人

之後有人加入或離開團隊,只要調整群組成員,IAM Policy 本身通常不必跟著修改。

例如需求是「群組只能查看某專案的 VM 設定」,可先評估該專案上的 roles/compute.viewer,不直接授予 VM 管理員。這是方案推演,尚未建立群組或變更政策。

核對項目需要回答的問題
Principal群組地址與成員是否正確?是否包含外部成員或巢狀群組?
Role/scope工作需要哪些 API 權限?能否縮小到必要資源?
驗證是否用真正的群組成員測試?查看設定不等於能 SSH 登入。
撤回移除成員後,還有其他群組、直接或繼承授權嗎?變更是否已傳播?

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。


四、基本、預定義與自訂角色怎麼選?

Role 是 Permission 的集合。選角色時先列出工作需要執行的動作,再找能涵蓋這些 Permission、但範圍不會大太多的角色。

4.1 基本角色(Basic Roles)

基本角色是 GCP 最早期的角色設計,擁有極廣泛的權限:

角色大致範圍使用時要注意
Viewer多項服務的唯讀存取仍可能超出實際讀取需求
Editor多項服務的讀取與修改權限廣,不適合作為預設值
OwnerEditor 加上 IAM 等管理能力只保留給必要的管理身分

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

為什麼不建議使用基本角色?

基本角色橫跨許多服務。假設需求只是管理 Compute Engine,授予 roles/editor 往往也會帶來 BigQuery、Cloud SQL 或 Cloud Storage 等不相關權限,範圍很難符合最小權限原則。

遇到最小權限題目時,不要只因為 Viewer 是唯讀就直接選它;仍要先確認是否有範圍更貼近需求的預定義角色。

4.2 預定義角色(Predefined Roles)

Google Cloud 為各項服務提供不同範圍的預定義角色,例如:

roles/compute.admin          → Compute Engine 完整管理
roles/compute.instanceAdmin.v1 → 管理 VM 與相關資源,仍需逐項檢查權限
roles/compute.viewer         → Compute Engine 唯讀
roles/storage.admin          → Cloud Storage 完整管理
roles/storage.objectViewer   → 僅讀取 Storage 物件
roles/iam.serviceAccountUser → 把服務帳號附加到資源(actAs)
roles/logging.viewer         → 查看 Cloud Logging

可以先搜尋候選角色,再查看裡面包含哪些 Permission:

# 搜尋特定服務的所有角色
gcloud iam roles list --filter="name:compute"

# 查看角色包含的所有權限
gcloud iam roles describe roles/storage.objectViewer
Console 顯示 roles/storage.objectViewer 角色詳情,說明為查看和編輯物件及其中繼資料,下方列出 8 項指派的權限,包含 storage.objects.get 與 storage.objects.list

角色詳情會列出 roles/storage.objectViewer 包含的 Permission,例如 storage.objects.get 與 storage.objects.list 。權限數量與描述是截圖當時的畫面;請以目前角色的 Permission 清單為準。Object Viewer 不包含物件寫入權,不能只看畫面中的一句說明判定授權範圍。

4.3 自訂角色(Custom Roles)

如果單一或少量預定義角色仍會多給不少權限,可以建立自訂角色,只納入工作真正需要的 Permission:

先寫成需求與權限草稿,不立即建立角色:

需求候選 Permission還要確認
查看單一 VM 設定compute.instances.get實際工具是否需要額外查詢權限
啟動/停止該 VMcompute.instances.start、compute.instances.stop授權範圍、工作流程與測試身分
維護與退場角色擁有者、變更紀錄、移除 Binding 的方法共用角色更新會影響所有使用者

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

草稿還不是可直接部署的最終角色。先確認服務支援的權限、授權層級與實際 API 呼叫,再依變更流程建立;本篇完整實作使用既有預定義角色,不另留自訂角色待清理。

自訂角色只能建在組織或專案層級,不能建在資料夾(要在資料夾裡用,就建在組織層級)。專案層級的自訂角色只能在該專案內授予;要在多個專案共用,就改用 --organization=ORGANIZATION_ID 建在組織層級。

Console 建立自訂角色畫面,標題填 VM Viewer、ID 為 vmViewer,新增權限對話框中篩選 compute.instances.list 並勾選該權限

建立自訂角色時,要自行挑選並維護 Permission。服務日後新增 API 動作時,自訂角色不會自動取得對應權限,因此也要指定擁有者並安排後續檢查。

有適當管理權限時,也能更新既有自訂角色的權限,不必每次重建。基本與預先定義角色的權限由 Google 維護,使用者不能直接修改。更新共用自訂角色前,要先盤點哪些主體與資源已綁定它,避免一次變更影響多個工作負載。

自訂角色常見於以下情況:

  • 需要多個預定義角色的部分權限組合
  • 企業安全政策要求比預定義角色更細的粒度
  • 服務帳號需要最小化權限

代價是後續維護也落在團隊身上。API 或工作流程改變後,要重新確認 Permission 是否仍然足夠、是否有多餘項目。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  subgraph DISCOVER["定義需求與尋找候選角色"]
    direction LR
    BASIC["Owner/Editor/Viewer<br/>權限過廣"] -. "不要當成捷徑" .-> START["拆出必要的<br/>API 動作"]
    START --> SEARCH["搜尋涵蓋權限的<br/>預定義角色"] --> FIT{"單一角色<br/>是否剛好足夠?"}
  end
  FIT -->|"是"| SCOPE["在最低可行的資源層級<br/>綁定角色"]
  FIT -->|"否"| COMBINE{"少量預定義角色的組合<br/>是否仍符合最小權限?"}
  subgraph NARROW["收斂角色"]
    direction LR
    COMBINE -->|"否"| CUSTOM["建立自訂角色<br/>只納入必要權限"]
    CUSTOM --> TEST["先測試並記錄<br/>維護責任"]
  end
  COMBINE -->|"是"| SCOPE
  TEST --> SCOPE
  subgraph OPERATE["綁定與治理"]
    direction LR
    SCOPE --> REVIEW["定期檢視使用情況<br/>移除不再需要的權限"]
  end

先找預定義角色,只有在組合後仍無法精準授權時才建立自訂角色。無論選哪一種,綁定層級也要盡量縮小,並把後續稽核納入流程;基本角色不應成為省事的預設答案。


五、讓程式使用服務帳號

什麼是服務帳號(Service Account)?

服務帳號代表應用程式、VM 或自動化流程。假設一個程式需要讀取 Cloud Storage,若直接使用開發者的個人帳號,人員異動或個人權限變更都可能影響程式。改用專屬服務帳號後,可以獨立管理這個工作負載需要的權限。

服務帳號 vs 使用者帳號

比較項目服務帳號使用者帳號
代表對象應用程式或自動化工作負載真實的人
認證方式附加身分、聯邦身分或服務帳號憑證使用者登入與 MFA
識別格式NAME@PROJECT.iam.gserviceaccount.comuser@example.com

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

建立服務帳號

建立身分、把角色授予它,以及允許別人使用它,是不同操作:

方向PrincipalRole/resource
程式讀取單一 Bucket應用服務帳號在該 Bucket 授予 roles/storage.objectViewer
操作者附加該身分部署者在該 SA 具備 iam.serviceAccounts.actAs,並有部署資源的權限
本機測試時模擬該身分測試操作者在該 SA 具備 Token Creator 或等效短期憑證權限

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

第六節會實際建立與刪除新的 SA;不把 Object Viewer 授在整個專案,也不將建立帳號誤當成已授予資料存取權。

Console 建立服務帳戶表單,名稱填 vm-sa,畫面自動組出電子郵件地址 vm-sa-414@專案 ID.iam.gserviceaccount.com,下方是「權限」與「具備存取權的主體」兩個非必要步驟

輸入名稱後,Console 會產生 NAME@PROJECT.iam.gserviceaccount.com 格式的電子郵件地址。後續在 IAM Binding 裡以服務帳號作為成員時,就會使用這個地址。帳號也可以先建立,再依實際需求另外授權。

服務帳號金鑰為什麼要少用?

工作負載取得服務帳號憑證時,常見做法可以分成兩類:

憑證方式說明維護責任
平台提供的短期憑證資源附加服務帳號,憑證由平台取得與更新不需保存 JSON 金鑰
使用者管理的金鑰下載 JSON 私密金鑰,由使用者保存與輪替外洩與輪替風險較高

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

持有使用者管理的 JSON 私密金鑰,可能讓人以該 SA 身分取得存取權;它不是一般設定檔。此處不提供下載金鑰的操作片段,本篇實作只用短期模擬身分。

  • 金鑰被複製到儲存庫、日誌或其他電腦後,需處理所有副本與憑證撤銷,不能只刪本機檔案。
  • 附加執行身分、短期模擬或聯邦身分也要配置正確的信任與最小權限,不是「免金鑰就沒有風險」。
  • VM 的 cloud-platform scope 不是 IAM 授權;API 存取仍要檢查 SA 的 IAM,VM 使用的 OAuth access scope 也會影響請求。

VM/Cloud Run 可依平台方式指定執行身分;GKE 應核對 Workload Identity Federation for GKE 的配置。部署者的 actAs 權限、執行身分與該身分對資料資源的權限,必須分開管理。

先從不使用長效金鑰的方案開始

程式在 VM/Cloud Run 執行時,先確認平台執行身分;GKE 使用適用的工作負載聯邦身分設定。在 GitHub Actions、地端或其他雲端執行時,再評估 Workload Identity Federation。只有前兩種方式都不適用時,才考慮使用者管理的 JSON 金鑰,並把它當成高敏感憑證保存。

Workload Identity Federation:從外部系統存取 GCP

應用程式若跑在 GitHub Actions、GitLab CI、AWS 或地端環境,可以評估 Workload Identity Federation:

  • 使用外部平台提供的身分,例如 GitHub Actions 的 OIDC Token
  • 經過信任設定後,交換短期有效的 Google Cloud 憑證
  • 不必在外部平台保存服務帳號 JSON 金鑰

設定會比直接下載金鑰多幾個步驟,但後續少了長效金鑰的保存與輪替工作。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  subgraph RUNTIME["確認執行環境"]
    direction LR
    START["工作負載需要<br/>呼叫 GCP API"] --> LOCATION{"程式跑在哪裡?"}
  end
  LOCATION -->|"VM/Cloud Run"| ATTACH["指定平台執行身分<br/>使用短期憑證"]
  LOCATION -->|"GKE"| GKE["核對 Workload Identity<br/>Federation for GKE"]
  GKE --> NOKEY
  LOCATION -->|"GitHub Actions、地端、其他雲"| FED{"外部平台能否提供<br/>OIDC 或受信任身分?"}
  subgraph EXTERNAL["外部工作負載的信任選擇"]
    direction LR
    FED -->|"能"| WIF["設定 Workload Identity Federation<br/>交換短期 Token"]
    FED -->|"暫時不能"| CHANGE{"能否調整平台或流程<br/>支援 Federation?"}
    CHANGE -->|"能"| WIF
    CHANGE -->|"不能"| KEY["最後手段:建立 JSON 金鑰<br/>視同高敏感密碼管理"]
  end
  subgraph CONTROLS["憑證治理結果"]
    direction LR
    ATTACH --> NOKEY["不保存長期金鑰"]
    WIF --> NOKEY
    KEY --> CONTROL["限制權限、集中保管<br/>輪換並監控外洩"]
  end

判斷順序先看執行位置:VM/Cloud Run 使用平台執行身分,GKE 核對 Workload Identity Federation for GKE,外部工作負載評估 Workload Identity Federation。只有兩條路都不可行時才短暫使用 JSON 金鑰,並補上最小權限、輪換與外洩監控。


六、用 gcloud 新增、查詢與移除 Binding

gcloud 常用 IAM 指令

先分清 CLI 操作對象,避免把所有角色都授在專案:

操作對象查詢與變更的用途範圍
Project policy查看/管理專案直接 Binding會影響可繼承的下層資源,不能當成單一 Bucket 授權
Bucket policy管理誰可存取該 Bucket 的物件本篇只對新建測試 Bucket 做變更
Service Account policy管理誰能使用/模擬該 SA不等於 SA 已有資料讀取權

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

接下來從一個可清理的完整實作,驗證這兩個授權方向。

實作準備與保存設定

預留 60–75 分鐘,包含身分核對、授權前後測試、撤回與清理;等待權限及排錯另計。使用 Cloud Shell Bash、gcloud、grep,以一般 Google/Workspace 使用者登入,且沒有預設服務帳號模擬。Workforce 等身分需另核對 principal 格式,本練習不直接套用 user:。

需要可用的獨立練習專案與 Billing,以及啟用 API、建立/刪除服務帳號、管理這個服務帳號的政策、建立/刪除 Bucket 與物件、修改該 Bucket 政策及列出資源的權限。若管理者沒有授權,先停下來確認;不改成 Owner/Editor,也不繞過組織政策。

本次建立一個全新的服務帳號、一個私有 Standard Bucket 與非敏感文字檔。只在這個服務帳號上,讓目前操作者取得 Token Creator;只在這個 Bucket 上,讓該服務帳號取得 Object Viewer。不下載金鑰、不變更專案 IAM、不公開物件。

儲存及 API 操作可能收費。僅對這個新建、可丟棄的測試 Bucket 停用 Soft Delete,不設定版本控管或保留政策;這表示刪除後不能依賴軟刪除復原,正式資料需另規劃保護。

中途停止: 使用撤回本次物件讀取授權與清理本次 IAM 資源。沿用設定檔,不重新產生另一組名稱。舊版的 my-app-sa、my-vm、key.json、自訂角色或專案級 Binding 不會由這份設定檔接管,需依原專案、用途及授權來源另行核對。

先將第一行換成 Project ID。準備步驟只讀取雲端狀態、建立私人本機檔案,不建立雲端資源:

export IAM_LAB_PROJECT='YOUR_PROJECT_ID'
export IAM_LAB_CONTEXT="$HOME/cloudon-iam-labs/iam05-$(date -u +%Y%m%d%H%M%S)-$RANDOM/context.env"
(
  set -eu
  umask 077
  case "$IAM_LAB_PROJECT" in
    ''|'YOUR_PROJECT_ID'|'(unset)') echo '請先填入自己的 Project ID。' >&2; exit 1 ;;
  esac
  command -v grep >/dev/null
  IAM_LAB_ACCOUNT="$(gcloud config get-value account)"
  case "$IAM_LAB_ACCOUNT" in
    *.gserviceaccount.com|''|'(unset)') echo '請使用已授權的一般使用者帳號。' >&2; exit 1 ;;
    *@*) ;;
    *) echo '請先核對目前使用者及 principal 格式。' >&2; exit 1 ;;
  esac
  IAM_LAB_AUTH="$(gcloud config get-value auth/impersonate_service_account)"
  case "$IAM_LAB_AUTH" in
    ''|'(unset)') ;;
    *) echo '目前有預設模擬身分,請先使用沒有預設模擬的核准設定。' >&2; exit 1 ;;
  esac
  gcloud projects describe "$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --format='yaml(projectId,name,projectNumber)'
  IAM_LAB_BILLING="$(gcloud billing projects describe "$IAM_LAB_PROJECT" \
    --account="$IAM_LAB_ACCOUNT" --format='value(billingEnabled)')"
  if [ "$IAM_LAB_BILLING" != 'True' ]; then
    echo 'Billing 尚未啟用,請先確認練習環境。' >&2
    exit 1
  fi
  IAM_LAB_DIR="$(dirname "$IAM_LAB_CONTEXT")"
  IAM_LAB_ID="$(basename "$IAM_LAB_DIR")"
  IAM_LAB_SA="$IAM_LAB_ID@$IAM_LAB_PROJECT.iam.gserviceaccount.com"
  IAM_LAB_MEMBER="user:$IAM_LAB_ACCOUNT"
  IAM_LAB_BUCKET="coa-$IAM_LAB_PROJECT-$IAM_LAB_ID"
  mkdir -p "$IAM_LAB_DIR"
  for field in IAM_LAB_PROJECT IAM_LAB_ACCOUNT IAM_LAB_DIR IAM_LAB_ID \
    IAM_LAB_SA IAM_LAB_MEMBER IAM_LAB_BUCKET; do
    printf 'export %s=%q\n' "$field" "${!field}"
  done > "$IAM_LAB_CONTEXT"
  cat >> "$IAM_LAB_CONTEXT" <<'IAM_LAB_CHECKS'
umask 077
IAM_LAB_AUTH="$(gcloud config get-value auth/impersonate_service_account)"
case "$IAM_LAB_AUTH" in
  ''|'(unset)') ;;
  *) echo '預設模擬身分已變更,請先恢復核准設定。' >&2; return 1 ;;
esac
iam_expect_storage_denied() {
  local permission="$1"
  shift
  case "$permission" in get|create|update|delete) ;; *) return 1 ;; esac
  local log="$IAM_LAB_DIR/denied-$permission.log"
  if "$@" > "$log" 2>&1; then
    cat "$log"
    echo '非預期成功:先檢查繼承或其他授權,不算完成拒絕測試。' >&2
    return 1
  fi
  cat "$log"
  if ! grep -Eq "does not have storage[.]objects[.]$permission access|[Pp]ermission ['\"]storage[.]objects[.]$permission['\"] denied" "$log"; then
    echo '這不是預期的物件權限拒絕;請分辨憑證、API、檔案或連線錯誤。' >&2
    return 1
  fi
  printf '觀察到 storage.objects.%s 被拒絕;仍須核對其他對照結果。\n' "$permission"
}
IAM_LAB_CHECKS
  printf 'CloudOn IAM private read test\n' > "$IAM_LAB_DIR/readme.txt"
  printf 'CloudOn IAM disposable write probe\n' > "$IAM_LAB_DIR/write-probe.txt"
  printf '帳號:%s\n本次設定檔:%s\nBucket:%s\nSA:%s\n' \
    "$IAM_LAB_ACCOUNT" "$IAM_LAB_CONTEXT" "$IAM_LAB_BUCKET" "$IAM_LAB_SA"
)

核對輸出的帳號、Project ID 與完整設定路徑。本次 SA ID 最長 26 字元;Bucket 名稱在標準 Project ID 的長度限制下不超過 63 字元。後續明確使用保存的 --account 與 --project,模擬時再額外帶 --impersonate-service-account,不修改 CLI 預設身分。

設定檔也含預設模擬檢查與拒絕測試函式;每段都會讀入。日誌只供自己查閱,切勿公開登入資料或權杖。函式只接受明確的 Storage 物件權限拒絕,不把任意非零狀態當成預期結果。

換分頁後如何恢復同一次練習?

先列出自己的設定檔,把第二行換成記下的實際路徑。source 會執行內容,僅載入自己在本篇建立的檔案。

ls -lt "$HOME/cloudon-iam-labs"/*/context.env
export IAM_LAB_CONTEXT="$HOME/cloudon-iam-labs/替換成本次目錄/context.env"
(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先指定本次設定檔路徑}"
  printf 'Project=%s\nAccount=%s\nBucket=%s\nSA=%s\n' \
    "$IAM_LAB_PROJECT" "$IAM_LAB_ACCOUNT" "$IAM_LAB_BUCKET" "$IAM_LAB_SA"
)

若家目錄不在了,先從私人筆記與 Console 找回實際資源與授權;不要用新名稱的空查詢判斷舊練習已清理。

建立本次服務帳號與私有資料

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud services enable iam.googleapis.com iamcredentials.googleapis.com storage.googleapis.com \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud iam service-accounts create "$IAM_LAB_ID" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --display-name="CloudOn IAM05 $IAM_LAB_ID"
  gcloud storage buckets create "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --location=us-central1 --default-storage-class=STANDARD \
    --uniform-bucket-level-access --public-access-prevention --soft-delete-duration=0
  gcloud storage cp "$IAM_LAB_DIR/readme.txt" "gs://$IAM_LAB_BUCKET/readme.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud storage buckets describe "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" --raw --format=json
)

核對 SA 與 Bucket 都是本次名稱,Bucket 的 iamConfiguration 中,uniformBucketLevelAccess.enabled 是 true、publicAccessPrevention 是 enforced。資料保護設定應符合本次可丟棄資料的規劃。任何步驟失敗,前面建立成功的資源仍在;直接依原設定清理,不重跑準備。

只允許操作者模擬這個服務帳號

Token Creator 授予在這個 SA 資源,成員是保存的操作者;還沒有授予 SA 讀取物件的權限。--condition=None 指定本次無條件 Binding,後續只移除同一筆。

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud iam service-accounts add-iam-policy-binding "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --member="$IAM_LAB_MEMBER" --role=roles/iam.serviceAccountTokenCreator --condition=None
  gcloud iam service-accounts get-iam-policy "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud auth print-access-token --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" > /dev/null
)

最後一行只檢查是否能取得短期憑證,不將權杖顯示在終端或另外寫成檔案;gcloud 仍會依自身機制管理登入與憑證快取。若尚未生效,等待傳播後重試,不擴大成專案級 Token Creator;如果出現 iam.serviceAccounts.getAccessToken 被拒,這是模擬身分的問題,不能當成物件讀取被拒。角色變更回應不明時,先重新查政策,不假設已回復,也不重跑準備。

授權前先做拒絕基準

先用操作者確認物件存在且內容正確,再用 SA 身分讀取同一個物件。SA 已經能讀時,代表還有其他授權路徑,不要繼續把之後的成功歸功於本次 Binding。

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  IAM_LAB_TEXT="$(gcloud storage cat "gs://$IAM_LAB_BUCKET/readme.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT")"
  [ "$IAM_LAB_TEXT" = 'CloudOn IAM private read test' ] || { echo '基準內容不符。' >&2; exit 1; }
  gcloud auth print-access-token --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" > /dev/null
  iam_expect_storage_denied get gcloud storage cat "gs://$IAM_LAB_BUCKET/readme.txt" \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

必須看見指向 storage.objects.get 的權限拒絕;物件不存在、API 未啟用、憑證錯誤與網路失敗都不是相同證據。保留這份基準,再做下一步。

授予單一 Bucket 的物件唯讀權限

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud storage buckets add-iam-policy-binding "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --member="serviceAccount:$IAM_LAB_SA" --role=roles/storage.objectViewer --condition=None
  gcloud storage buckets get-iam-policy "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

確認 SA 的 Object Viewer 出現在 Bucket 政策,不是在 Project 上授權。Bucket 的 get-iam-policy 顯示直接政策;還要另在上層檢查繼承來源,不能把這份輸出當成所有有效權限。

用同一個 SA 驗證能讀不能寫

本節測試讀取、新增物件、修改中繼資料與刪除四種動作。探針只操作本次可丟棄的資料;這些對照不是完整 API 權限掃描,仍須檢查角色內容與其他授權來源。

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  IAM_LAB_TEXT="$(gcloud storage cat "gs://$IAM_LAB_BUCKET/readme.txt" \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT")"
  [ "$IAM_LAB_TEXT" = 'CloudOn IAM private read test' ] || { echo 'SA 讀取內容不符。' >&2; exit 1; }
  printf 'SA 讀取成功:%s\n' "$IAM_LAB_TEXT"
  iam_expect_storage_denied create gcloud storage cp \
    "$IAM_LAB_DIR/write-probe.txt" "gs://$IAM_LAB_BUCKET/write-probe.txt" \
    --if-generation-match=0 \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  iam_expect_storage_denied update gcloud storage objects update "gs://$IAM_LAB_BUCKET/readme.txt" \
    --update-custom-metadata="iam-lab-probe=$(date -u +%Y%m%d%H%M%S)-$RANDOM" \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  iam_expect_storage_denied delete gcloud storage rm "gs://$IAM_LAB_BUCKET/readme.txt" \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

讀取成功,以及 storage.objects.create、storage.objects.update、storage.objects.delete 分別被拒,才取得本節四項操作的證據。任何一項意外成功,函式會停止該段;先記錄並檢查其他角色/繼承,不刪不屬於本次的政策。建立探針使用 --if-generation-match=0,每次重測前先依下方指引清理本次 write-probe.txt,確保測的是新建;既有物件造成的條件不符,不算權限拒絕。

更新或刪除探針意外成功,如何還原本次資料?

先保留失敗測試與授權來源的紀錄。使用原操作者,把本機非敏感檔案重新上傳到同一個測試物件;不重建 SA 或 Bucket,也不操作其他名稱。這只是還原測試內容,不代表已修正過大的權限。

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  # 只還原本次測試內容
  gcloud storage cp "$IAM_LAB_DIR/readme.txt" "gs://$IAM_LAB_BUCKET/readme.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

權限問題尚未解決時,保留未完成項目並清理,不繼續宣稱唯讀測試通過。

撤回本次物件讀取授權

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud storage buckets remove-iam-policy-binding "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --member="serviceAccount:$IAM_LAB_SA" --role=roles/storage.objectViewer --condition=None
  gcloud storage buckets get-iam-policy "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

只撤回本次無條件綁定,不使用 --all 移除同成員/角色的其他條件授權。接著重新核對:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  IAM_LAB_TEXT="$(gcloud storage cat "gs://$IAM_LAB_BUCKET/readme.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT")"
  [ "$IAM_LAB_TEXT" = 'CloudOn IAM private read test' ] || { echo '物件基準已變更。' >&2; exit 1; }
  gcloud auth print-access-token --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" > /dev/null
  iam_expect_storage_denied get gcloud storage cat "gs://$IAM_LAB_BUCKET/readme.txt" \
    --impersonate-service-account="$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

操作者仍能讀、SA 仍能取得憑證,只有 SA 的物件讀取被拒,才是有意義的撤回對照。IAM 變更有傳播時間,撤回後短時間仍可能成功;等待後重測,若持續成功,檢查直接與繼承權限。移除一筆 Allow 不會抵消其他 Allow,也不能把政策畫面更新等同立即撤銷所有既有憑證。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  A["操作者 → 本次 SA<br/>Token Creator 僅在該 SA"] --> B["基準:操作者能讀<br/>SA 尚不能讀"]
  B --> C["SA → 本次 Bucket<br/>Object Viewer 僅在該 Bucket"]
  C --> D["同一 SA:讀取成功<br/>新增、改 metadata、刪除被拒"]
  D --> E["移除這筆 Bucket Binding"]
  E --> F["等待傳播並重測<br/>操作者能讀、SA 不能讀"]
  F --> G["清理本次物件、Bucket、<br/>SA 上的模擬授權與 SA"]
  B -. "已能讀、憑證或 API 錯誤<br/>不算取得基準" .-> X["停止、記錄缺口<br/>沿用設定檔排錯/清理"]
  D -. "寫入意外成功" .-> X
模擬 SA 的權限與 SA 存取 Bucket 的權限是兩個不同方向。必須使用同一身分、同一物件與正反對照;任何失敗都不能自動算成最小權限驗證成功。

清理本次 IAM 資源

以下都在 Cloud Shell 執行。先依設定核對本次 Bucket、物件與 SA;每段獨立執行,保留 CLI 原生的刪除確認。不要遞迴刪除其他物件、刪整個專案或停用共用 API。

先刪本次 readme.txt:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud storage rm "gs://$IAM_LAB_BUCKET/readme.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)
寫入測試意外成功或上傳回應不明,如何清理 probe?

先查看這個確切名稱,只刪本次測試物件。正常預期拒絕時它不存在,就不用執行刪除;如果是查詢失敗或權限不足,不能當成不存在。

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud storage objects describe "gs://$IAM_LAB_BUCKET/write-probe.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud storage rm "gs://$IAM_LAB_BUCKET/write-probe.txt" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

只刪這個空 Bucket;若出現非本次預期物件,先查原因,不能改用廣泛遞迴刪除:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud storage buckets delete "gs://$IAM_LAB_BUCKET" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

再撤回操作者在本次 SA 上的無條件 Token Creator。若中途未授予或已撤回,先以 get-iam-policy 確認這筆不存在,才跳過移除:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud iam service-accounts get-iam-policy "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud iam service-accounts remove-iam-policy-binding "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --member="$IAM_LAB_MEMBER" --role=roles/iam.serviceAccountTokenCreator --condition=None
)

核對是本次新建 SA 後刪除,不處理專案其他服務帳號:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  gcloud iam service-accounts describe "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
  gcloud iam service-accounts delete "$IAM_LAB_SA" \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT"
)

部分建立失敗時,只有確認是本次名稱不存在/這筆 Binding 已不存在,才跳過該段;PERMISSION_DENIED、查詢失敗與資料保護限制都要留下待辦。SA 不存在時,部分 IAM 政策指令也可能回傳 PERMISSION_DENIED,需先用成功的專案清單確認,不能只看錯誤字串猜測。最後核對區也可在中途執行:

(
  set -eu
  source "${IAM_LAB_CONTEXT:?請先完成準備或恢復設定檔}"
  IAM_LAB_BUCKET_REMAINING="$(gcloud storage buckets list --raw \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --filter="name=$IAM_LAB_BUCKET" --format='value(name)')"
  IAM_LAB_SA_REMAINING="$(gcloud iam service-accounts list \
    --project="$IAM_LAB_PROJECT" --account="$IAM_LAB_ACCOUNT" \
    --filter="email=$IAM_LAB_SA" --format='value(email)')"
  if [ -n "$IAM_LAB_BUCKET_REMAINING$IAM_LAB_SA_REMAINING" ]; then
    printf '仍有本次資源:\n%s\n%s\n' "$IAM_LAB_BUCKET_REMAINING" "$IAM_LAB_SA_REMAINING" >&2
    exit 1
  fi
  echo '本次 Bucket 與服務帳號均未列出;請保留清理輸出。'
)

本篇先明確撤回操作者的 Token Creator,再刪除 SA;SA 的資料授權只放在本次已刪除的 Bucket。刪除 SA 不代表所有歷史 Binding 立刻清除;刪除與復原規則也說明,復原原 SA 與新建同名 SA 是不同操作。若自行新增過其他 Binding、金鑰或工作負載,仍須另行核對,不能只以 SA 清單查無資料代替權限治理。

保留本機設定與測試紀錄。Billing 資料可能延遲,不以即時零元判斷清理成功。

IAM 政策的 JSON 結構

以下是結構示意,不是完整專案政策或可直接覆寫的檔案。IAM Allow Policy 包含角色與成員的 Binding;某個資源支援哪些角色與條件,仍需另行核對:

{
  "bindings": [
    {
      "role": "roles/compute.admin",
      "members": ["user:admin@example.com"]
    },
    {
      "role": "roles/storage.objectViewer",
      "members": [
        "group:dev-team@example.com",
        "serviceAccount:app-sa@my-project.iam.gserviceaccount.com"
      ]
    }
  ],
  "etag": "BwXXXXX"
}

etag 用來偵測並行變更。若用整份 Policy 更新,應先讀取最新政策並保留 etag;衝突時重新讀取與合併,不能拿這份示意 JSON 覆蓋真實政策。本篇以新增/移除精確 Binding 操作,不寫回整份範例政策。


七、IAM 條件(IAM Conditions)入門

IAM Conditions 會在原本的角色 Binding 上加入條件。除了主體、角色與資源,系統還會檢查請求時間、資源名稱或已設定的 Access Level 等屬性,條件成立時這筆 Binding 才會授權。

常見條件類型

條件類型範例用途
請求時間只在指定時段內授權,或讓臨時權限到期
Access Level搭配 Access Context Manager 限制存取情境
資源類型只允許存取特定服務的資源
資源名稱將 Binding 限定在符合名稱條件的資源

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

設定有時效的臨時存取

以下是 CEL/Binding 結構示意,不套用政策;到期時間需依實際工作指定,基本角色不能直接加 Condition:

{
  "version": 3,
  "bindings": [
    {
      "role": "roles/compute.viewer",
      "members": ["user:contractor@example.com"],
      "condition": {
        "title": "temp-access",
        "expression": "request.time < timestamp(\"2027-01-01T00:00:00Z\")"
      }
    }
  ]
}

使用條件還要確認資源類型與可用屬性;Cloud Storage Bucket 的條件 Binding 需先啟用統一存取。帶條件的政策使用 version 3,更新完整政策時也要保留 etag。到期只讓這筆條件授權不再生效,不會刪除 Binding,也不抵消其他無條件或繼承的 Allow。

條件以 CEL(Common Expression Language) 撰寫。上面的 request.time < timestamp(...) 會在每次請求時比較目前時間與到期時間,而不是等到期限一到就刪除 Binding。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  NEED["確認最小角色與<br/>資源範圍"] --> CONDITION["Binding 加上 CEL 條件<br/>例如到期時間或來源"]
  CONDITION --> REQUEST["主體送出<br/>API 請求"]
  REQUEST --> EVAL{"主體、角色、資源匹配<br/>且 CEL 結果為真?"}
  EVAL -->|"否"| OTHER["這筆 Binding 不授權<br/>繼續檢查其他有效政策"]
  EVAL -->|"是"| GRANT["加入有效 Allow<br/>權限集合"]

IAM Condition 是每次請求都會重新判斷的授權條件,不是到期時才執行一次的排程。主體、角色、資源與 CEL 情境都符合時,這筆 Binding 的角色權限才會加入有效 Allow 集合。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart LR
  GRANT["Condition 通過<br/>取得這筆 Allow 權限"] --> DENY{"是否命中 IAM Deny<br/>或其他強制限制?"}
  DENY -->|"是"| BLOCK["拒絕存取"]
  DENY -->|"否"| ALLOW["允許這次操作"]
  ALLOW --> EXPIRE["條件到期後<br/>後續請求不再取得授權"]

Condition 通過只代表 Allow Binding 生效,不會越過 IAM Deny 或 Organization Policy 等強制限制。若沒有命中限制,這次操作才會被允許;到期後同一筆 Binding 會因條件不成立而自動停止授權。


八、把 IAM 納入日常維運

做法原因
從最小權限開始先給完成工作需要的權限,不足時再依實際錯誤補上
優先檢查預定義角色通常比基本角色範圍窄,也比自訂角色省維護成本
以 Google Group 管理團隊人員異動時集中調整群組成員
讓工作負載使用自己的服務帳號人員權限與程式權限可以分開管理
減少使用者管理的 JSON 金鑰降低長效憑證外洩、複製與忘記輪替的風險
保留並檢查稽核記錄發生異常時才能追查誰修改了政策
定期檢視 Policy 與群組成員移除不再需要的權限與離職成員
需要時使用 IAM Conditions為有期限或特定資源的授權縮小生效範圍
公開主體要明確記錄用途allUsers 與 allAuthenticatedUsers 會大幅擴大範圍
限制 Owner 等高權限角色的數量保留可復原的管理管道,同時避免不必要的高權限帳號

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。


九、幾個容易混淆的問題

Q: 我刪除了使用者的角色,他還能存取資源嗎?

有可能,因為相同權限也許還來自其他地方:

  1. 他還有其他角色(直接授予或透過群組繼承)
  2. 上層(Folder/Organization)還有政策授予他權限

Allow Policy 會合併所有適用的授權。要撤銷存取,不能只看目前這一層,還要確認群組成員資格、上層政策與其他角色。

從實際 Permission 往回查

如果移除一筆 Binding 後仍能操作,先確認該 API 需要哪個 Permission,再找出這個 Principal 從直接綁定、Google Group、上層政策或其他角色取得 Permission 的所有來源。這會比只盯著某個角色名稱更容易找到原因。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  subgraph DEFINE["定義排查目標"]
    direction LR
    START["角色已移除<br/>但操作仍然成功"] --> INVENTORY["以主體、權限與目標資源<br/>盤點所有授權來源"]
  end
  subgraph SOURCES["有效權限的可能來源"]
    direction LR
    DIRECT["資源或 Project<br/>直接綁定"]
    GROUP["Google Group<br/>成員資格"]
    PARENT["Folder/Organization<br/>向下繼承"]
    OTHER["其他角色或<br/>另一個主體身分"]
  end
  INVENTORY --> DIRECT
  INVENTORY --> GROUP
  INVENTORY --> PARENT
  INVENTORY --> OTHER
  DIRECT --> UNION["合併成有效 Allow<br/>權限聯集"]
  GROUP --> UNION
  PARENT --> UNION
  OTHER --> UNION
  subgraph RESOLVE["修正與驗證"]
    direction LR
    UNION --> SOURCE["找出授予該 API 權限的<br/>每一筆 Binding"]
    SOURCE --> REMOVE["移除不需要的來源<br/>再檢查群組與上層政策"]
    REMOVE --> RETEST{"再次測試是否<br/>仍被允許?"}
  end
  RETEST -->|"是"| REPEAT["回到盤點步驟<br/>繼續追查遺漏來源"]
  RETEST -->|"否"| DONE["確認存取已撤銷<br/>保留稽核紀錄"]

IAM Allow 權限會做聯集,因此撤掉眼前的一筆綁定,不代表相同權限沒有從群組、上層政策或另一個角色流入。排查時要以「目標權限」反查全部來源,直到重新測試確實被拒絕為止。

Q: 服務帳號本身也需要 IAM 權限嗎?

是的。服務帳號同時有兩個面向:

  1. 作為主體(Principal):服務帳號可以被授予資源存取權(例如:讓這個 SA 能讀 Cloud Storage)
  2. 作為資源(Resource):誰可以把這個服務帳號附加到資源上(actAs),本身也是一種 IAM 授權;要模擬(impersonate)它則需要另一個角色 Service Account Token Creator

例如部署者要把指定 SA 附加到 VM/Cloud Run,需核對該 SA 的 roles/iam.serviceAccountUser 或等效 actAs 權限,以及部署資源的權限。這個角色不提供 gcloud --impersonate-service-account 所需的 access token 簽發權;本篇模擬測試使用的是該 SA 上的 Token Creator。

Q: 如何知道某個角色包含哪些權限?

# 查看角色的所有權限
gcloud iam roles describe roles/compute.instanceAdmin.v1

# 列出可授予在指定資源上的角色
gcloud iam roles describe roles/iam.serviceAccountTokenCreator

十、IAM 管權限,Organization Policy 管限制

IAM 回答「誰可以呼叫哪些 API」;Organization Policy 則為資源設定大家都必須遵守的限制。兩者會一起影響最後結果,有 IAM 權限不代表可以繞過組織限制。

IAM vs Organization Policy

面向IAMOrganization Policy
控制對象誰(身分)什麼(行為/資源)
範例「Alice 可以建立 VM」「任何人都不能在海外區域建立 VM」
繼承方向Allow 向下繼承,最終存取還需評估其他限制依約束類型與政策的繼承/合併設定判斷,不能只看下層一份政策
管理者Project Owner / AdminOrganization Policy Administrator

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

用 Policy 檔設定限制

以下以禁止建立服務帳號金鑰說明 YAML 結構,不建立檔案或套用政策:

name: organizations/ORGANIZATION_ID/policies/iam.disableServiceAccountKeyCreation
spec:
  rules:
    - enforce: true

這是組織管理的 YAML 示意,本篇不執行套用。管理者需核對約束版本、支援範圍、目標層級及現有政策,經測試與審查再變更;不能把組織級變更當作個人 Lab 的前置操作。

布林約束與清單約束的 YAML 不同,繼承/合併行為也需依政策規則判斷。設定限制不會自動撤銷所有既有資源或憑證,應另看該約束的生效行為。

常見約束

約束效果
gcp.resourceLocations限制資源可部署的區域
compute.vmExternalIpAccess限制哪些 VM 可使用外部 IPv4(全部拒絕即禁止)
iam.disableServiceAccountKeyCreation禁止建立服務帳號金鑰
storage.uniformBucketLevelAccess強制 Bucket 使用統一存取控制
compute.requireShieldedVm強制使用 Shielded VM

表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。

如果需求是「整個組織建立 VM 時都不得配置外部 IP」,控制點在 Organization Policy。IAM 可以限制誰有建立 VM 的權限,防火牆可以控制封包,但兩者都不是資源組態的統一限制。

查看流程與節點文字

箭頭表示流程方向;方括號與引號內是節點名稱,可搭配下方圖解閱讀。

flowchart TB
  NEED["先把需求改寫成<br/>要控制的問題"] --> TARGET{"真正要控制的是什麼?"}
  TARGET -->|"誰能呼叫哪些 API"| ALLOW["IAM Allow Policy<br/>為主體授予最小角色"]
  TARGET -->|"即使已有 Allow 也要禁止<br/>特定主體執行敏感動作"| DENY["IAM Deny Policy<br/>建立明確拒絕護欄"]
  TARGET -->|"所有人建立資源時<br/>都必須遵守的組態限制"| ORG["Organization Policy<br/>設定後預設向下繼承"]
  TARGET -->|"哪些網路封包<br/>可以進出工作負載"| FW["VPC Firewall Policy/Rule<br/>控制網路流量"]
  ALLOW --> VERIFY["在正確的資源層級套用<br/>並用實際請求驗證"]
  DENY --> VERIFY
  ORG --> VERIFY
  FW --> VERIFY

「誰能建立 VM」是 IAM;「整個組織只能在哪些區域建立 VM」是 Organization Policy;「即使有 Allow 也不能做某個敏感動作」才是 IAM Deny。若題目談的是封包來源、連接埠與方向,則應回到防火牆。


十一、用四種題型檢查觀念

先自己作答,再展開參考答案。這是觀念檢核,不是實作評分或認證通過保證。

類型 1:角色選擇

題目:團隊只能列出與查看 VM 設定,不需要 SSH 登入、建立或刪除,應先評估哪個角色與範圍?

對照答案:查看設定與 SSH 是不同需求

先評估必要專案/資源範圍的 roles/compute.viewer,並查看目前 Permission 清單。Editor/Compute Admin 範圍過大;Viewer 不代表能 SSH,登入與連線通道需另評估。

類型 2:服務帳號設定

題目:Cloud Run 只需讀取一個 Bucket,誰是資料權限的 Principal?部署者與執行身分的權限要如何分開?

對照答案:在資料資源上授予執行身分

使用專用執行 SA,在該 Bucket 授予 Object Viewer 並讓 Cloud Run 使用它;部署者需具備部署及使用該 SA 的權限。不要把資料權限授給開發者個人帳號後當成服務已能存取,也不需把 JSON 金鑰寫進程式碼。

類型 3:政策繼承

題目:Alice 在 Organization 有 Viewer,Project A 有 Editor,Project B 沒有直接 Binding。只看 Allow 繼承,她在 B 得到什麼?能據此斷言所有請求成功嗎?

對照答案:分開看 Allow 與最終存取結果

她在 B 仍有上層繼承的 Viewer;刪除 B 的直接 Binding 不能抵消它。最終請求仍需核對群組/條件、Deny、PAB 與相關組織限制,不能只憑角色名稱判定所有 API 都會通過。

類型 4:最小權限

題目:自動化流程只能建立新物件,不需讀取、刪除或覆寫既有物件,應先評估哪個角色?選定角色後還要確認什麼?

對照答案:最小角色與實際工具都要核對

先評估目標 Bucket 上的 roles/storage.objectCreator,並確認沒有其他繼承權限。覆寫通常還需要刪除權限;實際工具可能額外做查詢或列舉,例如 gcloud storage cp 的目的地解析,因此需依 API/工具需求測試,不能為了排錯直接擴大成 Storage Admin。


十二、把整篇串起來

處理 IAM 問題時,可以依序問三件事:

  1. 主體(Principal):誰(使用者、服務帳號、群組)
  2. 角色(Role):能做什麼(基本/預定義/自訂)
  3. 資源(Resource):在哪個層級(Organization/Folder/Project/Resource)

實際授權時,再守住幾個原則:

  • 先找符合需求的預定義角色,避免直接使用範圍過大的基本角色
  • 工作負載使用服務帳號,和人員帳號分開管理
  • 能用附加身分或 Workload Identity Federation 時,就不要保存 JSON 金鑰
  • 團隊成員透過 Google Group 授權,人員異動比較容易管理
  • 權限授予後仍要定期檢查來源、使用情況與退場條件

系列文章連結


延伸閱讀

完成閱讀後,先用第六節的新建私有資源取得授權前後與撤回的證據。若基準、拒絕測試或清理尚未完成,保留待辦,再決定要補 IAM、繼承或工具權限哪一段。

GCP 入門基礎 — 第 5 篇/共 5 篇 查看系列全覽 →

讀完後,留一個學習紀錄

試著用自己的話說出一個重點,以及你會如何運用。實作文章請先核對結果與清理資源,再標記為讀完。

文章進度以你確認讀完的標記為準,可隨時取消。舊版造訪紀錄保留,但不計入讀完;這份自評不代表已掌握內容。

留言討論

文章目錄與系列

徽章解鎖!