Skip to content

Commit 0a75526

Browse files
authored
[7.17] Support removing ignore filters for audit logging (#87675) (#87947)
* Support removing ignore filters for audit logging (#87675) 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) # Conflicts: # x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java * fix
1 parent ed8a20f commit 0a75526

File tree

3 files changed

+144
-109
lines changed

3 files changed

+144
-109
lines changed

docs/changelog/87675.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 87675
2+
summary: Support removing ignore filters for audit logging
3+
area: Audit
4+
type: bug
5+
issues:
6+
- 68588

x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrail.java

Lines changed: 32 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.elasticsearch.common.settings.Setting.Property;
3131
import org.elasticsearch.common.settings.Settings;
3232
import org.elasticsearch.common.transport.TransportAddress;
33+
import org.elasticsearch.common.util.Maps;
3334
import org.elasticsearch.common.util.concurrent.ThreadContext;
3435
import org.elasticsearch.core.Nullable;
3536
import org.elasticsearch.core.TimeValue;
@@ -372,32 +373,24 @@ public LoggingAuditTrail(Settings settings, ClusterService clusterService, Threa
372373
INCLUDE_REQUEST_BODY
373374
)
374375
);
375-
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_PRINCIPALS, (policyName, filtersList) -> {
376-
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
377-
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings))
378-
.changePrincipalsFilter(filtersList);
379-
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
380-
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
381-
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_REALMS, (policyName, filtersList) -> {
382-
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
383-
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeRealmsFilter(filtersList);
384-
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
385-
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
386-
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_ROLES, (policyName, filtersList) -> {
387-
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
388-
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeRolesFilter(filtersList);
389-
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
390-
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
391-
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_INDICES, (policyName, filtersList) -> {
392-
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
393-
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeIndicesFilter(filtersList);
394-
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
395-
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
396-
clusterService.getClusterSettings().addAffixUpdateConsumer(FILTER_POLICY_IGNORE_ACTIONS, (policyName, filtersList) -> {
397-
final Optional<EventFilterPolicy> policy = eventFilterPolicyRegistry.get(policyName);
398-
final EventFilterPolicy newPolicy = policy.orElse(new EventFilterPolicy(policyName, settings)).changeActionsFilter(filtersList);
399-
this.eventFilterPolicyRegistry.set(policyName, newPolicy);
400-
}, (policyName, filtersList) -> EventFilterPolicy.parsePredicate(filtersList));
376+
clusterService.getClusterSettings()
377+
.addAffixGroupUpdateConsumer(
378+
org.elasticsearch.core.List.of(
379+
FILTER_POLICY_IGNORE_PRINCIPALS,
380+
FILTER_POLICY_IGNORE_REALMS,
381+
FILTER_POLICY_IGNORE_ROLES,
382+
FILTER_POLICY_IGNORE_INDICES,
383+
FILTER_POLICY_IGNORE_ACTIONS
384+
),
385+
(policyName, updatedSettings) -> {
386+
if (updatedSettings.keySet().isEmpty()) {
387+
this.eventFilterPolicyRegistry.remove(policyName);
388+
} else {
389+
this.eventFilterPolicyRegistry.set(policyName, new EventFilterPolicy(policyName, updatedSettings));
390+
}
391+
}
392+
);
393+
401394
// this log filter ensures that audit events are not filtered out because of the log level
402395
final LoggerContext ctx = LoggerContext.getContext(false);
403396
MarkerFilter auditMarkerFilter = MarkerFilter.createFilter(AUDIT_MARKER.getName(), Result.ACCEPT, Result.NEUTRAL);
@@ -1569,92 +1562,21 @@ private static final class EventFilterPolicy {
15691562
private final Predicate<String> ignoreIndicesPredicate;
15701563
private final Predicate<String> ignoreActionsPredicate;
15711564

1572-
EventFilterPolicy(String name, Settings settings) {
1573-
this(
1574-
name,
1575-
parsePredicate(FILTER_POLICY_IGNORE_PRINCIPALS.getConcreteSettingForNamespace(name).get(settings)),
1576-
parsePredicate(FILTER_POLICY_IGNORE_REALMS.getConcreteSettingForNamespace(name).get(settings)),
1577-
parsePredicate(FILTER_POLICY_IGNORE_ROLES.getConcreteSettingForNamespace(name).get(settings)),
1578-
parsePredicate(FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace(name).get(settings)),
1579-
parsePredicate(FILTER_POLICY_IGNORE_ACTIONS.getConcreteSettingForNamespace(name).get(settings))
1580-
);
1581-
}
1582-
15831565
/**
15841566
* An empty filter list for a field will match events with that field missing.
15851567
* An event with an undefined field has the field value the empty string ("") or
15861568
* a singleton list of the empty string ([""]).
15871569
*/
1588-
EventFilterPolicy(
1589-
String name,
1590-
Predicate<String> ignorePrincipalsPredicate,
1591-
Predicate<String> ignoreRealmsPredicate,
1592-
Predicate<String> ignoreRolesPredicate,
1593-
Predicate<String> ignoreIndicesPredicate,
1594-
Predicate<String> ignoreActionsPredicate
1595-
) {
1570+
EventFilterPolicy(String name, Settings settings) {
15961571
this.name = name;
15971572
// "null" values are "unexpected" and should not match any ignore policy
1598-
this.ignorePrincipalsPredicate = ignorePrincipalsPredicate;
1599-
this.ignoreRealmsPredicate = ignoreRealmsPredicate;
1600-
this.ignoreRolesPredicate = ignoreRolesPredicate;
1601-
this.ignoreIndicesPredicate = ignoreIndicesPredicate;
1602-
this.ignoreActionsPredicate = ignoreActionsPredicate;
1603-
}
1604-
1605-
private EventFilterPolicy changePrincipalsFilter(List<String> filtersList) {
1606-
return new EventFilterPolicy(
1607-
name,
1608-
parsePredicate(filtersList),
1609-
ignoreRealmsPredicate,
1610-
ignoreRolesPredicate,
1611-
ignoreIndicesPredicate,
1612-
ignoreActionsPredicate
1613-
);
1614-
}
1615-
1616-
private EventFilterPolicy changeRealmsFilter(List<String> filtersList) {
1617-
return new EventFilterPolicy(
1618-
name,
1619-
ignorePrincipalsPredicate,
1620-
parsePredicate(filtersList),
1621-
ignoreRolesPredicate,
1622-
ignoreIndicesPredicate,
1623-
ignoreActionsPredicate
1624-
);
1625-
}
1626-
1627-
private EventFilterPolicy changeRolesFilter(List<String> filtersList) {
1628-
return new EventFilterPolicy(
1629-
name,
1630-
ignorePrincipalsPredicate,
1631-
ignoreRealmsPredicate,
1632-
parsePredicate(filtersList),
1633-
ignoreIndicesPredicate,
1634-
ignoreActionsPredicate
1635-
);
1636-
}
1637-
1638-
private EventFilterPolicy changeIndicesFilter(List<String> filtersList) {
1639-
return new EventFilterPolicy(
1640-
name,
1641-
ignorePrincipalsPredicate,
1642-
ignoreRealmsPredicate,
1643-
ignoreRolesPredicate,
1644-
parsePredicate(filtersList),
1645-
ignoreActionsPredicate
1646-
);
1647-
}
1648-
1649-
private EventFilterPolicy changeActionsFilter(List<String> filtersList) {
1650-
return new EventFilterPolicy(
1651-
name,
1652-
ignorePrincipalsPredicate,
1653-
ignoreRealmsPredicate,
1654-
ignoreRolesPredicate,
1655-
ignoreIndicesPredicate,
1656-
parsePredicate(filtersList)
1573+
this.ignorePrincipalsPredicate = parsePredicate(
1574+
FILTER_POLICY_IGNORE_PRINCIPALS.getConcreteSettingForNamespace(name).get(settings)
16571575
);
1576+
this.ignoreRealmsPredicate = parsePredicate(FILTER_POLICY_IGNORE_REALMS.getConcreteSettingForNamespace(name).get(settings));
1577+
this.ignoreRolesPredicate = parsePredicate(FILTER_POLICY_IGNORE_ROLES.getConcreteSettingForNamespace(name).get(settings));
1578+
this.ignoreIndicesPredicate = parsePredicate(FILTER_POLICY_IGNORE_INDICES.getConcreteSettingForNamespace(name).get(settings));
1579+
this.ignoreActionsPredicate = parsePredicate(FILTER_POLICY_IGNORE_ACTIONS.getConcreteSettingForNamespace(name).get(settings));
16581580
}
16591581

16601582
static Predicate<String> parsePredicate(List<String> l) {
@@ -1725,16 +1647,18 @@ private EventFilterPolicyRegistry(Settings settings) {
17251647
predicate = buildIgnorePredicate(policyMap);
17261648
}
17271649

1728-
private Optional<EventFilterPolicy> get(String policyName) {
1729-
return Optional.ofNullable(policyMap.get(policyName));
1730-
}
1731-
17321650
private synchronized void set(String policyName, EventFilterPolicy eventFilterPolicy) {
17331651
policyMap = MapBuilder.newMapBuilder(policyMap).put(policyName, eventFilterPolicy).immutableMap();
17341652
// precompute predicate
17351653
predicate = buildIgnorePredicate(policyMap);
17361654
}
17371655

1656+
private synchronized void remove(String policyName) {
1657+
policyMap = Maps.copyMapWithRemovedEntry(policyMap, policyName);
1658+
// precompute predicate
1659+
predicate = buildIgnorePredicate(policyMap);
1660+
}
1661+
17381662
Predicate<AuditEventMetaInfo> ignorePredicate() {
17391663
return predicate;
17401664
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/audit/logfile/LoggingAuditTrailFilterTests.java

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ public class LoggingAuditTrailFilterTests extends ESTestCase {
7272
private Settings settings;
7373
private DiscoveryNode localNode;
7474
private ClusterService clusterService;
75+
private ClusterSettings clusterSettings;
7576
private ApiKeyService apiKeyService;
7677

7778
@Before
@@ -87,7 +88,7 @@ public void init() throws Exception {
8788
when(localNode.getHostAddress()).thenReturn(buildNewFakeTransportAddress().toString());
8889
clusterService = mock(ClusterService.class);
8990
when(clusterService.localNode()).thenReturn(localNode);
90-
final ClusterSettings clusterSettings = mockClusterSettings();
91+
clusterSettings = mockClusterSettings();
9192
when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
9293
Mockito.doAnswer((Answer) invocation -> {
9394
final LoggingAuditTrail arg0 = (LoggingAuditTrail) invocation.getArguments()[0];
@@ -2723,6 +2724,110 @@ public void testActionsFilter() throws Exception {
27232724
threadContext.stashContext();
27242725
}
27252726

2727+
public void testRemoveIgnoreFilter() throws IllegalAccessException, IOException {
2728+
final Logger logger = CapturingLogger.newCapturingLogger(Level.INFO, null);
2729+
final ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
2730+
2731+
final String policyName = randomAlphaOfLengthBetween(5, 8);
2732+
final List<String> filteredUsers = randomNonEmptyListOfFilteredNames();
2733+
final List<String> filteredRoles = randomNonEmptyListOfFilteredNames();
2734+
final List<String> filteredRealms = randomNonEmptyListOfFilteredNames();
2735+
final List<String> filteredIndices = randomNonEmptyListOfFilteredNames();
2736+
final List<String> filteredActions = randomNonEmptyListOfFilteredActions();
2737+
2738+
// First create an auditTrail with no filtering
2739+
final LoggingAuditTrail auditTrail = new LoggingAuditTrail(
2740+
Settings.builder().put(settings).build(),
2741+
clusterService,
2742+
logger,
2743+
threadContext
2744+
);
2745+
final List<String> logOutput = CapturingLogger.output(logger.getName(), Level.INFO);
2746+
2747+
// First create a working ignore filter
2748+
final Settings.Builder settingsBuilder = Settings.builder();
2749+
final String username;
2750+
if (randomBoolean()) {
2751+
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".users", filteredUsers);
2752+
username = randomFrom(filteredUsers);
2753+
} else {
2754+
username = null;
2755+
}
2756+
2757+
final String realmName;
2758+
if (randomBoolean()) {
2759+
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".realms", filteredRealms);
2760+
realmName = randomFrom(filteredRealms);
2761+
} else {
2762+
realmName = null;
2763+
}
2764+
2765+
final String roleName;
2766+
if (randomBoolean()) {
2767+
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".roles", filteredRoles);
2768+
roleName = randomFrom(filteredRoles);
2769+
} else {
2770+
roleName = null;
2771+
}
2772+
2773+
final String indexName;
2774+
if (randomBoolean()) {
2775+
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".indices", filteredIndices);
2776+
indexName = randomFrom(filteredIndices);
2777+
} else {
2778+
indexName = null;
2779+
}
2780+
2781+
// If nothing is filtered so far due to randomisation, always filter on action name
2782+
final String actionName;
2783+
if (randomBoolean() || (username == null && realmName == null && roleName == null && indexName == null)) {
2784+
settingsBuilder.putList("xpack.security.audit.logfile.events.ignore_filters." + policyName + ".actions", filteredActions);
2785+
actionName = randomFrom(filteredActions);
2786+
} else {
2787+
actionName = null;
2788+
}
2789+
2790+
final String requestId = randomAlphaOfLength(10);
2791+
final Authentication authentication = new Authentication(
2792+
new User(
2793+
username != null ? username : randomAlphaOfLengthBetween(3, 10),
2794+
roleName != null ? roleName : randomAlphaOfLengthBetween(3, 10)
2795+
),
2796+
new RealmRef(
2797+
realmName != null ? realmName : randomAlphaOfLengthBetween(3, 10),
2798+
randomAlphaOfLengthBetween(3, 10),
2799+
randomAlphaOfLengthBetween(3, 8)
2800+
),
2801+
null
2802+
);
2803+
final MockIndicesRequest request = new MockIndicesRequest(
2804+
threadContext,
2805+
indexName != null ? indexName : randomAlphaOfLengthBetween(3, 10)
2806+
);
2807+
final AuthorizationInfo authorizationInfo = authzInfo(authentication.getUser().roles());
2808+
final String action = actionName != null ? actionName : randomAlphaOfLengthBetween(3, 10);
2809+
2810+
// Filter not created yet, message should be logged
2811+
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
2812+
assertThat("AccessGranted message: should not filter since we have no filter", logOutput.size(), is(1));
2813+
logOutput.clear();
2814+
threadContext.stashContext();
2815+
2816+
// Create the filter, the same message should be filtered
2817+
clusterSettings.applySettings(settingsBuilder.build());
2818+
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
2819+
assertThat("AccessGranted message: should be filtered out", logOutput.size(), is(0));
2820+
logOutput.clear();
2821+
threadContext.stashContext();
2822+
2823+
// Remove the filter, the message is logged again
2824+
clusterSettings.applySettings(Settings.EMPTY);
2825+
auditTrail.accessGranted(requestId, authentication, action, request, authorizationInfo);
2826+
assertThat("AccessGranted message: should not filter since filter is removed", logOutput.size(), is(1));
2827+
logOutput.clear();
2828+
threadContext.stashContext();
2829+
}
2830+
27262831
private InetSocketAddress randomLoopbackInetSocketAddress() {
27272832
return new InetSocketAddress(InetAddress.getLoopbackAddress(), randomIntBetween(0, 65535));
27282833
}

0 commit comments

Comments
 (0)