Kubernetes Audit Log Fields: Reference With Examples
Published
One JSON event per request handled by the Kubernetes API server: who called it, from which address, which verb against which object, what the authorizer decided and how the server responded.
Sample record
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"a1b2c3d4-0001","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/production/secrets","verb":"list","user":{"username":"attacker-sa","uid":"abc123","groups":["system:serviceaccounts","system:authenticated"]},"sourceIPs":["10.0.4.55"],"userAgent":"kubectl/v1.28.0 (linux/amd64) kubernetes/abc1234","objectRef":{"resource":"secrets","namespace":"production","apiVersion":"v1"},"responseStatus":{"code":200},"requestReceivedTimestamp":"2024-03-15T03:10:00.000000Z","stageTimestamp":"2024-03-15T03:10:00.050000Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":"RBAC: allowed by ClusterRoleBinding"}},
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"RequestResponse","auditID":"a1b2c3d4-0002","stage":"ResponseComplete","requestURI":"/api/v1/namespaces/production/secrets/db-credentials","verb":"get","user":{"username":"attacker-sa","uid":"abc123","groups":["system:serviceaccounts","system:authenticated"]},"sourceIPs":["10.0.4.55"],"userAgent":"kubectl/v1.28.0 (linux/amd64) kubernetes/abc1234","objectRef":{"resource":"secrets","namespace":"production","name":"db-credentials","apiVersion":"v1"},"responseStatus":{"code":200},"requestReceivedTimestamp":"2024-03-15T03:10:05.000000Z","stageTimestamp":"2024-03-15T03:10:05.030000Z","annotations":{"authorization.k8s.io/decision":"allow"}}, From the LogTriage parser test corpus — synthetic data, no real hosts or users.
Field reference
| Field | Type | Meaning | Why it matters for security |
|---|---|---|---|
| apiVersione.g. audit.k8s.io/v1 | string | Schema version of the audit event itself, not of the object being acted on. | Confirms which field set a parser should expect; older clusters may emit earlier audit API versions. |
| levele.g. RequestResponse | None | Metadata | Request | RequestResponse | How much of the request the audit policy recorded for this rule. | Metadata-level events contain no request or response body, so you can see that a secret was read but not what it contained. |
| auditIDe.g. a1b2c3d4-0001 | string (UID) | Identifier shared by every event emitted for one API request. | Join or deduplicate stages of the same request, and quote it when handing an event to the platform team. |
| stagee.g. ResponseComplete | RequestReceived | ResponseStarted | ResponseComplete | Panic | Point in request handling at which the event was written. | ResponseComplete carries the final status; counting all stages double-counts activity. |
| requestURIe.g. /api/v1/namespaces/production/secrets/db-credentials | string | Full request path including namespace, resource, name and any query string. | Shows subresource paths such as /exec, and is the quickest way to read what was touched. |
| verbe.g. list | string | API verb: get, list, watch, create, update, patch, delete, deletecollection, and connect-style verbs. | A pod exec appears as verb create against the exec subresource, not as a verb named "exec". |
| user.usernamee.g. system:serviceaccount:kube-system:deployment-controller | string | Authenticated identity as presented by the authenticator; service accounts normally appear as system:serviceaccount:namespace:name. | The principal to pivot on. system:anonymous means the request was never authenticated. |
| user.groupse.g. system:serviceaccounts | array of strings | Groups the authenticator attached to the identity, used by RBAC. | Membership of system:masters or a cluster-admin-bound group explains why a dangerous call was allowed. |
| sourceIPse.g. 10.0.4.55 | array of strings | Client address chain seen by the API server. | Pod CIDR addresses mean the caller was inside the cluster; an unexpected external address on an admin call is worth escalating. |
| userAgente.g. kubectl/v1.28.0 (linux/amd64) | string | User-Agent sent by the client, typically kubectl, a controller or a client library. | Controller traffic has stable agents; a kubectl agent on a service-account token suggests a human or a tool using stolen credentials. |
| objectRef.resourcee.g. secrets | string | Resource type the request targeted. | secrets, clusterrolebindings and pods with a subresource are the high-value types to watch. |
| objectRef.namespacee.g. production | string | Namespace of the target object; absent for cluster-scoped resources. | One identity touching many namespaces in minutes is enumeration. |
| objectRef.namee.g. db-credentials | string | Name of the specific object, present when the request targeted one object rather than a collection. | Tells you exactly which secret or binding to rotate or delete. |
| objectRef.subresourcee.g. exec | string | Subresource such as exec, attach, portforward, log or status. | exec, attach and portforward give shell-level or network access into a running pod. |
| responseStatus.codee.g. 200 | integer | HTTP status returned to the client. | 403 bursts are RBAC probing; 101 is the protocol upgrade that a successful exec or attach produces; 201 marks a created object. |
| annotations."authorization.k8s.io/decision"e.g. allow | string | Authorizer decision recorded for the request, with the matching rule often in authorization.k8s.io/reason. | Separates what was attempted from what was permitted — the key filter when triaging a noisy identity. |
What it records
Every request the kube-apiserver handles can produce an audit event: the authenticated identity in user.username and user.groups, the client address in sourceIPs, the verb and requestURI, a structured objectRef describing the target, the authorizer’s verdict in the annotations map, and the outcome in responseStatus.code. Because almost everything in Kubernetes goes through the API server — kubectl, controllers, CI pipelines, operators and the kubelet — this is the single best record of who did what in a cluster.
How much is recorded is decided per request by the audit policy. A rule can be None (drop), Metadata (who, what, when, result), Request (plus the submitted object) or RequestResponse (plus what the server returned). The sample files on this page mix Metadata and RequestResponse events, which is what a real policy usually looks like: full bodies for a few interesting resources, metadata for the rest.
Where to get it
- Self-managed control planes: start the API server with
--audit-policy-fileand either the log backend (--audit-log-path, newline-delimited JSON) or the webhook backend, which posts events to a collector. Without a policy file, no audit events are generated. - Amazon EKS: enable the
auditcontrol plane log type; events land in CloudWatch Logs. - Azure Kubernetes Service: add the
kube-audit(or the lower-volumekube-audit-admin) diagnostic log category and route it to a Log Analytics workspace, storage account or event hub. - Google Kubernetes Engine: API server activity arrives as Cloud Audit Logs entries in Cloud Logging, wrapped in Google’s log structure rather than the raw
audit.k8s.io/v1shape.
Parsing pitfalls
- Array vs. JSON lines. The log backend writes one JSON object per line; exports and API responses (including the samples here) are a JSON array. Handle both or you will read zero events.
- Multiple events per request. Filter to
stageResponseCompletebefore counting actions, and deduplicate onauditID. - Missing fields are normal.
objectRef.nameis absent on collection requests,objectRef.namespaceon cluster-scoped resources such asclusterrolebindings, and request/response bodies onMetadata-level events. Treat absence as “not recorded”, not “did not happen”. sourceIPsis a list. Behind a load balancer or proxy it can hold several hops; the first entry is not always the real client.- The verb is not the action name. Exec, attach and port-forward all appear as
createagainst a subresource. - Status code 101. A successful exec or attach returns a protocol upgrade, so a query that looks only for 2xx misses it.
- Volume. Controller list and watch loops dominate; most clusters filter
system:identities in the policy or at ingest, which also means attacker activity that borrows a controller identity may be filtered away.
Detection ideas
- Secret harvesting: one
user.usernameperforminglistorgetonobjectRef.resourcesecretsacross severalobjectRef.namespacevalues in a short window — the basis of the Kubernetes secret exfiltration rule. - Shell into a pod:
objectRef.subresourceofexec,attachorportforwardwithresponseStatus.code101, especially from a service account that normally only patches deployments. - Privilege escalation:
createorupdateonclusterrolebindingsandrolebindings, then check which subject was bound. - RBAC probing: repeated events with
annotations."authorization.k8s.io/decision"offorbidandresponseStatus.code403 from one identity. - Unauthenticated access:
user.usernameofsystem:anonymousor auser.groupsentry ofsystem:unauthenticatedon anything beyond health endpoints. - Out-of-place clients: a
kubectluserAgenton a service-account identity, or asourceIPsvalue outside your pod and bastion ranges.
You can run these checks on an exported audit file in the free browser log analyzer without sending it anywhere.
Detections and guides for Kubernetes Audit Log
Frequently Asked Questions
- Why does my cluster produce no audit events at all?
- The API server only audits when it is started with an audit policy and a backend. On managed control planes you enable it in the provider console instead — EKS control plane logging, AKS diagnostic settings, GKE Cloud Audit Logs — and those offerings may not let you supply your own policy file, so the level of detail is fixed.
- Can I see the contents of a secret that was read?
- Only if the audit policy recorded that request at RequestResponse level, which writes the response body into the event. Many policies deliberately drop bodies for secrets and configmaps, because audit logs stored at that level become as sensitive as the secrets themselves.
- How do I tell a pod exec from an ordinary pod read?
- Look for objectRef.subresource of exec with verb create, or a requestURI ending in /exec. A successful exec upgrades the connection, so responseStatus.code is 101 rather than 200, and the command itself is not in the event body.
- Why do I see several events with the same auditID?
- One request can be audited at multiple stages — RequestReceived, ResponseStarted, ResponseComplete and Panic. Deduplicate on auditID and keep the ResponseComplete event, which is the one that carries the final responseStatus, before counting operations per user.
Analyze Kubernetes Audit Log logs
LogTriage auto-detects this format, enriches every IP, user agent and hash with threat intelligence, and writes an incident report mapped to MITRE ATT&CK.