Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Filebeat] Add module for Kibana audit logs #22696

Merged
merged 14 commits into from
Dec 15, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ https:/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

*Filebeat*

- Add fileset to ingest Kibana's ECS audit logs. {pull}22696[22696]


*Auditbeat*

Expand Down
99 changes: 99 additions & 0 deletions filebeat/docs/fields.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -89769,6 +89769,105 @@ kibana Module



*`kibana.space_id`*::
+
--
The id of the space associated with this request.

type: keyword

example: default

--

*`kibana.saved_object.type`*::
+
--
The type of the saved object associated with this request.

type: keyword

example: dashboard

--

*`kibana.saved_object.id`*::
+
--
The id of the saved object associated with this request.

type: keyword

example: 6295bdd0-0a0e-11e7-825f-6748cda7d858

--

*`kibana.add_to_spaces`*::
+
--
The set of space ids that a saved object was shared to.

type: keyword

example: ['default', 'marketing']

--

*`kibana.delete_from_spaces`*::
+
--
The set of space ids that a saved object was removed from.

type: keyword

example: ['default', 'marketing']

--

*`kibana.authentication_provider`*::
+
--
The authentication provider associated with a login event.

type: keyword

example: basic1

--

*`kibana.authentication_type`*::
+
--
The authentication provider type associated with a login event.

type: keyword

example: basic

--

*`kibana.authentication_realm`*::
+
--
The Elasticsearch authentication realm name which fulfilled a login event.

type: keyword

example: native

--

*`kibana.lookup_realm`*::
+
--
The Elasticsearch lookup realm which fulfilled a login event.

type: keyword

example: native

--

[float]
=== log

Expand Down
5 changes: 5 additions & 0 deletions filebeat/docs/modules/kibana.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ include::../include/var-paths.asciidoc[]
:fileset_ex!:

:modulename!:
[float]
==== `audit` fileset settings

include::../include/var-paths.asciidoc[]



[float]
Expand Down
10 changes: 9 additions & 1 deletion filebeat/filebeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,22 @@ filebeat.modules:

#-------------------------------- Kibana Module --------------------------------
- module: kibana
# All logs
# Server logs
log:
enabled: true

# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
#var.paths:

# Audit logs
audit:
enabled: true

# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
#var.paths:

#------------------------------- Logstash Module -------------------------------
#- module: logstash
# logs
Expand Down
10 changes: 9 additions & 1 deletion filebeat/module/kibana/_meta/config.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
- module: kibana
# All logs
# Server logs
log:
enabled: true

# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
#var.paths:

# Audit logs
audit:
enabled: true

# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
#var.paths:
5 changes: 5 additions & 0 deletions filebeat/module/kibana/_meta/docs.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ include::../include/var-paths.asciidoc[]
:fileset_ex!:

:modulename!:
[float]
==== `audit` fileset settings

include::../include/var-paths.asciidoc[]

38 changes: 37 additions & 1 deletion filebeat/module/kibana/_meta/fields.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,41 @@
fields:
- name: kibana
type: group
description: >
description: ''
legrego marked this conversation as resolved.
Show resolved Hide resolved
fields:
- name: space_id
description: "The id of the space associated with this request."
thomheymann marked this conversation as resolved.
Show resolved Hide resolved
example: "default"
type: keyword
- name: saved_object.type
description: "The type of the saved object associated with this request."
example: "dashboard"
type: keyword
- name: saved_object.id
description: "The id of the saved object associated with this request."
example: "6295bdd0-0a0e-11e7-825f-6748cda7d858"
type: keyword
- name: add_to_spaces
description: "The set of space ids that a saved object was shared to."
example: "['default', 'marketing']"
type: keyword
- name: delete_from_spaces
description: "The set of space ids that a saved object was removed from."
example: "['default', 'marketing']"
type: keyword
- name: authentication_provider
description: "The authentication provider associated with a login event."
example: "basic1"
type: keyword
- name: authentication_type
description: "The authentication provider type associated with a login event."
example: "basic"
type: keyword
- name: authentication_realm
description: "The Elasticsearch authentication realm name which fulfilled a login event."
example: "native"
type: keyword
- name: lookup_realm
description: "The Elasticsearch lookup realm which fulfilled a login event."
example: "native"
type: keyword
13 changes: 13 additions & 0 deletions filebeat/module/kibana/audit/config/audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: log
paths:
{{ range $i, $path := .paths }}
- {{$path}}
{{ end }}
exclude_files: [".gz$"]

processors:
- add_locale: ~
- add_fields:
target: ''
fields:
ecs.version: 1.6.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something we should log from Kibana instead?
If we want to update to a later version of ECS that should be controlled by Kibana, not the Filebeat module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question; I think this would make sense for Kibana to log instead of Filebeat. I copied this over from the ES config, but it probably doesn't make sense for us to follow suit here.

Copy link
Contributor

@thomheymann thomheymann Dec 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I'll create a PR to log that in Kibana: elastic/kibana#85390

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I came across this when testing. It appears that Filebeat is expecting that it'll set an explicit version itself:

self.assert_explicit_ecs_version_set(module, fileset)

I worked around this, but I don't know if that's "ok" or not 😕

88 changes: 88 additions & 0 deletions filebeat/module/kibana/audit/ingest/pipeline-json.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
description: Pipeline for parsing Kibana audit logs in JSON format
processors:
- json:
legrego marked this conversation as resolved.
Show resolved Hide resolved
field: message
target_field: kibana._audit_temp

- set:
field: "@timestamp"
value: "{{kibana._audit_temp.@timestamp}}"

- set:
field: message
value: "{{kibana._audit_temp.message}}"

- set:
if: ctx.kibana._audit_temp.event.action != null
field: event.action
value: "{{kibana._audit_temp.event.action}}"
- set:
if: ctx.kibana._audit_temp.event.category != null
field: event.category
value: "{{kibana._audit_temp.event.category}}"
- set:
if: ctx.kibana._audit_temp.event.outcome != null
field: event.outcome
value: "{{kibana._audit_temp.event.outcome}}"

- rename:
if: ctx.kibana._audit_temp.url != null
field: kibana._audit_temp.url
target_field: "url"

- set:
if: ctx.url?.query == null
field: url.original
value: '{{url.path}}'
ignore_empty_value: true
- set:
if: ctx.url?.path != null && ctx.url?.query != null
field: url.original
value: '{{url.path}}?{{url.query}}'

- rename:
if: ctx.kibana._audit_temp.http != null
field: kibana._audit_temp.http
target_field: http

- rename:
if: ctx.kibana._audit_temp.user != null
field: kibana._audit_temp.user
target_field: user

- rename:
if: ctx.kibana._audit_temp.trace != null
field: kibana._audit_temp.trace
target_field: trace

- rename:
if: ctx.kibana._audit_temp.process?.pid != null
target_field: process
field: kibana._audit_temp.process

- rename:
if: ctx.kibana._audit_temp.kibana.space_id != null
target_field: kibana.space_id
field: kibana._audit_temp.kibana.space_id
thomheymann marked this conversation as resolved.
Show resolved Hide resolved

- rename:
if: ctx.kibana._audit_temp.kibana.saved_object != null
target_field: kibana.saved_object
field: kibana._audit_temp.kibana.saved_object

- rename:
if: ctx.kibana._audit_temp.kibana.add_to_spaces != null
target_field: kibana.add_to_spaces
field: kibana._audit_temp.kibana.add_to_spaces

- rename:
if: ctx.kibana._audit_temp.kibana.delete_from_spaces != null
target_field: kibana.delete_from_spaces
field: kibana._audit_temp.kibana.delete_from_spaces
Copy link
Contributor

@thomheymann thomheymann Dec 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about picking fields within kibana manually since it's supposed to be possible for plugin developer to add whatever fields they need and I would expect that to be ingested using the Filebeat module.

https:/elastic/kibana/blob/master/x-pack/plugins/security/server/audit/audit_events.ts#L50-L53

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit worried about picking fields within kibana manually since it's supposed to be free for login developer to add whatever fields they need and I would expect that to be ingested using the Filebeat module.

I'm torn on this too. On one hand, it would be great if this module "just worked" whenever a new field was added. On the other hand, unknown fields will be dynamically mapped, which might not be what we want. @P1llus are there recommendations on how filesets should handle dynamic fields? Should we accept them so they are mapped dynamically, or should we attempt to keep the mapping in sync with changes in Kibana going forward?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The general way we approach it, is ensuring that all fields coming from filebeat modules would be appended under modulename.filesetname.*.
I usually copy the whole event under the correct field root first (in this case kibana.audit), then rename the ones that are moved to ECS while keeping the rest under kibana.audit

In terms of dynamic mapping, I would recommend to map as many fields as possible, however if you know that there is certain fields with complex or everchanging field names, you can apply the "flattened" type to the top field for that usecase, though please don't set the root field kibana.audit as flattened, you could also have a specific field if you want, in which you put all custom data.

Hope that makes sense @legrego @thomheymann

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not a fan of changing the JSON structure from what's logged since it is already in the format that we want and it would be super confusing for users otherwise.

I'm starting to think that it then might be better to restrict the ECS schema in Kibana so that only known fields are allowed to be logged which we can map manually in the Filebeat pipeline.

Is there a way of defining this pipeline using an npm module in the Kibana repository or can we publish a Go module from within Kibana? I'm a bit worried about these things being dislocated and going out of sync. (Developers adding fields without updating Filebeat pipeline)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm starting to think that it then might be better to restrict the ECS schema in Kibana so that only known fields are allowed to be logged which we can map manually in the Filebeat pipeline.

++ I think this is the safest approach for now too, until we get to the point where we actually require dynamic fields that can't be known ahead of time.

Is there a way of defining this pipeline using an npm module in the Kibana repository or can we publish a Go module from within Kibana? I'm a bit worried about these things being dislocated and going out of sync. (Developers adding fields without updating Filebeat pipeline)

I wonder if there's something we could do within Kibana to enforce this. If we had the audit logger run alongside the functional test suites, we could inspect it to ensure that it's not capturing anything unexpected.

If it wouldn't be so expensive to do, we could do a runtime enforcement, but I don't think that's worth the overhead at this point.

Copy link
Contributor

@thomheymann thomheymann Dec 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've typed the kibana namespace in elastic/kibana#85451


thomheymann marked this conversation as resolved.
Show resolved Hide resolved
- remove:
field: 'kibana._audit_temp'
on_failure:
- set:
field: error.message
value: '{{ _ingest.on_failure_message }}'
21 changes: 21 additions & 0 deletions filebeat/module/kibana/audit/ingest/pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
description: Pipeline for parsing Kibana audit logs
processors:
- set:
field: event.ingested
value: '{{_ingest.timestamp}}'
- rename:
field: '@timestamp'
target_field: event.created
- pipeline:
name: '{< IngestPipeline "pipeline-json" >}'
- set:
legrego marked this conversation as resolved.
Show resolved Hide resolved
field: event.kind
value: event
- append:
field: related.user
value: "{{user.name}}"
if: "ctx?.user?.name != null"
on_failure:
- set:
field: error.message
value: '{{ _ingest.on_failure_message }}'
12 changes: 12 additions & 0 deletions filebeat/module/kibana/audit/manifest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module_version: 1.0

var:
- name: paths
default:
- /var/log/kibana/*_audit.json

ingest_pipeline:
- ingest/pipeline.yml
- ingest/pipeline-json.yml

input: config/audit.yml
7 changes: 7 additions & 0 deletions filebeat/module/kibana/audit/test/test-audit-711.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{"@timestamp":"2020-11-20T12:05:14.528-05:00","message":"User is updating config [id=8.0.0]","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"saved_object_update","category":"database","type":"change","outcome":"unknown"},"kibana":{"space_id":"marketing","saved_object":{"type":"config","id":"8.0.0"}},"user":{"name":"elastic","roles":["superuser"]},"trace":{"id":"e0bd67a1-a1b0-424d-9652-a350f88188eb"}}
{"@timestamp":"2020-11-20T12:05:14.849-05:00","message":"User is requesting [/foo/s/marketing/api/saved_objects/_find] endpoint","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"http_request","category":"web","outcome":"unknown"},"http":{"request":{"method":"get"}},"url":{"domain":"0.0.0.0","path":"/foo/s/marketing/api/saved_objects/_find","port":5603,"query":"default_search_operator=AND&has_reference=%5B%5D&page=1&per_page=1000&search_fields=title%5E3&search_fields=description&type=dashboard","scheme":"https:"},"user":{"name":"elastic","roles":["superuser"]},"kibana":{"space_id":"marketing"},"trace":{"id":"ae67a156-3847-4d89-9c97-86769df5bc2e"}}
{"@timestamp":"2020-11-20T12:05:15.841-05:00","message":"User is requesting [/foo/s/marketing/api/saved_objects/_bulk_get] endpoint","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"http_request","category":"web","outcome":"unknown"},"http":{"request":{"method":"post"}},"url":{"domain":"0.0.0.0","path":"/foo/s/marketing/api/saved_objects/_bulk_get","port":5603,"scheme":"https:"},"user":{"name":"elastic","roles":["superuser"]},"kibana":{"space_id":"marketing"},"trace":{"id":"cef382d1-7442-4f9a-8bee-0512c2b1da5a"}}
{"@timestamp":"2020-11-20T12:05:15.853-05:00","message":"User has accessed index-pattern [id=metrics-*]","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"saved_object_get","category":"database","type":"access","outcome":"success"},"kibana":{"space_id":"marketing","saved_object":{"type":"index-pattern","id":"metrics-*"}},"user":{"name":"elastic","roles":["superuser"]},"trace":{"id":"cef382d1-7442-4f9a-8bee-0512c2b1da5a"}}
{"@timestamp":"2020-11-20T12:05:24.103-05:00","message":"User is requesting [/foo/s/marketing/api/saved_objects/_find] endpoint","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"http_request","category":"web","outcome":"unknown"},"http":{"request":{"method":"get"}},"url":{"domain":"0.0.0.0","path":"/foo/s/marketing/api/saved_objects/_find","port":5603,"query":"fields=title&per_page=10&search=%22My%20Dashboard%22&search_fields=title&type=dashboard","scheme":"https:"},"user":{"name":"elastic","roles":["superuser"]},"kibana":{"space_id":"marketing"},"trace":{"id":"b10ee3ab-8102-4122-b4b5-5727e9b3d6a3"}}
{"@timestamp":"2020-11-20T12:05:24.143-05:00","message":"User is requesting [/foo/s/marketing/api/saved_objects/dashboard] endpoint","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"http_request","category":"web","outcome":"unknown"},"http":{"request":{"method":"post"}},"url":{"domain":"0.0.0.0","path":"/foo/s/marketing/api/saved_objects/dashboard","port":5603,"query":"overwrite=true","scheme":"https:"},"user":{"name":"elastic","roles":["superuser"]},"kibana":{"space_id":"marketing"},"trace":{"id":"4995c6bd-903c-42c2-af28-5cf17cc1cb6b"}}
{"@timestamp":"2020-11-20T12:05:24.150-05:00","message":"User is creating dashboard [id=undefined]","log":{"level":"INFO","logger":"plugins.security.audit.ecs"},"process":{"pid":86516},"event":{"action":"saved_object_create","category":"database","type":"creation","outcome":"unknown"},"kibana":{"space_id":"marketing","saved_object":{"type":"dashboard"}},"user":{"name":"elastic","roles":["superuser"]},"trace":{"id":"4995c6bd-903c-42c2-af28-5cf17cc1cb6b"}}
Loading