Service accounts & tokens
Disable automount; scope and bind narrowly.
Every pod runs as a ServiceAccount, and if you do not name one it runs as the namespace default — with its token mounted into the container at a well-known path. Most pods never call the Kubernetes API, so for them that token is pure attack surface: a credential an intruder who lands in the container can read and replay against the API server. The first move is to stop mounting it where it is not used.
apiVersion: v1kind: ServiceAccountmetadata: { name: payments-api, namespace: payments }automountServiceAccountToken: false # default off for the account---apiVersion: v1kind: Podmetadata: { name: payments-api, namespace: payments }spec:serviceAccountName: payments-apiautomountServiceAccountToken: false # and/or opt out per-pod (wins)containers: [{ name: app, image: registry.internal/payments-api:1.4.2 }]
How tokens work now
The old model minted a Secret holding a JWT that never expired — steal it once and it worked forever. Modern Kubernetes uses bound service-account tokens: the kubelet requests a short-lived, audience-scoped token via the TokenRequest API and projects it into the pod, rotating it before expiry. Combined with --service-account-lookup=true on the API server, deleting the ServiceAccount actually invalidates its tokens, which the legacy model could not do.
# an on-demand, expiring token (not a stored forever-secret)$ kubectl create token payments-api -n payments --duration=1heyJhbGciOiJSUzI1NiIsImtpZCI6... # valid 1h, audience = the API server# the projected token a pod actually gets, with audience + expiry, kubelet-rotated
Least privilege by binding
Where a workload does need API access, give it a dedicated ServiceAccount bound to a narrow Role — never the default account, never a ClusterRoleBinding to something broad. When a task hands you several accounts and asks for the least-privileged one, read the roles bound to each: an account with no RoleBinding at all has zero granted permissions, which is frequently the correct answer.
# which SAs in this namespace have bindings, and to what?$ kubectl -n omni get rolebindings,clusterrolebindings -o json | jq -r '.items[] | .roleRef.name as $r | .subjects[]?| select(.kind=="ServiceAccount") | "\(.name) -> \($r)"'fe -> editfrontend -> view# frontend-default appears nowhere -> it has the fewest privileges