API Server Should Enable The ServiceAccount Admission Plugin
More Info:
Verifies that the ServiceAccount admission plugin is not disabled so automated service account tokens are managed and enforced for pods.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, back up the current API server manifest (run as root):
cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak.$(date +%F-%H%M%S) -
On every control plane node, open the API server manifest for editing:
vi /etc/kubernetes/manifests/kube-apiserver.yaml -
In the
spec.containers[0].commandlist, locate any--disable-admission-plugins=entry.- If it exists and contains
ServiceAccount, removeServiceAccountfrom the comma-separated list (leave the other plugins unchanged). - If the list becomes empty after removal, delete the entire
--disable-admission-plugins=...argument line.
Save and exit the editor.
(Changing this file will automatically restart the kube-apiserver static pod on that node.)
- If it exists and contains
-
Wait for the kube-apiserver pod to restart and become Running on the control plane node:
crictl ps | grep kube-apiserverEnsure the most recent kube-apiserver container is in a
Runningstate. -
From any control plane node, verify that the
ServiceAccountadmission plugin is not disabled:/bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--disable-admission-plugins'Confirm that:
- Either no
--disable-admission-pluginsflag is present, or - The flag is present but its value does not include
ServiceAccount.
- Either no
Using kubectl
kubectl cannot modify the kube-apiserver static pod manifest or its process flags. This finding must be remediated by editing /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node; see the Manual Steps section for the required host-level changes and verification.
Automation
#!/usr/bin/env bash
#
# Automation: Ensure ServiceAccount admission plugin is NOT disabled
# Scope: every control plane node
# Effect: Editing /etc/kubernetes/manifests/kube-apiserver.yaml will restart kube-apiserver (static pod)
#
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-serviceaccount-$(date +%Y%m%d%H%M%S)"
echo "[INFO] Verifying kube-apiserver manifest exists at ${APISERVER_MANIFEST}"
if [ ! -f "${APISERVER_MANIFEST}" ]; then
echo "[ERROR] kube-apiserver manifest not found at ${APISERVER_MANIFEST}. Aborting."
exit 1
fi
echo "[INFO] Creating backup in ${BACKUP_DIR}"
mkdir -p "${BACKUP_DIR}"
cp -p "${APISERVER_MANIFEST}" "${BACKUP_DIR}/"
# Function to normalize YAML for simple text replacement:
# We will operate only on the '--disable-admission-plugins=' argument line(s).
# Idempotency: if ServiceAccount is already not present, no functional change.
TMP_MANIFEST="$(mktemp)"
cp "${APISERVER_MANIFEST}" "${TMP_MANIFEST}"
# Remove 'ServiceAccount' from any --disable-admission-plugins flag.
# Handles comma-separated lists with or without spaces.
echo "[INFO] Ensuring ServiceAccount is not listed in --disable-admission-plugins"
python3 - "$TMP_MANIFEST" > "${TMP_MANIFEST}.new" << 'PYCODE'
import sys, re
path = sys.argv[1]
with open(path, 'r') as f:
lines = f.readlines()
def clean_plugins(value: str) -> str:
# Split by comma, strip spaces, remove empty and 'ServiceAccount'
plugins = [p.strip() for p in value.split(',')]
plugins = [p for p in plugins if p and p != 'ServiceAccount']
return ','.join(plugins)
out = []
pattern = re.compile(r'(--disable-admission-plugins=)(\S+)')
for line in lines:
if '--disable-admission-plugins=' in line:
# Handle the simple case of the flag entirely on this line
m = pattern.search(line)
if m:
prefix, val = m.groups()
new_val = clean_plugins(val)
# If list becomes empty, remove the flag entirely from this line
if not new_val:
# Remove the whole argument from the line
# Handle possible preceding spaces and comma separation (args separated by spaces).
# Replace '--disable-admission-plugins=<...>' by '' and clean up spaces.
new_line = pattern.sub('', line)
# Collapse multiple spaces
new_line = re.sub(r' {2,}', ' ', new_line)
# Strip trailing spaces before newline
new_line = re.sub(r' +\n', '\n', new_line)
line = new_line
else:
line = pattern.sub(prefix + new_val, line)
out.append(line)
sys.stdout.writelines(out)
PYCODE
mv "${TMP_MANIFEST}.new" "${TMP_MANIFEST}"
# Only replace the live manifest if there is a change
if cmp -s "${APISERVER_MANIFEST}" "${TMP_MANIFEST}"; then
echo "[INFO] No changes needed; ServiceAccount is already not disabled."
rm -f "${TMP_MANIFEST}"
else
echo "[INFO] Updating ${APISERVER_MANIFEST} (this will restart kube-apiserver static pod)"
mv "${TMP_MANIFEST}" "${APISERVER_MANIFEST}"
fi
# Verification: check running kube-apiserver process args do NOT disable ServiceAccount
echo "[INFO] Waiting up to 120 seconds for kube-apiserver to restart (if changed)"
end=$((SECONDS + 120))
while (( SECONDS < end )); do
if /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
break
fi
sleep 3
done
if ! /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
echo "[ERROR] kube-apiserver process not detected after waiting. Please investigate."
exit 1
fi
echo "[INFO] Verifying that ServiceAccount is NOT disabled in kube-apiserver arguments"
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--disable-admission-plugins' | grep -q 'ServiceAccount'; then
echo "[ERROR] kube-apiserver still started with ServiceAccount in --disable-admission-plugins."
echo "[ERROR] Please review ${APISERVER_MANIFEST} manually."
exit 1
fi
echo "[INFO] Verification successful: ServiceAccount admission plugin is not disabled."
exit 0