RBAC, service accounts and pod security
Who may do what to the API, what identity a pod carries, and how to stop containers running as root with a full set of capabilities.
RBAC: roles, bindings and verbs
Authorization is a decision the API server makes on every request: does this identity have this verb on this resource in this scope? RBAC answers it with roles (what is allowed) and bindings (who gets it).
| Kind | Scope | Grants |
|---|---|---|
| Role | One namespace | Verbs on resources inside that namespace |
| ClusterRole | Cluster-wide | Verbs on cluster-scoped resources, or the same rules in every namespace |
| RoleBinding | One namespace | A Role or a ClusterRole to a subject, in that namespace |
| ClusterRoleBinding | Cluster-wide | A ClusterRole to a subject across the whole cluster |
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: storefront
rules:
- apiGroups: [""] # "" means the core API group
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader
namespace: storefront
subjects:
- kind: ServiceAccount
name: ci
namespace: storefront
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.iokubectl auth can-i get pods -n storefront --as=system:serviceaccount:storefront:ci
kubectl auth can-i --list -n storefront --as=system:serviceaccount:storefront:ci
kubectl create role deployer --verb=get,list,watch,update --resource=deployments -n storefront
kubectl describe clusterrole cluster-admin # note the wildcards- Subjects are users, groups, or service accounts. There is no User object: users come from your identity provider or client certificate.
cluster-adminis bound tosystem:masters. Nothing should ever run with it.- roleRef in a binding is immutable. Changing the role means creating a new binding.
- Avoid wildcards in resources and verbs. They silently grant access to resources that do not exist yet, including future custom resources.
- Verb
createon pods is effectively node-level code execution. Treat it as privileged, because it is.
Service accounts and token projection
A service account is the identity a pod uses when it talks to the API server. Every namespace has a default one, and a pod that does not name one gets it — with whatever the namespace grants to default, which should be nothing.
apiVersion: v1
kind: ServiceAccount
metadata:
name: api
namespace: storefront
automountServiceAccountToken: false # opt in per pod, not by default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
template:
spec:
serviceAccountName: api
automountServiceAccountToken: false # this pod never calls the API
containers:
- name: api
image: ghcr.io/example/api:2.1.0- Since Kubernetes 1.24 there is no long-lived token Secret by default: the kubelet requests a short-lived, audience-bound, automatically rotating token through the TokenRequest API.
- The token is mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token. Anything that can read the pod's filesystem can use it, which is another argument for read-only root filesystems. - Turn automounting off and mount a token explicitly only in the containers that need one.
- Give each workload its own service account. Sharing one makes the audit log useless and the blast radius large.
- For external systems, use workload identity federation rather than copying a cloud credential into a Secret.
kubectl create serviceaccount api -n storefront
kubectl get serviceaccounts -A
kubectl get rolebindings,clusterrolebindings -A -o wide | grep storefront
kubectl auth whoami --as=system:serviceaccount:storefront:apiKeeping containers unprivileged
A container that runs as root inside a namespace can often become root on the node. securityContext closes that path, and Pod Security Admission enforces a minimum standard per namespace.
spec:
securityContext: # applies to every container in the pod
runAsNonRoot: true
runAsUser: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: ghcr.io/example/api:2.1.0
securityContext: # per container
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
privileged: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}| Setting | Stops |
|---|---|
runAsNonRoot | Containers that would start as uid 0 |
allowPrivilegeEscalation: false | setuid binaries and gaining more privileges than the parent |
readOnlyRootFilesystem | Writing over binaries or dropping a payload on the container filesystem |
capabilities.drop: ALL | Raw sockets, mounting, changing ownership and the rest of the default set |
seccompProfile: RuntimeDefault | Most dangerous syscalls |
privileged: false | Full access to the host device tree and kernel capabilities |
# enforce a baseline on a namespace, with a visible warning first
kubectl label namespace storefront \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=latest \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted
kubectl get pod api-abc -o jsonpath='{.spec.securityContext}'create on pods, or the ability to mount hostPath volumes, can reach the node's filesystem and often its credentials. Restricting who may create pods matters more than restricting what a pod image contains.FAQ
Why does my container fail to start after adding runAsNonRoot?
runAsUser to a numeric id that exists in the image, or rebuild the image with a non-root USER.How do I give a pod access to just one Secret?
get on that one Secret name via resourceNames, bind it to that service account, and set serviceAccountName on the pod.Related
Networking, DNS and NetworkPolicy Helm and packaging applications
Last refreshed 2026-09-18.