Ensure That A Minimal Audit Policy Is Created
More Info:
An audit policy file must be configured so the API server records audit events. Without it, security-relevant activity is not logged.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Review current API server flags for audit configuration
Run on: every control plane node/bin/ps -ef | grep kube-apiserver | grep -v grepCheck whether
--audit-policy-file=is present. If it is already configured and points to an existing file, refine that policy instead of creating a new one. -
Create a minimal audit policy file
Run on: every control plane nodesudo 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: MetadataEOFAdjust the rules later as appropriate for your organization’s logging and privacy requirements.
-
Ensure filesystem permissions are appropriate
Run on: every control plane nodesudo chown root:root /etc/kubernetes/audit/audit-policy.yamlsudo chmod 600 /etc/kubernetes/audit/audit-policy.yaml -
Configure the API server static pod to use the audit policy
Run on: every control plane node
Edit the manifest:sudo vi /etc/kubernetes/manifests/kube-apiserver.yamlIn the
command:orargs:list forkube-apiserver, ensure these flags are present (add them if missing, adjusting paths as needed):- --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=100In the
volumeMounts:section for the container, add:- mountPath: /etc/kubernetes/auditname: audit-policyreadOnly: true- mountPath: /var/log/kubernetesname: audit-logsIn the
volumes:section of the pod spec, add:- name: audit-policyhostPath:path: /etc/kubernetes/audittype: DirectoryOrCreate- name: audit-logshostPath:path: /var/log/kubernetestype: DirectoryOrCreateOperational impact: saving this file will cause the kubelet to restart the
kube-apiserverstatic pod. -
Confirm that the API server restarted cleanly and is writing audit logs
Run on: every control plane nodesudo ls -l /var/log/kubernetes/audit.logsudo tail -n 5 /var/log/kubernetes/audit.logEnsure new entries appear when you make API calls (for example, run
kubectl get podsfrom a machine with kubectl access). -
Verify the API server process now includes the audit policy flag
Run on: every control plane node/bin/ps -ef | grep kube-apiserver | grep -v grepConfirm that the output includes
--audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml(and associated audit log flags), demonstrating that the minimal audit policy is in use.
Using kubectl
kubectl cannot configure the API server’s audit policy or edit /etc/kubernetes/manifests/kube-apiserver.yaml, because these are host-level settings on each control plane node. To address this finding, make the changes directly on the control plane nodes as described in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Automation: Ensure a minimal audit policy is created and enabled for kube-apiserver
# Scope: run on every control plane node (with root privileges)
#
# This script will:
# - Create a minimal audit policy file if missing
# - Ensure kube-apiserver static pod manifest references that policy
# - Ensure audit log output file path is configured
# - Verify kube-apiserver is running with the expected audit flags
#
# Safe to re-run (idempotent).
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
AUDIT_POLICY_FILE="/etc/kubernetes/audit-policy.yaml"
AUDIT_LOG_FILE="/var/log/kubernetes/apiserver-audit.log"
BACKUP_SUFFIX=".pre_audit_$(date +%Y%m%d%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
}
check_files_exist() {
if [[ ! -f "$APISERVER_MANIFEST" ]]; then
echo "ERROR: kube-apiserver static pod manifest not found at $APISERVER_MANIFEST" >&2
echo "This script expects a static pod-based control plane." >&2
exit 1
fi
}
ensure_audit_policy_file() {
if [[ -f "$AUDIT_POLICY_FILE" ]]; then
echo "Audit policy file already exists at $AUDIT_POLICY_FILE"
return
fi
echo "Creating minimal audit policy at $AUDIT_POLICY_FILE"
install -o root -g root -m 0640 /dev/null "$AUDIT_POLICY_FILE"
cat >"$AUDIT_POLICY_FILE" <<'EOF'
apiVersion: audit.k8s.io/v1
kind: Policy
# Minimal audit policy: log at least metadata for all requests.
rules:
- level: Metadata
EOF
}
ensure_audit_log_path() {
# Ensure directory for log exists
local log_dir
log_dir="$(dirname "$AUDIT_LOG_FILE")"
if [[ ! -d "$log_dir" ]]; then
echo "Creating audit log directory $log_dir"
mkdir -p "$log_dir"
chmod 0750 "$log_dir"
fi
}
backup_manifest_once() {
# Only back up once per script run
if [[ -z "${_BACKED_UP:-}" ]]; then
cp "$APISERVER_MANIFEST" "${APISERVER_MANIFEST}${BACKUP_SUFFIX}"
echo "Backed up $APISERVER_MANIFEST to ${APISERVER_MANIFEST}${BACKUP_SUFFIX}"
_BACKED_UP=1
fi
}
ensure_flag_in_manifest() {
local flag="$1" # e.g. --audit-policy-file
local value="$2" # e.g. /etc/kubernetes/audit-policy.yaml
local file="$APISERVER_MANIFEST"
if grep -qE "[[:space:]]${flag}=" "$file"; then
# Replace existing value
backup_manifest_once
# Use perl-compatible regex for safe in-place replacement
sed -i "s#${flag}=[^\"'[:space:]]*#${flag}=${value}#g" "$file"
echo "Updated existing ${flag} in $file to ${value}"
elif grep -qE "[[:space:]]${flag}[[:space:]]" "$file"; then
# Flag present without =value form; ensure correct form
backup_manifest_once
sed -i "s#${flag}[[:space:]]#${flag}=${value} #g" "$file"
echo "Updated existing ${flag} in $file to ${value}"
else
# Add new flag under the kube-apiserver command args
backup_manifest_once
# Try to append into the 'command:' array if present; otherwise append to 'args:'
if grep -qE '^\s*- kube-apiserver' "$file"; then
# Typical static pod: first list item under container command
sed -i "s#^\(\s*-\s*kube-apiserver.*\)#\1\n\1 \"${flag}=${value}\"#g" "$file" || true
fi
# Fallback: try to add under args:
if ! grep -q "${flag}=${value}" "$file"; then
if grep -qE '^\s*args:\s*$' "$file"; then
# Append new arg line
awk -v f="${flag}=${value}" '
/^[[:space:]]*args:[[:space:]]*$/ && !added {
print $0
print " - " f
added=1
next
}
{ print $0 }
' "$file" >"${file}.tmp" && mv "${file}.tmp" "$file"
else
# As a last resort, append an args section with this flag
cat >>"$file" <<EOF_APPEND
args:
- "${flag}=${value}"
EOF_APPEND
fi
fi
echo "Ensured ${flag}=${value} configured in $file"
fi
}
ensure_audit_flags() {
echo "Ensuring kube-apiserver manifest has audit flags configured"
ensure_flag_in_manifest "--audit-policy-file" "$AUDIT_POLICY_FILE"
ensure_flag_in_manifest "--audit-log-path" "$AUDIT_LOG_FILE"
# Optional but sensible defaults; harmless if already present
ensure_flag_in_manifest "--audit-log-maxage" "30"
ensure_flag_in_manifest "--audit-log-maxbackup" "10"
ensure_flag_in_manifest "--audit-log-maxsize" "100"
}
restart_notice() {
echo
echo "NOTE: Editing $APISERVER_MANIFEST causes the kubelet to restart the kube-apiserver static pod."
echo "This restart should occur automatically within a short time."
echo
}
verify() {
echo "Verifying that kube-apiserver is running with audit flags..."
sleep 10
/bin/ps -ef | grep kube-apiserver | grep -v grep || {
echo "ERROR: kube-apiserver process not found. Check the static pod status with:" >&2
echo " kubectl -n kube-system get pods -l component=kube-apiserver" >&2
exit 1
}
local ps_out
ps_out="$(/bin/ps -ef | grep kube-apiserver | grep -v grep)"
echo "$ps_out" | grep -q -- "--audit-policy-file=${AUDIT_POLICY_FILE}" || {
echo "ERROR: --audit-policy-file flag not active on kube-apiserver process." >&2
echo "Current kube-apiserver command line:" >&2
echo "$ps_out" >&2
exit 1
}
echo "$ps_out" | grep -q -- "--audit-log-path=${AUDIT_LOG_FILE}" || {
echo "ERROR: --audit-log-path flag not active on kube-apiserver process." >&2
echo "Current kube-apiserver command line:" >&2
echo "$ps_out" >&2
exit 1
}
echo "Verification successful: kube-apiserver is running with audit policy and log path configured."
}
main() {
require_root
check_files_exist
ensure_audit_policy_file
ensure_audit_log_path
ensure_audit_flags
restart_notice
verify
}
main "$@"