Kubernetes RBAC Explained — Roles, Bindings & Service Accounts with YAML Examples
Kubernetes RBAC answers four questions at once: who is making this request, what verb are they performing, what resource are they touching, and in which namespace. You describe the permissions in a Role or ClusterRole, attach them to a subject (user, group, or service account) with a RoleBinding or ClusterRoleBinding, and Kubernetes enforces them on every API call. Get this model right and you can run a locked-down production cluster. Get it wrong and a single compromised pod can read every secret in the cluster.
RBAC is one of those topics people skim over until they hit a forbidden error in production. Then they grant cluster-admin to everything just to make it work. This guide shows you how to think about RBAC properly, with real YAML examples you can copy into your cluster today.
The Four Building Blocks
Kubernetes RBAC is made of exactly four object types. Once you understand how they fit together, every RBAC error starts to make sense.
| Object | Scope | Purpose |
|---|---|---|
| Role | Namespace | Defines a set of permissions (verbs on resources) inside one namespace |
| ClusterRole | Cluster | Defines a set of permissions across all namespaces or on cluster-scoped resources |
| RoleBinding | Namespace | Grants a Role or ClusterRole to a subject inside one namespace |
| ClusterRoleBinding | Cluster | Grants a ClusterRole to a subject cluster-wide |
The mental model: Role/ClusterRole = the permissions. RoleBinding/ClusterRoleBinding = who gets them. You never put the subject inside the Role itself — that separation is what makes RBAC composable.
Example 1 — A Namespace-Scoped Role
Your developer needs to read pods and view logs in the dev namespace, but nothing else. Here is the minimum RBAC for that.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
Breakdown:
apiGroups: [""]— the empty string means the core API group, which contains pods, services, configmaps, secrets, and nodesresources— what objects this Role applies to.pods/logis a subresource, which is why reading logs needs to be granted separatelyverbs—get(one by name),list(all),watch(stream changes). Nocreate,update, ordeletemeans read-only
Now bind this Role to a user:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-pod-reader
namespace: dev
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Alice can now read pods and logs in dev, and nothing else in the cluster. If she tries kubectl get pods -n prod, she gets Error from server (Forbidden). That is RBAC working correctly.
Example 2 — ClusterRole Reused Across Namespaces
Real clusters have multiple namespaces. Defining the same pod-reader Role in every namespace is tedious and error-prone. Instead, define the permissions once as a ClusterRole, then use RoleBindings to grant it in each namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
# Grants pod-reader in the dev namespace only
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: alice-pod-reader
namespace: dev
subjects:
- kind: User
name: alice@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # <-- reusing the cluster-scoped definition
name: pod-reader
apiGroup: rbac.authorization.k8s.io
This is a key pattern. A RoleBinding can reference either a Role or a ClusterRole, but the permissions are scoped to the namespace where the RoleBinding lives. You get reusable permission definitions without accidentally granting cluster-wide access.
Example 3 — Service Accounts for Pods
Human users are only half of RBAC. The other half is pods calling the Kubernetes API. Every pod runs under a service account, and you attach permissions to service accounts the same way you do for users.
Scenario: you are running a controller pod that needs to list deployments and update their replicas (a simple autoscaler).
# 1. Create a service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: autoscaler
namespace: production
---
# 2. Define the permissions it needs
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: deployment-scaler
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments/scale"]
verbs: ["update", "patch"]
---
# 3. Bind the role to the service account
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: autoscaler-binding
namespace: production
subjects:
- kind: ServiceAccount
name: autoscaler
namespace: production
roleRef:
kind: Role
name: deployment-scaler
apiGroup: rbac.authorization.k8s.io
Now reference the service account in your pod spec:
apiVersion: apps/v1
kind: Deployment
metadata:
name: autoscaler
namespace: production
spec:
template:
spec:
serviceAccountName: autoscaler
containers:
- name: autoscaler
image: myorg/autoscaler:v1.2.0
Inside the pod, the token at /var/run/secrets/kubernetes.io/serviceaccount/token is automatically mounted. Your application code (or a Kubernetes client library) uses that token to authenticate to the API. RBAC then checks whether the autoscaler service account has the permission it is trying to use.
Critical rule: never use the default service account for anything. Every namespace has one, but it exists as a placeholder. Always create a purpose-specific service account per workload, with the minimum permissions it needs. If an attacker compromises a pod, they get exactly those permissions and nothing more.
Free Download
Terraform & Kubernetes — 100 Senior Interview Questions (PDF)
133 pages covering Kubernetes RBAC, networking, storage, scheduling, and production patterns. Every question has a direct answer, mental model, and production depth.
Download PDFUnderstanding the Verbs
RBAC verbs map directly to HTTP methods on the Kubernetes API. Here are the ones you will actually use:
| Verb | Meaning |
|---|---|
get | Read a single named resource |
list | Read all resources of a type (can be expensive on secrets) |
watch | Stream changes to resources over time |
create | Create a new resource |
update | Replace an entire resource |
patch | Partial update to a resource |
delete | Delete a single resource |
deletecollection | Delete multiple resources at once |
* | All verbs (avoid in production) |
A common junior mistake is writing verbs: ["*"] because it is easier than figuring out exactly what is needed. This is how service accounts end up with permission to delete every pod in the cluster. Always enumerate verbs explicitly.
Common RBAC Patterns
Pattern 1 — Read-only viewer
Give support engineers access to see what is happening in a namespace without being able to change anything. Use the built-in view ClusterRole and bind it with a RoleBinding per namespace.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: oncall-viewers
namespace: production
subjects:
- kind: Group
name: sre-oncall
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: view # built-in ClusterRole shipped with Kubernetes
apiGroup: rbac.authorization.k8s.io
Pattern 2 — Namespace admin
Developers get full control inside their team namespace but nothing outside it. Use the built-in admin ClusterRole.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-admins
namespace: team-alpha
subjects:
- kind: Group
name: team-alpha
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: admin # built-in: full rights inside the namespace
apiGroup: rbac.authorization.k8s.io
Pattern 3 — Secrets-only access
A sealed-secrets controller needs to read specific secret types but should not touch deployments or pods.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
resourceNames: ["db-credentials", "api-tokens"] # scope to specific names
verbs: ["get"]
Notice resourceNames — this restricts the permission to only these specific secrets, not all secrets in the namespace. This is one of the most underused RBAC features and it is incredibly powerful for least privilege.
RBAC on EKS — The IAM Connection
On AWS EKS, authentication goes through IAM and authorization goes through RBAC. These are two separate decisions:
- IAM decides who you are. Your IAM user or role is authenticated by AWS and mapped to a Kubernetes username/group via the
aws-authConfigMap (or Access Entries in newer EKS). - RBAC decides what you can do. Once you are authenticated, Kubernetes RBAC rules check whether your username or group can perform the requested action.
Example aws-auth mapping:
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: arn:aws:iam::123456789012:role/DevOpsRole
username: devops-admin
groups:
- system:masters
- rolearn: arn:aws:iam::123456789012:role/DeveloperRole
username: developer
groups:
- team-alpha
Here, anyone assuming DeveloperRole in IAM becomes a member of the team-alpha group in Kubernetes. Your RoleBinding for team-alpha-admins then grants them admin inside the team-alpha namespace.
For pods, the modern pattern is IRSA (IAM Roles for Service Accounts). You annotate a Kubernetes service account with an IAM role ARN, and pods using that service account can call AWS APIs (like S3, DynamoDB, SQS) with temporary AWS credentials — no access keys, no secrets. See the OIDC guide for the same trust model applied to GitHub Actions.
Debugging RBAC Errors
When a pod or user gets a forbidden error, use kubectl auth can-i to check what they are actually allowed to do.
# Can my current user get pods in dev?
kubectl auth can-i get pods -n dev
# Can the autoscaler service account update deployments?
kubectl auth can-i update deployments \
--as=system:serviceaccount:production:autoscaler \
-n production
# List everything the autoscaler can do in its namespace
kubectl auth can-i --list \
--as=system:serviceaccount:production:autoscaler \
-n production
The --as flag is one of the most valuable debugging tools in Kubernetes. It lets you impersonate any user or service account (if you have impersonation permission, or are cluster-admin) and see exactly what they can and cannot do, before deploying changes that depend on RBAC.
Production Rules I Follow
- Never use
cluster-adminfor workloads. It is fine for the initial cluster bootstrapper and nothing else. - Never bind to the
defaultservice account. Create a purpose-specific SA for every workload that needs API access. - Enumerate verbs explicitly.
verbs: ["*"]is a security smell. - Prefer Role over ClusterRole whenever the permission is namespace-scoped. Blast radius matters.
- Use
resourceNamesto scope permissions to specific resources when possible. Most controllers only need a few named objects. - Audit with
kubectl auth can-i --listbefore every production rollout that changes RBAC. - Keep RBAC in Git. Apply it via CI/CD like any other manifest, with code review. RBAC changes are security changes.
Frequently Asked Questions
What is RBAC in Kubernetes?
RBAC is the authorization system Kubernetes uses to decide who can perform which actions on which resources. It maps subjects (users, groups, service accounts) to permissions (verbs on resources) through Roles and RoleBindings.
Role vs ClusterRole — which do I use?
Use Role when permissions should stay inside one namespace. Use ClusterRole when permissions cross namespaces or apply to cluster-scoped resources like nodes, PVs, or CRDs. You can reuse a ClusterRole from a namespace-scoped RoleBinding to avoid duplication.
What is a service account?
A service account is the identity pods use when calling the Kubernetes API. Every namespace has a default service account, but in production you should create a purpose-specific one per workload and grant it only the permissions it needs.
How does RBAC work with EKS?
IAM handles authentication (who you are), RBAC handles authorization (what you can do). The aws-auth ConfigMap maps IAM identities to Kubernetes groups, and your RoleBindings then grant permissions to those groups. For pods, IRSA lets service accounts assume IAM roles to call AWS services securely.
How do I debug a forbidden error?
Use kubectl auth can-i <verb> <resource> --as=<user> -n <namespace> to test whether a subject has a specific permission. Use --as=system:serviceaccount:<ns>:<sa> to impersonate a service account. kubectl auth can-i --list shows everything a subject can do.
Next Steps
If you are building on EKS, these guides pair well with RBAC:
- Production EKS Cluster with Terraform — provision a real EKS cluster end-to-end
- AWS IAM Best Practices — Least Privilege — the IAM side of the EKS auth story
- GitHub Actions OIDC — No Access Keys — same trust model for CI/CD
- Free DevOps resources — including the Terraform/Kubernetes 100Q interview PDF