# 🛡️ SecurityContext, capabilities và Pod Security Admission

Chào bạn, mình đỗ CKA và phần security là phần mình từng làm sai nhiều nhất khi luyện. Lý do rất đơn giản: đề không hỏi "khái niệm", đề đưa một YAML hoặc một namespace và yêu cầu bạn làm cho pod chạy được trong điều kiện bảo mật mới. Bài này mình sẽ dạy đúng theo hướng đó: viết YAML cho non-root, bỏ capability, và gắn label PSA cho namespace, kèm những lỗi mình đã trả giá.

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

- Phân biệt được `securityContext` ở cấp pod và cấp container, biết trường nào đặt ở đâu.
- Cấu hình pod chạy non-root với `runAsUser`, `runAsNonRoot`, `fsGroup` và `readOnlyRootFilesystem`.
- Drop toàn bộ capability và add lại đúng capability cần thiết.
- Gắn label Pod Security Admission cho namespace (enforce/audit/warn) và sửa pod vi phạm cho qua mức `restricted`.
- Nhận diện các lỗi hay gặp trong phòng thi liên quan đến ba phần trên.

## Ôn tập nhanh (5 phút)

Bài trước chúng ta đã nói: `ServiceAccount` là identity theo namespace, và pod không khai báo gì sẽ chạy bằng SA `default`. Điều đó chỉ giải quyết **pod là ai** (authentication/authorization với API server).

Bài này là chuyện khác: **pod chạy với đặc quyền gì bên trong container**. Một pod có thể dùng ServiceAccount quyền rất hẹp nhưng process bên trong vẫn chạy uid 0 và giữ đủ Linux capabilities. Đề CKA hay tách hai ý này ra thành hai câu riêng.

> [!NOTE]
> Nhớ nhanh: ServiceAccount = quyền gọi API server. SecurityContext = quyền của process trong container. PSA = luật của cả namespace, áp lúc tạo pod.

## Lý thuyết

### 1. SecurityContext: hai cấp, một mục tiêu

`securityContext` xuất hiện ở hai nơi:

- `spec.securityContext` (cấp pod): áp cho mọi container, dùng cho các trường chung như user/group/fsGroup.
- `spec.containers[].securityContext` (cấp container): ghi đè cấp pod, và là **nơi duy nhất** khai báo được `capabilities`, `privileged`, `allowPrivilegeEscalation`, `readOnlyRootFilesystem`.

```yaml
spec:
  securityContext:          # cấp pod
    runAsUser: 1000
    runAsGroup: 3000
    runAsNonRoot: true
    fsGroup: 2000
  containers:
    - name: app
      image: busybox:1.36
      securityContext:      # cấp container
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
```

Nếu bạn đặt `capabilities` ở cấp pod, API server trả lỗi kiểu `unknown field "capabilities"` trong `spec.securityContext`. Đây là lỗi mình gặp đầu tiên khi luyện, và nó tốn của mình gần 5 phút trong một buổi mock.

| Trường | Cấp pod | Cấp container |
| --- | --- | --- |
| `runAsUser`, `runAsGroup`, `runAsNonRoot` | Có | Có (ghi đè) |
| `fsGroup` | Có | Không |
| `supplementalGroups` | Có | Không |
| `seccompProfile` | Có | Có (ghi đè) |
| `capabilities` | Không | Có |
| `privileged` | Không | Có |
| `allowPrivilegeEscalation` | Không | Có |
| `readOnlyRootFilesystem` | Không | Có |

Trường quan trọng nhất khi thi là `runAsNonRoot: true`. Nó không set uid, nó chỉ **bắt buộc** runtime phải chứng minh được process không chạy uid 0. Nếu image có `USER root` và bạn không set `runAsUser`, pod sẽ vào `CreateContainerConfigError` với message `container has runAsNonRoot and image will run as root`. Cách sửa luôn là thêm `runAsUser: 1000`.

Còn `fsGroup` thì liên quan đến volume: các volume kiểu `emptyDir`, `configMap`, `secret`, PV đều được `chown` cho group này, nhờ vậy process non-root ghi được vào volume. Đây là lý do vì sao đề hay đi kèm "pod phải ghi được vào volume" với yêu cầu non-root.

### 2. Capabilities: drop trước, add sau

Linux capabilities là các mảnh quyền root bị chia nhỏ (`NET_ADMIN`, `SYS_TIME`, `CHOWN`, ...). Container mặc định giữ một bộ capabilities của runtime. Cách hardening chuẩn là drop hết rồi add lại đúng cái cần:

```yaml
securityContext:
  capabilities:
    drop: ["ALL"]
    add: ["NET_BIND_SERVICE"]
```

Hai điều cần nhớ:

- `drop: ["ALL"]` phải viết đúng chữ `ALL` in hoa, trong một list.
- `allowPrivilegeEscalation: false` là bạn đồng minh của `drop`. Thiếu nó, process vẫn có thể leo thang qua `setuid`. Mức `restricted` của PSA yêu cầu cả hai.

Với mức `restricted`, chỉ được add lại duy nhất `NET_BIND_SERVICE`. Nếu đề yêu cầu `NET_ADMIN`, bạn buộc phải nới namespace xuống `privileged`. Đây là cái bẫy rất hay gặp: đề cho namespace đang `restricted`, yêu cầu pod có `NET_ADMIN`, và người thi loay hoay sửa YAML mãi mà không hiểu vì sao bị từ chối.

### 3. Pod Security Admission: luật của namespace

PSA là admission controller có sẵn trong Kubernetes (GA từ 1.25), hoạt động bằng **label trên namespace**, không cần cài gì thêm. Có ba mức:

| Mức | Ý nghĩa | Dùng khi |
| --- | --- | --- |
| `privileged` | Không hạn chế gì | workload hệ thống thật sự cần đặc quyền |
| `baseline` | Chặn các thiết lập nguy hiểm: `privileged`, `hostNetwork`, `hostPID`, `hostPath`, thêm capability ngoài danh sách cho phép | workload thông thường |
| `restricted` | `baseline` cộng thêm: non-root, `drop: ALL`, `allowPrivilegeEscalation: false`, `seccompProfile` là `RuntimeDefault` hoặc `Localhost`, chỉ cho volume type an toàn | đề hardening, chuẩn cao nhất |

Ba label tương ứng ba chế độ:

- `pod-security.kubernetes.io/enforce` : chặn thật, pod vi phạm bị từ chối.
- `pod-security.kubernetes.io/audit` : chỉ ghi annotation vào audit log.
- `pod-security.kubernetes.io/warn` : chỉ in cảnh báo ra `kubectl`, vẫn tạo pod.

Mặc định khi namespace không có label nào, mức hiệu lực là {{c5::privileged}}, tức là không chặn gì. Vì vậy đề bài luôn phải yêu cầu bạn tự gắn label.

```bash
kubectl label namespace sec-lab \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted \
  --overwrite
```

Muốn ghim phiên bản luật (tránh cluster nâng cấp làm đổi hành vi) thì thêm `pod-security.kubernetes.io/enforce-version=v1.29`.

> [!IMPORTANT]
> PSA chỉ kiểm tra **lúc tạo pod**. Đổi label không xoá hay sửa pod đang chạy. Nếu đề yêu cầu "namespace phải chặn pod privileged mà các pod cũ vẫn còn", bạn phải tự xoá hoặc sửa chúng.

Luồng kiểm tra diễn ra như sau:

```mermaid
flowchart TD
    Req["📝 kubectl apply pod"] --> API["API Server"]
    API --> PSA{"Namespace có label enforce?"}
    PSA -->|"privileged"| OK1["✅ Gần như cho qua tất cả"]
    PSA -->|"baseline"| Check2["Kiểm tra privileged, hostNetwork, hostPath"]
    PSA -->|"restricted"| Check3["Kiểm tra non-root, drop ALL, seccomp"]
    Check2 -->|"hợp lệ"| OK2["✅ Nhận pod"]
    Check3 -->|"hợp lệ"| OK3["✅ Nhận pod"]
    Check2 -->|"vi phạm"| Rej["❌ Từ chối pod"]
    Check3 -->|"vi phạm"| Rej
```

Khi bị từ chối, message có dạng: `pods "x" is forbidden: violates PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "app" must set securityContext.allowPrivilegeEscalation=false)`. Đọc kỹ message này, nó liệt kê đủ mọi vi phạm, sửa đúng từng dòng là xong.

## Ví dụ có hướng dẫn

### Ví dụ 1: Pod non-root, filesystem read-only, vẫn ghi được vào volume

Yêu cầu: pod chạy uid 1000, group 3000, không cho ghi vào root filesystem, nhưng process cần ghi vào `/tmp`.

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: secure-app
  namespace: sec-lab
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    runAsNonRoot: true
    fsGroup: 2000
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "while true; do date > /tmp/out; sleep 30; done"]
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      volumeMounts:
        - name: tmp
          mountPath: /tmp
  volumes:
    - name: tmp
      emptyDir: {}
```

Các bước kiểm tra:

```bash
kubectl apply -f secure-app.yaml
kubectl get pod secure-app -n sec-lab
kubectl exec -n sec-lab secure-app -- id
kubectl exec -n sec-lab secure-app -- cat /tmp/out
kubectl exec -n sec-lab secure-app -- sh -c 'touch /root/test'   # phải bị Permission denied
```

Kết quả `id` phải là `uid=1000 gid=3000 groups=2000`. Nếu lệnh `touch /root/test` thành công thì `readOnlyRootFilesystem` chưa ăn.

### Ví dụ 2: Drop ALL rồi add lại một capability

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: cap-pod
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sleep", "3600"]
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
          add: ["NET_BIND_SERVICE"]
```

Kiểm chứng bằng cách xem capability hiệu lực của process:

```bash
kubectl exec cap-pod -- cat /proc/1/status | grep -i cap
```

Bạn sẽ thấy `CapPrm` và `CapEff` khác `0000003fffffffff` (bộ đầy đủ mặc định). Đây là cách chứng minh nhanh khi đề yêu cầu "verify".

### Ví dụ 3: Bật restricted cho namespace rồi sửa pod vi phạm

Bước 1, gắn label:

```bash
kubectl create namespace sec-lab
kubectl label ns sec-lab pod-security.kubernetes.io/enforce=restricted --overwrite
```

Bước 2, apply một pod "xấu":

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: bad-pod
  namespace: sec-lab
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sleep", "3600"]
      securityContext:
        privileged: true
```

Kết quả: bị từ chối với vài dòng vi phạm. Bước 3, sửa thành bản hợp lệ tối thiểu cho `restricted`:

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: good-pod
  namespace: sec-lab
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: busybox:1.36
      command: ["sleep", "3600"]
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
```

Bốn thứ bắt buộc để qua `restricted`: `runAsNonRoot: true` (kèm uid non-root), `allowPrivilegeEscalation: false`, `capabilities.drop: ["ALL"]`, và `seccompProfile.type` là `RuntimeDefault`. Thiếu bất kỳ cái nào là bị chặn.

Mẹo mình dùng nhiều nhất: chạy `--dry-run=server` trước khi apply thật. Server-side dry run vẫn đi qua admission nên bạn thấy cảnh báo và lỗi từ chối ngay mà không tạo rác trong cluster.

```bash
kubectl apply -f good-pod.yaml --dry-run=server
```

## Luyện tập có hướng dẫn

Làm theo thứ tự, mỗi bài tự bấm giờ. Đáp án ở phần thu gọn bên dưới, cố gắng làm trước khi mở.

**Bài 1.** Tạo namespace `hardening` và gắn cả ba label `enforce`, `warn`, `audit` ở mức `restricted`, ghim version `v1.29`.

**Bài 2.** Viết pod `probe` trong namespace đó: image `busybox:1.36`, chạy `sleep 3600`, uid 1000, drop toàn bộ capability, `allowPrivilegeEscalation: false`, seccomp `RuntimeDefault`. Pod phải ở trạng thái `Running`, và `kubectl exec` vào xác nhận được uid.

**Bài 3.** Pod sau bị từ chối. Chỉ ra ba vi phạm và sửa lại cho qua `restricted`:

```yaml
spec:
  containers:
    - name: web
      image: nginx:1.25
      securityContext:
        runAsNonRoot: true
        capabilities:
          add: ["NET_ADMIN", "SYS_TIME"]
```

**Bài 4.** Đề yêu cầu pod trong namespace `hardening` phải có capability `NET_ADMIN`. Trình bày hai hướng xử lý và nói rõ hướng nào đúng về mặt bảo mật.

**Bài 5.** Namespace `sec-lab` đang có 3 pod chạy bằng `privileged: true`. Bạn gắn `enforce=restricted` lên namespace. Ba pod đó có bị xoá không? Làm sao để đưa namespace về trạng thái sạch?

<details><summary>Đáp án bài 1</summary>

```bash
kubectl create namespace hardening
kubectl label ns hardening \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=v1.29 \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted --overwrite
kubectl get ns hardening --show-labels
```

</details>

<details><summary>Đáp án bài 2</summary>

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: probe
  namespace: hardening
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: probe
      image: busybox:1.36
      command: ["sleep", "3600"]
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
```

```bash
kubectl apply -f probe.yaml --dry-run=server   # xem trước
kubectl apply -f probe.yaml
kubectl get pod probe -n hardening -w
kubectl exec -n hardening probe -- id
```

</details>

<details><summary>Đáp án bài 3</summary>

Ba vi phạm:

1. `capabilities.add` có `NET_ADMIN` và `SYS_TIME`: `restricted` chỉ cho add lại `NET_BIND_SERVICE`.
2. Thiếu `allowPrivilegeEscalation: false`.
3. Thiếu `seccompProfile.type` là `RuntimeDefault` hoặc `Localhost`.

Bản sửa:

```yaml
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 101        # nginx-unprivileged dùng uid 101
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: web
      image: nginxinc/nginx-unprivileged:1.25
      securityContext:
        allowPrivilegeEscalation: false
        capabilities:
          drop: ["ALL"]
```

Lưu ý: image `nginx` thường cần root để bind port 80, nên đổi sang bản unprivileged. Trong đề thi, nếu image cố định là `nginx` và yêu cầu non-root, hãy kiểm tra lại xem đề có cho phép giữ port 80 hay không.

</details>

<details><summary>Đáp án bài 4</summary>

Hướng 1 (sai về bảo mật): hạ namespace xuống `privileged` hoặc `baseline`. `NET_ADMIN` không nằm trong danh sách capability được add ở `baseline`, nên thực tế chỉ `privileged` mới chạy được.

Hướng 2 (đúng): giữ namespace `restricted` và giữ pod không cần `NET_ADMIN`. Nếu thật sự cần, tách namespace riêng cho workload đặc quyền với `enforce=privileged`, giới hạn phạm vi RBAC, và ghi rõ lý do. Trong phòng thi, hãy đọc đề xem họ yêu cầu "giữ restricted" hay "cho pod chạy" trước khi chọn.

</details>

<details><summary>Đáp án bài 5</summary>

Không. PSA chỉ áp lúc tạo pod, label không có tác dụng hồi tố. Ba pod cũ vẫn chạy. Để sạch, phải xoá hoặc rollout lại chúng:

```bash
kubectl get pods -n sec-lab -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'
kubectl delete pod <tên> -n sec-lab
```

Với workload do Deployment quản lý, dùng `kubectl rollout restart deployment/<tên> -n sec-lab` để ReplicaSet tạo pod mới, và pod mới sẽ bị PSA kiểm tra, nên phải sửa template trước.

</details>

## Góc chuyên gia

**Ba lỗi mình từng mắc, theo thứ tự tần suất:**

1. Đặt `capabilities` hoặc `allowPrivilegeEscalation` ở `spec.securityContext` (cấp pod). Không hợp lệ, YAML bị từ chối ngay. Luôn kiểm tra bằng `kubectl explain pod.spec.securityContext` và `kubectl explain pod.spec.containers.securityContext` trước khi viết.
2. `runAsNonRoot: true` mà quên `runAsUser`, dẫn tới `CreateContainerConfigError`. Đọc `kubectl describe pod`, message nằm ở phần `Events`, không nằm trong log.
3. `readOnlyRootFilesystem: true` rồi thắc mắc vì sao app crash. Rất nhiều app cần ghi vào `/tmp`, `/var/run`, `/var/cache`. Cách sửa nhanh: mount `emptyDir` vào đúng thư mục đó.

**Quy tắc ngón tay:**

- Pod non-root: `runAsNonRoot: true` + `runAsUser: <uid>` + `fsGroup` nếu có volume. Viết cả ba ngay từ đầu, đừng chờ lỗi.
- Namespace restricted: dùng đúng bốn dòng vàng là `runAsNonRoot`, `runAsUser`, `allowPrivilegeEscalation: false`, `capabilities.drop: ["ALL"]`, cộng `seccompProfile: RuntimeDefault`. Thuộc lòng bộ này là xong 90% câu hỏi hardening.
- Trước khi apply vào namespace có PSA, chạy `--dry-run=server`. Mất 3 giây, tiết kiệm 3 phút.
- Khi bị từ chối, đọc nguyên message lỗi. PSA liệt kê từng vi phạm kèm tên container, không cần đoán.
- Đừng giả định `kube-system` được miễn trừ PSA. Kiểm tra bằng `kubectl get ns kube-system --show-labels` trước khi kết luận.

**Mẹo tốc độ trong phòng thi:** phần security thường chỉ 2 đến 3 câu, mỗi câu nên xong trong 6 đến 8 phút. Cứ generate bằng `kubectl run` rồi patch, hoặc viết YAML tay, nhưng nhớ set context và namespace trước:

```bash
kubectl config set-context --current --namespace=hardening
```

Sau khi làm xong, luôn verify bằng một lệnh `kubectl get pod` cộng một lệnh `kubectl exec` hoặc `kubectl describe`. Điểm CKA chấm trên trạng thái thực tế của cluster, không chấm trên YAML bạn viết.

**Vài câu cloze để tự kiểm tra nhanh:** `capabilities` chỉ khai báo được ở cấp {{c1::container}}; mức PSA chặt nhất trong ba mức là {{c2::restricted}}; label bắt buộc chặn thật là `pod-security.kubernetes.io/{{c3::enforce}}`; để bắt process không chạy uid 0 ta đặt `runAsNonRoot: {{c4::true}}`; và {{c6::allowPrivilegeEscalation: false}} là trường bắt buộc phải có ở mức restricted.

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

Tóm gọn bài này trong bốn dòng:

- `securityContext` ở cấp container ghi đè cấp pod; `capabilities`, `privileged`, `allowPrivilegeEscalation`, `readOnlyRootFilesystem` chỉ có ở cấp container.
- Non-root cần `runAsNonRoot: true` cộng `runAsUser`, thêm `fsGroup` khi có volume.
- Hardening chuẩn là `drop: ["ALL"]` cộng `allowPrivilegeEscalation: false`.
- PSA là label trên namespace với ba mức `privileged`/`baseline`/`restricted` và ba chế độ `enforce`/`audit`/`warn`; chỉ áp lúc tạo pod.

Flashcard dưới đây để bạn ôn lại trước khi chuyển bài. Mình khuyên chạy hết một lượt, đánh dấu những thẻ sai rồi làm lại sau 24 giờ.

```flashcard
[
  { "front": "securityContext đặt ở cấp nào?", "back": "Hai cấp: spec.securityContext (pod) và spec.containers[].securityContext (container), cấp container ghi đè cấp pod." },
  { "front": "Trường nào CHỈ khai báo được ở cấp container?", "back": "capabilities, privileged, allowPrivilegeEscalation, readOnlyRootFilesystem." },
  { "front": "Trường nào chỉ có ở cấp pod?", "back": "fsGroup, supplementalGroups, sysctls." },
  { "front": "runAsNonRoot: true khác runAsUser thế nào?", "back": "runAsNonRoot chỉ bắt buộc process không chạy uid 0; runAsUser mới là thứ set uid cụ thể." },
  { "front": "Lỗi gì xảy ra khi runAsNonRoot: true mà image chạy root?", "back": "CreateContainerConfigError với message 'container has runAsNonRoot and image will run as root'." },
  { "front": "fsGroup dùng để làm gì?", "back": "Set group sở hữu cho volume (emptyDir, configMap, secret, PV) để process non-root ghi được." },
  { "front": "Cách drop toàn bộ Linux capabilities trong YAML?", "back": "securityContext.capabilities.drop: [\"ALL\"], đặt ở cấp container." },
  { "front": "Ba mức của Pod Security Admission?", "back": "privileged (không hạn chế), baseline (chặn đặc quyền nguy hiểm), restricted (non-root, drop ALL, seccomp)." },
  { "front": "Label nào chặn thật pod vi phạm?", "back": "pod-security.kubernetes.io/enforce, các label audit và warn chỉ ghi log hoặc in cảnh báo." },
  { "front": "Namespace không có label PSA thì mức hiệu lực là gì?", "back": "privileged, tức là không chặn gì." },
  { "front": "Bốn yêu cầu tối thiểu để pod qua mức restricted?", "back": "runAsNonRoot: true (kèm uid non-root), allowPrivilegeEscalation: false, capabilities.drop: [\"ALL\"], seccompProfile RuntimeDefault hoặc Localhost." },
  { "front": "Ở mức restricted được add lại capability nào?", "back": "Chỉ NET_BIND_SERVICE. NET_ADMIN hay SYS_TIME đều bị từ chối." },
  { "front": "Đổi label PSA có xoá pod đang chạy không?", "back": "Không. PSA chỉ kiểm tra lúc tạo pod, phải tự xoá hoặc rollout lại workload." },
  { "front": "Lệnh nhanh để xem trước vi phạm PSA mà không tạo pod?", "back": "kubectl apply -f pod.yaml --dry-run=server" }
]
```

**Bài tiếp theo: CertificateSigningRequest và kubeconfig.** Chúng ta sẽ đi từ việc "process trong container chạy bằng quyền gì" sang "client nào được phép gọi API server": tạo CSR, approve, ký certificate và dựng kubeconfig riêng cho từng user. Đây là dạng bài chắc chắn xuất hiện trong domain Security, và cũng là phần dễ mất điểm nhất nếu bạn không thuộc thứ tự lệnh.
