RBAC:誰能對叢集做什麼

· tech

#kubernetes#security

📑 目錄

前面十篇都在讓東西「跑起來、連得到」。這篇換一個維度:誰有權對叢集下命令? 一個 kubectl delete 打進 API Server,它憑什麼知道你是誰、又憑什麼准你刪?這是 RBAC(Role-Based Access Control)的地盤,也是 CKA 佔比最高的 Cluster Architecture 領域裡最該吃透的一塊。起手式是先分清兩件常被混為一談的事:認證授權

kubectl / Pod 發一個請求 ① 認證 authn 你是誰? 憑證 · token · OIDC ② 授權 authz 你能做這動作嗎? ← 這就是 RBAC 執行 API Server 驗不出身分 → 401 沒權限 → 403 兩道門各管一件事:先確認「你是誰」,再判斷「你能不能」
每個請求進 API Server 要過兩道門:①認證確認你是誰(失敗 401)、②授權(RBAC)判斷你能不能做這個動作(失敗 403)。RBAC 只管第二道——它假設你的身分已經驗好了

認證 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)身上。
主體 Subjects(誰) User: aidan Group: dev ServiceAccount:ci-bot(叢集內物件) RoleBinding 把主體黏到角色 (膠水) Role: pod-reader 一組權限(不屬於任何人) apiGroups: "" resources: pods verbs: get · list · watch 主體本身零權限、Role 也只是躺著的權限——RoleBinding 把兩者黏起來,權限才生效
RBAC 的核心是這條鏈:主體(User / Group / ServiceAccount)靠 RoleBinding 綁上一個 Role。Role 定義「能對什麼資源做什麼動作」(這裡是對 pods 的 get/list/watch)。少了 Binding,Role 只是躺著沒人擁有的權限;主體在被綁之前也一無所有

一條規則(rule)由三段組成:apiGroups(資源屬於哪個 API 群組)+ resources(pods、deployments…)+ verbs(get、list、watch、create、update、delete…)。而且 RBAC 跟 NetworkPolicy 是同一種脾氣:只有允許、沒有拒絕,規則相加取聯集,預設一律不准。 你只能一條條把權限「加上去」,加到剛好夠用為止。

namespaced 還是 cluster-wide:兩個維度別搞混

Role 那對有 namespaced / cluster 之分,Binding 那對也有——這兩個維度是獨立的,組合起來才決定「權限在哪裡生效」:

權限定義 綁定 生效範圍 Rolenamespaced RoleBinding 只在單一 namespace ClusterRolecluster-wide ClusterRoleBinding 全叢集(所有 ns +node 等叢集級資源) ClusterRole借用定義 RoleBinding在 ns-A 只在 ns-A 生效(重用叢集級定義) 第三列最好用:寫一次通用的 ClusterRole,靠 RoleBinding 把它限縮到各個 namespace 重複用
「權限定義」與「綁定」是兩個獨立維度。前兩列是直覺組合;第三列(ClusterRole + RoleBinding)是實務最愛的技巧——把權限定義寫成一份通用 ClusterRole,再用 RoleBinding 限縮到指定 namespace,不必每個 namespace 重寫一份 Role

還有個重點: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、而不是整座叢集。