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 Critical Security Controls v8
  • CIS EKS
  • 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 service accounts that may not need API access (run on any machine with kubectl access):

    # Pods that currently auto-mount a token (candidate review set)
    kubectl get pods --all-namespaces -o json | jq '
    [.items[] | select(.spec.automountServiceAccountToken != false)] |
    [.[] | {ns:.metadata.namespace, pod:.metadata.name, sa:(.spec.serviceAccountName // "default")}]'

    # ServiceAccounts that might be used by those pods
    kubectl get sa --all-namespaces -o json | jq '
    [.items[] | {ns:.metadata.namespace, sa:.metadata.name,
    automount:(.automountServiceAccountToken // "unset")}]'
  2. For each pod, determine if it really needs API server access (manual review):

    • Inspect the container images and commands:
      kubectl -n <namespace> describe pod <pod-name>
    • Look for:
      • Use of kubectl, client SDKs, or API calls to Kubernetes
      • RBAC permissions or config mounting kubeconfig/service-account token
      • Controllers/operators, admission webhooks, or in-cluster automation
    • If you are unsure, consult the application owner before disabling token mounting.
  3. For workloads that do NOT need API access, set automountServiceAccountToken: false at the pod/workload level (preferred; run on any machine with kubectl access):

    • For a Deployment (similar for StatefulSet/DaemonSet/Job/CronJob):
      kubectl -n <namespace> get deploy <deploy-name> -o yaml > /tmp/deploy.yaml
      Edit /tmp/deploy.yaml and under spec.template.spec add:
      automountServiceAccountToken: false
      Then apply:
      kubectl apply -f /tmp/deploy.yaml
    • For a standalone Pod manifest, add the same field under spec and re-create the pod if needed.
  4. Optionally harden ServiceAccounts that should never mount tokens by default (run on any machine with kubectl access):

    • For each such ServiceAccount:
      kubectl -n <namespace> get sa <sa-name> -o yaml > /tmp/sa.yaml
      Edit /tmp/sa.yaml to include:
      automountServiceAccountToken: false
      under metadata (same level as name/namespace), then:
      kubectl apply -f /tmp/sa.yaml
    • Ensure any pods that DO require API access and use this ServiceAccount explicitly set:
      spec:
      automountServiceAccountToken: true
      in their pod template.
  5. Coordinate and monitor rollout impact:

    • Changing pod templates causes pods to be re-created/rolled; schedule changes during a maintenance window if needed.
    • After changes, confirm affected pods are running and application functionality is intact:
      kubectl -n <namespace> get pods
  6. Verification (run on any machine with kubectl access):

    pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
    [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

    if [ "$pods_with_token_mount" -gt 0 ]; then
    echo "automountServiceAccountToken"
    else
    echo "OK: all pods explicitly set automountServiceAccountToken=false or do not mount tokens unnecessarily"
    fi
Using kubectl

On any machine with kubectl access:

  1. Identify pods and service accounts that do not need API server access

    kubectl get pods --all-namespaces -o wide
    kubectl get sa --all-namespaces

    Review workloads and decide which ones truly need to talk to the Kubernetes API. Only those should keep token mounting enabled.

  2. Patch individual Pods that do not need the token (ephemeral; also fix their controllers)

    # Example: disable token mount on a running pod
    kubectl patch pod <pod-name> -n <namespace> \
    --type=merge \
    -p '{"spec":{"automountServiceAccountToken":false}}'
  3. Configure ServiceAccounts to not mount tokens by default
    For service accounts whose workloads do not need API access:

    kubectl patch serviceaccount <sa-name> -n <namespace> \
    --type=merge \
    -p '{"automountServiceAccountToken":false}'

    Or declaratively, create/update a manifest such as:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: example-sa
    namespace: example-namespace
    automountServiceAccountToken: false

    Apply it:

    kubectl apply -f example-sa.yaml
  4. Configure controllers (Deployments, DaemonSets, StatefulSets, Jobs, CronJobs) so new pods do not mount tokens
    Edit or patch the pod template for each controller whose workloads don’t need API access.

    Example patch for a Deployment:

    kubectl patch deployment <deploy-name> -n <namespace> \
    --type=merge \
    -p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'

    Example declarative Deployment snippet:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
    name: example-deployment
    namespace: example-namespace
    spec:
    template:
    spec:
    automountServiceAccountToken: false
    serviceAccountName: example-sa
    containers:
    - name: app
    image: nginx:stable

    Apply it:

    kubectl apply -f example-deployment.yaml
  5. Recreate pods if needed
    For controllers, new pods will inherit the new setting automatically after rollout. To force recreation:

    kubectl rollout restart deployment <deploy-name> -n <namespace>
  6. Verification (same logic as the audit)

    pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
    [.items[] | select(.spec.automountServiceAccountToken != false)] | length')

    if [ "$pods_with_token_mount" -gt 0 ]; then
    echo "automountServiceAccountToken still enabled on some pods"
    else
    echo "All pods have automountServiceAccountToken set to false"
    fi
Automation
#!/usr/bin/env bash
set -euo pipefail

# Automation: Disable automountServiceAccountToken where not explicitly required
# Scope: Run on any machine with kubectl and jq installed, authenticated to the cluster.
#
# WARNING:
# - This will PATCH pods and serviceaccounts that do not explicitly set
# automountServiceAccountToken=false to set it to false.
# - It cannot distinguish workloads that truly need API access from those that don't.
# Review candidates before/after running, or restrict via namespace/label filters.

# Optional: limit scope by namespace (uncomment and set, or leave empty for all)
NAMESPACE_FILTER=""

# Helper to run kubectl with optional namespace filter
k() {
if [[ -n "$NAMESPACE_FILTER" ]]; then
kubectl -n "$NAMESPACE_FILTER" "$@"
else
kubectl "$@"
fi
}

echo "Discovering pods with automountServiceAccountToken not explicitly set to false..."

# List pods that currently have automountServiceAccountToken != false
pods_json=$(k get pods --all-namespaces -o json)

pods_to_patch=$(echo "$pods_json" | jq -r '
.items[]
| select(.spec.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"')

if [[ -z "$pods_to_patch" ]]; then
echo "No pods require patch for automountServiceAccountToken."
else
echo "Patching pods to set spec.automountServiceAccountToken=false..."
while read -r ns name; do
[[ -z "$ns" || -z "$name" ]] && continue
echo " Patching pod: $ns/$name"
k -n "$ns" patch pod "$name" --type=merge -p '{
"spec": {
"automountServiceAccountToken": false
}
}' >/dev/null
done <<< "$pods_to_patch"
fi

echo "Discovering ServiceAccounts with automountServiceAccountToken not explicitly set to false..."

sa_json=$(k get serviceaccounts --all-namespaces -o json)

sa_to_patch=$(echo "$sa_json" | jq -r '
.items[]
| select(.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"')

if [[ -z "$sa_to_patch" ]]; then
echo "No ServiceAccounts require patch for automountServiceAccountToken."
else
echo "Patching ServiceAccounts to set automountServiceAccountToken=false..."
while read -r ns name; do
[[ -z "$ns" || -z "$name" ]] && continue
echo " Patching ServiceAccount: $ns/$name"
k -n "$ns" patch serviceaccount "$name" --type=merge -p '{
"automountServiceAccountToken": false
}' >/dev/null
done <<< "$sa_to_patch"
fi

echo "Verification: re-running audit for pods..."

pods_with_token_mount=$(kubectl get pods --all-namespaces -o json | jq '
[.items[] | select(.spec.automountServiceAccountToken != false)] | length')

if [ "$pods_with_token_mount" -gt 0 ]; then
echo "Verification FAILED: Some pods still have automountServiceAccountToken != false"
echo "Count: $pods_with_token_mount"
exit 1
else
echo "Verification PASSED: All pods now have automountServiceAccountToken=false"
fi

echo "Listing ServiceAccounts that still allow token automount (if any)..."
kubectl get serviceaccounts --all-namespaces -o json | jq -r '
.items[]
| select(.automountServiceAccountToken != false)
| "\(.metadata.namespace) \(.metadata.name)"' || true

echo "Automation completed."

Additional Reading: