Skip to main content

Service Account Tokens Are Only Mounted Where Necessary

More Info:

Service accounts tokens should not be mounted in pods except where the workload running in the pod explicitly needs to communicate with the API server

Risk Level

Medium

Address

Security

Compliance Standards

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS AKS
  • CIS Critical Security Controls v8
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • Essential 8
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation

Remediation

Manual Steps
  1. List pods and identify candidates that likely do NOT need API access

    • Run on: any machine with kubectl access
    kubectl get pods --all-namespaces -o wide

    Focus on application workloads (your business apps, stateless services, jobs) and exclude obvious control-plane/addon components (e.g., coredns, metrics-server, ingress controllers, CNI, CSI drivers, autoscalers) that usually require API access.

  2. Inspect each candidate pod’s token mounting behavior and service account

    • Run on: any machine with kubectl access
      Replace <namespace> and <pod> with real values.
    kubectl get pod <pod> -n <namespace> -o yaml \
    | sed -n '1,/^spec:/p;/^spec:/,/^status:/p' \
    | sed '/^status:/q'

    In the output, check:

    • spec.automountServiceAccountToken at pod level
    • Each container’s automountServiceAccountToken (if present)
    • spec.serviceAccountName
      Note pods where automountServiceAccountToken is missing or true and where API access is probably unnecessary (e.g., simple web frontends, batch processors, data-plane only components).
  3. Review workload templates (Deployment/StatefulSet/DaemonSet/Job/CronJob) for those pods

    • Run on: any machine with kubectl access
      First identify the owning controller:
    kubectl get pod <pod> -n <namespace> -o jsonpath='{.metadata.ownerReferences}'
    echo

    Then describe the controller and inspect its pod template:

    # Example for a Deployment owner
    kubectl get deploy <deployment-name> -n <namespace> -o yaml \
    | sed -n '/spec:/,/status:/p' \
    | sed '/^status:/q'

    For the pod template under .spec.template.spec, look for:

    • automountServiceAccountToken at pod level
    • Any container-level automountServiceAccountToken
    • Whether a particular serviceAccountName is used globally and if that SA is intended to talk to the API.
  4. Decide whether a given workload actually needs API server access
    For each controller/pod from step 3, evaluate with the workload owner/developer:

    • Does the app call Kubernetes APIs (e.g., watches pods, configmaps, endpoints)?
    • Does it rely on in-cluster auth via the pod’s service account token?
    • Is there any library, operator SDK, or sidecar that needs the token?
      If no to all, mark the workload as safe to disable automatic token mounting.
  5. Update manifests to disable token mounting where not needed and roll out

    • Run on: any machine with kubectl access
      For each safe workload, edit the controller manifest (Git/IaC preferred; otherwise directly):
    kubectl edit deploy <deployment-name> -n <namespace>

    Under .spec.template.spec, add or set:

    spec:
    automountServiceAccountToken: false

    Save and exit. This will recreate pods for that workload with tokens not mounted.
    If using raw manifests, modify them similarly and apply:

    kubectl apply -f <path-to-updated-manifest>.yaml

    Ensure application owners confirm the workload still functions correctly after the change.

  6. Verify that service account tokens are no longer mounted for updated pods

    • Run on: any machine with kubectl access
      After rollouts complete:
    kubectl get pods -n <namespace>
    kubectl get pod <pod> -n <namespace> -o yaml \
    | sed -n '1,/^spec:/p;/^spec:/,/^status:/p' \
    | sed '/^status:/q'

    Confirm:

    • spec.automountServiceAccountToken: false is present in the pod spec
    • No projected serviceAccountToken volume or /var/run/secrets/kubernetes.io/serviceaccount volume/mount is defined for containers that should not have API access.
Using kubectl
# 1) List all namespaces
# Run on: any machine with kubectl access
kubectl get ns
# 2) List all workloads that can create pods and show whether they auto-mount SA tokens
# Run on: any machine with kubectl access
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
echo "=== Namespace: $ns ==="
echo "--- Deployments ---"
kubectl get deploy -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{" automountServiceAccountToken="}{.spec.template.spec.automountServiceAccountToken}{"\n"}{end}' || true
echo "--- StatefulSets ---"
kubectl get sts -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{" automountServiceAccountToken="}{.spec.template.spec.automountServiceAccountToken}{"\n"}{end}' || true
echo "--- DaemonSets ---"
kubectl get ds -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{" automountServiceAccountToken="}{.spec.template.spec.automountServiceAccountToken}{"\n"}{end}' || true
echo
done

Interpretation:

  • automountServiceAccountToken=false (explicit) → token will not be mounted for that workload’s pods (good for workloads that do not need API access).
  • automountServiceAccountToken=true (explicit) → token will be mounted (only acceptable if the workload truly needs API access).
  • Empty/blank value → not set at the pod template level; you must check service accounts and global defaults.
# 3) Check ServiceAccount-level automount settings
# Run on: any machine with kubectl access
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
echo "=== Namespace: $ns ==="
kubectl get sa -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{" automountServiceAccountToken="}{.automountServiceAccountToken}{"\n"}{end}' || true
echo
done

Interpretation:

  • automountServiceAccountToken=false on a ServiceAccount → pods using this SA will not mount tokens unless overridden at pod level (good default for non-API workloads).
  • automountServiceAccountToken=true or empty/blank → token will be mounted by default for pods that use this SA, unless the pod explicitly disables it. Treat this as a potential problem for workloads that do not need API access.
# 4) Inspect a specific workload and the effective pod spec
# Run on: any machine with kubectl access

# Replace NAMESPACE and DEPLOYMENT_NAME with real values
kubectl get deploy DEPLOYMENT_NAME -n NAMESPACE -o yaml | \
sed -n '1,80p' # inspect metadata, serviceAccountName, and template.spec.automountServiceAccountToken

# Check one of its running pods to confirm if the token volume is actually mounted
POD=$(kubectl get pod -n NAMESPACE -l app=APP_LABEL -o jsonpath='{.items[0].metadata.name}')
kubectl get pod "$POD" -n NAMESPACE -o jsonpath='{.spec.automountServiceAccountToken}{"\n"}'
kubectl get pod "$POD" -n NAMESPACE -o jsonpath='{.spec.volumes[*].name}{"\n"}'
kubectl exec -n NAMESPACE "$POD" -- ls -R /var/run/secrets/kubernetes.io || true

Interpretation:

  • Pod .spec.automountServiceAccountToken=true or empty AND you see a volume like kube-api-access-... and files under /var/run/secrets/kubernetes.io → this pod is mounting a service account token.
  • If this pod/workload does not need to call the Kubernetes API, this is a problem and you should plan to set spec.automountServiceAccountToken: false in the workload template (Deployment/DaemonSet/StatefulSet) or update the ServiceAccount as appropriate.
# 5) Cluster-wide spot-check of running pods where the token is mounted
# Run on: any machine with kubectl access
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{" automount="}{.spec.automountServiceAccountToken}{" sa="}{.spec.serviceAccountName}{"\n"}{end}' | head -n 50

Interpretation:

  • Lines where automount= is true or blank and the pod is not a system component (e.g., not in kube-system or similar) should be reviewed to decide if that workload truly requires API access.
Automation
#!/usr/bin/env bash
# Purpose: Report which workloads will mount service account tokens,
# so you can review whether they actually need API access.

set -euo pipefail

echo "=== 1) Workloads with explicit automountServiceAccountToken:false (sa token disabled) ==="
kubectl get deploy,sts,ds,job,cronjob -A -o json \
| jq -r '
.items[]
| . as $w
| (
# Deployment/RS/RC/StatefulSet/DaemonSet/Job/CronJob pod template
.spec.template.spec.automountServiceAccountToken
// (
# Fallbacks for older API forms if needed
.spec.jobTemplate.spec.template.spec.automountServiceAccountToken
)
) as $auto
| select($auto == false)
| [$w.kind, $w.metadata.namespace, $w.metadata.name, "automountServiceAccountToken=false"]
| @tsv' \
| column -t || echo "No workloads found with automountServiceAccountToken:false"

echo
echo "=== 2) Workloads that EXPLICITLY enable token mount (automountServiceAccountToken:true) ==="
kubectl get deploy,sts,ds,job,cronjob -A -o json \
| jq -r '
.items[]
| . as $w
| (
.spec.template.spec.automountServiceAccountToken
// .spec.jobTemplate.spec.template.spec.automountServiceAccountToken
) as $auto
| select($auto == true)
| [$w.kind, $w.metadata.namespace, $w.metadata.name, "automountServiceAccountToken=true (explicit)"]
| @tsv' \
| column -t || echo "No workloads found with explicit automountServiceAccountToken:true"

echo
echo "=== 3) Workloads that INHERIT automountServiceAccountToken from their ServiceAccount ==="
echo "Note: These are any workloads without an explicit setting in the pod template."
echo "They may still mount tokens if their ServiceAccount (or the cluster default) allows it."
kubectl get deploy,sts,ds,job,cronjob -A -o json \
| jq -r '
.items[]
| . as $w
| (
.spec.template.spec.automountServiceAccountToken
// .spec.jobTemplate.spec.template.spec.automountServiceAccountToken
) as $auto
| select($auto == null)
| [
$w.kind,
$w.metadata.namespace,
$w.metadata.name,
(
.spec.template.spec.serviceAccountName
// .spec.jobTemplate.spec.template.spec.serviceAccountName
// "default"
)
]
| @tsv' \
| column -t \
|| echo "No workloads found that inherit automountServiceAccountToken"

echo
echo "=== 4) ServiceAccounts with explicit automountServiceAccountToken settings ==="
kubectl get sa -A -o json \
| jq -r '
.items[]
| . as $sa
| .automountServiceAccountToken as $auto
| select($auto != null)
| [$sa.metadata.namespace, $sa.metadata.name, "automountServiceAccountToken=" + (if $auto then "true" else "false" end)]
| @tsv' \
| column -t || echo "No ServiceAccounts found with explicit automountServiceAccountToken"

echo
echo "=== 5) Pods currently mounting service account tokens (running state) ==="
echo "This shows current pods where the projected service account token volume is present."
kubectl get pods -A -o json \
| jq -r '
.items[]
| . as $p
| [
$p.kind,
$p.metadata.namespace,
$p.metadata.name,
(
[ .spec.volumes[]
| select(.projected.sources[]? | has("serviceAccountToken"))
| .name
]
| unique
| join(",")
)
]
| select(.[3] != "")
| @tsv' \
| column -t || echo "No pods found with projected service account token volumes"

echo
echo "=== How to interpret this report ==="
cat <<'EOF'
Potential problems (to be manually reviewed):

1) Any workload in section (2) with:
- automountServiceAccountToken=true
AND
- The application does not truly need to call the Kubernetes API.

2) Any workload in section (3) where:
- The associated ServiceAccount in section (4) has automountServiceAccountToken=true, OR
- The ServiceAccount has no explicit setting (null) but your cluster default is to mount tokens,
AND
- The application does not need Kubernetes API access.

3) Any pod listed in section (5) where:
- The workload is not expected to talk to the Kubernetes API,
but has a projected service account token volume.

There is no safe, fully automated fix: for each suspicious workload,
review with the application owner whether API access is required.
If not, update the workload manifest to set:

spec:
automountServiceAccountToken: false

and, optionally, on the ServiceAccount:

automountServiceAccountToken: false

Then recreate the workloads so new pods are launched with the updated spec.
EOF

Additional Reading: