Kubernetes RBAC Complete Guide: Configuring Role, ClusterRole, and ServiceAccount for Least Privilege Access

Security tutorial - IT technology blog
Security tutorial - IT technology blog

The Day Everything Almost Broke

A junior DevOps engineer on my team once deployed a CI/CD pipeline where the build pod had access to list, create, and delete nodes across the entire cluster. It worked fine for months — until someone accidentally triggered a cleanup script that wiped production workloads. Root cause? The ServiceAccount had cluster-admin privileges because nobody had bothered to scope it properly.

One incident. Three hours of recovery. A very uncomfortable post-mortem.

That experience hammered home something I now treat as a rule: in Kubernetes, what you don’t restrict will eventually bite you. This guide walks through setting up RBAC properly — no assumed knowledge, no hand-waving.

Why Over-Permissive Access Keeps Happening

It almost always traces back to one of three things:

  • Speed over security — granting cluster-admin to unblock someone in the moment
  • Unclear ownership — nobody knows what a ServiceAccount actually needs, so it gets everything
  • Misconfigured defaults — the default ServiceAccount in a namespace quietly accumulates more permissions than intended

Kubernetes has shipped with RBAC enabled by default since v1.6. But enabled isn’t the same as configured. The API server will happily accept a RoleBinding that hands a pod full write access to secrets across all namespaces — no warning, no prompt, no guardrails.

How Kubernetes RBAC Actually Works

Four objects make up the entire RBAC system. Get these straight first and everything else clicks:

  • Role — defines permissions within a single namespace
  • ClusterRole — defines permissions cluster-wide, or reusably across namespaces
  • RoleBinding — grants a Role (or ClusterRole) to a user, group, or ServiceAccount within one namespace
  • ClusterRoleBinding — grants a ClusterRole cluster-wide, no namespace restriction

A ServiceAccount is just an identity that pods use to authenticate with the API server. By itself it can do nothing — permissions only exist once you attach a binding.

The mental model that helped me most: think of Roles as job descriptions, and Bindings as signed employment contracts.

Three Common Patterns (And What’s Wrong With Two of Them)

Pattern 1: Just Use cluster-admin

The path of least resistance. Bind the built-in cluster-admin ClusterRole to your ServiceAccount and move on with your day.

# Don't do this in production
kubectl create clusterrolebinding my-app-admin \
  --clusterrole=cluster-admin \
  --serviceaccount=production:my-app

Full cluster control. Create, delete, modify anything. It solves the immediate problem, but now anyone who can exec into that pod effectively owns your cluster. One compromised container and it’s game over.

Pattern 2: Use Built-in ClusterRoles

Kubernetes ships with around 50 built-in roles. The useful general-purpose ones are view, edit, and admin. Better than cluster-admin, but still broader than most workloads need.

# Better, but usually still too broad
kubectl create rolebinding my-app-viewer \
  --clusterrole=view \
  --serviceaccount=production:my-app \
  --namespace=production

view gives read-only access to most resources. edit adds write access. These are fine for quick setups — but they bundle in resources your app probably never touches, and every unnecessary permission is a liability.

Pattern 3: Custom Roles with Least Privilege

More upfront work. Worth it every time.

You define exactly which verbs (get, list, watch, create, update, delete) apply to exactly which resources in exactly which namespace. Nothing more. This is the only approach that actually limits blast radius when something goes wrong — and something always eventually goes wrong.

Step-by-Step RBAC Setup

Let’s use a concrete example: a monitoring application that reads pod and node metrics but should never modify anything.

Step 1: Create a Dedicated Namespace and ServiceAccount

kubectl create namespace monitoring
kubectl create serviceaccount metrics-reader -n monitoring

Never use the default ServiceAccount for real workloads. A dedicated ServiceAccount makes auditing clean and permission scoping explicit — you always know exactly what’s bound to what.

Step 2: Define a Role with Minimal Permissions

Reading node metrics requires cluster-wide access, so we need a ClusterRole here rather than a namespace-scoped Role:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: metrics-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "nodes", "nodes/metrics"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["metrics.k8s.io"]
    resources: ["pods", "nodes"]
    verbs: ["get", "list"]
kubectl apply -f metrics-reader-clusterrole.yaml

Three things worth noting:

  • apiGroups: [""] targets the core API group — pods, nodes, services, configmaps
  • Only get, list, watch — no create, update, or delete
  • Resources listed explicitly, not wildcarded

Step 3: Bind the Role to the ServiceAccount

Node metrics need cluster-wide visibility, so a ClusterRoleBinding is appropriate here:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics-reader-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: metrics-reader
subjects:
  - kind: ServiceAccount
    name: metrics-reader
    namespace: monitoring
kubectl apply -f metrics-reader-binding.yaml

Step 4: Assign the ServiceAccount to Your Pod

apiVersion: v1
kind: Pod
metadata:
  name: metrics-collector
  namespace: monitoring
spec:
  serviceAccountName: metrics-reader
  containers:
    - name: collector
      image: your-metrics-image:latest

Step 5: Verify What the ServiceAccount Can Actually Do

Run this before deploying — not after wondering why something broke:

# Should return: yes
kubectl auth can-i list pods \
  --as=system:serviceaccount:monitoring:metrics-reader

# Should return: no
kubectl auth can-i delete pods \
  --as=system:serviceaccount:monitoring:metrics-reader

# Dump the full permission set
kubectl auth can-i --list \
  --as=system:serviceaccount:monitoring:metrics-reader

If that second command returns yes, your Role is too permissive. Fix it now, not in a post-mortem.

Step 6: Disable Automounting When Not Needed

Kubernetes automounts the ServiceAccount token into every pod by default. If your pod never calls the Kubernetes API directly, that token is just an attack surface sitting on the filesystem:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: metrics-reader
  namespace: monitoring
automountServiceAccountToken: false

Or disable it per-pod instead:

spec:
  automountServiceAccountToken: false

No token on disk means no way to authenticate against the API server, even if the role bindings exist. Defense in depth.

Common Mistakes Worth Calling Out

  • Wildcards in resources or verbsresources: ["*"] grants access to everything. That’s cluster-admin in disguise.
  • ClusterRoleBinding when a RoleBinding is enough — if your app only touches one namespace, scope the binding there, even if the Role itself is a ClusterRole
  • Never auditing existing bindings — run kubectl get clusterrolebindings -o wide periodically; privilege creep is real and it accumulates quietly
  • Skipping the auth can-i check — five minutes of verification beats two hours of incident response

A Note on ServiceAccount Credentials

When setting up cluster access for human users (not just pods), you’ll generate kubeconfig files with credentials. For tokens and passwords, I use the generator at toolcraft.app/en/tools/security/password-generator — it runs entirely in the browser, so nothing touches a server. For anything security-related, client-side-only matters.

Auditing Your RBAC Configuration

Set up periodic audits. These three commands cover most of what you need:

# Find every ServiceAccount with cluster-admin
kubectl get clusterrolebindings -o json | \
  jq '.items[] | select(.roleRef.name=="cluster-admin") | \
  {name: .metadata.name, subjects: .subjects}'

# List all RoleBindings in a namespace
kubectl get rolebindings -n production -o wide

# Check who can perform sensitive operations
kubectl auth can-i create pods --all-namespaces --list

For larger clusters, rbac-lookup and kubectl-who-can make this considerably less painful.

The Principle in Practice

Least privilege isn’t a checkbox. It’s a habit you build into every deployment.

Each time you add a new workload, ask: what does this pod actually need to call? Start with nothing. Add permissions only when something breaks and you can clearly justify why. The monitoring example above is straightforward — real applications get messier. Operators managing CRDs, pipelines deploying across namespaces, admission webhooks reading secrets. The approach stays the same regardless.

RBAC misconfiguration shows up consistently as one of the top Kubernetes security findings in production clusters — not because the tooling is hard, but because nobody made time to do it right the first time. That post-mortem my team sat through was avoidable. Yours can be too.

Share: