Skip to content

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.

Kubernetes Audit Policy: What to Log for Forensics

Write a Kubernetes audit policy that captures the evidence an investigation needs (exec, RBAC, workloads, tokens) without logging secrets or drowning in noise.

Published on 5 min read

TL;DR. An audit policy is an ordered list of rules; the first match sets the audit level of each request. For forensics, you want Metadata on everything, Request or RequestResponse on writes to RBAC objects, pods and workload controllers, Request on pods/exec, pods/attach, pods/portforward, nodes/proxy and serviceaccounts/token, and strictly Metadata on secrets, configmaps and token reviews. Drop the health checks and the kubelet and kube-proxy chatter. Ship the result outside the cluster and keep it for at least 90 days.

A self-managed API server writes nothing until you give it a policy: the Kubernetes auditing documentation is explicit that without --audit-policy-file no events are logged. On EKS, GKE and AKS, the provider sets the policy for you, and your only choice is which logs to export (see the EKS, GKE and AKS export guide). Either way, knowing what a good policy looks like tells you what evidence you can expect.

How rules are evaluated

Each rule can match on users, userGroups, verbs, resources (with API group and optional resourceNames), namespaces and nonResourceURLs. Rules are processed in order and the first matching rule wins. A request that matches no rule is not logged. That has two practical consequences:

  • Put your exclusions (level: None) and your "never log bodies" rules before the broad rules.
  • End with a catch-all level: Metadata so nothing falls through silently.

omitStages removes stages globally or per rule. Almost everyone omits RequestReceived, which duplicates the final event. omitManagedFields: true drops the bulky metadata.managedFields from logged bodies.

What investigators need, and why

EvidenceMinimum levelWhy
Who called what, from where, with which resultMetadata on everythingIdentity, source IP, verb, object, code: the backbone of every timeline
Pod and workload specs (pods, deployments, daemonsets, statefulsets, jobs, cronjobs) on create / update / patchRequestWithout the body you cannot see privileged, hostPID, a hostPath or the image
RBAC writes (roles, rolebindings, clusterroles, clusterrolebindings)Request or RequestResponseThe roleRef and subjects tell you whether a binding grants cluster-admin and to whom
pods/exec, pods/attach, pods/portforward, nodes/proxyMetadata is enough, Request is fineThe command is in requestURI; the session itself is never recorded
serviceaccounts/tokenRequest, not RequestResponseThe request shows the requested expiry and audience; the response contains the token itself
secrets, configmaps, tokenreviewsMetadata onlyBodies contain the credentials; logging them turns the audit log into a credential store
Deletion of eventsMetadataDeleting Events is a cheap anti-forensics step

A forensics-oriented policy

This policy is a starting point, not a drop-in. Test it on a non-production cluster and watch log volume.

apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - "RequestReceived"
omitManagedFields: true
rules:
  # 1. Noise: health checks and high-frequency system reads
  - level: None
    nonResourceURLs: ["/healthz*", "/livez*", "/readyz*", "/version"]
  - level: None
    users: ["system:kube-proxy"]
    verbs: ["watch"]
    resources:
      - group: ""
        resources: ["endpoints", "services", "services/status"]
  - level: None
    userGroups: ["system:nodes"]
    verbs: ["get"]
    resources:
      - group: ""
        resources: ["nodes", "nodes/status"]

  # 2. Credentials: never log bodies
  - level: Metadata
    resources:
      - group: ""
        resources: ["secrets", "configmaps"]
      - group: "authentication.k8s.io"
        resources: ["tokenreviews"]

  # 3. Interactive access and token minting (request only: the
  #    TokenRequest response contains the token)
  - level: Request
    resources:
      - group: ""
        resources: ["pods/exec", "pods/attach", "pods/portforward",
                    "nodes/proxy", "serviceaccounts/token"]

  # 4. Events: keep deletions, drop the rest
  - level: Metadata
    verbs: ["delete", "deletecollection"]
    resources:
      - group: ""
        resources: ["events"]
      - group: "events.k8s.io"
        resources: ["events"]
  - level: None
    resources:
      - group: ""
        resources: ["events"]
      - group: "events.k8s.io"
        resources: ["events"]

  # 5. Writes to workloads, RBAC, service accounts, admission: full bodies
  - level: RequestResponse
    verbs: ["create", "update", "patch", "delete", "deletecollection"]
    resources:
      - group: ""
        resources: ["pods", "serviceaccounts", "namespaces", "nodes"]
      - group: "apps"
      - group: "batch"
      - group: "rbac.authorization.k8s.io"
      - group: "admissionregistration.k8s.io"

  # 6. Everything else
  - level: Metadata

A few choices worth explaining:

  • Rule 2 before rule 5. Secrets must stay at Metadata even on create and update, so the secrets rule has to match first.
  • Rule 3 uses Request. Streaming subresources have no meaningful body; what you need (the command, the container) is in the URI. For serviceaccounts/token, RequestResponse would log a working bearer token.
  • Kubelet status updates (nodes/status, pods/status patches) fall into rule 6 at Metadata. That is plenty for investigations and far cheaper than bodies.
  • Reads stay at Metadata. Logging response bodies of list calls is what makes audit logs explode in size; it rarely helps an investigation.

For comparison, the policy AWS publishes in the EKS best practices guide follows the same structure: Metadata for secrets, configmaps and token reviews, Request for serviceaccounts/token, full bodies for most known API groups. One notable difference: it drops events entirely, so event deletions are not visible on EKS.

Enabling it on a self-managed API server

On a kubeadm-style control plane, the API server runs as a static pod. Add the flags to /etc/kubernetes/manifests/kube-apiserver.yaml and mount the policy and the log directory:

- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100

maxage is in days, maxsize in megabytes. The kubelet restarts the API server when the manifest changes. Distributions such as k3s and RKE2 expose the same flags through their own configuration; check their documentation for the exact keys.

The documentation also notes that auditing increases API server memory use, proportional to what you log. That is one more reason to keep bodies to the writes that matter.

Get the logs off the box

An attacker with cluster-admin and a privileged pod can read and edit files on control-plane nodes. Logs that only exist there are logs the attacker controls. Two options:

  • The webhook backend (--audit-webhook-config-file) sends batches to an HTTP endpoint: a log collector, a SIEM, a queue.
  • The log backend plus a shipper (Fluent Bit, Vector, the cloud agent) tails audit.log into central storage.

The NSA/CISA Kubernetes Hardening Guide also recommends enabling audit logging and storing logs outside the cluster. Keep at least 90 days: initial access often predates detection by weeks.

Check your policy against real detections

A good test: run a known-bad sequence on a lab cluster (exec into a pod, create a privileged pod with a hostPath of /, bind a service account to cluster-admin), export the log and drop it into the audit log analyzer. If the privileged-pod or cluster-admin findings do not appear, the relevant bodies are not being logged. The analyzer's reference section lists the detections; the how-to guide explains how to read the result.

Related articles

The blind spots of Kubernetes audit logs: in-container activity, kubelet and etcd access, policy gaps, spoofable fields, and the evidence that fills them.
Field-by-field guide to the Kubernetes audit log format: stages, levels, user, sourceIPs, objectRef, responseStatus, annotations, and what matters in forensics.
A fictional Kubernetes incident, step by step in the audit log: exposed dashboard token, can-i recon, secret theft, privileged DaemonSet, cluster-admin, XMRig.

This tool is not affiliated with, endorsed by or sponsored by The Linux Foundation, the Cloud Native Computing Foundation (CNCF) or the Kubernetes project. Kubernetes and K8s are registered trademarks of The Linux Foundation. EKS, GKE, AKS and other names are trademarks of their respective owners.