Skip to main content

Ensure Admission Control Plugin ServiceAccount Is Set

More Info:

Automate service accounts management.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every control plane node, review existing ServiceAccounts to understand what is already in use (run on any machine with kubectl access):

    kubectl get serviceaccounts --all-namespaces -o wide

    If needed, create or adjust ServiceAccounts for your workloads according to your environment’s requirements:

    kubectl create serviceaccount <name> -n <namespace>
  2. On every control plane node, back up the existing kube-apiserver static pod manifest:

    sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.backup
  3. On every control plane node, open the kube-apiserver manifest for editing:

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
  4. In the spec.containers[0].command section, locate the --disable-admission-plugins argument.

    • If it includes ServiceAccount, remove ServiceAccount from the comma-separated list, leaving the other plugins unchanged.
    • If --disable-admission-plugins is not present, no change is needed for this argument.
      Save and exit the editor.
      Note: Editing this file will cause the kubelet to restart the kube-apiserver static pod.
  5. Wait for the kube-apiserver pod to restart and become Ready (run on any machine with kubectl access):

    kubectl get pods -n kube-system -l component=kube-apiserver -o wide
  6. Verify on every control plane node that the kube-apiserver process is now running without ServiceAccount in --disable-admission-plugins:

    /bin/ps -ef | grep kube-apiserver | grep -v grep

    Inspect the output and confirm that either --disable-admission-plugins is absent or, if present, its value does not contain ServiceAccount.

Using kubectl

kubectl cannot modify the kube-apiserver static pod manifest or its process flags on the control plane node. To remediate this finding, you must edit /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node; see the Manual Steps section for detailed guidance.

Automation
#!/usr/bin/env bash
#
# Automation: Ensure ServiceAccount admission plugin is NOT disabled
# Scope: Run on every control plane node
# Effect: Edits /etc/kubernetes/manifests/kube-apiserver.yaml
# Note: Changing this static pod manifest will restart the kube-apiserver.

set -euo pipefail

KUBE_APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

if [ ! -f "$KUBE_APISERVER_MANIFEST" ]; then
echo "ERROR: $KUBE_APISERVER_MANIFEST not found on this node. Are you on a control plane node?"
exit 1
fi

if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: python3 is required for safe YAML manipulation."
exit 1
fi

echo "Creating backup: ${KUBE_APISERVER_MANIFEST}.${BACKUP_SUFFIX}"
cp -p "$KUBE_APISERVER_MANIFEST" "${KUBE_APISERVER_MANIFEST}.${BACKUP_SUFFIX}"

python3 << 'PYEOF'
import sys, re, copy
from pathlib import Path

path = Path("/etc/kubernetes/manifests/kube-apiserver.yaml")
text = path.read_text()

# Very small YAML editor tailored for kube-apiserver command args line.
# Goal: ensure --disable-admission-plugins does NOT contain ServiceAccount.
lines = text.splitlines()
changed = False

def process_arg_list(arg_str: str) -> str:
"""
Given a comma-separated list (e.g. 'NamespaceLifecycle,ServiceAccount,NodeRestriction'),
remove 'ServiceAccount' token if present and deduplicate commas.
"""
parts = [p.strip() for p in arg_str.split(",") if p.strip()]
parts = [p for p in parts if p != "ServiceAccount"]
return ",".join(parts)

new_lines = []
for line in lines:
if "--disable-admission-plugins" in line:
orig_line = line

# Match patterns like:
# - --disable-admission-plugins=Something,ServiceAccount,Other
# - --disable-admission-plugins=ServiceAccount
# --disable-admission-plugins=ServiceAccount,Other
def repl(m):
pre = m.group(1)
val = m.group(2)
new_val = process_arg_list(val)
return f"{pre}{new_val}"

new_line = re.sub(
r"(--disable-admission-plugins=)([^ \"']+)",
repl,
line
)

if new_line != orig_line:
changed = True
line = new_line
new_lines.append(line)

if changed:
new_text = "\n".join(new_lines) + ("\n" if text.endswith("\n") else "")
path.write_text(new_text)

PYEOF

echo "Configuration updated. kube-apiserver static pod will be reloaded automatically."

# Verification: ensure kube-apiserver is running and ServiceAccount is NOT in --disable-admission-plugins
echo "Waiting briefly for kube-apiserver to stabilize..."
sleep 10

echo "Current kube-apiserver process flags:"
/bin/ps -ef | grep kube-apiserver | grep -v grep || {
echo "ERROR: kube-apiserver process not found. Check pod status."
exit 1
}

if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--disable-admission-plugins"; then
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--disable-admission-plugins=.*ServiceAccount"; then
echo "FAIL: ServiceAccount still present in --disable-admission-plugins."
exit 1
fi
fi

echo "PASS: ServiceAccount admission plugin is not disabled on this control plane node."

Additional Reading: