From 8bcbc690ce7be085f47c7e2ee3ebf9897c6dc135 Mon Sep 17 00:00:00 2001 From: Alexander Reelsen Date: Tue, 2 Dec 2014 13:39:03 +0100 Subject: [PATCH] SSL: Re-enabling configuration option to disable client auth In order to not require client side SSL certs for transport clients another option was added in the profile configuration to enable or disable client side certs. The same option has also been added for HTTP. Original commit: elastic/x-pack-elasticsearch@9658598bdc152eee1e83a8e1e30d36bdc56cdecf --- .../elasticsearch/shield/ssl/SSLService.java | 18 +-- .../NettySecuredHttpServerTransport.java | 2 +- .../netty/NettySecuredTransport.java | 2 +- .../transport/ssl/SslClientAuthTests.java | 123 ++++++++++++++++++ 4 files changed, 129 insertions(+), 16 deletions(-) create mode 100644 src/test/java/org/elasticsearch/shield/transport/ssl/SslClientAuthTests.java diff --git a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java index dfb2cebcf10..da620e5ca2b 100644 --- a/src/main/java/org/elasticsearch/shield/ssl/SSLService.java +++ b/src/main/java/org/elasticsearch/shield/ssl/SSLService.java @@ -74,24 +74,14 @@ public class SSLService extends AbstractComponent { return sslContext.getSocketFactory(); } - /** - * This engine is configured with a trust manager and a keystore that should have only one private key. - * Four possible usages for elasticsearch exist: - * Node-to-Node outbound: - * - sslEngine.setUseClientMode(true) - * Node-to-Node inbound: - * - sslEngine.setUseClientMode(false) - * - sslEngine.setNeedClientAuth(true) - * Client-to-Node: - * - sslEngine.setUseClientMode(true) - * Http Client-to-Node (inbound): - * - sslEngine.setUserClientMode(false) - * - sslEngine.setNeedClientAuth(false) - */ public SSLEngine createSSLEngine() { return createSSLEngine(this.sslContext); } + public SSLContext getSslContext() { + return sslContext; + } + public SSLEngine createSSLEngineWithTruststore(Settings settings) { String trustStore = settings.get("truststore.path", System.getProperty("javax.net.ssl.trustStore")); String trustStorePassword = settings.get("truststore.password", System.getProperty("javax.net.ssl.trustStorePassword")); diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransport.java b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransport.java index dfb1d58be39..1c424bb384b 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransport.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredHttpServerTransport.java @@ -54,7 +54,7 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport { if (sslService != null) { SSLEngine engine = sslService.createSSLEngine(); engine.setUseClientMode(false); - engine.setNeedClientAuth(false); + engine.setNeedClientAuth(settings.getAsBoolean("shield.http.ssl.client.auth", false)); pipeline.addFirst("ssl", new SslHandler(engine)); } diff --git a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java index e5f244a2420..5f3019e2f5b 100644 --- a/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java +++ b/src/main/java/org/elasticsearch/shield/transport/netty/NettySecuredTransport.java @@ -69,7 +69,7 @@ public class NettySecuredTransport extends NettyTransport { serverEngine = sslService.createSSLEngine(); } serverEngine.setUseClientMode(false); - serverEngine.setNeedClientAuth(true); + serverEngine.setNeedClientAuth(profileSettings.getAsBoolean("shield.ssl.client.auth", settings.getAsBoolean("shield.transport.ssl.client.auth", true))); pipeline.addFirst("ssl", new SslHandler(serverEngine)); pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, logger)); diff --git a/src/test/java/org/elasticsearch/shield/transport/ssl/SslClientAuthTests.java b/src/test/java/org/elasticsearch/shield/transport/ssl/SslClientAuthTests.java new file mode 100644 index 00000000000..38ddfe02aa6 --- /dev/null +++ b/src/test/java/org/elasticsearch/shield/transport/ssl/SslClientAuthTests.java @@ -0,0 +1,123 @@ +/* + * 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.ssl; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.client.transport.TransportClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.http.HttpServerTransport; +import org.elasticsearch.node.internal.InternalNode; +import org.elasticsearch.shield.ssl.SSLService; +import org.elasticsearch.shield.ssl.SSLServiceProvider; +import org.elasticsearch.test.ShieldIntegrationTest; +import org.elasticsearch.test.ShieldSettingsSource; +import org.elasticsearch.test.rest.client.http.HttpRequestBuilder; +import org.elasticsearch.test.rest.client.http.HttpResponse; +import org.elasticsearch.transport.Transport; +import org.junit.Test; + +import javax.net.ssl.SSLHandshakeException; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; + +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.containsString; + +@ClusterScope(scope = Scope.SUITE) +public class SslClientAuthTests extends ShieldIntegrationTest { + + @Override + protected Settings nodeSettings(int nodeOrdinal) { + return settingsBuilder() + .put(super.nodeSettings(nodeOrdinal)) + // invert the require auth settings + .put("shield.transport.ssl", true) + .put("shield.http.ssl", true) + .put("shield.http.ssl.client.auth", true) + .put("transport.profiles.default.shield.ssl.client.auth", false) + .put(InternalNode.HTTP_ENABLED, true) + .build(); + } + + @Override + protected boolean sslTransportEnabled() { + return true; + } + + @Test(expected = SSLHandshakeException.class) + public void testThatHttpFailsWithoutSslClientAuth() throws IOException { + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + SSLContexts.createDefault(), + SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + + CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); + + new HttpRequestBuilder(client) + .httpTransport(internalCluster().getInstance(HttpServerTransport.class)) + .method("GET").path("/") + .protocol("https") + .execute(); + } + + @Test + public void testThatHttpWorksWithSslClientAuth() throws IOException { + Settings settings = settingsBuilder().put(ShieldSettingsSource.getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient.jks", "testclient")).build(); + SSLService sslService = new SSLServiceProvider(settings).get(); + + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( + sslService.getSslContext(), + SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + + CloseableHttpClient client = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); + + HttpResponse response = new HttpRequestBuilder(client) + .httpTransport(internalCluster().getInstance(HttpServerTransport.class)) + .method("GET").path("/") + .protocol("https") + .addHeader("Authorization", basicAuthHeaderValue(transportClientUsername(), transportClientPassword())) + .execute(); + assertThat(response.getBody(), containsString("You Know, for Search")); + } + + @Test + public void testThatTransportWorksWithoutSslClientAuth() throws Exception { + // specify an arbitrary keystore, that does not include the certs needed to connect to the transport protocol + File store; + try { + store = new File(ShieldSettingsSource.class.getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testclient-client-profile.jks").toURI()); + } catch (URISyntaxException e) { + throw new ElasticsearchException("exception while reading the store", e); + } + + if (!store.exists()) { + throw new ElasticsearchException("store path doesn't exist"); + } + + Settings settings = settingsBuilder() + .put("shield.transport.ssl", true) + .put("shield.ssl.keystore.path", store.getPath()) + .put("shield.ssl.keystore.password", "testclient-client-profile") + .put("cluster.name", internalCluster().getClusterName()) + .put("shield.user", transportClientUsername() + ":" + new String(transportClientPassword().internalChars())) + .build(); + try (TransportClient client = new TransportClient(settings)) { + Transport transport = internalCluster().getDataNodeInstance(Transport.class); + TransportAddress transportAddress = transport.boundAddress().publishAddress(); + client.addTransportAddress(transportAddress); + + assertGreenClusterState(client); + } + } +}