Service accounts: stop mounting tokens nobody uses
Why this is a guaranteed task
Every Pod gets a service account token mounted at/var/run/secrets/kubernetes.io/serviceaccount/token unless you say otherwise.
If the SA can do something interesting, anybody who pops the container owns that
permission — and the token never expires unless it is projected with an expiry.
Switch it off in both places
apiVersion: v1
kind: ServiceAccount
metadata: {name: api, namespace: prod}
automountServiceAccountToken: false # all Pods using this SA
---
apiVersion: v1
kind: Pod
metadata: {name: client, namespace: prod}
spec:
serviceAccountName: api
automountServiceAccountToken: false # this Pod, whatever the SA says
The Pod field wins over the SA field. Set both when the task says "must not be
mounted" — the marker may check either object.
serviceAccountName is immutable. If you have to change it on a running
Deployment, the Pod template change triggers a rollout; on a bare Pod you delete
and recreate.
Verify
kubectl exec -n prod client -- ls /var/run/secrets/kubernetes.io/serviceaccount/
# ls: cannot access ...: No such file or directory
kubectl get sa api -n prod -o jsonpath='{.automountServiceAccountToken}'
kubectl get pod client -n prod -o jsonpath='{.spec.automountServiceAccountToken}'
And from the Pod's identity:
kubectl auth can-i --list --as=system:serviceaccount:prod:api
Projected tokens: audience and expiry
The default token is a Secret that lives as long as the SA. A projected token
is issued per Pod, bound to a workload, expires, and can carry an audience:
spec:
serviceAccountName: api
volumes:
- name: token
projected:
defaultMode: 0400
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
audience: vault
containers:
- name: app
image: registry.example.com/app:1.0
volumeMounts:
- {name: token, mountPath: /var/run/secrets/tokens, readOnly: true}
kubectl create token api -n prod --duration=1h --audience=vault produces a
short-lived token from the CLI — useful when the task wants a token without
mounting anything.
The cluster-admin binding that must go
kubectl get clusterrolebindings -o wide | grep -i cluster-admin
kubectl get clusterrolebinding bad-binding -o yaml # find the subject
kubectl delete clusterrolebinding bad-binding
kubectl auth can-i --list --as=system:serviceaccount:prod:api # now minimal
Also check for system:serviceaccounts or system:authenticated as subjects —
those grant to every service account in the cluster.
Default SA in a namespace
apiVersion: v1
kind: ServiceAccount
metadata: {name: default, namespace: prod}
automountServiceAccountToken: false
Doing that once per namespace is the cheapest cluster-wide win, and it is a
common "harden this namespace" sub-task.