RBAC (Role-Based Access Control) is Kubernetes’ authorization mechanism. It determines what operations a given user or service account can perform on which resources. Done well, it limits blast radius when something goes wrong. Done poorly, it’s cluster-admin for everything and a compliance nightmare.
The Core Model
Kubernetes RBAC has four main resource types:
Role: A namespaced set of permissions. Can only grant access to resources in the same namespace.
ClusterRole: A cluster-wide set of permissions. Can grant access to namespaced resources across all namespaces, or cluster-scoped resources (Nodes, PersistentVolumes, etc.).
RoleBinding: Grants a Role or ClusterRole to a user/group/ServiceAccount within a namespace.
ClusterRoleBinding: Grants a ClusterRole to a user/group/ServiceAccount across the entire cluster.
Permission = (Role or ClusterRole) → (RoleBinding or ClusterRoleBinding) → Subject
Subject = User | Group | ServiceAccount
The Problem with cluster-admin
The first instinct when something doesn’t work is to grant cluster-admin. It fixes the problem instantly. It also grants that service account or user full read/write access to every resource in the cluster, including secrets.
Never use cluster-admin for:
- Application service accounts
- CI/CD pipeline credentials
- Developer access for daily work
- Monitoring or logging agents
cluster-admin is for cluster bootstrapping and emergency break-glass scenarios.
Service Account Principles
Every pod runs with a service account. If you don’t specify one, it uses the default service account in its namespace. The default service account typically has no permissions, but its token is mounted in the pod and available to any process running in the pod.
Best practices:
- Create dedicated service accounts per application
- Disable token auto-mounting when not needed
- Grant minimum required permissions
# ServiceAccount with token auto-mount disabled
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: my-app
automountServiceAccountToken: false
---
# Deployment explicitly opting in with its own SA
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
serviceAccountName: my-app
automountServiceAccountToken: false # Belt-and-suspenders
containers:
- name: my-app
For the minority of applications that do need to interact with the Kubernetes API (operators, monitoring agents, CI/CD systems), create a specific ServiceAccount with only the required permissions:
apiVersion: v1
kind: ServiceAccount
metadata:
name: deployment-reader
namespace: monitoring
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: deployment-reader
namespace: my-app
rules:
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: deployment-reader-binding
namespace: my-app
subjects:
- kind: ServiceAccount
name: deployment-reader
namespace: monitoring
roleRef:
kind: Role
name: deployment-reader
apiGroup: rbac.authorization.k8s.io
The deployment-reader ServiceAccount in the monitoring namespace can read deployments in the my-app namespace. Nothing else.
Human User RBAC
For human users, integrate with your identity provider (OIDC, LDAP via Dex, AWS IAM for EKS, etc.) rather than creating individual Kubernetes users.
A typical team-level structure:
# ClusterRole that allows reading/managing resources in any namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: namespace-admin
rules:
- apiGroups: ["", "apps", "batch", "networking.k8s.io", "autoscaling"]
resources: ["*"]
verbs: ["*"]
- apiGroups: [""]
resources: ["resourcequotas", "limitranges"]
verbs: ["get", "list", "watch"] # Can view but not modify quotas
---
# Bind to the team's OIDC group, scoped to their namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: checkout-team-admin
namespace: checkout
subjects:
- kind: Group
name: "oidc:checkout-team"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: namespace-admin
apiGroup: rbac.authorization.k8s.io
The checkout team has full admin access to their checkout namespace. They can’t touch other teams’ namespaces or cluster-level resources.
The View Role Pattern
Read access is safer than no access. Give developers cluster-wide read access to see what’s happening, while restricting write access to their namespaces:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: developers-view
subjects:
- kind: Group
name: "oidc:developers"
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: view # Built-in ClusterRole - read access to most resources
apiGroup: rbac.authorization.k8s.io
The built-in view ClusterRole gives read access to most resources (excluding Secrets—important). Combined with namespace-level write access for their own namespace, developers can see the full cluster while only writing to their area.
Protecting Secrets
The view ClusterRole explicitly excludes Secrets from read access. This is intentional. Treat Secrets as a separate privilege tier:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: secret-reader
namespace: my-app
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
resourceNames: # Restrict to specific secrets by name
- my-app-config
- my-app-tls
Restrict secret access by both namespace and, where possible, by specific secret name. A service account that needs one database password shouldn’t be able to read all secrets in the namespace.
Auditing RBAC
Understanding what permissions a subject has is difficult with vanilla kubectl. Tools help:
# kubectl auth can-i
kubectl auth can-i list pods --namespace my-app --as system:serviceaccount:my-app:my-app
# rbac-lookup
kubectl rbac-lookup my-service-account --kind serviceaccount
# kube-rbac-proxy (sidecar that enforces RBAC on non-Kubernetes endpoints)
# rback (RBAC visualization)
Audit logs are essential for understanding who did what. Enable Kubernetes audit logging with a policy that captures sensitive operations:
# audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: RequestResponse
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: Metadata
resources:
- group: ""
resources: ["pods"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services"]
Common Mistakes
Using * verbs or resources broadly: Audit every * in your RBAC configuration. Each one is a permission grant you probably didn’t fully think through.
Not scoping ClusterRoleBindings: Using ClusterRoleBindings when RoleBindings would suffice gives cluster-wide access when namespace-scoped would be appropriate.
Shared service accounts: Multiple applications sharing a ServiceAccount means a compromise of one application gives access to all their combined permissions.
Not rotating service account tokens: Use projected service account tokens (expiring, audience-bound) rather than long-lived static tokens:
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: my-app
Kubernetes RBAC isn’t glamorous work, but it’s foundational security work. The 30 minutes spent properly scoping service account permissions is insurance against the incident where a compromised pod doesn’t become a cluster takeover.