Skip to main content

API Server Audit Log Maxbackup Should Be 10 Or More

More Info:

Verifies that --audit-log-maxbackup is set to 10 or an appropriate value so enough old audit log files are retained.

Risk Level

Medium

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every control plane node, back up the API server manifest before editing:

    sudo cp -p /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak
  2. Edit the kube-apiserver static pod manifest to set --audit-log-maxbackup (this edit triggers an automatic kube-apiserver restart):

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml

    In the command: or args: list for kube-apiserver, add or update this flag to an appropriate value (10 or higher), for example:

    - --audit-log-maxbackup=10
  3. Save the file and exit the editor; wait 30–60 seconds for the kubelet to detect the manifest change and restart the kube-apiserver static pod.

  4. On the same control plane node, confirm the API server process is running with the desired --audit-log-maxbackup value:

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

    Ensure the output includes:

    --audit-log-maxbackup=10
Using kubectl

kubectl cannot modify the API server’s static pod manifest or its process flags, so this finding cannot be fixed via the Kubernetes API. The required change must be made directly on each control plane node in /etc/kubernetes/manifests/kube-apiserver.yaml; see the Manual Steps section for the exact procedure.

Automation
#!/usr/bin/env bash
# Remediation: Ensure kube-apiserver --audit-log-maxbackup is set to >= 10
# Scope: Run on every control plane node (as root)

set -euo pipefail

APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
MIN_VALUE=10
TMP_SUFFIX=".$$.tmp"

if [ ! -f "$APISERVER_MANIFEST" ]; then
echo "ERROR: $APISERVER_MANIFEST not found on this node. Are you on a control plane node?"
exit 1
fi

if ! command -v python3 >/dev/null 2>&1; then
echo "ERROR: python3 is required for safe YAML editing."
exit 1
fi

# 1) Backup once per node (idempotent if re-run)
BACKUP="${APISERVER_MANIFEST}.backup"
if [ ! -f "$BACKUP" ]; then
cp -p "$APISERVER_MANIFEST" "$BACKUP"
echo "Backup created at $BACKUP"
else
echo "Backup already exists at $BACKUP"
fi

# 2) Use Python to enforce --audit-log-maxbackup >= MIN_VALUE in the container args
python3 << 'EOF'
import sys, re, shutil, os
from pathlib import Path

manifest_path = Path("/etc/kubernetes/manifests/kube-apiserver.yaml")
min_value = 10

try:
import yaml # type: ignore
except Exception:
yaml = None

content = manifest_path.read_text()

if yaml is None:
# Fallback: regex-based edit on --audit-log-maxbackup=
pattern = re.compile(r'(--audit-log-maxbackup=)(\d+)\b')
m = pattern.search(content)
if m:
current = int(m.group(2))
if current >= min_value:
sys.exit(0)
new = f"{m.group(1)}{min_value}"
new_content = pattern.sub(new, content, count=1)
else:
# Try to inject into a line with kube-apiserver command
lines = content.splitlines()
inserted = False
for i, line in enumerate(lines):
if "kube-apiserver" in line and "command:" in content:
# For command-based spec, we won't try to auto-edit safely without YAML
break
if "kube-apiserver" in line and "--secure-port" in line:
# args-style single line; append new arg
indent = re.match(r'^(\s*)', line).group(1)
lines.insert(i + 1, f'{indent}- --audit-log-maxbackup={min_value}')
inserted = True
break
if not inserted:
# As a last resort, append under first 'args:' section
for i, line in enumerate(lines):
if re.match(r'^\s*args:\s*$', line):
indent = re.match(r'^(\s*)', lines[i+1]).group(1)
lines.insert(i + 1, f'{indent}- --audit-log-maxbackup={min_value}')
inserted = True
break
if not inserted:
print("ERROR: Could not safely inject --audit-log-maxbackup without PyYAML.", file=sys.stderr)
sys.exit(1)
new_content = "\n".join(lines)

tmp_path = str(manifest_path) + ".tmp"
with open(tmp_path, "w") as f:
f.write(new_content)
os.replace(tmp_path, manifest_path)
sys.exit(0)

# YAML-based edit (preferred)
with open(manifest_path) as f:
data = yaml.safe_load(f)

spec = data.get("spec", {})
containers = spec.get("containers", [])
if not containers:
print("ERROR: No containers found in kube-apiserver manifest.", file=sys.stderr)
sys.exit(1)

api = containers[0]
args = api.get("args", [])

# Normalize existing values, if any
new_args = []
found = False
for arg in args:
if isinstance(arg, str) and arg.startswith("--audit-log-maxbackup="):
try:
cur = int(arg.split("=", 1)[1])
except ValueError:
cur = min_value
if cur < min_value:
arg = f"--audit-log-maxbackup={min_value}"
found = True
new_args.append(arg)

if not found:
new_args.append(f"--audit-log-maxbackup={min_value}")

# If nothing changed and already >= min_value, exit without rewrite
if args == new_args:
sys.exit(0)

api["args"] = new_args
spec["containers"] = [api]
data["spec"] = spec

tmp_path = str(manifest_path) + ".tmp"
with open(tmp_path, "w") as f:
yaml.safe_dump(data, f, default_flow_style=False)
os.replace(tmp_path, manifest_path)
EOF

echo "Updated $APISERVER_MANIFEST; kubelet will restart kube-apiserver static pod automatically."

# 3) Wait for kube-apiserver to restart and stabilize
echo "Waiting up to 120s for kube-apiserver process to be running with new flag..."
end=$((SECONDS+120))
while [ $SECONDS -lt $end ]; do
if /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
break
fi
sleep 3
done

# 4) Verification (same basis as audit command)
/bin/ps -ef | grep kube-apiserver | grep -v grep

# Additional verification of the flag value
echo
echo "Verifying --audit-log-maxbackup is set to at least ${MIN_VALUE}:"
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--audit-log-maxbackup=' >/dev/null 2>&1; then
/bin/ps -ef | grep kube-apiserver | grep -v grep | tr -s ' ' | \
sed 's/ /\n/g' | grep -- '^--audit-log-maxbackup=' || true
else
echo "ERROR: kube-apiserver is running but --audit-log-maxbackup flag not found in process list."
exit 1
fi