API Server Pod Specification File Permissions Should Be 600
More Info:
Verifies that the kube-apiserver pod manifest file has permissions of 600 or more restrictive. Restrictive permissions prevent unauthorized users from tampering with control plane configuration.
Risk Level
Medium
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, check the current permissions of the API server manifest file:
stat -c permissions=%a /etc/kubernetes/manifests/kube-apiserver.yaml -
If the permissions are more permissive than 600 (for example, 644 or 640), set them to 600:
chmod 600 /etc/kubernetes/manifests/kube-apiserver.yaml -
Confirm the file owner is root (recommended). If not, set it:
chown root:root /etc/kubernetes/manifests/kube-apiserver.yaml -
Be aware: changing this static pod manifest file may trigger the kubelet to restart the kube-apiserver pod on this control plane node. Plan to do this one node at a time if running a multi-node control plane.
-
Re-verify the permissions are now 600:
stat -c permissions=%a /etc/kubernetes/manifests/kube-apiserver.yaml
Using kubectl
kubectl cannot modify file permissions on control plane nodes, so it cannot be used to fix /etc/kubernetes/manifests/kube-apiserver.yaml. This must be corrected directly on every control plane node’s filesystem; see the Manual Steps section for how to update the permissions and verify the fix.
Automation
#!/usr/bin/env bash
#
# Harden kube-apiserver pod manifest permissions on all control plane nodes.
# Run this script on each control plane node (e.g., via SSH or Ansible shell module).
#
# Requirements:
# - Run as root (or with sudo) on the control plane node.
set -euo pipefail
MANIFEST_PATH="/etc/kubernetes/manifests/kube-apiserver.yaml"
REQUIRED_MODE="600"
echo "==> Checking for kube-apiserver manifest at ${MANIFEST_PATH}"
if [ ! -e "${MANIFEST_PATH}" ]; then
echo "WARNING: ${MANIFEST_PATH} does not exist on this node. Skipping."
exit 0
fi
current_mode="$(stat -c '%a' "${MANIFEST_PATH}")"
echo "Current permissions: ${current_mode}"
# Normalize modes to 3 digits for comparison
pad_mode() {
local m="$1"
printf "%03d" "${m}"
}
current_mode_padded="$(pad_mode "${current_mode}")"
required_mode_padded="$(pad_mode "${REQUIRED_MODE}")"
# Convert octal modes to decimal to compare "restrictiveness"
octal_to_decimal() {
local o="$1"
echo "ibase=8; ${o}" | bc
}
current_dec="$(octal_to_decimal "${current_mode_padded}")"
required_dec="$(octal_to_decimal "${required_mode_padded}")"
# If current mode is numerically greater (less restrictive) than required, update it.
if [ "${current_dec}" -gt "${required_dec}" ]; then
echo "Permissions are too permissive; setting to ${REQUIRED_MODE}"
chmod "${REQUIRED_MODE}" "${MANIFEST_PATH}"
else
# Even if already restrictive (e.g., 400), we leave as is for idempotency.
echo "Permissions are already ${current_mode} which is as restrictive or more restrictive than ${REQUIRED_MODE}; no change needed."
fi
echo "==> Verifying final permissions:"
/bin/sh -c 'if test -e /etc/kubernetes/manifests/kube-apiserver.yaml; then stat -c permissions=%a /etc/kubernetes/manifests/kube-apiserver.yaml; fi'