How to Configure Kubernetes RBAC

How to Configure Kubernetes RBAC

Every cluster I’ve inherited that wasn’t set up carefully from day one had the same problem: nearly everyone had cluster-admin access, “just to be safe,” because nobody wanted to deal with permission errors mid-incident. That’s exactly backwards, and it’s also exactly what Role-Based Access Control (RBAC) exists to fix — giving every user, team, and service account only the permissions they actually need. In this guide I’ll cover the core RBAC objects, walk through creating scoped roles for real scenarios, and cover how to audit and debug permission issues.

The Four Core RBAC Objects

  • Role — a set of permissions (verbs like get, list, create, delete on resources like pods, deployments) scoped to a single namespace.
  • ClusterRole — the same idea, but scoped cluster-wide (or reusable across namespaces).
  • RoleBinding — grants a Role (or ClusterRole) to a user, group, or service account, within a specific namespace.
  • ClusterRoleBinding — grants a ClusterRole cluster-wide, to every namespace.

The key mental model: Roles/ClusterRoles define what is allowed; Bindings define who gets it and where. A ClusterRole can still be namespace-scoped in effect if it’s attached via a RoleBinding rather than a ClusterRoleBinding — this is a common and useful pattern for reusing a permission set across multiple namespaces without duplicating YAML.

Step 1: A Read-Only Role for Developers

Let’s say developers need to view Pods, logs, and Deployments in the staging namespace, but shouldn’t be able to modify anything:

# developer-role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: staging
  name: developer-read-only
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log", "services", "configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["apps"]
    resources: ["deployments", "replicasets"]
    verbs: ["get", "list", "watch"]
kubectl apply -f developer-role.yaml

Bind it to a specific user (assuming your cluster’s authentication is set up to recognize this identity, e.g. via OIDC):

# developer-rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developer-read-only-binding
  namespace: staging
subjects:
  - kind: User
    name: jane@example.com
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer-read-only
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f developer-rolebinding.yaml

Step 2: A Role for a Group Instead of an Individual

Binding to individual users doesn’t scale well. Bind to a group instead, sourced from your identity provider:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: developers-group-binding
  namespace: staging
subjects:
  - kind: Group
    name: engineering-team
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer-read-only
  apiGroup: rbac.authorization.k8s.io

Step 3: Service Account Permissions

Applications running inside the cluster (a CI/CD runner, an operator, an in-cluster tool) authenticate via ServiceAccount, not human user accounts. Let’s grant a CI/CD deployment tool permission to manage Deployments in a specific namespace:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: ci-deployer
  namespace: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: deployment-manager
rules:
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["get", "list", "watch", "update", "patch"]
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: ci-deployer-binding
  namespace: production
subjects:
  - kind: ServiceAccount
    name: ci-deployer
    namespace: production
roleRef:
  kind: Role
  name: deployment-manager
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f ci-deployer-sa.yaml

Generate a token for use in a pipeline (short-lived, as of newer Kubernetes versions):

kubectl create token ci-deployer -n production --duration=1h

Step 4: ClusterRole for Cross-Namespace Access

Some tools genuinely need to operate cluster-wide — a monitoring agent scraping Pods across every namespace, for example:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: metrics-reader
rules:
  - apiGroups: [""]
    resources: ["pods", "nodes", "services", "endpoints"]
    verbs: ["get", "list", "watch"]
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics-reader-binding
subjects:
  - kind: ServiceAccount
    name: prometheus
    namespace: monitoring
roleRef:
  kind: ClusterRole
  name: metrics-reader
  apiGroup: rbac.authorization.k8s.io

Step 5: Using Built-In ClusterRoles

Kubernetes ships several default ClusterRoles worth knowing rather than reinventing: view (read-only across most resources), edit (read-write, excluding RBAC changes and some sensitive resources), and admin (full control within a namespace, including managing Roles/RoleBindings there). cluster-admin grants unrestricted access cluster-wide and should be handed out extremely sparingly.

kubectl get clusterrole view -o yaml

Reusing these for a namespace-scoped binding is often better than writing your own from scratch:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: qa-team-edit
  namespace: qa
subjects:
  - kind: Group
    name: qa-team
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: edit
  apiGroup: rbac.authorization.k8s.io

Step 6: Auditing and Debugging Permissions

The single most useful RBAC debugging command:

kubectl auth can-i delete deployments --namespace=production --as=jane@example.com
no

For a service account:

kubectl auth can-i get pods --namespace=production --as=system:serviceaccount:production:ci-deployer
yes

To see everything a subject can do (helpful for audits):

kubectl auth can-i --list --as=jane@example.com --namespace=staging

To see who can perform a specific action, tools like rbac-lookup or kubectl-who-can (a krew plugin) are worth installing:

kubectl who-can delete secrets -n production

Step 7: Least-Privilege Patterns Worth Adopting

  • Scope by namespace whenever possible. Reach for ClusterRole/ClusterRoleBinding only when access genuinely needs to span namespaces.
  • Avoid wildcard verbs and resources (verbs: ["*"], resources: ["*"]) except for genuinely trusted cluster-admin-level identities.
  • Separate human and machine identities. Never share a ServiceAccount token across multiple unrelated tools — if one is compromised, you want the blast radius contained.
  • Grant secrets access very deliberately. Read access to Secrets is effectively read access to whatever credentials they contain — treat get/list on secrets as a high-privilege grant.
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["myapp-db-credentials"]
    verbs: ["get"]

Scoping by resourceNames like this limits access to one named Secret instead of every Secret in the namespace — worth doing whenever the tool doesn’t genuinely need broad access.

Common Mistakes

  • Binding cluster-admin to a ServiceAccount “temporarily” during setup and forgetting to revoke it.
  • Confusing Role/RoleBinding (namespaced) with ClusterRole/ClusterRoleBinding (cluster-wide) — a ClusterRoleBinding referencing a namespace-appropriate ClusterRole will grant that access everywhere, not just the namespace you had in mind.
  • Not testing permissions with kubectl auth can-i before shipping a change, and discovering the gap only when something breaks in production.
  • Granting broad secrets access to tools that only need a single specific Secret.

Best Practices

  • Treat RBAC changes with the same review rigor as application code — they’re security-critical and easy to get subtly wrong.
  • Regularly audit ClusterRoleBindings for anything overly broad; a kubectl get clusterrolebindings -o yaml review every quarter catches drift.
  • Use groups from your identity provider rather than individual user bindings, so access changes when someone joins or leaves a team without manual RBAC edits.
  • Combine RBAC with Pod Security Standards/admission controllers and NetworkPolicies — RBAC governs the Kubernetes API, not what a Pod can do on the network or at the OS level.

Aggregated ClusterRoles

For platform teams building internal tooling, ClusterRole supports aggregation — combining multiple smaller ClusterRoles into one composite role automatically, based on label matching, rather than manually listing every rule in one giant object:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: custom-monitoring
  labels:
    rbac.example.com/aggregate-to-monitoring: "true"
rules:
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: monitoring-aggregate
aggregationRule:
  clusterRoleSelectors:
    - matchLabels:
        rbac.example.com/aggregate-to-monitoring: "true"
rules: []

Any ClusterRole carrying the matching label automatically gets folded into monitoring-aggregate‘s effective rule set, which the built-in view, edit, and admin ClusterRoles actually use internally — this is exactly how many operators and CRDs extend default permissions without you having to hand-edit the base roles.

Impersonation for Testing and Debugging

Beyond kubectl auth can-i, you can literally act as another identity (if you yourself have impersonate permission) to verify real-world behavior, not just a yes/no permission check:

kubectl get pods -n production --as=jane@example.com
Error from server (Forbidden): pods is forbidden: User "jane@example.com" cannot list resource "pods" in API group "" in the namespace "production"

This is particularly useful when debugging a report of “my pipeline’s service account can’t do X” — impersonating the exact service account reproduces the precise error the automation is hitting, rather than guessing from the RBAC YAML alone.

kubectl get pods -n production --as=system:serviceaccount:production:ci-deployer

RBAC for Custom Resources

Operators and CRDs introduce their own resource types, and RBAC applies to them identically to built-in resources — a detail worth remembering when a team installs an operator and then can’t figure out why their otherwise-broad edit role doesn’t let them manage the new custom resource:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cnpg-cluster-manager
rules:
  - apiGroups: ["postgresql.cnpg.io"]
    resources: ["clusters"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

The built-in edit/admin ClusterRoles only cover resources known at cluster-creation time or explicitly aggregated in — CRDs installed afterward generally need their own explicit rules unless the operator’s Helm chart already wires up aggregation labels for you (many well-maintained operators do this automatically).

Namespace-Scoped Admin Delegation

A pattern I use often for multi-tenant clusters: give each team full control within their own namespace, without any cluster-wide reach at all, by combining the built-in admin ClusterRole with a namespace-scoped RoleBinding per team:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: team-alpha-admin
  namespace: team-alpha
subjects:
  - kind: Group
    name: team-alpha-engineers
    apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: admin
  apiGroup: rbac.authorization.k8s.io

Repeated per team/namespace, this gives every team meaningful self-service control (creating their own Roles and RoleBindings within their namespace, managing their own workloads) while keeping the blast radius of any single team’s mistakes or compromised credentials contained to their own namespace.

Summary

RBAC in Kubernetes comes down to four objects working together: Role/ClusterRole define permissions, RoleBinding/ClusterRoleBinding grant them to specific subjects, scoped to a namespace or the whole cluster. The discipline that actually matters is least privilege — scoping by namespace, avoiding wildcards, and being deliberate about secrets access — verified continuously with kubectl auth can-i rather than assumed.

References

Total
0
Shares

Leave a Reply

Previous Post
How to Expose a Service in Kubernetes

How to Expose a Service in Kubernetes

Next Post
How to Create a Stock Chart in Excel

How to Create a Stock Chart in Excel

Related Posts