Secret, Lối Vòng và Hardening

K
Kai··5 min read

Bài 31 đã giới thiệu Secret như một loại object, Bài 52 cho thấy view không đọc được nó. Bài này khép Part XI bằng hai câu hỏi còn lại: Secret nằm ở đâu và có thật sự an toàn không, và ai đọc được nó. Câu trả lời thứ hai có một lối vòng mà nhiều người không lường, nên ta dựng lại bằng tay.

Secret trong etcd: có mã hóa

Bài 5 đặt --encryption-provider-config lên API server để mã hóa Secret trước khi ghi etcd. Kiểm chứng bằng cách đọc thẳng etcd, vượt qua API server:

kubectl -n sec56 create secret generic topsecret --from-literal=password=hunter2
# đọc thẳng từ etcd trên controller-0
sudo etcdctl --cacert=/etc/etcd/etcd-ca.pem --cert=/etc/etcd/etcd.pem --key=/etc/etcd/etcd-key.pem \
  get /registry/secrets/sec56/topsecret
Tiền tố lưu trong etcd: /registry/secrets/sec56/topsecret k8s:enc:...
Có chuỗi hunter2 plaintext trong etcd không?  0

Giá trị bắt đầu bằng k8s:enc: — đánh dấu nó đã qua encryption provider, và hunter2 không xuất hiện dạng thô (grep đếm 0). Nếu Bài 5 không bật mã hóa, ai đọc được file etcd hoặc bản backup là đọc trọn Secret. Điểm cần nhớ ngược lại: base64 trong manifest Secret không phải mã hóa — data.password: aHVudGVyMg== giải ra ngay hunter2, nên manifest Secret không được đẩy lên Git như code thường.

Lối vòng: tạo được pod là đọc được Secret

RBAC chặn đọc Secret trực tiếp (Bài 52), nhưng có một đường khác. Doc nói thẳng: ai tạo được pod dùng một Secret thì đọc được giá trị Secret đó, dù không có quyền đọc Secret. Dựng lại: một ServiceAccount creator chỉ được tạo pod và đọc log, không được đọc Secret:

kind: Role
metadata: {namespace: sec56, name: pod-creator}
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["create", "get", "list"]
SA=system:serviceaccount:sec56:creator
kubectl auth can-i get secrets --as=$SA -n sec56   # no
kubectl auth can-i create pods --as=$SA -n sec56   # yes

Đọc Secret trực tiếp bằng token của creator thì bị chặn đúng như RBAC:

kubectl --token="$TOK" ... -n sec56 get secret topsecret
Error from server (Forbidden): secrets "topsecret" is forbidden:
  User "system:serviceaccount:sec56:creator" cannot get resource "secrets" ...

Nhưng creator tạo được pod. Tạo một pod đưa Secret vào biến môi trường rồi in ra log:

spec:
  restartPolicy: Never
  containers:
  - name: c
    image: busybox:1.36
    command: ["sh", "-c", "echo LEAKED=$SECRET"]
    env:
    - name: SECRET
      valueFrom: {secretKeyRef: {name: topsecret, key: password}}
kubectl --token="$TOK" ... -n sec56 apply -f leak.yaml     # pod/leak created
kubectl --token="$TOK" ... -n sec56 logs leak
LEAKED=hunter2

creator vừa đọc được hunter2 — giá trị Secret mà nó không có quyền get. Cơ chế: kubelet mới là bên nạp Secret vào pod, dùng credential của node chứ không phải của creator; RBAC của creator chỉ gác cửa tạo pod, không gác việc pod mount gì. Hệ quả thực tế: quyền tạo pod (hoặc Deployment, Job...) trong một namespace gần như tương đương quyền đọc mọi Secret trong namespace đó. Vì vậy kiểm soát ai được tạo workload trong từng namespace quan trọng ngang kiểm soát ai đọc Secret — và đây là lý do nên tách namespace theo ranh giới tin cậy thật.

Bảng hardening cụm tự dựng

Gom lại những bước siết mà Part XI và các phần trước đã chạm, đối chiếu cụm tự dựng này:

Bước siết Trong series Trạng thái cụm
TLS mọi thành phần (API, etcd, kubelet) Bài 4 đã có
Mã hóa Secret at-rest Bài 5 đã có (k8s:enc:)
--authorization-mode=Node,RBAC Bài 7 đã có
RBAC least-privilege Bài 52 mẫu có; áp cho từng workload là việc vận hành
Bound token, tắt auto-mount khi không cần Bài 53 cơ chế có; automountServiceAccountToken:false áp tùy pod
Pod Security Admission (enforce restricted) Bài 54 PSA bật sẵn; gắn nhãn namespace là việc vận hành
seccomp/AppArmor/drop capabilities Bài 55 runtime áp AppArmor mặc định; seccomp/cap khai trong securityContext
Kiểm soát quyền tạo pod (vì lối vòng Secret) bài này thuộc về thiết kế RBAC + tách namespace
--anonymous-auth=false Bài 51 mặc định còn bật; RBAC đang chặn ẩn danh
Audit logging chưa bật; sẽ làm ở Part XIII
etcd backup + xoay cert Part XIII

Cụm có các nền tảng (TLS, mã hóa, RBAC, PSA) nhưng phần lớn việc siết là vận hành liên tục: gắn nhãn PSA cho namespace, viết RBAC tối thiểu, tắt auto-mount, tách namespace theo tin cậy. Bảo mật không phải một công tắc mà là tổng của nhiều mặc định đúng.

🧹 Dọn dẹp

kubectl delete namespace sec56

Bài này chỉ tạo một namespace với Secret + SA + pod, không đụng cấu hình cụm. Lệnh dùng trong bài ở github.com/nghiadaulau/kubernetes-from-scratch, thư mục 56-secrets-hardening.

Tổng kết

Secret được mã hóa at-rest từ Bài 5 — đọc thẳng etcd thấy tiền tố k8s:enc: và không có giá trị thô, nhưng base64 trong manifest thì không phải mã hóa. Lối vòng quan trọng nhất: ai tạo được pod trong một namespace thì moi được mọi Secret trong đó, vì kubelet nạp Secret bằng credential của node chứ không kiểm RBAC của người tạo pod — ta dựng lại bằng một SA bị chặn get secrets nhưng vẫn in được hunter2 ra log qua một pod nó tự tạo. Hệ quả: quyền tạo workload xấp xỉ quyền đọc Secret, nên phải kiểm soát nó và tách namespace theo ranh giới tin cậy. Bảng hardening cho thấy cụm tự dựng đã có nền (TLS, mã hóa, RBAC, PSA) nhưng phần lớn việc siết là vận hành liên tục, còn audit logging và backup/xoay cert để dành Part XIII.

Part XI khép lại ở đây — từ một request đi vào (Bài 51), qua quyền (52), danh tính workload (53), chặn pod nguy hiểm (54), cơ chế kernel (55), tới Secret và lối vòng của nó. Part XII chuyển hướng: mở rộng chính Kubernetes — CustomResourceDefinition, admission webhook, operator — biến API server thành nền cho API của riêng bạn.

Related Posts