RBAC:誰能對叢集做什麼
· tech
📑 目錄
前面十篇都在讓東西「跑起來、連得到」。這篇換一個維度:誰有權對叢集下命令? 一個 kubectl delete 打進 API Server,它憑什麼知道你是誰、又憑什麼准你刪?這是 RBAC(Role-Based Access Control)的地盤,也是 CKA 佔比最高的 Cluster Architecture 領域裡最該吃透的一塊。起手式是先分清兩件常被混為一談的事:認證與授權。
認證 vs 授權:K8s 只管第二道門
這兩個詞的分工是理解 RBAC 的地基:認證(authn)問「你是誰」,授權(authz)問「你能做什麼」。 RBAC 純粹是後者——它從不驗證身分,只在「身分已知」的前提下判斷這個人能不能執行某動作。
有件事很反直覺:K8s 裡根本沒有「使用者(User)」這種物件。 你不會 kubectl create user。人類身分是由外部認證機制決定的——客戶端憑證、bearer token、雲商 IAM、OIDC……API Server 驗完之後,只拿到一個「使用者名 + 所屬群組」的字串,RBAC 就拿這個字串去比對權限。唯一由 K8s 自己管理的身分,是給程式用的 ServiceAccount(後面會講)。記住:User / Group 是外面來的,ServiceAccount 才是叢集內的物件。
RBAC 的積木:Role 是權限、Binding 是膠水
RBAC 只有四種物件,兩兩成對,想通「角色」與「綁定」的分工就通了:
- Role / ClusterRole:一組權限——「能對哪些資源、做哪些動作」。它只是一張權限清單,本身不屬於任何人。
- RoleBinding / ClusterRoleBinding:一條綁定——把某個 Role 黏到某個主體(subject)身上。
一條規則(rule)由三段組成:apiGroups(資源屬於哪個 API 群組)+ resources(pods、deployments…)+ verbs(get、list、watch、create、update、delete…)。而且 RBAC 跟 NetworkPolicy 是同一種脾氣:只有允許、沒有拒絕,規則相加取聯集,預設一律不准。 你只能一條條把權限「加上去」,加到剛好夠用為止。
namespaced 還是 cluster-wide:兩個維度別搞混
Role 那對有 namespaced / cluster 之分,Binding 那對也有——這兩個維度是獨立的,組合起來才決定「權限在哪裡生效」:
還有個重點:node、PersistentVolume、namespace 這類「不屬於任何 namespace」的叢集級資源,只能用 ClusterRole 授權——Role 管不到它們。想給某人「看所有 node」的權限,一定是 ClusterRole + ClusterRoleBinding。
ServiceAccount:給 workload 的身分
人用憑證登入,那跑在叢集裡的程式(一個 CI bot、一個要讀 K8s API 的 controller)用什麼身分?答案是 ServiceAccount——專門給 workload 的身分。每個 Pod 都以某個 SA 的身分執行(沒指定就用該 namespace 的 default SA),API Server 會把這個 SA 的 token 掛進 Pod,程式拿它去呼叫 API 時,RBAC 就用這個 SA 去比對權限。
所以要讓一個 Pod 能列出 Pod,標準三步:建一個 ServiceAccount → 建一個 Role(或 ClusterRole)→ 用 RoleBinding 把兩者綁起來,然後讓 Pod 指定用那個 SA。這裡最該守住的原則是最小權限:那個 default SA 預設幾乎什麼都不能做,是刻意的——別為了省事給 workload 一個 cluster-admin,那等於把整座叢集的鑰匙插在門上。
那「標準三步」落成 YAML,剛好就是綁定鏈那張圖的三個積木:
apiVersion: v1
kind: ServiceAccount # ① 主體:給 workload 的身分
metadata: { name: ci-bot, namespace: ci }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role # ② 角色:一組權限(namespaced,不屬於任何人)
metadata: { name: pod-reader, namespace: ci }
rules:
- apiGroups: [ "" ] # "" = 核心 API 群組(pods 就在這)
resources: [ "pods" ]
verbs: [ "get", "list", "watch" ] # 只給讀,不給刪
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding # ③ 綁定:把 Role 黏到主體身上
metadata: { name: ci-bot-can-read-pods, namespace: ci }
subjects:
- { kind: ServiceAccount, name: ci-bot, namespace: ci }
roleRef:
{ kind: Role, name: pod-reader, apiGroup: rbac.authorization.k8s.io }
roleRef 指向「給什麼權限」、subjects 指向「給誰」——少了這條 RoleBinding,pod-reader 只是躺著沒人擁有的權限,ci-bot 也一無所有。 Pod 那邊再寫 serviceAccountName: ci-bot,它跑起來呼叫 API 時,就只有讀 pod 的權限、別的一律 403。
想確認到底有沒有權限,不用猜,kubectl auth can-i 直接問:
kubectl auth can-i delete pods # 我自己能不能刪 pod
kubectl auth can-i list nodes --as=system:serviceaccount:ci:ci-bot # 冒充某 SA 來測
反思
認證與授權分不清,RBAC 永遠學不透
我看過太多人(包括早年的自己)把「連得進叢集」跟「能操作叢集」當成同一件事。它們是兩道獨立的門:憑證只證明「你是 aidan」,至於 aidan 能不能刪 Production 的 Deployment,是 RBAC 另外一關的事。想通這條界線後,很多怪現象瞬間有解——401 是身分沒驗過(認證問題),403 是身分沒問題但沒權限(授權問題),兩者的排查方向天差地遠。我現在遇到權限報錯,第一件事永遠是先看它是 401 還是 403,直接就分流到對的那道門去查,不再瞎試。
「Role 不屬於任何人」是最關鍵、也最反直覺的一點
RBAC 剛學會覺得零件很多,但真正的鑰匙是理解Role 只是一張漂在空中的權限清單,它不主動屬於誰。權限要落到人或程式身上,一定得經過一條 Binding 把它黏過去。這個「定義」與「授予」分離的設計乍看囉嗦,好處卻很大:同一份 pod-reader 可以綁給十個人、十個 SA,權限定義只維護一份。這跟我在 NetworkPolicy 看到的「白名單、預設拒絕、只加不減」是同一種安全哲學——權限系統的預設值必須是『不准』,一切開放都得是明確、可追溯的一筆綁定。
最小權限不是潔癖,是把爆炸半徑先關進籠子
給 workload 授權時,我的紀律是從零開始加,而不是從 admin 往下砍。這件事在忙的時候特別容易妥協——「先給個大權限讓它跑起來,之後再收」,而「之後」永遠不會來。但 ServiceAccount 的權限就是一顆 Pod 被打下來後,攻擊者立刻繼承的能力:給了 cluster-admin,一顆 Pod 淪陷等於整個叢集淪陷。這正是 我在 SRE 那幾篇反覆講的「先想爆炸半徑」在權限層的版本——最小權限的價值,不在平時省了什麼,而在出事那一刻,它把災難鎖在一個 namespace、而不是整座叢集。