diff --git a/x-pack/plugin/core/src/main/config/log4j2.properties b/x-pack/plugin/core/src/main/config/log4j2.properties index c4cdbc0640c85..1c9358f3cc490 100644 --- a/x-pack/plugin/core/src/main/config/log4j2.properties +++ b/x-pack/plugin/core/src/main/config/log4j2.properties @@ -1,9 +1,64 @@ appender.audit_rolling.type = RollingFile appender.audit_rolling.name = audit_rolling -appender.audit_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_access.log +appender.audit_rolling.fileName = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_audit.log appender.audit_rolling.layout.type = PatternLayout -appender.audit_rolling.layout.pattern = [%d{ISO8601}] %m%n -appender.audit_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_access-%d{yyyy-MM-dd}.log +appender.audit_rolling.layout.pattern = {\ + "@timestamp":"%d{ISO8601}"\ + %varsNotEmpty{, "node.name":"%enc{%map{node.name}}{JSON}"}\ + %varsNotEmpty{, "node.id":"%enc{%map{node.id}}{JSON}"}\ + %varsNotEmpty{, "host.name":"%enc{%map{host.name}}{JSON}"}\ + %varsNotEmpty{, "host.ip":"%enc{%map{host.ip}}{JSON}"}\ + %varsNotEmpty{, "event.type":"%enc{%map{event.type}}{JSON}"}\ + %varsNotEmpty{, "event.action":"%enc{%map{event.action}}{JSON}"}\ + %varsNotEmpty{, "user.name":"%enc{%map{user.name}}{JSON}"}\ + %varsNotEmpty{, "user.run_by.name":"%enc{%map{user.run_by.name}}{JSON}"}\ + %varsNotEmpty{, "user.run_as.name":"%enc{%map{user.run_as.name}}{JSON}"}\ + %varsNotEmpty{, "user.realm":"%enc{%map{user.realm}}{JSON}"}\ + %varsNotEmpty{, "user.run_by.realm":"%enc{%map{user.run_by.realm}}{JSON}"}\ + %varsNotEmpty{, "user.run_as.realm":"%enc{%map{user.run_as.realm}}{JSON}"}\ + %varsNotEmpty{, "user.roles":%map{user.roles}}\ + %varsNotEmpty{, "origin.type":"%enc{%map{origin.type}}{JSON}"}\ + %varsNotEmpty{, "origin.address":"%enc{%map{origin.address}}{JSON}"}\ + %varsNotEmpty{, "realm":"%enc{%map{realm}}{JSON}"}\ + %varsNotEmpty{, "url.path":"%enc{%map{url.path}}{JSON}"}\ + %varsNotEmpty{, "url.query":"%enc{%map{url.query}}{JSON}"}\ + %varsNotEmpty{, "request.body":"%enc{%map{request.body}}{JSON}"}\ + %varsNotEmpty{, "action":"%enc{%map{action}}{JSON}"}\ + %varsNotEmpty{, "request.name":"%enc{%map{request.name}}{JSON}"}\ + %varsNotEmpty{, "indices":%map{indices}}\ + %varsNotEmpty{, "opaque_id":"%enc{%map{opaque_id}}{JSON}"}\ + %varsNotEmpty{, "transport.profile":"%enc{%map{transport.profile}}{JSON}"}\ + %varsNotEmpty{, "rule":"%enc{%map{rule}}{JSON}"}\ + %varsNotEmpty{, "event.category":"%enc{%map{event.category}}{JSON}"}\ + }%n +# "node.name" node name from the `elasticsearch.yml` settings +# "node.id" node id which should not change between cluster restarts +# "host.name" unresolved hostname of the local node +# "host.ip" the local bound ip (i.e. the ip listening for connections) +# "event.type" a received REST request is translated into one or more transport requests. This indicates which processing layer generated the event "rest" or "transport" (internal) +# "event.action" the name of the audited event, eg. "authentication_failed", "access_granted", "run_as_granted", etc. +# "user.name" the subject name as authenticated by a realm +# "user.run_by.name" the original authenticated subject name that is impersonating another one. +# "user.run_as.name" if this "event.action" is of a run_as type, this is the subject name to be impersonated as. +# "user.realm" the name of the realm that authenticated "user.name" +# "user.run_by.realm" the realm name of the impersonating subject ("user.run_by.name") +# "user.run_as.realm" if this "event.action" is of a run_as type, this is the realm name the impersonated user is looked up from +# "user.roles" the roles array of the user; these are the roles that are granting privileges +# "origin.type" it is "rest" if the event is originating (is in relation to) a REST request; possible other values are "transport" and "ip_filter" +# "origin.address" the remote address and port of the first network hop, i.e. a REST proxy or another cluster node +# "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.body" the content of the request body entity, JSON escaped +# "action" an action is the most granular operation that is authorized and this identifies it in a namespaced way (internal) +# "request.name" if the event is in connection to a transport message this is the name of the request class, similar to how rest requests are identified by the url path (internal) +# "indices" the array of indices that the "action" is acting upon +# "opaque_id" opaque value conveyed by the "X-Opaque-Id" request header +# "transport.profile" name of the transport profile in case this is a "connection_granted" or "connection_denied" event +# "rule" name of the applied rulee if the "origin.type" is "ip_filter" +# "event.category" fixed value "elasticsearch-audit" + +appender.audit_rolling.filePattern = ${sys:es.logs.base_path}${sys:file.separator}${sys:es.logs.cluster_name}_audit-%d{yyyy-MM-dd}.log appender.audit_rolling.policies.type = Policies appender.audit_rolling.policies.time.type = TimeBasedTriggeringPolicy appender.audit_rolling.policies.time.interval = 1 diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/audit/logfile/CapturingLogger.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/audit/logfile/CapturingLogger.java index 2091f8fb75fde..ede18c8241b34 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/audit/logfile/CapturingLogger.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/audit/logfile/CapturingLogger.java @@ -8,26 +8,51 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Layout; import org.apache.logging.log4j.core.LogEvent; import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.StringLayout; import org.apache.logging.log4j.core.appender.AbstractAppender; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.apache.logging.log4j.core.filter.RegexFilter; +import org.elasticsearch.common.Nullable; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.Loggers; import java.util.ArrayList; import java.util.List; +/** + * Logger that captures events and appends them to in memory lists, with one + * list for each log level. This works with the global log manager context, + * meaning that there could only be a single logger with the same name. + */ public class CapturingLogger { - public static Logger newCapturingLogger(final Level level) throws IllegalAccessException { + /** + * Constructs a new {@link CapturingLogger} named as the fully qualified name of + * the invoking method. One name can be assigned to a single logger globally, so + * don't call this method multiple times in the same method. + * + * @param level + * The minimum priority level of events that will be captured. + * @param layout + * Optional parameter allowing to set the layout format of events. + * This is useful because events are captured to be inspected (and + * parsed) later. When parsing, it is useful to be in control of the + * printing format as well. If not specified, + * {@code event.getMessage().getFormattedMessage()} is called to + * format the event. + * @return The new logger. + */ + public static Logger newCapturingLogger(final Level level, @Nullable StringLayout layout) throws IllegalAccessException { + // careful, don't "bury" this on the call stack, unless you know what you're doing final StackTraceElement caller = Thread.currentThread().getStackTrace()[2]; final String name = caller.getClassName() + "." + caller.getMethodName() + "." + level.toString(); final Logger logger = ESLoggerFactory.getLogger(name); Loggers.setLevel(logger, level); - final MockAppender appender = new MockAppender(name); + final MockAppender appender = new MockAppender(name, layout); appender.start(); Loggers.addAppender(logger, appender); return logger; @@ -40,11 +65,27 @@ private static MockAppender getMockAppender(final String name) { return (MockAppender) loggerConfig.getAppenders().get(name); } + /** + * Checks if the logger's appender has captured any events. + * + * @param name + * The unique global name of the logger. + * @return {@code true} if no event has been captured, {@code false} otherwise. + */ public static boolean isEmpty(final String name) { final MockAppender appender = getMockAppender(name); return appender.isEmpty(); } + /** + * Gets the captured events for a logger by its name. + * + * @param name + * The unique global name of the logger. + * @param level + * The priority level of the captured events to be returned. + * @return A list of captured events formated to {@code String}. + */ public static List output(final String name, final Level level) { final MockAppender appender = getMockAppender(name); return appender.output(level); @@ -58,8 +99,8 @@ private static class MockAppender extends AbstractAppender { public final List debug = new ArrayList<>(); public final List trace = new ArrayList<>(); - private MockAppender(final String name) throws IllegalAccessException { - super(name, RegexFilter.createFilter(".*(\n.*)*", new String[0], false, null, null), null); + private MockAppender(final String name, StringLayout layout) throws IllegalAccessException { + super(name, RegexFilter.createFilter(".*(\n.*)*", new String[0], false, null, null), layout); } @Override @@ -68,25 +109,34 @@ public void append(LogEvent event) { // we can not keep a reference to the event here because Log4j is using a thread // local instance under the hood case "ERROR": - error.add(event.getMessage().getFormattedMessage()); + error.add(formatMessage(event)); break; case "WARN": - warn.add(event.getMessage().getFormattedMessage()); + warn.add(formatMessage(event)); break; case "INFO": - info.add(event.getMessage().getFormattedMessage()); + info.add(formatMessage(event)); break; case "DEBUG": - debug.add(event.getMessage().getFormattedMessage()); + debug.add(formatMessage(event)); break; case "TRACE": - trace.add(event.getMessage().getFormattedMessage()); + trace.add(formatMessage(event)); break; default: throw invalidLevelException(event.getLevel()); } } + private String formatMessage(LogEvent event) { + final Layout layout = getLayout(); + if (layout instanceof StringLayout) { + return ((StringLayout) layout).toSerializable(event); + } else { + return event.getMessage().getFormattedMessage(); + } + } + private IllegalArgumentException invalidLevelException(Level level) { return new IllegalArgumentException("invalid level, expected [ERROR|WARN|INFO|DEBUG|TRACE] but was [" + level + "]"); } diff --git a/x-pack/plugin/security/build.gradle b/x-pack/plugin/security/build.gradle index 71b22531ccab0..74241be4a91f6 100644 --- a/x-pack/plugin/security/build.gradle +++ b/x-pack/plugin/security/build.gradle @@ -140,6 +140,7 @@ artifacts { } sourceSets.test.resources { srcDir '../core/src/test/resources' + srcDir '../core/src/main/config' } dependencyLicenses { mapping from: /java-support|opensaml-.*/, to: 'shibboleth' 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 5da6a9eb77cdd..040dee806e6de 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 @@ -6,15 +6,16 @@ package org.elasticsearch.xpack.security.audit.logfile; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.message.StringMapMessage; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterStateListener; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Setting.Property; @@ -37,11 +38,16 @@ import org.elasticsearch.xpack.security.rest.RemoteHostHeader; import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule; +import com.fasterxml.jackson.core.io.JsonStringEncoder; + +import org.apache.logging.log4j.LogManager; + import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -52,7 +58,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.elasticsearch.common.Strings.arrayToCommaDelimitedString; import static org.elasticsearch.xpack.core.security.SecurityField.setting; import static org.elasticsearch.xpack.security.audit.AuditLevel.ACCESS_DENIED; import static org.elasticsearch.xpack.security.audit.AuditLevel.ACCESS_GRANTED; @@ -71,55 +76,81 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail, ClusterStateListener { + public static final String REST_ORIGIN_FIELD_VALUE = "rest"; + public static final String LOCAL_ORIGIN_FIELD_VALUE = "local_node"; + public static final String TRANSPORT_ORIGIN_FIELD_VALUE = "transport"; + public static final String IP_FILTER_ORIGIN_FIELD_VALUE = "ip_filter"; + + // changing any of this names requires changing the log4j2.properties file too + public static final String ORIGIN_TYPE_FIELD_NAME = "origin.type"; + public static final String ORIGIN_ADDRESS_FIELD_NAME = "origin.address"; + public static final String NODE_NAME_FIELD_NAME = "node.name"; + public static final String NODE_ID_FIELD_NAME = "node.id"; + public static final String HOST_ADDRESS_FIELD_NAME = "host.ip"; + public static final String HOST_NAME_FIELD_NAME = "host.name"; + public static final String EVENT_TYPE_FIELD_NAME = "event.type"; + public static final String EVENT_ACTION_FIELD_NAME = "event.action"; + public static final String PRINCIPAL_FIELD_NAME = "user.name"; + public static final String PRINCIPAL_RUN_BY_FIELD_NAME = "user.run_by.name"; + public static final String PRINCIPAL_RUN_AS_FIELD_NAME = "user.run_as.name"; + public static final String PRINCIPAL_REALM_FIELD_NAME = "user.realm"; + public static final String PRINCIPAL_RUN_BY_REALM_FIELD_NAME = "user.run_by.realm"; + public static final String PRINCIPAL_RUN_AS_REALM_FIELD_NAME = "user.run_as.realm"; + public static final String PRINCIPAL_ROLES_FIELD_NAME = "user.roles"; + 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_BODY_FIELD_NAME = "request.body"; + public static final String ACTION_FIELD_NAME = "action"; + public static final String INDICES_FIELD_NAME = "indices"; + public static final String REQUEST_NAME_FIELD_NAME = "request.name"; + public static final String TRANSPORT_PROFILE_FIELD_NAME = "transport.profile"; + public static final String RULE_FIELD_NAME = "rule"; + public static final String OPAQUE_ID_FIELD_NAME = "opaque_id"; + public static final String NAME = "logfile"; - public static final Setting HOST_ADDRESS_SETTING = - Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_address"), false, Property.NodeScope, Property.Dynamic); - public static final Setting HOST_NAME_SETTING = - Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_name"), false, Property.NodeScope, Property.Dynamic); - public static final Setting NODE_NAME_SETTING = - Setting.boolSetting(setting("audit.logfile.prefix.emit_node_name"), true, Property.NodeScope, Property.Dynamic); - private static final List DEFAULT_EVENT_INCLUDES = Arrays.asList( - ACCESS_DENIED.toString(), - ACCESS_GRANTED.toString(), - ANONYMOUS_ACCESS_DENIED.toString(), - AUTHENTICATION_FAILED.toString(), - CONNECTION_DENIED.toString(), - TAMPERED_REQUEST.toString(), - RUN_AS_DENIED.toString(), - RUN_AS_GRANTED.toString() - ); - public static final Setting> INCLUDE_EVENT_SETTINGS = - Setting.listSetting(setting("audit.logfile.events.include"), DEFAULT_EVENT_INCLUDES, Function.identity(), Property.NodeScope, - Property.Dynamic); - public static final Setting> EXCLUDE_EVENT_SETTINGS = - Setting.listSetting(setting("audit.logfile.events.exclude"), Collections.emptyList(), Function.identity(), Property.NodeScope, - Property.Dynamic); - public static final Setting INCLUDE_REQUEST_BODY = - Setting.boolSetting(setting("audit.logfile.events.emit_request_body"), false, Property.NodeScope, Property.Dynamic); + public static final Setting EMIT_HOST_ADDRESS_SETTING = Setting + .boolSetting(setting("audit.logfile.prefix.emit_node_host_address"), false, Property.NodeScope, Property.Dynamic); + public static final Setting EMIT_HOST_NAME_SETTING = Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_name"), + false, Property.NodeScope, Property.Dynamic); + public static final Setting EMIT_NODE_NAME_SETTING = Setting.boolSetting(setting("audit.logfile.prefix.emit_node_name"), false, + Property.NodeScope, Property.Dynamic); + public static final Setting EMIT_NODE_ID_SETTING = Setting.boolSetting(setting("audit.logfile.prefix.emit_node_id"), true, + Property.NodeScope, Property.Dynamic); + private static final List DEFAULT_EVENT_INCLUDES = Arrays.asList(ACCESS_DENIED.toString(), ACCESS_GRANTED.toString(), + ANONYMOUS_ACCESS_DENIED.toString(), AUTHENTICATION_FAILED.toString(), CONNECTION_DENIED.toString(), TAMPERED_REQUEST.toString(), + RUN_AS_DENIED.toString(), RUN_AS_GRANTED.toString()); + public static final Setting> INCLUDE_EVENT_SETTINGS = Setting.listSetting(setting("audit.logfile.events.include"), + DEFAULT_EVENT_INCLUDES, Function.identity(), Property.NodeScope, Property.Dynamic); + public static final Setting> EXCLUDE_EVENT_SETTINGS = Setting.listSetting(setting("audit.logfile.events.exclude"), + Collections.emptyList(), Function.identity(), Property.NodeScope, Property.Dynamic); + public static final Setting INCLUDE_REQUEST_BODY = Setting.boolSetting(setting("audit.logfile.events.emit_request_body"), + false, Property.NodeScope, Property.Dynamic); private static final String FILTER_POLICY_PREFIX = setting("audit.logfile.events.ignore_filters."); // because of the default wildcard value (*) for the field filter, a policy with // an unspecified filter field will match events that have any value for that // particular field, as well as events with that particular field missing - private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_PRINCIPALS = - Setting.affixKeySetting(FILTER_POLICY_PREFIX, "users", (key) -> Setting.listSetting(key, Collections.singletonList("*"), - Function.identity(), Property.NodeScope, Property.Dynamic)); - private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_REALMS = - Setting.affixKeySetting(FILTER_POLICY_PREFIX, "realms", (key) -> Setting.listSetting(key, Collections.singletonList("*"), - Function.identity(), Property.NodeScope, Property.Dynamic)); - private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_ROLES = - Setting.affixKeySetting(FILTER_POLICY_PREFIX, "roles", (key) -> Setting.listSetting(key, Collections.singletonList("*"), - Function.identity(), Property.NodeScope, Property.Dynamic)); - private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_INDICES = - Setting.affixKeySetting(FILTER_POLICY_PREFIX, "indices", (key) -> Setting.listSetting(key, Collections.singletonList("*"), - Function.identity(), Property.NodeScope, Property.Dynamic)); + private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_PRINCIPALS = Setting.affixKeySetting(FILTER_POLICY_PREFIX, + "users", + (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), Property.NodeScope, Property.Dynamic)); + private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_REALMS = Setting.affixKeySetting(FILTER_POLICY_PREFIX, + "realms", + (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), Property.NodeScope, Property.Dynamic)); + private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_ROLES = Setting.affixKeySetting(FILTER_POLICY_PREFIX, + "roles", + (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), Property.NodeScope, Property.Dynamic)); + private static final Setting.AffixSetting> FILTER_POLICY_IGNORE_INDICES = Setting.affixKeySetting(FILTER_POLICY_PREFIX, + "indices", + (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), Property.NodeScope, Property.Dynamic)); private final Logger logger; - final EventFilterPolicyRegistry eventFilterPolicyRegistry; private final ThreadContext threadContext; + final EventFilterPolicyRegistry eventFilterPolicyRegistry; // package for testing volatile EnumSet events; boolean includeRequestBody; - LocalNodeInfo localNodeInfo; + // fields that all entries have in common + EntryCommonFields entryCommonFields; @Override public String name() { @@ -127,7 +158,7 @@ public String name() { } public LoggingAuditTrail(Settings settings, ClusterService clusterService, ThreadPool threadPool) { - this(settings, clusterService, Loggers.getLogger(LoggingAuditTrail.class), threadPool.getThreadContext()); + this(settings, clusterService, LogManager.getLogger(), threadPool.getThreadContext()); } LoggingAuditTrail(Settings settings, ClusterService clusterService, Logger logger, ThreadContext threadContext) { @@ -136,20 +167,18 @@ public LoggingAuditTrail(Settings settings, ClusterService clusterService, Threa this.events = parse(INCLUDE_EVENT_SETTINGS.get(settings), EXCLUDE_EVENT_SETTINGS.get(settings)); this.includeRequestBody = INCLUDE_REQUEST_BODY.get(settings); this.threadContext = threadContext; - this.localNodeInfo = new LocalNodeInfo(settings, null); + this.entryCommonFields = new EntryCommonFields(settings, null); this.eventFilterPolicyRegistry = new EventFilterPolicyRegistry(settings); clusterService.addListener(this); clusterService.getClusterSettings().addSettingsUpdateConsumer(newSettings -> { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - final Settings.Builder builder = Settings.builder().put(localNodeInfo.settings).put(newSettings, false); - this.localNodeInfo = new LocalNodeInfo(builder.build(), localNodeInfo.localNode); + this.entryCommonFields = this.entryCommonFields.withNewSettings(newSettings); this.includeRequestBody = INCLUDE_REQUEST_BODY.get(newSettings); // `events` is a volatile field! Keep `events` write last so that - // `localNodeInfo` and `includeRequestBody` writes happen-before! `events` is - // always read before `localNodeInfo` and `includeRequestBody`. + // `entryCommonFields` and `includeRequestBody` writes happen-before! `events` is + // always read before `entryCommonFields` and `includeRequestBody`. this.events = parse(INCLUDE_EVENT_SETTINGS.get(newSettings), EXCLUDE_EVENT_SETTINGS.get(newSettings)); - }, Arrays.asList(HOST_ADDRESS_SETTING, HOST_NAME_SETTING, NODE_NAME_SETTING, INCLUDE_EVENT_SETTINGS, EXCLUDE_EVENT_SETTINGS, - INCLUDE_REQUEST_BODY)); + }, Arrays.asList(EMIT_HOST_ADDRESS_SETTING, EMIT_HOST_NAME_SETTING, EMIT_NODE_NAME_SETTING, EMIT_NODE_ID_SETTING, + INCLUDE_EVENT_SETTINGS, EXCLUDE_EVENT_SETTINGS, INCLUDE_REQUEST_BODY)); clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_PRINCIPALS, (policyName, filtersList) -> { final Optional policy = eventFilterPolicyRegistry.get(policyName); final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)) @@ -158,35 +187,36 @@ public LoggingAuditTrail(Settings settings, ClusterService clusterService, Threa }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_REALMS, (policyName, filtersList) -> { final Optional policy = eventFilterPolicyRegistry.get(policyName); - final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)) - .changeRealmsFilter(filtersList); + final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeRealmsFilter(filtersList); this.eventFilterPolicyRegistry.set(policyName, newPolicy); }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_ROLES, (policyName, filtersList) -> { final Optional policy = eventFilterPolicyRegistry.get(policyName); - final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)) - .changeRolesFilter(filtersList); + final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeRolesFilter(filtersList); this.eventFilterPolicyRegistry.set(policyName, newPolicy); }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_INDICES, (policyName, filtersList) -> { final Optional policy = eventFilterPolicyRegistry.get(policyName); - final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)) - .changeIndicesFilter(filtersList); + final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeIndicesFilter(filtersList); this.eventFilterPolicyRegistry.set(policyName, newPolicy); }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); } @Override public void authenticationSuccess(String realm, User user, RestRequest request) { - if (events.contains(AUTHENTICATION_SUCCESS) && (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(user), Optional.of(realm), Optional.empty(), Optional.empty())) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [authentication_success]\t{}, realm=[{}], uri=[{}], params=[{}]{}, request_body=[{}]", - localNodeInfo.prefix, principal(user), realm, request.uri(), request.params(), opaqueId(), restRequestContent(request)); - } else { - logger.info("{}[rest] [authentication_success]\t{}, realm=[{}], uri=[{}], params=[{}]{}", - localNodeInfo.prefix, principal(user), realm, request.uri(), request.params(), opaqueId()); - } + if (events.contains(AUTHENTICATION_SUCCESS) && eventFilterPolicyRegistry.ignorePredicate() + .test(new AuditEventMetaInfo(Optional.of(user), Optional.of(realm), Optional.empty(), Optional.empty())) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_success") + .with(REALM_FIELD_NAME, realm) + .withRestUri(request) + .withPrincipal(user) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -196,16 +226,18 @@ public void authenticationSuccess(String realm, User user, String action, Transp final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(user), Optional.of(realm), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [authentication_success]\t{}, {}, realm=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), principal(user), realm, action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [authentication_success]\t{}, {}, realm=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), principal(user), realm, action, - message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_success") + .with(REALM_FIELD_NAME, realm) + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withPrincipal(user) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @@ -216,16 +248,16 @@ public void anonymousAccessDenied(String action, TransportMessage message) { final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [anonymous_access_denied]\t{}, action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [anonymous_access_denied]\t{}, action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), action, - message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @@ -233,14 +265,16 @@ localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), a @Override public void anonymousAccessDenied(RestRequest request) { if (events.contains(ANONYMOUS_ACCESS_DENIED) - && (eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [anonymous_access_denied]\t{}, uri=[{}]{}, request_body=[{}]", localNodeInfo.prefix, - hostAttributes(request), request.uri(), opaqueId(), restRequestContent(request)); - } else { - logger.info("{}[rest] [anonymous_access_denied]\t{}, uri=[{}]{}", localNodeInfo.prefix, - hostAttributes(request), request.uri(), opaqueId()); - } + && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") + .withRestUri(request) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -250,31 +284,33 @@ public void authenticationFailed(AuthenticationToken token, String action, Trans final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), token.principal(), action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [authentication_failed]\t{}, principal=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), token.principal(), action, - message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") + .with(ACTION_FIELD_NAME, action) + .with(PRINCIPAL_FIELD_NAME, token.principal()) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override public void authenticationFailed(RestRequest request) { - if (events.contains(AUTHENTICATION_FAILED) - && (eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [authentication_failed]\t{}, uri=[{}]{}, request_body=[{}]", localNodeInfo.prefix, - hostAttributes(request), request.uri(), opaqueId(), restRequestContent(request)); - } else { - logger.info("{}[rest] [authentication_failed]\t{}, uri=[{}]{}", localNodeInfo.prefix, - hostAttributes(request), request.uri(), opaqueId()); - } + if (events.contains(AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") + .withRestUri(request) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -284,33 +320,34 @@ public void authenticationFailed(String action, TransportMessage message) { final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [authentication_failed]\t{}, action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [authentication_failed]\t{}, action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), action, - message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override public void authenticationFailed(AuthenticationToken token, RestRequest request) { - if (events.contains(AUTHENTICATION_FAILED) - && (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), Optional.empty())) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}]{}, request_body=[{}]", - localNodeInfo.prefix, hostAttributes(request), token.principal(), request.uri(), opaqueId(), - restRequestContent(request)); - } else { - logger.info("{}[rest] [authentication_failed]\t{}, principal=[{}], uri=[{}]{}", - localNodeInfo.prefix, hostAttributes(request), token.principal(), request.uri(), opaqueId()); - } + if (events.contains(AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate() + .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), Optional.empty())) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") + .with(PRINCIPAL_FIELD_NAME, token.principal()) + .withRestUri(request) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -320,36 +357,37 @@ public void authenticationFailed(String realm, AuthenticationToken token, String final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info( - "{}[transport] [realm_authentication_failed]\trealm=[{}], {}, principal=[{}], action=[{}], indices=[{}], " - + "request=[{}]{}", - localNodeInfo.prefix, realm, originAttributes(threadContext, message, localNodeInfo), token.principal(), action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [realm_authentication_failed]\trealm=[{}], {}, principal=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, realm, originAttributes(threadContext, message, localNodeInfo), token.principal(), action, - message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") + .with(REALM_FIELD_NAME, realm) + .with(PRINCIPAL_FIELD_NAME, token.principal()) + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override public void authenticationFailed(String realm, AuthenticationToken token, RestRequest request) { - if (events.contains(REALM_AUTHENTICATION_FAILED) - && (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), Optional.empty())) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [realm_authentication_failed]\trealm=[{}], {}, principal=[{}], uri=[{}]{}, " - + "request_body=[{}]", - localNodeInfo.prefix, realm, hostAttributes(request), token.principal(), request.uri(), opaqueId(), - restRequestContent(request)); - } else { - logger.info("{}[rest] [realm_authentication_failed]\trealm=[{}], {}, principal=[{}], uri=[{}]{}", - localNodeInfo.prefix, realm, hostAttributes(request), token.principal(), request.uri(), opaqueId()); - } + if (events.contains(REALM_AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate() + .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), Optional.empty())) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") + .with(REALM_FIELD_NAME, realm) + .with(PRINCIPAL_FIELD_NAME, token.principal()) + .withRestUri(request) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -361,17 +399,18 @@ public void accessGranted(Authentication authentication, String action, Transpor final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(user), Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [access_granted]\t{}, {}, roles=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), subject(authentication), - arrayToCommaDelimitedString(roleNames), action, arrayToCommaDelimitedString(indices.get()), - message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [access_granted]\t{}, {}, roles=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), subject(authentication), - arrayToCommaDelimitedString(roleNames), action, message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "access_granted") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withSubject(authentication) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @@ -382,31 +421,34 @@ public void accessDenied(Authentication authentication, String action, Transport final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [access_denied]\t{}, {}, roles=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), subject(authentication), - arrayToCommaDelimitedString(roleNames), action, arrayToCommaDelimitedString(indices.get()), - message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [access_denied]\t{}, {}, roles=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), subject(authentication), - arrayToCommaDelimitedString(roleNames), action, message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "access_denied") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withSubject(authentication) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override public void tamperedRequest(RestRequest request) { - if (events.contains(TAMPERED_REQUEST) && (eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [tampered_request]\t{}, uri=[{}]{}, request_body=[{}]", localNodeInfo.prefix, - hostAttributes(request), request.uri(), opaqueId(), restRequestContent(request)); - } else { - logger.info("{}[rest] [tampered_request]\t{}, uri=[{}]{}", localNodeInfo.prefix, hostAttributes(request), - request.uri(), opaqueId()); - } + if (events.contains(TAMPERED_REQUEST) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "tampered_request") + .withRestUri(request) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -416,53 +458,70 @@ public void tamperedRequest(String action, TransportMessage message) { final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [tampered_request]\t{}, action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), action, - arrayToCommaDelimitedString(indices.get()), message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [tampered_request]\t{}, action=[{}], request=[{}]{}", localNodeInfo.prefix, - originAttributes(threadContext, message, localNodeInfo), action, message.getClass().getSimpleName(), - opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "tampered_request") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override - public void tamperedRequest(User user, String action, TransportMessage request) { + public void tamperedRequest(User user, String action, TransportMessage message) { if (events.contains(TAMPERED_REQUEST)) { - final Optional indices = indices(request); + final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(user), Optional.empty(), Optional.empty(), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [tampered_request]\t{}, {}, action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, request, localNodeInfo), principal(user), action, - arrayToCommaDelimitedString(indices.get()), request.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [tampered_request]\t{}, {}, action=[{}], request=[{}]{}", localNodeInfo.prefix, - originAttributes(threadContext, request, localNodeInfo), principal(user), action, - request.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "tampered_request") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRestOrTransportOrigin(message, threadContext) + .withPrincipal(user) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @Override public void connectionGranted(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - if (events.contains(CONNECTION_GRANTED) && (eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false)) { - logger.info("{}[ip_filter] [connection_granted]\torigin_address=[{}], transport_profile=[{}], rule=[{}]{}", - localNodeInfo.prefix, NetworkAddress.format(inetAddress), profile, rule, opaqueId()); + if (events.contains(CONNECTION_GRANTED) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, IP_FILTER_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "connection_granted") + .with(ORIGIN_TYPE_FIELD_NAME, IP_FILTER_ORIGIN_FIELD_VALUE) + .with(ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(inetAddress)) + .with(TRANSPORT_PROFILE_FIELD_NAME, profile) + .with(RULE_FIELD_NAME, rule.toString()) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @Override public void connectionDenied(InetAddress inetAddress, String profile, SecurityIpFilterRule rule) { - if (events.contains(CONNECTION_DENIED) && (eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false)) { - logger.info("{}[ip_filter] [connection_denied]\torigin_address=[{}], transport_profile=[{}], rule=[{}]{}", - localNodeInfo.prefix, NetworkAddress.format(inetAddress), profile, rule, opaqueId()); + if (events.contains(CONNECTION_DENIED) && eventFilterPolicyRegistry.ignorePredicate().test(AuditEventMetaInfo.EMPTY) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, IP_FILTER_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "connection_denied") + .with(ORIGIN_TYPE_FIELD_NAME, IP_FILTER_ORIGIN_FIELD_VALUE) + .with(ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(inetAddress)) + .with(TRANSPORT_PROFILE_FIELD_NAME, profile) + .with(RULE_FIELD_NAME, rule.toString()) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } @@ -472,17 +531,18 @@ public void runAsGranted(Authentication authentication, String action, Transport final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [run_as_granted]\t{}, {}, roles=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), runAsSubject(authentication), - arrayToCommaDelimitedString(roleNames), action, arrayToCommaDelimitedString(indices.get()), - message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [run_as_granted]\t{}, {}, roles=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), runAsSubject(authentication), - arrayToCommaDelimitedString(roleNames), action, message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "run_as_granted") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRunAsSubject(authentication) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @@ -493,17 +553,18 @@ public void runAsDenied(Authentication authentication, String action, TransportM final Optional indices = indices(message); if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), indices)) == false) { - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if (indices.isPresent()) { - logger.info("{}[transport] [run_as_denied]\t{}, {}, roles=[{}], action=[{}], indices=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), runAsSubject(authentication), - arrayToCommaDelimitedString(roleNames), action, arrayToCommaDelimitedString(indices.get()), - message.getClass().getSimpleName(), opaqueId()); - } else { - logger.info("{}[transport] [run_as_denied]\t{}, {}, roles=[{}], action=[{}], request=[{}]{}", - localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), runAsSubject(authentication), - arrayToCommaDelimitedString(roleNames), action, message.getClass().getSimpleName(), opaqueId()); - } + final StringMapMessage logEntry = new LogEntryBuilder() + .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(EVENT_ACTION_FIELD_NAME, "run_as_denied") + .with(ACTION_FIELD_NAME, action) + .with(REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()) + .withRunAsSubject(authentication) + .withRestOrTransportOrigin(message, threadContext) + .with(INDICES_FIELD_NAME, indices.orElse(null)) + .with(PRINCIPAL_ROLES_FIELD_NAME, roleNames) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); } } } @@ -511,113 +572,183 @@ localNodeInfo.prefix, originAttributes(threadContext, message, localNodeInfo), r @Override public void runAsDenied(Authentication authentication, RestRequest request, String[] roleNames) { if (events.contains(RUN_AS_DENIED) - && (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), - Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), Optional.empty())) == false)) { - if (includeRequestBody) { - logger.info("{}[rest] [run_as_denied]\t{}, {}, roles=[{}], uri=[{}], request_body=[{}]{}", - localNodeInfo.prefix, hostAttributes(request), runAsSubject(authentication), - arrayToCommaDelimitedString(roleNames), request.uri(), restRequestContent(request), opaqueId()); + && eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), + Optional.of(effectiveRealmName(authentication)), Optional.of(roleNames), Optional.empty())) == false) { + final StringMapMessage logEntry = new LogEntryBuilder() + .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) + .withRunAsSubject(authentication) + .withRestOrigin(request) + .withRequestBody(request) + .withOpaqueId(threadContext) + .build(); + logger.info(logEntry); + } + } + + private class LogEntryBuilder { + + private final StringMapMessage logEntry; + + LogEntryBuilder() { + logEntry = new StringMapMessage(LoggingAuditTrail.this.entryCommonFields.commonFields); + } + + LogEntryBuilder withRestUri(RestRequest request) { + final int queryStringIndex = request.uri().indexOf('?'); + int queryStringLength = request.uri().indexOf('#'); + if (queryStringLength < 0) { + queryStringLength = request.uri().length(); + } + if (queryStringIndex < 0) { + logEntry.with(URL_PATH_FIELD_NAME, request.uri().substring(0, queryStringLength)); } else { - logger.info("{}[rest] [run_as_denied]\t{}, {}, roles=[{}], uri=[{}]{}", localNodeInfo.prefix, - hostAttributes(request), runAsSubject(authentication), arrayToCommaDelimitedString(roleNames), request.uri(), - opaqueId()); + logEntry.with(URL_PATH_FIELD_NAME, request.uri().substring(0, queryStringIndex)); + } + if (queryStringIndex > -1) { + logEntry.with(URL_QUERY_FIELD_NAME, request.uri().substring(queryStringIndex + 1, queryStringLength)); } + return this; } - } - static String runAsSubject(Authentication authentication) { - final StringBuilder sb = new StringBuilder("principal=["); - sb.append(authentication.getUser().authenticatedUser().principal()); - sb.append("], realm=["); - sb.append(authentication.getAuthenticatedBy().getName()); - sb.append("], run_as_principal=["); - sb.append(authentication.getUser().principal()); - if (authentication.getLookedUpBy() != null) { - sb.append("], run_as_realm=[").append(authentication.getLookedUpBy().getName()); + LogEntryBuilder withRunAsSubject(Authentication authentication) { + logEntry.with(PRINCIPAL_FIELD_NAME, authentication.getUser().authenticatedUser().principal()) + .with(PRINCIPAL_REALM_FIELD_NAME, authentication.getAuthenticatedBy().getName()) + .with(PRINCIPAL_RUN_AS_FIELD_NAME, authentication.getUser().principal()); + if (authentication.getLookedUpBy() != null) { + logEntry.with(PRINCIPAL_RUN_AS_REALM_FIELD_NAME, authentication.getLookedUpBy().getName()); + } + return this; } - sb.append("]"); - return sb.toString(); - } - static String subject(Authentication authentication) { - final StringBuilder sb = new StringBuilder("principal=["); - sb.append(authentication.getUser().principal()).append("], realm=["); - if (authentication.getUser().isRunAs()) { - sb.append(authentication.getLookedUpBy().getName()).append("], run_by_principal=["); - sb.append(authentication.getUser().authenticatedUser().principal()).append("], run_by_realm=["); + LogEntryBuilder withRestOrigin(RestRequest request) { + assert LOCAL_ORIGIN_FIELD_VALUE.equals(logEntry.get(ORIGIN_TYPE_FIELD_NAME)); // this is the default + final InetSocketAddress socketAddress = request.getHttpChannel().getRemoteAddress(); + if (socketAddress != null) { + logEntry.with(ORIGIN_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(socketAddress)); + } + // fall through to local_node default + return this; } - sb.append(authentication.getAuthenticatedBy().getName()).append("]"); - return sb.toString(); - } - private static String hostAttributes(RestRequest request) { - final InetSocketAddress socketAddress = request.getHttpChannel().getRemoteAddress(); - String formattedAddress = NetworkAddress.format(socketAddress.getAddress()); - return "origin_address=[" + formattedAddress + "]"; - } + LogEntryBuilder withRestOrTransportOrigin(TransportMessage message, ThreadContext threadContext) { + assert LOCAL_ORIGIN_FIELD_VALUE.equals(logEntry.get(ORIGIN_TYPE_FIELD_NAME)); // this is the default + final InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); + if (restAddress != null) { + logEntry.with(ORIGIN_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) + .with(ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(restAddress)); + } else { + final TransportAddress address = message.remoteAddress(); + if (address != null) { + logEntry.with(ORIGIN_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) + .with(ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address.address())); + } + } + // fall through to local_node default + return this; + } - protected static String originAttributes(ThreadContext threadContext, TransportMessage message, LocalNodeInfo localNodeInfo) { - return restOriginTag(threadContext).orElse(transportOriginTag(message).orElse(localNodeInfo.localOriginTag)); - } + LogEntryBuilder withRequestBody(RestRequest request) { + if (includeRequestBody) { + final String requestContent = restRequestContent(request); + if (Strings.hasLength(requestContent)) { + logEntry.with(REQUEST_BODY_FIELD_NAME, requestContent); + } + } + return this; + } - private String opaqueId() { - String opaqueId = threadContext.getHeader(Task.X_OPAQUE_ID); - if (opaqueId != null) { - return ", opaque_id=[" + opaqueId + "]"; - } else { - return ""; + LogEntryBuilder withOpaqueId(ThreadContext threadContext) { + final String opaqueId = threadContext.getHeader(Task.X_OPAQUE_ID); + if (opaqueId != null) { + logEntry.with(OPAQUE_ID_FIELD_NAME, opaqueId); + } + return this; } - } - private static Optional restOriginTag(ThreadContext threadContext) { - final InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); - if (restAddress == null) { - return Optional.empty(); + LogEntryBuilder withPrincipal(User user) { + logEntry.with(PRINCIPAL_FIELD_NAME, user.principal()); + if (user.isRunAs()) { + logEntry.with(PRINCIPAL_RUN_BY_FIELD_NAME, user.authenticatedUser().principal()); + } + return this; } - return Optional.of(new StringBuilder("origin_type=[rest], origin_address=[").append(NetworkAddress.format(restAddress.getAddress())) - .append("]") - .toString()); - } - private static Optional transportOriginTag(TransportMessage message) { - final TransportAddress address = message.remoteAddress(); - if (address == null) { - return Optional.empty(); + LogEntryBuilder withSubject(Authentication authentication) { + logEntry.with(PRINCIPAL_FIELD_NAME, authentication.getUser().principal()); + if (authentication.getUser().isRunAs()) { + logEntry.with(PRINCIPAL_REALM_FIELD_NAME, authentication.getLookedUpBy().getName()) + .with(PRINCIPAL_RUN_BY_FIELD_NAME, authentication.getUser().authenticatedUser().principal()) + .with(PRINCIPAL_RUN_BY_REALM_FIELD_NAME, authentication.getAuthenticatedBy().getName()); + } else { + logEntry.with(PRINCIPAL_REALM_FIELD_NAME, authentication.getAuthenticatedBy().getName()); + } + return this; + } + + LogEntryBuilder with(String key, String value) { + if (value != null) { + logEntry.with(key, value); + } + return this; } - return Optional.of( - new StringBuilder("origin_type=[transport], origin_address=[").append(NetworkAddress.format(address.address().getAddress())) - .append("]") - .toString()); + + LogEntryBuilder with(String key, String[] values) { + if (values != null) { + logEntry.with(key, toQuotedJsonArray(values)); + } + return this; + } + + StringMapMessage build() { + return logEntry; + } + + String toQuotedJsonArray(String[] values) { + assert values != null; + final StringBuilder stringBuilder = new StringBuilder(); + final JsonStringEncoder jsonStringEncoder = JsonStringEncoder.getInstance(); + stringBuilder.append("["); + for (final String value : values) { + if (value != null) { + if (stringBuilder.length() > 1) { + stringBuilder.append(","); + } + stringBuilder.append("\""); + jsonStringEncoder.quoteAsString(value, stringBuilder); + stringBuilder.append("\""); + } + } + stringBuilder.append("]"); + return stringBuilder.toString(); + } + } - static Optional indices(TransportMessage message) { + + private static Optional indices(TransportMessage message) { if (message instanceof IndicesRequest) { final String[] indices = ((IndicesRequest) message).indices(); - if ((indices != null) && (indices.length != 0)) { + if (indices != null) { return Optional.of(((IndicesRequest) message).indices()); } } return Optional.empty(); } - static String effectiveRealmName(Authentication authentication) { + private static String effectiveRealmName(Authentication authentication) { return authentication.getLookedUpBy() != null ? authentication.getLookedUpBy().getName() : authentication.getAuthenticatedBy().getName(); } - static String principal(User user) { - final StringBuilder builder = new StringBuilder("principal=["); - builder.append(user.principal()); - if (user.isRunAs()) { - builder.append("], run_by_principal=[").append(user.authenticatedUser().principal()); - } - return builder.append("]").toString(); - } - public static void registerSettings(List> settings) { - settings.add(HOST_ADDRESS_SETTING); - settings.add(HOST_NAME_SETTING); - settings.add(NODE_NAME_SETTING); + settings.add(EMIT_HOST_ADDRESS_SETTING); + settings.add(EMIT_HOST_NAME_SETTING); + settings.add(EMIT_NODE_NAME_SETTING); + settings.add(EMIT_NODE_ID_SETTING); settings.add(INCLUDE_EVENT_SETTINGS); settings.add(EXCLUDE_EVENT_SETTINGS); settings.add(INCLUDE_REQUEST_BODY); @@ -813,56 +944,53 @@ public void clusterChanged(ClusterChangedEvent event) { void updateLocalNodeInfo(DiscoveryNode newLocalNode) { // check if local node changed - final LocalNodeInfo localNodeInfo = this.localNodeInfo; - if ((localNodeInfo.localNode == null) || (localNodeInfo.localNode.equals(newLocalNode) == false)) { + final EntryCommonFields localNodeInfo = this.entryCommonFields; + if (localNodeInfo.localNode == null || localNodeInfo.localNode.equals(newLocalNode) == false) { // no need to synchronize, called only from the cluster state applier thread - this.localNodeInfo = new LocalNodeInfo(localNodeInfo.settings, newLocalNode); + this.entryCommonFields = this.entryCommonFields.withNewLocalNode(newLocalNode); } } - static class LocalNodeInfo { + static class EntryCommonFields { private final Settings settings; private final DiscoveryNode localNode; - final String prefix; - private final String localOriginTag; + final Map commonFields; - LocalNodeInfo(Settings settings, @Nullable DiscoveryNode newLocalNode) { + EntryCommonFields(Settings settings, @Nullable DiscoveryNode newLocalNode) { this.settings = settings; this.localNode = newLocalNode; - this.prefix = resolvePrefix(settings, newLocalNode); - this.localOriginTag = localOriginTag(newLocalNode); - } - - static String resolvePrefix(Settings settings, @Nullable DiscoveryNode localNode) { - final StringBuilder builder = new StringBuilder(); - if (HOST_ADDRESS_SETTING.get(settings)) { - final String address = localNode != null ? localNode.getHostAddress() : null; - if (address != null) { - builder.append("[").append(address).append("] "); + final Map commonFields = new HashMap<>(); + if (EMIT_NODE_NAME_SETTING.get(settings)) { + final String nodeName = Node.NODE_NAME_SETTING.get(settings); + if (Strings.hasLength(nodeName)) { + commonFields.put(NODE_NAME_FIELD_NAME, nodeName); } } - if (HOST_NAME_SETTING.get(settings)) { - final String hostName = localNode != null ? localNode.getHostName() : null; - if (hostName != null) { - builder.append("[").append(hostName).append("] "); + if (newLocalNode != null && newLocalNode.getAddress() != null) { + if (EMIT_HOST_ADDRESS_SETTING.get(settings)) { + commonFields.put(HOST_ADDRESS_FIELD_NAME, newLocalNode.getAddress().getAddress()); } - } - if (NODE_NAME_SETTING.get(settings)) { - final String name = Node.NODE_NAME_SETTING.get(settings); - if (name != null) { - builder.append("[").append(name).append("] "); + if (EMIT_HOST_NAME_SETTING.get(settings)) { + commonFields.put(HOST_NAME_FIELD_NAME, newLocalNode.getAddress().address().getHostString()); + } + if (EMIT_NODE_ID_SETTING.get(settings)) { + commonFields.put(NODE_ID_FIELD_NAME, newLocalNode.getId()); } + // the default origin is local + commonFields.put(ORIGIN_ADDRESS_FIELD_NAME, newLocalNode.getAddress().toString()); } - return builder.toString(); + // the default origin is local + commonFields.put(ORIGIN_TYPE_FIELD_NAME, LOCAL_ORIGIN_FIELD_VALUE); + this.commonFields = Collections.unmodifiableMap(commonFields); } - private static String localOriginTag(@Nullable DiscoveryNode localNode) { - if (localNode == null) { - return "origin_type=[local_node]"; - } - return new StringBuilder("origin_type=[local_node], origin_address=[").append(localNode.getHostAddress()) - .append("]") - .toString(); + EntryCommonFields withNewSettings(Settings newSettings) { + final Settings mergedSettings = Settings.builder().put(this.settings).put(newSettings, false).build(); + return new EntryCommonFields(mergedSettings, this.localNode); + } + + EntryCommonFields withNewLocalNode(DiscoveryNode newLocalNode) { + return new EntryCommonFields(this.settings, newLocalNode); } } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java index 56d5fec3f20d6..76482c5fa92a7 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/test/SecuritySettingsSource.java @@ -137,9 +137,10 @@ public Settings nodeSettings(int nodeOrdinal) { .put(XPackSettings.WATCHER_ENABLED.getKey(), false) .put(XPackSettings.MONITORING_ENABLED.getKey(), false) .put(XPackSettings.AUDIT_ENABLED.getKey(), randomBoolean()) - .put(LoggingAuditTrail.HOST_ADDRESS_SETTING.getKey(), randomBoolean()) - .put(LoggingAuditTrail.HOST_NAME_SETTING.getKey(), randomBoolean()) - .put(LoggingAuditTrail.NODE_NAME_SETTING.getKey(), randomBoolean()) + .put(LoggingAuditTrail.EMIT_HOST_ADDRESS_SETTING.getKey(), randomBoolean()) + .put(LoggingAuditTrail.EMIT_HOST_NAME_SETTING.getKey(), randomBoolean()) + .put(LoggingAuditTrail.EMIT_NODE_NAME_SETTING.getKey(), randomBoolean()) + .put(LoggingAuditTrail.EMIT_NODE_ID_SETTING.getKey(), randomBoolean()) .put("xpack.security.authc.realms.file.type", FileRealmSettings.TYPE) .put("xpack.security.authc.realms.file.order", 0) .put("xpack.security.authc.realms.index.type", NativeRealmSettings.TYPE) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java index 2f32ba9c537f1..e05f4620ccca2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java @@ -21,11 +21,11 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; -import java.util.regex.Pattern; - import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.startsWith; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -106,28 +106,45 @@ public void testInvalidFilterSettings() throws Exception { public void testDynamicHostSettings() { final boolean persistent = randomBoolean(); final Settings.Builder settingsBuilder = Settings.builder(); - settingsBuilder.put(LoggingAuditTrail.HOST_ADDRESS_SETTING.getKey(), true); - settingsBuilder.put(LoggingAuditTrail.HOST_NAME_SETTING.getKey(), true); - settingsBuilder.put(LoggingAuditTrail.NODE_NAME_SETTING.getKey(), true); + settingsBuilder.put(LoggingAuditTrail.EMIT_HOST_ADDRESS_SETTING.getKey(), true); + settingsBuilder.put(LoggingAuditTrail.EMIT_HOST_NAME_SETTING.getKey(), true); + settingsBuilder.put(LoggingAuditTrail.EMIT_NODE_NAME_SETTING.getKey(), true); + settingsBuilder.put(LoggingAuditTrail.EMIT_NODE_ID_SETTING.getKey(), true); updateSettings(settingsBuilder.build(), persistent); - final LoggingAuditTrail loggingAuditTrail = ((LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) + final LoggingAuditTrail loggingAuditTrail = (LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) .iterator() .next() .getAuditTrails() .iterator() - .next()); - assertTrue(Pattern.matches("\\[127\\.0\\.0\\.1\\] \\[127\\.0\\.0\\.1\\] \\[node_.*\\] ", loggingAuditTrail.localNodeInfo.prefix)); - settingsBuilder.put(LoggingAuditTrail.HOST_ADDRESS_SETTING.getKey(), false); + .next(); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.NODE_NAME_FIELD_NAME), startsWith("node_")); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_ID_FIELD_NAME), is(true)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.HOST_ADDRESS_FIELD_NAME), is("127.0.0.1")); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.HOST_NAME_FIELD_NAME), is("127.0.0.1")); + settingsBuilder.put(LoggingAuditTrail.EMIT_HOST_ADDRESS_SETTING.getKey(), false); + updateSettings(settingsBuilder.build(), persistent); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.NODE_NAME_FIELD_NAME), startsWith("node_")); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_ID_FIELD_NAME), is(true)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_ADDRESS_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.HOST_NAME_FIELD_NAME), is("127.0.0.1")); + settingsBuilder.put(LoggingAuditTrail.EMIT_HOST_NAME_SETTING.getKey(), false); updateSettings(settingsBuilder.build(), persistent); - assertTrue(Pattern.matches("\\[127\\.0\\.0\\.1\\] \\[node_.*\\] ", loggingAuditTrail.localNodeInfo.prefix)); - settingsBuilder.put(LoggingAuditTrail.HOST_ADDRESS_SETTING.getKey(), true); - settingsBuilder.put(LoggingAuditTrail.HOST_NAME_SETTING.getKey(), false); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.get(LoggingAuditTrail.NODE_NAME_FIELD_NAME), startsWith("node_")); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_ID_FIELD_NAME), is(true)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_ADDRESS_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_NAME_FIELD_NAME), is(false)); + settingsBuilder.put(LoggingAuditTrail.EMIT_NODE_NAME_SETTING.getKey(), false); updateSettings(settingsBuilder.build(), persistent); - assertTrue(Pattern.matches("\\[127\\.0\\.0\\.1\\] \\[node_.*\\] ", loggingAuditTrail.localNodeInfo.prefix)); - settingsBuilder.put(LoggingAuditTrail.HOST_NAME_SETTING.getKey(), true); - settingsBuilder.put(LoggingAuditTrail.NODE_NAME_SETTING.getKey(), false); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_NAME_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_ID_FIELD_NAME), is(true)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_ADDRESS_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_NAME_FIELD_NAME), is(false)); + settingsBuilder.put(LoggingAuditTrail.EMIT_NODE_ID_SETTING.getKey(), false); updateSettings(settingsBuilder.build(), persistent); - assertTrue(Pattern.matches("\\[127\\.0\\.0\\.1\\] \\[127\\.0\\.0\\.1\\] ", loggingAuditTrail.localNodeInfo.prefix)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_NAME_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.NODE_ID_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_ADDRESS_FIELD_NAME), is(false)); + assertThat(loggingAuditTrail.entryCommonFields.commonFields.containsKey(LoggingAuditTrail.HOST_NAME_FIELD_NAME), is(false)); } public void testDynamicRequestBodySettings() { @@ -136,12 +153,12 @@ public void testDynamicRequestBodySettings() { final Settings.Builder settingsBuilder = Settings.builder(); settingsBuilder.put(LoggingAuditTrail.INCLUDE_REQUEST_BODY.getKey(), enableRequestBody); updateSettings(settingsBuilder.build(), persistent); - final LoggingAuditTrail loggingAuditTrail = ((LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) + final LoggingAuditTrail loggingAuditTrail = (LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) .iterator() .next() .getAuditTrails() .iterator() - .next()); + .next(); assertEquals(enableRequestBody, loggingAuditTrail.includeRequestBody); settingsBuilder.put(LoggingAuditTrail.INCLUDE_REQUEST_BODY.getKey(), !enableRequestBody); updateSettings(settingsBuilder.build(), persistent); @@ -158,12 +175,12 @@ public void testDynamicEventsSettings() { settingsBuilder.putList(LoggingAuditTrail.INCLUDE_EVENT_SETTINGS.getKey(), includedEvents); settingsBuilder.putList(LoggingAuditTrail.EXCLUDE_EVENT_SETTINGS.getKey(), excludedEvents); updateSettings(settingsBuilder.build(), randomBoolean()); - final LoggingAuditTrail loggingAuditTrail = ((LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) + final LoggingAuditTrail loggingAuditTrail = (LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class) .iterator() .next() .getAuditTrails() .iterator() - .next()); + .next(); assertEquals(AuditLevel.parse(includedEvents, excludedEvents), loggingAuditTrail.events); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java index 4c9df8fd9d382..67f38c0e58159 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java @@ -59,9 +59,6 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { private Settings settings; private DiscoveryNode localNode; private ClusterService clusterService; - private ThreadContext threadContext; - private Logger logger; - List logOutput; @Before public void init() throws Exception { @@ -83,12 +80,11 @@ public void init() throws Exception { arg0.updateLocalNodeInfo(localNode); return null; }).when(clusterService).addListener(Mockito.isA(LoggingAuditTrail.class)); - threadContext = new ThreadContext(Settings.EMPTY); - logger = CapturingLogger.newCapturingLogger(Level.INFO); - logOutput = CapturingLogger.output(logger.getName(), Level.INFO); } public void testSingleCompletePolicyPredicate() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); // create complete filter policy final Settings.Builder settingsBuilder = Settings.builder().put(settings); // filter by username @@ -179,6 +175,8 @@ public void testSingleCompletePolicyPredicate() throws Exception { } public void testSingleCompleteWithEmptyFieldPolicyPredicate() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); // create complete filter policy final Settings.Builder settingsBuilder = Settings.builder().put(settings); // filter by username @@ -275,6 +273,8 @@ public void testSingleCompleteWithEmptyFieldPolicyPredicate() throws Exception { } public void testTwoPolicyPredicatesWithMissingFields() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); final Settings.Builder settingsBuilder = Settings.builder().put(settings); // first policy: realms and roles filters final List filteredRealms = randomNonEmptyListOfFilteredNames(); @@ -341,6 +341,8 @@ public void testTwoPolicyPredicatesWithMissingFields() throws Exception { } public void testUsersFilter() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); final List allFilteredUsers = new ArrayList<>(); final Settings.Builder settingsBuilder = Settings.builder().put(settings); for (int i = 0; i < randomIntBetween(1, 4); i++) { @@ -387,6 +389,7 @@ public void testUsersFilter() throws Exception { final MockToken unfilteredToken = new MockToken(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 4)); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied("_action", message); if (filterMissingUser) { @@ -623,6 +626,8 @@ public void testUsersFilter() throws Exception { } public void testRealmsFilter() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); final List allFilteredRealms = new ArrayList<>(); final Settings.Builder settingsBuilder = Settings.builder().put(settings); for (int i = 0; i < randomIntBetween(1, 4); i++) { @@ -659,6 +664,7 @@ public void testRealmsFilter() throws Exception { final MockToken authToken = new MockToken("token1"); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied("_action", message); if (filterMissingRealm) { @@ -908,6 +914,8 @@ public void testRealmsFilter() throws Exception { } public void testRolesFilter() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); final List> allFilteredRoles = new ArrayList<>(); final Settings.Builder settingsBuilder = Settings.builder().put(settings); for (int i = 0; i < randomIntBetween(1, 4); i++) { @@ -966,6 +974,7 @@ public void testRolesFilter() throws Exception { final MockToken authToken = new MockToken("token1"); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied("_action", message); if (filterMissingRoles) { @@ -1179,6 +1188,8 @@ public void testRolesFilter() throws Exception { } public void testIndicesFilter() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); final List> allFilteredIndices = new ArrayList<>(); final Settings.Builder settingsBuilder = Settings.builder().put(settings); for (int i = 0; i < randomIntBetween(1, 3); i++) { @@ -1236,6 +1247,7 @@ public void testIndicesFilter() throws Exception { final TransportMessage noIndexMessage = new MockMessage(threadContext); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); + final List logOutput = CapturingLogger.output(logger.getName(), Level.INFO); // anonymous accessDenied auditTrail.anonymousAccessDenied("_action", noIndexMessage); if (filterMissingIndices) { 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 1059e22abd663..6c302c9311f3d 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 @@ -7,6 +7,7 @@ import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.layout.PatternLayout; import org.elasticsearch.action.IndicesRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.node.DiscoveryNode; @@ -14,6 +15,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.network.NetworkAddress; import org.elasticsearch.common.settings.ClusterSettings; @@ -39,23 +41,29 @@ import org.elasticsearch.xpack.security.rest.RemoteHostHeader; import org.elasticsearch.xpack.security.transport.filter.IPFilter; import org.elasticsearch.xpack.security.transport.filter.SecurityIpFilterRule; +import org.junit.After; +import org.junit.AfterClass; import org.junit.Before; +import org.junit.BeforeClass; import org.mockito.stubbing.Answer; import java.io.IOException; +import java.io.InputStream; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; - -import static org.hamcrest.Matchers.equalTo; +import java.util.Properties; +import java.util.regex.Pattern; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.containsString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -113,17 +121,46 @@ protected String expectedMessage() { }; protected abstract boolean hasContent(); + protected abstract BytesReference content(); + protected abstract String expectedMessage(); } - private String prefix; + private static PatternLayout patternLayout; private Settings settings; private DiscoveryNode localNode; private ClusterService clusterService; private ThreadContext threadContext; private boolean includeRequestBody; - private String opaqueId; + private Map commonFields; + private Logger logger; + private LoggingAuditTrail auditTrail; + + @BeforeClass + public static void lookupPatternLayout() throws Exception { + final Properties properties = new Properties(); + try (InputStream configStream = LoggingAuditTrail.class.getClassLoader().getResourceAsStream("log4j2.properties")) { + properties.load(configStream); + } + // This is a minimal and brittle parsing of the security log4j2 config + // properties. If any of these fails, then surely the config file changed. In + // this case adjust the assertions! The goal of this assertion chain is to + // validate that the layout pattern we are testing with is indeed the one + // attached to the LoggingAuditTrail.class logger. + assertThat(properties.getProperty("logger.xpack_security_audit_logfile.name"), is(LoggingAuditTrail.class.getName())); + assertThat(properties.getProperty("logger.xpack_security_audit_logfile.appenderRef.audit_rolling.ref"), is("audit_rolling")); + assertThat(properties.getProperty("appender.audit_rolling.name"), is("audit_rolling")); + assertThat(properties.getProperty("appender.audit_rolling.layout.type"), is("PatternLayout")); + final String patternLayoutFormat = properties.getProperty("appender.audit_rolling.layout.pattern"); + assertThat(patternLayoutFormat, is(notNullValue())); + patternLayout = PatternLayout.newBuilder().withPattern(patternLayoutFormat).withCharset(StandardCharsets.UTF_8).build(); + } + + @AfterClass + public static void releasePatternLayout() { + patternLayout = null; + } @Before public void init() throws Exception { @@ -135,7 +172,7 @@ public void init() throws Exception { .put("xpack.security.audit.logfile.events.emit_request_body", includeRequestBody) .build(); localNode = mock(DiscoveryNode.class); - when(localNode.getHostAddress()).thenReturn(buildNewFakeTransportAddress().toString()); + when(localNode.getAddress()).thenReturn(buildNewFakeTransportAddress()); clusterService = mock(ClusterService.class); when(clusterService.localNode()).thenReturn(localNode); Mockito.doAnswer((Answer) invocation -> { @@ -145,597 +182,708 @@ public void init() throws Exception { }).when(clusterService).addListener(Mockito.isA(LoggingAuditTrail.class)); final ClusterSettings clusterSettings = mockClusterSettings(); when(clusterService.getClusterSettings()).thenReturn(clusterSettings); - prefix = LoggingAuditTrail.LocalNodeInfo.resolvePrefix(settings, localNode); + commonFields = new LoggingAuditTrail.EntryCommonFields(settings, localNode).commonFields; threadContext = new ThreadContext(Settings.EMPTY); if (randomBoolean()) { - String id = randomAlphaOfLength(10); - threadContext.putHeader(Task.X_OPAQUE_ID, id); - opaqueId = ", opaque_id=[" + id + "]"; - } else { - opaqueId = ""; + threadContext.putHeader(Task.X_OPAQUE_ID, randomAlphaOfLengthBetween(1, 4)); } + logger = CapturingLogger.newCapturingLogger(Level.INFO, patternLayout); + auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + } + + @After + public void clearLog() throws Exception { + CapturingLogger.output(logger.getName(), Level.INFO).clear(); } public void testAnonymousAccessDeniedTransport() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); + auditTrail.anonymousAccessDenied("_action", message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [anonymous_access_denied]\t" + origins + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [anonymous_access_denied]\t" + origins + - ", action=[_action], request=[MockMessage]" + opaqueId); - } + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action"); + indicesRequest(message, checkedFields, checkedArrayFields); + restOrTransportOrigin(message, threadContext, checkedFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "anonymous_access_denied").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "anonymous_access_denied") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.anonymousAccessDenied("_action", message); assertEmptyLog(logger); } public void testAnonymousAccessDeniedRest() throws Exception { - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200)); + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + auditTrail.anonymousAccessDenied(request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, prefix + "[rest] [anonymous_access_denied]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId + ", request_body=[" + expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, prefix + "[rest] [anonymous_access_denied]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId); - } + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .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_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, null); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "anonymous_access_denied").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "anonymous_access_denied") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.anonymousAccessDenied(request); assertEmptyLog(logger); } public void testAuthenticationFailed() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + final AuthenticationToken mockToken = new MockToken(); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - auditTrail.authenticationFailed(new MockToken(), "_action", message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_failed]\t" + origins + - ", principal=[_principal], action=[_action], indices=[" + indices(message) + - "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_failed]\t" + origins + - ", principal=[_principal], action=[_action], request=[MockMessage]" + opaqueId); - } + + auditTrail.authenticationFailed(mockToken, "_action", message); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_failed") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal()) + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed(new MockToken(), "_action", message); assertEmptyLog(logger); } public void testAuthenticationFailedNoToken() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); + auditTrail.authenticationFailed("_action", message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_failed]\t" + origins + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_failed]\t" + origins + - ", action=[_action], request=[MockMessage]" + opaqueId); - } + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_failed") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed("_action", message); assertEmptyLog(logger); } public void testAuthenticationFailedRest() throws Exception { - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200)); + final Map params = new HashMap<>(); + if (randomBoolean()) { + params.put("foo", "bar"); + } + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address, params); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.authenticationFailed(new MockToken(), request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, prefix + "[rest] [authentication_failed]\torigin_address=[" + - NetworkAddress.format(address) + "], principal=[_principal], uri=[_uri]" + opaqueId + ", request_body=[" + - expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, prefix + "[rest] [authentication_failed]\torigin_address=[" + - NetworkAddress.format(address) + "], principal=[_principal], uri=[_uri]" + opaqueId); - } + final AuthenticationToken mockToken = new MockToken(); + + auditTrail.authenticationFailed(mockToken, request); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_failed") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, null) + .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_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, params.isEmpty() ? null : "foo=bar"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed(new MockToken(), request); assertEmptyLog(logger); } public void testAuthenticationFailedRestNoToken() throws Exception { - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200)); + final Map params = new HashMap<>(); + if (randomBoolean()) { + params.put("bar", "baz"); + } + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address, params); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + auditTrail.authenticationFailed(request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, prefix + "[rest] [authentication_failed]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId + ", request_body=[" + expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, prefix + "[rest] [authentication_failed]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId); - } + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_failed") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, null) + .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_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, params.isEmpty() ? null : "bar=baz"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationFailed(request); assertEmptyLog(logger); } public void testAuthenticationFailedRealm() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + final AuthenticationToken mockToken = new MockToken(); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - auditTrail.authenticationFailed("_realm", new MockToken(), "_action", message); + final String realm = randomAlphaOfLengthBetween(1, 6); + auditTrail.authenticationFailed(realm, mockToken, "_action", message); assertEmptyLog(logger); // test enabled - settings = - Settings.builder().put(settings).put("xpack.security.audit.logfile.events.include", "realm_authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.include", "realm_authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - auditTrail.authenticationFailed("_realm", new MockToken(), "_action", message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [realm_authentication_failed]\trealm=[_realm], " + origins + - ", principal=[_principal], action=[_action], indices=[" + indices(message) + "], " + - "request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [realm_authentication_failed]\trealm=[_realm], " + origins + - ", principal=[_principal], action=[_action], request=[MockMessage]" + opaqueId); - } + auditTrail.authenticationFailed(realm, mockToken, "_action", message); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") + .put(LoggingAuditTrail.REALM_FIELD_NAME, realm) + .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, mockToken.principal()) + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); } public void testAuthenticationFailedRealmRest() throws Exception { - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200)); + final Map params = new HashMap<>(); + if (randomBoolean()) { + params.put("_param", "baz"); + } + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address, params); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.authenticationFailed("_realm", new MockToken(), request); + final AuthenticationToken mockToken = new MockToken(); + final String realm = randomAlphaOfLengthBetween(1, 6); + auditTrail.authenticationFailed(realm, mockToken, request); assertEmptyLog(logger); // test enabled - settings = - Settings.builder().put(settings).put("xpack.security.audit.logfile.events.include", "realm_authentication_failed").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.include", "realm_authentication_failed") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.authenticationFailed("_realm", new MockToken(), request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, prefix + "[rest] [realm_authentication_failed]\trealm=[_realm], origin_address=[" + - NetworkAddress.format(address) + "], principal=[_principal], uri=[_uri]" + opaqueId + ", request_body=[" + - expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, prefix + "[rest] [realm_authentication_failed]\trealm=[_realm], origin_address=[" + - NetworkAddress.format(address) + "], principal=[_principal], uri=[_uri]" + opaqueId); - } + auditTrail.authenticationFailed(realm, mockToken, request); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") + .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.PRINCIPAL_FIELD_NAME, mockToken.principal()) + .put(LoggingAuditTrail.ACTION_FIELD_NAME, null) + .put(LoggingAuditTrail.REQUEST_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, params.isEmpty() ? null : "_param=baz"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); } public void testAccessGranted() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final boolean runAs = randomBoolean(); - User user; - if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); - } else { - user = new User("_username", new String[]{"r1"}); - } - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.accessGranted(createAuthentication(user), "_action", message, new String[] { role }); - final String userInfo = (runAs ? "principal=[running as], realm=[lookRealm], run_by_principal=[_username], run_by_realm=[authRealm]" - : "principal=[_username], realm=[authRealm]") + ", roles=[" + role + "]"; - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + - ", action=[_action], request=[MockMessage]" + opaqueId); - } + final String[] roles = randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = createAuthentication(); + + auditTrail.accessGranted(authentication, "_action", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "access_granted") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + subject(authentication, checkedFields); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "access_granted").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "access_granted") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.accessGranted(createAuthentication(user), "_action", message, new String[] { role }); + auditTrail.accessGranted(authentication, "_action", message, roles); assertEmptyLog(logger); } public void testAccessGrantedInternalSystemAction() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.accessGranted(createAuthentication(SystemUser.INSTANCE), "internal:_action", message, new String[] { role }); + final String[] roles = randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = new Authentication(SystemUser.INSTANCE, new RealmRef("_reserved", "test", "foo"), null); + auditTrail.accessGranted(authentication, "internal:_action", message, roles); assertEmptyLog(logger); // test enabled - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.include", "system_access_granted").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.include", "system_access_granted") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - auditTrail.accessGranted(createAuthentication(SystemUser.INSTANCE), "internal:_action", message, new String[] { role }); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[" + - SystemUser.INSTANCE.principal() - + "], realm=[authRealm], roles=[" + role + "], action=[internal:_action], indices=[" + indices(message) - + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", principal=[" + - SystemUser.INSTANCE.principal() + "], realm=[authRealm], roles=[" + role - + "], action=[internal:_action], request=[MockMessage]" + opaqueId); - } + auditTrail.accessGranted(authentication, "internal:_action", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "access_granted") + .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, SystemUser.INSTANCE.principal()) + .put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, "_reserved") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "internal:_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); } public void testAccessGrantedInternalSystemActionNonSystemUser() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final boolean runAs = randomBoolean(); - User user; - if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); - } else { - user = new User("_username", new String[]{"r1"}); - } - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.accessGranted(createAuthentication(user), "internal:_action", message, new String[] { role }); - final String userInfo = (runAs ? "principal=[running as], realm=[lookRealm], run_by_principal=[_username], run_by_realm=[authRealm]" - : "principal=[_username], realm=[authRealm]") + ", roles=[" + role + "]"; - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + - ", action=[internal:_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_granted]\t" + origins + ", " + userInfo + - ", action=[internal:_action], request=[MockMessage]" + opaqueId); - } + final String[] roles = randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = createAuthentication(); + + auditTrail.accessGranted(authentication, "internal:_action", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "access_granted") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "internal:_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + subject(authentication, checkedFields); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "access_granted").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "access_granted") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.accessGranted(createAuthentication(user), "internal:_action", message, new String[] { role }); + auditTrail.accessGranted(authentication, "internal:_action", message, roles); assertEmptyLog(logger); } public void testAccessDenied() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final boolean runAs = randomBoolean(); - User user; - if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); - } else { - user = new User("_username", new String[]{"r1"}); - } - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.accessDenied(createAuthentication(user), "_action", message, new String[] { role }); - final String userInfo = (runAs ? "principal=[running as], realm=[lookRealm], run_by_principal=[_username], run_by_realm=[authRealm]" - : "principal=[_username], realm=[authRealm]") + ", roles=[" + role + "]"; - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [access_denied]\t" + origins + ", " + userInfo + - ", action=[_action], request=[MockMessage]" + opaqueId); - } + final String[] roles = randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = createAuthentication(); + + auditTrail.accessDenied(authentication, "_action/bar", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "access_denied") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action/bar") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + subject(authentication, checkedFields); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "access_denied").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "access_denied") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.accessDenied(createAuthentication(user), "_action", message, new String[] { role }); + auditTrail.accessDenied(authentication, "_action", message, roles); assertEmptyLog(logger); } public void testTamperedRequestRest() throws Exception { - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200)); + final Map params = new HashMap<>(); + if (randomBoolean()) { + params.put("_param", "baz"); + } + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address, params); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.tamperedRequest(request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, prefix + "[rest] [tampered_request]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId + ", request_body=[" + expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, prefix + "[rest] [tampered_request]\torigin_address=[" + - NetworkAddress.format(address) + "], uri=[_uri]" + opaqueId); - } + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .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_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, params.isEmpty() ? null : "_param=baz"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "tampered_request").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "tampered_request") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.tamperedRequest(request); assertEmptyLog(logger); } public void testTamperedRequest() throws Exception { - final String action = "_action"; final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - auditTrail.tamperedRequest(action, message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [tampered_request]\t" + origins + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [tampered_request]\t" + origins + - ", action=[_action], request=[MockMessage]" + opaqueId); - } - // test disabled + auditTrail.tamperedRequest("_action", message); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "tampered_request") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); + // test disabled + CapturingLogger.output(logger.getName(), Level.INFO).clear(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "tampered_request") + .build(); + auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + auditTrail.tamperedRequest("_action", message); + assertEmptyLog(logger); } public void testTamperedRequestWithUser() throws Exception { - final String action = "_action"; + final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); final boolean runAs = randomBoolean(); - User user; + final User user; if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); + user = new User("running_as", new String[] { "r2" }, new User("_username", new String[] { "r1" })); } else { - user = new User("_username", new String[]{"r1"}); + user = new User("_username", new String[] { "r1" }); } - final String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]"; - final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - auditTrail.tamperedRequest(user, action, message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [tampered_request]\t" + origins + ", " + userInfo + - ", action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); + + auditTrail.tamperedRequest(user, "_action", message); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "tampered_request") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + if (runAs) { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "running_as"); + checkedFields.put(LoggingAuditTrail.PRINCIPAL_RUN_BY_FIELD_NAME, "_username"); } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [tampered_request]\t" + origins + ", " + userInfo + - ", action=[_action], request=[MockMessage]" + opaqueId); + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "_username"); } + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "tampered_request").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "tampered_request") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.tamperedRequest(user, action, message); + auditTrail.tamperedRequest(user, "_action", message); assertEmptyLog(logger); } public void testConnectionDenied() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final InetAddress inetAddress = InetAddress.getLoopbackAddress(); final SecurityIpFilterRule rule = new SecurityIpFilterRule(false, "_all"); - auditTrail.connectionDenied(inetAddress, "default", rule); - assertMsg(logger, Level.INFO, String.format(Locale.ROOT, prefix + - "[ip_filter] [connection_denied]\torigin_address=[%s], transport_profile=[%s], rule=[deny %s]" + opaqueId, - NetworkAddress.format(inetAddress), "default", "_all")); + final String profile = randomAlphaOfLengthBetween(1, 6); + + auditTrail.connectionDenied(inetAddress, profile, rule); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.IP_FILTER_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "connection_denied") + .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.IP_FILTER_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(inetAddress)) + .put(LoggingAuditTrail.TRANSPORT_PROFILE_FIELD_NAME, profile) + .put(LoggingAuditTrail.RULE_FIELD_NAME, "deny _all"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "connection_denied").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "connection_denied") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.connectionDenied(inetAddress, "default", rule); + auditTrail.connectionDenied(inetAddress, profile, rule); assertEmptyLog(logger); } public void testConnectionGranted() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final InetAddress inetAddress = InetAddress.getLoopbackAddress(); final SecurityIpFilterRule rule = IPFilter.DEFAULT_PROFILE_ACCEPT_ALL; - auditTrail.connectionGranted(inetAddress, "default", rule); + final String profile = randomAlphaOfLengthBetween(1, 6); + + auditTrail.connectionGranted(inetAddress, profile, rule); assertEmptyLog(logger); // test enabled - CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.include", "connection_granted").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.include", "connection_granted") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.connectionGranted(inetAddress, "default", rule); - assertMsg(logger, Level.INFO, String.format(Locale.ROOT, prefix + "[ip_filter] [connection_granted]\torigin_address=[%s], " + - "transport_profile=[default], rule=[allow default:accept_all]" + opaqueId, - NetworkAddress.format(inetAddress))); + auditTrail.connectionGranted(inetAddress, profile, rule); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.IP_FILTER_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "connection_granted") + .put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.IP_FILTER_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(inetAddress)) + .put(LoggingAuditTrail.TRANSPORT_PROFILE_FIELD_NAME, profile) + .put(LoggingAuditTrail.RULE_FIELD_NAME, "allow default:accept_all"); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); } public void testRunAsGranted() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.runAsGranted(createAuthentication(user), "_action", message, new String[] { role }); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, - prefix + "[transport] [run_as_granted]\t" + origins - + ", principal=[_username], realm=[authRealm], run_as_principal=[running as], run_as_realm=[lookRealm], roles=[" - + role + "], action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, - prefix + "[transport] [run_as_granted]\t" + origins - + ", principal=[_username], realm=[authRealm], run_as_principal=[running as], run_as_realm=[lookRealm], roles=[" - + role + "], action=[_action], request=[MockMessage]" + opaqueId); - } + final String[] roles = randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = new Authentication( + new User("running as", new String[] { "r2" }, new User("_username", new String[] { "r1" })), + new RealmRef("authRealm", "test", "foo"), + new RealmRef("lookRealm", "up", "by")); + + auditTrail.runAsGranted(authentication, "_action", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "run_as_granted") + .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "_username") + .put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, "authRealm") + .put(LoggingAuditTrail.PRINCIPAL_RUN_AS_FIELD_NAME, "running as") + .put(LoggingAuditTrail.PRINCIPAL_RUN_AS_REALM_FIELD_NAME, "lookRealm") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "run_as_granted").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "run_as_granted") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.runAsGranted(createAuthentication(user), "_action", message, new String[] { role }); + auditTrail.runAsGranted(authentication, "_action", message, roles); assertEmptyLog(logger); } public void testRunAsDenied() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final User user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); - final String role = randomAlphaOfLengthBetween(1, 6); - auditTrail.runAsDenied(createAuthentication(user), "_action", message, new String[] { role }); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, - prefix + "[transport] [run_as_denied]\t" + origins - + ", principal=[_username], realm=[authRealm], run_as_principal=[running as], run_as_realm=[lookRealm], roles=[" - + role + "], action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, - prefix + "[transport] [run_as_denied]\t" + origins - + ", principal=[_username], realm=[authRealm], run_as_principal=[running as], run_as_realm=[lookRealm], roles=[" - + role + "], action=[_action], request=[MockMessage]" + opaqueId); - } + final String[] roles = randomArray(0, 4, String[]::new, () -> randomAlphaOfLengthBetween(1, 4)); + final Authentication authentication = new Authentication( + new User("running as", new String[] { "r2" }, new User("_username", new String[] { "r1" })), + new RealmRef("authRealm", "test", "foo"), + new RealmRef("lookRealm", "up", "by")); + + auditTrail.runAsDenied(authentication, "_action", message, roles); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "run_as_denied") + .put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "_username") + .put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, "authRealm") + .put(LoggingAuditTrail.PRINCIPAL_RUN_AS_FIELD_NAME, "running as") + .put(LoggingAuditTrail.PRINCIPAL_RUN_AS_REALM_FIELD_NAME, "lookRealm") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME, roles); + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); // test disabled CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(settings).put("xpack.security.audit.logfile.events.exclude", "run_as_denied").build(); + settings = Settings.builder() + .put(settings) + .put("xpack.security.audit.logfile.events.exclude", "run_as_denied") + .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - auditTrail.runAsDenied(createAuthentication(user), "_action", message, new String[] { role }); + auditTrail.runAsDenied(authentication, "_action", message, roles); assertEmptyLog(logger); } - public void testOriginAttributes() throws Exception { - final MockMessage message = new MockMessage(threadContext); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); - final String text = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); - if (restAddress != null) { - assertThat(text, equalTo("origin_type=[rest], origin_address=[" + - NetworkAddress.format(restAddress.getAddress()) + "]")); - return; - } - final TransportAddress address = message.remoteAddress(); - if (address == null) { - assertThat(text, equalTo("origin_type=[local_node], origin_address=[" + localNode.getHostAddress() + "]")); - return; - } - - assertThat(text, equalTo("origin_type=[transport], origin_address=[" + - NetworkAddress.format(address.address().getAddress()) + "]")); - } - public void testAuthenticationSuccessRest() throws Exception { final Map params = new HashMap<>(); - params.put("foo", "bar"); - final InetAddress address = forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"); - final Tuple tuple = prepareRestContent("_uri", new InetSocketAddress(address, 9200), params); + if (randomBoolean()) { + params.put("foo", "bar"); + params.put("evac", "true"); + } + final InetSocketAddress address = new InetSocketAddress(forge("_hostname", randomBoolean() ? "127.0.0.1" : "::1"), + randomIntBetween(9200, 9300)); + final Tuple tuple = prepareRestContent("_uri", address, params); final String expectedMessage = tuple.v1().expectedMessage(); final RestRequest request = tuple.v2(); - final boolean runAs = randomBoolean(); - User user; - if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); + final String realm = randomAlphaOfLengthBetween(1, 6); + final User user; + if (randomBoolean()) { + user = new User("running as", new String[] { "r2" }, new User("_username", new String[] { "r1" })); } else { user = new User("_username", new String[] { "r1" }); } - final String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]"; - final String realm = "_realm"; - Settings settings = Settings.builder().put(this.settings) - .put("xpack.security.audit.logfile.events.include", "authentication_success") - .build(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); + // event by default disabled auditTrail.authenticationSuccess(realm, user, request); - if (includeRequestBody) { - assertMsg(logger, Level.INFO, - prefix + "[rest] [authentication_success]\t" + userInfo + ", realm=[_realm], uri=[_uri], params=[" + params - + "]" + opaqueId + ", request_body=[" + expectedMessage + "]"); - } else { - assertMsg(logger, Level.INFO, - prefix + "[rest] [authentication_success]\t" + userInfo + ", realm=[_realm], uri=[_uri], params=[" + params - + "]" + opaqueId); - } + assertEmptyLog(logger); - // test disabled - CapturingLogger.output(logger.getName(), Level.INFO).clear(); - settings = Settings.builder().put(this.settings).put("xpack.security.audit.logfile.events.exclude", "authentication_success") + settings = Settings.builder() + .put(this.settings) + .put("xpack.security.audit.logfile.events.include", "authentication_success") .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationSuccess(realm, user, request); - assertEmptyLog(logger); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_success") + .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_BODY_FIELD_NAME, + includeRequestBody && Strings.hasLength(expectedMessage) ? expectedMessage : null) + .put(LoggingAuditTrail.URL_PATH_FIELD_NAME, "_uri") + .put(LoggingAuditTrail.URL_QUERY_FIELD_NAME, params.isEmpty() ? null : "foo=bar&evac=true"); + if (user.isRunAs()) { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "running as"); + checkedFields.put(LoggingAuditTrail.PRINCIPAL_RUN_BY_FIELD_NAME, "_username"); + } else { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "_username"); + } + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap()); } public void testAuthenticationSuccessTransport() throws Exception { - Settings settings = Settings.builder().put(this.settings) - .put("xpack.security.audit.logfile.events.include", "authentication_success").build(); - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - LoggingAuditTrail auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final TransportMessage message = randomBoolean() ? new MockMessage(threadContext) : new MockIndicesRequest(threadContext); - final String origins = LoggingAuditTrail.originAttributes(threadContext, message, auditTrail.localNodeInfo); - final boolean runAs = randomBoolean(); - User user; - if (runAs) { - user = new User("running as", new String[]{"r2"}, new User("_username", new String[] {"r1"})); + final User user; + if (randomBoolean()) { + user = new User("running as", new String[] { "r2" }, new User("_username", new String[] { "r1" })); } else { user = new User("_username", new String[] { "r1" }); } - final String userInfo = runAs ? "principal=[running as], run_by_principal=[_username]" : "principal=[_username]"; - final String realm = "_realm"; + final String realm = randomAlphaOfLengthBetween(1, 6); + + // event by default disabled auditTrail.authenticationSuccess(realm, user, "_action", message); - if (message instanceof IndicesRequest) { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_success]\t" + origins + ", " + userInfo - + ", realm=[_realm], action=[_action], indices=[" + indices(message) + "], request=[MockIndicesRequest]" + opaqueId); - } else { - assertMsg(logger, Level.INFO, prefix + "[transport] [authentication_success]\t" + origins + ", " + userInfo - + ", realm=[_realm], action=[_action], request=[MockMessage]" + opaqueId); - } + assertEmptyLog(logger); - // test disabled - CapturingLogger.output(logger.getName(), Level.INFO).clear(); settings = Settings.builder() .put(this.settings) - .put("xpack.security.audit.logfile.events.exclude", "authentication_success") + .put("xpack.security.audit.logfile.events.include", "authentication_success") .build(); auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); auditTrail.authenticationSuccess(realm, user, "_action", message); - assertEmptyLog(logger); + final MapBuilder checkedFields = new MapBuilder<>(commonFields); + final MapBuilder checkedArrayFields = new MapBuilder<>(); + checkedFields.put(LoggingAuditTrail.EVENT_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.EVENT_ACTION_FIELD_NAME, "authentication_success") + .put(LoggingAuditTrail.ACTION_FIELD_NAME, "_action") + .put(LoggingAuditTrail.REALM_FIELD_NAME, realm) + .put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, message.getClass().getSimpleName()); + if (user.isRunAs()) { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "running as"); + checkedFields.put(LoggingAuditTrail.PRINCIPAL_RUN_BY_FIELD_NAME, "_username"); + } else { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, "_username"); + } + restOrTransportOrigin(message, threadContext, checkedFields); + indicesRequest(message, checkedFields, checkedArrayFields); + opaqueId(threadContext, checkedFields); + assertMsg(logger, checkedFields.immutableMap(), checkedArrayFields.immutableMap()); } public void testRequestsWithoutIndices() throws Exception { - final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - final Settings allEventsSettings = Settings.builder() + settings = Settings.builder() .put(settings) .put("xpack.security.audit.logfile.events.include", "_all") .build(); - final LoggingAuditTrail auditTrail = new LoggingAuditTrail(allEventsSettings, clusterService, logger, threadContext); + auditTrail = new LoggingAuditTrail(settings, clusterService, logger, threadContext); final User user = new User("_username", new String[] { "r1" }); final String role = randomAlphaOfLengthBetween(1, 6); final String realm = randomAlphaOfLengthBetween(1, 6); @@ -748,48 +896,93 @@ public void testRequestsWithoutIndices() throws Exception { for (final TransportMessage message : messages) { auditTrail.anonymousAccessDenied("_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.authenticationFailed(new MockToken(), "_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.authenticationFailed("_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.authenticationFailed(realm, new MockToken(), "_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); - auditTrail.accessGranted(createAuthentication(user), "_action", message, new String[] { role }); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); + auditTrail.accessGranted(createAuthentication(), "_action", message, new String[] { role }); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); - auditTrail.accessDenied(createAuthentication(user), "_action", message, new String[] { role }); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); + auditTrail.accessDenied(createAuthentication(), "_action", message, new String[] { role }); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.tamperedRequest("_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.tamperedRequest(user, "_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); - auditTrail.runAsGranted(createAuthentication(user), "_action", message, new String[] { role }); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); + auditTrail.runAsGranted(createAuthentication(), "_action", message, new String[] { role }); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); - auditTrail.runAsDenied(createAuthentication(user), "_action", message, new String[] { role }); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); + auditTrail.runAsDenied(createAuthentication(), "_action", message, new String[] { role }); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); auditTrail.authenticationSuccess(realm, user, "_action", message); assertThat(output.size(), is(logEntriesCount++)); - assertThat(output.get(logEntriesCount - 2), not(containsString("indices=["))); + assertThat(output.get(logEntriesCount - 2), not(containsString("indices="))); } } - private void assertMsg(Logger logger, Level level, String message) { - final List output = CapturingLogger.output(logger.getName(), level); - assertThat(output.size(), is(1)); - assertThat(output.get(0), equalTo(message)); + private void assertMsg(Logger logger, Map checkFields) { + assertMsg(logger, checkFields, Collections.emptyMap()); + } + + private void assertMsg(Logger logger, Map checkFields, Map checkArrayFields) { + final List output = CapturingLogger.output(logger.getName(), Level.INFO); + assertThat("Exactly one logEntry expected. Found: " + output.size(), output.size(), is(1)); + if (checkFields == null) { + // only check msg existence + return; + } + String logLine = output.get(0); + // check each field + for (final Map.Entry checkField : checkFields.entrySet()) { + if (null == checkField.getValue()) { + // null checkField means that the field does not exist + assertThat("Field: " + checkField.getKey() + " should be missing.", + logLine.contains(Pattern.quote("\"" + checkField.getKey() + "\":")), is(false)); + } else { + final String quotedValue = "\"" + checkField.getValue().replaceAll("\"", "\\\\\"") + "\""; + final Pattern logEntryFieldPattern = Pattern.compile(Pattern.quote("\"" + checkField.getKey() + "\":" + quotedValue)); + assertThat("Field " + checkField.getKey() + " value mismatch. Expected " + quotedValue, + logEntryFieldPattern.matcher(logLine).find(), is(true)); + // remove checked field + logLine = logEntryFieldPattern.matcher(logLine).replaceFirst(""); + } + } + for (final Map.Entry checkArrayField : checkArrayFields.entrySet()) { + if (null == checkArrayField.getValue()) { + // null checkField means that the field does not exist + assertThat("Field: " + checkArrayField.getKey() + " should be missing.", + logLine.contains(Pattern.quote("\"" + checkArrayField.getKey() + "\":")), is(false)); + } else { + final String quotedValue = "[" + Arrays.asList(checkArrayField.getValue()) + .stream() + .filter(s -> s != null) + .map(s -> "\"" + s.replaceAll("\"", "\\\\\"") + "\"") + .reduce((x, y) -> x + "," + y) + .orElse("") + "]"; + final Pattern logEntryFieldPattern = Pattern.compile(Pattern.quote("\"" + checkArrayField.getKey() + "\":" + quotedValue)); + assertThat("Field " + checkArrayField.getKey() + " value mismatch. Expected " + quotedValue, + logEntryFieldPattern.matcher(logLine).find(), is(true)); + // remove checked field + logLine = logEntryFieldPattern.matcher(logLine).replaceFirst(""); + } + } + logLine = logLine.replaceFirst("\"@timestamp\":\"[^\"]*\"", "").replaceAll("[{},]", ""); + // check no extra fields + assertThat("Log event has extra unexpected content: " + logLine, Strings.hasText(logLine), is(false)); } private void assertEmptyLog(Logger logger) { - assertThat(CapturingLogger.isEmpty(logger.getName()), is(true)); + assertThat("Logger is not empty", CapturingLogger.isEmpty(logger.getName()), is(true)); } protected Tuple prepareRestContent(String uri, InetSocketAddress remoteAddress) { @@ -802,7 +995,18 @@ private Tuple prepareRestContent(String uri, InetSocke if (content.hasContent()) { builder.withContent(content.content(), XContentType.JSON); } - builder.withPath(uri); + if (params.isEmpty()) { + builder.withPath(uri); + } else { + final StringBuilder queryString = new StringBuilder("?"); + for (final Map.Entry entry : params.entrySet()) { + if (queryString.length() > 1) { + queryString.append('&'); + } + queryString.append(entry.getKey() + "=" + entry.getValue()); + } + builder.withPath(uri + queryString.toString()); + } builder.withRemoteAddress(remoteAddress); builder.withParams(params); return new Tuple<>(content, builder.build()); @@ -814,12 +1018,16 @@ protected static InetAddress forge(String hostname, String address) throws IOExc return InetAddress.getByAddress(hostname, bytes); } - private static String indices(TransportMessage message) { - return Strings.arrayToCommaDelimitedString(((IndicesRequest) message).indices()); - } - - private static Authentication createAuthentication(User user) { - final RealmRef lookedUpBy = user.authenticatedUser() == user ? null : new RealmRef("lookRealm", "up", "by"); + private static Authentication createAuthentication() { + final RealmRef lookedUpBy; + final User user; + if (randomBoolean()) { + user = new User("running_as", new String[] { "r2" }, new User("_username", new String[] { "r1" })); + lookedUpBy = new RealmRef("lookRealm", "up", "by"); + } else { + user = new User("_username", new String[] { "r1" }); + lookedUpBy = null; + } return new Authentication(user, new RealmRef("authRealm", "test", "foo"), lookedUpBy); } @@ -849,7 +1057,8 @@ static class MockMessage extends TransportMessage { static class MockIndicesRequest extends org.elasticsearch.action.MockIndicesRequest { MockIndicesRequest(ThreadContext threadContext) throws IOException { - super(IndicesOptions.strictExpandOpenAndForbidClosed(), "idx1", "idx2"); + super(IndicesOptions.strictExpandOpenAndForbidClosed(), + randomArray(0, 4, String[]::new, () -> randomBoolean() ? null : randomAlphaOfLengthBetween(1, 4))); if (randomBoolean()) { remoteAddress(buildNewFakeTransportAddress()); } @@ -882,4 +1091,50 @@ public void clearCredentials() { } } + private static void restOrTransportOrigin(TransportMessage message, ThreadContext threadContext, + MapBuilder checkedFields) { + final InetSocketAddress restAddress = RemoteHostHeader.restRemoteAddress(threadContext); + if (restAddress != null) { + checkedFields.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.REST_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(restAddress)); + } else { + final TransportAddress address = message.remoteAddress(); + if (address != null) { + checkedFields.put(LoggingAuditTrail.ORIGIN_TYPE_FIELD_NAME, LoggingAuditTrail.TRANSPORT_ORIGIN_FIELD_VALUE) + .put(LoggingAuditTrail.ORIGIN_ADDRESS_FIELD_NAME, NetworkAddress.format(address.address())); + } + } + } + + private static void subject(Authentication authentication, MapBuilder checkedFields) { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_FIELD_NAME, authentication.getUser().principal()); + if (authentication.getUser().isRunAs()) { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, authentication.getLookedUpBy().getName()) + .put(LoggingAuditTrail.PRINCIPAL_RUN_BY_FIELD_NAME, authentication.getUser().authenticatedUser().principal()) + .put(LoggingAuditTrail.PRINCIPAL_RUN_BY_REALM_FIELD_NAME, authentication.getAuthenticatedBy().getName()); + } else { + checkedFields.put(LoggingAuditTrail.PRINCIPAL_REALM_FIELD_NAME, authentication.getAuthenticatedBy().getName()); + } + } + + private static void opaqueId(ThreadContext threadContext, MapBuilder checkedFields) { + final String opaqueId = threadContext.getHeader(Task.X_OPAQUE_ID); + if (opaqueId != null) { + checkedFields.put(LoggingAuditTrail.OPAQUE_ID_FIELD_NAME, opaqueId); + } else { + checkedFields.put(LoggingAuditTrail.OPAQUE_ID_FIELD_NAME, null); + } + } + + private static void indicesRequest(TransportMessage message, MapBuilder checkedFields, + MapBuilder checkedArrayFields) { + if (message instanceof IndicesRequest) { + checkedFields.put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, MockIndicesRequest.class.getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.INDICES_FIELD_NAME, ((IndicesRequest) message).indices()); + } else { + checkedFields.put(LoggingAuditTrail.REQUEST_NAME_FIELD_NAME, MockMessage.class.getSimpleName()); + checkedArrayFields.put(LoggingAuditTrail.INDICES_FIELD_NAME, null); + } + } + } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java index 739952af63ee7..3acb4888d7834 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserPasswdStoreTests.java @@ -185,7 +185,7 @@ public void testParseFile() throws Exception { public void testParseFile_Empty() throws Exception { Path empty = createTempFile(); - Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG); + Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG, null); Map users = FileUserPasswdStore.parseFile(empty, logger, Settings.EMPTY); assertThat(users.isEmpty(), is(true)); List events = CapturingLogger.output(logger.getName(), Level.DEBUG); @@ -195,7 +195,7 @@ public void testParseFile_Empty() throws Exception { public void testParseFile_WhenFileDoesNotExist() throws Exception { Path file = createTempDir().resolve(randomAlphaOfLength(10)); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map users = FileUserPasswdStore.parseFile(file, logger, Settings.EMPTY); assertThat(users, nullValue()); users = FileUserPasswdStore.parseFileLenient(file, logger, Settings.EMPTY); @@ -207,7 +207,7 @@ public void testParseFile_WhenCannotReadFile() throws Exception { Path file = createTempFile(); // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); try { FileUserPasswdStore.parseFile(file, logger, Settings.EMPTY); fail("expected a parse failure"); @@ -228,7 +228,7 @@ public void testParseFileLenient_WhenCannotReadFile() throws Exception { Path file = createTempFile(); // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map users = FileUserPasswdStore.parseFileLenient(file, logger, Settings.EMPTY); assertThat(users, notNullValue()); assertThat(users.isEmpty(), is(true)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserRolesStoreTests.java index f987f4df5724f..8e4011b1159d5 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/file/FileUserRolesStoreTests.java @@ -175,7 +175,7 @@ public void testParseFile() throws Exception { public void testParseFileEmpty() throws Exception { Path empty = createTempFile(); - Logger log = CapturingLogger.newCapturingLogger(Level.DEBUG); + Logger log = CapturingLogger.newCapturingLogger(Level.DEBUG, null); FileUserRolesStore.parseFile(empty, log); List events = CapturingLogger.output(log.getName(), Level.DEBUG); assertThat(events.size(), is(1)); @@ -184,7 +184,7 @@ public void testParseFileEmpty() throws Exception { public void testParseFileWhenFileDoesNotExist() throws Exception { Path file = createTempDir().resolve(randomAlphaOfLength(10)); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map usersRoles = FileUserRolesStore.parseFile(file, logger); assertThat(usersRoles, nullValue()); usersRoles = FileUserRolesStore.parseFileLenient(file, logger); @@ -199,7 +199,7 @@ public void testParseFileWhenCannotReadFile() throws Exception { // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, lines, StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG); + Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG, null); try { FileUserRolesStore.parseFile(file, logger); fail("expected a parse failure"); @@ -256,7 +256,7 @@ public void testParseFileLenientWhenCannotReadFile() throws Exception { // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, lines, StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG); + Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG, null); Map usersRoles = FileUserRolesStore.parseFileLenient(file, logger); assertThat(usersRoles, notNullValue()); assertThat(usersRoles.isEmpty(), is(true)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java index f6d18b7cfc106..263c5ee4929b4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/support/DnRoleMapperTests.java @@ -199,7 +199,7 @@ public void testAddNullListener() throws Exception { public void testParseFile() throws Exception { Path file = getDataPath("role_mapping.yml"); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map> mappings = DnRoleMapper.parseFile(file, logger, "_type", "_name", false); assertThat(mappings, notNullValue()); assertThat(mappings.size(), is(3)); @@ -229,18 +229,19 @@ public void testParseFile() throws Exception { public void testParseFile_Empty() throws Exception { Path file = createTempDir().resolve("foo.yaml"); Files.createFile(file); - Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG); + Logger logger = CapturingLogger.newCapturingLogger(Level.DEBUG, null); Map> mappings = DnRoleMapper.parseFile(file, logger, "_type", "_name", false); assertThat(mappings, notNullValue()); assertThat(mappings.isEmpty(), is(true)); List events = CapturingLogger.output(logger.getName(), Level.DEBUG); assertThat(events.size(), is(1)); assertThat(events.get(0), containsString("[0] role mappings found")); + events.clear(); } public void testParseFile_WhenFileDoesNotExist() throws Exception { Path file = createTempDir().resolve(randomAlphaOfLength(10)); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map> mappings = DnRoleMapper.parseFile(file, logger, "_type", "_name", false); assertThat(mappings, notNullValue()); assertThat(mappings.isEmpty(), is(true)); @@ -257,7 +258,7 @@ public void testParseFile_WhenCannotReadFile() throws Exception { Path file = createTempFile("", ".yml"); // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); try { DnRoleMapper.parseFile(file, logger, "_type", "_name", false); fail("expected a parse failure"); @@ -270,13 +271,14 @@ public void testParseFileLenient_WhenCannotReadFile() throws Exception { Path file = createTempFile("", ".yml"); // writing in utf_16 should cause a parsing error as we try to read the file in utf_8 Files.write(file, Collections.singletonList("aldlfkjldjdflkjd"), StandardCharsets.UTF_16); - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); Map> mappings = DnRoleMapper.parseFileLenient(file, logger, "_type", "_name"); assertThat(mappings, notNullValue()); assertThat(mappings.isEmpty(), is(true)); List events = CapturingLogger.output(logger.getName(), Level.ERROR); assertThat(events.size(), is(1)); assertThat(events.get(0), containsString("failed to parse role mappings file")); + events.clear(); } public void testYaml() throws Exception { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java index 1e2428e77791b..5cb93b898ba52 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java @@ -237,7 +237,9 @@ public void testParseFile() throws Exception { public void testParseFileWithFLSAndDLSDisabled() throws Exception { Path path = getDataPath("roles.yml"); - Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR); + Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR, null); + List events = CapturingLogger.output(logger.getName(), Level.ERROR); + events.clear(); Map roles = FileRolesStore.parseFile(path, logger, Settings.builder() .put(XPackSettings.DLS_FLS_ENABLED.getKey(), false) .build(), new XPackLicenseState(Settings.EMPTY)); @@ -247,7 +249,6 @@ public void testParseFileWithFLSAndDLSDisabled() throws Exception { assertThat(roles.get("role_query"), nullValue()); assertThat(roles.get("role_query_fields"), nullValue()); - List events = CapturingLogger.output(logger.getName(), Level.ERROR); assertThat(events, hasSize(3)); assertThat( events.get(0), @@ -263,7 +264,9 @@ public void testParseFileWithFLSAndDLSDisabled() throws Exception { public void testParseFileWithFLSAndDLSUnlicensed() throws Exception { Path path = getDataPath("roles.yml"); - Logger logger = CapturingLogger.newCapturingLogger(Level.WARN); + Logger logger = CapturingLogger.newCapturingLogger(Level.WARN, null); + List events = CapturingLogger.output(logger.getName(), Level.WARN); + events.clear(); XPackLicenseState licenseState = mock(XPackLicenseState.class); when(licenseState.isDocumentAndFieldLevelSecurityAllowed()).thenReturn(false); Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY, licenseState); @@ -273,7 +276,6 @@ public void testParseFileWithFLSAndDLSUnlicensed() throws Exception { assertNotNull(roles.get("role_query")); assertNotNull(roles.get("role_query_fields")); - List events = CapturingLogger.output(logger.getName(), Level.WARN); assertThat(events, hasSize(3)); assertThat( events.get(0), @@ -369,7 +371,9 @@ public void testThatEmptyFileDoesNotResultInLoop() throws Exception { public void testThatInvalidRoleDefinitions() throws Exception { Path path = getDataPath("invalid_roles.yml"); - Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR); + Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR, null); + List entries = CapturingLogger.output(logger.getName(), Level.ERROR); + entries.clear(); Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY, new XPackLicenseState(Settings.EMPTY)); assertThat(roles.size(), is(1)); assertThat(roles, hasKey("valid_role")); @@ -379,7 +383,6 @@ public void testThatInvalidRoleDefinitions() throws Exception { assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "valid_role" })); - List entries = CapturingLogger.output(logger.getName(), Level.ERROR); assertThat(entries, hasSize(6)); assertThat( entries.get(0), @@ -395,12 +398,13 @@ public void testThatInvalidRoleDefinitions() throws Exception { public void testThatRoleNamesDoesNotResolvePermissions() throws Exception { Path path = getDataPath("invalid_roles.yml"); - Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR); + Logger logger = CapturingLogger.newCapturingLogger(Level.ERROR, null); + List events = CapturingLogger.output(logger.getName(), Level.ERROR); + events.clear(); Set roleNames = FileRolesStore.parseFileForRoleNames(path, logger); assertThat(roleNames.size(), is(6)); assertThat(roleNames, containsInAnyOrder("valid_role", "role1", "role2", "role3", "role4", "role5")); - List events = CapturingLogger.output(logger.getName(), Level.ERROR); assertThat(events, hasSize(1)); assertThat( events.get(0), @@ -408,9 +412,9 @@ public void testThatRoleNamesDoesNotResolvePermissions() throws Exception { } public void testReservedRoles() throws Exception { - - Logger logger = CapturingLogger.newCapturingLogger(Level.INFO); - + Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + List events = CapturingLogger.output(logger.getName(), Level.ERROR); + events.clear(); Path path = getDataPath("reserved_roles.yml"); Map roles = FileRolesStore.parseFile(path, logger, Settings.EMPTY, new XPackLicenseState(Settings.EMPTY)); assertThat(roles, notNullValue()); @@ -418,7 +422,6 @@ public void testReservedRoles() throws Exception { assertThat(roles, hasKey("admin")); - List events = CapturingLogger.output(logger.getName(), Level.ERROR); assertThat(events, notNullValue()); assertThat(events, hasSize(4)); // the system role will always be checked first diff --git a/x-pack/qa/sql/security/build.gradle b/x-pack/qa/sql/security/build.gradle index 15f7734f9422e..9e6cc4eab2352 100644 --- a/x-pack/qa/sql/security/build.gradle +++ b/x-pack/qa/sql/security/build.gradle @@ -40,7 +40,7 @@ subprojects { integTestRunner { systemProperty 'tests.audit.logfile', - "${ -> integTest.nodes[0].homeDir}/logs/${ -> integTest.nodes[0].clusterName }_access.log" + "${ -> integTest.nodes[0].homeDir}/logs/${ -> integTest.nodes[0].clusterName }_audit.log" } runqa { diff --git a/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/RestSqlSecurityIT.java b/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/RestSqlSecurityIT.java index cb8afc876a418..e1c94ce463f80 100644 --- a/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/RestSqlSecurityIT.java +++ b/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/RestSqlSecurityIT.java @@ -248,14 +248,14 @@ public AuditLogAsserter expect(String eventType, String action, String principal final Matcher runByRealmMatcher = realm.equals("default_file") ? Matchers.nullValue(String.class) : Matchers.is("default_file"); logCheckers.add( - m -> eventType.equals(m.get("event_type")) + m -> eventType.equals(m.get("event.action")) && action.equals(m.get("action")) - && principal.equals(m.get("principal")) - && realm.equals(m.get("realm")) - && runByPrincipalMatcher.matches(m.get("run_by_principal")) - && runByRealmMatcher.matches(m.get("run_by_realm")) + && principal.equals(m.get("user.name")) + && realm.equals(m.get("user.realm")) + && runByPrincipalMatcher.matches(m.get("user.run_by.name")) + && runByRealmMatcher.matches(m.get("user.run_by.realm")) && indicesMatcher.matches(m.get("indices")) - && request.equals(m.get("request"))); + && request.equals(m.get("request.name"))); return this; } diff --git a/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/SqlSecurityTestCase.java b/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/SqlSecurityTestCase.java index 83047c93da327..1bea73b56a2c7 100644 --- a/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/SqlSecurityTestCase.java +++ b/x-pack/qa/sql/security/src/test/java/org/elasticsearch/xpack/qa/sql/security/SqlSecurityTestCase.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.qa.sql.security; import org.apache.lucene.util.SuppressForbidden; +import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.SpecialPermission; import org.elasticsearch.action.admin.indices.get.GetIndexAction; import org.elasticsearch.action.admin.indices.get.GetIndexRequest; @@ -14,6 +15,7 @@ import org.elasticsearch.common.Strings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.test.rest.ESRestTestCase; import org.hamcrest.Matcher; @@ -32,17 +34,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.function.Function; -import java.util.regex.Pattern; import static java.util.Collections.singletonMap; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.hasItems; public abstract class SqlSecurityTestCase extends ESRestTestCase { @@ -515,21 +516,22 @@ public AuditLogAsserter expect(boolean granted, String action, String principal, default: throw new IllegalArgumentException("Unknown action [" + action + "]"); } - final String eventType = granted ? "access_granted" : "access_denied"; + final String eventAction = granted ? "access_granted" : "access_denied"; final String realm = principal.equals("test_admin") ? "default_file" : "default_native"; - return expect(eventType, action, principal, realm, indicesMatcher, request); + return expect(eventAction, action, principal, realm, indicesMatcher, request); } - public AuditLogAsserter expect(String eventType, String action, String principal, String realm, + public AuditLogAsserter expect(String eventAction, String action, String principal, String realm, Matcher> indicesMatcher, String request) { - logCheckers.add(m -> eventType.equals(m.get("event_type")) + logCheckers.add(m -> + eventAction.equals(m.get("event.action")) && action.equals(m.get("action")) - && principal.equals(m.get("principal")) - && realm.equals(m.get("realm")) - && Matchers.nullValue(String.class).matches(m.get("run_by_principal")) - && Matchers.nullValue(String.class).matches(m.get("run_by_realm")) + && principal.equals(m.get("user.name")) + && realm.equals(m.get("user.realm")) + && Matchers.nullValue(String.class).matches(m.get("user.run_by.name")) + && Matchers.nullValue(String.class).matches(m.get("user.run_by.realm")) && indicesMatcher.matches(m.get("indices")) - && request.equals(m.get("request")) + && request.equals(m.get("request.name")) ); return this; } @@ -554,56 +556,39 @@ public void assertLogs() throws Exception { List> logs = new ArrayList<>(); String line; - Pattern logPattern = Pattern.compile( - ("PART PART PART PART origin_type=PART, origin_address=PART, principal=PART, realm=PART, " - + "(?:run_as_principal=IGN, )?(?:run_as_realm=IGN, )?(?:run_by_principal=PART, )?(?:run_by_realm=PART, )?" - + "roles=PART, action=\\[(.*?)\\], (?:indices=PART, )?request=PART") - .replace(" ", "\\s+").replace("PART", "\\[([^\\]]*)\\]").replace("IGN", "\\[[^\\]]*\\]")); - // fail(logPattern.toString()); while ((line = logReader.readLine()) != null) { - java.util.regex.Matcher m = logPattern.matcher(line); - if (false == m.matches()) { - throw new IllegalArgumentException("Unrecognized log: " + line); - } - int i = 1; - Map log = new HashMap<>(); - /* We *could* parse the date but leaving it in the original format makes it - * easier to find the lines in the file that this log comes from. */ - log.put("time", m.group(i++)); - log.put("node", m.group(i++)); - log.put("origin", m.group(i++)); - String eventType = m.group(i++); - if (false == ("access_denied".equals(eventType) || "access_granted".equals(eventType))) { - continue; - } - log.put("event_type", eventType); - log.put("origin_type", m.group(i++)); - log.put("origin_address", m.group(i++)); - String principal = m.group(i++); - log.put("principal", principal); - log.put("realm", m.group(i++)); - log.put("run_by_principal", m.group(i++)); - log.put("run_by_realm", m.group(i++)); - log.put("roles", m.group(i++)); - String action = m.group(i++); - if (false == (SQL_ACTION_NAME.equals(action) || GetIndexAction.NAME.equals(action))) { - //TODO we may want to extend this and the assertions to SearchAction.NAME as well - continue; - } - log.put("action", action); - // Use a sorted list for indices for consistent error reporting - List indices = new ArrayList<>(Strings.tokenizeByCommaToSet(m.group(i++))); - Collections.sort(indices); - if ("test_admin".equals(principal)) { - /* Sometimes we accidentally sneak access to the security tables. This is fine, SQL - * drops them from the interface. So we might have access to them, but we don't show - * them. */ - indices.remove(".security"); - indices.remove(".security-6"); + try { + final Map log = XContentHelper.convertToMap(JsonXContent.jsonXContent, line, false); + if (false == ("access_denied".equals(log.get("event.action")) + || "access_granted".equals(log.get("event.action")))) { + continue; + } + assertThat(log.containsKey("action"), is(true)); + if (false == (SQL_ACTION_NAME.equals(log.get("action")) || GetIndexAction.NAME.equals(log.get("action")))) { + // TODO we may want to extend this and the assertions to SearchAction.NAME as well + continue; + } + assertThat(log.containsKey("user.name"), is(true)); + List indices = new ArrayList<>(); + if (log.containsKey("indices")) { + indices = (ArrayList) log.get("indices"); + if ("test_admin".equals(log.get("user.name"))) { + /* + * Sometimes we accidentally sneak access to the security tables. This is fine, + * SQL drops them from the interface. So we might have access to them, but we + * don't show them. + */ + indices.remove(".security"); + indices.remove(".security-6"); + } + } + // Use a sorted list for indices for consistent error reporting + Collections.sort(indices); + log.put("indices", indices); + logs.add(log); + } catch (final ElasticsearchParseException e) { + throw new IllegalArgumentException("Unrecognized log: " + line, e); } - log.put("indices", indices); - log.put("request", m.group(i)); - logs.add(log); } List> allLogs = new ArrayList<>(logs); List notMatching = new ArrayList<>();