Performance: Only iterate ip filter rules per profile

This is a little cleanup to only iterate IP filter rules for each
profile instead of iterating all of them and check for the profile
inside of the rule.

Original commit: elastic/x-pack-elasticsearch@6774f1f165
This commit is contained in:
Alexander Reelsen 2014-12-03 17:58:49 +01:00
parent 150ac97ffe
commit 2aafcf40dd
9 changed files with 192 additions and 143 deletions

View File

@ -8,7 +8,7 @@ package org.elasticsearch.shield.audit;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.transport.filter.ProfileIpFilterRule;
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
@ -65,11 +65,11 @@ public interface AuditTrail {
}
@Override
public void connectionGranted(InetAddress inetAddress, ProfileIpFilterRule rule) {
public void connectionGranted(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
}
@Override
public void connectionDenied(InetAddress inetAddress, ProfileIpFilterRule rule) {
public void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
}
};
@ -93,7 +93,7 @@ public interface AuditTrail {
void tamperedRequest(User user, String action, TransportRequest request);
void connectionGranted(InetAddress inetAddress, ProfileIpFilterRule rule);
void connectionGranted(InetAddress inetAddress, String profile, ShieldIpFilterRule rule);
void connectionDenied(InetAddress inetAddress, ProfileIpFilterRule rule);
void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule);
}

View File

@ -11,7 +11,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.transport.filter.ProfileIpFilterRule;
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
@ -100,16 +100,16 @@ public class AuditTrailService extends AbstractComponent implements AuditTrail {
}
@Override
public void connectionGranted(InetAddress inetAddress, ProfileIpFilterRule rule) {
public void connectionGranted(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.connectionGranted(inetAddress, rule);
auditTrail.connectionGranted(inetAddress, profile, rule);
}
}
@Override
public void connectionDenied(InetAddress inetAddress, ProfileIpFilterRule rule) {
public void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
for (AuditTrail auditTrail : auditTrails) {
auditTrail.connectionDenied(inetAddress, rule);
auditTrail.connectionDenied(inetAddress, profile, rule);
}
}
}

View File

@ -17,7 +17,7 @@ import org.elasticsearch.shield.User;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.authz.Privilege;
import org.elasticsearch.shield.transport.filter.ProfileIpFilterRule;
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
import org.elasticsearch.transport.TransportMessage;
import org.elasticsearch.transport.TransportRequest;
@ -189,19 +189,15 @@ public class LoggingAuditTrail implements AuditTrail {
}
@Override
public void connectionGranted(InetAddress inetAddress, ProfileIpFilterRule rule) {
public void connectionGranted(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
if (logger.isTraceEnabled()) {
logger.trace("CONNECTION_GRANTED\thost=[{}], rule=[{}]", inetAddress.getHostAddress(), rule);
logger.trace("CONNECTION_GRANTED\thost=[{}], profile=[{}], rule=[{}]", inetAddress.getHostAddress(), profile, rule);
}
}
@Override
public void connectionDenied(InetAddress inetAddress, ProfileIpFilterRule rule) {
if (logger.isDebugEnabled()) {
logger.debug("CONNECTION_DENIED\thost=[{}], rule=[{}]", inetAddress.getHostAddress(), rule);
} else {
logger.error("CONNECTION_DENIED\thost=[{}]", inetAddress.getHostAddress());
}
public void connectionDenied(InetAddress inetAddress, String profile, ShieldIpFilterRule rule) {
logger.error("CONNECTION_DENIED\thost=[{}], profile=[{}], rule=[{}]", inetAddress.getHostAddress(), profile, rule);
}
private static String indices(TransportMessage message) {

View File

@ -6,23 +6,20 @@
package org.elasticsearch.shield.transport.filter;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.collect.Maps;
import org.elasticsearch.common.collect.ObjectArrays;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.jackson.dataformat.yaml.snakeyaml.error.YAMLException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.net.InetAddresses;
import org.elasticsearch.common.netty.handler.ipfilter.IpFilterRule;
import org.elasticsearch.common.netty.handler.ipfilter.IpSubnetFilterRule;
import org.elasticsearch.common.netty.handler.ipfilter.PatternRule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.audit.AuditTrail;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Collections;
import java.util.Map;
public class IPFilter extends AbstractComponent {
@ -35,13 +32,26 @@ public class IPFilter extends AbstractComponent {
*/
public static final String HTTP_PROFILE_NAME = ".http";
private static final ProfileIpFilterRule[] NO_RULES = new ProfileIpFilterRule[0];
private static final ProfileIpFilterRule ACCEPT_ALL_RULE = new ProfileIpFilterRule("default",
new PatternRule(true, "n:*"), "DEFAULT_ACCEPT_ALL");
public static final ShieldIpFilterRule DEFAULT_PROFILE_ACCEPT_ALL = new ShieldIpFilterRule(true, "default:accept_all") {
@Override
public boolean contains(InetAddress inetAddress) {
return true;
}
@Override
public boolean isAllowRule() {
return true;
}
@Override
public boolean isDenyRule() {
return false;
}
};
private final AuditTrail auditTrail;
private volatile ProfileIpFilterRule[] rules = NO_RULES;
private final Map<String, ShieldIpFilterRule[]> rules;
@Inject
public IPFilter(Settings settings, AuditTrail auditTrail) {
@ -51,75 +61,64 @@ public class IPFilter extends AbstractComponent {
}
public boolean accept(String profile, InetAddress peerAddress) {
if (rules == NO_RULES) {
if (!rules.containsKey(profile)) {
return true;
}
for (ProfileIpFilterRule rule : rules) {
if (rule.contains(profile, peerAddress)) {
for (ShieldIpFilterRule rule : rules.get(profile)) {
if (rule.contains(peerAddress)) {
boolean isAllowed = rule.isAllowRule();
if (isAllowed) {
auditTrail.connectionGranted(peerAddress, rule);
auditTrail.connectionGranted(peerAddress, profile, rule);
} else {
auditTrail.connectionDenied(peerAddress, rule);
auditTrail.connectionDenied(peerAddress, profile, rule);
}
return isAllowed;
}
}
auditTrail.connectionGranted(peerAddress, ACCEPT_ALL_RULE);
auditTrail.connectionGranted(peerAddress, profile, DEFAULT_PROFILE_ACCEPT_ALL);
return true;
}
private static ProfileIpFilterRule[] parseSettings(Settings settings, ESLogger logger) {
private static Map<String, ShieldIpFilterRule[]> parseSettings(Settings settings, ESLogger logger) {
if (!settings.getAsBoolean("shield.transport.filter.enabled", true)) {
return NO_RULES;
return Collections.EMPTY_MAP;
}
Map<String, ShieldIpFilterRule[]> profileRules = Maps.newHashMap();
String[] allowed = settings.getAsArray("shield.transport.filter.allow");
String[] denied = settings.getAsArray("shield.transport.filter.deny");
String[] httpAllowed = settings.getAsArray("shield.http.filter.allow", settings.getAsArray("transport.profiles.default.shield.filter.allow", settings.getAsArray("shield.transport.filter.allow")));
String[] httpDdenied = settings.getAsArray("shield.http.filter.deny", settings.getAsArray("transport.profiles.default.shield.filter.deny", settings.getAsArray("shield.transport.filter.deny")));
List<ProfileIpFilterRule> rules = new ArrayList<>();
try {
rules.addAll(parseValue(allowed, "default", true));
rules.addAll(parseValue(denied, "default", false));
rules.addAll(parseValue(httpAllowed, HTTP_PROFILE_NAME, true));
rules.addAll(parseValue(httpDdenied, HTTP_PROFILE_NAME, false));
profileRules.put("default", ObjectArrays.concat(parseValue(allowed, true), parseValue(denied, false), ShieldIpFilterRule.class));
profileRules.put(HTTP_PROFILE_NAME, ObjectArrays.concat(parseValue(httpAllowed, true), parseValue(httpDdenied, false), ShieldIpFilterRule.class));
Map<String, Settings> groupedSettings = settings.getGroups("transport.profiles.");
for (Map.Entry<String, Settings> entry : groupedSettings.entrySet()) {
String profile = entry.getKey();
Settings profileSettings = entry.getValue().getByPrefix("shield.filter.");
rules.addAll(parseValue(profileSettings.getAsArray("allow"), profile, true));
rules.addAll(parseValue(profileSettings.getAsArray("deny"), profile, false));
profileRules.put(profile, ObjectArrays.concat(
parseValue(profileSettings.getAsArray("allow"), true),
parseValue(profileSettings.getAsArray("deny"), false),
ShieldIpFilterRule.class));
}
} catch (IOException | YAMLException e) {
throw new ElasticsearchParseException("Failed to read & parse rules from settings", e);
}
logger.debug("Loaded {} ip filtering rules", rules.size());
return rules.toArray(new ProfileIpFilterRule[rules.size()]);
logger.debug("Loaded ip filtering profiles: {}", profileRules.keySet());
return ImmutableMap.copyOf(profileRules);
}
private static Collection<? extends ProfileIpFilterRule> parseValue(String[] values, String profile, boolean isAllowRule) throws UnknownHostException {
List<ProfileIpFilterRule> rules = new ArrayList<>();
for (String value : values) {
rules.add(new ProfileIpFilterRule(profile, getRule(isAllowRule, value), value));
private static ShieldIpFilterRule[] parseValue(String[] values, boolean isAllowRule) throws UnknownHostException {
ShieldIpFilterRule[] rules = new ShieldIpFilterRule[values.length];
for (int i = 0; i < values.length; i++) {
rules[i] = new ShieldIpFilterRule(isAllowRule, values[i]);
}
return rules;
}
private static IpFilterRule getRule(boolean isAllowRule, String value) throws UnknownHostException {
if ("_all".equals(value)) {
return new PatternRule(isAllowRule, "n:*");
} else if (value.contains("/")) {
return new IpSubnetFilterRule(isAllowRule, value);
}
boolean isInetAddress = InetAddresses.isInetAddress(value);
String prefix = isInetAddress ? "i:" : "n:";
return new PatternRule(isAllowRule, prefix + value);
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.transport.filter;
import org.elasticsearch.common.netty.handler.ipfilter.IpFilterRule;
import java.net.InetAddress;
/**
* helper interface for filter rules, which takes a tcp transport profile into account
*/
public class ProfileIpFilterRule {
private final String profile;
private final IpFilterRule ipFilterRule;
private final String ruleSpec;
public ProfileIpFilterRule(String profile, IpFilterRule ipFilterRule, String ruleSpec) {
this.profile = profile;
this.ipFilterRule = ipFilterRule;
this.ruleSpec = ruleSpec;
}
public boolean contains(String profile, InetAddress inetAddress) {
return this.profile.equals(profile) && ipFilterRule.contains(inetAddress);
}
public boolean isAllowRule() {
return ipFilterRule.isAllowRule();
}
public boolean isDenyRule() {
return ipFilterRule.isDenyRule();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("profile=[");
builder.append(profile);
builder.append("], rule=[");
if (isAllowRule()) {
builder.append("allow ");
} else {
builder.append("deny ");
}
builder.append(ruleSpec);
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.shield.transport.filter;
import org.elasticsearch.common.net.InetAddresses;
import org.elasticsearch.common.netty.handler.ipfilter.IpFilterRule;
import org.elasticsearch.common.netty.handler.ipfilter.IpSubnetFilterRule;
import org.elasticsearch.common.netty.handler.ipfilter.PatternRule;
import org.elasticsearch.shield.ShieldException;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* decorator class to have a useful toString() method for an IpFilterRule
* as this is needed for audit logging
*/
public class ShieldIpFilterRule implements IpFilterRule {
public static final ShieldIpFilterRule ACCEPT_ALL = new ShieldIpFilterRule(true, "accept_all") {
@Override
public boolean contains(InetAddress inetAddress) {
return true;
}
@Override
public boolean isAllowRule() {
return true;
}
@Override
public boolean isDenyRule() {
return false;
}
};
public static final ShieldIpFilterRule DENY_ALL = new ShieldIpFilterRule(true, "deny_all") {
@Override
public boolean contains(InetAddress inetAddress) {
return true;
}
@Override
public boolean isAllowRule() {
return false;
}
@Override
public boolean isDenyRule() {
return true;
}
};
private final IpFilterRule ipFilterRule;
private final String ruleSpec;
public ShieldIpFilterRule(boolean isAllowRule, String ruleSpec) {
this.ipFilterRule = getRule(isAllowRule, ruleSpec);
this.ruleSpec = ruleSpec;
}
@Override
public boolean contains(InetAddress inetAddress) {
return ipFilterRule.contains(inetAddress);
}
@Override
public boolean isAllowRule() {
return ipFilterRule.isAllowRule();
}
@Override
public boolean isDenyRule() {
return ipFilterRule.isDenyRule();
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isAllowRule()) {
builder.append("allow ");
} else {
builder.append("deny ");
}
builder.append(ruleSpec);
return builder.toString();
}
private static IpFilterRule getRule(boolean isAllowRule, String value) {
if ("_all".equals(value)) {
return isAllowRule ? ACCEPT_ALL : DENY_ALL;
}
if (value.contains("/")) {
try {
return new IpSubnetFilterRule(isAllowRule, value);
} catch (UnknownHostException e) {
throw new ShieldException("Unable to subnet shield filter for [" + value + "] with rule[" + isAllowRule + "]", e);
}
}
boolean isInetAddress = InetAddresses.isInetAddress(value);
String prefix = isInetAddress ? "i:" : "n:";
return new PatternRule(isAllowRule, prefix + value);
}
}

View File

@ -6,12 +6,12 @@
package org.elasticsearch.shield.audit;
import com.google.common.collect.ImmutableSet;
import org.elasticsearch.common.netty.handler.ipfilter.PatternRule;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.transport.filter.ProfileIpFilterRule;
import org.elasticsearch.shield.transport.filter.IPFilter;
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportMessage;
import org.junit.Before;
@ -109,20 +109,20 @@ public class AuditTrailServiceTests extends ElasticsearchTestCase {
@Test
public void testConnectionGranted() throws Exception {
InetAddress inetAddress = InetAddress.getLocalHost();
ProfileIpFilterRule rule = new ProfileIpFilterRule("client", new PatternRule(true, "i:*"), "all");
service.connectionGranted(inetAddress, rule);
ShieldIpFilterRule rule = randomBoolean() ? ShieldIpFilterRule.ACCEPT_ALL : IPFilter.DEFAULT_PROFILE_ACCEPT_ALL;
service.connectionGranted(inetAddress, "client", rule);
for (AuditTrail auditTrail : auditTrails) {
verify(auditTrail).connectionGranted(inetAddress, rule);
verify(auditTrail).connectionGranted(inetAddress, "client", rule);
}
}
@Test
public void testConnectionDenied() throws Exception {
InetAddress inetAddress = InetAddress.getLocalHost();
ProfileIpFilterRule rule = new ProfileIpFilterRule("client", new PatternRule(false, "i:*"), "all");
service.connectionDenied(inetAddress, rule);
ShieldIpFilterRule rule = new ShieldIpFilterRule(false, "_all");
service.connectionDenied(inetAddress, "client", rule);
for (AuditTrail auditTrail : auditTrails) {
verify(auditTrail).connectionDenied(inetAddress, rule);
verify(auditTrail).connectionDenied(inetAddress, "client", rule);
}
}
}

View File

@ -7,12 +7,12 @@ package org.elasticsearch.shield.audit.logfile;
import org.elasticsearch.action.IndicesRequest;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.netty.handler.ipfilter.PatternRule;
import org.elasticsearch.common.transport.LocalTransportAddress;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.shield.User;
import org.elasticsearch.shield.authc.AuthenticationToken;
import org.elasticsearch.shield.transport.filter.ProfileIpFilterRule;
import org.elasticsearch.shield.transport.filter.IPFilter;
import org.elasticsearch.shield.transport.filter.ShieldIpFilterRule;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.TransportMessage;
import org.junit.Test;
@ -275,20 +275,17 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
InetAddress inetAddress = InetAddress.getLocalHost();
ProfileIpFilterRule rule = new ProfileIpFilterRule("default", new PatternRule(false, "i:*"), "_all");
auditTrail.connectionDenied(inetAddress, rule);
ShieldIpFilterRule rule = new ShieldIpFilterRule(false, "_all");
auditTrail.connectionDenied(inetAddress, "default", rule);
switch (level) {
case ERROR:
assertMsg(logger, Level.ERROR, String.format(Locale.ROOT, "CONNECTION_DENIED\thost=[%s], profile=[%s], rule=[deny %s]",
inetAddress.getHostAddress(), "default", "_all"));
break;
case WARN:
case INFO:
assertMsg(logger, Level.ERROR, String.format(Locale.ROOT, "CONNECTION_DENIED\thost=[%s]",
inetAddress.getHostAddress()));
break;
case DEBUG:
case TRACE:
assertMsg(logger, Level.DEBUG, String.format(Locale.ROOT,
"CONNECTION_DENIED\thost=[%s], rule=[profile=[default], rule=[deny _all]]",
inetAddress.getHostAddress()));
}
}
}
@ -299,8 +296,8 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
CapturingLogger logger = new CapturingLogger(level);
LoggingAuditTrail auditTrail = new LoggingAuditTrail(logger);
InetAddress inetAddress = InetAddress.getLocalHost();
ProfileIpFilterRule rule = new ProfileIpFilterRule("default", new PatternRule(true, "i:*"), "_all");
auditTrail.connectionGranted(inetAddress, rule);
ShieldIpFilterRule rule = IPFilter.DEFAULT_PROFILE_ACCEPT_ALL;
auditTrail.connectionGranted(inetAddress, "default", rule);
switch (level) {
case ERROR:
case WARN:
@ -310,7 +307,7 @@ public class LoggingAuditTrailTests extends ElasticsearchTestCase {
break;
case TRACE:
assertMsg(logger, Level.TRACE, String.format(Locale.ROOT,
"CONNECTION_GRANTED\thost=[%s], rule=[profile=[default], rule=[allow _all]]",
"CONNECTION_GRANTED\thost=[%s], profile=[default], rule=[allow default:accept_all]",
inetAddress.getHostAddress()));
}
}

View File

@ -153,8 +153,8 @@ public class IPFilterTests extends ElasticsearchTestCase {
String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress);
InetAddress address = InetAddresses.forString(inetAddress);
assertThat(message, ipFilter.accept(profile, address), is(true));
ArgumentCaptor<ProfileIpFilterRule> ruleCaptor = ArgumentCaptor.forClass(ProfileIpFilterRule.class);
verify(auditTrail).connectionGranted(eq(address), ruleCaptor.capture());
ArgumentCaptor<ShieldIpFilterRule> ruleCaptor = ArgumentCaptor.forClass(ShieldIpFilterRule.class);
verify(auditTrail).connectionGranted(eq(address), eq(profile), ruleCaptor.capture());
assertNotNull(ruleCaptor.getValue());
}
}
@ -168,8 +168,8 @@ public class IPFilterTests extends ElasticsearchTestCase {
String message = String.format(Locale.ROOT, "Expected address %s to be denied", inetAddress);
InetAddress address = InetAddresses.forString(inetAddress);
assertThat(message, ipFilter.accept(profile, address), is(false));
ArgumentCaptor<ProfileIpFilterRule> ruleCaptor = ArgumentCaptor.forClass(ProfileIpFilterRule.class);
verify(auditTrail).connectionDenied(eq(address), ruleCaptor.capture());
ArgumentCaptor<ShieldIpFilterRule> ruleCaptor = ArgumentCaptor.forClass(ShieldIpFilterRule.class);
verify(auditTrail).connectionDenied(eq(address), eq(profile), ruleCaptor.capture());
assertNotNull(ruleCaptor.getValue());
}
}