Phần trước đo RBAC — ai được làm gì. Bài này đo nửa còn lại: pod tự xưng danh với API server bằng cách nào.
Ba tệp gắn vào mọi pod
/var/run/secrets/kubernetes.io/serviceaccount/
namespace r
ca.crt 1.107 byte
token 1.121 byte
namespace để pod biết nó đang ở đâu. ca.crt để tin chứng chỉ của API server. token để tự xưng danh.
Và cấu trúc thư mục vẫn là liên kết mềm qua ..data — giống hệt ConfigMap (phần 61). Nghĩa là token được làm mới tại chỗ, không cần khởi động lại pod.
Đó là thay đổi lớn từ Kubernetes 1.24: token không còn là một Secret sống mãi mà là token ngắn hạn do kubelet xin và tự gia hạn.
Bên trong token
sub: system:serviceaccount:r:ung
aud: https://kubernetes.default.svc.cluster.local
hạn dùng: 8.760 giờ (một năm)
gắn với pod: sa1
gắn với node: sc-worker2
Hai trường cuối là điểm quan trọng nhất.
Token gắn với pod cụ thể. Pod bị xoá là token hết hiệu lực ngay, không đợi hết hạn. Kẻ lấy được token của một pod đã chết không dùng được gì.
Với token kiểu Secret cũ (trước 1.24), token sống mãi và dùng được ở bất cứ đâu — lấy được một lần là dùng được vĩnh viễn.
aud giới hạn token chỉ dùng được với API server, không dùng để xưng danh với dịch vụ khác. Bạn xin token cho aud khác nếu cần liên thông với hệ thống ngoài:
kubectl create token ung --audience=https://vault.cong-ty.vn
Đó là nền của việc xác thực không cần mật khẩu giữa Kubernetes và Vault, AWS IAM, hay bất kỳ hệ thống nào tin OIDC của cụm.
Dùng token gọi thẳng API server
Từ trong pod:
T=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
C=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
curl --cacert $C -H "Authorization: Bearer $T" \
https://kubernetes.default.svc/api/v1/namespaces/r/pods
| Đường dẫn | Mã |
|---|---|
/api/v1/namespaces/r/pods |
200 |
/api/v1/namespaces/r/secrets |
200 |
/api/v1/nodes |
403 |
/api/v1/namespaces/default/pods |
403 |
| không có token | 403 |
Kết quả khớp chính xác RBAC đã cấp ở phần 72: Role cho pods và secrets trong namespace r, không gì khác.
Điều này làm rõ ranh giới: token chỉ là cách tự xưng danh; quyền vẫn do RBAC quyết định. Có token không có nghĩa có quyền.
Và kubernetes.default.svc là tên DNS luôn có trong mọi cụm, trỏ tới API server — thứ mà mọi thư viện client Kubernetes dùng khi chạy trong pod.
automountServiceAccountToken: false
ls /var/run/secrets/kubernetes.io/serviceaccount/
-> No such file or directory
Không có token thì không có gì để đánh cắp.
Với ứng dụng không gọi API Kubernetes — tức phần lớn ứng dụng — dòng này nên có. Đặt được ở hai chỗ:
# trên pod
spec:
automountServiceAccountToken: false
# hoặc trên ServiceAccount, áp cho mọi pod dùng nó
apiVersion: v1
kind: ServiceAccount
metadata: {name: ung}
automountServiceAccountToken: false
Cách thứ hai tốt hơn vì nó là mặc định an toàn: pod nào cần token thì tự khai true.
Ba lỗi hay gặp
Dùng ServiceAccount default. Pod không khai serviceAccountName sẽ dùng default của namespace. Nếu ai đó từng gắn quyền cho default, mọi pod trong namespace có quyền đó — kể cả pod chạy mã của bên thứ ba.
Mỗi ứng dụng nên có ServiceAccount riêng, kể cả khi nó không cần quyền gì.
Tạo token dài hạn cho CI/CD.
kubectl create token ci --duration=8760h
Token đó không gắn với pod nào — đo được gắn với pod: None — nên dùng được ở bất cứ đâu, và không thu hồi được trừ khi xoá ServiceAccount.
Với CI/CD hiện đại, dùng OIDC federation thay vì token tĩnh: GitHub Actions và GitLab đều xin được token ngắn hạn từ chính cụm.
Đọc token từ Secret theo thói cũ. Từ 1.24, tạo ServiceAccount không tự sinh Secret nữa — đo được namespace chỉ có đúng Secret tôi tự tạo. Mã cũ tìm Secret theo serviceAccountName sẽ không tìm thấy gì.
TokenRequest cho trường hợp đặc biệt
Cần token với aud riêng hoặc hạn riêng thì khai trong pod:
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: vault
kubelet tự xin và tự gia hạn khi còn 20% thời gian. Ứng dụng chỉ cần đọc lại tệp định kỳ — đọc một lần lúc khởi động rồi giữ mãi là lỗi phổ biến, và nó chỉ lộ ra sau khi token đầu tiên hết hạn.
Thử ba mươi giây
Tìm pod đang gắn token mà không cần:
kubectl get pods -A -o json | python3 -c '
import sys,json
for p in json.load(sys.stdin)["items"]:
s=p["spec"]
if s.get("automountServiceAccountToken") is False: continue
if s.get("serviceAccountName","default")=="default":
print(p["metadata"]["namespace"], p["metadata"]["name"])
'
Mỗi dòng là một pod dùng ServiceAccount default và có token gắn vào. Với ứng dụng không gọi API Kubernetes, đó là một khoá không dùng tới nằm trong hệ tệp của container.
Phần sau đo Pod Security Standards: ba mức, và mức chặt nhất từ chối cả một pod nginx bình thường.