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
-
Identify noncompliant pods
- Run 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| ([ ($m.ownerReferences // [])[] | select(.controller) ] | first) as $own| (.spec.automountServiceAccountToken == false) as $ok| select($ok | not)| "ns=\($m.namespace) name=\($m.name)"+ (if $own == null then "" else " owner=\($own.kind)/\($own.name)" end)][]' -
For a standalone Pod: capture the manifest
- Run on: any machine with kubectl access
Replace NAMESPACE and POD_NAME with values from step 1:
kubectl get pod POD_NAME -n NAMESPACE -o yaml > /tmp/pod-POD_NAME.yaml - Run on: any machine with kubectl access
-
Edit the pod manifest to disable token automount
- Run on: any machine with kubectl access
- Open the file and add or update the field under
spec:
sed -i 's/^spec:$/spec:\n automountServiceAccountToken: false/' /tmp/pod-POD_NAME.yaml- If
automountServiceAccountTokenalready exists underspec:, edit it so it reads:
spec:automountServiceAccountToken: false -
Recreate the pod with the updated manifest
- Run on: any machine with kubectl access
kubectl delete pod POD_NAME -n NAMESPACE --wait=truekubectl apply -f /tmp/pod-POD_NAME.yaml -
For controller-managed workloads (e.g., Deployment, DaemonSet, Job): patch the controller instead of the pod
- Run on: any machine with kubectl access
- Example for a Deployment; adjust KIND, NAME, and NAMESPACE based on the
owner=from step 1:
kubectl -n NAMESPACE patch deployment OWNER_NAME \--type merge \-p '{"spec":{"template":{"spec":{"automountServiceAccountToken":false}}}}'- The controller will automatically recreate pods with the new setting.
-
Verify compliance
- Run 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| select($ok | not)] as $rows| if ($rows | length) == 0 then "is_compliant=true" else $rows[] end'
Using kubectl
On any machine with kubectl access to the OKE cluster:
-
Identify a non‑compliant pod and its owner (from the audit output), for example:
- Namespace:
app-namespace - Pod name:
web-7c8b4f9d9b-xj9lt - Owner:
Deployment/app-namespace/web
- Namespace:
-
Export the owning workload manifest, edit it locally, and re‑apply (pod spec fields like
automountServiceAccountTokenmust be set on the controller, not on the individual pod):
kubectl -n app-namespace get deploy web -o yaml > web-deploy.yaml
Edit web-deploy.yaml and in spec.template.spec add (or change) this field:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: app-namespace
spec:
template:
spec:
automountServiceAccountToken: false
# ... other fields remain unchanged
Apply the updated manifest:
kubectl apply -f web-deploy.yaml
Kubernetes will recreate the pods managed by this Deployment with automountServiceAccountToken: false. Repeat this pattern for other controller types (StatefulSet, DaemonSet, Job, CronJob) by adjusting the kind in the kubectl get command and editing the same spec.template.spec section.
- If the pod is created directly (no controller ownerReference), edit the pod spec and re‑create it:
kubectl -n app-namespace get pod web-standalone -o yaml > web-standalone-pod.yaml
Edit web-standalone-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: web-standalone
namespace: app-namespace
spec:
automountServiceAccountToken: false
# ... other fields remain unchanged
Delete and recreate the pod:
kubectl -n app-namespace delete pod web-standalone
kubectl apply -f web-standalone-pod.yaml
- Optional: set this at the ServiceAccount level instead of each pod, if all pods using it do not need API access:
kubectl -n app-namespace get serviceaccount web-sa -o yaml > web-sa.yaml
Edit web-sa.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-sa
namespace: app-namespace
automountServiceAccountToken: false
Apply:
kubectl apply -f web-sa.yaml
- Verification (rerun the audit command):
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
#
# Disable automountServiceAccountToken for pods in OKE that do NOT need
# the Kubernetes API, by defaulting to a secure ServiceAccount setting.
#
# RUN ON: any machine with kubectl access and jq installed.
#
# NOTES:
# - This script does NOT try to guess which pods use the API.
# - It only operates on namespaces you explicitly approve.
# - It configures the *ServiceAccount* used by pods:
# automountServiceAccountToken: false
# so new pods using that SA will not auto-mount tokens.
# - Existing running pods will keep their current mounts until restarted.
set -euo pipefail
# ---------- CONFIGURATION ----------
# Comma-separated list of namespaces you have reviewed and decided that
# workloads there do NOT need to call the Kubernetes API.
# Example: SAFE_NAMESPACES="dev,test,frontend"
SAFE_NAMESPACES="${SAFE_NAMESPACES:-}"
if [[ -z "${SAFE_NAMESPACES}" ]]; then
echo "ERROR: SAFE_NAMESPACES is empty."
echo "Set SAFE_NAMESPACES to a comma-separated list of non-system namespaces"
echo "whose pods you have reviewed and determined do NOT need the Kubernetes API."
echo "Example:"
echo " SAFE_NAMESPACES='dev,test,frontend' $0"
exit 1
fi
# Convert SAFE_NAMESPACES to an array
IFS=',' read -r -a NS_LIST <<< "${SAFE_NAMESPACES}"
# Namespaces that must always be excluded
EXCLUDED_NS_REGEX='^(kube-system|kube-public|kube-node-lease)$'
echo "Using SAFE_NAMESPACES: ${SAFE_NAMESPACES}"
echo
# ---------- FUNCTIONS ----------
ensure_namespace_allowed() {
local ns="$1"
if [[ "${ns}" =~ ${EXCLUDED_NS_REGEX} ]]; then
return 1
fi
return 0
}
# Patch or create ServiceAccounts with automountServiceAccountToken: false
harden_service_accounts_in_namespace() {
local ns="$1"
if ! ensure_namespace_allowed "${ns}"; then
echo "Skipping system namespace: ${ns}"
return
fi
echo "Processing namespace: ${ns}"
# Get all ServiceAccounts in the namespace (including 'default')
local sa_list
if ! sa_list=$(kubectl get sa -n "${ns}" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null); then
echo " WARNING: Failed to list ServiceAccounts in namespace ${ns}, skipping."
return
fi
if [[ -z "${sa_list}" ]]; then
echo " No ServiceAccounts found in namespace ${ns}."
return
fi
while IFS= read -r sa; do
[[ -z "${sa}" ]] && continue
# Check current value (may be null/unset)
local current
current=$(kubectl get sa "${sa}" -n "${ns}" -o jsonpath='{.automountServiceAccountToken}' 2>/dev/null || echo "")
if [[ "${current}" == "false" ]]; then
echo " SA ${ns}/${sa}: automountServiceAccountToken already false"
continue
fi
echo " Patching SA ${ns}/${sa}: setting automountServiceAccountToken=false"
kubectl patch sa "${sa}" -n "${ns}" \
--type merge \
-p '{"automountServiceAccountToken": false}' >/dev/null
done <<< "${sa_list}"
}
# Verify by re-running a focused version of the audit logic
verify_namespace() {
local ns="$1"
echo
echo "Verification for namespace: ${ns}"
kubectl get pods -n "${ns}" -o json | jq -r '
[ .items[]
| .metadata as $m
| (.spec.automountServiceAccountToken == false) as $ok
| "kind=Pod ns=\($m.namespace) name=\($m.name) " +
"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 "no_pods_in_namespace" else $rows[] end'
}
# ---------- MAIN ----------
for ns in "${NS_LIST[@]}"; do
ns_trimmed="$(echo "${ns}" | xargs)"
[[ -z "${ns_trimmed}" ]] && continue
if ! kubectl get ns "${ns_trimmed}" >/dev/null 2>&1; then
echo "Namespace ${ns_trimmed} not found; skipping."
continue
fi
harden_service_accounts_in_namespace "${ns_trimmed}"
verify_namespace "${ns_trimmed}"
done
echo
echo "Completed. For cluster-wide verification, run:"
echo " kubectl get pods --all-namespaces -o json | jq -r '...<full audit expression>...'"