# 🧾 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.**

```bash
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.**

```bash
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.

```yaml
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"]
```

```bash
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:

```bash
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:

```yaml
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.**

```bash
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:

```bash
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`.

<details><summary>Xem đáp án</summary>

```bash
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.

</details>

### 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`.

<details><summary>Xem đáp án</summary>

```bash
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:

```yaml
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"]
```

```bash
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.

</details>

### 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`.

<details><summary>Xem đáp án</summary>

```bash
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á:

```bash
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.

</details>

### 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.

<details><summary>Xem đáp án</summary>

```bash
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.

</details>

### 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.

```yaml
rules:
- apiGroups: [""]
  resources: ["deployments"]
  verbs: ["get", "list"]
```

<details><summary>Xem đáp án</summary>

`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ì.

</details>

### 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.

<details><summary>Xem đáp án</summary>

```bash
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.

</details>

## 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:

```cloze
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ớ:

```flashcard
[
  { "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.
