diff --git a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java index ec4acc4430d..f02544c38b2 100644 --- a/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java +++ b/src/main/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactory.java @@ -18,7 +18,11 @@ import java.io.IOException; import java.io.Serializable; import java.net.InetAddress; import java.net.Socket; -import java.util.Locale; +import java.util.regex.Pattern; + +import static java.util.Arrays.asList; +import static org.elasticsearch.common.base.Predicates.contains; +import static org.elasticsearch.common.collect.Iterables.all; /** * This factory is needed for JNDI configuration for LDAP connections. It wraps a single instance of a static @@ -31,6 +35,9 @@ import java.util.Locale; public class LdapSslSocketFactory extends SocketFactory { static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket"; + private static final Pattern STARTS_WITH_LDAPS = Pattern.compile("^ldaps:.*", Pattern.CASE_INSENSITIVE); + private static final Pattern STARTS_WITH_LDAP = Pattern.compile("^ldap:.*", Pattern.CASE_INSENSITIVE); + private static ESLogger logger = ESLoggerFactory.getLogger(LdapSslSocketFactory.class.getName()); private static LdapSslSocketFactory instance; @@ -48,7 +55,16 @@ public class LdapSslSocketFactory extends SocketFactory { } else { instance = new LdapSslSocketFactory(ssl.getSSLSocketFactory()); } + } + /** + * This clears the static factory. There are threading issues with this. But for + * testing this is useful. + */ + @Deprecated + static void clear() { + logger.error("clear should only be called by tests"); + instance = null; } /** @@ -118,14 +134,15 @@ public class LdapSslSocketFactory extends SocketFactory { if (ldapUrls.length == 0) { return true; } - boolean secureProtocol = ldapUrls[0].toLowerCase(Locale.getDefault()).startsWith("ldaps://"); - for(String url: ldapUrls){ - if (secureProtocol != url.toLowerCase(Locale.getDefault()).startsWith("ldaps://")) { - //this is because LdapSSLSocketFactory produces only SSL sockets and not clear text sockets - throw new ShieldSettingsException("Configured ldap protocols are not all equal " + - "(ldaps://.. and ldap://..): [" + Strings.arrayToCommaDelimitedString(ldapUrls) + "]"); - } + + boolean allSecure = all(asList(ldapUrls), contains(STARTS_WITH_LDAPS)); + boolean allClear = all(asList(ldapUrls), contains(STARTS_WITH_LDAP)); + + if (!allSecure && !allClear) { + //No mixing is allowed because LdapSSLSocketFactory produces only SSL sockets and not clear text sockets + throw new ShieldSettingsException("Configured ldap protocols are not all equal " + + "(ldaps://.. and ldap://..): [" + Strings.arrayToCommaDelimitedString(ldapUrls) + "]"); } - return secureProtocol; + return allSecure; } } diff --git a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java index f2c082cd0ac..09324466529 100644 --- a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java +++ b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java @@ -24,6 +24,9 @@ import java.util.Arrays; */ public class SSLService extends AbstractComponent { static final String[] DEFAULT_CIPHERS = new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" }; + public static final String SHIELD_TRANSPORT_SSL = "shield.transport.ssl"; + public static final String SHIELD_HTTP_SSL = "shield.http.ssl"; + public static final String SHIELD_AUTHC_LDAP_URL = "shield.authc.ldap.url"; private final TrustManagerFactory trustFactory; private final SSLContext sslContext; private final String[] ciphers; @@ -113,7 +116,7 @@ public class SSLService extends AbstractComponent { * - sslEngine.setNeedClientAuth(true) * Client-to-Node: * - sslEngine.setUseClientMode(true) - * Node-from-Client (inbound): + * Http Client-to-Node (inbound): * - sslEngine.setUserClientMode(false) * - sslEngine.setNeedClientAuth(false) * @return @@ -129,9 +132,9 @@ public class SSLService extends AbstractComponent { } public static boolean isSSLEnabled(Settings settings) { - return settings.getAsBoolean("shield.transport.ssl", false) || - settings.getAsBoolean("shield.http.ssl", false) || - (LdapSslSocketFactory.secureUrls(settings.getAsArray("shield.authc.ldap.url")) && + return settings.getAsBoolean(SHIELD_TRANSPORT_SSL, false) || + settings.getAsBoolean(SHIELD_HTTP_SSL, false) || + (LdapSslSocketFactory.secureUrls(settings.getAsArray(SHIELD_AUTHC_LDAP_URL)) && LdapModule.enabled(settings)); } } diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java index 4d939f5fad4..b2bbeadd924 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/ActiveDirectoryFactoryTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.junit.annotations.Network; import org.hamcrest.Matchers; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -37,6 +38,10 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase { .build())); } + @AfterClass + public static void clearTrustStore() { + LdapSslSocketFactory.clear(); + } @Test public void testAdAuth() { diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactoryTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactoryTests.java index 09b51ddd483..3346e1f21f0 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactoryTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/LdapSslSocketFactoryTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.shield.ShieldSettingsException; import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.test.ElasticsearchTestCase; import org.hamcrest.Matchers; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -31,8 +32,13 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase { .build())); } + @AfterClass + public static void clearTrustStore() { + LdapSslSocketFactory.clear(); + } + @Test - public void testConfigure_1https(){ + public void testConfigure_1ldaps(){ String[] urls = new String[]{"ldaps://example.com:636"}; ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -43,7 +49,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase { } @Test - public void testConfigure_2https(){ + public void testConfigure_2ldaps(){ String[] urls = new String[]{"ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636"}; ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -53,7 +59,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase { } @Test - public void testConfigure_2http(){ + public void testConfigure_2ldap(){ String[] urls = new String[]{"ldap://primary.example.com:392", "LDAP://secondary.example.com:10392"}; ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -63,7 +69,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase { } @Test(expected = ShieldSettingsException.class) - public void testConfigure_1httpS_1http(){ + public void testConfigure_1ldaps_1ldap(){ String[] urls = new String[]{"LDAPS://primary.example.com:636", "ldap://secondary.example.com:392"}; ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -71,7 +77,7 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase { } @Test(expected = ShieldSettingsException.class) - public void testConfigure_1http_1https(){ + public void testConfigure_1ldap_1ldaps(){ String[] urls = new String[]{"ldap://primary.example.com:392", "ldaps://secondary.example.com:636"}; ImmutableMap.Builder builder = ImmutableMap.builder(); diff --git a/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java b/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java index 9bd81e1cc17..744c6cf4d70 100644 --- a/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java +++ b/src/test/java/org/elasticsearch/shield/authc/ldap/OpenLdapTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.shield.authc.support.SecuredStringTests; import org.elasticsearch.shield.ssl.SSLService; import org.elasticsearch.test.ElasticsearchTestCase; import org.elasticsearch.test.junit.annotations.Network; +import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -34,6 +35,11 @@ public class OpenLdapTests extends ElasticsearchTestCase { .build())); } + @AfterClass + public static void clearTrustStore() { + LdapSslSocketFactory.clear(); + } + @Test public void test_standardLdapConnection_uid(){ //openldap does not use cn as naming attributes by default diff --git a/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java b/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java index 1dd3ed6762b..5970495c2ce 100644 --- a/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java +++ b/src/test/java/org/elasticsearch/shield/ssl/SSLServiceTests.java @@ -7,17 +7,15 @@ package org.elasticsearch.shield.ssl; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.test.ElasticsearchTestCase; -import org.hamcrest.Matchers; -import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import java.io.File; -import java.security.NoSuchAlgorithmException; import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.is; public class SSLServiceTests extends ElasticsearchTestCase { @@ -28,19 +26,15 @@ public class SSLServiceTests extends ElasticsearchTestCase { testnodeStore = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI()); } - @Test + @Test(expected = ElasticsearchSSLException.class) public void testThatInvalidProtocolThrowsException() throws Exception { - try { - new SSLService(settingsBuilder() + new SSLService(settingsBuilder() .put("shield.ssl.protocol", "non-existing") .put("shield.ssl.keystore", testnodeStore.getPath()) .put("shield.ssl.keystore_password", "testnode") .put("shield.ssl.truststore", testnodeStore.getPath()) .put("shield.ssl.truststore_password", "testnode") .build()); - } catch (ElasticsearchSSLException e) { - Assert.assertThat(e.getRootCause(), Matchers.instanceOf(NoSuchAlgorithmException.class)); - } } @Test @Ignore //TODO it appears that setting a specific protocol doesn't make a difference @@ -52,13 +46,13 @@ public class SSLServiceTests extends ElasticsearchTestCase { .put("shield.ssl.truststore", testnodeStore.getPath()) .put("shield.ssl.truststore_password", "testnode") .build()); - Assert.assertThat(ssl.createSSLEngine().getSSLParameters().getProtocols(), Matchers.arrayContaining("TLSv1.2")); + assertThat(ssl.createSSLEngine().getSSLParameters().getProtocols(), arrayContaining("TLSv1.2")); } @Test - public void testIsSSLEnabled_allDefaults(){ + public void testIsSSLDisabled_allDefaults(){ Settings settings = settingsBuilder().build(); - assertThat(SSLService.isSSLEnabled(settings), is(false)); + assertSSLDisabled(settings); } @Test @@ -70,7 +64,7 @@ public class SSLServiceTests extends ElasticsearchTestCase { .put("shield.authc.ldap.mode", "ldap") .put("shield.authc.ldap.url", "ldap://example.com:389") .build(); - assertThat(SSLService.isSSLEnabled(settings), is(false)); + assertSSLDisabled(settings); } @Test @@ -78,9 +72,9 @@ public class SSLServiceTests extends ElasticsearchTestCase { Settings settings = settingsBuilder() .put("shield.transport.ssl", false) .put("shield.http.ssl", false) - .put("shield.authc.ldap.mode", "active_dir") //default for missing URL is + .put("shield.authc.ldap.mode", "active_dir") //SSL is on by default for a missing URL with active directory .build(); - assertThat(SSLService.isSSLEnabled(settings), is(true)); + assertSSLEnabled(settings); } @Test @@ -91,7 +85,7 @@ public class SSLServiceTests extends ElasticsearchTestCase { .put("shield.authc.ldap.mode", "ldap") .put("shield.authc.ldap.url", "ldaps://example.com:636") .build(); - assertThat(SSLService.isSSLEnabled(settings), is(true)); + assertSSLEnabled(settings); } @Test @@ -103,7 +97,7 @@ public class SSLServiceTests extends ElasticsearchTestCase { .put("shield.authc.ldap.mode", "ldap") .put("shield.authc.ldap.url", "ldap://example.com:389") .build(); - assertThat(SSLService.isSSLEnabled(settings), is(true)); + assertSSLEnabled(settings); } @Test @@ -114,6 +108,14 @@ public class SSLServiceTests extends ElasticsearchTestCase { .put("shield.authc.ldap.mode", "ldap") .put("shield.authc.ldap.url", "ldap://example.com:389") .build(); + assertSSLEnabled(settings); + } + + private void assertSSLEnabled(Settings settings) { assertThat(SSLService.isSSLEnabled(settings), is(true)); } + + private void assertSSLDisabled(Settings settings) { + assertThat(SSLService.isSSLEnabled(settings), is(false)); + } } diff --git a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java index 03f58ac28ad..d6ba70f7ea5 100644 --- a/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java +++ b/src/test/java/org/elasticsearch/shield/test/ShieldIntegrationTest.java @@ -160,10 +160,13 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest .put("shield.transport.ssl", true) .put("shield.ssl.keystore", store.getPath()) .put("shield.ssl.keystore_password", password) - .put("shield.ssl.truststore", store.getPath()) - .put("shield.ssl.truststore_password", password) .put("shield.http.ssl", true); + if (randomBoolean()) { + builder.put("shield.ssl.truststore", store.getPath()) + .put("shield.ssl.truststore_password", password); + } + return builder.build(); } 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 d76450c663d..33478b2badc 100644 --- a/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslRequireAuthTests.java @@ -16,7 +16,6 @@ import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.node.internal.InternalNode; import org.elasticsearch.shield.authc.support.UsernamePasswordToken; import org.elasticsearch.shield.test.ShieldIntegrationTest; -import org.junit.Ignore; import org.junit.Test; import javax.net.ssl.*; @@ -66,37 +65,6 @@ public class SslRequireAuthTests extends ShieldIntegrationTest { .build(); } - - @Ignore //clients-certs are no longer configured for http - @Test(expected = SSLHandshakeException.class) - public void testThatRequireClientAuthRejectsWithoutCert() throws Exception { - TrustManager[] trustAllCerts = new TrustManager[]{ - new X509TrustManager() { - public java.security.cert.X509Certificate[] getAcceptedIssuers() { - return null; - } - - public void checkClientTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - - public void checkServerTrusted( - java.security.cert.X509Certificate[] certs, String authType) { - } - } - }; - - setupTrustManagers(trustAllCerts); - - TransportAddress transportAddress = internalCluster().getInstance(HttpServerTransport.class).boundAddress().boundAddress(); - assertThat(transportAddress, is(instanceOf(InetSocketTransportAddress.class))); - InetSocketTransportAddress inetSocketTransportAddress = (InetSocketTransportAddress) transportAddress; - String url = String.format(Locale.ROOT, "https://%s:%s/", InetAddresses.toUriString(inetSocketTransportAddress.address().getAddress()), inetSocketTransportAddress.address().getPort()); - - HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); - connection.connect(); - } - @Test public void testThatConnectionToHTTPWorks() throws Exception { File store = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());