Skip to main content

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

Manual Steps
  1. Review current API server flags for audit configuration
    Run on: every control plane node

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

    Check 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.

  2. Create a minimal audit policy file
    Run on: every control plane node

    sudo mkdir -p /etc/kubernetes/audit
    sudo tee /etc/kubernetes/audit/audit-policy.yaml >/dev/null <<'EOF'
    apiVersion: audit.k8s.io/v1
    kind: Policy
    rules:
    # Log all requests at the Metadata level.
    - level: Metadata
    EOF

    Adjust the rules later as appropriate for your organization’s logging and privacy requirements.

  3. Ensure filesystem permissions are appropriate
    Run on: every control plane node

    sudo chown root:root /etc/kubernetes/audit/audit-policy.yaml
    sudo chmod 600 /etc/kubernetes/audit/audit-policy.yaml
  4. 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.yaml

    In the command: or args: list for kube-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=100

    In the volumeMounts: section for the container, add:

    - mountPath: /etc/kubernetes/audit
    name: audit-policy
    readOnly: true
    - mountPath: /var/log/kubernetes
    name: audit-logs

    In the volumes: section of the pod spec, add:

    - name: audit-policy
    hostPath:
    path: /etc/kubernetes/audit
    type: DirectoryOrCreate
    - name: audit-logs
    hostPath:
    path: /var/log/kubernetes
    type: DirectoryOrCreate

    Operational impact: saving this file will cause the kubelet to restart the kube-apiserver static pod.

  5. Confirm that the API server restarted cleanly and is writing audit logs
    Run on: every control plane node

    sudo ls -l /var/log/kubernetes/audit.log
    sudo tail -n 5 /var/log/kubernetes/audit.log

    Ensure new entries appear when you make API calls (for example, run kubectl get pods from a machine with kubectl access).

  6. 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 grep

    Confirm 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 "$@"