Ensure Minimal Audit Policy Is Created
More Info:
Kubernetes can audit the details of requests made to the API server. The --auditpolicy-file flag must be set for this logging to be enabled.
Risk Level
Low
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Confirm current API server static pod manifest
- On every control plane node:
sudo ls -l /etc/kubernetes/manifests/kube-apiserver.yaml
- On every control plane node:
-
Create a minimal audit policy file
- On every control plane node (same path on each):
sudo mkdir -p /etc/kubernetes/auditsudo tee /etc/kubernetes/audit/audit-policy.yaml >/dev/null <<'EOF'apiVersion: audit.k8s.io/v1kind: Policyrules:# Log all requests at the Metadata level.- level: MetadataEOF
- On every control plane node (same path on each):
-
Configure the API server to use the audit policy file and log file
- On every control plane node, edit the static pod manifest:
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
- Under
spec.containers[0].command:add (or adjust) these flags (one per line) making sure paths match what you created:- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml- --audit-log-path=/var/log/kubernetes/audit.log- --audit-log-maxage=30- --audit-log-maxbackup=10- --audit-log-maxsize=100 - Under
spec.containers[0].volumeMounts:ensure:- mountPath: /etc/kubernetes/auditname: audit-policyreadOnly: true- mountPath: /var/log/kubernetesname: audit-log - Under
spec.volumes:ensure:- name: audit-policyhostPath:path: /etc/kubernetes/audittype: DirectoryOrCreate- name: audit-loghostPath:path: /var/log/kubernetestype: DirectoryOrCreate - Saving this file will cause the kubelet to restart the kube-apiserver static pod on that node.
- On every control plane node, edit the static pod manifest:
-
Ensure log directory exists and has correct ownership
- On every control plane node:
sudo mkdir -p /var/log/kubernetessudo chown root:root /var/log/kubernetessudo chmod 750 /var/log/kubernetes
- On every control plane node:
-
Wait for kube-apiserver to restart and stabilize
- On every control plane node:
sudo crictl ps | grep kube-apiserver || sudo docker ps | grep kube-apiserver
- On every control plane node:
-
Verification (audit command–based)
- On every control plane node, confirm the audit policy flag is present:
/bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml'
- Optionally confirm audit log file is being written:
sudo ls -l /var/log/kubernetes/audit.log
- On every control plane node, confirm the audit policy flag is present:
Using kubectl
kubectl cannot be used to configure the API server’s --audit-policy-file or to edit /etc/kubernetes/manifests/kube-apiserver.yaml; those are host-level files on every control plane node. To address this finding, follow the guidance in the Manual Steps section on each control plane node.
Automation
#!/usr/bin/env bash
#
# Automation: Ensure minimal Kubernetes API server audit policy is created
# and configured via the kube-apiserver static pod manifest.
#
# Run on: every control plane node (with root privileges)
#
# Behavior:
# - Creates a minimal audit policy file if missing (idempotent)
# - Ensures kube-apiserver manifest has:
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml
# --audit-log-path=/var/log/kubernetes/audit.log
# - Ensures /var/log/kubernetes exists
# - kubelet will automatically restart the kube-apiserver static pod
# after the manifest is modified (expect brief control-plane disruption)
# - Verifies that the kube-apiserver process has the correct flags
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
AUDIT_POLICY_FILE="/etc/kubernetes/audit-policy.yaml"
AUDIT_LOG_DIR="/var/log/kubernetes"
AUDIT_LOG_FILE="${AUDIT_LOG_DIR}/audit.log"
# Minimal, broadly compatible audit policy
read -r -d '' MINIMAL_POLICY <<"EOF" || true
apiVersion: audit.k8s.io/v1
kind: Policy
# Log only metadata for all requests; adjust as needed for your org.
rules:
- level: Metadata
EOF
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
}
check_manifest_exists() {
if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
echo "ERROR: kube-apiserver manifest not found at ${APISERVER_MANIFEST}." >&2
echo " This script assumes a static pod manifest; adjust for your environment." >&2
exit 1
fi
}
ensure_audit_policy_file() {
if [[ -f "${AUDIT_POLICY_FILE}" ]]; then
echo "Audit policy file already exists at ${AUDIT_POLICY_FILE}; leaving as-is."
else
echo "Creating minimal audit policy at ${AUDIT_POLICY_FILE}."
umask 027
echo "${MINIMAL_POLICY}" > "${AUDIT_POLICY_FILE}"
chmod 640 "${AUDIT_POLICY_FILE}"
fi
}
ensure_audit_log_dir() {
if [[ ! -d "${AUDIT_LOG_DIR}" ]]; then
echo "Creating audit log directory ${AUDIT_LOG_DIR}."
mkdir -p "${AUDIT_LOG_DIR}"
chmod 750 "${AUDIT_LOG_DIR}"
else
echo "Audit log directory ${AUDIT_LOG_DIR} already exists."
fi
}
# Idempotently ensure a command-line flag in the kube-apiserver manifest
ensure_apiserver_flag() {
local flag_name="$1" # e.g. --audit-policy-file
local flag_value="$2" # e.g. /etc/kubernetes/audit-policy.yaml
local manifest="${APISERVER_MANIFEST}"
if grep -qE "[[:space:]]${flag_name}=" "${manifest}"; then
# Update in place
echo "Ensuring ${flag_name} is set to ${flag_value} in ${manifest}."
# Use sed to replace the value only; keep surrounding formatting
sed -i.bak -E "s#(${flag_name}=)[^[:space:]]*#\1${flag_value}#g" "${manifest}"
else
# Add a new flag line under the kube-apiserver container args
echo "Adding ${flag_name}=${flag_value} to ${manifest}."
# Insert before the first line that starts with ' - --' under args:
# This is heuristic but safe to re-run (we use grep above to avoid duplicates).
python3 - "$manifest" "$flag_name" "$flag_value" << 'PYEOF'
import sys, yaml
manifest_path, flag_name, flag_value = sys.argv[1:]
with open(manifest_path) as f:
doc = yaml.safe_load(f)
updated = False
for c in doc.get('spec', {}).get('containers', []):
if c.get('name') == 'kube-apiserver':
args = c.setdefault('command', []) or c.setdefault('args', [])
# If "command" is used explicitly, it's usually ["kube-apiserver", ...]
# If "args" is used, they are direct flags. We normalize to list of strings.
if not isinstance(args, list):
continue
# Safeguard: avoid duplicating the flag
if not any(a.startswith(flag_name + "=") for a in args):
args.append(f"{flag_name}={flag_value}")
updated = True
break
if not updated:
print("ERROR: Could not locate kube-apiserver container in manifest to add flag.", file=sys.stderr)
sys.exit(1)
with open(manifest_path, "w") as f:
yaml.safe_dump(doc, f, default_flow_style=False)
PYEOF
fi
}
main() {
require_root
check_manifest_exists
# Ensure policy file and log directory
ensure_audit_policy_file
ensure_audit_log_dir
# Ensure kube-apiserver flags
ensure_apiserver_flag "--audit-policy-file" "${AUDIT_POLICY_FILE}"
ensure_apiserver_flag "--audit-log-path" "${AUDIT_LOG_FILE}"
echo "Waiting for kube-apiserver to restart with new configuration..."
sleep 15
echo "Verification: checking kube-apiserver process flags."
/bin/ps -ef | grep kube-apiserver | grep -v grep || {
echo "ERROR: kube-apiserver process not found; investigate pod status." >&2
exit 1
}
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--audit-policy-file=${AUDIT_POLICY_FILE}"; then
echo "Verified: kube-apiserver is running with --audit-policy-file=${AUDIT_POLICY_FILE}."
else
echo "ERROR: kube-apiserver is not running with the expected --audit-policy-file flag." >&2
exit 1
fi
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--audit-log-path=${AUDIT_LOG_FILE}"; then
echo "Verified: kube-apiserver is running with --audit-log-path=${AUDIT_LOG_FILE}."
else
echo "ERROR: kube-apiserver is not running with the expected --audit-log-path flag." >&2
exit 1
fi
echo "Automation complete on this control plane node."
}
main "$@"