RBAC Deep Dive — Phân Quyền Trong Cluster
Vì sao RBAC là lớp authorization quyết định bên trong cluster
Sau khi một identity vượt qua cổng authentication (file 02), RBAC là thứ quyết định nó làm được gì. Đây là lớp phòng thủ then chốt chống lại hai mối đe dọa: privilege escalation (một identity bị lộ leo thang quyền) và blast radius (một workload bị chiếm gây thiệt hại tới đâu). RBAC thiết kế tốt biến một Pod compromise thành sự cố giới hạn; RBAC lỏng biến nó thành thảm họa toàn cluster.
Vấn đề là RBAC dễ làm sai theo hướng "quá rộng" vì quyền rộng luôn hoạt động, còn quyền hẹp đòi hỏi suy nghĩ. cluster-admin luôn chạy được; least-privilege đòi hỏi biết chính xác workload cần verb gì trên resource gì. Áp lực vận hành ("cho nó chạy đã") đẩy mọi thứ về phía rộng, và quyền dư thừa tích lũy âm thầm cho đến khi một sự cố phơi bày nó. File này trình bày cơ chế RBAC đủ sâu để bạn thiết kế hẹp một cách tự tin, và nhận diện các anti-pattern escalation phổ biến nhất.
Internal model: bốn primitive của RBAC
RBAC trên Kubernetes (và GKE) chỉ gồm bốn loại object, kết hợp theo hai trục: phạm vi (namespace vs cluster) và vai trò (định nghĩa quyền vs gán quyền).
| Định nghĩa quyền (chứa rules) | Gán quyền (binding) | |
|---|---|---|
| Namespace-scoped | Role | RoleBinding |
| Cluster-scoped | ClusterRole | ClusterRoleBinding |
Role và ClusterRole: tập hợp quyền
Một Role hoặc ClusterRole là một danh sách rules, mỗi rule là tổ hợp apiGroups × resources × verbs. RBAC là purely additive — không có "deny rule". Quyền là hợp của mọi rule áp dụng cho identity; không có cách nào "trừ" một quyền bằng RBAC (việc deny phải làm ở admission, không phải RBAC).
# Role: chỉ trong namespace 'team-a', chỉ đọc pods và logs
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-a
name: pod-reader
rules:
- apiGroups: [""] # core API group
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]Khác biệt phạm vi:
- Role chỉ chứa quyền trong một namespace cụ thể. Nó không thể cấp quyền cho cluster-scoped resource (node, PersistentVolume, namespace, CRD definition).
- ClusterRole chứa quyền có thể là cluster-scoped (node, PV, namespace) hoặc namespace-scoped resource nhưng áp dụng trên mọi namespace. ClusterRole còn dùng được cho non-resource URL (như
/healthz,/metrics).
RoleBinding và ClusterRoleBinding: gán cho ai
Binding nối một (Cluster)Role với các subject (User, Group, hoặc ServiceAccount).
- RoleBinding cấp quyền trong namespace của nó. Điểm tinh tế quan trọng: một RoleBinding có thể tham chiếu một ClusterRole — khi đó nó cấp các quyền của ClusterRole nhưng giới hạn trong namespace của RoleBinding. Đây là pattern cực kỳ hữu ích: định nghĩa quyền một lần bằng ClusterRole tái sử dụng, rồi cấp nó theo từng namespace bằng RoleBinding.
- ClusterRoleBinding cấp quyền trên toàn cluster, mọi namespace. Đây là loại binding nguy hiểm nhất — một sai lầm ở đây ảnh hưởng toàn cluster.
# RoleBinding tham chiếu một ClusterRole nhưng giới hạn trong namespace team-a
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-view
namespace: team-a
subjects:
- kind: Group
name: gke-team-a-developers@company.com # group từ Workspace
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # tái sử dụng ClusterRole 'view' có sẵn
name: view
apiGroup: rbac.authorization.k8s.ioQuy tắc bản lề về phạm vi: kind của binding quyết định phạm vi, không phải kind của role được tham chiếu. ClusterRole + RoleBinding = quyền giới hạn namespace. ClusterRole + ClusterRoleBinding = quyền toàn cluster. Nhầm hai cái này là một trong những lỗi escalation phổ biến nhất.
Aggregated ClusterRoles: cơ chế gộp quyền
Kubernetes có cơ chế aggregated ClusterRole: một ClusterRole có thể tự động gom rules từ các ClusterRole khác qua label selector. Controller theo dõi các ClusterRole mang label khớp và tự động merge rules của chúng vào ClusterRole tổng hợp.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: monitoring
aggregationRule:
clusterRoleSelectors:
- matchLabels:
rbac.example.com/aggregate-to-monitoring: "true"
rules: [] # rỗng — được điền tự động từ các ClusterRole có label trênĐây chính là cơ chế đứng sau các default ClusterRole của Kubernetes:
view: đọc hầu hết resource trong namespace, không thấy Secret (vì Secret chứa credential).edit: đọc/ghi hầu hết resource, không sửa Role/RoleBinding (để tránh tự leo thang quyền), có đọc Secret.admin: nhưeditcộng quản lý Role/RoleBinding trong namespace — quyền admin trong một namespace, không phải toàn cluster.cluster-admin: toàn quyền trên mọi thứ — superuser của cluster. Đây là role nguy hiểm nhất.
Hiểu cơ chế aggregation quan trọng vì một rủi ro tinh tế: khi bạn cài một operator/add-on tạo ClusterRole mang label aggregate (ví dụ aggregate-to-edit: "true"), nó âm thầm mở rộng quyền của role edit trên toàn cluster. Một CRD mới có thể bất ngờ trở nên ghi được bởi mọi người có edit. Khi cài third-party operator, luôn review các aggregation label của nó.
GKE predefined IAM roles và ánh xạ sang RBAC
Đây là điểm giao giữa hai cổng (file 02). GKE cung cấp các predefined IAM role cho việc truy cập cluster, và một số trong đó mang theo quyền Kubernetes RBAC tương đương:
| IAM role | Mục đích | Hệ quả RBAC |
|---|---|---|
roles/container.viewer | Xem cluster và resource read-only | Quyền đọc rộng |
roles/container.clusterViewer | Chỉ get/list cluster (kết nối) | Tối thiểu — lý tưởng để kết nối rồi RBAC chi tiết |
roles/container.developer | Thao tác Kubernetes API object trong cluster | Quyền rộng trên object — gần như edit toàn cluster |
roles/container.clusterAdmin | Quản lý cluster (tạo/xóa/cấu hình) | Quản trị vòng đời cluster |
roles/container.admin | Quản lý đầy đủ cluster và Kubernetes API object | Tương đương cluster-admin — bypass RBAC |
roles/container.hostServiceAgentUser | Cho GKE service agent cấu hình Shared VPC | Network host project |
Hai cặp dễ nhầm và đáng phân biệt rõ:
clusterViewervsviewer:clusterViewerchỉ cho phép nhìn thấy và kết nối cluster (get/list ở tầng GCP), gần như không cấp quyền RBAC nào — đây chính là role lý tưởng để cấp ở cổng IAM rồi để RBAC quyết định chi tiết.viewercấp quyền đọc rộng hơn bên trong.clusterAdminvsadmin:container.clusterAdminquản lý vòng đời cluster (tạo, xóa, nâng cấp) nhưng không nhất thiết là superuser Kubernetes object;container.adminlà cả hai — quản lý cluster lẫn toàn quyền Kubernetes object, thực chất ngangcluster-admin.
Nguyên tắc thiết kế rút ra: cấp roles/container.clusterViewer ở IAM cho người cần quyền chi tiết, rồi dùng RBAC để cấp đúng quyền theo namespace. Tránh container.developer và container.admin cho người dùng thông thường vì chúng làm RBAC trở nên vô nghĩa (quyền đến từ IAM, không qua RBAC bạn kiểm soát).
Production architecture patterns
Pattern: namespace-per-team với ClusterRole tái sử dụng
Mô hình chuẩn cho multi-team cluster: mỗi team một (hoặc vài) namespace; định nghĩa một bộ ClusterRole tái sử dụng (team-developer, team-viewer, team-deployer); rồi với mỗi team, tạo RoleBinding gắn ClusterRole đó vào group của team trong namespace của họ. Ưu điểm: quyền được định nghĩa một lần, audit tập trung; thêm team mới chỉ là tạo namespace + RoleBinding; không team nào chạm được namespace team khác.
# ClusterRole tái sử dụng: quyền của một developer trong namespace của họ
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: team-developer
rules:
- apiGroups: ["apps", ""]
resources: ["deployments", "pods", "services", "configmaps"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# KHÔNG có secrets write, KHÔNG có rolebindings — tránh tự leo thang
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-a-developers
namespace: team-a # giới hạn trong team-a
subjects:
- kind: Group
name: gke-team-a@company.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: team-developer
apiGroup: rbac.authorization.k8s.ioPattern: ServiceAccount riêng với RBAC tối thiểu cho mỗi controller
Mọi controller/operator/CI job chạy trong cluster nên có KSA riêng với RBAC đúng bằng nhu cầu — không hơn. Một CI deployer chỉ cần create/update deployments trong namespace mục tiêu, không cần đọc Secret toàn cluster. Một metrics agent chỉ cần get/list pods,nodes. Nguyên tắc: nếu một workload bị chiếm, RBAC của KSA nó dùng là trần thiệt hại — hãy giữ trần đó thấp nhất có thể.
Common mistakes / anti-patterns
1. cluster-admin cho ServiceAccount. Đây là anti-pattern nguy hiểm nhất. Một operator/CI tool được cấp cluster-admin cho tiện. Vì sao xảy ra: dễ, mọi thứ chạy ngay, không phải nghĩ. Hệ quả ở scale: KSA này thường gắn vào Pod; chiếm được Pod = cluster-admin toàn cluster = đọc mọi Secret, tạo Pod privileged, escape node. Đây là con đường escalation số một. Phòng tránh: không bao giờ bind cluster-admin cho KSA; cấp đúng verb × resource cần thiết; audit định kỳ mọi ClusterRoleBinding tới cluster-admin.
2. Wildcard * trong rules. verbs: ["*"], resources: ["*"], hoặc apiGroups: ["*"]. Vì sao xảy ra: không muốn liệt kê, "cho chắc". Hệ quả: role tự động bao gồm cả resource/verb mới (CRD cài sau, subresource nhạy cảm như pods/exec, secrets), mở rộng quyền ngoài ý định khi cluster tiến hóa. Đặc biệt nguy hiểm: * trên resource tự động bao gồm secrets và pods/exec (shell vào Pod). Phòng tránh: liệt kê tường minh verb và resource; coi wildcard như cấm kỵ trừ trường hợp thực sự là admin role có chủ đích.
3. Binding gắn group system:authenticated hoặc system:anonymous. Một ClusterRoleBinding cấp quyền cho system:authenticated cấp quyền đó cho mọi identity xác thực được — kể cả người chỉ có IAM tối thiểu. Vì sao xảy ra: copy-paste manifest, hiểu nhầm group này nghĩa "chỉ người trong team". Hệ quả: privilege mở rộng cho toàn bộ principal có thể authenticate vào project. Phòng tránh: không bao giờ bind quyền ghi/nhạy cảm cho system:authenticated; audit mọi binding tới group này.
4. ClusterRole + ClusterRoleBinding khi chỉ cần một namespace. Muốn cấp quyền cho team trong một namespace nhưng lại dùng ClusterRoleBinding (cấp toàn cluster). Vì sao xảy ra: nhầm rằng kind role quyết định phạm vi. Hệ quả: team A có quyền trên namespace của team B. Phòng tránh: nhớ quy tắc — binding kind quyết định phạm vi; dùng RoleBinding (tham chiếu ClusterRole) để giới hạn namespace.
5. Cấp pods/exec và secrets rộng rãi. pods/exec cho phép mở shell vào bất kỳ Pod nào — tương đương quyền chạy code trong workload đó; get secrets cho phép đọc mọi credential. Hai quyền này thường lọt vào role edit/developer mà không ai chú ý. Hệ quả: một "developer" có thể exec vào Pod production và đọc credential database. Phòng tránh: tách pods/exec thành quyền riêng cấp có kiểm soát; hạn chế get secrets tới đúng namespace và đúng người; ưu tiên Secret Manager + Workload Identity thay vì Kubernetes Secret cho dữ liệu nhạy cảm.
GCP-native implementation guidance
# Liệt kê mọi ClusterRoleBinding trỏ tới cluster-admin (điểm escalation số 1)
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.roleRef.name=="cluster-admin") |
.metadata.name + " -> " +
([.subjects[]? | .kind + "/" + .name] | join(", "))'
# Tìm mọi binding gắn vào system:authenticated (cờ đỏ)
kubectl get clusterrolebindings,rolebindings -A -o json | \
jq -r '.items[] | select(.subjects[]?.name=="system:authenticated") | .metadata.name'
# Kiểm chứng quyền thực tế của một ServiceAccount (least-privilege audit)
kubectl auth can-i --list \
--as=system:serviceaccount:team-a:deployer-sa -n team-a
# Một KSA có đọc được Secret toàn cluster không? (phải trả 'no')
kubectl auth can-i get secrets --all-namespaces \
--as=system:serviceaccount:team-a:deployer-saQuy trình audit RBAC tối thiểu nên chạy định kỳ: (1) liệt kê mọi binding tới cluster-admin và xác minh từng cái có lý do chính đáng; (2) tìm binding tới system:authenticated/system:anonymous; (3) tìm role chứa wildcard; (4) với mỗi KSA quan trọng, dùng auth can-i --list xác nhận quyền khớp nhu cầu. GKE Security Posture (file 10) có thể tự động hóa một phần việc này.
Operational implications
RBAC có một đặc tính vận hành dễ gây bất ngờ: nó additive và không có "deny", nên quyền chỉ có xu hướng tăng theo thời gian, không bao giờ tự giảm. Mỗi lần "cấp thêm cho nó chạy" để lại một rule; mỗi operator cài thêm mang ClusterRole của nó; mỗi aggregation label âm thầm mở rộng default role. Sau một năm, không ai biết tổng quyền thực tế là gì. Đây là lý do RBAC cần được quản lý như code (GitOps) với review bắt buộc, và audit định kỳ chủ động — vì entropy của hệ thống luôn đẩy về phía quyền rộng hơn.
Hệ quả thứ hai: vì IAM role rộng ghi đè RBAC (file 02), audit RBAC đơn thuần cho kết quả sai lệch nếu bỏ qua IAM. Một báo cáo "RBAC của chúng ta rất chặt" vô nghĩa nếu nửa tổ chức có roles/container.admin ở IAM. Audit quyền đúng nghĩa phải nhìn cả hai cổng đồng thời — đây là điểm tích hợp giữa file 02 và file này.