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.
This commit is contained in:
Albert Zaharovits 2019-01-13 15:26:23 +02:00 committed by GitHub
parent 96cfa000a5
commit 6fd57d90da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 23 additions and 10 deletions

View File

@ -118,6 +118,9 @@ common ones):
This is URL encoded. This is URL encoded.
`url.query` :: The query part of the URL (after "?", if present) of the `url.query` :: The query part of the URL (after "?", if present) of the
REST request associated with this event. This is URL encoded. 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 `request.body` :: The full content of the REST request associated with this
event, if enabled. This contains the query body. The body event, if enabled. This contains the query body. The body
is escaped according to the JSON RFC 4627. is escaped according to the JSON RFC 4627.

View File

@ -18,8 +18,7 @@ For more information, see {ref}/logging.html#configuring-logging-levels[configur
[[audit-log-entry-format]] [[audit-log-entry-format]]
=== Log entry format === Log entry format
The log entries in the `<clustername>_audit.log` file The log entries in the `<clustername>_audit.log` file have the following format:
have the following format:
- Each log entry is a one line JSON document and each one is printed on a separate line. - 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 - The fields of a log entry are ordered. However, if a field does not have a value it

View File

@ -22,6 +22,7 @@ appender.audit_rolling.layout.pattern = {\
%varsNotEmpty{, "realm":"%enc{%map{realm}}{JSON}"}\ %varsNotEmpty{, "realm":"%enc{%map{realm}}{JSON}"}\
%varsNotEmpty{, "url.path":"%enc{%map{url.path}}{JSON}"}\ %varsNotEmpty{, "url.path":"%enc{%map{url.path}}{JSON}"}\
%varsNotEmpty{, "url.query":"%enc{%map{url.query}}{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.body":"%enc{%map{request.body}}{JSON}"}\
%varsNotEmpty{, "request.id":"%enc{%map{request.id}}{JSON}"}\ %varsNotEmpty{, "request.id":"%enc{%map{request.id}}{JSON}"}\
%varsNotEmpty{, "action":"%enc{%map{action}}{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 # "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.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 # "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.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 # "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) # "action" an action is the most granular operation that is authorized and this identifies it in a namespaced way (internal)

View File

@ -100,6 +100,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
public static final String REALM_FIELD_NAME = "realm"; public static final String REALM_FIELD_NAME = "realm";
public static final String URL_PATH_FIELD_NAME = "url.path"; public static final String URL_PATH_FIELD_NAME = "url.path";
public static final String URL_QUERY_FIELD_NAME = "url.query"; 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_BODY_FIELD_NAME = "request.body";
public static final String REQUEST_ID_FIELD_NAME = "request.id"; public static final String REQUEST_ID_FIELD_NAME = "request.id";
public static final String ACTION_FIELD_NAME = "action"; 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_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "authentication_success") .with(EVENT_ACTION_FIELD_NAME, "authentication_success")
.with(REALM_FIELD_NAME, realm) .with(REALM_FIELD_NAME, realm)
.withRestUri(request) .withRestUriAndMethod(request)
.withRequestId(requestId) .withRequestId(requestId)
.withPrincipal(user) .withPrincipal(user)
.withRestOrigin(request) .withRestOrigin(request)
@ -276,7 +277,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
final StringMapMessage logEntry = new LogEntryBuilder() final StringMapMessage logEntry = new LogEntryBuilder()
.with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied")
.withRestUri(request) .withRestUriAndMethod(request)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
.withRequestId(requestId) .withRequestId(requestId)
@ -316,7 +317,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
final StringMapMessage logEntry = new LogEntryBuilder() final StringMapMessage logEntry = new LogEntryBuilder()
.with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "authentication_failed") .with(EVENT_ACTION_FIELD_NAME, "authentication_failed")
.withRestUri(request) .withRestUriAndMethod(request)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
.withRequestId(requestId) .withRequestId(requestId)
@ -357,7 +358,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
.with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "authentication_failed") .with(EVENT_ACTION_FIELD_NAME, "authentication_failed")
.with(PRINCIPAL_FIELD_NAME, token.principal()) .with(PRINCIPAL_FIELD_NAME, token.principal())
.withRestUri(request) .withRestUriAndMethod(request)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
.withRequestId(requestId) .withRequestId(requestId)
@ -401,7 +402,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
.with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed")
.with(REALM_FIELD_NAME, realm) .with(REALM_FIELD_NAME, realm)
.with(PRINCIPAL_FIELD_NAME, token.principal()) .with(PRINCIPAL_FIELD_NAME, token.principal())
.withRestUri(request) .withRestUriAndMethod(request)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
.withRequestId(requestId) .withRequestId(requestId)
@ -468,7 +469,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
final StringMapMessage logEntry = new LogEntryBuilder() final StringMapMessage logEntry = new LogEntryBuilder()
.with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "tampered_request") .with(EVENT_ACTION_FIELD_NAME, "tampered_request")
.withRestUri(request) .withRestUriAndMethod(request)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
.withRequestId(requestId) .withRequestId(requestId)
@ -617,7 +618,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
.with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE)
.with(EVENT_ACTION_FIELD_NAME, "run_as_denied") .with(EVENT_ACTION_FIELD_NAME, "run_as_denied")
.with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames)
.withRestUri(request) .withRestUriAndMethod(request)
.withRunAsSubject(authentication) .withRunAsSubject(authentication)
.withRestOrigin(request) .withRestOrigin(request)
.withRequestBody(request) .withRequestBody(request)
@ -637,7 +638,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
logEntry = new StringMapMessage(LoggingAuditTrail.this.entryCommonFields.commonFields); logEntry = new StringMapMessage(LoggingAuditTrail.this.entryCommonFields.commonFields);
} }
LogEntryBuilder withRestUri(RestRequest request) { LogEntryBuilder withRestUriAndMethod(RestRequest request) {
final int queryStringIndex = request.uri().indexOf('?'); final int queryStringIndex = request.uri().indexOf('?');
int queryStringLength = request.uri().indexOf('#'); int queryStringLength = request.uri().indexOf('#');
if (queryStringLength < 0) { if (queryStringLength < 0) {
@ -651,6 +652,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener {
if (queryStringIndex > -1) { if (queryStringIndex > -1) {
logEntry.with(URL_QUERY_FIELD_NAME, request.uri().substring(queryStringIndex + 1, queryStringLength)); logEntry.with(URL_QUERY_FIELD_NAME, request.uri().substring(queryStringIndex + 1, queryStringLength));
} }
logEntry.with(REQUEST_METHOD_FIELD_NAME, request.method().toString());
return this; return this;
} }

View File

@ -245,6 +245,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
.put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "anonymous_access_denied")
.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE)
.put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .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.PRINCIPAL_FIELD_NAME, mockToken.principal())
.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE)
.put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId)
@ -386,6 +388,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, null) .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, null)
.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE)
.put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .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.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal()) .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal())
.put(LoggingAuditTrail.ACTION_FIELD_NAME, null) .put(LoggingAuditTrail.ACTION_FIELD_NAME, null)
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .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.EVENT_ACTION_FIELD_NAME, "tampered_request")
.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE)
.put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId)
@ -891,6 +896,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
.put(LoggingAuditTrail.REALM_FIELD_NAME, realm) .put(LoggingAuditTrail.REALM_FIELD_NAME, realm)
.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE)
.put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address)) .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address))
.put(LoggingAuditTrail.REQUEST_METHOD_FIELD_NAME, request.method().toString())
.put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME,
includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null)
.put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId) .put(LoggingAuditTrail.REQUEST_ID_FIELD_NAME, requestId)
@ -1080,6 +1086,7 @@ public class LoggingAuditTrailTests extends ESTestCase {
} }
builder.withRemoteAddress(remoteAddress); builder.withRemoteAddress(remoteAddress);
builder.withParams(params); builder.withParams(params);
builder.withMethod(randomFrom(RestRequest.Method.values()));
return new Tuple<>(content, builder.build()); return new Tuple<>(content, builder.build());
} }