Intro
Role-Based Access Control (RBAC) is the primary authorization mechanism in Kubernetes. It governs who can perform actions on which resources within a cluster. When RBAC policies are misconfigured, users and service accounts encounter frustrating errors such as forbidden, cannot list resource, or User "system:serviceaccount:default:my-sa" cannot get resource. These errors can halt application deployments, break CI/CD pipelines, and confuse operators.
This guide focuses on common RBAC errors and their practical fixes. It is written for developers, DevOps engineers, and technical startup teams who manage Kubernetes clusters. We will explore how to identify version-specific behavior, inspect current permissions, apply the smallest safe change, and verify that the fix works. The goal is operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, and document recovery steps.
Throughout this article, you will see concrete commands, expected outputs, and failure signals. We will use namespaces, Roles, RoleBindings, ClusterRoles, and ClusterRoleBindings to demonstrate both namespaced and cluster-wide permission issues. By the end, you will have a systematic troubleshooting checklist to resolve RBAC errors with confidence.
Version and Environment Inventory
Before changing any RBAC configuration, you must understand your cluster environment. Kubernetes RBAC behavior can differ across versions due to API changes, deprecations, and new access modes. Start by identifying the installed version and the components involved.
Identify Cluster Version and API Availability
Run the following command to check the Kubernetes server version:
kubectl version --short
Expected output (example):
Client Version: v1.27.3
Kustomize Version: v5.0.1
Server Version: v1.28.2
RBAC API versions rbac.authorization.k8s.io/v1 became stable in Kubernetes 1.8. If you are using an older cluster (unlikely in production), you might need v1beta1. Most current clusters support v1.
Next, verify that the RBAC authorization mode is enabled. Check the API server flags (if you have access) or query the API resources:
kubectl api-resources | grep rbac
Expected output:
roles rbac.authorization.k8s.io/v1 true Role
rolebindings rbac.authorization.k8s.io/v1 true RoleBinding
clusterroles rbac.authorization.k8s.io/v1 false ClusterRole
clusterrolebindings rbac.authorization.k8s.io/v1 false ClusterRoleBinding
These resources should always be present. If RBAC is disabled, the API server may use the legacy ABAC or always-allow mode, which is rare and insecure.
Confirm Your Identity and Current Context
RBAC decisions depend on the identity of the requesting user or service account. Determine who you are and what context you are using:
kubectl config current-context
kubectl auth whoami
The second command requires the kubectl plugin whoami (available via krew or built-in in newer versions). If not available, you can check your certificate or token details. For service accounts, the identity is usually system:serviceaccount:<namespace>:<serviceaccount-name>.
Example: If a pod in namespace default with service account default attempts to list pods and fails, the error message often includes the identity. Knowing the exact identity helps you craft the correct RoleBinding.
Read-Only Observation Before Changes
For any suspected RBAC issue, first observe the current state without modifying anything. Use read-only commands:
kubectl get pods -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
If the failure is in a workload, examine its logs and events:
kubectl logs <pod-name> -n <namespace> --previous
kubectl get events -n <namespace> --sort-by=.metadata.creationTimestamp
For a deployment that depends on permissions, check its rollout status:
kubectl rollout status deployment/<deployment-name> -n <namespace>
These commands do not alter state. They provide clues about whether the issue is truly authorization or something else (missing image, scheduling constraints, etc.).
Keep Tests Isolated
When testing RBAC changes, start in a dedicated namespace. Create a small test pod or use kubectl auth can-i to evaluate permissions before applying any permanent manifests. For example:
kubectl create namespace rbac-test
kubectl auth can-i list pods --as=system:serviceaccount:rbac-test:test-sa -n rbac-test
The --as flag simulates a user or service account without modifying existing bindings. This is invaluable for verifying what a specific identity can do.
Safe Configuration Path
Once you have a clear picture of the environment, you can apply a fix. The safest path is to make the smallest change that grants exactly the required permissions. Avoid broad permissions like cluster-admin unless absolutely necessary.
Understand Roles vs ClusterRoles
- Role: Grants permissions within a single namespace.
- ClusterRole: Grants permissions cluster-wide or across all namespaces for namespaced resources. It is also required for non-namespaced resources like nodes, persistent volumes, and namespaces themselves.
If an application only needs access within its own namespace, use a Role with a RoleBinding. If it needs cluster-wide read access to pods, use a ClusterRole with a ClusterRoleBinding.
Example: Fix "forbidden" for a Service Account Listing Pods
Problem: A pod running with service account my-app-sa in namespace production logs:
Error from server (Forbidden): pods is forbidden: User "system:serviceaccount:production:my-app-sa" cannot list resource "pods" in API group "" in the namespace "production"
Solution: Create a Role that allows listing pods and bind it to that service account.
- Create the Role (save as
pod-reader-role.yaml):
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: production
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
Apply it:
kubectl apply -f pod-reader-role.yaml
- Create the RoleBinding (save as
pod-reader-binding.yaml):
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: ServiceAccount
name: my-app-sa
namespace: production
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Apply it:
kubectl apply -f pod-reader-binding.yaml
- Verify with
kubectl auth can-i:
kubectl auth can-i list pods --as=system:serviceaccount:production:my-app-sa -n production
Expected output:
yes
If the output is no, continue debugging (check for typos, namespace mismatches, or the service account existence).
Avoid Overly Broad Permissions
A common mistake is granting cluster-admin to a service account to quickly solve a permission error. This violates the principle of least privilege. Instead, enumerate the exact resources and verbs needed. For example, an ingress controller may need get, list, watch on services, endpoints, and ingresses. Grant only those.
Use kubectl create Commands for Quick Fixes
For temporary testing, you can create roles and bindings with one-liners:
kubectl create role pod-reader --verb=get,list,watch --resource=pods -n production
kubectl create rolebinding read-pods --role=pod-reader --serviceaccount=production:my-app-sa -n production
These commands generate the same YAML conceptually. However, for reproducibility, store manifests in version control.
Verify with a Test Pod
After applying the fix, run a short-lived pod using the same service account to confirm the operation succeeds. For example:
kubectl run test-pod --image=busybox --restart=Never --serviceaccount=my-app-sa -n production -- sleep 3600
Then exec into the pod and try the API call using the service account's token. But a simpler check is to inspect the pod's service account token and make a curl to the API server. However, kubectl auth can-i is usually sufficient.
Verification and Diagnostics
After making a change, you must verify that the error is resolved and that no unexpected permissions were added. This section details diagnostic commands and techniques.
Check Effective Permissions with kubectl auth can-i
The kubectl auth can-i command is the fastest way to test permissions. It supports the --as flag to impersonate any user or service account, and --namespace to scope the check.
Examples:
# Can user jane create deployments in namespace dev?
kubectl auth can-i create deployments --as=jane -n dev
# Can service account default in namespace kube-system get secrets?
kubectl auth can-i get secrets --as=system:serviceaccount:kube-system:default -n kube-system
# What can user bob do in namespace prod?
kubectl auth can-i --list --as=bob -n prod
The --list flag prints all allowed actions, which is useful for auditing.
Inspect RoleBindings and ClusterRoleBindings
Display the bindings to ensure they reference the correct subjects and roles:
kubectl get rolebindings -n production
kubectl describe rolebinding read-pods -n production
Expected describe output includes:
Name: read-pods
Namespace: production
Labels: <none>
Annotations: <none>
Role:
Kind: Role
Name: pod-reader
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount my-app-sa production
Check that the subject's kind and name match exactly. Service accounts are referenced as system:serviceaccount:<namespace>:<name>, but in the binding you specify kind: ServiceAccount and name: <name> with namespace if it's in a different namespace.
View Audit Logs for Denied Requests
Kubernetes audit logs record authorization decisions. If you have access to the control plane, examine the audit log for forbidden events. Search for the specific user and resource:
grep "system:serviceaccount:production:my-app-sa" /var/log/kubernetes/audit.log | grep "pods" | grep "forbidden"
Audit logs provide the exact API request, including the verb and resource, helping you craft the precise rule.
Test with a Real Workload
Sometimes, kubectl auth can-i says yes, but the application still fails. This can happen if the application uses a different API group or a subresource (like pods/log). For subresources, you must explicitly grant permissions. For example, to allow reading pod logs, the Role must include:
rules:
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
Use kubectl auth can-i with the subresource:
kubectl auth can-i get pods/log --as=system:serviceaccount:production:my-app-sa -n production
If it returns no, add the subresource to the Role.
Failure Modes and Recovery
RBAC changes can introduce new problems. Plan for failures and know how to recover quickly.
Common Failure Modes
- Overly restrictive permissions: The user still gets
forbiddenafter applying the fix. This often occurs because the Role does not include the correct API group or resource name. For example, deployments are in theappsAPI group, not the core group. A Role grantingresources: ["deployments"]withapiGroups: [""]will fail. It must beapiGroups: ["apps"].
- Binding to wrong subject: The RoleBinding references
name: my-sabut the service account is in a different namespace. If the subject is a service account, you must specify its namespace in the subject block (if different from the RoleBinding's namespace).
- ClusterRole vs Role confusion: A RoleBinding in namespace
defaultcan bind a ClusterRole to a subject, granting only permissions within that namespace. A ClusterRoleBinding is needed for cluster-wide access. Misunderstanding this may lead to insufficient permissions.
- Missing verbs: Some operations require multiple verbs. For example, to use
kubectl exec, you needcreateonpods/exec. To usekubectl logs -f, you needgetonpods/logandwatch.
Immediate Recovery Steps
If you apply a restrictive change and break an application, you can revert by deleting the binding or role:
kubectl delete rolebinding read-pods -n production
kubectl delete role pod-reader -n production
Or, if you used GitOps, roll back the commit. Always keep a backup of the previous RBAC objects. You can dump them with:
kubectl get role pod-reader -n production -o yaml > pod-reader-backup.yaml
Emergency Access Restoration
In a worst-case scenario where a critical service account loses access and the cluster is unreachable, you may need to use the cluster's admin credentials (e.g., the original kubeconfig with cluster-admin) to restore permissions. Never store admin credentials in the cluster; keep them offline as a break-glass measure.
If the API server itself is misconfigured and RBAC is denying all requests (including admin), you may need to access the control plane directly and modify the RBAC authorization flags or temporarily switch to AlwaysAllow (not recommended for production). This is rare and should be part of your disaster recovery plan.
Operations Checklist
Use this checklist to systematically troubleshoot RBAC errors:
- Identify the failing identity: From the error message, note the user or service account. Example:
User "system:serviceaccount:default:my-sa".
- Confirm the requested action: What verb and resource? Example:
list podsorcreate deployments.
- Check current permissions: Use
kubectl auth can-i --list --as=<identity> -n <namespace>to see allowed actions.
- Inspect existing Roles and Bindings: Run
kubectl get roles,rolebindings -n <namespace>and describe them to find missing rules.
- Determine the correct role type: Namespaced resource in one namespace -> Role; cluster-wide or non-namespaced -> ClusterRole.
- Create the minimal Role/ClusterRole: Write YAML with exact
apiGroups,resources, andverbs. Include subresources if needed.
- Bind the role to the identity: Create a RoleBinding (namespaced) or ClusterRoleBinding (cluster-wide).
- Verify with
kubectl auth can-i: Simulate the exact action.
- Test with a real workload: Deploy a test pod using the same service account and execute the operation.
- Document the fix: Save the YAML in version control and note the reason.
Example Checklist Execution for a Common Error
Error: Error from server (Forbidden): deployments.apps is forbidden: User "developer" cannot create resource "deployments" in API group "apps" in the namespace "dev"
- Identity:
developer(a user) - Action:
create deploymentsin namespacedev - Check:
kubectl auth can-i create deployments --as=developer -n devreturnsno - Inspect:
kubectl get rolebindings -n devshows no binding for developer - Role type: namespaced Role
- Create Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: developer
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["create", "get", "list", "update", "patch", "delete"]
- Bind:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: developer-binding
namespace: dev
subjects:
- kind: User
name: developer
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
- Verify:
kubectl auth can-i create deployments --as=developer -n devreturnsyes - Test: developer can now create deployments.
Conclusion
Kubernetes RBAC errors are common but manageable with a systematic approach. By starting with a thorough environment inventory, applying minimal and specific permissions, and verifying with kubectl auth can-i, you can resolve most issues without resorting to overly broad access.
Remember that RBAC is a critical security layer. Every change should be version-scoped, observable, and reversible where possible. Always test in a non-production namespace first, use impersonation to simulate identities, and keep backups of RBAC objects.
As a next step, take one low-risk RBAC error from your cluster and apply this checklist. Record the current state, run the diagnostic commands, make the smallest change, and verify the result. Review dependencies such as the API group and subresources to ensure completeness.
A reliable technical workflow makes failure visible, protects sensitive values, limits changes to the intended resource, and defines recovery verification before an incident forces the decision.