Trong đầu ai cũng có một thang bậc "mức độ an toàn" của quyền: xoá là nguy hiểm, sửa đỡ hơn, còn "chỉ đọc" là an toàn nhất có thể — cho đại trà cũng chẳng sao. Cái thang đó sai ở một chỗ chết người: sự nguy hiểm của một quyền không nằm ở động từ, mà ở chỗ nó với tới đâu. Quyền "chỉ đọc" một tủ hồ sơ nghe vô hại, cho tới khi tủ đó chứa chìa khoá của mọi tủ khác. Bài này cấp quyền từng lớp cho một ServiceAccount và đo chính xác mỗi quyền mở ra được gì — kể cả quyền tôi tưởng là hiền nhất.
Bốn object, hai cặp
Role quyền, phạm vi MỘT namespace
ClusterRole quyền, phạm vi TOÀN CỤM
RoleBinding gắn quyền cho ai đó, trong một namespace
ClusterRoleBinding gắn quyền cho ai đó, toàn cụm
Role và ClusterRole chỉ mô tả quyền — chúng không cấp cho ai cả. Việc cấp là của Binding. Và có một tổ hợp hay dùng: ClusterRole gắn bằng RoleBinding. Khi ấy quyền được định nghĩa một lần ở cấp cụm nhưng chỉ có hiệu lực trong một namespace — cách đúng để tái sử dụng định nghĩa quyền cho nhiều đội.
Đo từng lớp
ServiceAccount r/ung, chưa cấp gì:
list nodes -> no
Cấp Role "doc-pod" (get,list,watch trên pods) trong namespace r:
ns=r get pods |
yes |
ns=r list pods |
yes |
ns=r create pods |
no |
ns=r delete pods |
no |
ns=r get secrets |
no |
ns=r get deployments |
no |
ns=default get pods |
no |
ns=kube-system get pods |
no |
Phạm vi rất chặt: đúng động từ đã khai, đúng tài nguyên đã khai, đúng namespace chứa RoleBinding. Đổi sang ClusterRole cùng nội dung, gắn bằng ClusterRoleBinding:
list pods ở r, default, kube-system -> yes / yes / yes
Mọi namespace, kể cả kube-system. Đây là lý do ClusterRoleBinding phải được xem xét kỹ hơn nhiều: nó không có ranh giới nào.
Quyền đọc Secret là quyền đọc mật khẩu
Cấp Role "doc-secret" (get,list trên secrets), rồi đọc thử:
get secrets -> yes
giá trị đọc được: SieuBiMat123
Base64 không phải mã hoá (phần 61). Cấp "chỉ đọc" Secret là cấp quyền đọc mọi mật khẩu, khoá API và chứng chỉ trong namespace đó. "Chỉ đọc" nghe như quyền an toàn nhất có thể. Với secrets thì không.
Cách xử lý: đừng cấp secrets ở mức tài nguyên. Cấp theo tên cụ thể:
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["cau-hinh-cua-toi"]
verbs: ["get"]
resourceNames chỉ hoạt động với get, update, delete — không với list. Đó là chủ ý: list trả về mọi thứ, nên không lọc theo tên được.
Ba quyền tương đương quyền quản trị
create pods — tạo pod với hostPath: / và hostNetwork: true là có quyền root trên node đó (phần 64 đã nêu về DaemonSet). Từ đó đọc được /var/lib/kubelet và mọi Secret đang gắn trên node.
get secrets — như đo ở trên. Và trong đó thường có token của ServiceAccount khác, tức là leo thang tiếp.
create rolebindings — tự cấp thêm quyền cho chính mình. Kubernetes có chặn một phần: bạn không thể tạo RoleBinding cấp quyền cao hơn quyền mình đang có, trừ khi có động từ escalate. Nhưng nếu bạn có create rolebindings và một ClusterRole rộng nào đó, đường leo thang vẫn mở.
Cả ba đều nghe vô hại trong một bản kê YAML. Cả ba đều là đường leo thang quyền trong thực tế.
kubectl auth can-i là công cụ đúng
kubectl auth can-i <verb> <resource> \
--as=system:serviceaccount:<ns>:<sa> --namespace=<ns>
Một lệnh, trả lời yes hoặc no. Đây là cách duy nhất kiểm được quyền thực tế thay vì đọc YAML rồi đoán — vì quyền là hợp của mọi Role và ClusterRole được gắn qua mọi Binding, và với một ServiceAccount đã tồn tại lâu thì danh sách đó không ai nhớ hết. Xem toàn bộ:
kubectl auth can-i --list --as=system:serviceaccount:r:ung --namespace=r
Ba lỗi hay gặp
Dùng cluster-admin cho tiện. Đây là ClusterRole cho phép mọi thứ. Gắn nó cho một ServiceAccount của ứng dụng nghĩa là một lỗ hổng trong ứng dụng đó thành quyền toàn cụm.
verbs: ["*"] hoặc resources: ["*"]. Dấu sao trong RBAC bao gồm cả những tài nguyên chưa tồn tại — cài một toán tử mới là nó tự có quyền trên CRD của toán tử đó.
Quên ServiceAccount mặc định. 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 đó.
spec:
automountServiceAccountToken: false
Với pod không gọi API Kubernetes — tức phần lớn ứng dụng — dòng này nên có. Không có token gắn vào thì không có gì để đánh cắp.
Nhóm mặc định đáng biết
system:authenticated mọi người đã xác thực
system:serviceaccounts mọi ServiceAccount
system:masters bỏ qua RBAC hoàn toàn
system:masters là nhóm mà chứng chỉ quản trị của kubeadm thuộc về. Thành viên nhóm này không đi qua RBAC — không Role nào giới hạn được, và không cách nào thu hồi ngoài việc xoay chứng chỉ CA. Đó là lý do tệp kubeconfig của quản trị viên phải được coi như khoá gốc của cụm.
Mẫu số chung: đánh giá một quyền theo tầm-với, không theo động từ
Bài này gói hai luật của nguyên tắc đặc-quyền-tối-thiểu, đúng ở mọi hệ có phân quyền. Thứ nhất: sự nguy hiểm của một quyền nằm ở bao đóng bắc cầu của nó — nó với tới cái gì, rồi cái đó lại mở ra cái gì — chứ không ở chỗ động từ nghe hiền hay dữ. "Chỉ đọc" Secret là chiếm trọn namespace vì thứ nó đọc được (mật khẩu, token SA khác) lại mở tiếp cánh cửa sau. Cùng cái bẫy khắp nơi: một OAuth scope "chỉ đọc email" thực chất là chiếm tài khoản, vì email = link đặt lại mật khẩu; một GRANT SELECT trên một bảng chứa khoá API của người khác; quyền đọc /etc/shadow; một tài khoản "chỉ đọc" trên bản sao CSDL nhưng bản sao có bảng credentials. Least-privilege không phải là "chọn động từ nhẹ nhất" mà là giới hạn trên tập những gì có thể với tới — và muốn biết tập đó thì phải đi lần bao đóng, không đọc mỗi cái tên quyền.
Thứ hai: quyền cấp theo mẫu (*) âm thầm nới rộng khi hệ thống lớn lên. resources: ["*"] bao cả CRD của toán tử bạn cài ngày mai; cùng hình dạng với IAM Action: "s3:*" tự ôm luôn các hành động AWS thêm sau này, với GRANT ALL ON schema.* phủ cả bảng tạo về sau, với CORS *, với một dải phiên bản phụ thuộc ^1.0 kéo về các minor tương lai. Với bất cứ thứ gì nhạy cảm về bảo mật: liệt kê tường minh thay vì dùng dấu sao, vì dấu sao là một tấm séc khống cho tương lai bạn chưa thấy.
Mẫu số chung: cấp quyền theo cái nó với tới, không theo cái tên nghe êm; và liệt kê cụ thể thay vì * cho mọi quyền nhạy cảm — rồi kiểm bằng công cụ hỏi thẳng hệ ("ai thực sự làm được gì") thay vì đọc chính sách rồi tự suy, vì quyền thực tế là hợp của mọi thứ được gắn, mà cái đó không ai nhớ hết.
Muốn tự soi, tìm những gì được gắn cluster-admin trong cụm của bạn:
kubectl get clusterrolebindings -o json | python3 -c '
import sys,json
for b in json.load(sys.stdin)["items"]:
if b["roleRef"]["name"]=="cluster-admin":
for s in b.get("subjects") or []:
print(b["metadata"]["name"], "->", s["kind"], s.get("namespace",""), s["name"])
'
Ngoài system:masters và vài thành phần lõi, mỗi dòng còn lại là một danh tính có toàn quyền trên cụm — và danh sách đó thường dài hơn mọi người nghĩ.
Phần sau đo ServiceAccount và token: thứ pod dùng để tự xưng danh với API server.