GCP IAM 入門:看懂身分、角色與權限繼承
VM 和 VPC 建好後,很快就會碰到權限問題:誰可以查看 VM?應用程式要怎麼讀取 Cloud Storage?成員離開團隊後,又該從哪裡移除他的存取權?
Cloud IAM(Identity and Access Management)處理的就是這些問題。它把身分、角色和資源串在一起,決定某個人或工作負載能對哪些資源執行哪些動作。
這篇先拆解 IAM 的資源層級與政策繼承,再比較角色與服務帳號。完整實作集中在第六節:使用一個私有 Bucket 和新的服務帳號,取得授權前、授權後、撤回後的對照,不下載長效金鑰。
選擇閱讀路線與實作範圍
- 觀念閱讀:先讀第一到五節,再看 Conditions、政策限制及文末四題;不需要建立資源。7 天計畫 Day 4 的只讀觀察/方案推演也走這條路線。
- 完整實作:第六節準備約 60–75 分鐘,包含短期模擬、正反驗證、撤回與清理;權限等待與排錯另計。
- 提前中止:沿用保存的設定,跳到撤回物件讀取授權或清理本次 IAM 資源,不為排錯擴大專案角色。
教材核對:2026-10-22。前半段的角色、JSON 與組織政策是觀念示意;需要變更雲端資源的指令集中在完整實作中。

一筆 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 管理
成員人數一多,逐一綁定個人帳號很容易漏改。比較好維護的做法是:
- 建立 Google Group(例如:
dev-team@yourcompany.com) - 將群組成員加入/移除群組
- IAM 只授權給群組,而不是個人
之後有人加入或離開團隊,只要調整群組成員,IAM Policy 本身通常不必跟著修改。
例如需求是「群組只能查看某專案的 VM 設定」,可先評估該專案上的 roles/compute.viewer,不直接授予 VM 管理員。這是方案推演,尚未建立群組或變更政策。
| 核對項目 | 需要回答的問題 |
|---|---|
| Principal | 群組地址與成員是否正確?是否包含外部成員或巢狀群組? |
| Role/scope | 工作需要哪些 API 權限?能否縮小到必要資源? |
| 驗證 | 是否用真正的群組成員測試?查看設定不等於能 SSH 登入。 |
| 撤回 | 移除成員後,還有其他群組、直接或繼承授權嗎?變更是否已傳播? |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
四、基本、預定義與自訂角色怎麼選?
Role 是 Permission 的集合。選角色時先列出工作需要執行的動作,再找能涵蓋這些 Permission、但範圍不會大太多的角色。
4.1 基本角色(Basic Roles)
基本角色是 GCP 最早期的角色設計,擁有極廣泛的權限:
| 角色 | 大致範圍 | 使用時要注意 |
|---|---|---|
| Viewer | 多項服務的唯讀存取 | 仍可能超出實際讀取需求 |
| Editor | 多項服務的讀取與修改 | 權限廣,不適合作為預設值 |
| Owner | Editor 加上 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

角色詳情會列出 roles/storage.objectViewer 包含的 Permission,例如
storage.objects.get 與 storage.objects.list
。權限數量與描述是截圖當時的畫面;請以目前角色的 Permission 清單為準。Object Viewer
不包含物件寫入權,不能只看畫面中的一句說明判定授權範圍。
4.3 自訂角色(Custom Roles)
如果單一或少量預定義角色仍會多給不少權限,可以建立自訂角色,只納入工作真正需要的 Permission:
先寫成需求與權限草稿,不立即建立角色:
| 需求 | 候選 Permission | 還要確認 |
|---|---|---|
| 查看單一 VM 設定 | compute.instances.get | 實際工具是否需要額外查詢權限 |
| 啟動/停止該 VM | compute.instances.start、compute.instances.stop | 授權範圍、工作流程與測試身分 |
| 維護與退場 | 角色擁有者、變更紀錄、移除 Binding 的方法 | 共用角色更新會影響所有使用者 |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
草稿還不是可直接部署的最終角色。先確認服務支援的權限、授權層級與實際 API 呼叫,再依變更流程建立;本篇完整實作使用既有預定義角色,不另留自訂角色待清理。
自訂角色只能建在組織或專案層級,不能建在資料夾(要在資料夾裡用,就建在組織層級)。專案層級的自訂角色只能在該專案內授予;要在多個專案共用,就改用 --organization=ORGANIZATION_ID 建在組織層級。

建立自訂角色時,要自行挑選並維護 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.com | user@example.com |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
建立服務帳號
建立身分、把角色授予它,以及允許別人使用它,是不同操作:
| 方向 | Principal | Role/resource |
|---|---|---|
| 程式讀取單一 Bucket | 應用服務帳號 | 在該 Bucket 授予 roles/storage.objectViewer |
| 操作者附加該身分 | 部署者 | 在該 SA 具備 iam.serviceAccounts.actAs,並有部署資源的權限 |
| 本機測試時模擬該身分 | 測試操作者 | 在該 SA 具備 Token Creator 或等效短期憑證權限 |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
第六節會實際建立與刪除新的 SA;不把 Object Viewer 授在整個專案,也不將建立帳號誤當成已授予資料存取權。

輸入名稱後,Console 會產生
NAME@PROJECT.iam.gserviceaccount.com 格式的電子郵件地址。後續在 IAM Binding
裡以服務帳號作為成員時,就會使用這個地址。帳號也可以先建立,再依實際需求另外授權。
服務帳號金鑰為什麼要少用?
工作負載取得服務帳號憑證時,常見做法可以分成兩類:
| 憑證方式 | 說明 | 維護責任 |
|---|---|---|
| 平台提供的短期憑證 | 資源附加服務帳號,憑證由平台取得與更新 | 不需保存 JSON 金鑰 |
| 使用者管理的金鑰 | 下載 JSON 私密金鑰,由使用者保存與輪替 | 外洩與輪替風險較高 |
表格超出欄寬時,可左右捲動;鍵盤可聚焦表格後用左右方向鍵查看。
持有使用者管理的 JSON 私密金鑰,可能讓人以該 SA 身分取得存取權;它不是一般設定檔。此處不提供下載金鑰的操作片段,本篇實作只用短期模擬身分。
- 金鑰被複製到儲存庫、日誌或其他電腦後,需處理所有副本與憑證撤銷,不能只刪本機檔案。
- 附加執行身分、短期模擬或聯邦身分也要配置正確的信任與最小權限,不是「免金鑰就沒有風險」。
- VM 的
cloud-platformscope 不是 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
清理本次 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: 我刪除了使用者的角色,他還能存取資源嗎?
有可能,因為相同權限也許還來自其他地方:
- 他還有其他角色(直接授予或透過群組繼承)
- 上層(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 權限嗎?
是的。服務帳號同時有兩個面向:
- 作為主體(Principal):服務帳號可以被授予資源存取權(例如:讓這個 SA 能讀 Cloud Storage)
- 作為資源(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
| 面向 | IAM | Organization Policy |
|---|---|---|
| 控制對象 | 誰(身分) | 什麼(行為/資源) |
| 範例 | 「Alice 可以建立 VM」 | 「任何人都不能在海外區域建立 VM」 |
| 繼承方向 | Allow 向下繼承,最終存取還需評估其他限制 | 依約束類型與政策的繼承/合併設定判斷,不能只看下層一份政策 |
| 管理者 | Project Owner / Admin | Organization 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 問題時,可以依序問三件事:
- 主體(Principal):誰(使用者、服務帳號、群組)
- 角色(Role):能做什麼(基本/預定義/自訂)
- 資源(Resource):在哪個層級(Organization/Folder/Project/Resource)
實際授權時,再守住幾個原則:
- 先找符合需求的預定義角色,避免直接使用範圍過大的基本角色
- 工作負載使用服務帳號,和人員帳號分開管理
- 能用附加身分或 Workload Identity Federation 時,就不要保存 JSON 金鑰
- 團隊成員透過 Google Group 授權,人員異動比較容易管理
- 權限授予後仍要定期檢查來源、使用情況與退場條件
系列文章連結
延伸閱讀
- GCP IAM 官方文件
- IAM 角色說明
- 服務帳號使用建議
- Workload Identity Federation
- Cloud Audit Logs
- Service Account User 與 Token Creator
- 使用短期模擬身分
- IAM 變更傳播
- Storage 角色與權限
- gcloud storage 指令所需權限
- Bucket IAM 的直接與繼承政策
- IAM Conditions
完成閱讀後,先用第六節的新建私有資源取得授權前後與撤回的證據。若基準、拒絕測試或清理尚未完成,保留待辦,再決定要補 IAM、繼承或工具權限哪一段。
讀完後,留一個學習紀錄
試著用自己的話說出一個重點,以及你會如何運用。實作文章請先核對結果與清理資源,再標記為讀完。
文章進度以你確認讀完的標記為準,可隨時取消。舊版造訪紀錄保留,但不計入讀完;這份自評不代表已掌握內容。