CKS — Audit Best Practices
Audit Best Practices
CKS Exam Domain: Domain 6 — Monitoring, Logging & Runtime Security (20%)
A well-designed audit logging strategy balances three competing concerns: security visibility (log enough to detect threats), performance (don’t overload the API server), and cost (store and process audit data efficiently). This section covers production-grade audit policies, log management, compliance requirements, and the audit policy template you should know for the CKS exam.
1. Balancing Audit Detail vs Performance
The audit level you choose has a direct impact on API server performance and log volume.
Performance Impact by Level
| Level | Payload Size | Disk I/O | CPU Overhead | Use When |
|---|---|---|---|---|
| None | 0 bytes | None | None | Health checks, discovery endpoints, system component reads |
| Metadata | ~500 bytes–1 KB | Low | Minimal | General activity tracking, catch-all rule |
| Request | 1–50 KB | Medium | Low–Medium | Secrets, configmaps, RBAC changes |
| RequestResponse | 5–500+ KB | High | Medium–High | Forensic-critical resources (exec, token creation) |
The RequestResponse Trap
RequestResponse logs both the request body and response body. For list operations, the response body can be megabytes — every Pod, every Secret, every Deployment in the namespace. A single kubectl get pods -A at RequestResponse level generates an audit event that can be 10+ MB.
Rule: Never use RequestResponse as a catch-all. Reserve it for:
pods/execandpods/attach(small payloads, high security value)- RBAC mutations (role/binding creation — small payloads)
- TokenReview and CertificateSigningRequest creation
Reducing Log Volume
# 1. Omit RequestReceived stage (reduces events by ~50%)
omitStages:
- "RequestReceived"
# 2. Omit managedFields (reduces event size by 30-60%)
omitManagedFields: true
# 3. Exclude system component reads
- level: None
users:
- "system:kube-controller-manager"
- "system:kube-scheduler"
- "system:kube-proxy"
- "system:apiserver"
verbs: ["get", "list", "watch"]
# 4. Exclude health endpoints
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
- "/metrics"
2. Recommended Audit Policy for Production
A layered audit policy follows the principle of specific rules first, catch-all last:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# ─── Layer 1: Exclude noise ───────────────────────────
# Health and discovery endpoints
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
- "/metrics"
- "/api"
- "/api/v1"
- "/apis"
- "/apis/*"
- "/version"
- "/openapi/*"
- "/.well-known/*"
# System component read operations
- level: None
users:
- "system:apiserver"
- "system:kube-controller-manager"
- "system:kube-scheduler"
- "system:kube-proxy"
verbs: ["get", "list", "watch"]
# Kubelet node status updates (very high frequency)
- level: None
users:
- "system:node:*"
resources:
- group: ""
resources: ["nodes/status"]
verbs: ["patch", "update"]
# Events are informational, not security-relevant
- level: None
resources:
- group: ""
resources: ["events"]
# ─── Layer 2: High-security resources ─────────────────
# Secrets — log at Request level (captures secret values)
- level: Request
resources:
- group: ""
resources: ["secrets"]
# ConfigMaps — log at Request level
- level: Request
resources:
- group: ""
resources: ["configmaps"]
# Pod exec/attach/portforward — log at RequestResponse
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# RBAC mutations — log at RequestResponse
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# Authentication tokens and certificates
- level: RequestResponse
resources:
- group: "authentication.k8s.io"
resources: ["tokenreviews"]
- group: "certificates.k8s.io"
resources: ["certificatesigningrequests"]
- group: ""
resources: ["serviceaccounts/token"]
# ─── Layer 3: Medium-security resources ───────────────
# Pod/Deployment/StatefulSet mutations
- level: Request
resources:
- group: ""
resources: ["pods"]
- group: "apps"
resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
verbs: ["create", "update", "patch", "delete"]
# Namespace operations
- level: Request
resources:
- group: ""
resources: ["namespaces"]
verbs: ["create", "update", "patch", "delete"]
# Node operations
- level: Request
resources:
- group: ""
resources: ["nodes"]
verbs: ["create", "update", "patch", "delete"]
# ─── Layer 4: Catch-all ──────────────────────────────
# Everything else at Metadata level
- level: Metadata
3. Retaining and Rotating Audit Logs
Log Rotation Settings
| Flag | Recommended Value | Rationale |
|---|---|---|
--audit-log-maxsize |
100 (MB) |
Rotate before files become too large to process |
--audit-log-maxbackup |
10 |
Keep 10 rotated files (= 1 GB total max) |
--audit-log-maxage |
30 |
Delete rotated files older than 30 days |
Disk Space Calculation
Max disk usage = maxsize × (maxbackup + 1)
= 100 MB × 11
= 1.1 GB
With maxage = 30 days:
Files older than 30 days are deleted regardless of maxbackup count.
Monitoring Disk Usage
# Check current audit log size
du -sh /var/log/kubernetes/audit/
# Count rotated files
ls -la /var/log/kubernetes/audit/
# Set up a cron job to alert if disk usage exceeds threshold
cat <<'EOF' > /etc/cron.d/audit-disk-check
*/30 * * * * root [ $(du -sm /var/log/kubernetes/audit/ | cut -f1) -gt 800 ] && echo "Audit log disk usage critical" | logger
EOF
What Happens When Disk Fills Up
If the audit log directory runs out of disk space:
- The API server cannot write new audit events
- Depending on configuration, the API server may reject requests or crash
- Audit events are permanently lost during this period
Mitigation: Always monitor disk usage and set appropriate rotation values.
4. Centralized Log Collection
In production, audit logs should be shipped to a centralized system for aggregation, search, and alerting across all clusters.
Architecture: Fluent Bit → Elasticsearch → Kibana
┌──────────────────────┐ ┌──────────────────────┐
│ Control Plane Node │ │ Control Plane Node │
│ │ │ (HA Cluster) │
│ /var/log/kubernetes/ │ │ /var/log/kubernetes/ │
│ audit/audit.log │ │ audit/audit.log │
└──────────┬───────────┘ └──────────┬───────────┘
│ │
▼ ▼
┌──────────────────────────────────────────────────────┐
│ Fluent Bit │
│ input: tail /var/log/kubernetes/audit/audit.log │
│ parser: json │
│ filter: add cluster_name tag │
│ output: elasticsearch │
└──────────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Elasticsearch │
│ Index: kubernetes-audit-YYYY.MM.DD │
│ Retention: 90 days (ILM policy) │
└──────────────────────┬───────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ Kibana │
│ Dashboards: API activity, secret access, exec │
│ Alerts: anonymous access, RBAC changes, 403 spikes │
└──────────────────────────────────────────────────────┘
Fluent Bit Configuration for Audit Logs
[INPUT]
Name tail
Path /var/log/kubernetes/audit/audit.log
Parser json
Tag k8s.audit
Refresh_Interval 5
DB /var/log/fluent-bit/audit-position.db
[FILTER]
Name modify
Match k8s.audit
Add cluster_name production-us-east-1
[OUTPUT]
Name es
Match k8s.audit
Host elasticsearch.logging.svc.cluster.local
Port 9200
Index kubernetes-audit
Type _doc
Logstash_Format On
Logstash_Prefix kubernetes-audit
Retry_Limit 5
Alternative: Webhook Backend to Fluentd
Instead of reading the log file, you can use the webhook backend to push events directly:
# API server flags
--audit-webhook-config-file=/etc/kubernetes/audit/webhook-kubeconfig.yaml
--audit-webhook-batch-max-wait=5s
This avoids the need for a file-based log collector on the control plane node.
5. Compliance Requirements
Different compliance frameworks have specific audit trail requirements.
Compliance Mapping
| Framework | Audit Requirement | Kubernetes Implementation |
|---|---|---|
| SOC 2 | Log all access to sensitive data | Audit secrets and configmaps at Request level |
| SOC 2 | Log authentication events | Audit TokenReview, CSR at RequestResponse level |
| SOC 2 | Retain logs for 1 year | Set maxage appropriately or use centralized storage with 1-year retention |
| HIPAA | Track all access to PHI | Audit all namespace-scoped resources in PHI-containing namespaces |
| HIPAA | Access logs must be immutable | Ship to immutable storage (S3 with Object Lock, WORM storage) |
| PCI-DSS | Log all access to cardholder data | Audit secrets in PCI-scoped namespaces at Request level |
| PCI-DSS | Log all administrative actions | Audit RBAC, node operations, namespace changes at RequestResponse |
| PCI-DSS | Retain logs for 1 year (3 months immediately available) | Centralized storage with tiered retention |
Immutable Audit Storage
For compliance, audit logs must not be tampered with after collection:
# Ship to S3 with Object Lock (immutable)
aws s3api put-object-lock-configuration \
--bucket kubernetes-audit-logs \
--object-lock-configuration '{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": {
"Mode": "COMPLIANCE",
"Days": 365
}
}
}'
6. What to Audit at Minimum
Even with limited resources, these resources and actions must always be audited:
| Resource | Level | Rationale |
|---|---|---|
| Secrets (all verbs) | Request | Credential access — most critical resource for attackers |
| ConfigMaps (write verbs) | Request | Configuration tampering — can inject malicious configs |
| RBAC resources (write verbs) | RequestResponse | Privilege escalation — new bindings grant access |
| pods/exec, pods/attach | RequestResponse | Lateral movement — live access to running containers |
| Nodes (write verbs) | Request | Node compromise — tainting, draining, or modifying nodes |
| Authentication resources | RequestResponse | Token creation, CSR approval — credential generation |
| Namespaces (write verbs) | Request | Namespace creation/deletion affects isolation boundaries |
| ServiceAccount tokens | Request | Token generation — new credentials for API access |
The “Must-Have” Minimal Policy
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
omitManagedFields: true
rules:
- level: None
nonResourceURLs: ["/healthz*", "/readyz*", "/livez*"]
- level: None
resources:
- group: ""
resources: ["events"]
- level: Request
resources:
- group: ""
resources: ["secrets", "configmaps", "serviceaccounts/token"]
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach"]
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
- level: Metadata
7. Audit Policy Template for CKS Exam Preparation
This is the template you should memorize for the CKS exam. It covers the most commonly tested scenarios:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# 1. Silence noise
- level: None
nonResourceURLs:
- "/healthz*"
- "/readyz*"
- "/livez*"
# 2. Secrets — ALWAYS log at Request level
- level: Request
resources:
- group: ""
resources: ["secrets"]
# 3. Pod exec — log with full details
- level: RequestResponse
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# 4. RBAC changes — capture full details
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
verbs: ["create", "update", "patch", "delete"]
# 5. Everything else — metadata only
- level: Metadata
Exam Workflow
- Create the policy file:
/etc/kubernetes/audit/audit-policy.yaml - Add API server flags:
--audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml--audit-log-path=/var/log/kubernetes/audit/audit.log--audit-log-maxage=30--audit-log-maxbackup=10--audit-log-maxsize=100
- Add volumeMounts and volumes for both the policy directory and log directory
- Wait for restart and verify with
tail /var/log/kubernetes/audit/audit.log | jq .
8. Practice Questions
Question 1
Why should you avoid using RequestResponse level as a catch-all rule?
Show Answer
RequestResponse logs the full request body AND the full response body for every matching event. When used as a catch-all:
-
Massive log volume —
listoperations return the entire resource collection in the response body. Akubectl get pods -Aresponse can be megabytes. With hundreds of controllers doing list/watch operations, disk fills rapidly. -
API server performance degradation — serializing and writing large response bodies adds CPU and I/O overhead to every API request.
-
Sensitive data exposure — response bodies for list operations include ALL resources, including secrets and configmaps you may not intend to log.
-
Cost — storing and processing 100x more data than necessary increases infrastructure costs.
Best practice: Use RequestResponse only for high-value, low-volume resources: pods/exec, RBAC mutations, authentication tokens. Use Metadata as the catch-all.
Question 2
Design an audit policy for a cluster that must comply with PCI-DSS. What resources must be audited and at what levels?
Show Answer
PCI-DSS requires logging all access to cardholder data and all administrative actions:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: ["RequestReceived"]
omitManagedFields: true
rules:
- level: None
nonResourceURLs: ["/healthz*", "/readyz*", "/livez*"]
- level: None
resources:
- group: ""
resources: ["events"]
# PCI-DSS: Log all access to secrets (cardholder data may be stored here)
- level: Request
resources:
- group: ""
resources: ["secrets", "configmaps"]
# PCI-DSS: Log all administrative actions with full detail
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
- group: ""
resources: ["pods/exec", "pods/attach", "serviceaccounts/token"]
- group: "certificates.k8s.io"
resources: ["certificatesigningrequests"]
# PCI-DSS: Log namespace and node changes
- level: Request
resources:
- group: ""
resources: ["namespaces", "nodes"]
verbs: ["create", "update", "patch", "delete"]
# Catch-all at Metadata
- level: Metadata
Additional PCI-DSS requirements:
- Retain logs for at least 1 year (3 months immediately available)
- Store logs in immutable storage
- Review logs regularly
- Alert on suspicious activity
Question 3
What is the difference between --audit-log-maxage and --audit-log-maxbackup, and how do they interact?
Show Answer
| Flag | What It Does |
|---|---|
--audit-log-maxage |
Deletes rotated audit log files that are older than N days |
--audit-log-maxbackup |
Keeps only the most recent N rotated files, regardless of age |
They interact as follows:
- Both are checked — a rotated file is deleted if it violates either condition.
- If
maxage=30andmaxbackup=10, a file older than 30 days is deleted even if there are fewer than 10 backups. Similarly, if rotation happens frequently and there are 11 backup files, the oldest is deleted even if it is only 2 days old. - The current audit log file (
audit.log) is never deleted by either setting — it is only rotated when it reachesmaxsize.
Example:
maxsize=100, maxbackup=3, maxage=7
audit.log ← current file, never deleted
audit.log.1 ← 1 day old, kept (within maxbackup and maxage)
audit.log.2 ← 3 days old, kept
audit.log.3 ← 5 days old, kept
audit.log.4 ← 8 days old, DELETED (exceeds maxage=7)
Question 4
Describe a centralized audit log collection architecture using Fluent Bit and Elasticsearch. Why is this preferred over local log files?
Show Answer
Architecture:
- API server writes audit events to
/var/log/kubernetes/audit/audit.logon each control plane node. - Fluent Bit (DaemonSet or host-level agent) tails the audit log file, parses JSON, adds cluster metadata, and forwards to Elasticsearch.
- Elasticsearch stores events in time-based indices (
kubernetes-audit-YYYY.MM.DD) with Index Lifecycle Management (ILM) for retention. - Kibana provides dashboards for visualization and alerting on suspicious patterns.
Why centralized is preferred:
| Aspect | Local Files | Centralized |
|---|---|---|
| Search | jq on a single node |
Full-text search across all clusters |
| Retention | Limited by disk space | Scalable storage with tiered retention |
| Alerting | Manual (cron scripts) | Real-time alerts (Kibana, Prometheus) |
| Multi-cluster | No aggregation | Single pane of glass |
| HA | Lost if node fails | Replicated and persistent |
| Compliance | Hard to prove immutability | Immutable indices, audit trail of access |
| Access control | Root on node | RBAC-controlled access to log system |
Question 5
What are the minimum resources you must audit for security, regardless of compliance requirements?
Show Answer
At minimum, always audit:
- Secrets (all verbs) at
Requestlevel — credential access is the #1 attack target - RBAC resources (create/update/patch/delete) at
RequestResponselevel — privilege escalation detection - pods/exec and pods/attach at
RequestResponselevel — lateral movement detection - Authentication resources (TokenReview, CSR) at
RequestResponselevel — credential generation - Nodes (write verbs) at
Requestlevel — node compromise detection - Namespaces (write verbs) at
Requestlevel — isolation boundary changes - ServiceAccount tokens at
Requestlevel — new API credentials
Everything else should be logged at Metadata level as a catch-all, with noise sources (health endpoints, events, system component reads) excluded at None level.
This covers the MITRE ATT&CK phases most relevant to Kubernetes: credential access, privilege escalation, lateral movement, and persistence.
| Previous | Up | Next |
|---|---|---|
| Monitoring API Access | CKS Home | Audit Logging Lab |