ClusterRoleBinding vs RoleBinding Semantics
Vì Sao Quantum Trọng
Rất nhiều security bugs trong Kubernetes deployments bắt nguồn từ MISUNDERSTANDING semantics giữa RoleBinding và ClusterRoleBinding:
- Namespace override gotcha: ClusterRole dùng ClusterRoleBinding có scope CLUSTER-WIDE. Nhưng ClusterRole bound via RoleBinding chỉ grant permissions trong namespace binding đó. Developers often assume "ClusterRole = cluster-wide" tanpa realize binding scoping override semantics này.
- Immutability constraint: Nếu mistake binding role đến subject sai, không thể edit
roleRef. Phải delete and recreate. Nếu không biết constraint này, akan waste hours debugging. - Subject matching semantics: RoleBinding can reference ClusterRole, nhưng RoleBinding itself scoped đến namespace. Hasilnya là asymmetric semantics mà confusing.
Memahami scoping, immutability, và matching logic fundamental để write secure, debuggable RBAC.
RoleBinding Structure
RoleBinding (Namespaced)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: default
name: pod-reader-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role # or ClusterRole
name: pod-reader
subjects:
- kind: User
name: alice@example.com
- kind: ServiceAccount
namespace: default
name: my-app
- kind: Group
name: developersStructure:
| Field | Scoping | Options |
|---|---|---|
namespace | Binding scoped to this namespace | Required |
roleRef.kind | Role type being bound | Role hoặc ClusterRole |
roleRef.name | Role name | Unique per kind |
subjects | Who gets these permissions | Users, Groups, SAs |
ClusterRoleBinding (Cluster-Scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: monitoring-role-binding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole # Must be ClusterRole
name: monitoring-reader
subjects:
- kind: User
name: monitoring-user@example.com
- kind: ServiceAccount
namespace: monitoring
name: prometheusKey difference:
- No
namespacefield (cluster-scoped) - Can only reference
ClusterRole(notRole)
Scope Semantics: The Critical Distinction
Role + RoleBinding = Namespace-Scoped Access
# Role (namespaced)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# RoleBinding (grants access ONLY in default namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: default
name: pod-reader-binding
roleRef:
kind: Role
name: pod-reader
subjects:
- kind: User
name: aliceResult: Alice có thể get, list pods chỉ di default namespace. Di kube-system, Alice không có thể truy cập.
ClusterRole + ClusterRoleBinding = Cluster-Wide Access
# ClusterRole (cluster-scoped)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader-global
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# ClusterRoleBinding (grants access EVERYWHERE)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pod-reader-global-binding
roleRef:
kind: ClusterRole
name: pod-reader-global
subjects:
- kind: User
name: aliceResult: Alice có thể get, list pods di tất cả namespaces.
ClusterRole + RoleBinding = Namespace-Scoped Access (Asymmetric!)
# ClusterRole (can be reused across namespaces)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader-global
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# RoleBinding in namespace production (restricts to this namespace)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: production
name: prod-pod-reader-binding
roleRef:
kind: ClusterRole # Referencing cluster-scope role
name: pod-reader-global # But binding restricts scope!
subjects:
- kind: User
name: aliceResult: Alice có thể get, list pods chỉ di production namespace, không di namespace lain.
This is THE gotcha: ClusterRole scope is OVERRIDDEN bởi RoleBinding's namespace scope. Binding type menentukan effective scope, không phải role type.
Why This Design?
Kenapa Kubernetes allow ClusterRole + RoleBinding combination?
Reusability: Sama role có thể di-bind di multiple namespaces via RoleBinding, hoặc cluster-wide via ClusterRoleBinding:
# Single ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# Bind to namespace A
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-a
name: pod-reader
roleRef:
kind: ClusterRole
name: pod-reader
subjects:
- kind: ServiceAccount
namespace: team-a
name: app
---
# Bind to namespace B (same role, different binding)
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-b
name: pod-reader
roleRef:
kind: ClusterRole
name: pod-reader
subjects:
- kind: ServiceAccount
namespace: team-b
name: appOne role, multiple namespace-scoped bindings = reusability + namespace isolation.
Subject Matching Logic
Subject Identity Resolution
Khi authorization evaluates request, API server match subjects trong binding với request identity:
Request context:
{
user: "alice@example.com",
groups: ["developers", "system:authenticated"],
serviceAccount: "system:serviceaccount:default:my-app"
}RoleBinding subjects:
subjects:
- kind: User
name: alice@example.com # Matches request.user
- kind: Group
name: developers # Matches request.groups
- kind: ServiceAccount
namespace: default
name: my-app # Expands to system:serviceaccount:default:my-appMatching algorithm:
For each subject in binding:
if subject.kind == User:
if subject.name == request.user:
MATCH
else if subject.kind == Group:
if subject.name in request.groups:
MATCH
else if subject.kind == ServiceAccount:
sa_username = "system:serviceaccount:" + subject.namespace + ":" + subject.name
if sa_username == request.user:
MATCHServiceAccount Subject Expansion
ServiceAccount subjects automatically expand đến internal username:
subjects:
- kind: ServiceAccount
namespace: monitoring
name: prometheus
# Internally becomes:
# system:serviceaccount:monitoring:prometheusNếu request từ SA token, kubelet set request.user đến expanded SA username, và matching work.
Group Subject Semantics
Group subjects matched against request.groups array:
subjects:
- kind: Group
name: developersDi-match nếu "developers" trong request.groups. Request.groups populated bởi authentication layer (OIDC, LDAP):
OIDC example:
OIDC token claims:
{
"sub": "alice@example.com",
"groups": ["developers", "platform-team"]
}
Authenticator sets request.groups = ["developers", "platform-team", "system:authenticated"]RoleBinding với subject Group: developers akan match.
Binding Immutability
Critical Constraint: roleRef Cannot Change
Sekali RoleBinding/ClusterRoleBinding created, roleRef field IMMUTABLE:
# This FAILS
kubectl patch rolebinding pod-reader-binding \
--type='json' -p='[{"op":"replace","path":"/roleRef/name","value":"deployment-reader"}]'
# Error: spec.roleRef is immutableReason: Prevent privilege escalation via binding manipulation. Kalau roleRef có thể diubah tanpa creating new binding, auditing becomes impossible.
Recovery:
# Must delete and recreate
kubectl delete rolebinding pod-reader-binding
kubectl create rolebinding pod-reader-binding \
--clusterrole=deployment-reader \
--user=aliceWhy Immutability Matters
Ngoài audit, immutability ensure semantic stability:
Original binding binds User A đến Role X (permissions: [get pods, list pods])
If roleRef was mutable:
t=0: Binding grants User A [get pods, list pods]
t=1: Admin changes roleRef to Role Y (permissions: [*])
t=2: Audit log doesn't clearly show Who changed it and When
t=3: User A now has cluster-admin, source of escalation unclear
With immutability:
t=1: Change is explicit new RoleBinding creation
t=2: Audit shows creation event with full context
t=3: Clear audit trail linking escalation to creationSubject Reference Semantics
User Subject
subjects:
- kind: User
name: alice@example.comMatched against: request.user (from authentication)
Notes:
- Name là opaque string, no namespace
- Case-sensitive
- OIDC/LDAP authenticator populates request.user
Group Subject
subjects:
- kind: Group
name: developersMatched against: request.groups array
Notes:
- Name là opaque string
- Case-sensitive
- system:* groups reserved để system use
ServiceAccount Subject
subjects:
- kind: ServiceAccount
namespace: default
name: my-appInternally expands to username: system:serviceaccount:default:my-app
Important: ServiceAccount subject requires namespace field. It's not optional.
# WRONG
subjects:
- kind: ServiceAccount
name: my-app # Missing namespace!
# Cause validation error or unexpected no-match behaviorAuthorization Decision with Multiple Bindings
OR Semantics Across Bindings
Nếu user matched multiple bindings, permissions là union:
# Binding 1
kind: RoleBinding
metadata:
namespace: default
roleRef:
kind: Role
name: pod-reader # Rules: [get pods, list pods]
subjects:
- kind: User
name: alice
---
# Binding 2
kind: RoleBinding
metadata:
namespace: default
roleRef:
kind: Role
name: deployment-reader # Rules: [get deployments, list deployments]
subjects:
- kind: User
name: aliceResult: Alice trong default namespace có thể:
get pods,list pods(từ Binding 1)get deployments,list deployments(từ Binding 2)
Cross-Namespace Isolation
# Binding in namespace A
kind: RoleBinding
metadata:
namespace: ns-a
roleRef:
kind: Role
name: pod-reader
subjects:
- kind: User
name: alice
# Binding in namespace B (same user, different namespace)
kind: RoleBinding
metadata:
namespace: ns-b
roleRef:
kind: Role
name: pod-writer
subjects:
- kind: User
name: aliceResult:
- Alice trong
ns-a: có thểget pods, list pods - Alice trong
ns-b: có thểcreate pods, update pods - Alice di namespace lain: không punya access
Namespace boundary là hard boundary — RoleBinding di ns-a không affect access di ns-b.
Practical Patterns
Pattern 1: Namespace Admin
Beri user admin access chỉ trong một namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-a
name: team-a-admin
roleRef:
kind: ClusterRole
name: admin
subjects:
- kind: User
name: aliceAlice là admin chỉ trong team-a, không trong cluster.
Pattern 2: Cluster Admin
Beri user admin access cluster-wide:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: alice-cluster-admin
roleRef:
kind: ClusterRole
name: cluster-admin
subjects:
- kind: User
name: aliceAlice là cluster admin everywhere.
Pattern 3: Read-Only Access Across Namespaces
Single ClusterRole, multiple RoleBinding per namespace:
# ClusterRole (define once)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
# Bind trong each namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-a
name: pod-reader
roleRef:
kind: ClusterRole
name: pod-reader
subjects:
- kind: ServiceAccount
namespace: team-a
name: monitoring
---
# Same role, different namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: team-b
name: pod-reader
roleRef:
kind: ClusterRole
name: pod-reader
subjects:
- kind: ServiceAccount
namespace: team-b
name: monitoringMonitoring SA có thể read pods trong namespace-nya sendiri, không across namespaces.
Troubleshooting Binding Issues
Issue 1: Expected Permission Not Granted
# Check if binding exists
kubectl get rolebinding -n team-a -o wide
# Check if subject matches
# (requires manual inspection of role+binding)
kubectl get rolebinding pod-reader-binding -n team-a -o yaml
# Verify role rules
kubectl get role pod-reader -n team-a -o yaml
# Test permission
kubectl auth can-i get pods --as=alice -n team-aIssue 2: Permission Granted Unexpectedly
# Find all bindings that match user
kubectl get rolebindings,clusterrolebindings -A -o wide | grep alice
# Check each binding's role
for binding in $(kubectl get rolebindings,clusterrolebindings -A -o name | grep alice); do
kubectl get $binding -o yaml
done
# Check system groups
# alice might be member of system:authenticated or custom groupSummary
ClusterRoleBinding vs RoleBinding decision là scoping decision:
| Requirement | Use |
|---|---|
| Cluster-wide access | ClusterRoleBinding + ClusterRole |
| Namespace-only access | RoleBinding + Role |
| Reusable role across namespaces | ClusterRole + multiple RoleBindings per namespace |
| Tight security boundary | Namespace-scoped RoleBinding (even if using ClusterRole) |
Key points:
- Binding type determines scope, not role type
- roleRef immutable — delete and recreate to change
- Subject matching includes groups and SA expansion
- Namespace is hard boundary — never crossed by RoleBinding