API Server Should Set An Audit Log Path
More Info:
Verifies that the API server --audit-log-path argument is set so API activity is recorded. Without audit logging, security incidents cannot be investigated.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, back up the existing manifest so you can roll back if needed:
sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.backup -
On every control plane node, create the audit log directory and set safe permissions:
sudo mkdir -p /var/log/apiserversudo chmod 700 /var/log/apiserversudo chown root:root /var/log/apiserver -
On every control plane node, edit the API server static pod manifest:
sudo vi /etc/kubernetes/manifests/kube-apiserver.yamlIn the
command:list forkube-apiserver, add (or modify) this flag so it appears as a separate list item:- --audit-log-path=/var/log/apiserver/audit.logSave and exit. Editing this file will cause the kubelet to restart the kube-apiserver pod automatically.
-
On every control plane node, wait for the API server pod to restart and become Running:
sudo crictl ps | grep kube-apiserver(If you use Docker instead of containerd, use
sudo docker ps | grep kube-apiserver.) -
On any machine with
kubectlaccess, confirm the API server is healthy:kubectl get --raw=/healthzEnsure the output is
ok. -
On every control plane node, verify the process now includes the
--audit-log-pathflag:/bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--audit-log-path='The command should return a line showing
--audit-log-path=/var/log/apiserver/audit.log.
Using kubectl
kubectl cannot configure API server process flags or edit the static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml on control plane nodes. To set --audit-log-path as required, follow the host-level instructions in the Manual Steps section on every control plane node.
Automation
#!/usr/bin/env bash
#
# Automate CIS Kubernetes 1.2.16:
# Ensure that the --audit-log-path argument is set on the kube-apiserver
#
# Run on: every control plane node (with root privileges)
# Safe to re-run: yes (idempotent)
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
AUDIT_LOG_PATH="/var/log/apiserver/audit.log"
BACKUP_DIR="/etc/kubernetes/manifests/backup-apiserver-$(date +%Y%m%d%H%M%S)"
echo "==> Verifying kube-apiserver manifest exists at ${APISERVER_MANIFEST}"
if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
echo "ERROR: ${APISERVER_MANIFEST} not found on this node. Are you on a control plane node?"
exit 1
fi
echo "==> Creating backup in ${BACKUP_DIR}"
mkdir -p "${BACKUP_DIR}"
cp -p "${APISERVER_MANIFEST}" "${BACKUP_DIR}/"
echo "==> Ensuring audit log directory exists: $(dirname "${AUDIT_LOG_PATH}")"
mkdir -p "$(dirname "${AUDIT_LOG_PATH}")"
chmod 750 "$(dirname "${AUDIT_LOG_PATH}")" || true
# Function to check if --audit-log-path is already present
has_audit_log_path() {
grep -E -- '--audit-log-path(=| )' "${APISERVER_MANIFEST}" >/dev/null 2>&1
}
# Function to update or insert --audit-log-path in the container args
add_or_update_audit_log_path() {
# If flag is already there, replace its value to enforce desired path (idempotent)
if has_audit_log_path; then
echo "==> Updating existing --audit-log-path value to ${AUDIT_LOG_PATH}"
# Handle both "--audit-log-path=/path" and "--audit-log-path", "/path" styles
python3 - <<'PY' "${APISERVER_MANIFEST}" "${AUDIT_LOG_PATH}"
import sys, ruamel.yaml
manifest_path = sys.argv[1]
audit_path = sys.argv[2]
yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
with open(manifest_path, 'r') as f:
data = yaml.load(f)
containers = data.get('spec', {}).get('containers', [])
for c in containers:
if c.get('name') == 'kube-apiserver':
args = c.get('args', [])
new_args = []
skip_next = False
for i, a in enumerate(args):
if skip_next:
skip_next = False
continue
if a.startswith('--audit-log-path='):
new_args.append(f'--audit-log-path={audit_path}')
elif a == '--audit-log-path':
# Skip this and next (old value), replace with new single arg
skip_next = True
new_args.append(f'--audit-log-path={audit_path}')
else:
new_args.append(a)
c['args'] = new_args
with open(manifest_path, 'w') as f:
yaml.dump(data, f)
PY
return
fi
echo "==> Inserting --audit-log-path=${AUDIT_LOG_PATH} into kube-apiserver args"
python3 - <<'PY' "${APISERVER_MANIFEST}" "${AUDIT_LOG_PATH}"
import sys, ruamel.yaml
manifest_path = sys.argv[1]
audit_path = sys.argv[2]
yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
with open(manifest_path, 'r') as f:
data = yaml.load(f)
spec = data.setdefault('spec', {})
containers = spec.setdefault('containers', [])
for c in containers:
if c.get('name') == 'kube-apiserver':
args = c.setdefault('args', [])
# Add flag only if truly absent (extra safety)
if not any(
a == '--audit-log-path' or a.startswith('--audit-log-path=')
for a in args
):
args.append(f'--audit-log-path={audit_path}')
with open(manifest_path, 'w') as f:
yaml.dump(data, f)
PY
}
# Ensure python3 and ruamel.yaml are present
if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: python3 is required for YAML-safe editing. Install python3 and re-run."
exit 1
fi
if ! python3 -c "import ruamel.yaml" >/dev/null 2>&1; then
echo "==> Installing ruamel.yaml via pip (requires network/pip)"
if ! command -v pip3 >/dev/null 2>&1; then
echo "ERROR: pip3 not found and ruamel.yaml is required. Install pip3/ruamel.yaml and re-run."
exit 1
fi
pip3 install --quiet 'ruamel.yaml>=0.17'
fi
add_or_update_audit_log_path
echo "==> Configuration updated. kubelet will automatically restart the kube-apiserver static pod."
echo " Note: This will cause a brief kube-apiserver restart on this control plane node."
# Wait for kube-apiserver process to come back with the correct flag
echo "==> Waiting for kube-apiserver to be running with --audit-log-path ..."
RETRY=30
SLEEP=5
OK=0
for i in $(seq 1 "${RETRY}"); do
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -E -- '--audit-log-path(=| )' >/dev/null 2>&1; then
OK=1
break
fi
sleep "${SLEEP}"
done
echo "==> Verification:"
if [[ "${OK}" -eq 1 ]]; then
/bin/ps -ef | grep kube-apiserver | grep -v grep | sed 's/^/ /'
echo "==> PASS: kube-apiserver is running with --audit-log-path set."
exit 0
else
echo "ERROR: kube-apiserver did not show --audit-log-path in process args within timeout."
echo "Current kube-apiserver processes:"
/bin/ps -ef | grep kube-apiserver | grep -v grep || true
exit 1
fi