API Server Should Enable The NamespaceLifecycle Admission
More Info:
Verifies that the NamespaceLifecycle admission plugin is not disabled so objects cannot be created in non-existent or terminating namespaces.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
- On every control plane node, open the API server static pod manifest for editing (this will restart the API server when saved):
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
- In the
spec.containers[0].commandlist, locate any existing--disable-admission-plugins=argument. Edit it to removeNamespaceLifecyclefrom the comma-separated list of plugins, ensuring the argument either:
- does not exist at all, or
- exists but does not contain
NamespaceLifecycle(e.g. change
--disable-admission-plugins=NamespaceLifecycle,SomeOtherPlugin
to
--disable-admission-plugins=SomeOtherPlugin).
-
If
NamespaceLifecyclewas the only plugin listed, remove the entire--disable-admission-plugins=NamespaceLifecycleflag line from thecommandlist. -
Save the file and exit the editor. The kubelet will automatically detect the manifest change and restart the kube-apiserver static pod.
-
Wait for the API server pod to become ready:
# On any machine with kubectl access
kubectl get pods -n kube-system -l component=kube-apiserver -o wide
Confirm the pod is in Running status and READY is 1/1.
- Verify that the
NamespaceLifecycleplugin is not disabled:
# On every control plane node
/bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--disable-admission-plugins'
Confirm that the output either shows no --disable-admission-plugins flag, or that its value does not contain NamespaceLifecycle.
Using kubectl
kubectl cannot be used to change API server process flags or edit the static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml on the control plane nodes. To remediate this finding, make the change directly on each control plane node as described in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Fix: Ensure the NamespaceLifecycle admission plugin is NOT disabled
# Scope: every control plane node
# Target file: /etc/kubernetes/manifests/kube-apiserver.yaml
#
# Usage:
# - Run on each control plane node as root.
# - Safe to re-run; 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 on each control plane node." >&2
exit 1
fi
if [[ ! -f "$APISERVER_MANIFEST" ]]; then
echo "ERROR: kube-apiserver manifest not found at $APISERVER_MANIFEST" >&2
exit 1
fi
mkdir -p "$BACKUP_DIR"
cp -p "$APISERVER_MANIFEST" "$BACKUP_DIR/kube-apiserver.yaml.$TIMESTAMP"
# Function: remove NamespaceLifecycle from an existing --disable-admission-plugins arg
# Handles comma-separated lists and possible surrounding spaces.
clean_disable_plugins_value() {
local value="$1"
local cleaned
# Add leading/trailing commas to simplify removal
cleaned=",$value,"
# Remove 'NamespaceLifecycle' (with or without surrounding spaces) from list
cleaned="$(printf '%s' "$cleaned" \
| sed -E 's/,\s*NamespaceLifecycle\s*,/,/g')"
# Remove possible duplicate commas
cleaned="$(printf '%s' "$cleaned" \
| sed -E 's/,+/,/g')"
# Trim leading/trailing commas
cleaned="$(printf '%s' "$cleaned" \
| sed -E 's/^,+//; s/,+$//')"
# Trim spaces around commas
cleaned="$(printf '%s' "$cleaned" \
| sed -E 's/\s*,\s*/,/g')"
printf '%s' "$cleaned"
}
# Work on a temporary file and then move into place atomically
TMP_FILE="$(mktemp)"
cp "$APISERVER_MANIFEST" "$TMP_FILE"
# Detect if there is any --disable-admission-plugins arg present
if grep -q -- '--disable-admission-plugins' "$TMP_FILE"; then
# Process each line containing --disable-admission-plugins
# Supports both:
# - --disable-admission-plugins=Foo,Bar
# - --disable-admission-plugins Foo,Bar
# We only modify NamespaceLifecycle; other plugins remain untouched.
mapfile -t lines < <(grep -n -- '--disable-admission-plugins' "$TMP_FILE" | cut -d: -f1)
for lineno in "${lines[@]}"; do
line="$(sed -n "${lineno}p" "$TMP_FILE")"
if printf '%s' "$line" | grep -qE -- '--disable-admission-plugins='; then
# Form: --disable-admission-plugins=Foo,Bar
before="$(printf '%s' "$line" | sed -E 's/(.*--disable-admission-plugins=)([^"]*)(.*)/\1/')"
value="$(printf '%s' "$line" | sed -E 's/.*--disable-admission-plugins=([^"]*).*/\1/')"
after="$(printf '%s' "$line" | sed -E 's/.*--disable-admission-plugins=[^"]*(.*)/\1/')"
new_value="$(clean_disable_plugins_value "$value")"
if [[ -z "$new_value" ]]; then
# Entire list would be empty; remove the flag altogether
# Remove ' --disable-admission-plugins=...' from the line
newline="$(printf '%s' "$line" \
| sed -E 's/[[:space:]]*--disable-admission-plugins=[^"[:space:]]*//')"
else
newline="${before}${new_value}${after}"
fi
else
# Form: --disable-admission-plugins Foo,Bar
# Split on the first occurrence of the flag
prefix="$(printf '%s' "$line" | sed -E 's/(.*)--disable-admission-plugins(.*)/\1/')"
rest="$(printf '%s' "$line" | sed -E 's/.*--disable-admission-plugins(.*)/\1/')"
# Extract the value (first token after the flag)
value="$(printf '%s' "$rest" | sed -E 's/^[[:space:]]+([^[:space:]]+).*/\1/')"
suffix="$(printf '%s' "$rest" | sed -E 's/^[[:space:]]+[^[:space:]]+(.*)/\1/')"
new_value="$(clean_disable_plugins_value "$value")"
if [[ -z "$new_value" ]]; then
# Remove the flag entirely
newline="$(printf '%s' "$prefix$rest" \
| sed -E 's/[[:space:]]*--disable-admission-plugins[[:space:]]+[^[:space:]]+//')"
else
newline="${prefix}--disable-admission-plugins ${new_value}${suffix}"
fi
fi
# Replace line in temp file
sed -i "${lineno}s~.*~${newline}~" "$TMP_FILE"
done
else
# No --disable-admission-plugins flag: nothing to change; the plugin is enabled by default.
echo "INFO: --disable-admission-plugins not present; NamespaceLifecycle is not disabled."
fi
# Replace the manifest only if it changed
if cmp -s "$APISERVER_MANIFEST" "$TMP_FILE"; then
echo "INFO: No changes required in $APISERVER_MANIFEST"
rm -f "$TMP_FILE"
else
mv "$TMP_FILE" "$APISERVER_MANIFEST"
echo "INFO: Updated $APISERVER_MANIFEST (kube-apiserver static pod will restart automatically)."
fi
# Verification: ensure NamespaceLifecycle is NOT listed under --disable-admission-plugins
echo "INFO: Waiting for kube-apiserver process to be running..."
sleep 10
echo "INFO: Verifying that NamespaceLifecycle is not disabled..."
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 "ERROR: NamespaceLifecycle is still present in --disable-admission-plugins." >&2
exit 1
fi
echo "SUCCESS: kube-apiserver is running and NamespaceLifecycle is NOT disabled."
else
echo "SUCCESS: kube-apiserver is running with no --disable-admission-plugins flag; NamespaceLifecycle is enabled by default."
fi