From 6fd57d90dad0df06aece223e5e8e672b8d0bc908 Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Sun, 13 Jan 2019 15:26:23 +0200 Subject: [PATCH] Security Audit includes HTTP method for requests (#37322) Adds another field, named "request.method", to the structured logfile audit. This field is present for all events associated with a REST request (not a transport request) and the value is one of GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH, TRACE and CONNECT. --- .../en/security/auditing/event-types.asciidoc | 3 +++ .../security/auditing/output-logfile.asciidoc | 3 +-- .../core/src/main/config/log4j2.properties | 2 ++ .../audit/logfile/LoggingAuditTrail.java | 18 ++++++++++-------- .../audit/logfile/LoggingAuditTrailTests.java | 7 +++++++ 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/x-pack/docs/en/security/auditing/event-types.asciidoc b/x-pack/docs/en/security/auditing/event-types.asciidoc index 442877f31b5..417b26cbd09 100644 --- a/x-pack/docs/en/security/auditing/event-types.asciidoc +++ b/x-pack/docs/en/security/auditing/event-types.asciidoc @@ -118,6 +118,9 @@ common ones): This is URL encoded. `url.query` :: The query part of the URL (after "?", if present) of the REST request associated with this event. This is URL encoded. +`request.method` :: The HTTP method of the REST request associated with this + event. It is one of GET, POST, PUT, DELETE, OPTIONS, + HEAD, PATCH, TRACE and CONNECT. `request.body` :: The full content of the REST request associated with this event, if enabled. This contains the query body. The body is escaped according to the JSON RFC 4627. diff --git a/x-pack/docs/en/security/auditing/output-logfile.asciidoc b/x-pack/docs/en/security/auditing/output-logfile.asciidoc index d9e7eb81c3f..382307e893f 100644 --- a/x-pack/docs/en/security/auditing/output-logfile.asciidoc +++ b/x-pack/docs/en/security/auditing/output-logfile.asciidoc @@ -18,8 +18,7 @@ For more information, see {ref}/logging.html#configuring-logging-levels[configur [[audit-log-entry-format]] === Log entry format -The log entries in the `_audit.log` file -have the following format: +The log entries in the `_audit.log` file have the following format: - Each log entry is a one line JSON document and each one is printed on a separate line. - The fields of a log entry are ordered. However, if a field does not have a value it diff --git a/x-pack/plugin/core/src/main/config/log4j2.properties b/x-pack/plugin/core/src/main/config/log4j2.properties index 21b0732fed4..52b6ce39506 100644 --- a/x-pack/plugin/core/src/main/config/log4j2.properties +++ b/x-pack/plugin/core/src/main/config/log4j2.properties @@ -22,6 +22,7 @@ appender.audit_rolling.layout.pattern = {\ %varsNotEmpty{, "realm":"%enc{%map{realm}}{JSON}"}\ %varsNotEmpty{, "url.path":"%enc{%map{url.path}}{JSON}"}\ %varsNotEmpty{, "url.query":"%enc{%map{url.query}}{JSON}"}\ + %varsNotEmpty{, "request.method":"%enc{%map{request.method}}{JSON}"}\ %varsNotEmpty{, "request.body":"%enc{%map{request.body}}{JSON}"}\ %varsNotEmpty{, "request.id":"%enc{%map{request.id}}{JSON}"}\ %varsNotEmpty{, "action":"%enc{%map{action}}{JSON}"}\ @@ -51,6 +52,7 @@ appender.audit_rolling.layout.pattern = {\ # "realm" name of a realm that has generated an "authentication_failed" or an "authentication_successful"; the subject is not yet authenticated # "url.path" the URI component between the port and the query string; it is percent (URL) encoded # "url.query" the URI component after the path and before the fragment; it is percent (URL) encoded +# "request.method" the method of the HTTP request, i.e. one of GET, POST, PUT, DELETE, OPTIONS, HEAD, PATCH, TRACE, CONNECT # "request.body" the content of the request body entity, JSON escaped # "request.id" a synthentic identifier for the incoming request, this is unique per incoming request, and consistent across all audit events generated by that request # "action" an action is the most granular operation that is authorized and this identifies it in a namespaced way (internal) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java index cdeee882c1b..045140e331f 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java @@ -100,6 +100,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { public static final String REALM_FIELD_NAME = "realm"; public static final String URL_PATH_FIELD_NAME = "url.path"; public static final String URL_QUERY_FIELD_NAME = "url.query"; + public static final String REQUEST_METHOD_FIELD_NAME = "request.method"; public static final String REQUEST_BODY_FIELD_NAME = "request.body"; public static final String REQUEST_ID_FIELD_NAME = "request.id"; public static final String ACTION_FIELD_NAME = "action"; @@ -211,7 +212,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_success") .with(REALM_FIELD_NAME, realm) - .withRestUri(request) + .withRestUriAndMethod(request) .withRequestId(requestId) .withPrincipal(user) .withRestOrigin(request) @@ -276,7 +277,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { final StringMapMessage logEntry = new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") - .withRestUri(request) + .withRestUriAndMethod(request) .withRestOrigin(request) .withRequestBody(request) .withRequestId(requestId) @@ -316,7 +317,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { final StringMapMessage logEntry = new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") - .withRestUri(request) + .withRestUriAndMethod(request) .withRestOrigin(request) .withRequestBody(request) .withRequestId(requestId) @@ -357,7 +358,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") .with(PRINCIPAL_FIELD_NAME, token.principal()) - .withRestUri(request) + .withRestUriAndMethod(request) .withRestOrigin(request) .withRequestBody(request) .withRequestId(requestId) @@ -401,7 +402,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") .with(REALM_FIELD_NAME, realm) .with(PRINCIPAL_FIELD_NAME, token.principal()) - .withRestUri(request) + .withRestUriAndMethod(request) .withRestOrigin(request) .withRequestBody(request) .withRequestId(requestId) @@ -468,7 +469,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { final StringMapMessage logEntry = new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "tampered_request") - .withRestUri(request) + .withRestUriAndMethod(request) .withRestOrigin(request) .withRequestBody(request) .withRequestId(requestId) @@ -617,7 +618,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "run_as_denied") .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) - .withRestUri(request) + .withRestUriAndMethod(request) .withRunAsSubject(authentication) .withRestOrigin(request) .withRequestBody(request) @@ -637,7 +638,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { logEntry = new StringMapMessage(LoggingAuditTrail.this.entryCommonFields.commonFields); } - LogEntryBuilder withRestUri(RestRequest request) { + LogEntryBuilder withRestUriAndMethod(RestRequest request) { final int queryStringIndex = request.uri().indexOf('?'); int queryStringLength = request.uri().indexOf('#'); if (queryStringLength < 0) { @@ -651,6 +652,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (queryStringIndex > -1) { logEntry.with(URL_QUERY_FIELD_NAME, request.uri().substring(queryStringIndex + 1, queryStringLength)); } + logEntry.with(REQUEST_METHOD_FIELD_NAME, request.method().toString()); return this; } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java index da4823a3f3a..817ed2a2358 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailTests.java @@ -245,6 +245,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -346,6 +347,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal()) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -386,6 +388,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, null) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -468,6 +471,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal()) .put(LoggingAuditTrail.ACTION_FIELD_NAME, null) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -627,6 +631,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "tampered_request") .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -891,6 +896,7 @@ public class LoggingAuditTrailTests extends ESTestCase { .put(LoggingAuditTrail.REALM_FIELD_NAME, realm) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) + .put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString()) .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) @@ -1080,6 +1086,7 @@ public class LoggingAuditTrailTests extends ESTestCase { } builder.withRemoteAddress(remoteAddress); builder.withParams(params); + builder.withMethod(randomFrom(RestRequest.Method.values())); return new Tuple<>(content, builder.build()); }