HackBlue

Tools · Containers and Kubernetes security · 03 / 20

Kubernetes service account tokens: automount, projected tokens, and least privilege

See how pods receive API credentials, why most pods should not get one, and how bound projected tokens differ from legacy long-lived Secret tokens.

9 min read · practice only with authorization

How a pod gets a token

Every namespace has a default service account. Unless told otherwise, each pod runs as that account and gets a token mounted at /var/run/secrets/kubernetes.io/serviceaccount/token. Since Kubernetes 1.24, the token is a projected, audience-bound, time-limited token that the kubelet refreshes, not a long-lived Secret.

Anyone who can read that file inside the container can call the API server as the service account. If the account has broad RBAC, a single application bug becomes a cluster problem.

Reducing exposure

Most application pods never talk to the Kubernetes API. Set automountServiceAccountToken: false on the service account or the pod spec. Create a dedicated service account per workload that does need API access, and grant it only the verbs it uses. Avoid creating legacy token Secrets of type kubernetes.io/service-account-token; use kubectl create token for short-lived tokens when a human needs one.

Snippet
apiVersion: v1
kind: ServiceAccount
metadata:
  name: web
  namespace: shop
automountServiceAccountToken: false

Lab: look inside a pod

Run a test pod with the default settings and read the mounted token’s claims by decoding the middle part of the JWT. Note the expiry and audience. Then recreate the pod with automount disabled and confirm the directory is gone.

Snippet
kubectl run probe --image=busybox:1.36 --restart=Never -- sleep 3600
kubectl exec probe -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | cut -d. -f2 | base64 -d 2>/dev/null; echo
kubectl delete pod probe
kubectl run probe --image=busybox:1.36 --restart=Never \
  --overrides='{"spec":{"automountServiceAccountToken":false}}' -- sleep 3600
kubectl exec probe -- ls /var/run/secrets/kubernetes.io/serviceaccount

Auditing

List service accounts bound to powerful roles and list Secrets of the legacy token type, which do not expire. Cloud-managed clusters also map service accounts to cloud identities (IRSA or EKS Pod Identity, Azure Workload Identity, GKE Workload Identity), so a pod token can lead to cloud permissions as well.

Diagram

Legacy token Secret versus projected token

Short-lived and bound is the default for a reason.

Legacy Secret token

Lifetime. No expiry

Audience. Any

Stored. As a Secret in etcd

Projected token

Lifetime. Time-limited, rotated by kubelet

Audience. Bound to the API server or a set audience

Stored. Only in the pod’s volume

Check yourself

1. Which field stops a token from being mounted into pods that use a service account?

2. How do projected service account tokens differ from legacy token Secrets?

3. What can someone with the pod’s token file do?