Skip to content

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) ​

yaml
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: [email protected]
- kind: ServiceAccount
  namespace: default
  name: my-app
- kind: Group
  name: developers

Structure:

FieldScopingOptions
namespaceBinding scoped to this namespaceRequired
roleRef.kindRole type being boundRole hoặc ClusterRole
roleRef.nameRole nameUnique per kind
subjectsWho gets these permissionsUsers, Groups, SAs

ClusterRoleBinding (Cluster-Scoped) ​

yaml
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: [email protected]
- kind: ServiceAccount
  namespace: monitoring
  name: prometheus

Key difference:

  • No namespace field (cluster-scoped)
  • Can only reference ClusterRole (not Role)

Scope Semantics: The Critical Distinction ​

Role + RoleBinding = Namespace-Scoped Access ​

yaml
# 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: alice

Result: 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 ​

yaml
# 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: alice

Result: Alice có thể get, list pods di tất cả namespaces.

ClusterRole + RoleBinding = Namespace-Scoped Access (Asymmetric!) ​

yaml
# 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: alice

Result: 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:

yaml
# 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: app

One 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: "[email protected]",
  groups: ["developers", "system:authenticated"],
  serviceAccount: "system:serviceaccount:default:my-app"
}

RoleBinding subjects:

yaml
subjects:
- kind: User
  name: [email protected]        # Matches request.user
- kind: Group
  name: developers               # Matches request.groups
- kind: ServiceAccount
  namespace: default
  name: my-app                   # Expands to system:serviceaccount:default:my-app

Matching 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:
      MATCH

ServiceAccount Subject Expansion ​

ServiceAccount subjects automatically expand đến internal username:

yaml
subjects:
- kind: ServiceAccount
  namespace: monitoring
  name: prometheus
  # Internally becomes:
  # system:serviceaccount:monitoring:prometheus

Nế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:

yaml
subjects:
- kind: Group
  name: developers

Di-match nếu "developers" trong request.groups. Request.groups populated bởi authentication layer (OIDC, LDAP):

OIDC example:

OIDC token claims:
{
  "sub": "[email protected]",
  "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:

bash
# This FAILS
kubectl patch rolebinding pod-reader-binding \
  --type='json' -p='[{"op":"replace","path":"/roleRef/name","value":"deployment-reader"}]'
# Error: spec.roleRef is immutable

Reason: Prevent privilege escalation via binding manipulation. Kalau roleRef có thể diubah tanpa creating new binding, auditing becomes impossible.

Recovery:

bash
# Must delete and recreate
kubectl delete rolebinding pod-reader-binding
kubectl create rolebinding pod-reader-binding \
  --clusterrole=deployment-reader \
  --user=alice

Why 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 creation

Subject Reference Semantics ​

User Subject ​

yaml
subjects:
- kind: User
  name: [email protected]

Matched against: request.user (from authentication)

Notes:

  • Name là opaque string, no namespace
  • Case-sensitive
  • OIDC/LDAP authenticator populates request.user

Group Subject ​

yaml
subjects:
- kind: Group
  name: developers

Matched against: request.groups array

Notes:

  • Name là opaque string
  • Case-sensitive
  • system:* groups reserved để system use

ServiceAccount Subject ​

yaml
subjects:
- kind: ServiceAccount
  namespace: default
  name: my-app

Internally expands to username: system:serviceaccount:default:my-app

Important: ServiceAccount subject requires namespace field. It's not optional.

yaml
# WRONG
subjects:
- kind: ServiceAccount
  name: my-app  # Missing namespace!
  
# Cause validation error or unexpected no-match behavior

Authorization Decision with Multiple Bindings ​

OR Semantics Across Bindings ​

Nếu user matched multiple bindings, permissions là union:

yaml
# 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: alice

Result: Alice trong default namespace có thể:

  • get pods, list pods (từ Binding 1)
  • get deployments, list deployments (từ Binding 2)

Cross-Namespace Isolation ​

yaml
# 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: alice

Result:

  • 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:

yaml
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: alice

Alice là admin chỉ trong team-a, không trong cluster.

Pattern 2: Cluster Admin ​

Beri user admin access cluster-wide:

yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: alice-cluster-admin
roleRef:
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: User
  name: alice

Alice là cluster admin everywhere.

Pattern 3: Read-Only Access Across Namespaces ​

Single ClusterRole, multiple RoleBinding per namespace:

yaml
# 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: monitoring

Monitoring SA có thể read pods trong namespace-nya sendiri, không across namespaces.

Troubleshooting Binding Issues ​

Issue 1: Expected Permission Not Granted ​

bash
# 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-a

Issue 2: Permission Granted Unexpectedly ​

bash
# 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 group

Summary ​

ClusterRoleBinding vs RoleBinding decision là scoping decision:

RequirementUse
Cluster-wide accessClusterRoleBinding + ClusterRole
Namespace-only accessRoleBinding + Role
Reusable role across namespacesClusterRole + multiple RoleBindings per namespace
Tight security boundaryNamespace-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

Tham Khảo ​