Every pod holds a key to a door it never opens

Open a shell in almost any Kubernetes pod and look in /var/run/secrets/kubernetes.io. There’s a token
there — a live credential for the cluster API, mounted automatically, signed and ready. Now ask the harder
question: does this workload ever actually call the Kubernetes API? For a media server, a credential vault,
a DNS sinkhole, a search proxy — the answer is no. Never. Not once.
So every one of those pods is holding a key to a door it never opens. That’s not least privilege. That’s a standing credential waiting to be stolen.
Identity first, RBAC second
The reflex when someone says “least-privilege RBAC” is to start writing Roles. But a Role narrows what a
credential can do — it does nothing about a credential that shouldn’t exist. The first move is cheaper and
safer: give every workload its own ServiceAccount instead of the shared namespace default, and set
automountServiceAccountToken: false.
That single line changes no runtime behaviour — no capabilities, no user, no restart semantics — because the app wasn’t using the token anyway. You’ve simply stopped handing out a key. The handful of components that genuinely talk to the API (the GitOps controller, the load-balancer, the secrets operator) get a scoped Role and keep their token. Everything else gets an identity and nothing else. It’s the highest security-per-effort change in the whole estate, and it’s effectively free.
Then take away what root gave you
Identity is one axis; what the process can do on the node is another. A container that runs as root with the
full Linux capability set is one escape away from the host, regardless of how few API rights its token has.
So the next layer is the container itself: run as a non-root UID, drop all capabilities and add back only
the few a workload truly needs, forbid privilege escalation, and pin a RuntimeDefault seccomp profile.
This is where honesty matters. Some images — anything running several processes under an init system, or the
classic “start as root, drop to a user” pattern — break the moment you drop capabilities, because something
inside still expects to chown a directory or bind a port. The lesson isn’t “give up”; it’s that non-root is
a per-app, verified change, while own-identity-and-no-token is a universal, safe one. Do the free thing
everywhere; do the careful thing where you’ve tested it.
Make the floor non-negotiable
Authoring all of this per workload is good, but authoring isn’t enforcing — a future manifest can forget. The
enforcement floor is Pod Security Admission, set at the namespace. Pin each namespace’s level explicitly
rather than inheriting a cluster default you can’t see: baseline where root init-containers still live,
restricted for the apps that have earned it, and a documented privileged exception for the genuine cases
(a VPN sidecar that needs NET_ADMIN, a time daemon that needs SYS_TIME). Run the rest at warn for the
restricted profile, and every apply tells you exactly which workload is one refactor away from the top tier.
The principle
Least privilege isn’t a pile of Roles. It’s a direction: start by removing the access that nothing is using — the auto-mounted token first — then narrow what genuinely remains, then make the boundary enforce itself. Give every workload its own identity, hand it no credential it doesn’t need, strip it to the smallest process it can be, and let the namespace refuse anything bigger. The credential you never mount can’t leak, can’t be replayed, and can’t be the thing you forgot to revoke.

