PKI và TLS: Vì Sao Cluster Cần Nhiều Certificate Đến Vậy
Cuối Bài 1 ta để lại một câu hỏi: khắp sơ đồ kiến trúc, mỗi mũi tên giữa hai thành phần đều ngầm hỏi hai điều — kẻ ở đầu kia có đúng là ai nó nói không, và nó có quyền làm việc này không. Câu trả lời nằm ở một hệ thống PKI (Public Key Infrastructure) mà ta phải tự dựng. Bài này là lý thuyết, nhưng là lý thuyết bạn sẽ gõ thành lệnh thật ở Bài 4, nên nắm chắc bây giờ sẽ đỡ bối rối về sau rất nhiều.
TLS một chiều và mTLS hai chiều
Khi nghĩ tới TLS, ta thường hình dung HTTPS của web: trình duyệt kiểm tra certificate của server để chắc đang nói chuyện với đúng ngan-hang.com. Đó là TLS một chiều, chỉ client kiểm tra server. Server không quan tâm client là ai, vì ai cũng có thể vào xem web.
Trong Kubernetes thì chiều ngược lại quan trọng hơn. Khi kubelet gọi api-server, api-server phải biết chính xác kẻ gọi là ai, vì danh tính đó quyết định nó được làm gì. Nên Kubernetes dùng mTLS (mutual TLS), tức TLS hai chiều: cả hai phía cùng trình certificate và cùng kiểm tra nhau.
TLS một chiều (web thường) mTLS (trong Kubernetes)
──────────────────────── ───────────────────────
client ──► kiểm tra ──► server client ◄──► cùng kiểm tra ◄──► server
"server có đúng không?" "server đúng không?" VÀ
client là ai? — kệ "client là AI?" — quan trọng nhất
Một điểm cần nhớ cho cả bài: trong Kubernetes, certificate không chỉ để mã hóa mà còn là giấy tờ tùy thân. api-server không có một bảng người dùng kèm mật khẩu; thay vào đó, khi một kết nối mTLS thành công, nó đọc danh tính ngay từ trong certificate của kẻ gọi. Phần sau ta sẽ xem danh tính đó nằm ở đâu trong cert.
CA ký cho certificate nghĩa là gì
Một Certificate Authority (CA) là một cặp khóa (private key cộng certificate) đóng vai công chứng viên. Quy trình:
1. Mỗi thành phần (vd kubelet) tự sinh một cặp khóa: private key + public key.
2. Nó tạo một "đơn xin ký" (CSR) chứa public key + danh tính nó khai (CN, O...).
3. CA dùng private key của CA để KÝ, tạo ra certificate.
4. Certificate đó nói: "CA bảo đảm public key này thuộc về danh tính kia."
Niềm tin hoạt động như sau: nếu hai bên cùng tin một CA, thì khi A trình certificate do CA đó ký, B chỉ cần kiểm chữ ký đó có đúng của CA mình tin không. Đúng thì B tin danh tính trong cert, mà không cần biết trước A. CA là gốc của niềm tin; ai cầm private key của CA thì ký được cert giả mạo bất kỳ ai, nên private key của CA là thứ phải bảo vệ kỹ nhất.
Vì sao tới ba CA, không phải một
Về lý thuyết, một CA ký hết mọi thứ vẫn chạy được. Nhưng Kubernetes, và ta trong series này, tách thành ba CA độc lập để cô lập phạm vi tin cậy:
┌─ Kubernetes CA ──────────┐ ký cho mọi thứ quanh api-server:
│ "kubernetes-ca" │ apiserver, kubelet, controller-mgr,
└──────────────────────────┘ scheduler, kube-proxy, admin...
┌─ etcd CA ────────────────┐ ký RIÊNG cho thế giới etcd:
│ "etcd-ca" │ etcd server, etcd peer, và client
└──────────────────────────┘ của etcd (chính là api-server)
┌─ front-proxy CA ─────────┐ ký cho cơ chế aggregation layer
│ "front-proxy-ca" │ (extension API server)
└──────────────────────────┘
etcd CA tách riêng vì etcd giữ toàn bộ trạng thái cluster, kể cả Secret; ai nói chuyện được với etcd là chạm tới mọi thứ. Tách CA riêng nghĩa là dù CA chính của Kubernetes có lộ, kẻ tấn công vẫn chưa ký được cert để truy cập thẳng etcd. front-proxy CA tách ra cũng cùng lý do: nó phục vụ một cơ chế hẹp (aggregation, khi bạn mở rộng API), không nên dùng chung gốc tin cậy với phần còn lại.
Trường CN và O: danh tính mà cluster đọc được
Trong một certificate X.509 có hai trường thuộc phần Subject mà Kubernetes dùng làm danh tính:
- CN (Common Name) — tên riêng.
- O (Organization) — tổ chức, có thể có nhiều O.
Kubernetes ánh xạ hai trường này thành danh tính RBAC một cách trực tiếp:
CN trong certificate ──► tên USER mà api-server thấy
O trong certificate ──► GROUP mà user đó thuộc về
Không có bảng người dùng nào cả. Khi bạn ký một certificate với CN=jane, O=developers, thì bất kỳ ai cầm cert đó, khi gọi api-server, chính là user jane thuộc group developers, và RBAC phân quyền dựa trên đúng hai thứ đó. Cấp cert tức là cấp danh tính.
Hệ quả là có một loạt CN/O đặc biệt mà Kubernetes hiểu sẵn. Ta phải đặt đúng từng ký tự khi tạo cert ở Bài 4, nếu sai thì cluster sẽ từ chối:
| Thành phần | CN (user) | O (group) | Ý nghĩa |
|---|---|---|---|
| admin (bạn) | admin |
system:masters |
group siêu quyền, bỏ qua mọi kiểm tra RBAC |
| kubelet mỗi node | system:node:<tên-node> |
system:nodes |
kích hoạt chế độ phân quyền Node riêng |
| controller-manager | system:kube-controller-manager |
— | danh tính có RBAC dựng sẵn |
| scheduler | system:kube-scheduler |
— | danh tính có RBAC dựng sẵn |
| kube-proxy | system:kube-proxy |
— | danh tính có RBAC dựng sẵn |
Hai dòng đầu cần lưu ý. O=system:masters cho phép làm mọi thứ qua api-server mà không qua RBAC, nên cert admin phải được bảo vệ cẩn thận. Còn CN=system:node:worker-0, O=system:nodes cho kubelet không tùy tiện: prefix system:node: bật một bộ phân quyền tên là Node authorizer, giới hạn mỗi kubelet chỉ chạm được tới pod và secret thuộc đúng node của nó, để một worker bị chiếm không lấy được secret của node khác. Tên sau prefix phải khớp tên node, nên ở Bài 4 ta ký một cert kubelet riêng cho từng worker.
Toàn cảnh: ai trình cert gì cho ai
Gom lại, đây là toàn bộ certificate ta sẽ tự tạo, xếp theo cuộc hội thoại mà nó phục vụ:
┌──────────────── ký bởi Kubernetes CA ────────────────┐
│ │
│ api-server ──server cert "kube-apiserver"──► (mọi client kiểm tra)
│ api-server ──client cert──► kubelet (để gọi xuống kubelet: logs, exec)
│ kubelet ──client cert "system:node:X" / O=system:nodes──► api-server
│ controller-mgr ─client "system:kube-controller-manager"──► api-server
│ scheduler ──client "system:kube-scheduler"──► api-server
│ kube-proxy ──client "system:kube-proxy"──► api-server
│ admin (bạn) ──client "admin" / O=system:masters──► api-server
└───────────────────────────────────────────────────────┘
┌──────────────── ký bởi etcd CA ──────────────────────┐
│ etcd ──server cert──► (api-server kiểm tra) │
│ etcd ◄─peer cert─► etcd (3 node nói chuyện với nhau)│
│ api-server ──client cert──► etcd (để đọc/ghi state) │
└───────────────────────────────────────────────────────┘
┌──────────────── ký bởi front-proxy CA ───────────────┐
│ aggregation layer (dùng sau, khi mở rộng API) │
└───────────────────────────────────────────────────────┘
Đếm sơ cũng thấy số lượng không nhỏ: mỗi mũi tên là một cặp khóa cộng một cert phải tạo, và với 3 controller cùng 2 worker thì nhiều cái phải nhân lên theo số máy. Đây là phần kubeadm làm gọn trong một lệnh và giấu đi; còn ta sẽ làm tay từng cái ở Bài 4 để thấy rõ từng danh tính.
SAN: vì sao server cert phải liệt kê đủ địa chỉ
Một chi tiết kỹ thuật hay gây lỗi khó hiểu: với server certificate (như của api-server, etcd), client không kiểm CN mà kiểm trường SAN (Subject Alternative Name). SAN liệt kê mọi tên và IP mà server này có thể được gọi tới. Nếu client gọi qua một địa chỉ không nằm trong SAN, bắt tay TLS sẽ thất bại.
Với api-server, SAN phải gồm: IP của từng controller, địa chỉ load balancer (vì client gọi qua nó), các tên DNS nội bộ như kubernetes.default.svc.cluster.local, và ClusterIP đầu tiên của dải Service (vì các pod trong cluster gọi api-server qua một Service ảo tên kubernetes). Bỏ sót một mục thì về sau sẽ gặp lỗi x509: certificate is valid for ... not ..., một trong những lỗi tốn thời gian nhất khi dựng tay. Ta sẽ liệt kê đầy đủ ngay từ Bài 4 để tránh.
api-server server cert, SAN phải có:
├── 127.0.0.1, IP từng controller (10.x.x.x ×3)
├── IP của load balancer
├── kubernetes, kubernetes.default,
│ kubernetes.default.svc, ...svc.cluster.local
└── ClusterIP đầu dải Service (vd 10.32.0.1)
Một ngoại lệ: cặp khóa ServiceAccount không phải certificate
Cuối cùng là một thứ dễ nhầm ở Bài 4. Để cấp token cho ServiceAccount (danh tính mà pod dùng để gọi api-server), Kubernetes cần một cặp khóa để ký và kiểm token, gọi là sa.key (private) và sa.pub (public):
sa.key (private) ──► controller-manager dùng để KÝ token cho ServiceAccount
sa.pub (public) ──► api-server dùng để KIỂM token đó khi pod gọi tới
Khác biệt là đây là một cặp khóa trần, không có CA nào ký, không có CN/O, không phải X.509. Nó chỉ là một cặp khóa bất đối xứng để ký và xác minh chữ ký lên token JWT. Lý do nó tồn tại riêng: token của ServiceAccount được tạo bên trong cluster với số lượng lớn và vòng đời ngắn, nên một cơ chế ký token nhẹ hợp lý hơn là phát một X.509 cho mỗi cái. Ta vẫn tạo nó ở Bài 4 cùng đám cert, chỉ cần nhớ nó thuộc loại khác.
Tổng kết
- Kubernetes dùng mTLS hai chiều; certificate vừa để mã hóa vừa là giấy tờ tùy thân.
- Ba CA (Kubernetes, etcd, front-proxy) để cô lập phạm vi tin cậy, đặc biệt là khóa chặt cửa vào etcd.
- CN ánh xạ thành user, O ánh xạ thành group; một số CN/O là đặc biệt (
system:masters,system:node:<node>cùngsystem:nodes...) và phải đặt đúng từng ký tự. - Server cert cần SAN liệt kê đủ mọi địa chỉ server có thể bị gọi tới, kể cả load balancer và ClusterIP của Service
kubernetes. - Cặp khóa ServiceAccount (
sa.key/sa.pub) là ngoại lệ, không phải certificate.
Đó là phần lý thuyết nền. Từ bài sau ta rời lý thuyết và bắt đầu chạm tay vào hạ tầng: Bài 3 dựng sáu máy EC2, chuẩn bị hệ điều hành (hostname, kernel module, sysctl, tắt swap) và cài bộ công cụ cần dùng, để tới Bài 4 có thể ngồi xuống ký các certificate vừa liệt kê ở trên.