diff --git a/src/main/java/org/elasticsearch/shield/SecurityModule.java b/src/main/java/org/elasticsearch/shield/SecurityModule.java index 865fbc86585..e928a8adbce 100644 --- a/src/main/java/org/elasticsearch/shield/SecurityModule.java +++ b/src/main/java/org/elasticsearch/shield/SecurityModule.java @@ -26,29 +26,35 @@ import org.elasticsearch.shield.transport.netty.NettySecuredTransportModule; public class SecurityModule extends AbstractModule implements SpawnModules, PreProcessModule { private final Settings settings; + private final boolean isClient; + private final boolean isShieldEnabled; public SecurityModule(Settings settings) { this.settings = settings; + this.isClient = settings.getAsBoolean("node.client", false); + this.isShieldEnabled = settings.getComponentSettings(SecurityModule.class).getAsBoolean("enabled", true); } @Override public void processModule(Module module) { - if (module instanceof ActionModule) { + if (module instanceof ActionModule && isShieldEnabled && !isClient) { ((ActionModule) module).registerFilter(SecurityFilter.Action.class); } } @Override public Iterable spawnModules() { - - // don't spawn module in client mode - if (settings.getAsBoolean("node.client", false)) { + // don't spawn modules if shield is explicitly disabled + if (!isShieldEnabled) { return ImmutableList.of(); } - // don't spawn modules if shield is explicitly disabled - if (!settings.getComponentSettings(SecurityModule.class).getAsBoolean("enabled", true)) { - return ImmutableList.of(); + // spawn needed parts in client mode + if (isClient) { + return ImmutableList.of( + new N2NModule(), + new SecuredTransportModule() + ); } return ImmutableList.of( @@ -58,7 +64,7 @@ public class SecurityModule extends AbstractModule implements SpawnModules, PreP new N2NModule(), new NettySecuredHttpServerTransportModule(), new NettySecuredTransportModule(), - new SecuredTransportModule(settings)); + new SecuredTransportModule()); } @Override diff --git a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java index d40e8543b8a..fa43b3b33b1 100644 --- a/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java +++ b/src/main/java/org/elasticsearch/shield/transport/SecuredTransportModule.java @@ -6,7 +6,6 @@ package org.elasticsearch.shield.transport; import org.elasticsearch.common.inject.AbstractModule; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.shield.SecurityFilter; /** @@ -14,16 +13,8 @@ import org.elasticsearch.shield.SecurityFilter; */ public class SecuredTransportModule extends AbstractModule { - private final Settings settings; - - public SecuredTransportModule(Settings settings) { - this.settings = settings; - } - @Override protected void configure() { - if (!settings.getAsBoolean("node.client", false)) { - bind(TransportFilter.class).to(SecurityFilter.Transport.class).asEagerSingleton(); - } + bind(TransportFilter.class).to(SecurityFilter.Transport.class).asEagerSingleton(); } } diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/SecuredMessageChannelHandler.java b/src/main/java/org/elasticsearch/shield/transport/netty/SecuredMessageChannelHandler.java index 4559b168f37..01f9d226fac 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/SecuredMessageChannelHandler.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/SecuredMessageChannelHandler.java @@ -30,10 +30,10 @@ public class SecuredMessageChannelHandler extends MessageChannelHandler { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { - logger.debug("SSL / TLS handshake completed for the channel."); + logger.debug("SSL / TLS handshake completed for channel", ctx.getName()); ctx.sendUpstream(e); } else { - logger.error("SSL / TLS handshake failed, closing the channel"); + logger.error("SSL / TLS handshake failed, closing channel", ctx.getName()); future.getChannel().close(); throw new ElasticsearchSSLException("SSL / TLS handshake failed, closing the channel", future.getCause()); } diff --git a/src/test/java/org/elasticsearch/shield/n2n/IPFilteringN2NAuthenticatorTests.java b/src/test/java/org/elasticsearch/shield/n2n/IPFilteringN2NAuthenticatorTests.java index bf6086e505a..528ef0a7602 100644 --- a/src/test/java/org/elasticsearch/shield/n2n/IPFilteringN2NAuthenticatorTests.java +++ b/src/test/java/org/elasticsearch/shield/n2n/IPFilteringN2NAuthenticatorTests.java @@ -52,9 +52,6 @@ public class IPFilteringN2NAuthenticatorTests extends ElasticsearchTestCase { @Before public void init() throws Exception { configFile = temporaryFolder.newFile(); - Settings resourceWatcherServiceSettings = settingsBuilder().put("watcher.interval.medium", TimeValue.timeValueMillis(200)).build(); - resourceWatcherService = new ResourceWatcherService(resourceWatcherServiceSettings, new ThreadPool("resourceWatcher")).start(); - settings = settingsBuilder().put("shield.n2n.file", configFile.getPath()).build(); } @After @@ -66,75 +63,75 @@ public class IPFilteringN2NAuthenticatorTests extends ElasticsearchTestCase { public void testThatIpV4AddressesCanBeProcessed() throws Exception { writeConfigFile("allow: 127.0.0.1\ndeny: 10.0.0.0/8"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "10.2.3.4"); + assertAddressIsAllowed("127.0.0.1"); + assertAddressIsDenied("10.2.3.4"); } @Test public void testThatIpV6AddressesCanBeProcessed() throws Exception { writeConfigFile("allow: 2001:0db8:1234::/48\ndeny: 1234:0db8:85a3:0000:0000:8a2e:0370:7334"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "2001:0db8:1234:0000:0000:8a2e:0370:7334"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "1234:0db8:85a3:0000:0000:8a2e:0370:7334"); + assertAddressIsAllowed("2001:0db8:1234:0000:0000:8a2e:0370:7334"); + assertAddressIsDenied("1234:0db8:85a3:0000:0000:8a2e:0370:7334"); } @Test public void testThatHostnamesCanBeProcessed() throws Exception { writeConfigFile("allow: localhost\ndeny: '*.google.com'"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "173.194.70.100"); + assertAddressIsAllowed("127.0.0.1"); + assertAddressIsDenied("173.194.70.100"); } @Test public void testThatFileDeletionResultsInAllowingAll() throws Exception { writeConfigFile("allow: 127.0.0.1"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); + assertAddressIsAllowed("127.0.0.1"); configFile.delete(); assertThat(configFile.exists(), is(false)); sleep(250); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "127.0.0.1"); + assertAddressIsDenied("127.0.0.1"); } @Test public void testThatAnAllowAllAuthenticatorWorks() throws Exception { writeConfigFile("allow: all"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "173.194.70.100"); + assertAddressIsAllowed("127.0.0.1"); + assertAddressIsAllowed("173.194.70.100"); } @Test public void testThatCommaSeparatedValuesWork() throws Exception { writeConfigFile("allow: 192.168.23.0/24, localhost\ndeny: all"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "192.168.23.1"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "10.1.2.3"); + assertAddressIsAllowed("192.168.23.1"); + assertAddressIsAllowed("127.0.0.1"); + assertAddressIsDenied("10.1.2.3"); } @Test public void testThatOrderIsImportant() throws Exception { writeConfigFile("deny: localhost\nallow: localhost"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "127.0.0.1"); + assertAddressIsDenied("127.0.0.1"); } @Test public void testThatOrderIsImportantViceVersa() throws Exception { writeConfigFile("allow: localhost\ndeny: localhost"); - assertAddressIsAllowed(ipFilteringN2NAuthenticator, "127.0.0.1"); + assertAddressIsAllowed("127.0.0.1"); } @Test public void testThatEmptyFileDoesNotLeadIntoLoop() throws Exception { writeConfigFile("# \n\n"); - assertAddressIsDenied(ipFilteringN2NAuthenticator, "127.0.0.1"); + assertAddressIsDenied("127.0.0.1"); } @Test(expected = ElasticsearchParseException.class) @@ -145,17 +142,20 @@ public class IPFilteringN2NAuthenticatorTests extends ElasticsearchTestCase { private void writeConfigFile(String data) throws IOException { Files.write(data.getBytes(Charsets.UTF_8), configFile); + Settings resourceWatcherServiceSettings = settingsBuilder().put("watcher.interval.medium", TimeValue.timeValueMillis(200)).build(); + resourceWatcherService = new ResourceWatcherService(resourceWatcherServiceSettings, new ThreadPool("resourceWatcher")).start(); + settings = settingsBuilder().put("shield.n2n.file", configFile.getPath()).build(); ipFilteringN2NAuthenticator = new IPFilteringN2NAuthenticator(settings, new Environment(), resourceWatcherService); } - private void assertAddressIsAllowed(IPFilteringN2NAuthenticator ipFilteringN2NAuthenticator, String ... inetAddresses) { + private void assertAddressIsAllowed(String ... inetAddresses) { for (String inetAddress : inetAddresses) { String message = String.format(Locale.ROOT, "Expected address %s to be allowed", inetAddress); assertThat(message, ipFilteringN2NAuthenticator.authenticate(NULL_PRINCIPAL, InetAddresses.forString(inetAddress), 1024), is(true)); } } - private void assertAddressIsDenied(IPFilteringN2NAuthenticator ipFilteringN2NAuthenticator, String ... inetAddresses) { + private void assertAddressIsDenied(String ... inetAddresses) { for (String inetAddress : inetAddresses) { String message = String.format(Locale.ROOT, "Expected address %s to be denied", inetAddress); assertThat(message, ipFilteringN2NAuthenticator.authenticate(NULL_PRINCIPAL, InetAddresses.forString(inetAddress), 1024), is(false)); diff --git a/src/test/java/org/elasticsearch/shield/n2n/IpFilteringIntegrationTests.java b/src/test/java/org/elasticsearch/shield/n2n/IpFilteringIntegrationTests.java index 6b0edd039db..791af06a0b9 100644 --- a/src/test/java/org/elasticsearch/shield/n2n/IpFilteringIntegrationTests.java +++ b/src/test/java/org/elasticsearch/shield/n2n/IpFilteringIntegrationTests.java @@ -7,56 +7,48 @@ package org.elasticsearch.shield.n2n; import com.google.common.base.Charsets; import com.google.common.net.InetAddresses; -import org.elasticsearch.common.os.OsUtils; -import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; -import org.elasticsearch.shield.plugin.SecurityPlugin; -import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.shield.test.ShieldIntegrationTest; import org.elasticsearch.transport.Transport; -import org.junit.Ignore; import org.junit.Test; +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.Socket; import java.net.SocketException; import java.net.URL; import java.util.Locale; -import static org.apache.lucene.util.LuceneTestCase.AwaitsFix; import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -/** - * - */ -@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE, numDataNodes = 1, transportClientRatio = 0.0, numClientNodes = 0) -@AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/36") -public class IpFilteringIntegrationTests extends ElasticsearchIntegrationTest { +// no client nodes, no transport nodes, as they all get rejected on network connections +@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0, transportClientRatio = 0.0) +public class IpFilteringIntegrationTests extends ShieldIntegrationTest { + + private static final String CONFIG_IPFILTER_DENY_ALL = "deny: all\n"; @Override protected Settings nodeSettings(int nodeOrdinal) { - ImmutableSettings.Builder builder = settingsBuilder() - .put(super.nodeSettings(nodeOrdinal)) - .put("discovery.zen.ping.multicast.ping.enabled", false) - .put("node.mode", "network") - // todo http tests fail without an explicit IP (needs investigation) - .put("network.host", randomBoolean() ? "127.0.0.1" : "::1") - .put("plugin.types", SecurityPlugin.class.getName()); - //.put("shield.n2n.file", configFile.getPath()) + File folder = newFolder(); - if (OsUtils.MAC) { - builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); - } - return builder.build(); + return settingsBuilder() + .put(super.nodeSettings(nodeOrdinal)) + .put("shield.n2n.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_DENY_ALL)) + .build(); } @Test(expected = SocketException.class) public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaHttp() throws Exception { - TransportAddress transportAddress = internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddress(); + TransportAddress transportAddress = internalCluster().getDataNodeInstance(HttpServerTransport.class).boundAddress().boundAddress(); assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class))); InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress; String url = String.format(Locale.ROOT, "http://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort()); @@ -67,17 +59,22 @@ public class IpFilteringIntegrationTests extends ElasticsearchIntegrationTest { logger.info("HTTP connection response code [{}]", connection.getResponseCode()); } - @Ignore("Need to investigate further, why this does not fail") @Test(expected = SocketException.class) public void testThatIpFilteringIsIntegratedIntoNettyPipelineViaTransportClient() throws Exception { InetSocketTransportAddress transportAddress = (InetSocketTransportAddress) internalCluster().getDataNodeInstance(Transport.class).boundAddress().boundAddress(); - // TODO: This works and I do not understand why, telnet breaks... - Socket socket = new Socket(transportAddress.address().getAddress(), transportAddress.address().getPort()); - socket.getOutputStream().write("foo".getBytes(Charsets.UTF_8)); - socket.getOutputStream().flush(); - socket.getInputStream().close(); - assertThat(socket.isConnected(), is(true)); - socket.close(); + try (Socket socket = new Socket()) { + logger.info("Connecting to {}", transportAddress.address()); + socket.connect(transportAddress.address(), 500); + + assertThat(socket.isConnected(), is(true)); + try (OutputStream os = socket.getOutputStream()) { + os.write("foo".getBytes(Charsets.UTF_8)); + os.flush(); + } + try (InputStream is = socket.getInputStream()) { + is.read(); + } + } } } diff --git a/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java b/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java index 004691e0ef6..c611937288f 100644 --- a/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java +++ b/src/test/java/org/elasticsearch/shield/plugin/ShieldPluginTests.java @@ -7,101 +7,23 @@ package org.elasticsearch.shield.plugin; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; -import org.elasticsearch.common.os.OsUtils; -import org.elasticsearch.common.settings.ImmutableSettings; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.shield.transport.SecuredTransportService; -import org.elasticsearch.test.ElasticsearchIntegrationTest; -import org.elasticsearch.test.junit.annotations.TestLogging; -import org.elasticsearch.transport.TransportModule; -import org.junit.Rule; +import org.elasticsearch.shield.test.ShieldIntegrationTest; import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; - -import static org.apache.lucene.util.LuceneTestCase.AwaitsFix; -import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue; -import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; -import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.is; -/** - * - */ -@ClusterScope(scope = Scope.SUITE, numDataNodes = 2, randomDynamicTemplates = false) -@AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/36") -public class ShieldPluginTests extends ElasticsearchIntegrationTest { - - - @Rule - public TemporaryFolder tmpFolder = new TemporaryFolder(); - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - File folder = newFolder(); - ImmutableSettings.Builder builder = ImmutableSettings.builder() - .put("plugin.types", SecurityPlugin.class.getName()) - .put(super.nodeSettings(nodeOrdinal)) - .put("shield.audit.enabled", true) - .put("shield.authc.esusers.files.users", copyFile(folder, "users")) - .put("shield.authc.esusers.files.users_roles", copyFile(folder, "users_roles")) - .put("shield.authz.store.files.roles", copyFile(folder, "roles.yml")) - .put("shield.n2n.file", copyFile(folder, "ip_filter.yml")) - .put(TransportModule.TRANSPORT_SERVICE_TYPE_KEY, SecuredTransportService.class.getName()) - // for the test internal node clients - .put("request.headers.Authorization", basicAuthHeaderValue("test_user", "changeme".toCharArray())); - - if (OsUtils.MAC) { - builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); - } - - return builder.build(); - } - - - @Override - protected Settings transportClientSettings() { - return ImmutableSettings.builder() - .put("request.headers.Authorization", basicAuthHeaderValue("test_user", "changeme".toCharArray())) - .build(); - } +public class ShieldPluginTests extends ShieldIntegrationTest { @Test - @TestLogging("_root:INFO,plugins.PluginsService:TRACE") public void testThatPluginIsLoaded() { logger.info("--> Getting nodes info"); NodesInfoResponse nodeInfos = internalCluster().transportClient().admin().cluster().prepareNodesInfo().get(); - logger.info("--> Checking nodes info"); + logger.info("--> Checking nodes info that shield plugin is loaded"); for (NodeInfo nodeInfo : nodeInfos.getNodes()) { assertThat(nodeInfo.getPlugins().getInfos(), hasSize(1)); assertThat(nodeInfo.getPlugins().getInfos().get(0).getName(), is(SecurityPlugin.NAME)); } } - private File newFolder() { - try { - return tmpFolder.newFolder(); - } catch (IOException ioe) { - logger.error("could not create temporary folder", ioe); - fail("could not create temporary folder"); - return null; - } - } - - private String copyFile(File folder, String name) { - Path file = folder.toPath().resolve(name); - try { - Files.copy(getClass().getResourceAsStream(name), file); - } catch (IOException ioe) { - logger.error("could not copy temporary configuration file [" + name + "]", ioe); - fail("could not copy temporary configuration file [" + name + "]"); - } - return file.toAbsolutePath().toString(); - } - } diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java new file mode 100644 index 00000000000..3b1433f7449 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -0,0 +1,167 @@ +/* + * 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.test; + +import com.google.common.base.Charsets; +import com.google.common.net.InetAddresses; +import org.apache.lucene.util.AbstractRandomizedTest; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.os.OsUtils; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.InetSocketTransportAddress; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.plugins.PluginsService; +import org.elasticsearch.shield.plugin.SecurityPlugin; +import org.elasticsearch.shield.transport.netty.NettySecuredTransport; +import org.elasticsearch.test.ElasticsearchIntegrationTest; +import org.elasticsearch.transport.Transport; +import org.elasticsearch.transport.TransportModule; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.rules.TemporaryFolder; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; +import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; + + +@Ignore +@AbstractRandomizedTest.Integration +@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0) +public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest { + + private static final String DEFAULT_USER_NAME = "test_user"; + private static final String DEFAULT_PASSWORD = "changeme"; + private static final String DEFAULT_ROLE = "user"; + + public static final String CONFIG_IPFILTER_ALLOW_ALL = "allow: all\n"; + public static final String CONFIG_STANDARD_USER = DEFAULT_USER_NAME + ":{plain}" + DEFAULT_PASSWORD + "\n"; + public static final String CONFIG_STANDARD_USER_ROLES = DEFAULT_USER_NAME + ":" + DEFAULT_ROLE + "\n"; + public static final String CONFIG_ROLE_ALLOW_ALL = "user:\n" + + " cluster: ALL\n" + + " indices:\n" + + " '.*': ALL\n"; + + @ClassRule + public static TemporaryFolder tmpFolder = new TemporaryFolder(); + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + File folder = newFolder(); + + ImmutableSettings.Builder builder = ImmutableSettings.builder() + .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray())) + .put("discovery.zen.ping.multicast.enabled", false) + .put("discovery.type", "zen") + .put("node.mode", "network") + .put("plugin.types", SecurityPlugin.class.getName()) + .put("shield.authc.esusers.files.users", writeFile(folder, "users", CONFIG_STANDARD_USER)) + .put("shield.authc.esusers.files.users_roles", writeFile(folder, "users_roles", CONFIG_STANDARD_USER_ROLES)) + .put("shield.authz.store.files.roles", writeFile(folder, "roles.yml", CONFIG_ROLE_ALLOW_ALL)) + .put("shield.n2n.file", writeFile(folder, "ip_filter.yml", CONFIG_IPFILTER_ALLOW_ALL)) + .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode")) + .put("shield.audit.enabled", true); + + if (OsUtils.MAC) { + builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); + } + + return builder.build(); + } + + @Override + protected Settings transportClientSettings() { + return ImmutableSettings.builder() + .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray())) + .put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName()) + .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false) + .put("node.mode", "network") + .put("discovery.zen.ping.multicast.ping.enabled", false) + .putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress()) + .put(getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")) + .build(); + } + + protected String writeFile(File folder, String name, String content) { + Path file = folder.toPath().resolve(name); + try { + com.google.common.io.Files.write(content.getBytes(Charsets.UTF_8), file.toFile()); + } catch (IOException e) { + throw new ElasticsearchException("Error writing file in test", e); + } + return file.toFile().getAbsolutePath(); + } + + protected String getUnicastHostAddress() { + TransportAddress transportAddress = internalCluster().getDataNodeInstance(Transport.class).boundAddress().publishAddress(); + assertThat(transportAddress, instanceOf(InetSocketTransportAddress.class)); + InetSocketTransportAddress address = (InetSocketTransportAddress) transportAddress; + return InetAddresses.toAddrString(address.address().getAddress()) + ":" + address.address().getPort(); + } + + protected String getClientUsername() { + return DEFAULT_USER_NAME; + }; + + protected String getClientPassword() { + return DEFAULT_PASSWORD; + } + + protected Settings getSSLSettingsForStore(String resourcePathToStore, String password) { + File store; + try { + store = new File(getClass().getResource(resourcePathToStore).toURI()); + assertThat(store.exists(), is(true)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + ImmutableSettings.Builder builder = settingsBuilder() + .put("shield.transport.ssl", true) + .put("shield.transport.ssl.keystore", store.getPath()) + .put("shield.transport.ssl.keystore_password", password) + .put("shield.transport.ssl.truststore", store.getPath()) + .put("shield.transport.ssl.truststore_password", password) + .put("shield.http.ssl", true) + .put("shield.http.ssl.require.client.auth", false) + .put("shield.http.ssl.keystore", store.getPath()) + .put("shield.http.ssl.keystore_password", password) + .put("shield.http.ssl.truststore", store.getPath()) + .put("shield.http.ssl.truststore_password", password); + + return builder.build(); + } + + protected File newFolder() { + try { + return tmpFolder.newFolder(); + } catch (IOException ioe) { + logger.error("could not create temporary folder", ioe); + fail("could not create temporary folder"); + return null; + } + } + + protected String copyFile(File folder, String name) { + Path file = folder.toPath().resolve(name); + try { + Files.copy(getClass().getResourceAsStream(name), file); + } catch (IOException ioe) { + logger.error("could not copy temporary configuration file [" + name + "]", ioe); + fail("could not copy temporary configuration file [" + name + "]"); + } + return file.toAbsolutePath().toString(); + } +} diff --git a/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java b/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java index 3dad05e6193..ee255928934 100644 --- a/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslIntegrationTests.java @@ -5,117 +5,76 @@ */ package org.elasticsearch.shield.transport.ssl; -import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets; -import com.google.common.io.Files; -import com.google.common.net.InetAddresses; +import com.google.common.base.Charsets; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; import org.elasticsearch.client.Client; +import org.elasticsearch.client.transport.NoNodeAvailableException; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.io.Streams; -import org.elasticsearch.common.os.OsUtils; -import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.net.InetAddresses; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; -import org.elasticsearch.shield.plugin.SecurityPlugin; +import org.elasticsearch.plugins.PluginsService; +import org.elasticsearch.shield.test.ShieldIntegrationTest; import org.elasticsearch.shield.transport.netty.NettySecuredTransport; -import org.elasticsearch.test.ElasticsearchIntegrationTest; -import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.transport.Transport; import org.elasticsearch.transport.TransportModule; -import org.junit.BeforeClass; -import org.junit.ClassRule; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import javax.net.ssl.*; -import java.io.File; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Locale; -import static org.apache.lucene.util.LuceneTestCase.AwaitsFix; -import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; -import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; +import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; +import static org.elasticsearch.shield.authc.support.UsernamePasswordToken.basicAuthHeaderValue; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout; import static org.hamcrest.Matchers.*; -@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, transportClientRatio = 0.0, numClientNodes = 0) -@AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/36") -public class SslIntegrationTests extends ElasticsearchIntegrationTest { - - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private static File ipFilterFile; - - @BeforeClass - public static void writeAllowAllIpFilterFile() throws Exception { - ipFilterFile = temporaryFolder.newFile(); - Files.write("allow: all\n".getBytes(com.google.common.base.Charsets.UTF_8), ipFilterFile); - } - - @Override - protected Settings nodeSettings(int nodeOrdinal) { - File testnodeStore; - try { - testnodeStore = new File(getClass().getResource("certs/simple/testnode.jks").toURI()); - assertThat(testnodeStore.exists(), is(true)); - } catch (Exception e) { - throw new RuntimeException(e); - } - - ImmutableSettings.Builder builder = ImmutableSettings.settingsBuilder() - .put(super.nodeSettings(nodeOrdinal)) - .put("discovery.zen.ping.multicast.ping.enabled", false) - // - .put("shield.authz.file.roles", "not/existing") - // needed to ensure that netty transport is started - .put("node.mode", "network") - .put("shield.transport.ssl", true) - .put("shield.transport.ssl.keystore", testnodeStore.getPath()) - .put("shield.transport.ssl.keystore_password", "testnode") - .put("shield.transport.ssl.truststore", testnodeStore.getPath()) - .put("shield.transport.ssl.truststore_password", "testnode") - .put("shield.http.ssl", true) - .put("shield.http.ssl.require.client.auth", false) - .put("shield.http.ssl.keystore", testnodeStore.getPath()) - .put("shield.http.ssl.keystore_password", "testnode") - .put("shield.http.ssl.truststore", testnodeStore.getPath()) - .put("shield.http.ssl.truststore_password", "testnode") - // SSL SETUP - .put("plugin.types", SecurityPlugin.class.getName()) - .put("shield.n2n.file", ipFilterFile.getPath()); - - if (OsUtils.MAC) { - builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); - } - return builder.build(); - } +public class SslIntegrationTests extends ShieldIntegrationTest { @Test - @TestLogging("_root:INFO,org.elasticsearch.test:TRACE, org.elasticsearch.client.transport:DEBUG,org.elasticsearch.shield:TRACE") - public void testThatTransportClientCanConnectToNodeViaSsl() throws Exception { - TransportClient transportClient = new TransportClient(getSettings("transport_client").build(), false); - TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress(); - transportClient.addTransportAddress(transportAddress); - + public void testThatInternallyCreatedTransportClientCanConnect() throws Exception { + Client transportClient = internalCluster().transportClient(); assertGreenClusterState(transportClient); } - @Test(expected = ElasticsearchSSLException.class) - @TestLogging("_root:INFO,org.elasticsearch.client.transport:DEBUG") - public void testThatUnconfiguredCipchersAreRejected() { - // some randomly taken ciphers from SSLContext.getDefault().getSocketFactory().getSupportedCipherSuites() - // could be really randomized - Settings customSettings = getSettings("transport_client").put("shield.transport.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}).build(); + @Test + public void testThatProgrammaticallyCreatedTransportClientCanConnect() throws Exception { + Settings settings = settingsBuilder() + .put(transportClientSettings()) + .put("name", "programmatic_transport_client_") + .put("cluster.name", internalCluster().getClusterName()) + .build(); - TransportClient transportClient = new TransportClient(customSettings); + try (TransportClient transportClient = new TransportClient(settings, false)) { + TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress(); + transportClient.addTransportAddress(transportAddress); + assertGreenClusterState(transportClient); + } + + try (TransportClient transportClient = new TransportClient(settings, true)) { + TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress(); + transportClient.addTransportAddress(transportAddress); + assertGreenClusterState(transportClient); + } + } + + // no SSL exception as this is the exception is returned when connecting + @Test(expected = NoNodeAvailableException.class) + public void testThatUnconfiguredCipchersAreRejected() { + TransportClient transportClient = new TransportClient(settingsBuilder() + .put(transportClientSettings()) + .put("name", "programmatic_transport_client") + .put("cluster.name", internalCluster().getClusterName()) + .putArray("shield.transport.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}) + .build()); TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress(); transportClient.addTransportAddress(transportAddress); @@ -124,9 +83,19 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest { } @Test - @TestLogging("_root:DEBUG") public void testConnectNodeWorks() throws Exception { - try (Node node = NodeBuilder.nodeBuilder().settings(getSettings("ssl_node")).node().start()) { + Settings settings = settingsBuilder() + .put("name", "programmatic_node") + .put("cluster.name", internalCluster().getClusterName()) + + .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray())) + .put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName()) + .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false) + + .put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient")) + .build(); + + try (Node node = NodeBuilder.nodeBuilder().settings(settings).node()) { try (Client client = node.client()) { assertGreenClusterState(client); } @@ -135,34 +104,30 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest { @Test public void testConnectNodeClientWorks() throws Exception { - // no multicast, good old discovery - TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress(); - assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class))); - InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress; - Settings.Builder settingsBuilder = getSettings("node_client") - .put("node.client", true) - .put("discovery.zen.ping.multicast.ping.enabled", false) - .put("discovery.zen.ping.unicast.hosts", inetSocketTransportAddress.address().getHostString() + ":" + inetSocketTransportAddress.address().getPort()); + Settings settings = settingsBuilder() + .put("name", "programmatic_node_client") + .put("cluster.name", internalCluster().getClusterName()) + .put("node.mode", "network") - try (Node node = NodeBuilder.nodeBuilder().settings(settingsBuilder).node().start()) { + .put("discovery.zen.ping.multicast.enabled", false) + .put("discovery.type", "zen") + .putArray("discovery.zen.ping.unicast.hosts", getUnicastHostAddress()) + + .put("request.headers.Authorization", basicAuthHeaderValue(getClientUsername(), getClientPassword().toCharArray())) + .put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName()) + .put("plugins." + PluginsService.LOAD_PLUGIN_FROM_CLASSPATH, false) + .put("shield.n2n.file", writeFile(newFolder(), "ip_filter.yml", ShieldIntegrationTest.CONFIG_IPFILTER_ALLOW_ALL)) + + .put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient")) + .build(); + + try (Node node = NodeBuilder.nodeBuilder().settings(settings).client(true).node()) { try (Client client = node.client()) { assertGreenClusterState(client); } } } - @Test(expected = ElasticsearchSSLException.class) - public void testConnectNodeFailsWithWrongCipher() throws Exception { - Settings customSettings = getSettings("ssl_node").put("shield.transport.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}).build(); - NodeBuilder.nodeBuilder().settings(customSettings).node().start(); - } - - @Test(expected = ElasticsearchSSLException.class) - public void testConnectNodeClientFailsWithWrongCipher() throws Exception { - Settings customSettings = getSettings("ssl_node").put("node.client", true).put("shield.transport.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}).build(); - NodeBuilder.nodeBuilder().settings(customSettings).node().start(); - } - @Test public void testThatConnectionToHTTPWorks() throws Exception { TrustManager[] trustAllCerts = new TrustManager[]{ @@ -206,36 +171,9 @@ public class SslIntegrationTests extends ElasticsearchIntegrationTest { assertThat(data, containsString("You Know, for Search")); } - private ImmutableSettings.Builder getSettings(String name) { - File testClientKeyStore; - File testClientTrustStore; - try { - testClientKeyStore = new File(getClass().getResource("certs/simple/testclient.jks").toURI()); - testClientTrustStore = new File(getClass().getResource("certs/simple/testclient.jks").toURI()); - } catch (Exception e) { - throw new RuntimeException(e); - } - assertThat(testClientKeyStore.exists(), is(true)); - assertThat(testClientTrustStore.exists(), is(true)); - - return ImmutableSettings.settingsBuilder() - .put("node.name", name) - .put("plugins.load_classpath_plugins", false) - .put("shield.transport.ssl", true) - .put("shield.transport.ssl.keystore", testClientKeyStore.getPath()) - .put("shield.transport.ssl.keystore_password", "testclient") - .put("shield.transport.ssl.truststore", testClientTrustStore .getPath()) - .put("shield.transport.ssl.truststore_password", "testclient") - .put("discovery.zen.ping.multicast.ping.enabled", false) - .put(TransportModule.TRANSPORT_TYPE_KEY, NettySecuredTransport.class.getName()) - .put("shield.n2n.file", ipFilterFile.getPath()) - .put("cluster.name", internalCluster().getClusterName()); - } - private void assertGreenClusterState(Client client) { ClusterHealthResponse clusterHealthResponse = client.admin().cluster().prepareHealth().get(); assertNoTimeout(clusterHealthResponse); assertThat(clusterHealthResponse.getStatus(), is(ClusterHealthStatus.GREEN)); } - } diff --git a/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java b/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java index c4e4e4e0702..acae4ca4768 100644 --- a/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java @@ -6,22 +6,15 @@ package org.elasticsearch.shield.transport.ssl; import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets; -import com.google.common.io.Files; import com.google.common.net.InetAddresses; import org.elasticsearch.common.io.Streams; -import org.elasticsearch.common.os.OsUtils; import org.elasticsearch.common.settings.ImmutableSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.http.HttpServerTransport; -import org.elasticsearch.shield.plugin.SecurityPlugin; -import org.elasticsearch.test.ElasticsearchIntegrationTest; -import org.elasticsearch.test.junit.annotations.TestLogging; -import org.junit.BeforeClass; -import org.junit.ClassRule; +import org.elasticsearch.shield.test.ShieldIntegrationTest; import org.junit.Test; -import org.junit.rules.TemporaryFolder; import javax.net.ssl.*; import java.io.File; @@ -33,15 +26,16 @@ import java.security.KeyStore; import java.security.SecureRandom; import java.util.Locale; -import static org.apache.lucene.util.LuceneTestCase.AwaitsFix; +import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope; +import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope; import static org.hamcrest.Matchers.*; /** * */ -@ElasticsearchIntegrationTest.ClusterScope(scope = ElasticsearchIntegrationTest.Scope.SUITE, numDataNodes = 1, transportClientRatio = 0.0, numClientNodes = 0) -@AwaitsFix(bugUrl = "https://github.com/elasticsearch/elasticsearch-shield/issues/36") -public class SslRequireAuthTests extends ElasticsearchIntegrationTest { +@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, numClientNodes = 0) +public class SslRequireAuthTests extends ShieldIntegrationTest { public static final HostnameVerifier HOSTNAME_VERIFIER = new HostnameVerifier() { @Override @@ -50,54 +44,22 @@ public class SslRequireAuthTests extends ElasticsearchIntegrationTest { } }; - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private static File ipFilterFile; - - @BeforeClass - public static void writeAllowAllIpFilterFile() throws Exception { - ipFilterFile = temporaryFolder.newFile(); - Files.write("allow: all\n".getBytes(com.google.common.base.Charsets.UTF_8), ipFilterFile); + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return settingsBuilder() + .put(super.nodeSettings(nodeOrdinal)) + .put(getSSLSettingsForStore("certs/simple/testnode.jks", "testnode")) + .put("shield.transport.ssl.require.client.auth", true) + .put("shield.http.ssl.require.client.auth", true) + .build(); } @Override - protected Settings nodeSettings(int nodeOrdinal) { - File testnodeStore; - try { - testnodeStore = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI()); - assertThat(testnodeStore.exists(), is(true)); - } catch (Exception e) { - throw new RuntimeException(e); - } - - ImmutableSettings.Builder builder = ImmutableSettings.settingsBuilder() - .put(super.nodeSettings(nodeOrdinal)) - .put("discovery.zen.ping.multicast.ping.enabled", false) - // prevents exception until parsing has been fixed in PR - .put("shield.authz.file.roles", "not/existing") - // needed to ensure that netty transport is started - .put("node.mode", "network") - .put("shield.transport.ssl", true) - .put("shield.transport.ssl.require.client.auth", true) - .put("shield.transport.ssl.keystore", testnodeStore.getPath()) - .put("shield.transport.ssl.keystore_password", "testnode") - .put("shield.transport.ssl.truststore", testnodeStore.getPath()) - .put("shield.transport.ssl.truststore_password", "testnode") - .put("shield.http.ssl", true) - .put("shield.http.ssl.require.client.auth", true) - .put("shield.http.ssl.keystore", testnodeStore.getPath()) - .put("shield.http.ssl.keystore_password", "testnode") - .put("shield.http.ssl.truststore", testnodeStore.getPath()) - .put("shield.http.ssl.truststore_password", "testnode") - .put("plugin.types", SecurityPlugin.class.getName()) - .put("shield.n2n.file", ipFilterFile.getPath()); - - if (OsUtils.MAC) { - builder.put("network.host", randomBoolean() ? "127.0.0.1" : "::1"); - } - - return builder.build(); + protected Settings transportClientSettings() { + return ImmutableSettings.builder() + .put(super.transportClientSettings()) + .put(getSSLSettingsForStore("certs/simple/testclient.jks", "testclient")) + .build(); } @@ -131,7 +93,6 @@ public class SslRequireAuthTests extends ElasticsearchIntegrationTest { } @Test - @TestLogging("_root:DEBUG") public void testThatConnectionToHTTPWorks() throws Exception { File store = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());