🧾 RBAC: Role, ClusterRole và RoleBinding

Hôm trước tôi dùng cluster-admin cho mọi việc nên chẳng bao giờ đụng RBAC. Đến lúc đề bắt "cấp quyền cho user này, chỉ trong namespace này, chỉ với đúng mấy verb này", tôi mới thấy mình mất điểm ở phần dễ nhất của domain Security. Bài này là cách tôi làm lại từ đầu: viết Role, gắn binding, rồi kiểm chứng bằng kubectl auth can-i cho đến khi nó trả về đúng chữ yes và no mà tôi mong đợi.

Mục tiêu bài học

Sau khoảng 35 phút, bạn sẽ:

  • Phân biệt được Role và ClusterRole, RoleBinding và ClusterRoleBinding, và biết khi nào đề thi bắt dùng cái nào.
  • Viết được rules với đúng ba trường apiGroups, resources, verbs cho các resource quen thuộc (pods, deployments, pods/log).
  • Tạo binding đúng phạm vi namespace hoặc cluster.
  • Kiểm chứng bằng kubectl auth can-i, kể cả trường hợp phải trả về no.
  • Đọc được thông báo Forbidden để biết mình thiếu verb nào, resource nào.

[!NOTE] Trong RBAC, User chỉ là một chuỗi tên. Kubernetes không tạo user và không xác thực user. Phần xác thực bằng certificate và kubeconfig sẽ nằm ở bài 7.4, còn bài này chỉ lo phần phân quyền.

Ôn lại nhanh

Ở module Troubleshooting, chúng ta đã quen với phản xạ "đừng đoán, hãy kiểm tra": kubectl describe để xem event, đọc log để tìm nguyên nhân thật. RBAC dùng đúng tinh thần đó, chỉ đổi công cụ: kubectl auth can-i là describe của quyền hạn.

Bốn object cần nhớ

Object Phạm vi Dùng để
Role Namespace Định nghĩa tập quyền trong một namespace
ClusterRole Cluster Định nghĩa tập quyền dùng lại được, hoặc cho resource cấp cluster (nodes, PV)
RoleBinding Namespace Gắn Role hoặc ClusterRole vào subject, chỉ có hiệu lực trong namespace của nó
ClusterRoleBinding Cluster Gắn ClusterRole vào subject, có hiệu lực toàn cluster

Câu thần chú tôi tự nhắc: Role và RoleBinding thì luôn có metadata.namespace. ClusterRole và ClusterRoleBinding thì không bao giờ có.

Ví dụ mẫu từng bước

Đề bài kiểu CKA: "Trong namespace team-a, cho user dev-an quyền xem pods, xem log của pods và list deployments. Không được xoá bất cứ thứ gì."

Bước 1: Đọc đề thành ba trường. Verbs là get, list, watch (xem), get (log). Resources là pods, pods/log, deployments. Phạm vi là namespace team-a.

Bước 2: Chuẩn bị namespace và chắc chắn mình đang ở đúng context.

kubectl create namespace team-a
kubectl config set-context --current --namespace=team-a

kubectl config view --minify -o jsonpath='{..namespace}'

Bước 3: Sinh skeleton bằng dry-run, đừng gõ tay từ đầu.

kubectl create role pod-reader -n team-a \
  --verb=get,list,watch --resource=pods \
  --dry-run=client -o yaml > role.yaml

Bước 4: Sửa YAML cho đủ ý đề. Dry-run chỉ giúp phần khung, apiGroups vẫn phải tự kiểm tra.

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-reader
  namespace: team-a
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]
- apiGroups: ["apps"]
  resources: ["deployments"]
  verbs: ["get", "list"]
kubectl apply -f role.yaml
kubectl explain role.rules

Bước 5: Tạo RoleBinding. Nếu đề không bắt viết YAML, dùng lệnh imperative cho nhanh:

kubectl create rolebinding dev-reader-binding -n team-a \
  --role=dev-reader --user=dev-an

Bản YAML tương đương, để bạn nhìn rõ cấu trúc:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: dev-reader-binding
  namespace: team-a
subjects:
- kind: User
  name: dev-an
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: dev-reader
  apiGroup: rbac.authorization.k8s.io

Bước 6: Kiểm chứng, cả chiều được và chiều không được.

kubectl auth can-i get pods -n team-a --as=dev-an              # yes
kubectl auth can-i get pods/log -n team-a --as=dev-an          # yes
kubectl auth can-i list deployments -n team-a --as=dev-an      # yes
kubectl auth can-i delete pods -n team-a --as=dev-an           # no
kubectl auth can-i get secrets -n team-a --as=dev-an           # no
kubectl auth can-i list pods -n default --as=dev-an            # no
kubectl auth can-i --list -n team-a --as=dev-an

Lệnh cuối in ra bảng toàn bộ quyền của dev-an trong team-a. Đây là thứ tôi luôn chạy trước khi kết luận "đã xong bài".

[!TIP] kubectl auth can-i trả exit code 0 khi yes và 1 khi no, nên có thể dùng trong script: kubectl auth can-i delete pods -n team-a --as=dev-an && echo SAI || echo OK.

Thực hành có hướng dẫn

Làm lần lượt, mỗi bài tự bấm kiểm chứng trước khi mở đáp án. Chuẩn bị trước:

kubectl create namespace team-a
kubectl create namespace team-b

Bài 1: Role cơ bản (5 phút)

Cấp cho user dev-an quyền get, list, watch pods trong namespace team-a. Không cấp gì ở namespace khác.

Yêu cầu kiểm chứng: list pods trong team-a phải yes, trong team-b phải no.

Xem đáp án
kubectl create role pod-reader -n team-a \
  --verb=get,list,watch --resource=pods
kubectl create rolebinding pod-reader-binding -n team-a \
  --role=pod-reader --user=dev-an

kubectl auth can-i list pods -n team-a --as=dev-an   # yes
kubectl auth can-i list pods -n team-b --as=dev-an   # no

Điểm cần thấy: Role không có namespace trong kubectl create role sẽ rơi vào namespace hiện tại, nên luôn truyền -n cho rõ ràng.

Bài 2: Subresource pods/log và pods/exec (7 phút)

Cấp thêm cho dev-an quyền đọc log và quyền exec vào pod trong team-a, vẫn không cho xoá pod.

Gợi ý: log là subresource pods/log với verb get, exec là pods/exec với verb create.

Xem đáp án
kubectl create role dev-debug -n team-a \
  --verb=get,list --resource=pods \
  --dry-run=client -o yaml > role-debug.yaml

Sửa file thành:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: dev-debug
  namespace: team-a
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get"]
- apiGroups: [""]
  resources: ["pods/exec"]
  verbs: ["create"]
kubectl apply -f role-debug.yaml
kubectl create rolebinding dev-debug-binding -n team-a \
  --role=dev-debug --user=dev-an

kubectl auth can-i get pods/log -n team-a --as=dev-an    # yes
kubectl auth can-i create pods/exec -n team-a --as=dev-an # yes
kubectl auth can-i delete pods -n team-a --as=dev-an      # no

Nhiều bạn quên pods/exec dùng verb create chứ không phải get, đây là bẫy rất hay gặp.

Bài 3: ClusterRole cho resource cấp cluster (7 phút)

Cấp cho dev-an quyền get, list, watch nodes trên toàn cluster.

Yêu cầu kiểm chứng: list nodes phải yes. Sau đó thử cố tình bind bằng RoleBinding và giải thích tại sao nó no.

Xem đáp án
kubectl create clusterrole node-viewer --verb=get,list,watch --resource=nodes
kubectl create clusterrolebinding node-viewer-binding \
  --clusterrole=node-viewer --user=dev-an

kubectl auth can-i list nodes --as=dev-an   # yes

Thử phá:

kubectl create rolebinding node-viewer-wrong -n team-a \
  --clusterrole=node-viewer --user=dev-an

kubectl auth can-i list nodes -n team-a --as=dev-an   # no

Lý do: RoleBinding chỉ cấp quyền cho resource nằm trong namespace của nó. nodes là resource cấp cluster nên RoleBinding không "with" tới được. Với resource cấp cluster, luôn dùng ClusterRoleBinding.

Bài 4: ClusterRole dùng lại cho nhiều namespace (6 phút)

Tạo ClusterRole pod-auditor cho get, list pods. Bind nó vào team-a bằng RoleBinding. Chứng minh team-b không có quyền.

Xem đáp án
kubectl create clusterrole pod-auditor --verb=get,list --resource=pods
kubectl create rolebinding audit-a -n team-a \
  --clusterrole=pod-auditor --user=dev-an

kubectl auth can-i list pods -n team-a --as=dev-an   # yes
kubectl auth can-i list pods -n team-b --as=dev-an   # no

Đây là mẫu rất đáng nhớ: ClusterRole + RoleBinding = quyền chỉ trong một namespace. Muốn toàn cluster thì phải là ClusterRoleBinding.

Bài 5: Debug một Forbidden (5 phút)

Một đồng nghiệp tạo Role dưới đây và user dev-an báo không list được deployments. Tìm lỗi.

rules:
- apiGroups: [""]
  resources: ["deployments"]
  verbs: ["get", "list"]
Xem đáp án

deployments thuộc group apps, không thuộc core group, nên apiGroups phải là ["apps"]. Đọc thông báo lỗi thật để xác nhận:

Error from server (Forbidden): deployments.apps is forbidden: User "dev-an"
cannot list resource "deployments" in API group "apps" in the namespace "team-a"

Thông báo này cho bạn đủ bốn mảnh: user, verb, resource kèm group (deployments.apps), và namespace. Sửa apiGroups thành ["apps"] rồi kubectl apply lại là xong, quyền có hiệu lực ngay, không cần restart gì.

Bài 6: Tốc độ với ClusterRole có sẵn (3 phút)

Đề cho "quyền chỉ đọc như role view" trong namespace team-a. Đừng viết Role mới.

Xem đáp án
kubectl create rolebinding dev-view -n team-a --clusterrole=view --user=dev-an
kubectl auth can-i --list -n team-a --as=dev-an

Bốn ClusterRole có sẵn đáng nhớ: view (chỉ đọc), edit (đọc và sửa), admin (toàn quyền trong namespace), cluster-admin (toàn cluster). Trong bài thi chỉ dùng ba cái đầu.

Lỗi thường gặp và cách xử lý

Lỗi Biểu hiện Cách xử lý
apiGroups sai Resource thuộc apps nhưng để [""] pods, services, configmaps, secrets, PVC dùng ""; deployments, statefulsets, daemonsets, replicasets dùng "apps"
Bind ClusterRole bằng RoleBinding rồi lấy quyền cho nodes auth can-i list nodes trả no Resource cấp cluster thì phải dùng ClusterRoleBinding
Dùng get thay cho list kubectl get pods chạy được nhưng kubectl get pods -o name báo Forbidden get không bao gồm list. Đề nói "xem/liệt kê" thì luôn thêm list và watch
Quên namespace của binding Binding tạo ở default, quyền không có trong team-a Luôn -n <namespace> khi tạo Role và RoleBinding
Test bằng auth can-i mà quên --as Luôn trả yes vì đó là quyền của bạn Luôn thêm --as=<subject> -n <namespace>
Sửa roleRef rồi apply Báo lỗi cannot change roleRef roleRef là immutable, phải delete rolebinding rồi tạo lại
Nghĩ resourceNames chặn được list User vẫn list thấy hết object resourceNames chỉ dùng với get, update, patch, delete; list và watch không bị giới hạn bởi nó
Dùng cluster-admin cho tiện Bài bị chấm sai vì quyền quá rộng Chỉ cấp đúng verbs và resources đề yêu cầu

[!WARNING] Nếu kubectl auth can-i trả yes cho mọi thứ, kiểm tra lại ngay xem bạn có đang thiếu --as hoặc đang dùng kubeconfig cluster-admin hay không. Đây là lỗi "tự tin giả" phổ biến nhất khi luyện RBAC.

Góc chuyên gia

  • Đọc đề thành ba dòng trước khi gõ. Verbs, resources, scope. Gạch chân trong đề, đừng vừa đọc vừa gõ.
  • Luôn dry-run trước. k create role ... --dry-run=client -o yaml > r.yaml rồi sửa apiGroups và thêm subresource nhanh hơn nhiều so với viết tay.
  • Với resource thuộc group khác core, kiểm tra lại apiGroups sau khi dry-run. Đừng tin skeleton sinh ra, nó chỉ biết phần bạn đưa vào flag.
  • ClusterRole + RoleBinding là combo ăn điểm. Đề cho "quyền X trong namespace Y cho nhiều namespace" thì tạo một ClusterRole, bind nhiều RoleBinding.
  • kubectl auth can-i --list là vũ khí hạng nặng. Nếu đề yêu cầu "liệt kê các quyền hiện có của user", đây là câu trả lời, không phải get role.
  • Kiểm tra chiều no. Một binding đúng luôn có ít nhất một phép thử no đi kèm. Nó chứng minh bạn không cấp thừa.
  • Alias trước khi vào phòng thi: alias k=kubectl và export do="--dry-run=client -o yaml", tiết kiệm vài phút trên tổng 2 giờ.

Tóm tắt và bước tiếp theo

Bốn dòng tôi tự nhắc lại mỗi lần gặp câu RBAC:

  1. Role và RoleBinding thì có namespace, ClusterRole và ClusterRoleBinding thì không.
  2. apiGroups là "" cho core group, "apps" cho workloads.
  3. get không bao gồm list, exec thì dùng create.
  4. Kiểm chứng bằng auth can-i --as ... -n ..., cả chiều yes và chiều no.

Điền thử để tự kiểm tra trước khi làm flashcard:

Role là object thuộc phạm vi {{c1::namespace}} nên luôn cần metadata.namespace.
ClusterRole có phạm vi {{c2::cluster}} nên không có metadata.namespace.
RoleBinding gắn Role hoặc ClusterRole vào subject nhưng chỉ có hiệu lực trong {{c3::namespace của RoleBinding}}.
Với resource thuộc core group như pods, services, configmaps, apiGroups phải là {{c4::""}}.
Để kiểm chứng quyền của một user khác, dùng kubectl auth can-i với flag {{c5::--as}}.

Ôn lại bằng bộ flashcard dưới đây, mỗi ngày một lượt là đủ nhớ:

[
  { "front": "Role khác ClusterRole ở điểm nào?", "back": "Role thuộc một namespace, ClusterRole thuộc phạm vi cluster và dùng lại được." },
  { "front": "RoleBinding có thể tham chiếu kind nào ở roleRef?", "back": "Role hoặc ClusterRole." },
  { "front": "ClusterRoleBinding tham chiếu được Role hay chỉ ClusterRole?", "back": "Chỉ ClusterRole." },
  { "front": "apiGroups cho pods, services, configmaps, secrets là gì?", "back": "Chuỗi rỗng \"\" (core group)." },
  { "front": "apiGroups cho deployments, statefulsets, daemonsets là gì?", "back": "\"apps\"." },
  { "front": "Muốn đọc log của pod cần resource và verb nào?", "back": "Resource pods/log với verb get." },
  { "front": "Muốn exec vào pod cần resource và verb nào?", "back": "Resource pods/exec với verb create." },
  { "front": "Bind một ClusterRole bằng RoleBinding cho resource nodes có tác dụng không?", "back": "Không, RoleBinding chỉ cấp quyền cho resource trong namespace của nó." },
  { "front": "resourceNames có giới hạn được verb list không?", "back": "Không, list và watch trả về nhiều object nên resourceNames không áp dụng." },
  { "front": "Lệnh kiểm tra user dev-an có list được pods trong team-a?", "back": "kubectl auth can-i list pods -n team-a --as=dev-an" },
  { "front": "Lệnh liệt kê toàn bộ quyền của một user trong namespace?", "back": "kubectl auth can-i --list -n <namespace> --as=<user>" },
  { "front": "Sửa roleRef của RoleBinding bằng kubectl apply được không?", "back": "Không, roleRef là immutable, phải xoá và tạo lại binding." },
  { "front": "ClusterRole có sẵn nào cấp quyền chỉ đọc trong một namespace?", "back": "view." },
  { "front": "Subject kind ServiceAccount dùng apiGroup gì?", "back": "Chuỗi rỗng \"\", khác với User và Group dùng rbac.authorization.k8s.io." }
]

Bước tiếp theo: bài sau chúng ta gắn chính những Role và RoleBinding này vào một ServiceAccount để pod có quyền gọi API server, kèm theo cách kiểm tra token mount và các lỗi hay gặp khi workload không có đủ quyền.