RBAC and multi-tenancy
Roles that are actually least-privilege, and the three places tenancy leaks anyway.
RBAC is four object types and one rule: permissions are purely additive, and there is no deny. What a subject can do is the union of every binding that mentions it, which means you can never fix an over-permissive grant by adding a narrower one.
Four objects, two scopes
- Role — permissions, inside one namespace.
- ClusterRole — permissions, cluster-wide or reusable.
- RoleBinding — grants a Role or a ClusterRole inside one namespace.
- ClusterRoleBinding — grants a ClusterRole everywhere.
Ask the API rather than reading YAML
kubectl auth can-i --list --as=system:serviceaccount:team-a:deployer -n team-a
kubectl auth can-i delete pods --as=jane -n prod
kubectl auth can-i '*' '*' --as=system:serviceaccount:team-a:deployer # the scary one
Reviewing bindings by eye does not work at any real scale, because the answer depends on role aggregation and on bindings in other namespaces. can-i --list gives you the effective answer, which is the only one that matters.
The escalation paths that are not obvious
create podsin a namespace means you can mount any Secret in that namespace and run as any ServiceAccount in it. It is effectively read access to every credential there.create pods/execis a shell in a running container, bypassing whatever the image entrypoint was.escalateorbindon roles lets a subject grant itself permissions it does not have. RBAC normally prevents privilege escalation; these verbs are the exemption.get secretscluster-wide is usually equivalent to cluster-admin in practice, because one of those secrets is a token for something that is.
Three leaks to close
- Network. Without NetworkPolicy, every pod can reach every pod. A default-deny policy per namespace is the first real wall.
- Resources. Without a ResourceQuota and LimitRange, one tenant can take a whole node. Noisy-neighbour problems are tenancy problems.
- Node and kernel. Pods share a kernel. A privileged container, a hostPath mount or
hostNetworkescapes the namespace entirely — which is what Pod Security Admission is for.
# the baseline worth having in every tenant namespace
apiVersion: v1
kind: Namespace
metadata:
name: team-a
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
Default ServiceAccount tokens
Every pod gets a ServiceAccount token mounted unless you say otherwise. Most workloads never call the API, so that token is pure downside — it is the first thing anything inside a compromised container goes looking for.
spec:
automountServiceAccountToken: false # on the pod, or on the ServiceAccount
What to actually do with this
- Run
can-i --listfor your most-used ServiceAccount. Expect a surprise. - Find every binding of
cluster-adminand justify each one out loud. - Turn off token automounting on one workload that does not need it, and watch nothing break.
Something wrong or out of date? Open an issue โ corrections are welcome and get credited.