Kubernetes RBAC Setup: A Practical Guide (No More Guessing Permissions)

By KP  |  TZoneLabs  |  DevOps & Cloud Engineering

The fastest way to grant a pod too much power in Kubernetes is kubectl create clusterrolebinding temp --clusterrole=cluster-admin --serviceaccount=default:default. It works immediately, which is exactly the problem. Nobody circles back to fix it, and six months later a compromised pod can read every secret in the cluster.

We want to walk through how to scope RBAC so a workload gets exactly the permissions it needs, no more, and how to verify that before it ships.

Why RBAC Exists, In One Sentence

Every request that hits the Kubernetes API, whether it’s kubectl get pods, a controller listing ConfigMaps, or a CI pipeline deploying a Deployment, gets checked against RBAC first. If nothing explicitly allows it, it’s denied. RBAC is a pure allow-list; there’s no deny rule to write, only permissions to grant or withhold.

The Four Objects That Make Up RBAC

  • Role — a set of permissions scoped to one namespace (e.g., “read ConfigMaps in payments“).
  • ClusterRole — the same idea, but not tied to a namespace, either for cluster-scoped resources (nodes, PVs) or to be reused across many namespaces.
  • RoleBinding — grants a Role (or a ClusterRole) to a user, group, or ServiceAccount, scoped to one namespace.
  • ClusterRoleBinding — grants a ClusterRole cluster-wide, no namespace boundary.

The mistake we see most often: reaching for a ClusterRoleBinding because it’s one command, when a Role plus a RoleBinding in a single namespace would have covered it.

Start With What the Workload Actually Touches

Say we have a controller that watches Pods and reads ConfigMaps in its own namespace. Nothing else. The Role should reflect exactly that, not “just give it edit”:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: payments
  name: pod-watcher
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list"]

No create, no delete, no secrets. If the controller never writes, the Role never grants write.

Bind It to a Dedicated ServiceAccount, Skip default

Every pod that doesn’t specify a serviceAccountName runs as the namespace’s default ServiceAccount. If anything ever binds a permission to default, even temporarily, even for debugging, every unlabeled pod in that namespace inherits it. Always create a named ServiceAccount per workload:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: pod-watcher-sa
  namespace: payments
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: pod-watcher-binding
  namespace: payments
subjects:
  - kind: ServiceAccount
    name: pod-watcher-sa
    namespace: payments
roleRef:
  kind: Role
  name: pod-watcher
  apiGroup: rbac.authorization.k8s.io

Then reference it explicitly in the pod spec: serviceAccountName: pod-watcher-sa.

When You Actually Need Cluster Scope

Cluster scope is correct for things like a monitoring agent that needs to list nodes, or a cert-manager-style controller managing CRDs across every namespace. In that case, be explicit about the ClusterRole’s contents rather than reusing view/edit/admin (Kubernetes’ built-in aggregated roles), which are broader than most single controllers need:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: node-reader
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]

Test Permissions Before You Ship, Not After an Incident

kubectl auth can-i impersonates a ServiceAccount and tells you exactly what it can and can’t do. No need to trigger the real workload to find out:

kubectl auth can-i delete secrets \
  --as=system:serviceaccount:payments:pod-watcher-sa \
  -n payments
# no

kubectl auth can-i --list \
  --as=system:serviceaccount:payments:pod-watcher-sa \
  -n payments

We run the --list version in CI against every changed Role/RoleBinding pair. A diff in the permission list is a required reviewer callout, the same way we’d flag a diff in a security group.

A Quick Audit for Existing Clusters

If RBAC in your cluster grew organically, this finds every binding to cluster-admin, almost always worth a second look:

kubectl get clusterrolebindings -o json \
  | jq -r '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name'

And this finds every RoleBinding still pointed at a namespace’s default ServiceAccount:

kubectl get rolebindings --all-namespaces -o json \
  | jq -r '.items[] | select(.subjects[]?.name=="default") | "\(.metadata.namespace)/\(.metadata.name)"'

Both commands tend to turn up more than expected the first time they’re run.

Key Lessons

  1. RBAC is allow-only.
    There’s no explicit deny rule. If nothing grants a permission, it’s already denied, so scope by only granting what’s actually used.
  2. default ServiceAccount bindings are the most common accidental over-grant.
    Every unlabeled pod in the namespace inherits them.
  3. ClusterRoleBinding should be rare.
    Most workloads only ever touch resources in their own namespace.
  4. Run kubectl auth can-i in CI, not just during incident response.
    Verifying scope before merge is cheaper than discovering it during a breach postmortem.
  5. Audit existing clusters periodically.
    RBAC sprawl happens gradually, and a scheduled cluster-admin binding audit catches what code review missed.

Summary

Object Scope Use When
Role Single namespace Workload only touches resources in its own namespace
ClusterRole Cluster-wide (or reusable) Cluster-scoped resources (nodes, PVs) or shared across namespaces
RoleBinding Single namespace Grants a Role, or a ClusterRole, within one namespace
ClusterRoleBinding Cluster-wide Grants a ClusterRole with no namespace boundary, used sparingly

Most RBAC incidents don’t come from missing permissions. They come from convenience: cluster-admin because it was faster, default service accounts because nobody created a new one. Scope it right the first time and there’s nothing for the audit to find later.

Read Next

If you’re running Kubernetes in production, follow along on LinkedIn for more guides like this one as they’re published.


Tags:
#Kubernetes   #RBAC   #DevOps   #Security  
#K8sSecurity   #CloudNative   #AWS   #EKS

Leave a Comment