IP Filtering: Make IP filtering a dynamic setting

In order to be able to configure ip filtering in a dynamic way, all
the ip filter related settings have been made dynamic.

This commit also fixed a bug, as the setting shield.http.filter.enabled
was not working, but mentioned in the documentation.

Documentation has been updated along the way.

Closes elastic/elasticsearch#697

Original commit: elastic/x-pack-elasticsearch@2760c47b5b
This commit is contained in:
Alexander Reelsen 2015-03-02 18:12:45 +01:00
parent c8a169e3be
commit 6a6e44545c
6 changed files with 399 additions and 48 deletions

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.support.Headers;
import org.elasticsearch.cluster.settings.ClusterDynamicSettingsModule;
import org.elasticsearch.common.collect.ImmutableList;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Module;
@ -20,6 +21,7 @@ import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.authz.store.FileRolesStore;
import org.elasticsearch.shield.license.LicenseService;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.transport.filter.IPFilter;
import java.io.File;
import java.nio.file.Path;
@ -65,7 +67,7 @@ public class ShieldPlugin extends AbstractPlugin {
@Override
public Collection<Class<? extends LifecycleComponent>> services() {
return enabled && !clientMode ?
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, FileRolesStore.class, Realms.class, InternalSignatureService.class) :
ImmutableList.<Class<? extends LifecycleComponent>>of(LicenseService.class, FileRolesStore.class, Realms.class, InternalSignatureService.class, IPFilter.class) :
ImmutableList.<Class<? extends LifecycleComponent>>of();
}
@ -81,6 +83,10 @@ public class ShieldPlugin extends AbstractPlugin {
return settingsBuilder.build();
}
public void onModule(ClusterDynamicSettingsModule clusterDynamicSettingsModule) {
clusterDynamicSettingsModule.addDynamicSettings("shield.transport.filter.*", "shield.http.filter.*", "transport.profiles.*", IPFilter.IP_FILTER_ENABLED_SETTING, IPFilter.IP_FILTER_ENABLED_HTTP_SETTING);
}
private void addUserSettings(ImmutableSettings.Builder settingsBuilder) {
String authHeaderSettingName = Headers.PREFIX + "." + UsernamePasswordToken.BASIC_AUTH_HEADER;
if (settings.get(authHeaderSettingName) != null) {

View File

@ -5,24 +5,29 @@
*/
package org.elasticsearch.shield.transport.filter;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.collect.HppcMaps;
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.component.AbstractLifecycleComponent;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.component.LifecycleListener;
import org.elasticsearch.common.hppc.ObjectObjectOpenHashMap;
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.inject.internal.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.transport.Transport;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Map;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class IPFilter extends AbstractComponent {
public class IPFilter extends AbstractLifecycleComponent<IPFilter> {
/**
* .http has been chosen for handling HTTP filters, which are not part of the profiles
@ -32,6 +37,9 @@ public class IPFilter extends AbstractComponent {
*/
public static final String HTTP_PROFILE_NAME = ".http";
public static final String IP_FILTER_ENABLED_SETTING = "shield.transport.filter.enabled";
public static final String IP_FILTER_ENABLED_HTTP_SETTING = "shield.http.filter.enabled";
public static final ShieldIpFilterRule DEFAULT_PROFILE_ACCEPT_ALL = new ShieldIpFilterRule(true, "default:accept_all") {
@Override
public boolean contains(InetAddress inetAddress) {
@ -49,15 +57,61 @@ public class IPFilter extends AbstractComponent {
}
};
private final LifecycleListener parseSettingsListener = new LifecycleListener() {
@Override
public void afterStart() {
IPFilter.this.rules = IPFilter.this.parseSettings(settings);
}
};
private NodeSettingsService nodeSettingsService;
private final AuditTrail auditTrail;
private final Map<String, ShieldIpFilterRule[]> rules;
private final Transport transport;
private Map<String, ShieldIpFilterRule[]> rules = Collections.EMPTY_MAP;
private HttpServerTransport httpServerTransport = null;
@Inject
public IPFilter(Settings settings, AuditTrail auditTrail) {
public IPFilter(final Settings settings, AuditTrail auditTrail, NodeSettingsService nodeSettingsService, Transport transport) {
super(settings);
this.nodeSettingsService = nodeSettingsService;
this.auditTrail = auditTrail;
rules = parseSettings(settings, logger);
this.transport = transport;
}
@Override
protected void doStart() throws ElasticsearchException {
nodeSettingsService.addListener(new ApplySettings(settings));
if (transport.lifecycleState() == Lifecycle.State.STARTED) {
rules = parseSettings(settings);
} else {
transport.addLifecycleListener(parseSettingsListener);
}
}
@Override
protected void doStop() throws ElasticsearchException {
}
@Override
protected void doClose() throws ElasticsearchException {
}
// this cannot be put into the constructor as HTTP might be disabled
@Inject(optional = true)
public void setHttpServerTransport(@Nullable HttpServerTransport httpServerTransport) {
if (httpServerTransport == null) {
return;
}
this.httpServerTransport = httpServerTransport;
if (httpServerTransport.lifecycleState() == Lifecycle.State.STARTED) {
IPFilter.this.rules = IPFilter.this.parseSettings(settings);
} else {
httpServerTransport.addLifecycleListener(parseSettingsListener);
}
}
public boolean accept(String profile, InetAddress peerAddress) {
@ -81,44 +135,154 @@ public class IPFilter extends AbstractComponent {
return true;
}
private static Map<String, ShieldIpFilterRule[]> parseSettings(Settings settings, ESLogger logger) {
if (!settings.getAsBoolean("shield.transport.filter.enabled", true)) {
private Map<String, ShieldIpFilterRule[]> parseSettings(Settings settings) {
boolean isIpFilterEnabled = settings.getAsBoolean(IP_FILTER_ENABLED_SETTING, true);
boolean isHttpFilterEnabled = settings.getAsBoolean(IP_FILTER_ENABLED_HTTP_SETTING, isIpFilterEnabled);
if (!isIpFilterEnabled && !isHttpFilterEnabled) {
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")));
try {
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));
if (isHttpFilterEnabled && httpServerTransport != null && httpServerTransport.lifecycleState() == Lifecycle.State.STARTED) {
InetAddress localAddress = ((InetSocketTransportAddress) this.httpServerTransport.boundAddress().boundAddress()).address().getAddress();
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")));
profileRules.put(HTTP_PROFILE_NAME, ObjectArrays.concat(parseValue(httpAllowed, true, localAddress), parseValue(httpDdenied, false, localAddress), ShieldIpFilterRule.class));
}
if (isIpFilterEnabled && this.transport.lifecycleState() == Lifecycle.State.STARTED) {
InetAddress localAddress = ((InetSocketTransportAddress) this.transport.boundAddress().boundAddress()).address().getAddress();
String[] allowed = settings.getAsArray("shield.transport.filter.allow");
String[] denied = settings.getAsArray("shield.transport.filter.deny");
profileRules.put("default", ObjectArrays.concat(parseValue(allowed, true, localAddress), parseValue(denied, false, localAddress), 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.");
profileRules.put(profile, ObjectArrays.concat(
parseValue(profileSettings.getAsArray("allow"), true),
parseValue(profileSettings.getAsArray("deny"), false),
parseValue(profileSettings.getAsArray("allow"), true, localAddress),
parseValue(profileSettings.getAsArray("deny"), false, localAddress),
ShieldIpFilterRule.class));
}
} catch (IOException | YAMLException e) {
throw new ElasticsearchParseException("failed to read & parse rules from settings", e);
}
logger.debug("loaded ip filtering profiles: {}", profileRules.keySet());
return ImmutableMap.copyOf(profileRules);
}
private static ShieldIpFilterRule[] parseValue(String[] values, boolean isAllowRule) throws UnknownHostException {
ShieldIpFilterRule[] rules = new ShieldIpFilterRule[values.length];
private ShieldIpFilterRule[] parseValue(String[] values, boolean isAllowRule, InetAddress localAddress) {
List<ShieldIpFilterRule> rules = new ArrayList<>();
for (int i = 0; i < values.length; i++) {
rules[i] = new ShieldIpFilterRule(isAllowRule, values[i]);
// never ever deny on localhost, do not even add this rule
if (!isAllowRule && isLocalAddress(localAddress, values[i])) {
logger.warn("Configuration setting not applied to reject connections on [{}]. local connections are always allowed!", values[i]);
continue;
}
rules.add(new ShieldIpFilterRule(isAllowRule, values[i]));
}
return rules.toArray(new ShieldIpFilterRule[]{});
}
private boolean isLocalAddress(InetAddress localAddress, String address) {
return address.equals("127.0.0.1") || address.equals("localhost") || address.equals("::1") || address.startsWith("fe80::1") ||
address.equals(localAddress.getHostAddress()) || address.equals(localAddress.getHostName());
}
private class ApplySettings implements NodeSettingsService.Listener {
String[] allowed;
String[] denied;
String[] httpAllowed;
String[] httpDenied;
ObjectObjectOpenHashMap<String, String[]> profileAllowed;
ObjectObjectOpenHashMap<String, String[]> profileDenied;
private boolean enabled;
private boolean httpEnabled;
public ApplySettings(Settings settings) {
loadValuesFromSettings(settings);
}
private void loadValuesFromSettings(Settings settings) {
this.enabled = settings.getAsBoolean(IP_FILTER_ENABLED_SETTING, this.enabled);
this.httpEnabled = settings.getAsBoolean(IP_FILTER_ENABLED_HTTP_SETTING, this.httpEnabled);
this.allowed = settings.getAsArray("shield.transport.filter.allow");
this.denied = settings.getAsArray("shield.transport.filter.deny");
this.httpAllowed = settings.getAsArray("shield.http.filter.allow");
this.httpDenied = settings.getAsArray("shield.http.filter.deny");
if (settings.getGroups("transport.profiles.").size() == 0) {
profileAllowed = HppcMaps.newMap(0);
profileDenied = HppcMaps.newMap(0);
}
profileAllowed = HppcMaps.newNoNullKeysMap(settings.getGroups("transport.profiles.").size());
profileDenied = HppcMaps.newNoNullKeysMap(settings.getGroups("transport.profiles.").size());
for (Map.Entry<String, Settings> entry : settings.getGroups("transport.profiles.").entrySet()) {
profileAllowed.put(entry.getKey(), entry.getValue().getAsArray("shield.filter.allow"));
profileDenied.put(entry.getKey(), entry.getValue().getAsArray("shield.filter.deny"));
}
}
@Override
public void onRefreshSettings(Settings settings) {
if (ipFilterSettingsInvolved(settings) && settingsChanged(settings)) {
IPFilter.this.rules = parseSettings(settings);
loadValuesFromSettings(settings);
}
}
private boolean settingsChanged(Settings settings) {
// simple checks first
if (this.enabled != settings.getAsBoolean(IP_FILTER_ENABLED_SETTING, this.enabled) ||
this.httpEnabled != settings.getAsBoolean(IP_FILTER_ENABLED_HTTP_SETTING, this.httpEnabled) ||
!Arrays.equals(allowed, settings.getAsArray("shield.transport.filter.allow")) ||
!Arrays.equals(denied, settings.getAsArray("shield.transport.filter.deny")) ||
!Arrays.equals(httpAllowed, settings.getAsArray("shield.http.filter.allow")) ||
!Arrays.equals(httpDenied, settings.getAsArray("shield.http.filter.deny"))
) {
return true;
}
// profile checks now
ObjectObjectOpenHashMap<Object, Object> newProfileAllowed = HppcMaps.newNoNullKeysMap(settings.getGroups("transport.profiles.").size());
ObjectObjectOpenHashMap<Object, Object> newProfileDenied = HppcMaps.newNoNullKeysMap(settings.getGroups("transport.profiles.").size());
for (Map.Entry<String, Settings> entry : settings.getGroups("transport.profiles.").entrySet()) {
newProfileAllowed.put(entry.getKey(), entry.getValue().getAsArray("shield.filter.allow"));
newProfileDenied.put(entry.getKey(), entry.getValue().getAsArray("shield.filter.deny"));
}
boolean allowedProfileChanged = !newProfileAllowed.equals(profileAllowed);
boolean deniedProfileChanged = !newProfileDenied.equals(profileDenied);
return allowedProfileChanged || deniedProfileChanged;
}
private boolean ipFilterSettingsInvolved(Settings settings) {
boolean containsStaticIpFilterSettings = settings.get("shield.transport.filter.allow") != null ||
settings.get("shield.transport.filter.deny") != null ||
settings.get("shield.http.filter.allow") != null ||
settings.get("shield.http.filter.deny") != null ||
settings.get(IP_FILTER_ENABLED_SETTING) != null ||
settings.get(IP_FILTER_ENABLED_HTTP_SETTING) != null;
if (containsStaticIpFilterSettings) {
return true;
}
// now if any profile has a filter setting configured
for (Map.Entry<String, Settings> entry : settings.getGroups("transport.profiles.").entrySet()) {
if (entry.getValue().get("shield.filter.allow") != null || entry.getValue().get("shield.filter.deny") != null) {
return true;
}
}
return false;
}
return rules;
}
}

View File

@ -5,11 +5,18 @@
*/
package org.elasticsearch.shield.transport.filter;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.net.InetAddresses;
import org.elasticsearch.common.network.NetworkUtils;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.junit.annotations.Network;
import org.elasticsearch.transport.Transport;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@ -28,10 +35,24 @@ public class IPFilterTests extends ElasticsearchTestCase {
private IPFilter ipFilter;
private AuditTrail auditTrail;
private Transport transport;
private HttpServerTransport httpTransport;
private NodeSettingsService nodeSettingsService;
@Before
public void init() {
auditTrail = mock(AuditTrail.class);
nodeSettingsService = mock(NodeSettingsService.class);
httpTransport = mock(HttpServerTransport.class);
InetSocketTransportAddress httpAddress = new InetSocketTransportAddress(NetworkUtils.getLocalAddress(), 9200);
when(httpTransport.boundAddress()).thenReturn(new BoundTransportAddress(httpAddress, httpAddress));
when(httpTransport.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
transport = mock(Transport.class);
InetSocketTransportAddress address = new InetSocketTransportAddress(NetworkUtils.getLocalAddress(), 9300);
when(transport.boundAddress()).thenReturn(new BoundTransportAddress(address, address));
when(transport.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
}
@Test
@ -40,7 +61,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "127.0.0.1")
.put("shield.transport.filter.deny", "10.0.0.0/8")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("127.0.0.1");
assertAddressIsDenied("10.2.3.4");
@ -54,7 +75,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "2001:0db8:1234::/48")
.putArray("shield.transport.filter.deny", "1234:db8:85a3:0:0:8a2e:370:7334", "4321:db8:1234::/48")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("2001:0db8:1234:0000:0000:8a2e:0370:7334");
assertAddressIsDenied("1234:0db8:85a3:0000:0000:8a2e:0370:7334");
@ -68,7 +89,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "127.0.0.1")
.put("shield.transport.filter.deny", "*.google.com")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("127.0.0.1");
assertAddressIsDenied("8.8.8.8");
@ -79,7 +100,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
Settings settings = settingsBuilder()
.put("shield.transport.filter.allow", "_all")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("127.0.0.1");
assertAddressIsAllowed("173.194.70.100");
@ -93,7 +114,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("transport.profiles.client.shield.filter.allow", "192.168.0.1")
.put("transport.profiles.client.shield.filter.deny", "_all")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("127.0.0.1");
assertAddressIsDenied("192.168.0.1");
@ -107,7 +128,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "10.0.0.1")
.put("shield.transport.filter.deny", "10.0.0.0/8")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("10.0.0.1");
assertAddressIsDenied("10.0.0.2");
@ -116,7 +137,7 @@ public class IPFilterTests extends ElasticsearchTestCase {
@Test
public void testDefaultAllow() throws Exception {
Settings settings = settingsBuilder().build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
assertAddressIsAllowed("10.0.0.1");
assertAddressIsAllowed("10.0.0.2");
@ -128,12 +149,13 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "127.0.0.1")
.put("shield.transport.filter.deny", "10.0.0.0/8")
.put("shield.http.filter.allow", "10.0.0.0/8")
.put("shield.http.filter.deny", "127.0.0.1")
.put("shield.http.filter.deny", "192.168.0.1")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
ipFilter.setHttpServerTransport(httpTransport);
assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "10.2.3.4");
assertAddressIsDeniedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1");
assertAddressIsDeniedForProfile(IPFilter.HTTP_PROFILE_NAME, "192.168.0.1");
}
@Test
@ -142,12 +164,24 @@ public class IPFilterTests extends ElasticsearchTestCase {
.put("shield.transport.filter.allow", "127.0.0.1")
.put("shield.transport.filter.deny", "10.0.0.0/8")
.build();
ipFilter = new IPFilter(settings, auditTrail);
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
ipFilter.setHttpServerTransport(httpTransport);
assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1");
assertAddressIsDeniedForProfile(IPFilter.HTTP_PROFILE_NAME, "10.2.3.4");
}
@Test
public void testThatLocalhostIsNeverRejected() throws Exception {
Settings settings = settingsBuilder()
.put("shield.transport.filter.deny", "127.0.0.1")
.build();
ipFilter = new IPFilter(settings, auditTrail, nodeSettingsService, transport).start();
ipFilter.setHttpServerTransport(httpTransport);
assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1");
}
private void assertAddressIsAllowedForProfile(String profile, String ... inetAddresses) {
for (String inetAddress : inetAddresses) {
String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress);

View File

@ -0,0 +1,122 @@
/*
* 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.cluster.ClusterState;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.junit.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Locale;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope.TEST;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.hamcrest.Matchers.is;
@ClusterScope(scope = TEST, numDataNodes = 1)
public class IpFilteringUpdateTests extends ShieldIntegrationTest {
private boolean httpEnabled = false;
@Override
protected Settings nodeSettings(int nodeOrdinal) {
httpEnabled = randomBoolean();
return settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put(InternalNode.HTTP_ENABLED, httpEnabled)
.put("shield.transport.filter.deny", "127.0.0.200")
.build();
}
@Test
public void testThatIpFilterConfigurationCanBeChangedDynamically() throws Exception {
// ensure this did not get overwritten by the listener during startup
assertConnectionRejected("default", "127.0.0.200");
// allow all by default
assertConnectionAccepted("default", "127.0.0.8");
assertConnectionAccepted(".http", "127.0.0.8");
assertConnectionAccepted("client", "127.0.0.8");
Settings settings = settingsBuilder()
.put("shield.transport.filter.allow", "127.0.0.1")
.put("shield.transport.filter.deny", "127.0.0.8")
.build();
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
assertConnectionRejected("default", "127.0.0.8");
settings = settingsBuilder()
.putArray("shield.http.filter.allow", "127.0.0.1")
.putArray("shield.http.filter.deny", "127.0.0.8")
.build();
assertAcked(client().admin().cluster().prepareUpdateSettings().setPersistentSettings(settings));
assertConnectionRejected("default", "127.0.0.8");
assertConnectionRejected(".http", "127.0.0.8");
settings = settingsBuilder()
.put("transport.profiles.client.shield.filter.allow", "127.0.0.1")
.put("transport.profiles.client.shield.filter.deny", "127.0.0.8")
.build();
assertAcked(client().admin().cluster().prepareUpdateSettings().setTransientSettings(settings));
assertConnectionRejected("default", "127.0.0.8");
assertConnectionRejected(".http", "127.0.0.8");
assertConnectionRejected("client", "127.0.0.8");
// check that all is in cluster state
ClusterState clusterState = client().admin().cluster().prepareState().get().getState();
assertThat(clusterState.metaData().settings().get("shield.transport.filter.allow"), is("127.0.0.1"));
assertThat(clusterState.metaData().settings().get("shield.transport.filter.deny"), is("127.0.0.8"));
assertThat(clusterState.metaData().persistentSettings().get("shield.http.filter.allow.0"), is("127.0.0.1"));
assertThat(clusterState.metaData().persistentSettings().get("shield.http.filter.deny.0"), is("127.0.0.8"));
assertThat(clusterState.metaData().settings().get("transport.profiles.client.shield.filter.allow"), is("127.0.0.1"));
assertThat(clusterState.metaData().settings().get("transport.profiles.client.shield.filter.deny"), is("127.0.0.8"));
// now disable ip filtering dynamically and make sure nothing is rejected
settings = settingsBuilder()
.put(IPFilter.IP_FILTER_ENABLED_SETTING, false)
.put(IPFilter.IP_FILTER_ENABLED_HTTP_SETTING, true)
.build();
assertAcked(client().admin().cluster().prepareUpdateSettings().setPersistentSettings(settings));
assertConnectionAccepted("default", "127.0.0.8");
assertConnectionAccepted("client", "127.0.0.8");
// now also disable for HTTP
assertConnectionRejected(".http", "127.0.0.8");
settings = settingsBuilder()
.put(IPFilter.IP_FILTER_ENABLED_HTTP_SETTING, false)
.build();
assertAcked(client().admin().cluster().prepareUpdateSettings().setPersistentSettings(settings));
assertConnectionAccepted(".http", "127.0.0.8");
}
private void assertConnectionAccepted(String profile, String host) throws UnknownHostException {
// HTTP is not applied if disabled
if (!httpEnabled && IPFilter.HTTP_PROFILE_NAME.equals(profile)) {
return;
}
IPFilter ipFilter = internalCluster().getInstance(IPFilter.class);
String message = String.format(Locale.ROOT, "Expected allowed connection for profile %s against host %s", profile, host);
assertThat(message, ipFilter.accept(profile, InetAddress.getByName(host)), is(true));
}
private void assertConnectionRejected(String profile, String host) throws UnknownHostException {
// HTTP is not applied if disabled
if (!httpEnabled && IPFilter.HTTP_PROFILE_NAME.equals(profile)) {
return;
}
IPFilter ipFilter = internalCluster().getInstance(IPFilter.class);
String message = String.format(Locale.ROOT, "Expected rejection for profile %s against host %s", profile, host);
assertThat(message, ipFilter.accept(profile, InetAddress.getByName(host)), is(false));
}
}

View File

@ -1,4 +0,0 @@
+c:255.255.0.0/16
-i:192.168.100.2
-c:2001:db8::/48
-n:hostname

View File

@ -6,11 +6,18 @@
package org.elasticsearch.shield.transport.netty;
import com.google.common.net.InetAddresses;
import org.elasticsearch.common.component.Lifecycle;
import org.elasticsearch.common.netty.channel.*;
import org.elasticsearch.common.network.NetworkUtils;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.BoundTransportAddress;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.shield.audit.AuditTrail;
import org.elasticsearch.shield.transport.filter.IPFilter;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.transport.Transport;
import org.junit.Before;
import org.junit.Test;
@ -19,6 +26,8 @@ import java.net.SocketAddress;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*
@ -34,9 +43,29 @@ public class IPFilterNettyUpstreamHandlerTests extends ElasticsearchTestCase {
.put("shield.transport.filter.deny", "10.0.0.0/8")
.build();
IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP);
boolean isHttpEnabled = randomBoolean();
nettyUpstreamHandler = new IPFilterNettyUpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME);
Transport transport = mock(Transport.class);
InetSocketTransportAddress address = new InetSocketTransportAddress(NetworkUtils.getLocalAddress(), 9300);
when(transport.boundAddress()).thenReturn(new BoundTransportAddress(address, address));
when(transport.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
NodeSettingsService nodeSettingsService = mock(NodeSettingsService.class);
IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP, nodeSettingsService, transport).start();
if (isHttpEnabled) {
HttpServerTransport httpTransport = mock(HttpServerTransport.class);
InetSocketTransportAddress httpAddress = new InetSocketTransportAddress(NetworkUtils.getLocalAddress(), 9200);
when(httpTransport.boundAddress()).thenReturn(new BoundTransportAddress(httpAddress, httpAddress));
when(httpTransport.lifecycleState()).thenReturn(Lifecycle.State.STARTED);
ipFilter.setHttpServerTransport(httpTransport);
}
if (isHttpEnabled) {
nettyUpstreamHandler = new IPFilterNettyUpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME);
} else {
nettyUpstreamHandler = new IPFilterNettyUpstreamHandler(ipFilter, "default");
}
}
@Test