Skip to main content

Ensure Admission Control Plugin AlwaysPullImages Is Set

More Info:

Always pull images.

Risk Level

Low

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. Open the kube-apiserver static pod manifest for editing
    On every control plane node:

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
  2. Locate the --enable-admission-plugins argument
    In the spec.containers[0].command list, find the line starting with:

    - --enable-admission-plugins=
  3. Ensure AlwaysPullImages is included in the plugins list
    Edit that line so it includes AlwaysPullImages somewhere in the comma-separated list, for example:

    - --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,AlwaysPullImages

    If the argument is not present at all, add a new line in the command list:

    - --enable-admission-plugins=AlwaysPullImages
  4. Save the file and allow kubelet to restart the API server
    Save and exit the editor. The kubelet will automatically detect the manifest change and restart the kube-apiserver pod. Be aware this briefly restarts the API server on this node.

  5. Verify the kube-apiserver process includes AlwaysPullImages
    After 10–30 seconds, on the same control plane node, run:

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

    Confirm the output contains an --enable-admission-plugins= argument whose value includes AlwaysPullImages.

Using kubectl

kubectl cannot be used to enable the AlwaysPullImages admission plugin because this setting is defined in the kube-apiserver static pod manifest on the host. To remediate this finding, edit /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Automation: Ensure kube-apiserver has AlwaysPullImages enabled
# Scope: every control plane node (run on each control plane node via SSH or automation)
#
# This script:
# 1. Backs up /etc/kubernetes/manifests/kube-apiserver.yaml
# 2. Ensures --enable-admission-plugins includes AlwaysPullImages
# 3. Leaves other flags unchanged
# 4. Verifies the running kube-apiserver process includes AlwaysPullImages
#
# NOTE: Editing the static pod manifest under /etc/kubernetes/manifests
# will cause the kube-apiserver pod to be restarted automatically by kubelet.

set -euo pipefail

MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-$(date +%Y%m%d)"
BACKUP_FILE="${BACKUP_DIR}/kube-apiserver.yaml.$(date +%H%M%S)"

require_root() {
if [ "$(id -u)" -ne 0 ]; then
echo "ERROR: This script must be run as root on each control plane node." >&2
exit 1
fi
}

backup_manifest() {
if [ ! -f "$MANIFEST" ]; then
echo "ERROR: Manifest $MANIFEST not found. Is this a static-pod control plane?" >&2
exit 1
fi

mkdir -p "$BACKUP_DIR"
cp -p "$MANIFEST" "$BACKUP_FILE"
echo "Backup created at: $BACKUP_FILE"
}

ensure_always_pull_images() {
# If flag already includes AlwaysPullImages, do nothing
if grep -q -- '--enable-admission-plugins=.*AlwaysPullImages' "$MANIFEST"; then
echo "--enable-admission-plugins already includes AlwaysPullImages; no change needed."
return 0
fi

# Case 1: existing --enable-admission-plugins flag without AlwaysPullImages
if grep -q -- '--enable-admission-plugins=' "$MANIFEST"; then
# Append AlwaysPullImages to the list, handling trailing commas
# Example: --enable-admission-plugins=NamespaceLifecycle,NodeRestriction
# becomes: --enable-admission-plugins=NamespaceLifecycle,NodeRestriction,AlwaysPullImages
sed -i \
-e 's/\(--enable-admission-plugins=[^",]*\)\(".*\|\s*$\)/\1,AlwaysPullImages\2/' \
"$MANIFEST"

# Fallback in case the above pattern misses quoted args, try a more generic append
if ! grep -q -- '--enable-admission-plugins=.*AlwaysPullImages' "$MANIFEST"; then
sed -i \
-e 's/\(--enable-admission-plugins=[^ ]*\)/\1,AlwaysPullImages/' \
"$MANIFEST"
fi

echo "Updated existing --enable-admission-plugins to include AlwaysPullImages."
return 0
fi

# Case 2: no --enable-admission-plugins flag present; add a new one
# Insert into the container command args list.
# This is conservative: it looks for the first occurrence of "kube-apiserver"
# command line and adds a new line after it.
if grep -q 'kube-apiserver' "$MANIFEST"; then
# Try to detect YAML style: arguments as separate list items (common in kubeadm)
if grep -q '^- --advertise-address' "$MANIFEST"; then
# Insert a new list item line after the kube-apiserver command or near other flags
# Here we append near the top of the flag list.
sed -i \
-e '/^- --advertise-address/a\ - --enable-admission-plugins=AlwaysPullImages' \
"$MANIFEST" || true
else
# Generic: append a new arg line once under the kube-apiserver container args.
# This may need manual review if manifest is heavily customized.
awk '
/kube-apiserver/ && in_container == 0 { in_container=1; print; next }
in_container == 1 && $1 ~ /^-$/ {
print " - --enable-admission-plugins=AlwaysPullImages"
in_container=2
}
{ print }
' "$MANIFEST" > "${MANIFEST}.tmp" && mv "${MANIFEST}.tmp" "$MANIFEST"
fi

echo "Added new --enable-admission-plugins=AlwaysPullImages flag."
else
echo "WARNING: Could not locate kube-apiserver command in manifest; manual review required." >&2
fi
}

wait_for_apiserver_restart() {
echo "Waiting for kube-apiserver process to pick up new configuration..."
# Wait up to 120 seconds for process args to reflect AlwaysPullImages
local timeout=120
local interval=5
local elapsed=0

while [ "$elapsed" -lt "$timeout" ]; do
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q 'enable-admission-plugins=.*AlwaysPullImages'; then
echo "kube-apiserver process now includes AlwaysPullImages."
return 0
fi
sleep "$interval"
elapsed=$((elapsed + interval))
done

echo "WARNING: Timed out waiting for kube-apiserver to restart with AlwaysPullImages. Check pod and kubelet status." >&2
return 1
}

verify() {
echo "Verification: checking running kube-apiserver process..."
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q 'enable-admission-plugins=.*AlwaysPullImages'; then
echo "PASS: AlwaysPullImages is enabled on kube-apiserver."
return 0
else
echo "FAIL: AlwaysPullImages not found in kube-apiserver arguments." >&2
echo "Run: /bin/ps -ef | grep kube-apiserver | grep -v grep" >&2
return 1
fi
}

main() {
require_root
backup_manifest
ensure_always_pull_images
# kubelet will notice the manifest change and restart the static pod automatically.
wait_for_apiserver_restart || true
verify
}

main "$@"

Additional Reading: