CKS — Audit Policy
Audit Policy
CKS Exam Domain: Domain 6 — Monitoring, Logging & Runtime Security (20%)
The audit policy defines what gets logged and at what detail level. Without a policy, the API server records nothing — audit logging is opt-in. The policy file is a Kubernetes resource of kind Policy under the audit.k8s.io/v1 API group, and the API server evaluates its rules top-down for every incoming request.
Writing an effective audit policy is the foundation of Kubernetes audit logging. Too little logging and you miss security events. Too much logging and you drown in noise while degrading API server performance.
1. Audit Policy Resource Structure
Every audit policy follows this structure:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
- level: <None|Metadata|Request|RequestResponse>
users: [...]
userGroups: [...]
verbs: [...]
resources:
- group: ""
resources: ["secrets"]
resourceNames: ["my-secret"]
namespaces: ["production"]
nonResourceURLs: ["/healthz", "/version"]
| Field | Type | Description |
|---|---|---|
apiVersion |
string | Must be audit.k8s.io/v1 |
kind |
string | Must be Policy |
omitStages |
[]string | Request stages to skip (typically RequestReceived) |
omitManagedFields |
bool | Remove managedFields from logged objects (reduces noise significantly) |
rules |
[]Rule | Ordered list of audit rules — first match wins |
2. Audit Levels
The audit level determines how much detail is captured for a matching event. This is the single most important concept in audit policy design.
| Level | What Is Logged | Performance Impact | Use Case |
|---|---|---|---|
| None | Nothing — event is not recorded | Zero | High-frequency read-only endpoints (healthz, metrics) |
| Metadata | Request metadata: user, timestamp, resource, verb, response code | Low | Catch-all rule, general activity tracking |
| Request | Metadata + request body | Medium | Secret creation, RBAC changes, pod exec |
| RequestResponse | Metadata + request body + response body | High | Forensic-level logging for critical resources |
What Each Level Captures
None:
→ (nothing)
Metadata:
→ user.username, user.groups
→ verb (get, create, delete, etc.)
→ objectRef (resource, namespace, name)
→ responseStatus.code
→ sourceIPs
→ requestReceivedTimestamp, stageTimestamp
Request (Metadata +):
→ requestObject (the full body sent by the client)
→ e.g., the complete Secret YAML, the Pod spec for exec
RequestResponse (Request +):
→ responseObject (the full body returned by the API server)
→ e.g., the created Secret with server-generated fields
Security Impact:
Requestlevel for Secrets captures the actual secret values in the audit log. This is necessary for forensic investigation but means the audit log itself becomes a sensitive asset that must be protected.
3. Rule Matching
Each rule in the policy can match on multiple criteria. All specified fields must match for the rule to apply (logical AND). Within a field, values are OR’d.
Matching Fields
| Field | Description | Example |
|---|---|---|
users |
Match specific usernames | ["system:serviceaccount:kube-system:default"] |
userGroups |
Match user group membership | ["system:authenticated"] |
verbs |
Match API verbs | ["create", "delete", "patch"] |
resources |
Match API resources (group + resource + optional names) | [{group: "", resources: ["secrets"]}] |
namespaces |
Match target namespace | ["production", "finance"] |
nonResourceURLs |
Match non-resource endpoints | ["/healthz*", "/version"] |
Resource Matching Syntax
resources:
- group: "" # core API group
resources: ["secrets"]
resourceNames: ["db-password"] # optional — specific resource names
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
- group: "apps"
resources: ["deployments"]
4. Rule Precedence — First Match Wins
The API server evaluates rules top to bottom and uses the first matching rule. This makes rule ordering critical.
rules:
# Rule 1: Don't log read-only requests to endpoints/healthz
- level: None
nonResourceURLs:
- "/healthz*"
- "/version"
- "/readyz*"
# Rule 2: Log all Secret access at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
# Rule 3: Log RBAC changes at RequestResponse level
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# Rule 4: Catch-all — log everything else at Metadata
- level: Metadata
If a Secret access request comes in, it matches Rule 2 and is logged at Request level. The API server stops evaluating — Rules 3 and 4 are never checked for that request.
Exam Tip: Always place your most specific rules first and the catch-all rule last. If you put the catch-all first, every request matches it at Metadata level and your specific rules never fire.
5. omitStages
Kubernetes API requests pass through four stages:
| Stage | When |
|---|---|
RequestReceived |
Immediately when the API server receives the request (before authorization) |
ResponseStarted |
After response headers are sent but before the response body (only for long-running requests like watches) |
ResponseComplete |
After the response body has been completely sent |
Panic |
When a panic occurs during request handling |
The RequestReceived stage generates an event before the API server has even decided whether to allow the request. For most use cases, this is pure noise — you only care about completed requests.
omitStages:
- "RequestReceived"
This single line typically reduces audit log volume by ~50% without losing any meaningful security data.
6. omitManagedFields
Kubernetes 1.21+ adds managedFields to every object, tracking which field manager owns which fields. These fields are verbose and rarely useful for security auditing.
omitManagedFields: true
This can reduce the size of Request and RequestResponse level events by 30-60%.
7. Complete Audit Policy Examples
Example 1: Log All Secret Access at Request Level
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# Don't log requests to health endpoints
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
# Don't log requests from the system:kube-controller-manager
# or system:kube-scheduler to reduce noise
- level: None
users:
- "system:kube-controller-manager"
- "system:kube-scheduler"
- "system:kube-proxy"
# Log all Secret operations at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
# Catch-all: log everything else at Metadata
- level: Metadata
Example 2: Log Privileged Pod Creation at RequestResponse
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
# Log pod creation/modification at RequestResponse level
# This captures the full pod spec including security context
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
verbs: ["create", "update", "patch"]
# Log pod exec and attach at Request level
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# Log Secret access at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
# Log RBAC modifications at RequestResponse level
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# Catch-all
- level: Metadata
Example 3: CKS Exam-Focused Policy
This policy covers the resources most likely to appear in a CKS exam task:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# Exclude noise from system components and health checks
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
- "/api"
- "/api/v1"
- "/apis"
- "/apis/*"
- "/version"
- level: None
users:
- "system:apiserver"
- "system:kube-controller-manager"
- "system:kube-scheduler"
- "system:kube-proxy"
verbs: ["get", "list", "watch"]
# 1. Secrets — always log at Request level
- level: Request
resources:
- group: ""
resources: ["secrets", "configmaps"]
# 2. Pod exec/attach — critical for detecting lateral movement
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# 3. Authentication — token requests, CSRs
- level: RequestResponse
resources:
- group: "authentication.k8s.io"
resources: ["tokenreviews"]
- group: "certificates.k8s.io"
resources: ["certificatesigningrequests"]
# 4. RBAC changes — privilege escalation detection
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# 5. ServiceAccount token creation
- level: Request
resources:
- group: ""
resources: ["serviceaccounts/token"]
# 6. Node-level operations
- level: Request
resources:
- group: ""
resources: ["nodes", "nodes/status"]
verbs: ["create", "update", "patch", "delete"]
# Catch-all for everything else
- level: Metadata
8. Common Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Catch-all rule first | All specific rules become dead code | Move catch-all to the last position |
RequestResponse for everything |
API server performance degradation, massive log volume | Use RequestResponse only for critical resources |
Missing omitStages |
Duplicate events for every request (RequestReceived + ResponseComplete) | Add omitStages: ["RequestReceived"] |
Not logging pods/exec |
Lateral movement via exec is invisible | Add an explicit rule for pods/exec, pods/attach |
| Logging system components at high levels | Noise from controllers overwhelms real events | Add None rules for system users on read verbs |
9. Practice Questions
Question 1
What are the four audit levels in Kubernetes, and what does each capture?
Show Answer
- None — The event is not logged at all.
- Metadata — Logs request metadata only: user, timestamp, resource, verb, response code, source IPs. Does not include the request or response body.
- Request — Logs metadata plus the request body (the object sent by the client). For example, the full Secret YAML on creation.
- RequestResponse — Logs metadata, request body, and response body (the object returned by the API server). Most verbose and expensive.
Question 2
Write an audit policy rule that logs all Secret access in the production namespace at Request level but does not log anything for the kube-system namespace.
Show Answer
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# Exclude kube-system entirely
- level: None
namespaces: ["kube-system"]
# Log secrets in production at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
namespaces: ["production"]
# Catch-all
- level: Metadata
The None rule for kube-system must come before other rules because first match wins. This prevents any events from kube-system from being logged.
Question 3
Explain why rule ordering matters in an audit policy. What happens if you place a Metadata catch-all rule before a RequestResponse rule for secrets?
Show Answer
Kubernetes audit policy evaluation uses first-match-wins semantics. The API server evaluates rules top to bottom and uses the first rule whose criteria match the request.
If you place a Metadata catch-all rule before a RequestResponse rule for secrets:
rules:
- level: Metadata # ← Matches EVERYTHING
- level: RequestResponse # ← Never reached for any request
resources:
- group: ""
resources: ["secrets"]
Every request — including secret access — matches the catch-all at Metadata level. The RequestResponse rule for secrets is dead code and never fires. You lose the request and response bodies for secret operations.
Fix: Always place specific rules before the catch-all.
Question 4
What is the purpose of omitStages: ["RequestReceived"] and why is it recommended?
Show Answer
Every API request passes through multiple stages. Without omitStages, the audit system generates separate events at the RequestReceived stage (when the request arrives, before authorization) and again at ResponseComplete (after the request is fully processed).
The RequestReceived event is logged before the API server knows whether the request will be authorized or what the response will be. It rarely provides useful security information because:
- There is no response status code yet
- The request has not been authorized
- For
Request/RequestResponselevels, the body may not be fully parsed
Omitting RequestReceived typically reduces audit log volume by approximately 50% with no loss of meaningful security data. The ResponseComplete event captures everything you need.
Question 5
You need to create an audit policy that logs pods/exec operations at RequestResponse level. Write the policy rule and explain why this is security-critical.
Show Answer
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach"]
verbs: ["create"]
Note that pods/exec is a subresource of pods. The verb is create because exec requests create an exec session (they do not use the exec verb).
This is security-critical because kubectl exec is the primary mechanism for:
- Lateral movement — an attacker with exec permissions can get a shell inside any pod they have access to
- Data exfiltration — reading sensitive files, environment variables, or mounted secrets from within a running container
- Privilege escalation — if the container runs as root or has elevated capabilities, exec provides root access
Logging at RequestResponse level captures both the command executed (request body) and the API server’s response, providing a complete forensic record.
| Previous | Up | Next |
|---|---|---|
| Audit Logging | CKS Home | Audit Backends |