Kubernetes audit logs: improve field mapping - #3414
Conversation
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
There was a problem hiding this comment.
Hey, great work. A few things I noticed:
Missing field:
requestObject.spec.containers.securityContext.runasuser
type: integer
These fields should be nested under spec., not spec.container. :
- requestObject.spec.HostNetwork
- requestObject.spec.HostIPC
- requestObject.spec.HostPID
These may need to be flattened so that I can search on the whole object (hope I'm understanding flattened correctly):
- requestObject.spec.volumes.hostPath.path
- responseObject.spec.volumes.hostPath.path
These may need to be nested objects instead of arrays so that I can search for specific 'resources' that are attached to 'verbs'; I'm not sure how best to do this though. Can we flatten the fields first and then link them together? I'm not sure if that's an option but maybe we can use the rules.apiGroup field as the link between each rules.resources and rules.verbs pairing? I would want to be able to search for something like requestObject.rules.resources: pods/exec and requestObject.rules.verbs: create but would only want a hit back if those two groups were linked to the same rules.apiGroup. Because it's possible to have the rules.verb: create but it's attached to a different group of resources like "cronjobs" under a different apiGroups.
requestObject.rules.resources
requestObject.rules.verbs
responseObject.rules.resources
responseObject.rules.verbs
"kubernetes":{
"audit":{
"annotations":{
"authorization_k8s_io/decision":"allow",
"authorization_k8s_io/reason":"RBAC: allowed by ClusterRoleBinding \"system:controller:clusterrole-aggregation-controller\" of ClusterRole \"system:controller:clusterrole-aggregation-controller\" to ServiceAccount \"clusterrole-aggregation-controller/kube-system\""
},
"apiVersion":"audit.k8s.io/v1",
"auditID":"492ef3d2-698a-4529-ada4-6ac0e3a9246e",
"kind":"Event",
"level":"RequestResponse",
"objectRef":{
"apiGroup":"rbac.authorization.k8s.io",
"apiVersion":"v1",
"name":"admin",
"resource":"clusterroles"
},
"requestObject":{
"apiVersion":"rbac.authorization.k8s.io/v1",
"kind":"ClusterRole",
"metadata":{
"name":"admin"
},
"rules":[
{
"apiGroups":[
""
],
"resources":[
"pods/attach",
"pods/exec",
"pods/portforward",
"pods/proxy",
"secrets",
"services/proxy"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"serviceaccounts"
],
"verbs":[
"impersonate"
]
},
{
"apiGroups":[
""
],
"resources":[
"pods",
"pods/attach",
"pods/exec",
"pods/portforward",
"pods/proxy"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
""
],
"resources":[
"configmaps",
"events",
"persistentvolumeclaims",
"replicationcontrollers",
"replicationcontrollers/scale",
"secrets",
"serviceaccounts",
"services",
"services/proxy"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"apps"
],
"resources":[
"daemonsets",
"deployments",
"deployments/rollback",
"deployments/scale",
"replicasets",
"replicasets/scale",
"statefulsets",
"statefulsets/scale"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"autoscaling"
],
"resources":[
"horizontalpodautoscalers"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"batch"
],
"resources":[
"cronjobs",
"jobs"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"extensions"
],
"resources":[
"daemonsets",
"deployments",
"deployments/rollback",
"deployments/scale",
"ingresses",
"networkpolicies",
"replicasets",
"replicasets/scale",
"replicationcontrollers/scale"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"policy"
],
"resources":[
"poddisruptionbudgets"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"networking.k8s.io"
],
"resources":[
"ingresses",
"networkpolicies"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"metrics.k8s.io"
],
"resources":[
"pods",
"nodes"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"configmaps",
"endpoints",
"persistentvolumeclaims",
"persistentvolumeclaims/status",
"pods",
"replicationcontrollers",
"replicationcontrollers/scale",
"serviceaccounts",
"services",
"services/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"bindings",
"events",
"limitranges",
"namespaces/status",
"pods/log",
"pods/status",
"replicationcontrollers/status",
"resourcequotas",
"resourcequotas/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"namespaces"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"discovery.k8s.io"
],
"resources":[
"endpointslices"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"apps"
],
"resources":[
"controllerrevisions",
"daemonsets",
"daemonsets/status",
"deployments",
"deployments/scale",
"deployments/status",
"replicasets",
"replicasets/scale",
"replicasets/status",
"statefulsets",
"statefulsets/scale",
"statefulsets/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"autoscaling"
],
"resources":[
"horizontalpodautoscalers",
"horizontalpodautoscalers/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"batch"
],
"resources":[
"cronjobs",
"cronjobs/status",
"jobs",
"jobs/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"extensions"
],
"resources":[
"daemonsets",
"daemonsets/status",
"deployments",
"deployments/scale",
"deployments/status",
"ingresses",
"ingresses/status",
"networkpolicies",
"replicasets",
"replicasets/scale",
"replicasets/status",
"replicationcontrollers/scale"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"policy"
],
"resources":[
"poddisruptionbudgets",
"poddisruptionbudgets/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"networking.k8s.io"
],
"resources":[
"ingresses",
"ingresses/status",
"networkpolicies"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"authorization.k8s.io"
],
"resources":[
"localsubjectaccessreviews"
],
"verbs":[
"create"
]
},
{
"apiGroups":[
"rbac.authorization.k8s.io"
],
"resources":[
"rolebindings",
"roles"
],
"verbs":[
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
]
},
"requestReceivedTimestamp":"2022-05-20T20:53:43.652860Z",
"requestURI":"/apis/rbac.authorization.k8s.io/v1/clusterroles/admin?fieldManager=clusterrole-aggregation-controller\u0026force=true",
"responseObject":{
"aggregationRule":{
"clusterRoleSelectors":[
{
"matchLabels":{
"rbac.authorization.k8s.io/aggregate-to-admin":"true"
}
}
]
},
"apiVersion":"rbac.authorization.k8s.io/v1",
"kind":"ClusterRole",
"metadata":{
"annotations":{
"rbac.authorization.kubernetes.io/autoupdate":"true"
},
"creationTimestamp":"2022-05-20T20:53:29Z",
"labels":{
"kubernetes.io/bootstrapping":"rbac-defaults"
},
"managedFields":[
{
"apiVersion":"rbac.authorization.k8s.io/v1",
"fieldsType":"FieldsV1",
"fieldsV1":{
"f:rules":{
}
},
"manager":"clusterrole-aggregation-controller",
"operation":"Apply",
"time":"2022-05-20T20:53:43Z"
},
{
"apiVersion":"rbac.authorization.k8s.io/v1",
"fieldsType":"FieldsV1",
"fieldsV1":{
"f:aggregationRule":{
".":{
},
"f:clusterRoleSelectors":{
}
},
"f:metadata":{
"f:annotations":{
".":{
},
"f:rbac.authorization.kubernetes.io/autoupdate":{
}
},
"f:labels":{
".":{
},
"f:kubernetes.io/bootstrapping":{
}
}
}
},
"manager":"k3s",
"operation":"Update",
"time":"2022-05-20T20:53:29Z"
}
],
"name":"admin",
"resourceVersion":"509",
"uid":"ee78f4af-1057-454f-b0a3-d9d2e963162e"
},
"rules":[
{
"apiGroups":[
""
],
"resources":[
"pods/attach",
"pods/exec",
"pods/portforward",
"pods/proxy",
"secrets",
"services/proxy"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"serviceaccounts"
],
"verbs":[
"impersonate"
]
},
{
"apiGroups":[
""
],
"resources":[
"pods",
"pods/attach",
"pods/exec",
"pods/portforward",
"pods/proxy"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
""
],
"resources":[
"configmaps",
"events",
"persistentvolumeclaims",
"replicationcontrollers",
"replicationcontrollers/scale",
"secrets",
"serviceaccounts",
"services",
"services/proxy"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"apps"
],
"resources":[
"daemonsets",
"deployments",
"deployments/rollback",
"deployments/scale",
"replicasets",
"replicasets/scale",
"statefulsets",
"statefulsets/scale"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"autoscaling"
],
"resources":[
"horizontalpodautoscalers"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"batch"
],
"resources":[
"cronjobs",
"jobs"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"extensions"
],
"resources":[
"daemonsets",
"deployments",
"deployments/rollback",
"deployments/scale",
"ingresses",
"networkpolicies",
"replicasets",
"replicasets/scale",
"replicationcontrollers/scale"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"policy"
],
"resources":[
"poddisruptionbudgets"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"networking.k8s.io"
],
"resources":[
"ingresses",
"networkpolicies"
],
"verbs":[
"create",
"delete",
"deletecollection",
"patch",
"update"
]
},
{
"apiGroups":[
"metrics.k8s.io"
],
"resources":[
"pods",
"nodes"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"configmaps",
"endpoints",
"persistentvolumeclaims",
"persistentvolumeclaims/status",
"pods",
"replicationcontrollers",
"replicationcontrollers/scale",
"serviceaccounts",
"services",
"services/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"bindings",
"events",
"limitranges",
"namespaces/status",
"pods/log",
"pods/status",
"replicationcontrollers/status",
"resourcequotas",
"resourcequotas/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
""
],
"resources":[
"namespaces"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"discovery.k8s.io"
],
"resources":[
"endpointslices"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"apps"
],
"resources":[
"controllerrevisions",
"daemonsets",
"daemonsets/status",
"deployments",
"deployments/scale",
"deployments/status",
"replicasets",
"replicasets/scale",
"replicasets/status",
"statefulsets",
"statefulsets/scale",
"statefulsets/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"autoscaling"
],
"resources":[
"horizontalpodautoscalers",
"horizontalpodautoscalers/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"batch"
],
"resources":[
"cronjobs",
"cronjobs/status",
"jobs",
"jobs/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"extensions"
],
"resources":[
"daemonsets",
"daemonsets/status",
"deployments",
"deployments/scale",
"deployments/status",
"ingresses",
"ingresses/status",
"networkpolicies",
"replicasets",
"replicasets/scale",
"replicasets/status",
"replicationcontrollers/scale"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"policy"
],
"resources":[
"poddisruptionbudgets",
"poddisruptionbudgets/status"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"networking.k8s.io"
],
"resources":[
"ingresses",
"ingresses/status",
"networkpolicies"
],
"verbs":[
"get",
"list",
"watch"
]
},
{
"apiGroups":[
"authorization.k8s.io"
],
"resources":[
"localsubjectaccessreviews"
],
"verbs":[
"create"
]
},
{
"apiGroups":[
"rbac.authorization.k8s.io"
],
"resources":[
"rolebindings",
"roles"
],
"verbs":[
"create",
"delete",
"deletecollection",
"get",
"list",
"patch",
"update",
"watch"
]
}
]
},
"responseStatus":{
"code":200,
"metadata":{
}
},
"sourceIPs":[
"127.0.0.1"
],
"stage":"ResponseComplete",
"stageTimestamp":"2022-05-20T20:53:43.655728Z",
"user":{
"groups":[
"system:serviceaccounts",
"system:serviceaccounts:kube-system",
"system:authenticated"
],
"uid":"08f61082-e1cd-412d-8f2c-87fbd3d1f400",
"username":"system:serviceaccount:kube-system:clusterrole-aggregation-controller"
},
"userAgent":"k3s/v1.22.7+k3s1 (linux/amd64) kubernetes/8432d7f/system:serviceaccount:kube-system:clusterrole-aggregation-controller",
"verb":"patch"
}
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
🌐 Coverage report
|
| type: flattened | ||
| - name: rules | ||
| dynamic: true | ||
| type: nested |
There was a problem hiding this comment.
@imays11 fyi, with query as on the screenshot, it seems to give the desired result:

There was a problem hiding this comment.
Nice! Question, what would happen if another resource group had the verb "list" but secrets did not. Would running that query still capture those results?
There was a problem hiding this comment.
Would running that query still capture those results?
No.
I have 2 documents, first with
"resources": [
"services",
"endpoints",
"secrets"
],
"verbs": [
"get",
"list",
"watch"
]
second with (no list):
"resources": [
"services",
"endpoints",
"secrets"
],
"verbs": [
"get",
"watch"
]
query kubernetes.audit.requestObject.rules:{ resources : "secrets" and verbs : "watch"} capture 2 documents:

query kubernetes.audit.requestObject.rules:{ resources : "secrets" and verbs : "list"} gives only 1:

Is it expected outcome for you?
There was a problem hiding this comment.
Yes that is perfect! Thank you @tetianakravchenko
| - name: volumeMounts | ||
| type: flattened | ||
| - name: volumes.hostPath | ||
| type: flattened |
There was a problem hiding this comment.
@imays11 fyi:


the same situation is with volumeMounts
| type: integer | ||
| - name: volumeMounts | ||
| type: flattened | ||
| - name: volumes.hostPath |
There was a problem hiding this comment.
This is one level up in the original:
Is that correct?
There was a problem hiding this comment.
it should be on the same level, I've changed the fields ordering to make it clear - f4b6798
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
…t.metadata -> kubernetes.audit.requestObject.metadata Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>
* add fields Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * drop metadata field; disable dynamic mapping Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * rerun elastic-package build Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * add pr number Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * change order of fields Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * adjust changelog description; fix typo kubernetes.audit.responseObject.metadata -> kubernetes.audit.requestObject.metadata Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co> * run elastic-package format Signed-off-by: Tetiana Kravchenko <tetiana.kravchenko@elastic.co>

Signed-off-by: Tetiana Kravchenko tetiana.kravchenko@elastic.co
What does this PR do?
Audit logs are missing important fields, needed for security team to create detection rules.
Checklist
changelog.ymlfile.Author's Checklist
How to test this PR locally
audit-policy.yamlwith:kind config:
create cluster:
/var/log/kubernetes/kube-apiserver-audit-1.logRelated issues
Screenshots