All logging audit settings updateable (elastic/x-pack-elasticsearch#4227)

All logging audit settings are update-able via cluster settings
update API (prefix.emit_node_host_address,
prefix.emit_node_host_name, prefix.emit_node_name, events.include,
events.exclude).

Original commit: elastic/x-pack-elasticsearch@96adbd0ae2
This commit is contained in:
Albert Zaharovits 2018-03-28 21:46:28 +03:00 committed by GitHub
parent 00c391602d
commit 53436450c4
3 changed files with 113 additions and 28 deletions

View File

@ -21,6 +21,7 @@ import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.node.Node;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportMessage;
@ -72,11 +73,11 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail,
public static final String NAME = "logfile";
public static final Setting<Boolean> HOST_ADDRESS_SETTING =
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_address"), false, Property.NodeScope);
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_address"), false, Property.NodeScope, Property.Dynamic);
public static final Setting<Boolean> HOST_NAME_SETTING =
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_name"), false, Property.NodeScope);
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_host_name"), false, Property.NodeScope, Property.Dynamic);
public static final Setting<Boolean> NODE_NAME_SETTING =
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_name"), true, Property.NodeScope);
Setting.boolSetting(setting("audit.logfile.prefix.emit_node_name"), true, Property.NodeScope, Property.Dynamic);
private static final List<String> DEFAULT_EVENT_INCLUDES = Arrays.asList(
ACCESS_DENIED.toString(),
ACCESS_GRANTED.toString(),
@ -87,35 +88,37 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail,
RUN_AS_DENIED.toString(),
RUN_AS_GRANTED.toString()
);
private static final Setting<List<String>> INCLUDE_EVENT_SETTINGS =
Setting.listSetting(setting("audit.logfile.events.include"), DEFAULT_EVENT_INCLUDES, Function.identity(), Property.NodeScope);
private static final Setting<List<String>> EXCLUDE_EVENT_SETTINGS =
Setting.listSetting(setting("audit.logfile.events.exclude"), Collections.emptyList(), Function.identity(), Property.NodeScope);
private static final Setting<Boolean> INCLUDE_REQUEST_BODY =
Setting.boolSetting(setting("audit.logfile.events.emit_request_body"), false, Property.NodeScope);
public static final Setting<List<String>> INCLUDE_EVENT_SETTINGS =
Setting.listSetting(setting("audit.logfile.events.include"), DEFAULT_EVENT_INCLUDES, Function.identity(), Property.NodeScope,
Property.Dynamic);
public static final Setting<List<String>> EXCLUDE_EVENT_SETTINGS =
Setting.listSetting(setting("audit.logfile.events.exclude"), Collections.emptyList(), Function.identity(), Property.NodeScope,
Property.Dynamic);
public static final Setting<Boolean> 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<List<String>> FILTER_POLICY_IGNORE_PRINCIPALS =
Setting.affixKeySetting(FILTER_POLICY_PREFIX, "users", (key) -> Setting.listSetting(key, Collections.singletonList("*"),
Function.identity(), Setting.Property.NodeScope, Setting.Property.Dynamic));
Function.identity(), Property.NodeScope, Property.Dynamic));
private static final Setting.AffixSetting<List<String>> FILTER_POLICY_IGNORE_REALMS =
Setting.affixKeySetting(FILTER_POLICY_PREFIX, "realms", (key) -> Setting.listSetting(key, Collections.singletonList("*"),
Function.identity(), Setting.Property.NodeScope, Setting.Property.Dynamic));
Function.identity(), Property.NodeScope, Property.Dynamic));
private static final Setting.AffixSetting<List<String>> FILTER_POLICY_IGNORE_ROLES =
Setting.affixKeySetting(FILTER_POLICY_PREFIX, "roles", (key) -> Setting.listSetting(key, Collections.singletonList("*"),
Function.identity(), Setting.Property.NodeScope, Setting.Property.Dynamic));
Function.identity(), Property.NodeScope, Property.Dynamic));
private static final Setting.AffixSetting<List<String>> FILTER_POLICY_IGNORE_INDICES =
Setting.affixKeySetting(FILTER_POLICY_PREFIX, "indices", (key) -> Setting.listSetting(key, Collections.singletonList("*"),
Function.identity(), Setting.Property.NodeScope, Setting.Property.Dynamic));
Function.identity(), Property.NodeScope, Property.Dynamic));
private final Logger logger;
private final EnumSet<AuditLevel> events;
private final boolean includeRequestBody;
// protected for testing
final EventFilterPolicyRegistry eventFilterPolicyRegistry;
private final ThreadContext threadContext;
// protected for testing
volatile EnumSet<AuditLevel> events;
volatile boolean includeRequestBody;
volatile LocalNodeInfo localNodeInfo;
@Override
@ -136,6 +139,17 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail,
this.localNodeInfo = new LocalNodeInfo(settings, null);
this.eventFilterPolicyRegistry = new EventFilterPolicyRegistry(settings);
clusterService.addListener(this);
clusterService.getClusterSettings().addSettingsUpdateConsumer(newSettings -> {
this.includeRequestBody = INCLUDE_REQUEST_BODY.get(newSettings);
}, Arrays.asList(INCLUDE_REQUEST_BODY));
clusterService.getClusterSettings().addSettingsUpdateConsumer(newSettings -> {
this.events = parse(INCLUDE_EVENT_SETTINGS.get(newSettings), EXCLUDE_EVENT_SETTINGS.get(newSettings));
}, Arrays.asList(INCLUDE_EVENT_SETTINGS, EXCLUDE_EVENT_SETTINGS));
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);
}, Arrays.asList(HOST_ADDRESS_SETTING, HOST_NAME_SETTING, NODE_NAME_SETTING));
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))
@ -788,19 +802,21 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail,
void updateLocalNodeInfo(DiscoveryNode newLocalNode) {
// check if local node changed
final DiscoveryNode localNode = localNodeInfo.localNode;
if ((localNode == null) || (localNode.equals(newLocalNode) == false)) {
final LocalNodeInfo localNodeInfo = this.localNodeInfo;
if ((localNodeInfo.localNode == null) || (localNodeInfo.localNode.equals(newLocalNode) == false)) {
// no need to synchronize, called only from the cluster state applier thread
localNodeInfo = new LocalNodeInfo(settings, newLocalNode);
this.localNodeInfo = new LocalNodeInfo(localNodeInfo.settings, newLocalNode);
}
}
static class LocalNodeInfo {
private final Settings settings;
private final DiscoveryNode localNode;
private final String prefix;
final String prefix;
private final String localOriginTag;
LocalNodeInfo(Settings settings, @Nullable DiscoveryNode newLocalNode) {
this.settings = settings;
this.localNode = newLocalNode;
this.prefix = resolvePrefix(settings, newLocalNode);
this.localOriginTag = localOriginTag(newLocalNode);
@ -821,7 +837,7 @@ public class LoggingAuditTrail extends AbstractComponent implements AuditTrail,
}
}
if (NODE_NAME_SETTING.get(settings)) {
final String name = settings.get("name");
final String name = Node.NODE_NAME_SETTING.get(settings);
if (name != null) {
builder.append("[").append(name).append("] ");
}

View File

@ -12,13 +12,17 @@ import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.SecurityIntegTestCase;
import org.elasticsearch.xpack.security.audit.AuditLevel;
import org.elasticsearch.xpack.security.audit.AuditTrailService;
import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.junit.BeforeClass;
import java.util.ArrayList;
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;
@ -26,7 +30,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ClusterScope(scope = TEST, numDataNodes = 1)
public class AuditTrailFilteringUpdateTests extends SecurityIntegTestCase {
public class AuditTrailSettingsUpdateTests extends SecurityIntegTestCase {
private static Settings startupFilterSettings;
private static Settings updateFilterSettings;
@ -64,7 +68,7 @@ public class AuditTrailFilteringUpdateTests extends SecurityIntegTestCase {
return settingsBuilder.build();
}
public void testDynamicSettings() throws Exception {
public void testDynamicFilterSettings() throws Exception {
final ClusterService clusterService = mock(ClusterService.class);
final ClusterSettings clusterSettings = mockClusterSettings();
when(clusterService.getClusterSettings()).thenReturn(clusterSettings);
@ -76,7 +80,7 @@ public class AuditTrailFilteringUpdateTests extends SecurityIntegTestCase {
final LoggingAuditTrail auditTrail = new LoggingAuditTrail(settingsBuilder.build(), clusterService, logger, threadContext);
final String expected = auditTrail.eventFilterPolicyRegistry.toString();
// update settings on internal cluster
updateSettings(updateFilterSettings);
updateSettings(updateFilterSettings, randomBoolean());
final String actual = ((LoggingAuditTrail) internalCluster().getInstances(AuditTrailService.class)
.iterator()
.next()
@ -86,7 +90,7 @@ public class AuditTrailFilteringUpdateTests extends SecurityIntegTestCase {
assertEquals(expected, actual);
}
public void testInvalidSettings() throws Exception {
public void testInvalidFilterSettings() throws Exception {
final String invalidLuceneRegex = "/invalid";
final Settings.Builder settingsBuilder = Settings.builder();
final String[] allSettingsKeys = new String[] { "xpack.security.audit.logfile.events.ignore_filters.invalid.users",
@ -99,8 +103,72 @@ public class AuditTrailFilteringUpdateTests extends SecurityIntegTestCase {
assertThat(e.getMessage(), containsString("illegal value can't update"));
}
private void updateSettings(Settings settings) {
if (randomBoolean()) {
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);
updateSettings(settingsBuilder.build(), persistent);
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);
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);
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);
updateSettings(settingsBuilder.build(), persistent);
assertTrue(Pattern.matches("\\[127\\.0\\.0\\.1\\] \\[127\\.0\\.0\\.1\\] ", loggingAuditTrail.localNodeInfo.prefix));
}
public void testDynamicRequestBodySettings() {
final boolean persistent = randomBoolean();
final boolean enableRequestBody = randomBoolean();
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)
.iterator()
.next()
.getAuditTrails()
.iterator()
.next());
assertEquals(enableRequestBody, loggingAuditTrail.includeRequestBody);
settingsBuilder.put(LoggingAuditTrail.INCLUDE_REQUEST_BODY.getKey(), !enableRequestBody);
updateSettings(settingsBuilder.build(), persistent);
assertEquals(!enableRequestBody, loggingAuditTrail.includeRequestBody);
}
public void testDynamicEventsSettings() {
final List<String> allEventTypes = Arrays.asList("anonymous_access_denied", "authentication_failed", "realm_authentication_failed",
"access_granted", "access_denied", "tampered_request", "connection_granted", "connection_denied", "system_access_granted",
"authentication_success", "run_as_granted", "run_as_denied");
final List<String> includedEvents = randomSubsetOf(allEventTypes);
final List<String> excludedEvents = randomSubsetOf(allEventTypes);
final Settings.Builder settingsBuilder = Settings.builder();
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)
.iterator()
.next()
.getAuditTrails()
.iterator()
.next());
assertEquals(AuditLevel.parse(includedEvents, excludedEvents), loggingAuditTrail.events);
}
private void updateSettings(Settings settings, boolean persistent) {
if (persistent) {
assertAcked(client().admin().cluster().prepareUpdateSettings().setPersistentSettings(settings));
} else {
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));

View File

@ -552,7 +552,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
List<Map<String, Object>> logs = new ArrayList<>();
String line;
Pattern logPattern = Pattern.compile(
("PART PART PART origin_type=PART, origin_address=PART, principal=PART, realm=PART, "
("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", "\\[[^\\]]*\\]"));
@ -567,6 +567,7 @@ public abstract class SqlSecurityTestCase extends ESRestTestCase {
/* 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))) {