Skip to content

RBAC and Service Accounts

RBAC splits every permission decision into “what is allowed” (a Role or ClusterRole) and “who gets it” (a RoleBinding or ClusterRoleBinding), and every Pod acts through a ServiceAccount identity whether you configured one or not.

RBAC (Role-Based Access Control) lives under the rbac.authorization.k8s.io/v1 API group. A Role defines a set of permissions scoped to a single namespace — verbs like get, list, watch, create, update, delete applied to specific resources:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: checkout
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]

A ClusterRole is the same idea but cluster-scoped — it either grants access to cluster-scoped resources (like nodes or namespaces), or it grants access to namespaced resources in a way that can be reused across many namespaces via multiple RoleBindings:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: secret-reader
rules:
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list"]

Neither of these grants anything on its own — a Role or ClusterRole is just a description of permissions sitting unattached until something binds it to a subject.

RoleBinding and ClusterRoleBinding: who gets it

Section titled “RoleBinding and ClusterRoleBinding: who gets it”

A RoleBinding connects a Role (or a ClusterRole, reused in a single namespace) to a subject — a User, Group, or ServiceAccount — within one namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: checkout
subjects:
- kind: ServiceAccount
name: checkout-app
namespace: checkout
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io

A ClusterRoleBinding does the same thing but grants the ClusterRole’s permissions across the entire cluster, not just one namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-all-secrets
subjects:
- kind: Group
name: platform-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
Terminal window
# Check what a given identity can actually do
kubectl auth can-i list pods --namespace checkout --as system:serviceaccount:checkout:checkout-app
kubectl get rolebindings,clusterrolebindings -A -o wide

ServiceAccounts: identity for things running inside Pods

Section titled “ServiceAccounts: identity for things running inside Pods”

Kubernetes does not natively manage human users — those authenticate through an external identity provider (OIDC, a cloud IAM integration, client certificates, and so on) at the API server’s authentication layer. What Kubernetes does manage natively is the ServiceAccount: the identity used by processes running inside Pods.

Every Pod runs as some ServiceAccount. If you never specify one, it runs as the namespace’s default ServiceAccount:

apiVersion: v1
kind: Pod
metadata:
name: checkout
namespace: checkout
spec:
serviceAccountName: checkout-app
containers:
- name: checkout
image: checkout:2.3.0

By default, the ServiceAccount’s token is automatically mounted into the Pod’s filesystem so the application inside can authenticate to the API server as that identity. If a workload never talks to the Kubernetes API, that auto-mounted token is unnecessary attack surface — disable it explicitly:

apiVersion: v1
kind: ServiceAccount
metadata:
name: checkout-app
namespace: checkout
automountServiceAccountToken: false

The whole point of splitting Role/ClusterRole from RoleBinding/ClusterRoleBinding is to make it easy to scope access narrowly:

  • Grant only the specific verbs a workload actually needs (get/list/watch for a read-only controller, not create/update/delete).
  • Grant access to specific resources, and where possible specific resourceNames, instead of an entire resource type.
  • Prefer a namespaced Role plus RoleBinding over a ClusterRole plus ClusterRoleBinding whenever the workload only needs access within one namespace.
  • Avoid binding any workload ServiceAccount to the built-in cluster-admin ClusterRole — it grants unrestricted access to every resource in the cluster, and almost nothing legitimately needs that.
flowchart LR
  pod["Pod\n(identity: ServiceAccount)"] --> req["Request to API server"]
  req --> authz["API server\nauthorization check"]
  sa["ServiceAccount"] --> rb["RoleBinding"]
  role["Role\n(verbs on resources)"] --> rb
  rb --> authz
  authz -- "allowed" --> ok["Request proceeds"]
  authz -- "denied" --> deny["403 Forbidden"]
A ServiceAccount bound via a RoleBinding to a Role, checked by the API server on every request
What is the key difference between a Role and a ClusterRole?
What does a RoleBinding actually connect?
Why does a Pod have an RBAC identity even if you never explicitly configured a ServiceAccount for it?
What does the principle of least privilege mean in an RBAC context?