Server

Kubernetes RBAC: Roles and Bindings

By Prabath Thalangama· October 24, 2026· 3 min read
#kubernetes#rbac#security

Introduction

Kubernetes RBAC is built from four object types. Once you see how they pair up — a what and a who — the model is small and predictable.

The four objects

Object Scope Says
Role one namespace a set of permitted (verbs × resources)
ClusterRole cluster-wide same, but also usable in any namespace or for cluster-scoped resources
RoleBinding one namespace grants a Role (or ClusterRole) to subjects in that namespace
ClusterRoleBinding cluster-wide grants a ClusterRole to subjects across all namespaces

Rules are purely additive — there are no deny rules. If no rule grants an action, it's forbidden.

A namespace-scoped example

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: team-a
  name: deploy-manager
rules:
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  namespace: team-a
  name: team-a-deployers
subjects:
  - kind: Group
    name: team-a-devs                 # from your OIDC/auth provider
    apiGroup: rbac.authorization.k8s.io
  - kind: ServiceAccount
    name: ci-deployer
    namespace: team-a
roleRef:
  kind: Role
  name: deploy-manager
  apiGroup: rbac.authorization.k8s.io

Subjects

  • User / Group — Kubernetes has no user database; these strings come from your authenticator (OIDC claims, client cert CN/O, IAM). RBAC just matches the string.
  • ServiceAccount — a real cluster object, for pods. A pod's SA token is mounted at /var/run/secrets/kubernetes.io/serviceaccount/. Reference it as system:serviceaccount:<ns>:<name> in a ClusterRoleBinding.

ClusterRole for a namespaced resource

A ClusterRole listing pods can be bound with a RoleBinding to scope it to one namespace. This is how the built-in view / edit / admin ClusterRoles are reused per-namespace:

kind: RoleBinding
metadata: { namespace: team-b, name: readonly }
subjects: [{ kind: Group, name: auditors, apiGroup: rbac.authorization.k8s.io }]
roleRef: { kind: ClusterRole, name: view, apiGroup: rbac.authorization.k8s.io }

Aggregated ClusterRoles

kind: ClusterRole
metadata:
  name: monitoring
  labels:
    rbac.example.com/aggregate-to-view: "true"    # rolls up into the built-in "view"
rules:
  - apiGroups: ["monitoring.coreos.com"]
    resources: ["servicemonitors"]
    verbs: ["get", "list", "watch"]

view/edit/admin have aggregationRules; label a ClusterRole to extend them without editing the originals.

Debugging "forbidden"

# Can I do it?
kubectl auth can-i create deployments -n team-a
kubectl auth can-i '*' '*' --all-namespaces           # am I cluster-admin?

# Can someone else? (needs impersonation rights)
kubectl auth can-i list secrets -n team-a --as=jane
kubectl auth can-i get pods -n team-a \
  --as=system:serviceaccount:team-a:ci-deployer

# What can this SA do?
kubectl auth can-i --list --as=system:serviceaccount:team-a:ci-deployer -n team-a

# Who is bound to what
kubectl get rolebindings,clusterrolebindings -A -o wide | grep team-a-devs

Verification and troubleshooting

  • Error from server (Forbidden): ... cannot list resource "pods" in API group "" — read it literally: verb list, resource pods, core group. Add that tuple to a Role and bind it. kubectl auth can-i --list shows the current grants.
  • RoleBinding created but still forbidden — subject string mismatch (group/user name from the token differs from what you wrote — check kubectl get --raw /api with -v=8 or your OIDC claims), or wrong namespace, or you bound a Role that doesn't have the rule.
  • ServiceAccount in a pod gets 403 — the pod uses default SA (no rules). Set serviceAccountName: and bind that SA.
  • ClusterRoleBinding used where a RoleBinding was meant — grants cluster-wide, a common over-permission. Use a RoleBinding to a ClusterRole to scope it.
  • patch works but update doesn't (or vice versa) — they're separate verbs. apply (server-side) needs patch + create (+ get). kubectl client-side apply needs get + patch + create.
  • Can create pods but not exec — pods/exec is a subresource, its own rule (resources: ["pods/exec"], verbs: ["create"]).
PrabathStuck on something this site can't fix?Reach out to Prabath directly on LinkedIn.