KUBERNETES

Kubernetes RBAC Explained — Roles, Bindings & Service Accounts with YAML Examples

By Akshay Ghalme·April 12, 2026·12 min read

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.

ObjectScopePurpose
RoleNamespaceDefines a set of permissions (verbs on resources) inside one namespace
ClusterRoleClusterDefines a set of permissions across all namespaces or on cluster-scoped resources
RoleBindingNamespaceGrants a Role or ClusterRole to a subject inside one namespace
ClusterRoleBindingClusterGrants 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 nodes
  • resources — what objects this Role applies to. pods/log is a subresource, which is why reading logs needs to be granted separately
  • verbsget (one by name), list (all), watch (stream changes). No create, update, or delete means 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 PDF

Understanding the Verbs

RBAC verbs map directly to HTTP methods on the Kubernetes API. Here are the ones you will actually use:

VerbMeaning
getRead a single named resource
listRead all resources of a type (can be expensive on secrets)
watchStream changes to resources over time
createCreate a new resource
updateReplace an entire resource
patchPartial update to a resource
deleteDelete a single resource
deletecollectionDelete 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:

  1. IAM decides who you are. Your IAM user or role is authenticated by AWS and mapped to a Kubernetes username/group via the aws-auth ConfigMap (or Access Entries in newer EKS).
  2. 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

  1. Never use cluster-admin for workloads. It is fine for the initial cluster bootstrapper and nothing else.
  2. Never bind to the default service account. Create a purpose-specific SA for every workload that needs API access.
  3. Enumerate verbs explicitly. verbs: ["*"] is a security smell.
  4. Prefer Role over ClusterRole whenever the permission is namespace-scoped. Blast radius matters.
  5. Use resourceNames to scope permissions to specific resources when possible. Most controllers only need a few named objects.
  6. Audit with kubectl auth can-i --list before every production rollout that changes RBAC.
  7. 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:

  1. Production EKS Cluster with Terraform — provision a real EKS cluster end-to-end
  2. AWS IAM Best Practices — Least Privilege — the IAM side of the EKS auth story
  3. GitHub Actions OIDC — No Access Keys — same trust model for CI/CD
  4. Free DevOps resources — including the Terraform/Kubernetes 100Q interview PDF
AG

Akshay Ghalme

AWS DevOps Engineer with 3+ years building production cloud infrastructure. AWS Certified Solutions Architect. Currently managing a multi-tenant SaaS platform serving 1000+ customers.

More Guides & Terraform Modules

Every guide comes with a matching open-source Terraform module you can deploy right away.