Pods That Do Not Use The API Should Disable Token Automount
More Info:
Verifies automountServiceAccountToken is false for pods that do not call the Kubernetes API. A mounted token is a ready-made credential for an attacker who lands in the pod.
Risk Level
Medium
Address
Security
Compliance Standards
- Cloudanix Best Practice
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On any machine with kubectl access, list the non-compliant pods and choose one to review (replace NAMESPACE and POD_NAME in the next steps accordingly):
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.automountServiceAccountToken == false) as $ok| select($ok | not)| "ns=\($m.namespace) name=\($m.name)"][]' -
Still on any machine with kubectl access, inspect the chosen pod to determine whether it legitimately calls the Kubernetes API (look for in-cluster client libraries, API server URLs, or service account token usage in args/env/config):
kubectl -n NAMESPACE get pod POD_NAME -o yamlIf the workload needs to call the Kubernetes API, document the exception and do not change
automountServiceAccountTokenfor this pod. -
If the pod does not need Kubernetes API access and is controlled by a higher-level object (Deployment, StatefulSet, DaemonSet, Job, CronJob, etc.), identify that owner:
kubectl -n NAMESPACE get pod POD_NAME -o jsonpath='{.metadata.ownerReferences[0].kind}{" "}{.metadata.ownerReferences[0].name}{"\n"}'Then edit the owner resource’s pod template to disable token automount:
kubectl -n NAMESPACE edit OWNER_KIND OWNER_NAMEIn the opened YAML, under
spec.template.spec, add or set:automountServiceAccountToken: falseSave and exit to trigger a rolling update of the pods.
-
If the pod is not controlled by a higher-level object (no ownerReferences or kind is “Pod”), edit the pod spec directly (note this will not persist across re-creates from external systems):
kubectl -n NAMESPACE edit pod POD_NAMEUnder
spec, add or set:automountServiceAccountToken: false -
As an alternative (and where appropriate), you may set this at the ServiceAccount level so all pods using it disable token automount by default. On any machine with kubectl access:
kubectl -n NAMESPACE edit serviceaccount SERVICEACCOUNT_NAMEAdd or set:
automountServiceAccountToken: falseThen ensure pods that should not have tokens use this ServiceAccount in their pod templates.
-
Verify compliance on any machine with kubectl access:
kubectl get pods --all-namespaces -o json | jq -r '[ .items[]| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)| .metadata as $m| (.spec.automountServiceAccountToken == false) as $ok| "kind=Pod ns=\($m.namespace) name=\($m.name) automountServiceAccountToken=\(if .spec.automountServiceAccountToken == null then "unset" else .spec.automountServiceAccountToken end) is_compliant=\(if $ok then "true" else "false" end)"] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'Confirm that pods which do not need API access now show
automountServiceAccountToken=falseandis_compliant=true.
Using kubectl
On any machine with kubectl access:
-
Identify the noncompliant pod and its owner (from the audit output), for example:
- Namespace:
my-namespace - Pod name:
my-app-6f7b9d8c7d-abcde - Owner:
Deployment/my-app
- Namespace:
-
Export the owning workload manifest (example for a Deployment):
kubectl -n my-namespace get deployment my-app -o yaml > my-app-deployment.yaml
- Edit the manifest locally (
my-app-deployment.yaml) and setautomountServiceAccountToken: falsein the pod spec. For example:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: my-namespace
spec:
template:
spec:
automountServiceAccountToken: false
containers:
- name: my-app
image: myregistry/my-app:1.0.0
- Apply the updated manifest:
kubectl apply -f my-app-deployment.yaml
- (Optional) If the pod is created directly (no owner), patch it in place:
kubectl -n my-namespace patch pod my-pod \
--type merge \
-p '{"spec":{"automountServiceAccountToken":false}}'
- Verification (same style as the audit, on any kubectl machine):
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.automountServiceAccountToken == false) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " serviceAccount=\(.spec.serviceAccountName // "default")"
+ " automountServiceAccountToken=\(if .spec.automountServiceAccountToken == null then "unset" else .spec.automountServiceAccountToken end)"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Automation
#!/usr/bin/env bash
set -euo pipefail
# This script:
# - Lists all Pods that do NOT have automountServiceAccountToken=false set at pod level
# - Skips kube-system, kube-public, kube-node-lease
# - For each such Pod whose owner is a higher-level controller (Deployment, StatefulSet, etc.),
# it PATCHes the owning workload spec.template to set automountServiceAccountToken=false.
# - It does NOT touch bare Pods (no controller ownerReference), because they may be ephemeral or system-created.
#
# Run on: any machine with kubectl access to the AKS cluster.
# Requirements: kubectl, jq
# Safety guard: ensure we can reach the cluster
kubectl version --short >/dev/null
echo "Discovering non-compliant Pods (excluding kube-system, kube-public, kube-node-lease)..."
NON_COMPLIANT_JSON=$(kubectl get pods --all-namespaces -o json | jq -c '
.items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| select(.spec.automountServiceAccountToken != false)
| {
namespace: .metadata.namespace,
pod: .metadata.name,
owner: (
[(.metadata.ownerReferences // [])[] | select(.controller)] | first
)
}
')
if [[ -z "${NON_COMPLIANT_JSON}" ]]; then
echo "No non-compliant Pods found. Nothing to do."
exit 0
fi
echo "Evaluating Pods and their owners..."
echo
# Helper: map Pod owner.kind to the scalable controller resource that owns the Pod template
map_owner_kind_to_resource() {
local kind="$1"
case "${kind}" in
Deployment) echo "deployments" ;;
ReplicaSet) echo "replicasets" ;;
StatefulSet) echo "statefulsets" ;;
DaemonSet) echo "daemonsets" ;;
Job) echo "jobs" ;;
CronJob) echo "cronjobs" ;;
*)
# Unknown or bare Pod owner; return empty to skip
echo ""
;;
esac
}
# Process each Pod
while IFS= read -r item; do
ns=$(jq -r '.namespace' <<< "${item}")
pod=$(jq -r '.pod' <<< "${item}")
owner_present=$(jq -r 'has("owner") and .owner != null' <<< "${item}")
if [[ "${owner_present}" != "true" ]]; then
echo "Skipping bare Pod ${ns}/${pod} (no controller ownerReference). Please review manually if it should disable token automount."
continue
fi
owner_kind=$(jq -r '.owner.kind' <<< "${item}")
owner_name=$(jq -r '.owner.name' <<< "${item}")
resource=$(map_owner_kind_to_resource "${owner_kind}")
if [[ -z "${resource}" ]]; then
echo "Skipping Pod ${ns}/${pod} (owner kind ${owner_kind} not handled by this script). Review manually."
continue
fi
echo "Processing ${owner_kind} ${ns}/${owner_name} (from Pod ${pod})..."
# Patch the controller's Pod template to set automountServiceAccountToken=false
# This is idempotent: repeated patches keep the same value.
patch='{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'
kubectl -n "${ns}" patch "${resource}" "${owner_name}" --type=merge -p "${patch}" >/dev/null
echo " Patched ${owner_kind} ${ns}/${owner_name} to set spec.template.spec.automountServiceAccountToken=false"
done <<< "${NON_COMPLIANT_JSON}"
echo
echo "Waiting briefly for controllers to reconcile and new Pods to appear..."
sleep 10
echo
echo "Re-running compliance audit to verify..."
kubectl get pods --all-namespaces -o json | jq -r '
[ .items[]
| select(.metadata.namespace as $n | ["kube-system","kube-public","kube-node-lease"] | index($n) | not)
| .metadata as $m
| (.spec.nodeName // "") as $node
| (($m.labels // {}) | to_entries | map("\(.key):\(.value)") | join(",")) as $labels
| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own
| (.spec.automountServiceAccountToken == false) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) uid=\($m.uid) apiVersion=v1"
+ (if ($m.creationTimestamp // "") == "" then "" else " created=\($m.creationTimestamp)" end)
+ (if $node == "" then "" else " node=\($node)" end)
+ (if $labels == "" then "" else " labels=\($labels)" end)
+ (if $own == null then "" else " owner=\($own.kind)/\($m.namespace)/\($own.name)/\($own.uid)" end)
+ " serviceAccount=\(.spec.serviceAccountName // "default")"
+ " automountServiceAccountToken=\(if .spec.automountServiceAccountToken == null then "unset" else .spec.automountServiceAccountToken end)"
+ " is_compliant=\(if $ok then "true" else "false" end)"
] as $rows
| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'