Skip to content

Commit

Permalink
Support removing ignore filters for audit logging (#87675) (#87945)
Browse files Browse the repository at this point in the history
Ignore filters of audit logging can be configured with the cluster
settings APIs. While adding new filters work correclty, removing
filters does not work until node restart due to a bug (#68588). This PR
fixes the bug by correctly remove the ignore filter when all rules of a
filtering policy is set to null.

Resolves: #68588
(cherry picked from commit 06b4900)
  • Loading branch information
ywangd committed Jun 23, 2022
1 parent fc7f2c9 commit b545b99
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 109 deletions.
6 changes: 6 additions & 0 deletions docs/changelog/87675.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pr: 87675
summary: Support removing ignore filters for audit logging
area: Audit
type: bug
issues:
- 68588
Original file line number Diff line number Diff line change
Expand Up @@ -399,32 +399,24 @@ public LoggingAuditTrail(Settings settings, ClusterService clusterService, Threa
INCLUDE_REQUEST_BODY
)
);
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_PRINCIPALS, (policyName, filtersList) -> {
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings))
.changePrincipalsFilter(filtersList);
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_REALMS, (policyName, filtersList) -> {
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
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<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
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<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
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<EventFilterPolicy> 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));
clusterService.getClusterSettings()
.addAffixGroupUpdateConsumer(
List.of(
FILTER_POLICY_IGNORE_PRINCIPALS,
FILTER_POLICY_IGNORE_REALMS,
FILTER_POLICY_IGNORE_ROLES,
FILTER_POLICY_IGNORE_INDICES,
FILTER_POLICY_IGNORE_ACTIONS
),
(policyName, updatedSettings) -> {
if (updatedSettings.keySet().isEmpty()) {
this.eventFilterPolicyRegistry.remove(policyName);
} else {
this.eventFilterPolicyRegistry.set(policyName, new EventFilterPolicy(policyName, updatedSettings));
}
}
);

// 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);
Expand Down Expand Up @@ -1660,92 +1652,21 @@ private static final class EventFilterPolicy {
private final Predicate<String> ignoreIndicesPredicate;
private final Predicate<String> 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_ACTIONS.getConcreteSettingForNamespace(name).get(settings))
);
}

/**
* An empty filter list for a field will match events with that field missing.
* An event with an undefined field has the field value the empty string ("") or
* a singleton list of the empty string ([""]).
*/
EventFilterPolicy(
String name,
Predicate<String> ignorePrincipalsPredicate,
Predicate<String> ignoreRealmsPredicate,
Predicate<String> ignoreRolesPredicate,
Predicate<String> ignoreIndicesPredicate,
Predicate<String> ignoreActionsPredicate
) {
EventFilterPolicy(String name, Settings settings) {
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<String> filtersList) {
return new EventFilterPolicy(
name,
parsePredicate(filtersList),
ignoreRealmsPredicate,
ignoreRolesPredicate,
ignoreIndicesPredicate,
ignoreActionsPredicate
);
}

private EventFilterPolicy changeRealmsFilter(List<String> filtersList) {
return new EventFilterPolicy(
name,
ignorePrincipalsPredicate,
parsePredicate(filtersList),
ignoreRolesPredicate,
ignoreIndicesPredicate,
ignoreActionsPredicate
);
}

private EventFilterPolicy changeRolesFilter(List<String> filtersList) {
return new EventFilterPolicy(
name,
ignorePrincipalsPredicate,
ignoreRealmsPredicate,
parsePredicate(filtersList),
ignoreIndicesPredicate,
ignoreActionsPredicate
);
}

private EventFilterPolicy changeIndicesFilter(List<String> filtersList) {
return new EventFilterPolicy(
name,
ignorePrincipalsPredicate,
ignoreRealmsPredicate,
ignoreRolesPredicate,
parsePredicate(filtersList),
ignoreActionsPredicate
);
}

private EventFilterPolicy changeActionsFilter(List<String> filtersList) {
return new EventFilterPolicy(
name,
ignorePrincipalsPredicate,
ignoreRealmsPredicate,
ignoreRolesPredicate,
ignoreIndicesPredicate,
parsePredicate(filtersList)
this.ignorePrincipalsPredicate = parsePredicate(
FILTER_POLICY_IGNORE_PRINCIPALS.getConcreteSettingForNamespace(name).get(settings)
);
this.ignoreRealmsPredicate = parsePredicate(FILTER_POLICY_IGNORE_REALMS.getConcreteSettingForNamespace(name).get(settings));
this.ignoreRolesPredicate = parsePredicate(FILTER_POLICY_IGNORE_ROLES.getConcreteSettingForNamespace(name).get(settings));
this.ignoreIndicesPredicate = parsePredicate(FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace(name).get(settings));
this.ignoreActionsPredicate = parsePredicate(FILTER_POLICY_IGNORE_ACTIONS.getConcreteSettingForNamespace(name).get(settings));
}

static Predicate<String> parsePredicate(List<String> l) {
Expand Down Expand Up @@ -1816,16 +1737,18 @@ private EventFilterPolicyRegistry(Settings settings) {
predicate = buildIgnorePredicate(policyMap);
}

private Optional<EventFilterPolicy> get(String policyName) {
return Optional.ofNullable(policyMap.get(policyName));
}

private synchronized void set(String policyName, EventFilterPolicy eventFilterPolicy) {
policyMap = Maps.copyMapWithAddedOrReplacedEntry(policyMap, policyName, eventFilterPolicy);
// precompute predicate
predicate = buildIgnorePredicate(policyMap);
}

private synchronized void remove(String policyName) {
policyMap = Maps.copyMapWithRemovedEntry(policyMap, policyName);
// precompute predicate
predicate = buildIgnorePredicate(policyMap);
}

Predicate<AuditEventMetaInfo> ignorePredicate() {
return predicate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class LoggingAuditTrailFilterTests extends ESTestCase {
private Settings settings;
private DiscoveryNode localNode;
private ClusterService clusterService;
private ClusterSettings clusterSettings;
private ApiKeyService apiKeyService;

@Before
Expand All @@ -104,7 +105,7 @@ public void init() throws Exception {
when(clusterService.getClusterName()).thenReturn(ClusterName.CLUSTER_NAME_SETTING.get(settings));
when(clusterService.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
when(clusterService.state()).thenReturn(clusterState);
final ClusterSettings clusterSettings = mockClusterSettings();
clusterSettings = mockClusterSettings();
when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
Mockito.doAnswer((Answer) invocation -> {
final LoggingAuditTrail arg0 = (LoggingAuditTrail) invocation.getArguments()[0];
Expand Down Expand Up @@ -2696,6 +2697,110 @@ public void testActionsFilter() throws Exception {
threadContext.stashContext();
}

public void testRemoveIgnoreFilter() throws IllegalAccessException, IOException {
final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null);
final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);

final String policyName = randomAlphaOfLengthBetween(5, 8);
final List<String> filteredUsers = randomNonEmptyListOfFilteredNames();
final List<String> filteredRoles = randomNonEmptyListOfFilteredNames();
final List<String> filteredRealms = randomNonEmptyListOfFilteredNames();
final List<String> filteredIndices = randomNonEmptyListOfFilteredNames();
final List<String> filteredActions = randomNonEmptyListOfFilteredActions();

// First create an auditTrail with no filtering
final LoggingAuditTrail auditTrail = new LoggingAuditTrail(
Settings.builder().put(settings).build(),
clusterService,
logger,
threadContext
);
final List<String> logOutput = CapturingLogger.output(logger.getName(), Level.INFO);

// First create a working ignore filter
final Settings.Builder settingsBuilder = Settings.builder();
final String username;
if (randomBoolean()) {
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".users", filteredUsers);
username = randomFrom(filteredUsers);
} else {
username = null;
}

final String realmName;
if (randomBoolean()) {
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".realms", filteredRealms);
realmName = randomFrom(filteredRealms);
} else {
realmName = null;
}

final String roleName;
if (randomBoolean()) {
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".roles", filteredRoles);
roleName = randomFrom(filteredRoles);
} else {
roleName = null;
}

final String indexName;
if (randomBoolean()) {
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".indices", filteredIndices);
indexName = randomFrom(filteredIndices);
} else {
indexName = null;
}

// If nothing is filtered so far due to randomisation, always filter on action name
final String actionName;
if (randomBoolean() || (username == null && realmName == null && roleName == null && indexName == null)) {
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".actions", filteredActions);
actionName = randomFrom(filteredActions);
} else {
actionName = null;
}

final String requestId = randomAlphaOfLength(10);
final Authentication authentication = Authentication.newRealmAuthentication(
new User(
username != null ? username : randomAlphaOfLengthBetween(3, 10),
roleName != null ? roleName : randomAlphaOfLengthBetween(3, 10)
),
new RealmRef(
realmName != null ? realmName : randomAlphaOfLengthBetween(3, 10),
randomAlphaOfLengthBetween(3, 10),
randomAlphaOfLengthBetween(3, 8),
randomFrom(AuthenticationTestHelper.randomDomain(randomBoolean()), null)
)
);
final MockIndicesRequest request = new MockIndicesRequest(
threadContext,
indexName != null ? indexName : randomAlphaOfLengthBetween(3, 10)
);
final AuthorizationInfo authorizationInfo = authzInfo(authentication.getUser().roles());
final String action = actionName != null ? actionName : randomAlphaOfLengthBetween(3, 10);

// Filter not created yet, message should be logged
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
assertThat("AccessGranted message: should not filter since we have no filter", logOutput.size(), is(1));
logOutput.clear();
threadContext.stashContext();

// Create the filter, the same message should be filtered
clusterSettings.applySettings(settingsBuilder.build());
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
assertThat("AccessGranted message: should be filtered out", logOutput.size(), is(0));
logOutput.clear();
threadContext.stashContext();

// Remove the filter, the message is logged again
clusterSettings.applySettings(Settings.EMPTY);
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
assertThat("AccessGranted message: should not filter since filter is removed", logOutput.size(), is(1));
logOutput.clear();
threadContext.stashContext();
}

private InetSocketAddress randomLoopbackInetSocketAddress() {
return new InetSocketAddress(InetAddress.getLoopbackAddress(), randomIntBetween(0, 65535));
}
Expand Down

0 comments on commit b545b99

Please sign in to comment.