Enable Kubernetes audit logging
/etc/kubernetes/audit-policy.yamlapiVersion: audit.k8s.io/v1kind: Policyrules: # Don't log secret/configmap bodies — just that they were accessed - level: Metadata resources: - group: "" resources: ["secrets", "configmaps"] # Log changes at RequestResponse, reads at Metadata - level: RequestResponse verbs: ["create", "update", "patch", "delete"] - level: MetadataThen wire it up on the API server: --audit-policy-file=/etc/kubernetes/audit-policy.yaml
and --audit-log-path=/var/log/kubernetes/audit.log. On managed clusters,
audit logging is usually a provider setting (CloudWatch, Cloud Logging) rather
than these flags.
Why it matters
Section titled “Why it matters”Unlike audit logging in MongoDB or Elasticsearch, Kubernetes audit logging is free — it’s a core API server feature, no license required. But it’s off by default: a stock API server records nothing about who did what.
That gap is the difference between investigating an incident and guessing at one. Every meaningful action goes through the API server — who read which secret, who created that pod, who changed that RBAC binding, which service account authenticated from where. Without an audit policy, none of it is recorded, and after a compromise you have no way to answer the questions that matter: what did they touch, and what do you need to rotate.
This is medium because it closes no exposure — it changes what you can
reconstruct. But it’s free and the reconstruction is genuinely valuable, which is
why it ranks above the paid-only audit pages on the datastores.
The four levels
Section titled “The four levels”An audit policy assigns each request one of four levels, which controls how much is recorded:
| Level | Records |
|---|---|
None |
Nothing — use to drop noise |
Metadata |
Who, what, when, verb, resource — not the request/response body |
Request |
Metadata plus the request body |
RequestResponse |
Metadata plus request and response bodies |
The skill is matching level to resource. RequestResponse on everything produces
an enormous, expensive log — and, critically, logs secret contents. Metadata
on secrets and configmaps records that they were accessed without recording their
values; RequestResponse on mutations captures what changed for the objects where
that matters. Rules are evaluated top to bottom, first match wins, so put the
specific low-level rules (secrets → Metadata) first.
Don’t log your secrets into the audit log
Section titled “Don’t log your secrets into the audit log”This is the trap worth stating plainly, because the naive “log everything at
RequestResponse” advice creates it: at Request or RequestResponse level, the
audit log captures the body of the request — and for a secret, that’s the secret.
You’d have taken every secret in the cluster and copied it into a log file that
goes to your log aggregator, with different access controls and retention than
etcd. That’s the same mistake as the Postgres log_statement
trap, at cluster scale.
Always log secrets and configmaps at Metadata — the first rule in the policy
above does exactly this, before any broader rule can catch them.
Ship it off the node
Section titled “Ship it off the node”An audit log on the control plane node is readable by anyone who compromises the control plane — the same people you’re trying to have a record of. And it competes for disk with the thing whose health you depend on. Ship it to an external system (a log pipeline, SIEM, or the cloud provider’s logging), and set rotation on the local file so it can’t fill the disk:
- --audit-log-maxsize=100 # MB per file- --audit-log-maxbackup=10- --audit-log-maxage=30Related
Section titled “Related”- Secure the API server — where the audit policy is wired in.
- RBAC least privilege — the audit log shows whether your RBAC is being tested.