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

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

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

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

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:

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

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:

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:

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

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

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

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.

Xem đáp án bài 1
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.

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.

Xem đáp án bài 2
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:

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

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

Cho file /root/broken.kubeconfig:

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.

Xem đáp án bài 3

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:

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.

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:

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