Simplied SSL keystores and truststores

This puts one keystore in the SSLService.  An optional truststore can be configured but the
keystore defaults as the truststore.  This change also removed the ability to do certificate authentication with clients.

Resolves https://github.com/elasticsearch/elasticsearch-shield/issues/292

Original commit: elastic/x-pack-elasticsearch@59920db32a
This commit is contained in:
c-a-m 2014-11-12 17:06:57 -07:00
parent 90d4c43a6d
commit a224b54973
24 changed files with 337 additions and 336 deletions

View File

@ -17,9 +17,7 @@
"http.ssl": true,
"ssl" : {
"keystore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"keystore_password" : "testnode",
"truststore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"truststore_password" : "testnode"
"keystore_password" : "testnode"
},
"authc": {
"esusers.files" : {

View File

@ -17,9 +17,7 @@
"http.ssl": true,
"ssl" : {
"keystore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"keystore_password" : "testnode",
"truststore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"truststore_password" : "testnode"
"keystore_password" : "testnode"
},
"authc": {
"esusers.files" : {
@ -30,8 +28,6 @@
"mode" : "active_directory",
"domain_name" : "ad.test.elasticsearch.com",
"url" : "ldaps://ad.test.elasticsearch.com:636",
"truststore" : "src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks",
"truststore_password" : "changeit",
"unmapped_groups_as_roles" : "false",
"files" : {
"role_mapping": ".esvm-shield-config/role_mapping.yml"

View File

@ -17,9 +17,7 @@
"http.ssl": true,
"ssl" : {
"keystore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"keystore_password" : "testnode",
"truststore" : "src/test/resources/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks",
"truststore_password" : "testnode"
"keystore_password" : "testnode"
},
"authc": {
"ldap" : {
@ -28,8 +26,6 @@
"user_dn_templates": ["uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com"],
"group_search.group_search_dn" : "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com",
"group_search.subtree_search" : false,
"truststore" : "src/test/resources/org/elasticsearch/shield/authc/ldap/ldaptrust.jks",
"truststore_password" : "changeit",
"unmapped_groups_as_roles" : "false",
"files" : {
"role_mapping": ".esvm-shield-config/role_mapping.yml"

View File

@ -14,6 +14,7 @@ import org.elasticsearch.shield.audit.AuditTrailModule;
import org.elasticsearch.shield.authc.AuthenticationModule;
import org.elasticsearch.shield.authz.AuthorizationModule;
import org.elasticsearch.shield.key.KeyModule;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.shield.support.AbstractShieldModule;
import org.elasticsearch.shield.transport.SecuredRestModule;
import org.elasticsearch.shield.transport.SecuredTransportModule;
@ -62,5 +63,8 @@ public class ShieldModule extends AbstractShieldModule.Spawn implements PreProce
@Override
protected void configure(boolean clientMode) {
if (SSLService.isSSLEnabled(settings)) {
bind(SSLService.class).asEagerSingleton();
}
}
}

View File

@ -29,13 +29,13 @@ public class LdapModule extends AbstractShieldModule.Node {
protected void configureNode() {
if (enabled) {
/* This socket factory needs to be configured before any LDAP connections are created. LDAP configuration
for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory. This doesn't mesh well with
guice so we set the factory here during startup. See LdapSslSocketFactory for more details. */
LdapSslSocketFactory.init(settings);
for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory. */
requestStaticInjection(LdapSslSocketFactory.class);
bind(Realm.class).annotatedWith(named(LdapRealm.TYPE)).to(LdapRealm.class).asEagerSingleton();
bind(LdapGroupToRoleMapper.class).asEagerSingleton();
String mode = settings.getComponentSettings(LdapModule.class).get("mode", StandardLdapConnectionFactory.MODE_NAME);
if (StandardLdapConnectionFactory.MODE_NAME.equals(mode)) {
bind(LdapConnectionFactory.class).to(StandardLdapConnectionFactory.class);
} else if (ActiveDirectoryConnectionFactory.MODE_NAME.equals(mode)) {
@ -49,7 +49,7 @@ public class LdapModule extends AbstractShieldModule.Node {
}
}
static boolean enabled(Settings settings) {
public static boolean enabled(Settings settings) {
Settings authcSettings = settings.getAsSettings("shield.authc");
if (!authcSettings.names().contains("ldap")) {
return false;

View File

@ -7,11 +7,11 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.ESLoggerFactory;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.transport.ssl.SSLTrustConfig;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.SocketFactory;
import java.io.IOException;
@ -32,24 +32,23 @@ public class LdapSslSocketFactory extends SocketFactory {
static final String JAVA_NAMING_LDAP_FACTORY_SOCKET = "java.naming.ldap.factory.socket";
private static ESLogger logger = ESLoggerFactory.getLogger(LdapSslSocketFactory.class.getName());
private static LdapSslSocketFactory instance;
/**
* This should only be invoked once to establish a static instance that will be used for each constructor.
*/
public static void init(Settings settings) {
@Inject
public static void init(SSLService ssl) {
if (instance != null) {
logger.error("LdapSslSocketFactory already configured, this change could lead to threading issues");
}
Settings componentSettings = settings.getComponentSettings(LdapSslSocketFactory.class);
Settings generalSslSettings = settings.getByPrefix("shield.ssl.");
if (generalSslSettings.get("truststore") == null && componentSettings.get("truststore") == null){
logger.warn("No truststore has been configured for LDAP");
if (ssl == null) {
logger.warn("no keystore has been configured for LDAP");
} else {
SSLTrustConfig sslConfig = new SSLTrustConfig(componentSettings, generalSslSettings);
instance = new LdapSslSocketFactory(sslConfig.createSSLSocketFactory());
instance = new LdapSslSocketFactory(ssl.getSSLSocketFactory());
}
}
/**
@ -100,14 +99,7 @@ public class LdapSslSocketFactory extends SocketFactory {
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.
*/
public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) {
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 secureProtocol = secureUrls(ldapUrls);
if (secureProtocol && instance != null) {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
} else {
@ -117,4 +109,23 @@ public class LdapSslSocketFactory extends SocketFactory {
}
}
}
/**
* @param ldapUrls URLS in the form of "ldap://..." or "ldaps://..."
* @return true if all URLS are ldaps, also true it ldapUrls is empty. False otherwise
*/
public static boolean secureUrls(String[] ldapUrls) {
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) + "]");
}
}
return secureProtocol;
}
}

View File

@ -3,7 +3,7 @@
* 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;
package org.elasticsearch.shield.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.rest.RestStatus;

View File

@ -0,0 +1,137 @@
/*
* 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.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.ldap.LdapModule;
import org.elasticsearch.shield.authc.ldap.LdapSslSocketFactory;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Arrays;
/**
* This service houses the private key and trust managers needed for SSL/TLS negotiation. It is the central place to
* get SSLEngines and SocketFactories.
*/
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" };
private final TrustManagerFactory trustFactory;
private final SSLContext sslContext;
private final String[] ciphers;
@Inject
public SSLService(Settings settings) {
super(settings);
String keyStore = componentSettings.get("keystore", System.getProperty("javax.net.ssl.keyStore"));
String keyStorePassword = componentSettings.get("keystore_password", System.getProperty("javax.net.ssl.keyStorePassword"));
String keyStoreAlgorithm = componentSettings.get("keystore_algorithm", System.getProperty("ssl.KeyManagerFactory.algorithm", KeyManagerFactory.getDefaultAlgorithm()));
String trustStore = componentSettings.get("truststore", System.getProperty("javax.net.ssl.trustStore"));
String trustStorePassword = componentSettings.get("truststore_password", System.getProperty("javax.net.ssl.trustStorePassword"));
String trustStoreAlgorithm = componentSettings.get("truststore_algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm", TrustManagerFactory.getDefaultAlgorithm()));
if (trustStore == null) {
//the keystore will also be the truststore
trustStore = keyStore;
trustStorePassword = keyStorePassword;
}
if (keyStore == null) {
throw new ShieldSettingsException("No keystore configured");
}
if (keyStorePassword == null) {
throw new ShieldSettingsException("No keystore password configured");
}
this.ciphers = componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS);
//protocols supported: https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
String sslProtocol = componentSettings.get("protocol", "TLS");
logger.debug("using keyStore[{}], keyAlgorithm[{}], trustStore[{}], truststoreAlgorithm[{}], ciphersuites[{}], TLS protocol[{}]",
keyStore, keyStoreAlgorithm, trustStore, trustStoreAlgorithm, ciphers, sslProtocol);
try (FileInputStream in = new FileInputStream(trustStore)) {
// Load TrustStore
KeyStore ks = KeyStore.getInstance("jks");
ks.load(in, trustStorePassword == null ? null : trustStorePassword.toCharArray());
// Initialize a trust manager factory with the trusted store
trustFactory = TrustManagerFactory.getInstance(trustStoreAlgorithm);
trustFactory.init(ks);
} catch (Exception e) {
throw new ElasticsearchException("Failed to initialize a TrustManagerFactory", e);
}
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 ElasticsearchSSLException("Failed to initialize a KeyManagerFactory", e);
}
// Initialize sslContext
try {
sslContext = SSLContext.getInstance(sslProtocol);
sslContext.init(kmf.getKeyManagers(), trustFactory.getTrustManagers(), null);
} catch (Exception e) {
throw new ElasticsearchSSLException("Failed to initialize the SSLContext", e);
}
}
/**
* @return An SSLSocketFactory (for client-side SSL handshaking)
*/
public SSLSocketFactory getSSLSocketFactory() {
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)
* Node-from-Client (inbound):
* - sslEngine.setUserClientMode(false)
* - sslEngine.setNeedClientAuth(false)
* @return
*/
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);
}
return sslEngine;
}
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")) &&
LdapModule.enabled(settings));
}
}

View File

@ -14,7 +14,7 @@ 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.transport.ssl.SSLConfig;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.ssl.SSLEngine;
@ -25,12 +25,14 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
private final boolean ssl;
private final N2NNettyUpstreamHandler shieldUpstreamHandler;
private final SSLService sslService;
@Inject
public NettySecuredHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays,
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler) {
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler, SSLService sslService) {
super(settings, networkService, bigArrays);
this.ssl = settings.getAsBoolean("shield.http.ssl", false);
this.sslService = sslService;
this.shieldUpstreamHandler = shieldUpstreamHandler;
}
@ -41,25 +43,18 @@ public class NettySecuredHttpServerTransport extends NettyHttpServerTransport {
private class HttpSslChannelPipelineFactory extends HttpChannelPipelineFactory {
private final SSLConfig sslConfig;
public HttpSslChannelPipelineFactory(NettyHttpServerTransport transport) {
super(transport);
if (ssl) {
sslConfig = new SSLConfig(settings.getByPrefix("shield.http.ssl."), settings.getByPrefix("shield.ssl."), false);
// try to create an SSL engine, so that exceptions lead to early exit
sslConfig.createSSLEngine();
} else {
sslConfig = null;
}
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine engine = sslConfig.createSSLEngine();
SSLEngine engine = sslService.createSSLEngine();
engine.setUseClientMode(false);
engine.setNeedClientAuth(false);
pipeline.addFirst("ssl", new SslHandler(engine));
}
if (shieldUpstreamHandler != null) {

View File

@ -14,7 +14,7 @@ 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.transport.ssl.SSLConfig;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.netty.NettyTransport;
@ -27,13 +27,15 @@ public class NettySecuredTransport extends NettyTransport {
private final boolean ssl;
private final N2NNettyUpstreamHandler shieldUpstreamHandler;
private final SSLService sslService;
@Inject
public NettySecuredTransport(Settings settings, ThreadPool threadPool, NetworkService networkService, BigArrays bigArrays, Version version,
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler) {
@Nullable N2NNettyUpstreamHandler shieldUpstreamHandler, SSLService sslService) {
super(settings, threadPool, networkService, bigArrays, version);
this.shieldUpstreamHandler = shieldUpstreamHandler;
this.ssl = settings.getAsBoolean("shield.transport.ssl", false);
this.sslService = sslService;
}
@Override
@ -48,25 +50,17 @@ public class NettySecuredTransport extends NettyTransport {
private class SslServerChannelPipelineFactory extends ServerChannelPipelineFactory {
private final SSLConfig sslConfig;
public SslServerChannelPipelineFactory(NettyTransport nettyTransport, String name, Settings settings, Settings profileSettings) {
super(nettyTransport, name, settings);
if (ssl) {
sslConfig = new SSLConfig(this.settings.getByPrefix("shield.transport.ssl."), this.settings.getByPrefix("shield.ssl."), true);
// try to create an SSL engine, so that exceptions lead to early exit
sslConfig.createSSLEngine();
} else {
sslConfig = null;
}
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine serverEngine = sslConfig.createSSLEngine();
SSLEngine serverEngine = sslService.createSSLEngine();
serverEngine.setUseClientMode(false);
serverEngine.setNeedClientAuth(true);
pipeline.addFirst("ssl", new SslHandler(serverEngine));
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, logger));
@ -80,24 +74,15 @@ public class NettySecuredTransport extends NettyTransport {
private class SslClientChannelPipelineFactory extends ClientChannelPipelineFactory {
private final SSLConfig sslConfig;
public SslClientChannelPipelineFactory(NettyTransport transport) {
super(transport);
if (ssl) {
sslConfig = new SSLConfig(settings.getByPrefix("shield.transport.ssl."), settings.getByPrefix("shield.ssl."), true);
// try to create an SSL engine, so that exceptions lead to early exit
sslConfig.createSSLEngine();
} else {
sslConfig = null;
}
}
@Override
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine clientEngine = sslConfig.createSSLEngine();
SSLEngine clientEngine = sslService.createSSLEngine();
clientEngine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(clientEngine));

View File

@ -11,7 +11,6 @@ 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.shield.transport.ssl.ElasticsearchSSLException;
import org.elasticsearch.transport.netty.MessageChannelHandler;
public class SecuredMessageChannelHandler extends MessageChannelHandler {

View File

@ -1,93 +0,0 @@
/*
* 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.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import javax.net.ssl.*;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Arrays;
/**
*
*/
public class SSLConfig {
private static final ESLogger logger = Loggers.getLogger(SSLConfig.class);
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" };
private final boolean clientAuth;
private SSLContext sslContext;
private String[] ciphers;
public SSLConfig(Settings componentSettings, Settings defaultSettings, boolean defaultRequireClientAuth) {
this.clientAuth = componentSettings.getAsBoolean("require.client.auth", defaultSettings.getAsBoolean("require.client.auth", defaultRequireClientAuth));
TrustManager[] trustManagers = null;
if (clientAuth) {
trustManagers = new SSLTrustConfig(componentSettings, defaultSettings).getTrustManagers();
}
String keyStore = componentSettings.get("keystore", defaultSettings.get("keystore", System.getProperty("javax.net.ssl.keyStore")));
String keyStorePassword = componentSettings.get("keystore_password", defaultSettings.get("keystore_password", System.getProperty("javax.net.ssl.keyStorePassword")));
String keyStoreAlgorithm = componentSettings.get("keystore_algorithm", defaultSettings.get("keystore_algorithm", System.getProperty("ssl.KeyManagerFactory.algorithm")));
this.ciphers = componentSettings.getAsArray("ciphers", defaultSettings.getAsArray("ciphers", DEFAULT_CIPHERS));
if (keyStore == null) {
throw new ElasticsearchException("SSL Enabled, but keystore unconfigured");
}
if (keyStoreAlgorithm == null) {
keyStoreAlgorithm = KeyManagerFactory.getDefaultAlgorithm();
}
logger.debug("using keyStore[{}], keyAlgorithm[{}], ", keyStore, keyStoreAlgorithm);
if (!new File(keyStore).exists()) {
throw new ElasticsearchSSLException("Keystore at path ["+ keyStore +"] does not exist");
}
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 ElasticsearchSSLException("Failed to initialize a KeyManagerFactory", e);
}
// Initialize sslContext
try {
String algorithm = componentSettings.get("context_algorithm", defaultSettings.get("shield.ssl.context_algorithm", "TLS"));
sslContext = SSLContext.getInstance(algorithm);
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
} catch (Exception e) {
throw new ElasticsearchSSLException("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

@ -1,82 +0,0 @@
/*
* 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.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
/**
*
*/
public class SSLTrustConfig {
private static final ESLogger logger = Loggers.getLogger(SSLTrustConfig.class);
private final TrustManager[] trustManagers;
private final String sslContextAlgorithm;
private SSLContext sslContext;
public SSLTrustConfig(Settings componentSettings, Settings defaultSettings) {
this.sslContextAlgorithm = componentSettings.get("context_algorithm", defaultSettings.get("shield.ssl.context_algorithm", "TLS"));
String trustStore = componentSettings.get("truststore", defaultSettings.get("truststore", System.getProperty("javax.net.ssl.trustStore")));
String trustStorePassword = componentSettings.get("truststore_password", defaultSettings.get("truststore_password", System.getProperty("javax.net.ssl.trustStorePassword")));
String trustStoreAlgorithm = componentSettings.get("truststore_algorithm", defaultSettings.get("truststore_algorithm", System.getProperty("ssl.TrustManagerFactory.algorithm")));
if (trustStore == null) {
throw new ElasticsearchException("SSL Enabled, but truststore unconfigured");
}
if (trustStoreAlgorithm == null) {
trustStoreAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
}
logger.debug("using trustStore[{}], trustAlgorithm[{}]", trustStore, trustStoreAlgorithm);
if (!new File(trustStore).exists()) {
throw new ElasticsearchSSLException("Truststore at path ["+ trustStore +"] does not exist");
}
try (FileInputStream in = new FileInputStream(trustStore)) {
// Load TrustStore
KeyStore ks = KeyStore.getInstance("jks");
ks.load(in, trustStorePassword == null ? null : 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);
}
}
public SSLSocketFactory createSSLSocketFactory() {
// Initialize sslContext
try {
sslContext = SSLContext.getInstance(sslContextAlgorithm);
sslContext.init(null, trustManagers, null);
} catch (Exception e) {
throw new ElasticsearchSSLException("Failed to initialize the SSLContext", e);
}
return sslContext.getSocketFactory();
}
public TrustManager[] getTrustManagers() {
return trustManagers;
}
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
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.hamcrest.Matchers;
@ -29,9 +30,11 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
LdapSslSocketFactory.init(ImmutableSettings.builder()
.put(SETTINGS_PREFIX + "truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()))
.build());
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore", filename)
.put("shield.ssl.keystore_password", "changeit")
.build()));
}

View File

@ -8,6 +8,7 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.hamcrest.Matchers;
import org.junit.BeforeClass;
@ -23,10 +24,11 @@ public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
//LdapModule will set this up as a singleton normally
LdapSslSocketFactory.init(ImmutableSettings.builder()
.put("shield.authc.ldap.truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()))
.build());
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore", filename)
.put("shield.ssl.keystore_password", "changeit")
.build()));
}
@Test

View File

@ -7,6 +7,7 @@ package org.elasticsearch.shield.authc.ldap;
import org.elasticsearch.common.settings.ImmutableSettings;
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.BeforeClass;
@ -26,10 +27,11 @@ public class OpenLdapTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
//LdapModule will set this up as a singleton normally
LdapSslSocketFactory.init(ImmutableSettings.builder()
.put(SETTINGS_PREFIX + "truststore", new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()))
.build());
File filename = new File(LdapConnectionTests.class.getResource("ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore", filename)
.put("shield.ssl.keystore_password", "changeit")
.build()));
}
@Test

View File

@ -0,0 +1,119 @@
/*
* 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.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.*;
public class SSLServiceTests extends ElasticsearchTestCase {
File testnodeStore;
@Before
public void setup() throws Exception {
testnodeStore = new File(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks").toURI());
}
@Test
public void testThatInvalidProtocolThrowsException() throws Exception {
try {
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
public void testSpecificProtocol() {
SSLService ssl = new SSLService(settingsBuilder()
.put("shield.ssl.protocol", "TLSv1.2")
.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());
Assert.assertThat(ssl.createSSLEngine().getSSLParameters().getProtocols(), Matchers.arrayContaining("TLSv1.2"));
}
@Test
public void testIsSSLEnabled_allDefaults(){
Settings settings = settingsBuilder().build();
assertThat(SSLService.isSSLEnabled(settings), is(false));
}
@Test
public void testIsSSLEnabled_transportOffHttpOffLdapOff(){
Settings settings = settingsBuilder()
.put("shield.transport.ssl", false)
.put("shield.http.ssl", false)
.put("shield.authc.ldap.mode", "ldap")
.put("shield.authc.ldap.url", "ldap://example.com:389")
.build();
assertThat(SSLService.isSSLEnabled(settings), is(false));
}
@Test
public void testIsSSLEnabled_transportOffHttpOffLdapMissingUrl() {
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
.build();
assertThat(SSLService.isSSLEnabled(settings), is(true));
}
@Test
public void testIsSSLEnabled_transportOffHttpOffLdapOn(){
Settings settings = settingsBuilder()
.put("shield.transport.ssl", false)
.put("shield.http.ssl", false)
.put("shield.authc.ldap.mode", "ldap")
.put("shield.authc.ldap.url", "ldaps://example.com:636")
.build();
assertThat(SSLService.isSSLEnabled(settings), is(true));
}
@Test
public void testIsSSLEnabled_transportOffHttpOnLdapOff(){
Settings settings = settingsBuilder()
.put("shield.transport.ssl", false)
.put("shield.http.ssl", true)
.put("shield.authc.ldap.mode", "ldap")
.put("shield.authc.ldap.url", "ldap://example.com:389")
.build();
assertThat(SSLService.isSSLEnabled(settings), is(true));
}
@Test
public void testIsSSLEnabled_transportOnHttpOffLdapOff(){
Settings settings = settingsBuilder()
.put("shield.transport.ssl", true)
.put("shield.http.ssl", false)
.put("shield.authc.ldap.mode", "ldap")
.put("shield.authc.ldap.url", "ldap://example.com:389")
.build();
assertThat(SSLService.isSSLEnabled(settings), is(true));
}
}

View File

@ -158,15 +158,11 @@ public abstract class ShieldIntegrationTest extends ElasticsearchIntegrationTest
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.keystore", store.getPath())
.put("shield.http.ssl.keystore_password", password)
.put("shield.http.ssl.truststore", store.getPath())
.put("shield.http.ssl.truststore_password", password);
.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);
return builder.build();
}

View File

@ -1,69 +0,0 @@
/*
* 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.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.junit.Before;
import org.junit.Test;
import javax.net.ssl.SSLEngine;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
public class SSLConfigTests extends ElasticsearchTestCase {
File testnodeStore;
@Before
public void setup() throws Exception {
testnodeStore = new File(getClass().getResource("certs/simple/testnode.jks").toURI());
}
@Test
public void testThatInvalidContextAlgoThrowsException() throws Exception {
try {
new SSLConfig(ImmutableSettings.EMPTY,
settingsBuilder()
.put("context_algorithm", "non-existing")
.put("keystore", testnodeStore.getPath())
.put("keystore_password", "testnode")
.put("truststore", testnodeStore.getPath())
.put("truststore_password", "testnode")
.build(), false);
} catch (ElasticsearchSSLException e) {
assertThat(e.getRootCause(), instanceOf(NoSuchAlgorithmException.class));
}
}
@Test
public void testThatExactConfigOverwritesDefaultConfig() throws Exception {
//
Settings concreteSettings = settingsBuilder()
.put("ciphers", "TLS_RSA_WITH_AES_128_CBC_SHA")
.build();
Settings genericSettings = settingsBuilder()
.putArray("shield.ssl.ciphers", "TLS_RSA_WITH_AES_128_CBC_SHA256", "TLS_RSA_WITH_AES_128_CBC_SHA")
.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();
SSLConfig sslConfig = new SSLConfig(concreteSettings, genericSettings.getByPrefix("shield.ssl."), false);
SSLEngine sslEngine = sslConfig.createSSLEngine();
assertThat(sslEngine.getEnabledCipherSuites().length, is(1));
}
}

View File

@ -82,7 +82,7 @@ public class SslIntegrationTests extends ShieldIntegrationTest {
.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"})
.putArray("shield.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();

View File

@ -16,6 +16,7 @@ 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,6 +67,7 @@ public class SslRequireAuthTests extends ShieldIntegrationTest {
}
@Ignore //clients-certs are no longer configured for http
@Test(expected = SSLHandshakeException.class)
public void testThatRequireClientAuthRejectsWithoutCert() throws Exception {
TrustManager[] trustAllCerts = new TrustManager[]{