Service accounts
Users authenticate with a kubeconfig. Workloads authenticate with a ServiceAccount — a namespaced identity that a Pod runs as, and that RBAC rules are written against.
Every namespace has one called default, and every Pod that does not name another one runs as it. Most Pods never talk to the API server at all, and yet they all used to receive a token to do so. Understanding that is the point of this page.
Reference
- https://kubernetes.io/docs/concepts/security/service-accounts/
- https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/
Terminology
- ServiceAccount — An identity for a process. Namespaced, created like any other object, and referenced from a Pod with
spec.serviceAccountName. - Bound token — A token issued for one ServiceAccount, with an audience and an expiry, projected into the Pod and rotated automatically. This is the modern mechanism; the old approach of a token stored in a Secret no longer happens by default.
- Projected volume — The mount at
/var/run/secrets/kubernetes.io/serviceaccount/containing the token, the namespace, the CA certificate and the audience. automountServiceAccountToken— Set tofalseto stop the token being mounted at all. Can be set on the ServiceAccount or on the Pod.imagePullSecrets— Registry credentials attached to a ServiceAccount apply to every Pod that uses it, which is often neater than repeating them per Pod.
Where the Token Is
Inside any Pod that has one:
/var/run/secrets/kubernetes.io/serviceaccount/
├── token # the bearer token, rotated periodically
├── ca.crt # the API server's CA, to verify it
└── namespace # the Pod's namespace, as a fileThe kubectl and client libraries find these automatically, which is why a Pod that uses an in-cluster client “just works” with no configuration — and why a leaked Pod can sometimes talk to the API server as its workload.
Exercises
Create a ServiceAccount and run a Pod as it.
kubectl create serviceaccount app kubectl run client --image=docker.io/library/busybox --restart=Never \ --overrides='{"spec":{"serviceAccountName":"app"}}' -- sleep 3600Use the token from inside the Pod.
kubectl exec client -- sh -c ' TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) wget -qO- --header="Authorization: Bearer $TOKEN" \ https://kubernetes.default.svc/api/v1/namespaces/default/pods 'Expect a
403 Forbidden. The identity is real and the token is valid; it simply has no permissions. Authentication is not authorisation — the next page is the authorisation half.Mint a short-lived token from the command line. Useful for debugging, and worth knowing because these tokens have an expiry:
kubectl create token app --duration=10m kubectl create token app --duration=10m | cut -d. -f2 | base64 -d 2>/dev/null | head -c 200The decoded payload carries
aud,expand the ServiceAccount’s name. Note that the maximum duration is capped by the cluster — some managed clusters allow no more than 24 hours — so a token baked into a kubeconfig is not permanent.Turn the token off. Set
automountServiceAccountToken: falseon a Pod and confirm the directory is empty or absent. If the workload never calls the API server, this is free security: there is no token to steal.
Gotchas Worth Knowing
- Every Pod used to get a token whether it needed one or not. Turning it off is one line and removes a credential from your attack surface.
- Tokens expire now. Anything that copies a token to a file and expects it to work next week will stop working, and the error is a plain
401. defaulthas no permissions in a clean cluster — but some platforms bind additional roles to it, so check rather than assume:kubectl auth can-i --list --as=system:serviceaccount:default:default.- A ServiceAccount is namespaced and cannot be shared across namespaces. The full subject name is
system:serviceaccount:<namespace>:<name>, which is exactly what you will write in the RBAC objects on the next page. imagePullSecretson the ServiceAccount is the standard way to give every workload in a namespace access to a private registry.
What to Take Away
- A ServiceAccount is the identity of a process;
defaultis what a Pod gets if it says nothing. - The token is projected into the Pod, bound to that ServiceAccount, and expires.
- Mount it only if the workload needs it.
- Authentication is not authorisation — the token proves who you are, RBAC decides what you may do.