Programs authenticate as ServiceAccounts, namespaced objects the API server knows as system:serviceaccount:<namespace>:<name>. Every namespace has a default account, and every Pod runs as one. Since Kubernetes 1.24 5,150 the kubelet mounts a bound token from the TokenRequest API rather than a Secret: a JWT for the API server's audience, tied to the Pod, refreshed by the kubelet, and void once the Pod is gone. Decode the one in a front-end Pod, and a ten-minute token made on demand:
jwt() { jq -R 'split(".")[1] | @base64d | fromjson'; }
kubectl exec deploy/web -- cat /var/run/secrets/kubernetes.io/serviceaccount/token | jwt \
| jq -r '"\(.sub) ttl=\(.exp-.iat) warnafter=\(.["kubernetes.io"].warnafter-.iat)"'
kubectl create token jenkins -n jenkins --duration=10m | jwt | jq -c '{sub, ttl: (.exp-.iat)}'system:serviceaccount:booknest:default ttl=31536000 warnafter=3607
{"sub":"system:serviceaccount:jenkins:jenkins","ttl":600}The kubelet asks for about an hour, but the API server, by default, stretches Pod tokens to a year for clients that never reload them (--service-account-extend-token-expiration), and logs any use after warnafter. The binding to the Pod is what really limits it. Neither BookNest container calls the Kubernetes API, so these tokens are only something an intruder could use. Turn the API's off in its Pod template:
spec:
automountServiceAccountToken: false
securityContext: { fsGroup: 1000 }kubectl apply -f k8s/api-deployment.yaml >/dev/null
kubectl rollout status deploy/api >/dev/null
kubectl exec deploy/api -- ls /var/run/secrets/kubernetes.io 2>&1 | cut -c1-80
git commit -qam "Stop mounting a ServiceAccount token into the API"ls: cannot access '/var/run/secrets/kubernetes.io': No such file or directory command terminated with exit code 2
The jenkins-token Secret from Connecting Jenkins is the old style, valid until the Secret is deleted, and exactly what the TokenRequest API replaced. Prefer kubectl 5,150 create token or your cloud's workload identity whenever a client can refresh its credentials.