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 pod, nội dung token, và kết quả gọi API

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
/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 podssecrets 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.

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 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.