CKSKubernetes 1.35

Exercises

Exercise: audit logging, from policy to proof

updated 2026-10-22 by seed

Host: controlplane (root) · Time: 15 min · Domain: Monitoring, Logging and Runtime Security

Task

  • Log Metadata for all namespaces at the top of the policy.
  • Log RequestResponse for Secrets and ConfigMaps in namespace config-data.
  • Write to /var/log/kubernetes/audit.log, max age 30 days.
  • Do not log pods/log and pods/status request bodies.

Acceptance criteria

  • Creating a Secret in config-data produces a RequestResponse record with a body.
  • Creating a Pod elsewhere produces a Metadata record.
  • pods/log requests appear with level: Metadata and a redacted body.
  • /healthz still returns ok.

Solution

# /etc/kubernetes/audit/policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
rules:
- level: None
  resources:
  - {group: "", resources: ["pods/log", "pods/status"]}
  verbs: ["get", "list", "watch"]
- level: RequestResponse
  namespaces: ["config-data"]
  verbs: ["create", "update", "patch", "delete"]
  resources:
  - {group: "", resources: ["secrets", "configmaps"]}
- level: Metadata
# kube-apiserver.yaml
    - --audit-policy-file=/etc/kubernetes/audit/policy.yaml
    - --audit-log-path=/var/log/kubernetes/audit.log
    - --audit-log-maxage=30
    - --audit-log-maxbackup=10
    - --audit-log-maxsize=100
    volumeMounts:
    - {name: audit-policy, mountPath: /etc/kubernetes/audit/policy.yaml, readOnly: true}
    - {name: audit-log, mountPath: /var/log/kubernetes, readOnly: false}
  volumes:
  - {name: audit-policy, hostPath: {path: /etc/kubernetes/audit/policy.yaml, type: File}}
  - {name: audit-log, hostPath: {path: /var/log/kubernetes, type: DirectoryOrCreate}}
sleep 20 && kubectl get --raw /healthz
kubectl -n config-data create secret generic probe --from-literal=a=b
sudo tail -1 /var/log/kubernetes/audit.log | jq -r '"\(.level) \(.verb) \(.objectRef.resource)"'

Rule order is the whole exercise: the None rule for pods/log must come
first, or the broad Metadata catch-all swallows it.