KubernetesLX · ServiceAccountsServiceAccounts
SA tokens in pod spec — explicit projection and audiences
What you'll learn
- Configure a custom audience for the SA token in a Pod
- Set a custom expiry shorter than the default 1 hour
- Mount multiple tokens in one Pod for different audiences
- Identify the production use cases (Vault, AWS STS, cloud workload identity)
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
The default projected token volume mounts a token with the API server’s audience and a 1-hour expiry. A workload that needs to authenticate to a different consumer (Vault, AWS STS, a custom API) requires a token with a different audience. This lesson covers the explicit projection pattern: custom audiences, custom expiries, multiple tokens in one Pod, and the production use cases.
The default projection
The SA admission controller adds a default projection:
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- path: namespace
fieldRef:
fieldPath: metadata.namespace
The token’s audience is the API server’s
(https://kubernetes.default.svc); the expiry is 1
hour; the path is /var/run/secrets/kubernetes.io/serviceaccount/token.
A workload that uses this token to authenticate to any other consumer (Vault, AWS, custom API) is rejected — the audience does not match.
Custom audience projection
A workload that needs to authenticate to a non-API consumer adds an explicit projection:
spec:
serviceAccountName: api-sa
automountServiceAccountToken: false # disable the default
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
path: token
audience: https://kubernetes.default.svc
expirationSeconds: 3600
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: vault-token
audience: vault://prod.example.com
expirationSeconds: 600
containers:
- name: api
volumeMounts:
- name: kube-api-access
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
- name: vault-token
mountPath: /etc/secrets/vault
readOnly: true
This Pod has two tokens:
tokenat/var/run/secrets/kubernetes.io/serviceaccount/for the API server (audiencehttps://kubernetes.default.svc).vault-tokenat/etc/secrets/vault/vault-tokenfor Vault (audiencevault://prod.example.com).
The API server rejects the Vault token; Vault rejects the API server token.
flowchart LR
A[Pod] --> B[API server token]
A --> C[Vault token]
B --> D[aud: kubernetes]
C --> E[aud: vault]
D -->|matched| F[API server accepts]
E -->|matched| G[Vault accepts]
Custom expiry
The default expiry is 1 hour (3600 seconds). A workload
that needs a shorter rotation window (for tighter
revocation) sets expirationSeconds explicitly:
- serviceAccountToken:
path: short-lived-token
audience: vault://prod.example.com
expirationSeconds: 300 # 5 minutes
A 5-minute expiry means the token rotates every 4 minutes (at 80% of the lifetime). A revocation in the signing key invalidates the token within 5 minutes.
The bound-token cap is 1 hour 6 minutes; unbound tokens can have a longer duration (up to 24 hours, configurable per API server).
Multiple tokens in one Pod
A workload that authenticates to multiple consumers mounts multiple tokens:
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
path: token
audience: https://kubernetes.default.svc
expirationSeconds: 3600
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: vault-token
audience: vault://prod.example.com
expirationSeconds: 600
- name: aws-token
projected:
sources:
- serviceAccountToken:
path: aws-token
audience: sts.amazonaws.com
expirationSeconds: 900
The workload reads each token from its mount path and presents it to the corresponding consumer. The audiences are explicit; no consumer accepts a token with the wrong audience.
Production use cases
- Vault. A workload reads secrets from Vault
using a Kubernetes-authenticated token. The
audience is
vault://prod.example.com.
- serviceAccountToken:
path: vault-token
audience: vault://prod.example.com
expirationSeconds: 600
- AWS STS (IRSA). A workload assumes an IAM role
using a Kubernetes-authenticated token. The
audience is
sts.amazonaws.com.
- serviceAccountToken:
path: aws-token
audience: sts.amazonaws.com
expirationSeconds: 900
- GCP Workload Identity. A workload uses a
Kubernetes-authenticated token to impersonate a GCP
service account. The audience is
https://iam.googleapis.com/.
- serviceAccountToken:
path: gcp-token
audience: https://iam.googleapis.com/
expirationSeconds: 900
- Custom APIs. A workload authenticates to an internal API using a Kubernetes-authenticated token. The audience is the API’s URL.
- serviceAccountToken:
path: custom-token
audience: https://api.internal.example.com
expirationSeconds: 600
Common failure modes
- Wrong audience. The token has
aud=https://kubernetes.default.svcbut the workload presents it to Vault. Vault rejects. The fix is an explicit projection with the right audience. - Default mount not disabled. The Pod has both
the default mount (with API server audience) and
the custom projection. The workload uses the
default mount for Vault; Vault rejects. The fix is
automountServiceAccountToken: false. - Multiple tokens confused. A workload has three tokens and uses the Vault token for AWS. Both reject. The fix is to verify the mount paths and the audiences.
- Long expiry. A token with a 24-hour expiry is not revoked by a signing-key rotation within the window. The fix is the minimum duration the workload needs.
Production failure modes
- Tokens are projected without audiences. The
default mount has the API server’s audience; a
custom projection without an audience is rejected
by every consumer. The fix is to always set
audience. - Tokens are mounted but never used. A token at
/etc/secrets/vault/vault-tokenthat the workload never reads is a credential that can be exfiltrated anyway. The fix is to mount only the tokens the workload needs. - Tokens are not rotated. A workload with a 24-hour token that does not re-read sees a stale token after the kubelet’s rotation. The fix is shorter expiries and re-read before each call.
Cross-course references
- The Linux course covers JWT and JWKS — the primitives that the kubelet uses.
- The Observability course covers the audit log entries for projected token requests.
Quiz
Knowledge check · 4 questions
Q1. What is the right pattern for a Pod that needs to authenticate to both the API server and Vault?
Q2. AWS IRSA (IAM Roles for Service Accounts) requires a ServiceAccount token with audience `sts.amazonaws.com`, which can be projected into the Pod via a custom `serviceAccountToken` volume.
Q3. Your workload uses Vault to read database credentials. The Pod has a custom projection with `audience: vault://prod.example.com`. The workload presents the token to Vault; Vault returns `403 audience mismatch`. Why, and how do you fix it?
The Pod has both the default mount (API server audience) and the custom projection (Vault audience). The workload reads the default-mount token by mistake and presents it to Vault. The token's audience is the API server's, not Vault's. Vault rejects.
Q4. Name three audiences commonly used in projected ServiceAccount tokens and the consumer each one targets.
Passing score: 75%. Answers are checked in this browser.
Production discipline
Custom audience projection is the right primitive for workloads that authenticate to non-API consumers. Every consumer that accepts a Kubernetes-issued JWT has a specific audience; the projection must be configured for that audience. The default mount is for the API server; non-API consumers require explicit projections. A cluster whose workloads use custom audiences with explicit projections has a credential programme that is auditable; a cluster whose workloads use the default mount for non-API consumers has a credential programme that does not work.