IP Filtering: Add support for having on filters on HTTP transport

In order to fix elastic/elasticsearch#378 a problem was revealed, that the ip filter for HTTP was
always the one for the default profile, which lead to failed tests (along
with wrong socket connections, which made the test go green irregularly).

This commit fixes the tests and allow to configure own HTTP ip filters, adding
the following settings

* shield.http.filter.enabled
* shield.http.filter.allow
* shield.http.filter.deny

If not specific settings are configured, the one of the default profile are used.

Closes elastic/elasticsearch#378

Original commit: elastic/x-pack-elasticsearch@89dbaefe9a
This commit is contained in:
Alexander Reelsen 2014-12-02 16:44:15 +01:00
parent 63a483e77e
commit dca9f3115e
5 changed files with 84 additions and 33 deletions

View File

@ -27,6 +27,14 @@ import java.util.Map;
public class IPFilter extends AbstractComponent { public class IPFilter extends AbstractComponent {
/**
* .http has been chosen for handling HTTP filters, which are not part of the profiles
* The profiles are only handled for the transport protocol, so we need an own kind of profile
* for HTTP. This name starts withs a dot, because no profile name can ever start like that due to
* how we handle settings
*/
public static final String HTTP_PROFILE_NAME = ".http";
private static final ProfileIpFilterRule[] NO_RULES = new ProfileIpFilterRule[0]; private static final ProfileIpFilterRule[] NO_RULES = new ProfileIpFilterRule[0];
private static final ProfileIpFilterRule ACCEPT_ALL_RULE = new ProfileIpFilterRule("default", private static final ProfileIpFilterRule ACCEPT_ALL_RULE = new ProfileIpFilterRule("default",
new PatternRule(true, "n:*"), "DEFAULT_ACCEPT_ALL"); new PatternRule(true, "n:*"), "DEFAULT_ACCEPT_ALL");
@ -46,6 +54,7 @@ public class IPFilter extends AbstractComponent {
if (rules == NO_RULES) { if (rules == NO_RULES) {
return true; return true;
} }
for (ProfileIpFilterRule rule : rules) { for (ProfileIpFilterRule rule : rules) {
if (rule.contains(profile, peerAddress)) { if (rule.contains(profile, peerAddress)) {
boolean isAllowed = rule.isAllowRule(); boolean isAllowed = rule.isAllowRule();
@ -68,11 +77,15 @@ public class IPFilter extends AbstractComponent {
} }
String[] allowed = settings.getAsArray("shield.transport.filter.allow"); String[] allowed = settings.getAsArray("shield.transport.filter.allow");
String[] denied = settings.getAsArray("shield.transport.filter.deny"); 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<>(); List<ProfileIpFilterRule> rules = new ArrayList<>();
try { try {
rules.addAll(parseValue(allowed, "default", true)); rules.addAll(parseValue(allowed, "default", true));
rules.addAll(parseValue(denied, "default", false)); rules.addAll(parseValue(denied, "default", false));
rules.addAll(parseValue(httpAllowed, HTTP_PROFILE_NAME, true));
rules.addAll(parseValue(httpDdenied, HTTP_PROFILE_NAME, false));
Map<String, Settings> groupedSettings = settings.getGroups("transport.profiles."); Map<String, Settings> groupedSettings = settings.getGroups("transport.profiles.");
for (Map.Entry<String, Settings> entry : groupedSettings.entrySet()) { for (Map.Entry<String, Settings> entry : groupedSettings.entrySet()) {
@ -109,5 +122,4 @@ public class IPFilter extends AbstractComponent {
String prefix = isInetAddress ? "i:" : "n:"; String prefix = isInetAddress ? "i:" : "n:";
return new PatternRule(isAllowRule, prefix + value); return new PatternRule(isAllowRule, prefix + value);
} }
} }

View File

@ -57,7 +57,7 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
pipeline.addFirst("ssl", new SslHandler(engine)); pipeline.addFirst("ssl", new SslHandler(engine));
} }
pipeline.addFirst("ipfilter", new NettyIPFilterUpstreamHandler(ipFilter, "default")); pipeline.addFirst("ipfilter", new NettyIPFilterUpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME));
return pipeline; return pipeline;
} }
} }

View File

@ -122,6 +122,32 @@ public class IPFilterTests extends ElasticsearchTestCase {
assertAddressIsAllowed("10.0.0.2"); assertAddressIsAllowed("10.0.0.2");
} }
@Test
public void testThatHttpWorks() throws Exception {
Settings settings = settingsBuilder()
.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")
.build();
ipFilter = new IPFilter(settings, auditTrail);
assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "10.2.3.4");
assertAddressIsDeniedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1");
}
@Test
public void testThatHttpFallsbackToDefault() throws Exception {
Settings settings = settingsBuilder()
.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);
assertAddressIsAllowedForProfile(IPFilter.HTTP_PROFILE_NAME, "127.0.0.1");
assertAddressIsDeniedForProfile(IPFilter.HTTP_PROFILE_NAME, "10.2.3.4");
}
private void assertAddressIsAllowedForProfile(String profile, String ... inetAddresses) { private void assertAddressIsAllowedForProfile(String profile, String ... inetAddresses) {
for (String inetAddress : inetAddresses) { for (String inetAddress : inetAddresses) {
String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress); String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress);

View File

@ -6,7 +6,7 @@
package org.elasticsearch.shield.transport.filter; package org.elasticsearch.shield.transport.filter;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import org.apache.lucene.util.LuceneTestCase; import org.elasticsearch.client.Client;
import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.InetSocketTransportAddress;
@ -14,62 +14,75 @@ import org.elasticsearch.common.transport.TransportAddress;
import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.node.internal.InternalNode; import org.elasticsearch.node.internal.InternalNode;
import org.elasticsearch.test.ShieldIntegrationTest; import org.elasticsearch.test.ShieldIntegrationTest;
import org.elasticsearch.transport.Transport; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import java.io.InputStream; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@LuceneTestCase.AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/378")
// no client nodes, no transport clients, as they all get rejected on network connections // no client nodes, no transport clients, as they all get rejected on network connections
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0.0) @ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0.0)
public class IpFilteringIntegrationTests extends ShieldIntegrationTest { public class IpFilteringIntegrationTests extends ShieldIntegrationTest {
@Override private static int randomClientPort;
protected Settings nodeSettings(int nodeOrdinal) {
return ImmutableSettings.builder().put(super.nodeSettings(nodeOrdinal)) @BeforeClass
.put(InternalNode.HTTP_ENABLED, true) public static void getRandomPort() {
.put("shield.transport.filter.deny", "_all").build(); randomClientPort = randomIntBetween(49000, 65500); // ephemeral port
} }
@Test(expected = SocketException.class) @Override
protected Settings nodeSettings(int nodeOrdinal) {
String randomClientPortRange = randomClientPort + "-" + (randomClientPort+100);
return ImmutableSettings.builder().put(super.nodeSettings(nodeOrdinal))
.put(InternalNode.HTTP_ENABLED, true)
.put("transport.profiles.client.port", randomClientPortRange)
.put("transport.profiles.client.bind_host", "localhost") // make sure this is "localhost", no matter if ipv4 or ipv6, but be consistent
.put("transport.profiles.client.shield.filter.deny", "_all")
.put("shield.http.filter.deny", "_all").build();
}
@Test
public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaHttp() throws Exception { public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaHttp() throws Exception {
TransportAddress transportAddress = internalCluster().getDataNodeInstance(HttpServerTransport.class).boundAddress().boundAddress(); TransportAddress transportAddress = internalCluster().getDataNodeInstance(HttpServerTransport.class).boundAddress().boundAddress();
assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class))); assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class)));
InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress; InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress;
trySocketConnection(inetSocketTransportAddress.address());
}
@Test(expected = SocketException.class)
public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaTransportClient() throws Exception {
TransportAddress transportAddress = internalCluster().getDataNodeInstance(Transport.class).boundAddress().boundAddress();
assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class)));
InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress;
trySocketConnection(inetSocketTransportAddress.address());
}
private void trySocketConnection(InetSocketAddress address) throws Exception {
try (Socket socket = new Socket()){ try (Socket socket = new Socket()){
trySocketConnection(socket, inetSocketTransportAddress.address());
assertThat(socket.isClosed(), is(true));
}
}
@Test
public void testThatIpFilteringIsNotAppliedForDefaultTransport() throws Exception {
Client client = internalCluster().transportClient();
assertGreenClusterState(client);
}
@Test
public void testThatIpFilteringIsAppliedForProfile() throws Exception {
try (Socket socket = new Socket()){
trySocketConnection(socket, new InetSocketAddress("localhost", randomClientPort));
assertThat(socket.isClosed(), is(true));
}
}
private void trySocketConnection(Socket socket, InetSocketAddress address) throws IOException {
logger.info("Connecting to {}", address); logger.info("Connecting to {}", address);
socket.connect(address, 500); socket.connect(address, 500);
assertThat(socket.isConnected(), is(true)); assertThat(socket.isConnected(), is(true));
try (OutputStream os = socket.getOutputStream()) { try (OutputStream os = socket.getOutputStream()) {
os.write("foo".getBytes(Charsets.UTF_8)); os.write("fooooo".getBytes(Charsets.UTF_8));
os.flush(); os.flush();
} }
try (InputStream is = socket.getInputStream()) {
is.read();
}
}
} }
} }

View File

@ -36,7 +36,7 @@ public class NettyIPFilterUpstreamHandlerTests extends ElasticsearchTestCase {
IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP); IPFilter ipFilter = new IPFilter(settings, AuditTrail.NOOP);
nettyUpstreamHandler = new NettyIPFilterUpstreamHandler(ipFilter, "default"); nettyUpstreamHandler = new NettyIPFilterUpstreamHandler(ipFilter, IPFilter.HTTP_PROFILE_NAME);
} }
@Test @Test