CKS — Audit Policy

9 minute read

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: Request level 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
  1. None — The event is not logged at all.
  2. Metadata — Logs request metadata only: user, timestamp, resource, verb, response code, source IPs. Does not include the request or response body.
  3. Request — Logs metadata plus the request body (the object sent by the client). For example, the full Secret YAML on creation.
  4. 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/RequestResponse levels, 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

Updated: