Prefer Bound Projected ServiceAccount Tokens Over Secret
More Info:
Advisory: avoid long-lived ServiceAccount token Secrets; use projected (TokenRequest) tokens with an audience and expiry instead.
Risk Level
Informational
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all ServiceAccount token Secrets
- Run on: any machine with kubectl access
- Command:
kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token -o wide
- Review which namespaces and ServiceAccounts still have token Secrets and note any in application namespaces (not just
kube-system).
-
Identify who uses each token Secret and how
- For a specific Secret, inspect it (do not paste token contents into tickets/logs):
kubectl describe secret -n <namespace> <secret-name>
- Search for references to that Secret name in:
- Pod specs:
kubectl get pods -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
- Deployments/StatefulSets/DaemonSets/Jobs/CronJobs:
kubectl get deploy,sts,ds,job,cronjob -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
- Pod specs:
- Also search your app/IaC repos for the Secret name or
service-account-tokenusage.
- For a specific Secret, inspect it (do not paste token contents into tickets/logs):
-
Decide whether the workload can switch to projected ServiceAccount tokens
For each workload that uses a ServiceAccount token Secret:- Confirm if it really needs to call the Kubernetes API from inside the pod. If not, plan to remove the token mount entirely.
- If it does:
- Check if the code or sidecar can read the token from:
- The default projected token mount (
/var/run/secrets/kubernetes.io/serviceaccount/token), or - An explicit projected ServiceAccount token volume with
audienceandexpirationSeconds.
- The default projected token mount (
- If the current configuration is hard‑wiring a Secret volume/volumeMount, plan to migrate it to a projected ServiceAccount token volume.
- Check if the code or sidecar can read the token from:
-
Reconfigure manifests to stop using long‑lived token Secrets
- For each affected ServiceAccount:
- If the only reason it exists is to auto‑provision a legacy token Secret, ensure there are no pods mounting that Secret, then plan to delete the Secret.
- For each affected workload manifest (e.g., Deployment):
- Remove explicit
secretvolume entries andvolumeMountsthat point atkubernetes.io/service-account-tokenSecrets. - Ensure
spec.serviceAccountNameis set to the intended ServiceAccount. - If the app cannot use the default mount, define a projected token volume similar to:
volumes:- name: sa-tokenprojected:sources:- serviceAccountToken:path: tokenaudience: "<audience-for-this-app>"expirationSeconds: 3600volumeMounts:- name: sa-tokenmountPath: /var/run/myappreadOnly: true
- Remove explicit
- Apply changes:
kubectl apply -f <updated-manifest>.yaml
- For each affected ServiceAccount:
-
Remove unused ServiceAccount token Secrets after migration
- For each previously identified Secret, confirm no pod is mounting it:
kubectl get pods -n <namespace> -o yaml | grep -C3 "<secret-name>" || true
- When you are sure it is unused, delete it:
kubectl delete secret -n <namespace> <secret-name>
- In AKS, new long‑lived ServiceAccount token Secrets should not be auto‑created by recent Kubernetes versions; if you see new ones appear, investigate controllers or tools that may be creating them manually.
- For each previously identified Secret, confirm no pod is mounting it:
-
Verify that only projected tokens are in use
- Re-run the review command and confirm that no application namespaces rely on
kubernetes.io/service-account-tokenSecrets:kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token -o wide - Spot‑check updated pods to ensure they are using projected tokens (either the default mount or your projected volume) and that no
secret‑type volumes for ServiceAccount tokens remain.
- Re-run the review command and confirm that no application namespaces rely on
Using kubectl
# 1. List all long-lived ServiceAccount token Secrets
# Run on: any machine with kubectl access
kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token \
-o wide
What to look for (potential problems):
- Many Secrets of type
kubernetes.io/service-account-tokenin namespaces that host apps using modern SDKs or that already use projected tokens. - Namespaces where workloads are short‑lived or highly sensitive but still have multiple legacy token Secrets.
- ServiceAccounts used by critical apps that also have a corresponding token Secret and no sign of projected token use in the Pods.
# 2. For a specific Secret, inspect annotations and age
# Replace NAMESPACE and SECRET_NAME with values from the previous output
kubectl get secret SECRET_NAME -n NAMESPACE -o yaml
What to look for:
type: kubernetes.io/service-account-token- Very old
metadata.creationTimestamp(indicates a long‑lived token). - Annotation
kubernetes.io/service-account.namepointing to a ServiceAccount still in use. - No automated process rotating or deleting these Secrets.
# 3. Identify whether active Pods mount these Secrets directly
kubectl get pods -A -o jsonpath='{range .items[*]}{@.metadata.namespace}{" "}{@.metadata.name}{"\n"}{range @.spec.volumes[*]}{" "}{@.name}{" => "}{@.secret.secretName}{"\n"}{end}{"\n"}{end}' 2>/dev/null | grep -v " => <no value>"
What to look for:
- Pods that reference Secrets of type
kubernetes.io/service-account-tokenas volumes. - These indicate applications may be using long‑lived token Secrets rather than projected tokens.
# 4. For a specific Pod, confirm whether it uses projected ServiceAccount tokens
kubectl get pod POD_NAME -n NAMESPACE -o yaml
What to look for (good vs. problematic):
- Good (preferred):
- Volumes of type
projectedwith aserviceAccountTokensource:volumes:- name: sa-tokenprojected:sources:- serviceAccountToken:path: tokenaudience: <some-audience>expirationSeconds: <short duration>
- Volumes of type
- Problematic (legacy):
- Volumes of type
secretthat reference a ServiceAccount token Secret:volumes:- name: sa-tokensecret:secretName: <serviceaccount-name>-token-xxxxx - Containers reading the token from
/var/run/secrets/kubernetes.io/serviceaccount/tokendirectly with no projected token volume.
- Volumes of type
# 5. Inspect the ServiceAccount to see how tokens are being used
kubectl get serviceaccount SA_NAME -n NAMESPACE -o yaml
What to look for:
secrets:list containing entries like<serviceaccount-name>-token-xxxxx(indicates classic token Secret exists).- Whether Pods using this ServiceAccount (from step 4) mount that Secret directly instead of using a projected
serviceAccountTokenvolume.
# 6. (AKS-specific context) Check for legacy ServiceAccount token use across a namespace
# Helpful to focus review on application namespaces
kubectl get secrets -n NAMESPACE --field-selector type=kubernetes.io/service-account-token \
-o jsonpath='{range .items[*]}{@.metadata.name}{" "}{@.metadata.creationTimestamp}{"\n"}{end}'
What to look for:
- Very old tokens in application namespaces still attached to active ServiceAccounts.
- Patterns where every ServiceAccount automatically gets and keeps a token Secret, while Pods are not using projected volumes. This suggests refactoring to TokenRequest/projected tokens is needed.
These kubectl commands only surface current usage; a human must decide per application whether and how to migrate from long‑lived ServiceAccount token Secrets to projected tokens with audience and expiry.
Automation
#!/usr/bin/env bash
# Report legacy ServiceAccount token Secrets and pods using projected tokens in an AKS cluster.
# Run on: any machine with kubectl access and current context set to the AKS cluster.
set -euo pipefail
echo "=== 1) Legacy ServiceAccount token Secrets (type=kubernetes.io/service-account-token) ==="
echo
kubectl get secrets -A \
--field-selector type=kubernetes.io/service-account-token \
-o json | jq -r '
.items[]? |
[
.metadata.namespace,
.metadata.name,
.metadata.annotations."kubernetes.io/service-account.name",
.metadata.annotations."kubernetes.io/service-account.uid",
(.metadata.creationTimestamp // "unknown")
] | @tsv
' | column -t || echo "No legacy ServiceAccount token Secrets found."
cat <<'EOF'
Interpretation:
- Any row above represents a long-lived ServiceAccount token Secret.
- These are what you should be avoiding in favor of projected (TokenRequest) tokens.
- Pay special attention to:
* Very old creationTimestamp values (indicates long-lived tokens).
* ServiceAccounts that should not need cluster access, or have broad RBAC.
EOF
echo "=== 2) Pods using projected ServiceAccount tokens (serviceAccountToken projection) ==="
echo
kubectl get pods -A -o json | jq -r '
.items[]
| . as $pod
| [
$pod.metadata.namespace,
$pod.metadata.name,
(
[$pod.spec.volumes[]?
| select(.projected?.sources[]? .serviceAccountToken != null)
| .name
] | unique | join(",")
)
]
| select(.[2] != "")
| @tsv
' | column -t || echo "No pods with projected ServiceAccountToken volumes found."
cat <<'EOF'
Interpretation:
- Rows above show pods that are already using projected ServiceAccount tokens.
- Namespaces/pods NOT listed here but that use ServiceAccounts likely still rely on:
* The legacy auto-mounted Secret token volume, or
* Out-of-cluster storage of long-lived ServiceAccount tokens.
EOF
echo "=== 3) ServiceAccounts with legacy token auto-mount still enabled (serviceAccountToken secrets present) ==="
echo
kubectl get serviceaccounts -A -o json | jq -r '
.items[]
| . as $sa
| [
$sa.metadata.namespace,
$sa.metadata.name,
(
[$sa.secrets[]?.name] | join(",")
)
]
| select(.[2] != "")
| @tsv
' | column -t || echo "No ServiceAccounts with attached Secrets recorded."
cat <<'EOF'
Interpretation:
- Any ServiceAccount listed above has Secrets attached; many of these may be
kubernetes.io/service-account-token Secrets.
- Cross-check the secret names here against the legacy token list in section (1).
- ServiceAccounts with attached token Secrets are candidates for migration to
projected TokenRequest-based tokens with audience and expiry.
EOF
echo "=== Review guidance ==="
cat <<'EOF'
A configuration is potentially problematic when:
- A ServiceAccount has one or more kubernetes.io/service-account-token Secrets, AND
- Workloads using that ServiceAccount are not using projected serviceAccountToken volumes
(i.e., they are absent from section (2)).
Use this report to:
- Identify which ServiceAccounts and namespaces still depend on legacy token Secrets.
- Prioritize those for migration to projected tokens with explicit audiences and expiries.
Note: This script only surfaces current state; migration to projected tokens must be
planned and applied manually per application and ServiceAccount.
EOF