Added SSL support in netty

This introduces the possibility to have all communications (transport
and HTTP) to run over SSL.

Original commit: elastic/x-pack-elasticsearch@c816a65f53
This commit is contained in:
Alexander Reelsen 2014-07-17 08:32:59 +02:00
parent bc0e233589
commit d0673b0cfb
17 changed files with 716 additions and 3 deletions

View File

@ -4,5 +4,37 @@ This plugins adds security features to elasticsearch
== Access control
== Encrypted communication
== Encrypted communication using TLS/SSL
=== Configuration parameters
==== Transport protocol
* `transport.tcp.ssl`: true|false (defaults to true)
* `transport.tcp.ssl.keystore`: /path/to/the/keystore (absolute path to the keystore, which contains private keys)
* `transport.tcp.ssl.keystore_password`: password of the keystore
* `transport.tcp.ssl.keystore_algorithm`: keystore format (defaults to SunX509)
* `transport.tcp.ssl.truststore`: /path/to/the/truststore (absolute path to the truststore, which contains trusted keys)
* `transport.tcp.ssl.truststore_password`: password of the truststore
* `transport.tcp.ssl.truststore_algorithm`: truststore format (defaults to SunX509)
* `transport.tcp.ssl.client.auth`: true|false (defaults to true)
* `transport.tcp.ssl.ciphers`: Supported ciphers, defaults to `TLS_RSA_WITH_AES_128_CBC_SHA256` and `TLS_RSA_WITH_AES_128_CBC_SHA`
==== HTTP
* `http.ssl`: true|false (defaults to true)
* `http.ssl.keystore`: /path/to/the/keystore (absolute path to the keystore, which contains private keys)
* `http.ssl.keystore_password`: password of the keystore
* `http.ssl.keystore_algorithm`: keystore format (defaults to SunX509)
* `http.ssl.truststore`: /path/to/the/truststore (absolute path to the truststore, which contains trusted keys)
* `http.ssl.truststore_password`: password of the truststore
* `http.ssl.truststore_algorithm`: truststore format (defaults to SunX509)
* `http.ssl.client.auth`: true|false (defaults to true)
* `http.ssl.ciphers`: Supported ciphers, defaults to `TLS_RSA_WITH_AES_128_CBC_SHA256` and `TLS_RSA_WITH_AES_128_CBC_SHA`
== Generating certificates
=== Using self signed certificates per node
=== Using an own CA

View File

@ -77,6 +77,13 @@
<version>2.1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
<scope>test</scope>
</dependency>
<!-- real dependencies -->

View File

@ -29,6 +29,11 @@ public class SecurityModule extends AbstractModule implements SpawnModules {
@Override
public Iterable<? extends Module> spawnModules() {
// dont spawn module in client mode
if (settings.getAsBoolean("node.client", false)) {
return ImmutableList.of();
}
return ImmutableList.of(
Modules.createModule(AuthenticationModule.class, settings),
Modules.createModule(AuthorizationModule.class, settings),

View File

@ -0,0 +1,29 @@
/*
* 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.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.rest.RestStatus;
/**
*
*/
public class ElasticsearchSSLException extends ElasticsearchException {
public ElasticsearchSSLException(String msg) {
super(msg);
}
public ElasticsearchSSLException(String msg, Throwable cause) {
super(msg, cause);
}
@Override
public RestStatus status() {
return RestStatus.BAD_REQUEST;
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Arrays;
/**
*
*/
public class SSLConfig {
private static final ESLogger logger = Loggers.getLogger(SSLConfig.class);
// TODO removing the second one results in fails, need to verify the differences, maybe per JVM?
public static final String[] DEFAULT_CIPHERS = new String[] { "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA" };
private final boolean clientAuth;
private SSLContext sslContext;
private String[] ciphers;
public SSLConfig(Settings componentSettings) {
this(componentSettings, ImmutableSettings.EMPTY);
}
public SSLConfig(Settings componentSettings, Settings settings) {
this.clientAuth = componentSettings.getAsBoolean("client.auth", settings.getAsBoolean("ssl.client.auth", true));
String keyStore = componentSettings.get("keystore", settings.get("ssl.keystore", System.getProperty("javax.net.ssl.keyStore")));
String keyStorePassword = componentSettings.get("keystore_password", settings.get("ssl.keystore_password", System.getProperty("javax.net.ssl.keyStorePassword")));
String keyStoreAlgorithm = componentSettings.get("keystore_algorithm", settings.get("ssl.keystore_algorithm", System.getProperty("ssl.KeyManagerFactory.algorithm")));
String trustStore = componentSettings.get("truststore", settings.get("ssl.truststore", System.getProperty("javax.net.ssl.trustStore")));
String trustStorePassword = componentSettings.get("truststore_password", settings.get("ssl.truststore_password", System.getProperty("javax.net.ssl.trustStorePassword")));
String trustStoreAlgorithm = componentSettings.get("truststore_algorithm", settings.get("ssl.truststore_algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm")));
this.ciphers = componentSettings.getAsArray("ciphers", settings.getAsArray("ssl.ciphers", DEFAULT_CIPHERS));
if (keyStoreAlgorithm == null) {
keyStoreAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
}
if (trustStoreAlgorithm == null) {
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
}
logger.debug("using keyStore[{}], keyAlgorithm[{}], trustStore[{}], trustAlgorithm[{}]", keyStore, keyStoreAlgorithm, trustStore, trustStoreAlgorithm);
KeyStore ks = null;
KeyManagerFactory kmf = null;
try (FileInputStream in = new FileInputStream(keyStore)){
// Load KeyStore
ks = KeyStore.getInstance("jks");
ks.load(in, keyStorePassword.toCharArray());
// Initialize KeyManagerFactory
kmf = KeyManagerFactory.getInstance(keyStoreAlgorithm);
kmf.init(ks, keyStorePassword.toCharArray());
} catch (Exception e) {
throw new ElasticsearchException("Failed to initialize a KeyManagerFactory", e);
}
TrustManager[] trustManagers = null;
try (FileInputStream in = new FileInputStream(trustStore)) {
// Load TrustStore
ks.load(in, trustStorePassword.toCharArray());
// Initialize a trust manager factory with the trusted store
TrustManagerFactory trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
trustFactory.init(ks);
// Retrieve the trust managers from the factory
trustManagers = trustFactory.getTrustManagers();
} catch (Exception e) {
throw new ElasticsearchException("Failed to initialize a TrustManagerFactory", e);
}
// Initialize sslContext
try {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
} catch (Exception e) {
throw new Error("Failed to initialize the SSLContext", e);
}
}
public SSLEngine createSSLEngine() {
SSLEngine sslEngine = sslContext.createSSLEngine();
try {
sslEngine.setEnabledCipherSuites(ciphers);
} catch (Throwable t) {
throw new ElasticsearchSSLException("Error loading cipher suites ["+Arrays.asList(ciphers)+"]", t);
}
sslEngine.setNeedClientAuth(clientAuth);
return sslEngine;
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.ssl.netty;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.netty.channel.ChannelPipeline;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.http.netty.NettyHttpServerTransport;
import org.elasticsearch.shield.ssl.SSLConfig;
import javax.net.ssl.SSLEngine;
/**
*
*/
public class NettySSLHttpServerTransport extends NettyHttpServerTransport {
private final boolean ssl;
@Inject
public NettySSLHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays) {
super(settings, networkService, bigArrays);
this.ssl = settings.getAsBoolean("http.ssl", false);
}
@Override
public ChannelPipelineFactory configureServerChannelPipelineFactory() {
return new HttpSslChannelPipelineFactory(this);
}
private class HttpSslChannelPipelineFactory extends HttpChannelPipelineFactory {
public HttpSslChannelPipelineFactory(NettyHttpServerTransport transport) {
super(transport);
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLConfig sslConfig = new SSLConfig(settings.getByPrefix("http.ssl."));
SSLEngine engine = sslConfig.createSSLEngine();
engine.setUseClientMode(false);
// TODO MAKE ME CONFIGURABLE
engine.setNeedClientAuth(false);
pipeline.addFirst("ssl", new SslHandler(engine));
}
return pipeline;
}
}
}

View File

@ -0,0 +1,22 @@
/*
* 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.ssl.netty;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.http.HttpServerTransport;
import org.elasticsearch.http.netty.NettyHttpServerTransport;
/**
*
*/
public class NettySSLHttpServerTransportModule extends AbstractModule {
@Override
protected void configure() {
bind(HttpServerTransport.class).to(NettySSLHttpServerTransport.class).asEagerSingleton();
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.ssl.netty;
import org.elasticsearch.Version;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.netty.channel.ChannelPipeline;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.shield.ssl.SSLConfig;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.netty.NettyTransport;
import javax.net.ssl.SSLEngine;
/**
*
*/
public class NettySSLTransport extends NettyTransport {
private final boolean ssl;
@Inject
public NettySSLTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, Version version) {
super(settings, threadPool, networkService, bigArrays, version);
this.ssl = settings.getAsBoolean("transport.tcp.ssl", false);
}
@Override
public ChannelPipelineFactory configureClientChannelPipelineFactory() {
return new SslClientChannelPipelineFactory(this);
}
@Override
public ChannelPipelineFactory configureServerChannelPipelineFactory() {
return new SslServerChannelPipelineFactory(this);
}
private class SslServerChannelPipelineFactory extends ServerChannelPipeFactory {
private final SSLConfig sslConfig;
public SslServerChannelPipelineFactory(NettyTransport nettyTransport) {
super(nettyTransport);
sslConfig = new SSLConfig(settings.getByPrefix("transport.tcp.ssl."));
// try to create an SSL engine, so that exceptions lead to early exit
sslConfig.createSSLEngine();
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine serverEngine = sslConfig.createSSLEngine();
serverEngine.setUseClientMode(false);
pipeline.addFirst("ssl", new SslHandler(serverEngine));
pipeline.replace("dispatcher", "dispatcher", new SecureMessageChannelHandler(nettyTransport, logger));
}
return pipeline;
}
}
private class SslClientChannelPipelineFactory extends ClientChannelPipelineFactory {
private final SSLConfig sslConfig;
public SslClientChannelPipelineFactory(NettyTransport transport) {
super(transport);
sslConfig = new SSLConfig(settings.getByPrefix("transport.tcp.ssl."));
// try to create an SSL engine, so that exceptions lead to early exit
sslConfig.createSSLEngine();
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine clientEngine = sslConfig.createSSLEngine();
clientEngine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(clientEngine));
pipeline.replace("dispatcher", "dispatcher", new SecureMessageChannelHandler(nettyTransport, logger));
}
return pipeline;
}
}
}

View File

@ -0,0 +1,28 @@
/*
* 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.ssl.netty;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.transport.Transport;
/**
*
*/
public class NettySSLTransportModule extends AbstractModule {
private final Settings settings;
public NettySSLTransportModule(Settings settings) {
this.settings = settings;
}
@Override
protected void configure() {
bind(NettySSLTransport.class).asEagerSingleton();
bind(Transport.class).to(NettySSLTransport.class);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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.ssl.netty;
import org.elasticsearch.shield.ssl.ElasticsearchSSLException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.netty.channel.ChannelFuture;
import org.elasticsearch.common.netty.channel.ChannelFutureListener;
import org.elasticsearch.common.netty.channel.ChannelHandlerContext;
import org.elasticsearch.common.netty.channel.ChannelStateEvent;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
import org.elasticsearch.transport.netty.MessageChannelHandler;
public class SecureMessageChannelHandler extends MessageChannelHandler {
public SecureMessageChannelHandler(org.elasticsearch.transport.netty.NettyTransport transport, ESLogger logger) {
super(transport, logger);
}
@Override
public void channelConnected(final ChannelHandlerContext ctx, final ChannelStateEvent e) throws Exception {
SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);
// Get notified when SSL handshake is done.
final ChannelFuture handshakeFuture = sslHandler.handshake();
handshakeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
logger.debug("SSL / TLS handshake completed for the channel.");
ctx.sendUpstream(e);
} else {
logger.error("SSL / TLS handshake failed, closing the channel");
future.getChannel().close();
throw new ElasticsearchSSLException("SSL / TLS handshake failed, closing the channel", future.getCause());
}
}
});
}
}

View File

@ -21,7 +21,7 @@ import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
/**
*
*/
@ClusterScope(scope = Scope.SUITE)
@ClusterScope(scope = Scope.SUITE, numDataNodes = 2)
public class ShieldPluginTests extends ElasticsearchIntegrationTest {
@Override
@ -36,7 +36,9 @@ public class ShieldPluginTests extends ElasticsearchIntegrationTest {
@Test
@TestLogging("_root:INFO,plugins.PluginsService:TRACE")
public void testThatPluginIsLoaded() {
NodesInfoResponse nodeInfos = internalCluster().clientNodeClient().admin().cluster().prepareNodesInfo().get();
logger.info("--> Getting nodes info");
NodesInfoResponse nodeInfos = internalCluster().transportClient().admin().cluster().prepareNodesInfo().get();
logger.info("--> Checking nodes info");
for (NodeInfo nodeInfo : nodeInfos.getNodes()) {
assertThat(nodeInfo.getPlugins().getInfos(), hasSize(1));
assertThat(nodeInfo.getPlugins().getInfos().get(0).getName(), is("shield"));

View File

@ -0,0 +1,250 @@
/*
* 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.ssl;
import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.base.Charsets;
import com.google.common.net.InetAddresses;
import org.elasticsearch.shield.ssl.ElasticsearchSSLException;
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.TransportClient;
import org.elasticsearch.common.io.Streams;
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.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.shield.plugin.SecurityPlugin;
import org.elasticsearch.shield.ssl.netty.NettySSLHttpServerTransportModule;
import org.elasticsearch.shield.ssl.netty.NettySSLTransportModule;
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.After;
import org.junit.Before;
import org.junit.Test;
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.elasticsearch.test.ElasticsearchIntegrationTest.ClusterScope;
import static org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoTimeout;
import static org.hamcrest.Matchers.*;
/**
* Created a testnode cert and a test client cert, which is imported into the keystore
*
* keytool -genkeypair -alias testnode -keystore testnode.jks -keyalg RSA -storepass testnode -keypass testnode -dname "cn=Elasticsearch Test Node, ou=elasticsearch, o=org"
* keytool -export -alias testnode -keystore testnode.jks -rfc -file testnode.cert -storepass testnode
*
* keytool -genkeypair -alias testclient -keystore testclient.jks -keyalg RSA -storepass testclient -keypass testclient -dname "cn=Elasticsearch Test Client, ou=elasticsearch, o=org"
* keytool -export -alias testclient -keystore testclient.jks -rfc -file testclient.cert -storepass testclient
*
* keytool -import -trustcacerts -alias testclient -file testclient.cert -keystore testnode.jks -storepass testnode -noprompt
* keytool -import -trustcacerts -alias testnode -file testnode.cert -keystore testclient.jks -storepass testclient -noprompt
*
*/
@ClusterScope(scope = Scope.SUITE, numDataNodes = 1, transportClientRatio = 0.0, numClientNodes = 0)
public class SslIntegrationTests extends ElasticsearchIntegrationTest {
/*
# transport.tcp.ssl.keystore: /path/to/the/keystore
# transport.tcp.ssl.keystore_password: password
# transport.tcp.ssl.keystore_algorithm: SunX509
#
# transport.tcp.ssl.truststore: /path/to/the/truststore
# transport.tcp.ssl.truststore_password: password
# transport.tcp.ssl.truststore_algorithm: PKIX
*/
@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);
}
return ImmutableSettings.settingsBuilder()
.put(super.nodeSettings(nodeOrdinal))
.put("discovery.zen.ping.multicast.ping.enabled", false)
// needed to ensure that netty transport is started
.put("node.mode", "network")
.put("transport.tcp.ssl", true)
.put("transport.tcp.ssl.keystore", testnodeStore.getPath())
.put("transport.tcp.ssl.keystore_password", "testnode")
.put("transport.tcp.ssl.truststore", testnodeStore.getPath())
.put("transport.tcp.ssl.truststore_password", "testnode")
.put("http.ssl", true)
.put("http.ssl.keystore", testnodeStore.getPath())
.put("http.ssl.keystore_password", "testnode")
.put("http.ssl.truststore", testnodeStore.getPath())
.put("http.ssl.truststore_password", "testnode")
// SSL SETUP
.put("http.type", NettySSLHttpServerTransportModule.class.getName())
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
.put("plugin.types", SecurityPlugin.class.getName())
.build();
}
@Before
public void setup() {
System.setProperty("javax.net.debug", "all");
}
@After
public void teardown() {
System.clearProperty("javax.net.debug");
}
@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);
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("transport.tcp.ssl.ciphers", new String[]{"TLS_ECDH_anon_WITH_RC4_128_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA"}).build();
TransportClient transportClient = new TransportClient(customSettings);
TransportAddress transportAddress = internalCluster().getInstance(Transport.class).boundAddress().boundAddress();
transportClient.addTransportAddress(transportAddress);
transportClient.admin().cluster().prepareHealth().get();
}
@Test
public void testConnectNodeWorks() throws Exception {
try (Node node = NodeBuilder.nodeBuilder().settings(getSettings("ssl_node")).node().start()) {
try (Client client = node.client()) {
assertGreenClusterState(client);
}
}
}
@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());
try (Node node = NodeBuilder.nodeBuilder().settings(settingsBuilder).node().start()) {
try (Client client = node.client()) {
assertGreenClusterState(client);
}
}
}
@Test(expected = ElasticsearchSSLException.class)
public void testConnectNodeFailsWithWrongCipher() throws Exception {
Settings customSettings = getSettings("ssl_node").put("transport.tcp.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("transport.tcp.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[]{
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) {
}
}
};
// Install the all-trusting trust manager
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
// totally secure
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
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();
assertThat(connection.getResponseCode(), is(200));
String data = Streams.copyToString(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8));
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("transport.tcp.ssl", true)
.put("transport.tcp.ssl.keystore", testClientKeyStore.getPath())
.put("transport.tcp.ssl.keystore_password", "testclient")
.put("transport.tcp.ssl.truststore", testClientTrustStore .getPath())
.put("transport.tcp.ssl.truststore_password", "testclient")
.put("discovery.zen.ping.multicast.ping.enabled", false)
.put(TransportModule.TRANSPORT_TYPE_KEY, NettySSLTransportModule.class.getName())
//.put("plugin.types", SecurityPlugin.class.getName())
.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));
}
}

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIIDMzCCAhugAwIBAgIEIqIBljANBgkqhkiG9w0BAQsFADBKMQwwCgYDVQQKEwNvcmcxFjAUBgNV
BAsTDWVsYXN0aWNzZWFyY2gxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGllbnQwHhcN
MTQwNzIyMDc1MzA1WhcNMTQxMDIwMDc1MzA1WjBKMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVs
YXN0aWNzZWFyY2gxIjAgBgNVBAMTGUVsYXN0aWNzZWFyY2ggVGVzdCBDbGllbnQwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCZTqtl7W0u/e7GAqyFhbC4DLlBByH7B0ZwPdh/ulVfIzDi
Er7zkOGQtxkrsWHQZmfeyroLwKhfIaA69Jvd+JRFqn53zW3iziduXeMs9qnNP6Mb1ePmBmrs6icU
J1Wcxz5O+JofJ8g3+9KWPHVS4Ls1or43W14fV5LAnSt5lOhC33ayawUstls3mdb2LUvqkgZTwFxd
SwRAB6o6x+5JLrA3p2sVh97C4PyC5BxfR7EWfR6/gc7BvcqP9yxfKPf4TmaufStNQ3ESoH5wzGUy
i/XWUy0GYjaqk+DM1RTjhVetDr2jPl2akILqA0W3pTtjutsVxmN0W8EN9hGBlCWHyxjHAgMBAAGj
ITAfMB0GA1UdDgQWBBR15qT6iIEGeeeE7IMY9ZxGhDQHWzANBgkqhkiG9w0BAQsFAAOCAQEAdhic
qBw2TqnkOZOtRCxt5u4DnZE63i1W//kuFEjf0ee6/PFpPlKLROCzBCnf/ys49ixg6Q50H6U0SKwL
bMhdyAqfZakEknR8uTpvWP3WA+BcCuUB2drlvd3DWI+FF889otOwacNOrwyHnrN7Uv18Hh9PPcyD
x3V9uZSWzIQ4hU2+Fe0HHLenxIUFh6nqfCBy+81+AKQQuN4vE7TRUq/2WIK5rmwB8pakEoyc15ks
i/NGa1GjUvfM8G4FprINJ+Xrg6kcC7SL5pJVMCA+8c/iMPm22/XOYj7m4UL9BXKW9FFJX+3AMVZm
I4xHtBAZU4nkbnvDrbmsQvzGAp565LBv1g==
-----END CERTIFICATE-----

Binary file not shown.

View File

@ -0,0 +1,17 @@
-----BEGIN CERTIFICATE-----
MIIDLzCCAhegAwIBAgIEQv9ZtTANBgkqhkiG9w0BAQsFADBIMQwwCgYDVQQKEwNvcmcxFjAUBgNV
BAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNVBAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMB4XDTE0
MDcyMjA3NTI0N1oXDTE0MTAyMDA3NTI0N1owSDEMMAoGA1UEChMDb3JnMRYwFAYDVQQLEw1lbGFz
dGljc2VhcmNoMSAwHgYDVQQDExdFbGFzdGljc2VhcmNoIFRlc3QgTm9kZTCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAJJ7G1e8NzJAhx4PjZQQQimDECwe3nN8ulG23cOE5fYePKh8b0L7
cr5O7gMOo7XiXZnptXh8n2J0Fi+3UqnyEallxUmUewdUiwGuzeFHHBtpS2S6LjXR2J+n0Q267u7j
wisnUxRv7Cwre/PP2U89b303M1mg/fqNE5Zjb1MpCywoWq+Z07xNDX9aoOWTKQbRuR2931IGZ1QL
gX//emymIGGh7XVPoydhtEkPcGXAdFz3OO/K33PTEk24vGzr33dBm/yywa12kaC/6Q49luNmdgsG
M5JQ8kKQe0Cv64yMC17pMI2PWwpBuInF1RCx588I3ctHCATm7AL2E/YvG9MqlvkCAwEAAaMhMB8w
HQYDVR0OBBYEFPi2gUxc88/DfM89Ku63Z/JkjeQxMA0GCSqGSIb3DQEBCwUAA4IBAQAm3XJyNIAZ
jXyI/Hxv0rdZRRIoMzSjA+vyGwcrr4lBLcOxDgtHM68PPfhleHDK4JtTaGftuFqv8ylGa+zVJ+0N
e7xFqvFwi0R9goU5j1GgTmt7mYPhgcgS8j9VCTxKZEsO0KmvV5CRz2jB1m4FdHbSR3KKwpgCt6kp
K0HifL+pRHQa3Ts9DCBmzG4fwNGPsZvoI47T+JQ9EiUWdEW+dUvr/0T6yLTRsfc0ftZ2OTIJ97JV
BgOlAa4b1b8v9933wv53fDwkhhdBvO/uFxrxeYk4szofe2l4fy5CdE7rkDk1mHIHtYWdAQhVL6cL
vuDnZppB7koaDhBsssDBpRstnEHL
-----END CERTIFICATE-----

Binary file not shown.

View File

@ -28,4 +28,7 @@ grant {
permission java.security.SecurityPermission "getProperty.networkaddress.cache.ttl";
permission java.security.SecurityPermission "getProperty.networkaddress.cache.negative.ttl";
// Needed for accept all ssl certs in tests
permission javax.net.ssl.SSLPermission "setHostnameVerifier";
};