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:
parent
63a483e77e
commit
dca9f3115e
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue