Kubernetes Audit Logging: Security Compliance Guide
Kubernetes audit logging records every API server request — who made it, what resource was affected, and what the outcome was. This audit trail is essential for security incident investigation, compliance frameworks like SOC2, PCI-DSS, and HIPAA, and detecting insider threats or compromised service accounts. Without audit logging, answering "who deleted that namespace?" or "which pod read that secret?" is impossible. This guide covers configuring a comprehensive audit policy, shipping logs to a SIEM, and writing detection rules for common attack patterns.
Table of Contents
Audit Levels and Event Stages
Kubernetes audit events are generated at four possible levels of detail. The level determines how much information is captured for each event:
- None — no events are recorded for this rule
- Metadata — records request metadata (user, verb, resource, namespace) but not the request or response body. Low volume, always safe to enable.
- Request — records metadata plus the request body. Useful for capturing what was created or updated.
- RequestResponse — records metadata, request body, and response body. Highest verbosity; use sparingly on large resources.
Events are also associated with stages in the request lifecycle:
- RequestReceived — event generated as soon as the API server receives the request
- ResponseStarted — for streaming requests (watch), when the response headers are sent
- ResponseComplete — when the response body has been sent completely
- Panic — when the API server panics handling a request
Metadata level for most resources and Request level for sensitive operations (secret access, exec into pods, privilege escalation). RequestResponse is extremely verbose and should only be used for targeted forensic investigations.
Writing an Audit Policy
The audit policy file is a YAML document that defines ordered rules. The first matching rule wins. Design your policy from most specific to most general.
# /etc/kubernetes/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
# Omit RequestReceived stage events (reduces volume by ~50%)
omitStages:
- "RequestReceived"
rules:
# Never audit health check endpoints — creates enormous noise
- level: None
nonResourceURLs:
- "/healthz*"
- "/livez*"
- "/readyz*"
- "/version"
- "/metrics"
# Never audit watch events from controllers — very high volume
- level: None
users:
- "system:kube-scheduler"
- "system:kube-controller-manager"
verbs:
- "watch"
- "list"
# Log secret access at Request level — capture what was read
- level: Request
resources:
- group: ""
resources: ["secrets"]
verbs:
- "get"
- "list"
# Log secret mutations at RequestResponse level
- level: RequestResponse
resources:
- group: ""
resources: ["secrets"]
verbs:
- "create"
- "update"
- "patch"
- "delete"
# Log exec, port-forward, attach — critical for insider threat detection
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/portforward", "pods/attach"]
# Log RBAC changes at RequestResponse
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["clusterroles", "clusterrolebindings", "roles", "rolebindings"]
verbs:
- "create"
- "update"
- "patch"
- "delete"
# Log namespace-level resource mutations at Request level
- level: Request
resources:
- group: ""
resources: ["pods", "services", "configmaps", "persistentvolumeclaims"]
- group: "apps"
resources: ["deployments", "statefulsets", "daemonsets"]
verbs:
- "create"
- "update"
- "patch"
- "delete"
# Catch-all: Metadata level for everything else
- level: Metadata
omitStages:
- "RequestReceived"
Enabling Audit Logging on the API Server
Audit logging is configured via API server flags. For kubeadm clusters, edit the static pod manifest at /etc/kubernetes/manifests/kube-apiserver.yaml.
# /etc/kubernetes/manifests/kube-apiserver.yaml — relevant section
spec:
containers:
- command:
- kube-apiserver
# ... existing flags ...
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30 # days to retain log files
- --audit-log-maxbackup=10 # max number of rotated files
- --audit-log-maxsize=100 # max size in MB before rotation
- --audit-log-compress=true # gzip rotated files
volumeMounts:
- mountPath: /etc/kubernetes/audit-policy.yaml
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit/
name: audit-log
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit-policy.yaml
type: File
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit/
type: DirectoryOrCreate
kubectl get pods -n kube-system | grep apiserver.
Webhook Backend for Real-Time Shipping
File-based audit logs must be scraped by a log agent (Fluentbit, Promtail) and shipped to your SIEM or log store. The webhook backend sends audit events directly to an HTTP endpoint in real time, enabling near-instant alerting on suspicious activity.
# audit-webhook-config.yaml
apiVersion: v1
kind: Config
clusters:
- name: audit-webhook
cluster:
server: https://siem.internal.example.com/kubernetes/audit
certificate-authority: /etc/kubernetes/audit-webhook-ca.crt
contexts:
- name: default
context:
cluster: audit-webhook
user: audit-user
current-context: default
users:
- name: audit-user
user:
token: my-webhook-token
# Add to kube-apiserver flags
- --audit-webhook-config-file=/etc/kubernetes/audit-webhook-config.yaml
- --audit-webhook-batch-max-size=400
- --audit-webhook-batch-max-wait=5s
- --audit-webhook-mode=batch # or 'blocking' for guaranteed delivery
Querying and Analysing Audit Logs
Audit log entries are JSON objects. Use jq for quick analysis of local log files, or structured queries in your SIEM platform.
# Find all secret reads in the last hour
jq 'select(.objectRef.resource == "secrets" and .verb == "get")
| {time: .requestReceivedTimestamp, user: .user.username, secret: .objectRef.name, ns: .objectRef.namespace}' \
/var/log/kubernetes/audit/audit.log
# Find all exec commands (insider threat hunting)
jq 'select(.objectRef.subresource == "exec")
| {time: .requestReceivedTimestamp, user: .user.username, pod: .objectRef.name, ns: .objectRef.namespace, command: .requestObject}' \
/var/log/kubernetes/audit/audit.log
# Find failed API calls (potential brute force or misconfigured service accounts)
jq 'select(.responseStatus.code >= 403)
| {time: .requestReceivedTimestamp, user: .user.username, verb: .verb, resource: .objectRef.resource, code: .responseStatus.code}' \
/var/log/kubernetes/audit/audit.log | head -50
Threat Detection Rules
These audit log patterns should trigger immediate security alerts in your SIEM or Falco rule set:
- Anonymous API access:
user.username == "system:anonymous"with any verb other thangeton health endpoints - ClusterRole escalation: create or update of
ClusterRoleBindinggrantingcluster-admin - Secret mass read: single user reads more than 10 secrets within 5 minutes
- Exec into privileged pod: exec subresource on a pod in
kube-systemnamespace - Namespace deletion: delete verb on resource
namespaces— almost always catastrophic if accidental - ServiceAccount token creation: create on
serviceaccounts/tokenby non-controller users
# Example Falco rule for Kubernetes audit events
- rule: K8s Cluster-Admin Binding Created
desc: A ClusterRoleBinding granting cluster-admin was created
condition: >
ka.verb=create and
ka.target.resource=clusterrolebindings and
ka.req.binding.role=cluster-admin
output: >
Cluster admin binding created (user=%ka.user.name binding=%ka.target.name)
priority: CRITICAL
source: k8s_audit
Compliance Requirements (SOC2, PCI-DSS)
Audit logging satisfies multiple control requirements across common compliance frameworks. Here is the mapping:
- SOC2 CC6.1, CC6.6: Logical and physical access restrictions — audit logs prove who accessed what resources and when
- PCI-DSS Req 10: Track and monitor all access to network resources and cardholder data — Kubernetes audit logs cover API-level access to secrets and config containing cardholder data
- HIPAA §164.312(b): Audit controls — audit logs of access to pods and secrets containing PHI satisfy this requirement
- CIS Kubernetes Benchmark 3.2.1: Ensure audit log is enabled — minimum 90-day retention required
Audit Logging on Managed Clusters
Managed Kubernetes services provide built-in audit logging that routes to the cloud provider's native logging service.
- EKS: Enable API server audit logs via the EKS console or CLI (
aws eks update-cluster-config --logging). Logs go to CloudWatch Logs. - GKE: Audit logs are automatically enabled and sent to Cloud Audit Logs (Data Access logs must be explicitly enabled for secret reads).
- AKS: Enable via Diagnostics settings — send to Log Analytics workspace for Kusto queries.
# Enable EKS control plane audit logging
aws eks update-cluster-config \
--name my-cluster \
--region us-east-1 \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
# Query EKS audit logs in CloudWatch
aws logs filter-log-events \
--log-group-name /aws/eks/my-cluster/cluster \
--log-stream-name-prefix kube-apiserver-audit \
--filter-pattern '{ $.objectRef.resource = "secrets" && $.verb = "get" }'