Authentication và Đường Vào API Server

K
Kai··5 min read

Part X lo lưu lượng mạng. Part XI chuyển sang ai được làm gì trên cụm, và điểm bắt đầu là đường vào API server. Mỗi lệnh kubectl get, mỗi lần controller gọi API, đều là một request HTTPS, và API server xử lý nó qua ba chặng theo thứ tự:

   request (kubectl, controller, pod...)
        │
        ▼
   ┌─────────────────┐   bạn là AI?        401 nếu mọi authenticator từ chối
   │ Authentication  │ ──────────────────►  (gắn username + groups vào request)
   └─────────────────┘
        │
        ▼
   ┌─────────────────┐   bạn được làm gì?   403 nếu không có quyền
   │ Authorization   │ ──────────────────►  (Node, RBAC — Bài 52)
   └─────────────────┘
        │
        ▼
   ┌─────────────────┐   request có hợp lệ / cần sửa?
   │ Admission       │ ──────────────────►  (NodeRestriction, PSA — Bài 54)
   └─────────────────┘
        │
        ▼   ghi vào etcd / trả dữ liệu

Bài này dừng ở chặng đầu: API server nhận ra bạn là ai. Chặng này chỉ gắn usernamegroups vào request rồi chuyển tiếp — nó không quyết định bạn được làm gì, đó là việc của authorization ở bài sau.

Hai loại danh tính

Kubernetes phân biệt hai loại người gọi. Normal user (người dùng thường) — như bạn qua kubectl — không phải object trong cluster; không có kind: User nào để kubectl get. Danh tính của họ nằm ngoài cluster, trong một client certificate hoặc token, và API server chỉ tin vào CA đã ký. ServiceAccount thì ngược lại, là object trong cluster, thuộc một namespace, dành cho tiến trình chạy trong cluster (pod, controller) gọi API.

Cờ authentication trên API server của cụm (đặt từ Bài 7) cho thấy những cách nó chấp nhận:

ssh controller-0 'grep -oE "\-\-(client-ca-file|service-account-key-file|anonymous-auth)[^ ]*" \
  /etc/systemd/system/kube-apiserver.service'
--client-ca-file=/var/lib/kubernetes/ca.pem
--service-account-key-file=/var/lib/kubernetes/service-account.pem

--client-ca-file bật xác thực bằng client certificate (CA này ký từ Bài 4); --service-account-key-file bật xác thực token ServiceAccount (cặp khóa từ Bài 5).

Client certificate: cái ta đang dùng

admin.kubeconfig chứa một client certificate. Xem nó mã hóa danh tính ra sao:

openssl x509 -in admin.pem -noout -subject
subject=C = VN, O = system:masters, OU = k8s-scratch, CN = admin

API server đọc hai trường: CN (admin) thành username, O (system:masters) thành group. Hỏi API server xem nó thấy ta là ai:

kubectl auth whoami
ATTRIBUTE                                           VALUE
Username                                            admin
Groups                                              [system:masters system:authenticated]
Extra: authentication.kubernetes.io/credential-id   [X509SHA256=69463ed56ced...]

Username admin đúng từ CN, group system:masters đúng từ O, và system:authenticated được API server tự thêm cho mọi request đã xác thực. Group system:masters là lý do admin toàn quyền — có một ClusterRoleBinding sẵn gắn nó vào quyền cao nhất:

kubectl get clusterrolebinding cluster-admin \
  -o jsonpath='{.roleRef.kind}/{.roleRef.name} <- {.subjects[0].kind}/{.subjects[0].name}'
ClusterRole/cluster-admin <- Group/system:masters

Đây là điểm cần nhớ: bản thân certificate không cấp quyền gì, nó chỉ chứng minh danh tính. Quyền đến từ binding ở chặng authorization. system:masters toàn quyền chỉ vì có sẵn binding đó — và cũng vì vậy, ký một cert với O=system:masters là trao toàn quyền cả cụm, nên CA client phải giữ chặt.

ServiceAccount token

Loại danh tính thứ hai dành cho tiến trình trong cluster. Tạo một ServiceAccount rồi xin token cho nó:

kubectl create namespace authn-demo
kubectl -n authn-demo create serviceaccount bot
TOK=$(kubectl -n authn-demo create token bot --duration=10m)

Token này là một JWT. Dùng nó để hỏi API server (kubeconfig rỗng để token không bị client cert của admin lấn át):

kubectl --kubeconfig=/dev/null --token="$TOK" \
  --server=https://10.0.1.10:6443 --certificate-authority=ca.pem auth whoami
Username   system:serviceaccount:authn-demo:bot
Groups     [system:serviceaccounts system:serviceaccounts:authn-demo system:authenticated]

Username theo khuôn system:serviceaccount:<namespace>:<tên>, và token tự xếp vào hai group theo namespace. Giải phần payload của JWT (phần giữa, base64) thấy API server đặt gì vào:

echo "$TOK" | cut -d. -f2 | base64 -d | python3 -m json.tool
{
  "aud": ["https://10.0.1.10:6443"],
  "exp": 1779579030,
  "iss": "https://10.0.1.10:6443",
  "sub": "system:serviceaccount:authn-demo:bot",
  "kubernetes.io": {"namespace": "authn-demo", "serviceaccount": {"name": "bot", "uid": "..."}}
}

sub là danh tính, issaud ràng token vào đúng API server này (token phát cho server này không xài được ở nơi khác), exp là hạn, token có thời hạn rõ ràng thay vì sống mãi. Đây là token bound hiện đại, khác token vĩnh viễn kiểu cũ; Bài 53 đào sâu cơ chế phát token này và cách pod nhận token tự động.

Request ẩn danh

Nếu một request không mang credential nào, API server không từ chối ngay ở authentication — nó gán danh tính ẩn danh rồi để authorization quyết:

curl -s --cacert ca.pem https://10.0.1.10:6443/api/v1/namespaces/default/pods
{
  "kind": "Status",
  "status": "Failure",
  "message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" ...",
  "reason": "Forbidden",
  "code": 403
}

Request được xác thực thành system:anonymous (group system:unauthenticated), qua authentication trót lọt, rồi bị authorization chặn ở 403 Forbidden. Phân biệt hai mã: 401 Unauthorized nghĩa là không authenticator nào nhận ra credential; 403 Forbidden nghĩa là đã biết bạn là ai nhưng bạn không có quyền. Ở đây là 403 vì RBAC không cấp gì cho ẩn danh. Muốn chặn ngay từ authentication thì đặt --anonymous-auth=false trên API server.

🧹 Dọn dẹp

kubectl delete namespace authn-demo

Bài này chỉ tạo một namespace với một ServiceAccount, xóa là sạch. Không đụng cấu hình API server. Lệnh dùng trong bài ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 51-authentication.

Tổng kết

Mỗi request tới API server đi qua ba chặng — authentication, authorization, admission — và bài này soi chặng đầu, nơi API server gắn username + groups cho request. Cụm tự dựng nhận ba kiểu danh tính: client certificate (CN thành username, O thành group — admin/system:masters, cấu hình qua --client-ca-file từ Bài 4), ServiceAccount token (JWT với sub/aud/exp, username system:serviceaccount:ns:tên, qua --service-account-key-file từ Bài 5), và request ẩn danh (system:anonymous, qua authentication nhưng bị authorization chặn 403). Điểm cốt lõi: authentication chỉ xác định bạn là ai, không cấp quyền — certificate hay token tự nó không mở được gì, quyền đến từ binding ở chặng sau. Phân biệt 401 (không nhận ra credential) với 403 (biết bạn là ai nhưng không đủ quyền).

Chặng tiếp theo trả lời câu hỏi còn lại: biết bạn là ai rồi, bạn được làm gì? Bài 52 đào vào RBAC — Role, ClusterRole, và các binding biến username/group thành quyền cụ thể trên từng resource.