# 🔑 CertificateSigningRequest và kubeconfig

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

Nhắc lại nhanh bài trước: Kubernetes có hai loại "danh tính". ServiceAccount là danh tính cho workload chạy trong cluster, còn **user thường** (như `dev-anna`, `ci-bot`) không có object nào trong API, nó chỉ tồn tại thông qua **client certificate** do cluster ký. Bài trước mình đã siết pod bằng SecurityContext và PSA, bài này mình đi vào phía con người: cấp cert cho user và đóng gói thành kubeconfig.

Sau bài này, bạn phải làm được:

- Tạo private key, CSR bằng `openssl` và submit thành object `CertificateSigningRequest`.
- Phê duyệt CSR và export certificate đã ký ra file.
- Dựng file kubeconfig hoàn chỉnh (cluster, user, context) bằng `kubectl config`.
- Kiểm chứng: phân biệt lỗi 401 (sai cert) và 403 (thiếu RBAC).
- Nhận diện các bẫy khiến bài CSR trong đề thi bị mất điểm oan.

Đây là dạng bài gần như chắc chắn xuất hiện trong đề CKA, và nó cũng là dạng bài mà thí sinh hay mất điểm vì gõ sai một cờ.

## Ví dụ mẫu: cấp cert cho user `dev-anna` và dựng kubeconfig

Đề bài kiểu thi: "User `dev-anna` thuộc group `dev-team` cần một kubeconfig tại `/root/dev-anna.kubeconfig`, dùng cluster hiện tại, context tên `dev-anna@kubernetes`. Chứng chỉ phải do cluster ký."

Luồng tổng quát:

```mermaid
flowchart LR
  A["🔑<br/>openssl genrsa"] --> B["📝<br/>openssl req -new"]
  B --> C["📦<br/>base64 CSR"]
  C --> D["📄<br/>CertificateSigningRequest"]
  D --> E["✅<br/>certificate approve"]
  E --> F["📜<br/>status.certificate"]
  F --> G["⚙️<br/>kubectl config set-credentials"]
  G --> H["🚀<br/>kubectl --kubeconfig"]
```

### Bước 1: Tạo key và file CSR

```bash
openssl genrsa -out dev-anna.key 2048
openssl req -new -key dev-anna.key -out dev-anna.csr -subj "/CN=dev-anna/O=dev-team"
```

`CN` là username, `O` là group. Không có `O` thì mọi RoleBinding gắn với Group sẽ vô tác dụng, đây là lỗi rất hay gặp.

### Bước 2: Mã hóa base64 nội dung CSR

```bash
cat dev-anna.csr | base64 | tr -d '\n'
```

Phải bỏ newline, nếu không API server sẽ từ chối. Cách nhanh hơn: `base64 -w0 dev-anna.csr`.

### Bước 3: Tạo object CertificateSigningRequest

```yaml
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: dev-anna
spec:
  request: <DÁN_CHUỖI_BASE64_Ở_BƯỚC_2>
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth
```

> [!TIP]
> Trong phòng thi, đừng copy paste hai lần. Dùng heredoc để vừa encode vừa apply trong một lệnh:
>
> ```bash
> cat <<EOF | kubectl apply -f -
> apiVersion: certificates.k8s.io/v1
> kind: CertificateSigningRequest
> metadata:
>   name: dev-anna
> spec:
>   request: $(cat dev-anna.csr | base64 | tr -d '\n')
>   signerName: kubernetes.io/kube-apiserver-client
>   usages:
>   - client auth
> EOF
> ```

### Bước 4: Phê duyệt và lấy certificate

```bash
kubectl get csr
kubectl certificate approve dev-anna
kubectl get csr dev-anna -o jsonpath='{.status.certificate}' | base64 -d > dev-anna.crt
```

Kiểm tra nội dung cert vừa nhận:

```bash
openssl x509 -in dev-anna.crt -noout -subject
# subject=O = dev-team, CN = dev-anna
```

### Bước 5: Dựng kubeconfig

Lấy thông tin cluster đang dùng:

```bash
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
```

Rồi tạo kubeconfig từng phần:

```bash
kubectl config set-cluster kubernetes \
  --server=https://<API_SERVER>:6443 \
  --certificate-authority=/etc/kubernetes/pki/ca.crt \
  --embed-certs=true \
  --kubeconfig=/root/dev-anna.kubeconfig

kubectl config set-credentials dev-anna \
  --client-certificate=dev-anna.crt \
  --client-key=dev-anna.key \
  --embed-certs=true \
  --kubeconfig=/root/dev-anna.kubeconfig

kubectl config set-context dev-anna@kubernetes \
  --cluster=kubernetes \
  --user=dev-anna \
  --kubeconfig=/root/dev-anna.kubeconfig

kubectl config use-context dev-anna@kubernetes --kubeconfig=/root/dev-anna.kubeconfig
```

> [!WARNING]
> Thiếu `--embed-certs=true` thì kubeconfig chỉ lưu đường dẫn file. Copy sang máy khác hoặc chấm bài trên thư mục khác là hỏng ngay. Đề CKA gần như luôn yêu cầu kubeconfig "self-contained", nên mặc định hãy thêm cờ này.

### Bước 6: Kiểm chứng

```bash
kubectl config view --kubeconfig=/root/dev-anna.kubeconfig
kubectl --kubeconfig=/root/dev-anna.kubeconfig auth can-i get pods -n dev
kubectl --kubeconfig=/root/dev-anna.kubeconfig get pods -n dev
```

Nếu kết quả là `Forbidden` (403) thì tin tốt: cert và kubeconfig đã đúng, chỉ còn thiếu RBAC (bài trước đã làm RoleBinding cho user hoặc group). Nếu là `Unauthorized` (401) thì cert hoặc CA đang sai.

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

Làm lần lượt ba bài dưới đây trên cluster lab. Đừng mở đáp án trước khi thử ít nhất một lần.

### Bài 1: CSR cơ bản, kiểm tra subject

Tạo key và CSR cho user `ci-bot`, group `ci`, submit với signer phù hợp, approve, export ra `/root/ci-bot.crt` và chứng minh subject đúng.

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

```bash
cd /root
openssl genrsa -out ci-bot.key 2048
openssl req -new -key ci-bot.key -out ci-bot.csr -subj "/CN=ci-bot/O=ci"

cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: ci-bot
spec:
  request: $(cat ci-bot.csr | base64 | tr -d '\n')
  signerName: kubernetes.io/kube-apiserver-client
  usages:
  - client auth
EOF

kubectl certificate approve ci-bot
kubectl get csr ci-bot -o jsonpath='{.status.certificate}' | base64 -d > /root/ci-bot.crt
openssl x509 -in /root/ci-bot.crt -noout -subject
```

Kết quả mong đợi: `subject=O = ci, CN = ci-bot`.

</details>

### Bài 2: Dựng kubeconfig đầy đủ

Từ cert ở bài 1, tạo `/root/ci-bot.kubeconfig` với cluster `kubernetes`, user `ci-bot`, context `ci-bot@kubernetes`, namespace mặc định là `ci`. Sau đó chạy `kubectl --kubeconfig=/root/ci-bot.kubeconfig get pods` và giải thích kết quả nhận được.

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

```bash
kubectl config set-cluster kubernetes \
  --server=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}') \
  --certificate-authority=/etc/kubernetes/pki/ca.crt \
  --embed-certs=true \
  --kubeconfig=/root/ci-bot.kubeconfig

kubectl config set-credentials ci-bot \
  --client-certificate=/root/ci-bot.crt \
  --client-key=/root/ci-bot.key \
  --embed-certs=true \
  --kubeconfig=/root/ci-bot.kubeconfig

kubectl config set-context ci-bot@kubernetes \
  --cluster=kubernetes \
  --user=ci-bot \
  --namespace=ci \
  --kubeconfig=/root/ci-bot.kubeconfig

kubectl config use-context ci-bot@kubernetes --kubeconfig=/root/ci-bot.kubeconfig
kubectl --kubeconfig=/root/ci-bot.kubeconfig get pods
```

Kết quả điển hình: `Error from server (Forbidden)`. Như vậy là phần authentication đã xong, chỉ còn thiếu Role/RoleBinding trong namespace `ci`. Nếu bạn thấy `Unauthorized` thì quay lại kiểm tra cert và CA.

Kiểm tra nhanh namespace mặc định đã ăn chưa:

```bash
kubectl --kubeconfig=/root/ci-bot.kubeconfig config view --minify -o jsonpath='{.contexts[0].context.namespace}'
```

</details>

### Bài 3: Sửa một kubeconfig bị hỏng

Cho file `/root/broken.kubeconfig`:

```yaml
apiVersion: v1
kind: Config
clusters:
- name: k8s-lab
  cluster:
    server: https://127.0.0.1:8443
    certificate-authority-data: <CA_DATA>
users:
- name: ci-bot
  user:
    client-certificate-data: <CERT_DATA>
    client-key-data: <KEY_DATA>
contexts:
- name: ci-bot@kubernetes
  context:
    cluster: kubernetes
    user: ci-bot
current-context: ci-bot@kubernetes
```

Lệnh `kubectl --kubeconfig=/root/broken.kubeconfig get ns` báo lỗi kết nối. Tìm và sửa **hai** lỗi.

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

Lỗi 1: port sai. API server mặc định là `6443`, không phải `8443`.

Lỗi 2: tên cluster không khớp. Khối `clusters` đặt tên `k8s-lab` nhưng `contexts` lại trỏ tới `kubernetes`, nên context không tìm thấy cluster.

Sửa bằng lệnh thay vì sửa tay file YAML:

```bash
kubectl config set-cluster k8s-lab \
  --server=https://127.0.0.1:6443 \
  --kubeconfig=/root/broken.kubeconfig

kubectl config set-context ci-bot@kubernetes \
  --cluster=k8s-lab \
  --user=ci-bot \
  --kubeconfig=/root/broken.kubeconfig

kubectl --kubeconfig=/root/broken.kubeconfig get ns
```

Bài học: tên cluster trong `contexts` là **con trỏ tới tên**, không phải tên miền tự do. Sai một ký tự là kubeconfig chết.

</details>

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

| Triệu chứng | Nguyên nhân thường gặp | Cách xử lý |
| --- | --- | --- |
| `error decoding certificate request` khi apply CSR | Chuỗi base64 còn newline hoặc dán thiếu | Encode lại bằng `base64 -w0`, dùng heredoc `$(...)` |
| CSR tạo được nhưng không có cert | Chưa `approve`, hoặc đã `deny` | `kubectl get csr`, rồi `kubectl certificate approve <tên>` |
| Không approve được, báo forbidden | Thiếu quyền trên `certificatesigningrequests/approval` | Trong thi bạn là admin nên hiếm gặp, kiểm tra đang dùng đúng kubeconfig chưa |
| `Unauthorized` (401) khi test kubeconfig | Cert hết hạn, sai CA, hoặc client-key không khớp cert | So lại `--certificate-authority`, kiểm tra subject bằng `openssl x509` |
| `Forbidden` (403) | Cert đúng nhưng thiếu RBAC | Tạo Role/RoleBinding cho user hoặc group tương ứng |
| Kubeconfig chỉ chạy trên máy hiện tại | Quên `--embed-certs=true` | Tạo lại với `--embed-certs=true` |
| `context ... does not exist` | Tên cluster trong context không khớp khối `clusters` | `kubectl config get-clusters` rồi `set-context` lại |
| Sửa CSR báo lỗi | CSR là immutable | `kubectl delete csr <tên>` rồi tạo lại từ đầu |

Một lưu ý về signer: `kubernetes.io/kube-apiserver-client` **cần approve thủ công**. Còn `kubernetes.io/kube-apiserver-client-kubelet` thì controller-manager tự approve cho kubelet, nên đừng thắc mắc vì sao CSR của kubelet đã `Approved` sẵn.

## Góc kinh nghiệm thi

- **Đọc kỹ yêu cầu về vị trí file và tên context.** Đề cho `/root/dev-anna.kubeconfig` và `dev-anna@kubernetes` thì phải đúng từng ký tự. Sai tên context là mất điểm dù cert đúng.
- **Đừng đoán API server URL.** Luôn lấy từ kubeconfig hiện tại: `kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'`. Trên cluster thi thường là `https://<node-ip>:6443`.
- **`--embed-certs=true` là mặc định của bạn**, trừ khi đề nói rõ là dùng đường dẫn file.
- **Kiểm chứng bằng `kubectl auth can-i --as`** thay vì đoán: `kubectl auth can-i get pods -n dev --as=dev-anna --as-group=dev-team`.
- **Phân biệt 401 và 403** là kỹ năng chẩn đoán nhanh nhất trong bài này. 401 = vấn đề danh tính, 403 = vấn đề quyền.
- **Đừng quên `O=` trong subject.** Nếu đề nói "user thuộc group X" thì group đó phải nằm trong CSR, không thể thêm vào sau.
- **Cert do CSR ký có thời hạn** (mặc định khoảng 1 năm với signer client). Nếu cert cũ không dùng được, xóa CSR cũ và làm lại từ bước 1.
- Nếu đề bắt đầu bằng "user đã tồn tại", hãy kiểm tra xem cert đã có sẵn ở `/root` chưa trước khi sinh key mới, tiết kiệm được vài phút.

> [!IMPORTANT]
> Quy tắc một dòng để nhớ: CSR lo phần **danh tính** (bạn là ai), RBAC lo phần **quyền** (bạn được làm gì), kubeconfig chỉ là **cách đóng gói** cả hai để kubectl dùng. Ba thứ độc lập, hỏng cái nào sửa cái đó.

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

Tóm gọn bài này trong sáu dòng:

1. `openssl genrsa` tạo key, `openssl req -new -subj "/CN=user/O=group"` tạo CSR.
2. Base64 nội dung CSR (bỏ newline) và đặt vào `spec.request` của object `CertificateSigningRequest`.
3. `signerName: kubernetes.io/kube-apiserver-client` với `usages: [client auth]` cho user thường.
4. `kubectl certificate approve` rồi lấy cert từ `status.certificate`.
5. Dựng kubeconfig bằng bốn lệnh `kubectl config set-cluster`, `set-credentials`, `set-context`, `use-context`, luôn kèm `--embed-certs=true`.
6. Test bằng `auth can-i`; 401 là lỗi danh tính, 403 là lỗi RBAC.

Ôn nhanh bằng cách điền vào chỗ trống:

- Signer dùng để ký client certificate cho user thường là {{c1::kubernetes.io/kube-apiserver-client}}.
- Nội dung file `.csr` sau khi mã hóa base64 được đặt vào trường {{c2::spec.request}}.
- Certificate đã ký nằm ở {{c3::status.certificate}} dưới dạng base64.
- Trong subject, giá trị {{c4::CN}} trở thành username và giá trị {{c5::O}} trở thành group.
- Lệnh phê duyệt CSR là {{c6::kubectl certificate approve}} theo sau là tên CSR.
- Kubeconfig mặc định nằm ở `~/.kube/config` và có thể ghi đè bằng biến môi trường {{c7::KUBECONFIG}}.

Deck ôn tập cho bài này:

```flashcard
[
  { "front": "signerName nào dùng để cấp client certificate cho user thường?", "back": "kubernetes.io/kube-apiserver-client" },
  { "front": "Trường nào trong CSR chứa nội dung file .csr?", "back": "spec.request, chứa PEM CSR đã mã hóa base64 và đã bỏ newline." },
  { "front": "Lệnh phê duyệt một CSR?", "back": "kubectl certificate approve <tên-csr>" },
  { "front": "Lấy certificate đã ký từ CSR ra file?", "back": "kubectl get csr <tên> -o jsonpath='{.status.certificate}' | base64 -d > user.crt" },
  { "front": "Với subject /CN=alice/O=dev, CN và O map thành gì trong Kubernetes?", "back": "CN là username, O là group dùng để gán RBAC." },
  { "front": "Tạo private key 2048 bit bằng openssl?", "back": "openssl genrsa -out user.key 2048" },
  { "front": "Tạo file CSR bằng openssl với user và group cho trước?", "back": "openssl req -new -key user.key -out user.csr -subj \"/CN=user/O=group\"" },
  { "front": "Cờ nào nhúng certificate trực tiếp vào kubeconfig thay vì trỏ đường dẫn file?", "back": "--embed-certs=true" },
  { "front": "Một context trong kubeconfig gồm những thành phần nào?", "back": "cluster, user và namespace (tùy chọn)." },
  { "front": "Kubeconfig mặc định nằm ở đâu và biến môi trường nào ghi đè nó?", "back": "~/.kube/config, ghi đè bằng biến KUBECONFIG." },
  { "front": "Kiểm tra user alice có quyền get pods không?", "back": "kubectl auth can-i get pods --as=alice" },
  { "front": "Có sửa được một CSR đã tạo không?", "back": "Không, CSR là immutable, phải delete rồi tạo lại." },
  { "front": "usages nào bắt buộc phải có với signer kube-apiserver-client?", "back": "client auth" },
  { "front": "Lỗi 403 khác lỗi 401 thế nào khi test kubeconfig?", "back": "403 là đã xác thực nhưng thiếu RBAC, 401 là certificate hoặc danh tính sai." }
]
```

**Bài tiếp theo:** Checkpoint của Module 7. Bạn sẽ gặp một đề tổng hợp ngắn trộn cả ba mảng: tạo Role/RoleBinding đúng phạm vi, gán ServiceAccount cho workload, và bài CSR kèm kubeconfig như vừa luyện. Hãy chuẩn bị sẵn alias `k` và thói quen chạy `kubectl auth can-i --as` để tự kiểm tra trước khi nộp.
