diff --git a/docs/reference/settings/audit-settings.asciidoc b/docs/reference/settings/audit-settings.asciidoc index a293444e2a25..280078fbe4b1 100644 --- a/docs/reference/settings/audit-settings.asciidoc +++ b/docs/reference/settings/audit-settings.asciidoc @@ -149,6 +149,15 @@ A list of authentication realm names or wildcards. The specified policy will not print audit events for users in these realms. // end::xpack-sa-lf-events-ignore-realms-tag[] +[[xpack-sa-lf-events-ignore-actions]] +// tag::xpack-sa-lf-events-ignore-actions-tag[] +`xpack.security.audit.logfile.events.ignore_filters..actions`:: +(<>) +A list of action names or wildcards. Action name can be found in the `action` +field of the audit event. The specified policy will not print audit events +for actions matching these values. +// end::xpack-sa-lf-events-ignore-actions-tag[] + [[xpack-sa-lf-events-ignore-roles]] // tag::xpack-sa-lf-events-ignore-roles-tag[] `xpack.security.audit.logfile.events.ignore_filters..roles`:: diff --git a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java index eff07f844e8c..9d86a7fb5206 100644 --- a/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java +++ b/x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/audit/logfile/AuditTrailSettingsUpdateTests.java @@ -96,7 +96,8 @@ public class AuditTrailSettingsUpdateTests extends SecurityIntegTestCase { final String[] allSettingsKeys = new String[] { "xpack.security.audit.logfile.events.ignore_filters.invalid.users", "xpack.security.audit.logfile.events.ignore_filters.invalid.realms", "xpack.security.audit.logfile.events.ignore_filters.invalid.roles", - "xpack.security.audit.logfile.events.ignore_filters.invalid.indices" }; + "xpack.security.audit.logfile.events.ignore_filters.invalid.indices", + "xpack.security.audit.logfile.events.ignore_filters.invalid.actions"}; settingsBuilder.put(randomFrom(allSettingsKeys), invalidLuceneRegex); final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> client().admin().cluster().prepareUpdateSettings().setTransientSettings(settingsBuilder.build()).get()); @@ -223,6 +224,12 @@ public class AuditTrailSettingsUpdateTests extends SecurityIntegTestCase { final List filteredIndices = randomNonEmptyListOfFilteredNames(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".indices", filteredIndices); } + if (randomBoolean()) { + // filter by actions + final List filteredActions = randomNonEmptyListOfFilteredNames(); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".actions", + filteredActions); + } } while (settingsBuilder.build().isEmpty()); assertFalse(settingsBuilder.build().isEmpty()); 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 e560744ad854..71ca8ec39b62 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 @@ -218,6 +218,10 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { Setting.affixKeySetting(FILTER_POLICY_PREFIX, "indices", (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), value -> EventFilterPolicy.parsePredicate(value), Property.NodeScope, Property.Dynamic)); + protected static final Setting.AffixSetting> FILTER_POLICY_IGNORE_ACTIONS = + Setting.affixKeySetting(FILTER_POLICY_PREFIX, "actions", + (key) -> Setting.listSetting(key, Collections.singletonList("*"), Function.identity(), + value -> EventFilterPolicy.parsePredicate(value), Property.NodeScope, Property.Dynamic)); private static final Marker AUDIT_MARKER = MarkerManager.getMarker("org.elasticsearch.xpack.security.audit"); @@ -277,6 +281,12 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeIndicesFilter(filtersList); this.eventFilterPolicyRegistry.set(policyName, newPolicy); }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); + clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_ACTIONS, (policyName, filtersList) -> { + final Optional policy = eventFilterPolicyRegistry.get(policyName); + final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)). + changeActionsFilter(filtersList); + this.eventFilterPolicyRegistry.set(policyName, newPolicy); + }, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList)); // this log filter ensures that audit events are not filtered out because of the log level final LoggerContext ctx = LoggerContext.getContext(false); MarkerFilter auditMarkerFilter = MarkerFilter.createFilter(AUDIT_MARKER.getName(), Result.ACCEPT, Result.NEUTRAL); @@ -295,6 +305,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), Optional.empty(), + Optional.empty(), Optional.empty())) == false) { // this is redundant information maintained for bwc purposes final String authnRealm = authentication.getAuthenticatedBy().getName(); @@ -323,7 +334,8 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), Optional.empty(), - indices)) == false) { + indices, + Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_success") @@ -345,7 +357,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (events.contains(ANONYMOUS_ACCESS_DENIED)) { final Optional indices = indices(transportRequest); if (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { + .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "anonymous_access_denied") @@ -383,7 +395,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (events.contains(AUTHENTICATION_FAILED)) { final Optional indices = indices(transportRequest); if (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), indices)) == false) { + .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") @@ -421,7 +433,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (events.contains(AUTHENTICATION_FAILED)) { final Optional indices = indices(transportRequest); if (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { + .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") @@ -440,7 +452,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { @Override public void authenticationFailed(String requestId, AuthenticationToken token, RestRequest request) { if (events.contains(AUTHENTICATION_FAILED) && eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), Optional.empty())) == false) { + .test(new AuditEventMetaInfo(Optional.of(token), Optional.empty(), Optional.empty(), Optional.empty())) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "authentication_failed") @@ -461,7 +473,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (events.contains(REALM_AUTHENTICATION_FAILED)) { final Optional indices = indices(transportRequest); if (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), indices)) == false) { + .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") @@ -482,7 +494,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { @Override public void authenticationFailed(String requestId, 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) { + .test(new AuditEventMetaInfo(Optional.of(token), Optional.of(realm), Optional.empty(), Optional.empty())) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "realm_authentication_failed") @@ -508,7 +520,8 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(user), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), indices)) == false) { + Optional.of(authorizationInfo), indices, + Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "access_granted") @@ -593,7 +606,8 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .test(new AuditEventMetaInfo(Optional.of(user), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), Optional.ofNullable(indices))) == false) { + Optional.of(authorizationInfo), Optional.ofNullable(indices), + Optional.of(action))) == false) { final LogEntryBuilder logEntryBuilder = new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, eventType == ACCESS_DENIED ? "access_denied" : "access_granted") @@ -628,7 +642,8 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), indices)) == false) { + Optional.of(authorizationInfo), indices, + Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "access_denied") @@ -667,7 +682,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (events.contains(TAMPERED_REQUEST)) { final Optional indices = indices(transportRequest); if (eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices)) == false) { + .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "tampered_request") @@ -692,7 +707,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), Optional.empty(), - indices)) == false) { + indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "tampered_request") @@ -751,7 +766,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), indices)) == false) { + Optional.of(authorizationInfo), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "run_as_granted") @@ -777,7 +792,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { if (eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(authentication.getUser()), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), indices)) == false) { + Optional.of(authorizationInfo), indices, Optional.of(action))) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, TRANSPORT_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "run_as_denied") @@ -801,7 +816,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { new AuditEventMetaInfo(Optional.of(authentication.getUser()), // can be null for API keys created before version 7.7 Optional.ofNullable(ApiKeyService.getCreatorRealmName(authentication)), - Optional.of(authorizationInfo), Optional.empty())) == false) { + Optional.of(authorizationInfo), Optional.empty(), Optional.empty())) == false) { new LogEntryBuilder() .with(EVENT_TYPE_FIELD_NAME, REST_ORIGIN_FIELD_VALUE) .with(EVENT_ACTION_FIELD_NAME, "run_as_denied") @@ -1327,6 +1342,7 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { settings.add(FILTER_POLICY_IGNORE_INDICES); settings.add(FILTER_POLICY_IGNORE_ROLES); settings.add(FILTER_POLICY_IGNORE_REALMS); + settings.add(FILTER_POLICY_IGNORE_ACTIONS); } /** @@ -1342,12 +1358,14 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { private final Predicate ignoreRealmsPredicate; private final Predicate ignoreRolesPredicate; private final Predicate ignoreIndicesPredicate; + private final Predicate ignoreActionsPredicate; EventFilterPolicy(String name, Settings settings) { this(name, parsePredicate(FILTER_POLICY_IGNORE_PRINCIPALS.getConcreteSettingForNamespace(name).get(settings)), parsePredicate(FILTER_POLICY_IGNORE_REALMS.getConcreteSettingForNamespace(name).get(settings)), parsePredicate(FILTER_POLICY_IGNORE_ROLES.getConcreteSettingForNamespace(name).get(settings)), - parsePredicate(FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace(name).get(settings))); + parsePredicate(FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace(name).get(settings)), + parsePredicate(FILTER_POLICY_IGNORE_ACTIONS.getConcreteSettingForNamespace(name).get(settings))); } /** @@ -1356,33 +1374,40 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { * a singleton list of the empty string ([""]). */ EventFilterPolicy(String name, Predicate ignorePrincipalsPredicate, Predicate ignoreRealmsPredicate, - Predicate ignoreRolesPredicate, Predicate ignoreIndicesPredicate) { + Predicate ignoreRolesPredicate, Predicate ignoreIndicesPredicate, + Predicate ignoreActionsPredicate) { this.name = name; // "null" values are "unexpected" and should not match any ignore policy this.ignorePrincipalsPredicate = ignorePrincipalsPredicate; this.ignoreRealmsPredicate = ignoreRealmsPredicate; this.ignoreRolesPredicate = ignoreRolesPredicate; this.ignoreIndicesPredicate = ignoreIndicesPredicate; + this.ignoreActionsPredicate = ignoreActionsPredicate; } private EventFilterPolicy changePrincipalsFilter(List filtersList) { return new EventFilterPolicy(name, parsePredicate(filtersList), ignoreRealmsPredicate, ignoreRolesPredicate, - ignoreIndicesPredicate); + ignoreIndicesPredicate, ignoreActionsPredicate); } private EventFilterPolicy changeRealmsFilter(List filtersList) { return new EventFilterPolicy(name, ignorePrincipalsPredicate, parsePredicate(filtersList), ignoreRolesPredicate, - ignoreIndicesPredicate); + ignoreIndicesPredicate, ignoreActionsPredicate); } private EventFilterPolicy changeRolesFilter(List filtersList) { return new EventFilterPolicy(name, ignorePrincipalsPredicate, ignoreRealmsPredicate, parsePredicate(filtersList), - ignoreIndicesPredicate); + ignoreIndicesPredicate, ignoreActionsPredicate); } private EventFilterPolicy changeIndicesFilter(List filtersList) { return new EventFilterPolicy(name, ignorePrincipalsPredicate, ignoreRealmsPredicate, ignoreRolesPredicate, - parsePredicate(filtersList)); + parsePredicate(filtersList), ignoreActionsPredicate); + } + + private EventFilterPolicy changeActionsFilter(List filtersList) { + return new EventFilterPolicy(name, ignorePrincipalsPredicate, ignoreRealmsPredicate, ignoreRolesPredicate, + ignoreIndicesPredicate, parsePredicate(filtersList)); } static Predicate parsePredicate(List l) { @@ -1408,16 +1433,20 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { * predicate of the corresponding field. */ Predicate ignorePredicate() { - return eventInfo -> eventInfo.principal != null && ignorePrincipalsPredicate.test(eventInfo.principal) + return eventInfo -> { + return eventInfo.principal != null && ignorePrincipalsPredicate.test(eventInfo.principal) && eventInfo.realm != null && ignoreRealmsPredicate.test(eventInfo.realm) + && eventInfo.action != null && ignoreActionsPredicate.test(eventInfo.action) && eventInfo.roles.get().allMatch(role -> role != null && ignoreRolesPredicate.test(role)) && eventInfo.indices.get().allMatch(index -> index != null && ignoreIndicesPredicate.test(index)); + }; } @Override public String toString() { return "[users]:" + ignorePrincipalsPredicate.toString() + "&[realms]:" + ignoreRealmsPredicate.toString() + "&[roles]:" - + ignoreRolesPredicate.toString() + "&[indices]:" + ignoreIndicesPredicate.toString(); + + ignoreRolesPredicate.toString() + "&[indices]:" + ignoreIndicesPredicate.toString() + "&[actions]:" + + ignoreActionsPredicate.toString(); } } @@ -1477,11 +1506,13 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { static final class AuditEventMetaInfo { final String principal; final String realm; + final String action; final Supplier> roles; final Supplier> indices; // empty is used for events can be filtered out only by the lack of a field - static final AuditEventMetaInfo EMPTY = new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.empty()); + static final AuditEventMetaInfo EMPTY = new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty()); /** * If a field is missing for an event, its value for filtering purposes is the @@ -1492,9 +1523,10 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { * "elastic" username. */ AuditEventMetaInfo(Optional user, Optional realm, Optional authorizationInfo, - Optional indices) { + Optional indices, Optional action) { this.principal = user.map(u -> u.principal()).orElse(""); this.realm = realm.orElse(""); + this.action = action.orElse(""); // Supplier indirection and lazy generation of Streams serves 2 purposes: // 1. streams might not get generated due to short circuiting logical // conditions on the `principal` and `realm` fields @@ -1510,9 +1542,11 @@ public class LoggingAuditTrail implements AuditTrail, ClusterStateListener { .map(Arrays::stream).orElse(Stream.of("")); } - AuditEventMetaInfo(Optional authenticationToken, Optional realm, Optional indices) { + AuditEventMetaInfo(Optional authenticationToken, Optional realm, Optional indices, + Optional action) { this.principal = authenticationToken.map(u -> u.principal()).orElse(""); this.realm = realm.orElse(""); + this.action = action.orElse(""); this.roles = () -> Stream.of(""); this.indices = () -> indices.filter(r -> r.length != 0).map(i -> Arrays.stream(i)).orElse(Stream.of("")); } 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 c9ffd2364309..61ac598ba1b9 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 @@ -54,6 +54,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Random; import java.util.stream.Collectors; import static org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail.PRINCIPAL_ROLES_FIELD_NAME; @@ -120,12 +121,17 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { // filter by indices final List filteredIndices = randomNonEmptyListOfFilteredNames(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.indicesPolicy.indices", filteredIndices); + // filter by actions + final List filteredActions = randomNonEmptyListOfFilteredActions(); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.actionsPolicy.actions", + filteredActions); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); // user field matches assertTrue("Matches the user filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test( - new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.empty(), Optional.empty(), Optional.empty()))); + new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty()))); final User unfilteredUser = mock(User.class); if (randomBoolean()) { when(unfilteredUser.authenticatedUser()).thenReturn(new User(randomFrom(filteredUsernames))); @@ -133,39 +139,52 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { // null user field does NOT match assertFalse("Does not match the user filter predicate because of null username.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.of(unfilteredUser), Optional.empty(), Optional.empty(), Optional.empty()))); + .test(new AuditEventMetaInfo(Optional.of(unfilteredUser), Optional.empty(), Optional.empty(), Optional.empty(), + Optional.empty()))); // realm field matches assertTrue("Matches the realm filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test( - new AuditEventMetaInfo(Optional.empty(), Optional.of(randomFrom(filteredRealms)), Optional.empty(), Optional.empty()))); + new AuditEventMetaInfo(Optional.empty(), Optional.of(randomFrom(filteredRealms)), Optional.empty(), Optional.empty(), + Optional.empty()))); // null realm field does NOT match assertFalse("Does not match the realm filter predicate because of null realm.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() - .test(new AuditEventMetaInfo(Optional.empty(), Optional.ofNullable(null), Optional.empty(), Optional.empty()))); + .test(new AuditEventMetaInfo(Optional.empty(), Optional.ofNullable(null), Optional.empty(), Optional.empty(), + Optional.empty()))); // role field matches assertTrue("Matches the role filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.of(authzInfo( randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles).toArray(new String[0]))), - Optional.empty()))); + Optional.empty(), Optional.empty()))); + // action field matches + Random random = random(); + assertTrue("Matches the actions filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test( + new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), + Optional.of(randomFrom(filteredActions))))); + // null privilege field does NOT match + assertFalse("Does not matches the actions filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() + .test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), + Optional.ofNullable(null)))); final List unfilteredRoles = new ArrayList<>(); unfilteredRoles.add(null); unfilteredRoles.addAll(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles)); // null role among roles field does NOT match assertFalse("Does not match the role filter predicate because of null role.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), - Optional.of(authzInfo(unfilteredRoles.toArray(new String[0]))), Optional.empty()))); + Optional.of(authzInfo(unfilteredRoles.toArray(new String[0]))), Optional.empty(), Optional.empty()))); // indices field matches assertTrue("Matches the index filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), Optional.empty(), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.empty()))); final List unfilteredIndices = new ArrayList<>(); unfilteredIndices.add(null); unfilteredIndices.addAll(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices)); // null index among indices field does NOT match assertFalse("Does not match the indices filter predicate because of null index.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.empty(), Optional.empty(), - Optional.empty(), Optional.of(unfilteredIndices.toArray(new String[0]))))); + Optional.empty(), Optional.of(unfilteredIndices.toArray(new String[0])), Optional.empty()))); } public void testSingleCompletePolicyPredicate() throws Exception { @@ -192,15 +211,21 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { // filter by indices final List filteredIndices = randomNonEmptyListOfFilteredNames(); settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.indices", filteredIndices); + // filter by actions + final List filteredActions = randomNonEmptyListOfFilteredActions(); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.actions", + filteredActions); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); // all fields match + Random random = random(); assertTrue("Matches the filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo( Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles).toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final User unfilteredUser; if (randomBoolean()) { unfilteredUser = new User(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)); @@ -214,25 +239,36 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertFalse("Does not match the filter predicate because of the empty user.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.empty(), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertFalse("Does not match the filter predicate because of the realm.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertFalse("Does not match the filter predicate because of the empty realm.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.empty(), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); + assertFalse("Does not match the filter predicate because of the empty actions.", + auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), + Optional.of(randomFrom(filteredRealms)), + Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) + .toArray(new String[0]))), + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.empty()))); final List someRolesDoNotMatch = new ArrayList<>(randomSubsetOf(randomIntBetween(0, filteredRoles.size()), filteredRoles)); for (int i = 0; i < randomIntBetween(1, 8); i++) { someRolesDoNotMatch.add(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)); @@ -240,12 +276,14 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { assertFalse("Does not match the filter predicate because of some of the roles.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(someRolesDoNotMatch.toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final Optional emptyRoles = randomBoolean() ? Optional.empty() : Optional.of(authzInfo(new String[0])); assertFalse("Does not match the filter predicate because of the empty roles.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), emptyRoles, - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final List someIndicesDoNotMatch = new ArrayList<>( randomSubsetOf(randomIntBetween(0, filteredIndices.size()), filteredIndices)); for (int i = 0; i < randomIntBetween(1, 8); i++) { @@ -256,14 +294,15 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(someIndicesDoNotMatch.toArray(new String[0]))))); + Optional.of(someIndicesDoNotMatch.toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final Optional emptyIndices = randomBoolean() ? Optional.empty() : Optional.of(new String[0]); assertFalse("Does not match the filter predicate because of the empty indices.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - emptyIndices))); + emptyIndices, Optional.of(randomFrom(filteredActions))))); } public void testSingleCompleteWithEmptyFieldPolicyPredicate() throws Exception { @@ -297,16 +336,22 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { filteredIndices.add(""); // filter by missing index name settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.indices", filteredIndices); filteredIndices.remove(""); + // filter by actions + final List filteredActions = randomNonEmptyListOfFilteredActions(); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.completeFilterPolicy.actions", + filteredActions); final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext); // all fields match + Random random = random(); assertTrue("Matches the filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final User unfilteredUser; if (randomBoolean()) { unfilteredUser = new User(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)); @@ -320,25 +365,36 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertTrue("Matches the filter predicate because of the empty user.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.empty(), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertFalse("Does not match the filter predicate because of the realm.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertTrue("Matches the filter predicate because of the empty realm.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.empty(), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); + assertFalse("Does not match the filter predicate because of the pivilege.", + auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), + Optional.of(randomFrom(filteredRealms)), + Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) + .toArray(new String[0]))), + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8))))); final List someRolesDoNotMatch = new ArrayList<>(randomSubsetOf(randomIntBetween(0, filteredRoles.size()), filteredRoles)); for (int i = 0; i < randomIntBetween(1, 8); i++) { someRolesDoNotMatch.add(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)); @@ -346,12 +402,14 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { assertFalse("Does not match the filter predicate because of some of the roles.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(someRolesDoNotMatch.toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final Optional emptyRoles = randomBoolean() ? Optional.empty() : Optional.of(authzInfo(new String[0])); assertTrue("Matches the filter predicate because of the empty roles.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), emptyRoles, - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); final List someIndicesDoNotMatch = new ArrayList<>( randomSubsetOf(randomIntBetween(0, filteredIndices.size()), filteredIndices)); for (int i = 0; i < randomIntBetween(1, 8); i++) { @@ -362,22 +420,24 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(someIndicesDoNotMatch.toArray(new String[0]))))); + Optional.of(someIndicesDoNotMatch.toArray(new String[0])), + Optional.of(randomFrom(filteredActions))))); assertTrue("Matches the filter predicate because of the empty indices.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo( randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles).toArray(new String[0]))), - Optional.empty()))); + Optional.empty(), Optional.of(randomFrom(filteredActions))))); assertTrue("Matches the filter predicate because of the empty indices.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo( randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles).toArray(new String[0]))), - Optional.of(new String[0])))); + Optional.of(new String[0]), Optional.of(randomFrom(filteredActions))))); assertTrue("Matches the filter predicate because of the empty indices.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo( randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles).toArray(new String[0]))), - Optional.of(new String[] { null })))); + Optional.of(new String[] { null }), + Optional.of(randomFrom(filteredActions))))); } public void testTwoPolicyPredicatesWithMissingFields() throws Exception { @@ -427,27 +487,29 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.empty()))); // matches first policy but not the second assertTrue("Matches the first filter predicate but not the second.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(unfilteredUser), Optional.of(randomFrom(filteredRealms)), Optional.of(authzInfo(randomSubsetOf(randomIntBetween(1, filteredRoles.size()), filteredRoles) .toArray(new String[0]))), - Optional.of(someIndicesDoNotMatch.toArray(new String[0]))))); + Optional.of(someIndicesDoNotMatch.toArray(new String[0])), Optional.of("_action")))); // matches the second policy but not the first assertTrue("Matches the second filter predicate but not the first.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.of(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)), Optional.of(authzInfo(someRolesDoNotMatch.toArray(new String[0]))), - Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0]))))); + Optional.of(randomSubsetOf(randomIntBetween(1, filteredIndices.size()), filteredIndices).toArray(new String[0])), + Optional.empty()))); // matches neither the first nor the second policies assertFalse("Matches neither the first nor the second filter predicates.", auditTrail.eventFilterPolicyRegistry.ignorePredicate() .test(new AuditEventMetaInfo(Optional.of(unfilteredUser), Optional.of(UNFILTER_MARKER + randomAlphaOfLengthBetween(1, 8)), Optional.of(authzInfo(someRolesDoNotMatch.toArray(new String[0]))), - Optional.of(someIndicesDoNotMatch.toArray(new String[0]))))); + Optional.of(someIndicesDoNotMatch.toArray(new String[0])), Optional.empty()))); } public void testUsersFilter() throws Exception { @@ -1817,6 +1879,231 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { threadContext.stashContext(); } + public void testActionsFilter() throws Exception { + final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null); + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final List filteredActions = randomNonEmptyListOfFilteredActions(); + + final Settings.Builder settingsBuilder = Settings.builder().put(settings); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.actionsPolicy.actions", + filteredActions); + // a filter for a field consisting of an empty string ("") or an empty list([]) + // will match events that lack that field + final boolean filterMissingAction = randomBoolean(); + if (filterMissingAction) { + if (randomBoolean()) { + filteredActions.add(""); + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.missingPolicy.actions", + filteredActions); + } else { + settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters.missingPolicy.actions", + Collections.emptyList()); + } + } + final String filteredAction = randomFrom(filteredActions); + final String unfilteredAction = "mock_action/mock_action"; + User user; + if (randomBoolean()) { + user = new User("user1", new String[] { "r1" }, new User("authUsername", new String[] { "r2" })); + } else { + user = new User("user1", new String[] { "r1" }); + } + final TransportRequest request = randomBoolean() ? new MockRequest(threadContext) + : new MockIndicesRequest(threadContext, new String[] { "idx1", "idx2" }); + 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(randomAlphaOfLength(8), filteredAction, request); + assertThat("Anonymous message: not filtered out by the action filter", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.anonymousAccessDenied(randomAlphaOfLength(8), getRestRequest()); + if (filterMissingAction){ + assertThat("Anonymous rest request: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("Anonymous rest request: filtered out by action filter", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + // authenticationFailed + auditTrail.authenticationFailed(randomAlphaOfLength(8), getRestRequest()); + if (filterMissingAction){ + assertThat("AuthenticationFailed: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("AuthenticationFailed: filtered out by action filter", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), authToken, filteredAction, request); + assertThat("AuthenticationFailed: not filtered out by the action filter", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), filteredAction, request); + assertThat("AuthenticationFailed no token message: not filtered out by the action filter", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), authToken, getRestRequest()); + if (filterMissingAction) { + assertThat("AuthenticationFailed rest request: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("AuthenticationFailed rest request: filtered out by action filter", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), "realm", authToken, unfilteredAction, request); + assertThat("AuthenticationFailed realm message: unfiltered action is filtered out", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), "realm", authToken, filteredAction, request); + assertThat("AuthenticationFailed realm message: filtered action is not filtered out", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationFailed(randomAlphaOfLength(8), "realm", authToken, getRestRequest()); + if (filterMissingAction) { + assertThat("AuthenticationFailed realm rest request: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("AuthenticationFailed realm rest request: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + // accessGranted + Authentication authentication = createAuthentication(user, "realm"); + auditTrail.accessGranted(randomAlphaOfLength(8), authentication, filteredAction, request, authzInfo(new String[]{"role1"})); + assertThat("AccessGranted message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.accessGranted(randomAlphaOfLength(8), authentication, unfilteredAction, request, authzInfo(new String[]{"role1"})); + assertThat("AccessGranted message: unfiltered action filtered out by the action filter", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + logOutput.clear(); + threadContext.stashContext(); + + // accessDenied + auditTrail.accessDenied(randomAlphaOfLength(8), authentication, filteredAction, request, authzInfo(new String[]{"role1"})); + assertThat("AccessDenied message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.accessDenied(randomAlphaOfLength(8), authentication, unfilteredAction, request, authzInfo(new String[]{"role1"})); + assertThat("AccessDenied message: unfiltered action filtered out by the action filter", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + + // tamperedRequest + auditTrail.tamperedRequest(randomAlphaOfLength(8), getRestRequest()); + if (filterMissingAction) { + assertThat("Tampered rest: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("Tampered rest: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.tamperedRequest(randomAlphaOfLength(8), filteredAction, request); + assertThat("Tampered message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.tamperedRequest(randomAlphaOfLength(8), authentication, filteredAction, request); + assertThat("Tampered message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.tamperedRequest(randomAlphaOfLength(8), authentication, unfilteredAction, request); + assertThat("Tampered message: unfiltered action filtered out by the action filter", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + + // connection denied + auditTrail.connectionDenied(InetAddress.getLoopbackAddress(), "default", new SecurityIpFilterRule(false, "_all")); + if (filterMissingAction) { + assertThat("Connection denied: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("Connection denied: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + // connection granted + auditTrail.connectionGranted(InetAddress.getLoopbackAddress(), "default", new SecurityIpFilterRule(false, "_all")); + if (filterMissingAction) { + assertThat("Connection granted: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("Connection granted: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + // runAsGranted + auditTrail.runAsGranted(randomAlphaOfLength(8), createAuthentication(user, "realm"), filteredAction, + new MockRequest(threadContext), authzInfo(new String[] { "role1" })); + assertThat("RunAsGranted message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.runAsGranted(randomAlphaOfLength(8), createAuthentication(user, "realm"), unfilteredAction, + new MockRequest(threadContext), authzInfo(new String[] { "role1" })); + assertThat("RunAsGranted message: unfiltered action is filtered out", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + + // runAsDenied + auditTrail.runAsDenied(randomAlphaOfLength(8), createAuthentication(user, "realm"), filteredAction, new MockRequest(threadContext), + authzInfo(new String[] { "role1" })); + assertThat("RunAsDenied message: not filtered out by the action filters", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.runAsDenied(randomAlphaOfLength(8), createAuthentication(user, "realm"), unfilteredAction, + new MockRequest(threadContext), authzInfo(new String[] { "role1" })); + assertThat("RunAsDenied message: unfiltered action filtered out by the action filters", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.runAsDenied(randomAlphaOfLength(8), createAuthentication(user, "realm"), getRestRequest(), + authzInfo(new String[] { "role1" })); + if (filterMissingAction) { + assertThat("RunAsDenied rest request: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("RunAsDenied rest request: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + // authentication Success + auditTrail.authenticationSuccess(randomAlphaOfLength(8), createAuthentication(user, "realm"), getRestRequest()); + if (filterMissingAction) { + assertThat("AuthenticationSuccess rest request: not filtered out by the missing action filter", logOutput.size(), is(0)); + } else { + assertThat("AuthenticationSuccess rest request: filtered out by the action filters", logOutput.size(), is(1)); + } + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationSuccess(randomAlphaOfLength(8), createAuthentication(user, "realm"), filteredAction, request); + assertThat("AuthenticationSuccess message: filtered action is not filtered out", logOutput.size(), is(0)); + logOutput.clear(); + threadContext.stashContext(); + + auditTrail.authenticationSuccess(randomAlphaOfLength(8), createAuthentication(user, "realm"), unfilteredAction, request); + assertThat("AuthenticationSuccess message: unfiltered action is filtered out", logOutput.size(), is(1)); + logOutput.clear(); + threadContext.stashContext(); + } + private List randomListFromLengthBetween(List l, int min, int max) { assert (min >= 0) && (min <= max) && (max <= l.size()); final int len = randomIntBetween(min, max); @@ -1851,6 +2138,65 @@ public class LoggingAuditTrailFilterTests extends ESTestCase { return filtered; } + private List randomNonEmptyListOfFilteredActions() { + final List filtered = new ArrayList<>(4); + final String[] actionPatterns = { + "internal:transport/proxy/indices:*", + "indices:data/read/*", + "internal:transport/proxy/indices:data/read/*", + "indices:data/write/index*", + "indices:data/write/bulk*", + "indices:data/write/index", + "indices:data/write/index[*", + "indices:data/write/index:op_type/create", + "indices:data/write/update*", + "indices:data/write/delete*", + "indices:data/write/*", + "indices:monitor/*", + "indices:admin/*", + "indices:admin/ilm/*", + "indices:admin/refresh*", + "indices:admin/flush*", + "indices:admin/synced_flush", + "indices:admin/forcemerge*", + "cluster:admin/xpack/security/*", + "cluster:admin/xpack/security/saml/*", + "cluster:admin/xpack/security/oidc/*", + "cluster:admin/xpack/security/token/*", + "cluster:admin/xpack/security/api_key/*", + "cluster:monitor/*", + "cluster:monitor/xpack/ml/*", + "cluster:monitor/text_structure/*", + "cluster:monitor/data_frame/*", + "cluster:monitor/xpack/watcher/*", + "cluster:monitor/xpack/rollup/*", + "cluster:*", + "indices:admin/index_template/*", + "indices:admin/data_stream/*", + "cluster:admin/xpack/ml/*", + "cluster:admin/data_frame/*", + "cluster:monitor/data_frame/*", + "cluster:monitor/transform/*", + "cluster:admin/transform/*", + "cluster:admin/xpack/watcher/*", + "cluster:monitor/nodes/liveness", + "cluster:monitor/state", + "indices:admin/template/*", + "cluster:admin/component_template/*", + "cluster:admin/ingest/pipeline/*", + "cluster:admin/xpack/rollup/*", + "cluster:admin/xpack/ccr/*", + "cluster:admin/ilm/*", + "cluster:admin/slm/*", + "cluster:admin/xpack/enrich/*"}; + Random random = random(); + for (int i = 0; i < randomIntBetween(1, 4); i++) { + Object name = actionPatterns[random.nextInt(actionPatterns.length)]; + filtered.add((String)name); + } + return filtered; + } + private RestRequest getRestRequest() throws IOException { final RestContent content = randomFrom(RestContent.values()); final FakeRestRequest.Builder builder = new Builder(NamedXContentRegistry.EMPTY); 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 b043c2d201a7..bad457d612e7 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 @@ -273,7 +273,8 @@ public class LoggingAuditTrailTests extends ESTestCase { LoggingAuditTrail.INCLUDE_EVENT_SETTINGS, LoggingAuditTrail.EXCLUDE_EVENT_SETTINGS, LoggingAuditTrail.INCLUDE_REQUEST_BODY, LoggingAuditTrail.FILTER_POLICY_IGNORE_PRINCIPALS, LoggingAuditTrail.FILTER_POLICY_IGNORE_REALMS, LoggingAuditTrail.FILTER_POLICY_IGNORE_ROLES, - LoggingAuditTrail.FILTER_POLICY_IGNORE_INDICES, Loggers.LOG_LEVEL_SETTING)); + LoggingAuditTrail.FILTER_POLICY_IGNORE_INDICES, LoggingAuditTrail.FILTER_POLICY_IGNORE_ACTIONS, + Loggers.LOG_LEVEL_SETTING)); when(clusterService.getClusterSettings()).thenReturn(clusterSettings); commonFields = new LoggingAuditTrail.EntryCommonFields(settings, localNode).commonFields; threadContext = new ThreadContext(Settings.EMPTY); @@ -342,6 +343,15 @@ public class LoggingAuditTrailTests extends ESTestCase { e = expectThrows(IllegalArgumentException.class, () -> LoggingAuditTrail.FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace("filter4").get(settings4)); assertThat(e, hasToString(containsString("invalid pattern [/no-inspiration]"))); + + Settings settings5 = Settings.builder() + .putList(prefix + "ignore_filters.filter2.users", Arrays.asList("tom", "cruise")) + .putList(prefix + "ignore_filters.filter2.actions", Arrays.asList("indices:data/read/*", "/foo")).build(); + assertThat(LoggingAuditTrail.FILTER_POLICY_IGNORE_PRINCIPALS.getConcreteSettingForNamespace("filter2").get(settings5), + containsInAnyOrder("tom", "cruise")); + e = expectThrows(IllegalArgumentException.class, + () -> LoggingAuditTrail.FILTER_POLICY_IGNORE_ACTIONS.getConcreteSettingForNamespace("filter2").get(settings5)); + assertThat(e, hasToString(containsString("invalid pattern [/foo]"))); } public void testSecurityConfigChangeEventFormattingForRoles() throws IOException {