Where Kubernetes audit logs live depends on who runs your control plane. On a self-managed cluster, they’re a JSON-lines file on the control plane node, at whatever path –audit-log-path points to. On EKS, they go to CloudWatch Logs, on AKS to a Log Analytics workspace or storage account, and on GKE to Cloud Audit Logs. One thing holds everywhere: audit logging has to be switched on. On self-managed clusters and EKS it’s off until someone enables it.
The audit log records every request to the Kubernetes API: who ran kubectl exec into which pod, who read which secret, who bound themselves to cluster-admin. Prime Core has 93 detection rules for Kubernetes audit data, mapped to 220 MITRE ATT&CK techniques. Among them: exec into running containers (T1609), deploying rogue containers (T1610) and reading credentials through the container API (T1552.007).
Before you start
Is it enabled?
| Platform | Where to check |
|---|---|
| Self-managed (kubeadm and similar) | –audit-policy-file and –audit-log-path on the API server; with kubeadm, in /etc/kubernetes/manifests/kube-apiserver.yaml |
| EKS | Cluster → Observability → control plane logging, “audit” log type |
| AKS | Diagnostic settings on the cluster, categories kube-audit or kube-audit-admin |
| GKE | On by default for Admin Activity; Data Access logging for Kubernetes is opt-in |
If it wasn’t on, there’s nothing to export for the past, and the rest of this guide is about next time.
Detail depends on the policy. At Metadata level you see who did what to which object. At Request or RequestResponse level you also see the bodies, for example the pod spec that asked for privileged: true. Know which one you have before you go looking.
Expect volume. Controllers and service accounts talk to the API constantly. On AKS, kube-audit-admin leaves out read-only get and list calls, which keeps size sane but also hides secret reads.
Export it
Self-managed cluster, on a control plane node:
# confirm the path first
sudo grep -- '--audit-log-path' /etc/kubernetes/manifests/kube-apiserver.yaml
sudo cat /var/log/kubernetes/audit/audit.log > k8s_audit.jsonl
Rotated files (from –audit-log-maxbackup) sit next to it with a timestamp in the name. Include them if the window reaches back.
EKS, from CloudWatch Logs:
aws logs filter-log-events --log-group-name /aws/eks/my-cluster/cluster \
--log-stream-name-prefix kube-apiserver-audit \
--start-time $(date -d '2026-10-19T00:00:00Z' +%s000) --end-time $(date -d '2026-10-20T00:00:00Z' +%s000) \
--output json | jq -c '.events[].message | fromjson' > eks_audit_1019.jsonl
Each CloudWatch event wraps the audit entry as a string; fromjson unwraps it. The date -d syntax is GNU; on macOS, compute the millisecond timestamps another way.
AKS, from Log Analytics (resource-specific tables):
az monitor log-analytics query -w <workspace-id> \
--analytics-query "AKSAuditAdmin | where TimeGenerated between (datetime(2026-10-19) .. datetime(2026-10-20))" \
--output json > aks_audit_1019.json
GKE, from Cloud Audit Logs:
gcloud logging read 'resource.type="k8s_cluster" AND logName:"cloudaudit.googleapis.com" AND timestamp>="2026-10-19T00:00:00Z" AND timestamp<"2026-10-20T00:00:00Z"' \
--project=my-project --format=json | jq -c '.[]' > gke_audit_1019.jsonl
What to look for
| Request | Why it matters |
|---|---|
| create on pods/exec (objectRef.subresource: exec) | Someone opened a shell inside a running container |
| create of a ClusterRoleBinding to cluster-admin | Privilege escalation to full control |
| get or list on secrets by an unusual identity | Credential harvesting |
| Pods created with privileged, hostPath or hostNetwork | A step toward escaping onto the node |
| user.username: system:anonymous with a 2xx response | Anonymous access that shouldn’t work |
| Bursts of 403 from one service account | A stolen token feeling out its permissions |
Upload, sanitize and review in LogTotal
- Upload. Open logtotal.com and drop in the .jsonl file.
- Sanitize. [what gets masked: usernames, service account names, source IPs, namespaces, object names]. Look it over before you continue.
- Review.
When it goes wrong
| Symptom | Likely cause | Fix |
|---|---|---|
| No audit file on the control plane | API server has no audit policy configured | Expected; configure it for the future |
| EKS log group or audit streams missing | The “audit” control plane log type isn’t enabled | Enable it for the future |
| Each line is one long escaped string | CloudWatch wrapping | Unwrap with fromjson, as above |
| You can see who changed a pod, but not how | Policy is at Metadata level | Raise the level for sensitive resources |
| No secret reads in AKS data | kube-audit-admin excludes get and list | Collect kube-audit if you need them |
FAQ
Aren’t container logs enough?
No. Container logs show what an application printed. The audit log shows what people and service accounts did to the cluster. Different questions, different logs.
Can I read the audit file on a managed cluster?
No. On EKS, AKS and GKE, you don’t get access to the control plane nodes. The provider’s logging service is the only route.