Skip to main content

Ensure Admission Control Plugin NamespaceLifecycle Is Set

More Info:โ€‹

Reject creating objects in a namespace that is undergoing termination.

Risk Levelโ€‹

Low

Addressโ€‹

Security

Compliance Standardsโ€‹

  • CIS Kubernetes

Triage and Remediationโ€‹

Remediationโ€‹

Manual Steps
  1. On every control plane node, back up the kube-apiserver manifest:

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

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
  3. In the command: section of the kube-apiserver container, locate the line starting with - --disable-admission-plugins=.

    • If it does not exist, go to step 4.
    • If it exists and contains NamespaceLifecycle, remove only NamespaceLifecycle from the comma-separated list, keeping any other plugins as-is.
      Example before:
    - --disable-admission-plugins=NamespaceLifecycle,ServiceAccount

    Example after:

    - --disable-admission-plugins=ServiceAccount
  4. If there is no --disable-admission-plugins line, add nothing for this control; no additional flag is required as long as NamespaceLifecycle is not disabled. Save and exit the editor.
    Note: Editing this static pod manifest will cause the kubelet to automatically restart the kube-apiserver pod.

  5. Wait 1โ€“2 minutes for the kube-apiserver pod to be recreated, then on every control plane node verify that NamespaceLifecycle is not listed under --disable-admission-plugins:

    /bin/ps -ef | grep kube-apiserver | grep -v grep | tr ' ' '\n' | grep -- '--disable-admission-plugins' || echo "No disable-admission-plugins flag present"

    Confirm that the printed value (if any) does not contain NamespaceLifecycle.

Using kubectl

kubectl cannot modify kube-apiserver process flags or the static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml on control plane nodes, so this finding cannot be fixed via the Kubernetes API. To remediate, make the changes directly on each control plane node as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Automation: Ensure NamespaceLifecycle admission plugin is NOT disabled
# Scope: every control plane node
#
# Usage:
# 1) Copy this script to each control plane node, e.g. /root/fix-namespace-lifecycle.sh
# 2) Run as root: bash /root/fix-namespace-lifecycle.sh
# 3) Safe to re-run; it is idempotent.

set -euo pipefail

APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-namespace-lifecycle"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"

if [[ $EUID -ne 0 ]]; then
echo "ERROR: Run as root." >&2
exit 1
fi

if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
echo "ERROR: ${APISERVER_MANIFEST} not found on this node." >&2
exit 1
fi

mkdir -p "${BACKUP_DIR}"

backup_file="${BACKUP_DIR}/kube-apiserver.yaml.${TIMESTAMP}"
cp "${APISERVER_MANIFEST}" "${backup_file}"
echo "Backup created at ${backup_file}"

# Function to clean a single line containing --disable-admission-plugins
clean_disable_line() {
local line="$1"

# Remove NamespaceLifecycle from a comma-separated list while preserving others
# Handle cases:
# NamespaceLifecycle
# NamespaceLifecycle,Other
# Other,NamespaceLifecycle
# Other,NamespaceLifecycle,Another
# and variants with spaces.
python3 - "$line" << 'PYEOF'
import sys
line = sys.argv[1]

# Split around the flag
import re
m = re.search(r'(--disable-admission-plugins\s*=\s*)([^"\']+)', line)
if not m:
# Flag may be like: --disable-admission-plugins=NamespaceLifecycle,Other \
# inside quotes or different formatting; fall back to simple replace
cleaned = line.replace("NamespaceLifecycle,", "").replace(",NamespaceLifecycle", "").replace("NamespaceLifecycle", "")
print(cleaned)
sys.exit(0)

prefix, value = m.groups()
plugins = [p.strip() for p in value.split(',') if p.strip()]
plugins = [p for p in plugins if p != "NamespaceLifecycle"]

if plugins:
new_value = ",".join(plugins)
cleaned = line[:m.start()] + f"{prefix}{new_value}" + line[m.end():]
else:
# Remove entire flag if no plugins left
cleaned = line[:m.start()] + line[m.end():]

print(cleaned)
PYEOF
}

tmp_file="$(mktemp)"
changed=0

while IFS= read -r line; do
if echo "${line}" | grep -q -- "--disable-admission-plugins"; then
if echo "${line}" | grep -q "NamespaceLifecycle"; then
new_line="$(clean_disable_line "${line}")"
if [[ "${new_line}" != "${line}" ]]; then
changed=1
echo "${new_line}" >> "${tmp_file}"
else
echo "${line}" >> "${tmp_file}"
fi
else
echo "${line}" >> "${tmp_file}"
fi
else
echo "${line}" >> "${tmp_file}"
fi
done < "${APISERVER_MANIFEST}"

if [[ "${changed}" -eq 1 ]]; then
mv "${tmp_file}" "${APISERVER_MANIFEST}"
echo "Updated ${APISERVER_MANIFEST} to ensure NamespaceLifecycle is not disabled."
echo "Note: Because this is a static pod manifest, kube-apiserver will restart automatically."
else
rm -f "${tmp_file}"
echo "No changes needed; NamespaceLifecycle not found in --disable-admission-plugins."
fi

echo "Waiting for kube-apiserver process to be running..."
# Simple wait loop to ensure API server is back (best-effort)
for i in {1..30}; do
if /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
break
fi
sleep 2
done

echo
echo "Verification (on this control plane node):"
/bin/ps -ef | grep kube-apiserver | grep -v grep | sed 's/^/ /'

# Stronger verification: confirm NamespaceLifecycle is NOT in any disable-admission-plugins argument
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 -- "--disable-admission-plugins" | grep -q "NamespaceLifecycle"; then
echo "VERIFICATION FAILED: NamespaceLifecycle still appears in --disable-admission-plugins." >&2
exit 1
fi
fi

echo
echo "Verification passed: kube-apiserver is running and NamespaceLifecycle is not disabled."

Additional Reading:โ€‹