Add hostname verification support for transport and ldaps connections

SSL and TLS do not require hostname verification, but without it they are susceptible
to man in the middle attacks. This adds support for hostname verification for
transport client connections and for ldaps connections.

Closes elastic/elasticsearch#489

Original commit: elastic/x-pack-elasticsearch@c9380f0319
This commit is contained in:
jaymode 2014-12-17 11:19:19 -05:00
parent c052a8ca95
commit cc9568d1bb
29 changed files with 736 additions and 303 deletions

View File

@ -10,7 +10,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.active_directory.ActiveDirectoryRealm;
import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.ldap.LdapRealm;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import org.elasticsearch.shield.support.AbstractShieldModule;
/**
@ -27,7 +27,7 @@ public class AuthenticationModule extends AbstractShieldModule.Node {
// This socket factory needs to be configured before any LDAP connections are created. LDAP configuration
// for JNDI invokes a static getSocketFactory method from LdapSslSocketFactory.
requestStaticInjection(LdapSslSocketFactory.class);
requestStaticInjection(AbstractLdapSslSocketFactory.class);
MapBinder<String, Realm.Factory> mapBinder = MapBinder.newMapBinder(binder(), String.class, Realm.Factory.class);
mapBinder.addBinding(ESUsersRealm.TYPE).to(ESUsersRealm.Factory.class).asEagerSingleton();

View File

@ -12,7 +12,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
@ -59,7 +59,7 @@ public class ActiveDirectoryConnectionFactory extends ConnectionFactory {
.put("java.naming.ldap.attributes.binary", "tokenGroups")
.put(Context.REFERRAL, "follow");
LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder);
configureJndiSSL(ldapUrls, builder);
sharedLdapEnv = builder.build();
}

View File

@ -12,7 +12,7 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import javax.naming.Context;
import javax.naming.NamingException;
@ -63,7 +63,7 @@ public class LdapConnectionFactory extends ConnectionFactory {
.put(JNDI_LDAP_CONNECT_TIMEOUT, Long.toString(settings.getAsTime(TIMEOUT_TCP_CONNECTION_SETTING, TIMEOUT_DEFAULT).millis()))
.put(Context.REFERRAL, "follow");
LdapSslSocketFactory.configureJndiSSL(ldapUrls, builder);
configureJndiSSL(ldapUrls, builder);
sharedLdapEnv = builder.build();
groupSearchDN = settings.get(GROUP_SEARCH_BASEDN_SETTING);

View File

@ -0,0 +1,86 @@
/*
* 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.authc.support.ldap;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* Abstract class that wraps a SSLSocketFactory and uses it to create sockets for use with LDAP via JNDI
*/
public abstract class AbstractLdapSslSocketFactory extends SocketFactory {
protected static ESLogger logger = Loggers.getLogger(AbstractLdapSslSocketFactory.class);
protected static SSLService sslService;
private final SSLSocketFactory socketFactory;
/**
* This should only be invoked once to establish a static instance that will be used for each constructor.
*/
@Inject
public static void init(SSLService sslService) {
AbstractLdapSslSocketFactory.sslService = sslService;
}
public AbstractLdapSslSocketFactory(SSLSocketFactory sslSocketFactory) {
socketFactory = sslSocketFactory;
}
//The following methods are all wrappers around the instance of socketFactory
@Override
public SSLSocket createSocket() throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket();
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(String host, int port) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port, localHost, localPort);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(InetAddress host, int port) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port);
configureSSLSocket(socket);
return socket;
}
@Override
public SSLSocket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
SSLSocket socket = (SSLSocket) socketFactory.createSocket(host, port, localHost, localPort);
configureSSLSocket(socket);
return socket;
}
/**
* This method allows for performing additional configuration on each socket. All 'createSocket' methods will
* call this method before returning the socket to the caller. The default implementation is a no-op
* @param sslSocket
*/
protected void configureSSLSocket(SSLSocket sslSocket) {
}
}

View File

@ -5,12 +5,22 @@
*/
package org.elasticsearch.shield.authc.support.ldap;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.logging.ESLogger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.support.SecuredString;
import java.io.Serializable;
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 holds settings needed for authenticating to LDAP and creating LdapConnections.
* Each created LdapConnection needs to be closed or else connections will pill up consuming resources.
@ -31,7 +41,11 @@ public abstract class ConnectionFactory {
public static final String TIMEOUT_TCP_CONNECTION_SETTING = "timeout.tcp_connect";
public static final String TIMEOUT_TCP_READ_SETTING = "timeout.tcp_read";
public static final String TIMEOUT_LDAP_SETTING = "timeout.ldap_search";
public static final String HOSTNAME_VERIFICATION_SETTING = "hostname_verification";
public static final TimeValue TIMEOUT_DEFAULT = TimeValue.timeValueSeconds(5);
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);
protected final ESLogger logger = Loggers.getLogger(getClass());
private final Settings settings;
@ -49,4 +63,45 @@ public abstract class ConnectionFactory {
*/
public abstract AbstractLdapConnection open(String user, SecuredString password) ;
/**
* If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder
*
* @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing)
* @param builder set of jndi properties, that will
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.
*/
protected void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) {
boolean secureProtocol = secureUrls(ldapUrls);
if (secureProtocol) {
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, HostnameVerifyingLdapSslSocketFactory.class.getName());
logger.debug("using encryption for LDAP connections with hostname verification");
} else {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
logger.debug("using encryption for LDAP connections without hostname verification");
}
} else {
logger.warn("encryption not used for LDAP connections");
}
}
/**
* @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
*/
private boolean secureUrls(String[] ldapUrls) {
if (ldapUrls.length == 0) {
return true;
}
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 allSecure;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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.authc.support.ldap;
import javax.net.SocketFactory;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.io.IOException;
import java.net.InetAddress;
/**
* This factory is needed for JNDI configuration for LDAP connections with hostname verification. Each SSLSocket must
* have the appropriate SSLParameters set to indicate that hostname verification is required
*/
public class HostnameVerifyingLdapSslSocketFactory extends AbstractLdapSslSocketFactory {
private static HostnameVerifyingLdapSslSocketFactory instance;
private final SSLParameters sslParameters;
public HostnameVerifyingLdapSslSocketFactory(SSLSocketFactory socketFactory) {
super(socketFactory);
sslParameters = new SSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("LDAPS");
}
/**
* This is invoked by JNDI and the returned SocketFactory must be an HostnameVerifyingLdapSslSocketFactory object
*
* @return a singleton instance of HostnameVerifyingLdapSslSocketFactory set by calling the init static method.
*/
public static synchronized SocketFactory getDefault() {
if (instance == null) {
instance = new HostnameVerifyingLdapSslSocketFactory(sslService.getSSLSocketFactory());
}
return instance;
}
/**
* This clears the static factory. There are threading issues with this. But for
* testing this is useful.
*
* WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
*
* TODO: find a way to change the tests such that we can remove this method
*/
public static void clear() {
logger.error("clear should only be called by tests");
instance = null;
}
/**
* Configures the socket to require hostname verification using the LDAPS
* @param sslSocket
*/
@Override
protected void configureSSLSocket(SSLSocket sslSocket) {
sslSocket.setSSLParameters(sslParameters);
}
}

View File

@ -5,24 +5,8 @@
*/
package org.elasticsearch.shield.authc.support.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.Loggers;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.ssl.SSLService;
import javax.net.SocketFactory;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
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;
import javax.net.ssl.SSLSocketFactory;
/**
* This factory is needed for JNDI configuration for LDAP connections. It wraps a single instance of a static
@ -32,37 +16,12 @@ import static org.elasticsearch.common.collect.Iterables.all;
* <p/>
* http://docs.oracle.com/javase/tutorial/jndi/ldap/ssl.html
*/
public class LdapSslSocketFactory extends SocketFactory {
private static ESLogger logger = Loggers.getLogger(LdapSslSocketFactory.class);
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);
public class LdapSslSocketFactory extends AbstractLdapSslSocketFactory {
private static LdapSslSocketFactory instance;
private static SSLService sslService;
/**
* This should only be invoked once to establish a static instance that will be used for each constructor.
*/
@Inject
public static void init(SSLService sslService) {
LdapSslSocketFactory.sslService = sslService;
}
/**
* This clears the static factory. There are threading issues with this. But for
* testing this is useful.
*
* WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
*
* TODO: find a way to change the tests such that we can remove this method
*/
public static void clear() {
logger.error("clear should only be called by tests");
instance = null;
public LdapSslSocketFactory(SSLSocketFactory socketFactory) {
super(socketFactory);
}
/**
@ -77,75 +36,16 @@ public class LdapSslSocketFactory extends SocketFactory {
return instance;
}
final private SocketFactory socketFactory;
private LdapSslSocketFactory(SocketFactory wrappedSocketFactory) {
socketFactory = wrappedSocketFactory;
}
//The following methods are all wrappers around the instance of socketFactory
@Override
public Socket createSocket() throws IOException {
return socketFactory.createSocket();
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return socketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return socketFactory.createSocket(host, port, localHost, localPort);
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return socketFactory.createSocket(host, port);
}
@Override
public Socket createSocket(InetAddress host, int port, InetAddress localHost, int localPort) throws IOException {
return socketFactory.createSocket(host, port, localHost, localPort);
}
/**
* If one of the ldapUrls are SSL this will set the LdapSslSocketFactory as a socket provider on the builder
* This clears the static factory. There are threading issues with this. But for
* testing this is useful.
*
* @param ldapUrls array of ldap urls, either all SSL or none with SSL (no mixing)
* @param builder set of jndi properties, that will
* @throws org.elasticsearch.shield.ShieldSettingsException if URLs have mixed protocols.
* WARNING: THIS METHOD SHOULD ONLY BE CALLED IN TESTS!!!!
*
* TODO: find a way to change the tests such that we can remove this method
*/
public static void configureJndiSSL(String[] ldapUrls, ImmutableMap.Builder<String, Serializable> builder) {
boolean secureProtocol = secureUrls(ldapUrls);
if (secureProtocol) {
builder.put(JAVA_NAMING_LDAP_FACTORY_SOCKET, LdapSslSocketFactory.class.getName());
} else {
logger.warn("LdapSslSocketFactory not used for LDAP connections");
if (logger.isDebugEnabled()) {
logger.debug("LdapSslSocketFactory: secureProtocol = [{}]", secureProtocol);
}
}
}
/**
* @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 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 allSecure;
public static void clear() {
logger.error("clear should only be called by tests");
instance = null;
}
}

View File

@ -46,8 +46,12 @@ public class SSLService extends AbstractComponent {
}
public SSLEngine createSSLEngine(Settings settings) {
return createSSLEngine(settings, null, -1);
}
public SSLEngine createSSLEngine(Settings settings, String host, int port) {
String[] ciphers = settings.getAsArray("ciphers", componentSettings.getAsArray("ciphers", DEFAULT_CIPHERS));
return createSSLEngine(getSslContext(settings), ciphers);
return createSSLEngine(getSslContext(settings), ciphers, host, port);
}
public SSLContext getSslContext() {
@ -99,8 +103,8 @@ public class SSLService extends AbstractComponent {
return sslContext;
}
private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers) {
SSLEngine sslEngine = sslContext.createSSLEngine();
private SSLEngine createSSLEngine(SSLContext sslContext, String[] ciphers, String host, int port) {
SSLEngine sslEngine = sslContext.createSSLEngine(host, port);
try {
sslEngine.setEnabledCipherSuites(ciphers);
} catch (Throwable t) {

View File

@ -8,10 +8,10 @@ package org.elasticsearch.shield.transport.netty;
import org.elasticsearch.Version;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.inject.internal.Nullable;
import org.elasticsearch.common.netty.channel.ChannelPipeline;
import org.elasticsearch.common.netty.channel.ChannelPipelineFactory;
import org.elasticsearch.common.netty.channel.*;
import org.elasticsearch.common.netty.handler.ssl.SslHandler;
import org.elasticsearch.common.network.NetworkService;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.shield.ssl.SSLService;
@ -20,11 +20,14 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.netty.NettyTransport;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLParameters;
import java.net.InetSocketAddress;
/**
*
*/
public class NettySecuredTransport extends NettyTransport {
public static final String HOSTNAME_VERIFICATION_SETTING = "shield.ssl.hostname_verification";
private final SSLService sslService;
private final @Nullable IPFilter authenticator;
@ -91,13 +94,38 @@ public class NettySecuredTransport extends NettyTransport {
public ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = super.getPipeline();
if (ssl) {
SSLEngine clientEngine = sslService.createSSLEngine();
clientEngine.setUseClientMode(true);
pipeline.addFirst("ssl", new SslHandler(clientEngine));
pipeline.addFirst("sslInitializer", new ClientSslHandlerInitializer());
}
pipeline.replace("dispatcher", "dispatcher", new SecuredMessageChannelHandler(nettyTransport, "default", logger));
return pipeline;
}
/**
* Handler that waits until connect is called to create a SSLEngine with the proper parameters in order to
* perform hostname verification
*/
private class ClientSslHandlerInitializer extends SimpleChannelHandler {
@Override
public void connectRequested(ChannelHandlerContext ctx, ChannelStateEvent e) {
SSLEngine sslEngine;
if (settings.getAsBoolean(HOSTNAME_VERIFICATION_SETTING, true)) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) e.getValue();
String hostname = inetSocketAddress.getHostName();
int port = inetSocketAddress.getPort();
sslEngine = sslService.createSSLEngine(ImmutableSettings.EMPTY, hostname, port);
SSLParameters parameters = new SSLParameters();
parameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(parameters);
} else {
sslEngine = sslService.createSSLEngine();
}
sslEngine.setUseClientMode(true);
ctx.getPipeline().replace(this, "ssl", new SslHandler(sslEngine));
ctx.sendDownstream(e);
}
}
}
}

View File

@ -37,12 +37,14 @@ public class SecuredMessageChannelHandler extends MessageChannelHandler {
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.
if (sslHandler == null) {
// Make sure handler is present and we are the client
if (sslHandler == null || !sslHandler.getEngine().getUseClientMode()) {
return;
}
final ChannelFuture handshakeFuture = sslHandler.handshake();
// Get notified when SSL handshake is done.
handshakeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {

View File

@ -11,11 +11,9 @@ import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.ldap.LdapConnection;
import org.elasticsearch.shield.authc.ldap.LdapConnectionFactory;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
import org.elasticsearch.shield.authc.ldap.LdapException;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapConnection;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapTest;
import org.elasticsearch.shield.authc.support.ldap.*;
import org.elasticsearch.shield.ssl.SSLService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.test.junit.annotations.Network;
@ -40,7 +38,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
AbstractLdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename)
.put("shield.ssl.keystore.password", "changeit")
.build()));
@ -49,12 +47,13 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@AfterClass
public static void clearTrustStore() {
LdapSslSocketFactory.clear();
HostnameVerifyingLdapSslSocketFactory.clear();
}
@Test @SuppressWarnings("unchecked")
public void testAdAuth() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN));
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false));
String userName = "ironman";
try (AbstractLdapConnection ldap = connectionFactory.open(userName, SecuredStringTests.build(PASSWORD))) {
@ -76,6 +75,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public void testTcpReadTimeout() {
Settings settings = ImmutableSettings.builder()
.put(buildAdSettings(AD_LDAP_URL, AD_DOMAIN))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms")
.build();
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings);
@ -90,7 +90,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@Test
public void testAdAuth_avengers() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN));
buildAdSettings(AD_LDAP_URL, AD_DOMAIN, false));
String[] users = new String[]{"cap", "hawkeye", "hulk", "ironman", "thor", "blackwidow", };
for(String user: users) {
@ -102,7 +102,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
@Test @SuppressWarnings("unchecked")
public void testAdAuth_specificUserSearch() {
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com");
Settings settings = buildAdSettings(AD_LDAP_URL, AD_DOMAIN, "CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com", false);
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(settings);
String userName = "hulk";
@ -124,7 +124,7 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
public void testAD_standardLdapConnection(){
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true);
Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true, false);
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
String user = "Bruce Banner";
@ -146,18 +146,52 @@ public class ActiveDirectoryFactoryTests extends ElasticsearchTestCase {
}
}
@Test(expected = ActiveDirectoryException.class)
public void testAdAuthWithHostnameVerification() {
ActiveDirectoryConnectionFactory connectionFactory = new ActiveDirectoryConnectionFactory(
buildAdSettings(AD_LDAP_URL, AD_DOMAIN));
String userName = "ironman";
try (AbstractLdapConnection ldap = connectionFactory.open(userName, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
}
}
@Test(expected = LdapException.class)
public void testADStandardLdapHostnameVerification(){
String groupSearchBase = "DC=ad,DC=test,DC=elasticsearch,DC=com";
String userTemplate = "CN={0},CN=Users,DC=ad,DC=test,DC=elasticsearch,DC=com";
Settings settings = LdapTest.buildLdapSettings(AD_LDAP_URL, userTemplate, groupSearchBase, true);
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
String user = "Bruce Banner";
try (LdapConnection ldap = connectionFactory.open(user, SecuredStringTests.build(PASSWORD))) {
fail("Test active directory certificate does not have proper hostname/ip address for hostname verification");
}
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName) {
return buildAdSettings(ldapUrl, adDomainName, true);
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName, boolean hostnameVerification) {
return ImmutableSettings.builder()
.put(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectoryConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification)
.build();
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN) {
return buildAdSettings(ldapUrl, adDomainName, userSearchDN, true);
}
public static Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, boolean hostnameVerification) {
return ImmutableSettings.builder()
.putArray(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
.put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
.put(ActiveDirectoryConnectionFactory.AD_USER_SEARCH_BASEDN_SETTING, userSearchDN)
.put(ActiveDirectoryConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification)
.build();
}
}

View File

@ -10,16 +10,17 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.shield.authc.support.SecuredStringTests;
import org.elasticsearch.shield.authc.support.ldap.ConnectionFactory;
import org.elasticsearch.shield.authc.support.ldap.AbstractLdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.HostnameVerifyingLdapSslSocketFactory;
import org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory;
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;
import org.junit.*;
import java.io.File;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
@ -32,16 +33,18 @@ public class OpenLdapTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename)
Path keystore = Paths.get(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).toAbsolutePath();
SSLService sslService = new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", keystore)
.put("shield.ssl.keystore.password", "changeit")
.build()));
.build());
AbstractLdapSslSocketFactory.init(sslService);
}
@AfterClass
public static void clearTrustStore() {
LdapSslSocketFactory.clear();
HostnameVerifyingLdapSslSocketFactory.clear();
}
@Test
@ -51,7 +54,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true));
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true, false));
String[] users = new String[] { "blackwidow", "cap", "hawkeye", "hulk", "ironman", "thor" };
for (String user : users) {
@ -67,6 +70,7 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
Settings settings = ImmutableSettings.builder()
.put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_TCP_READ_SETTING, "1ms") //1 millisecond
.build();
@ -86,16 +90,32 @@ public class OpenLdapTests extends ElasticsearchTestCase {
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
Settings settings = ImmutableSettings.builder()
.put(LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true))
.put(ConnectionFactory.HOSTNAME_VERIFICATION_SETTING, false)
.put(ConnectionFactory.TIMEOUT_LDAP_SETTING, "1ms") //1 millisecond
.build();
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(settings);
try (LdapConnection ldap = connectionFactory.open("thor", SecuredStringTests.build(PASSWORD))){
try (LdapConnection ldap = connectionFactory.open("thor", SecuredStringTests.build(PASSWORD))) {
ldap.groups();
fail("The server should timeout the group request");
} catch (LdapException e) {
assertThat(e.getCause().getMessage(), containsString("error code 32")); //openldap response for timeout
}
}
@Test(expected = LdapException.class)
public void testStandardLdapConnectionHostnameVerification() {
//openldap does not use cn as naming attributes by default
String groupSearchBase = "ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
String userTemplate = "uid={0},ou=people,dc=oldap,dc=test,dc=elasticsearch,dc=com";
LdapConnectionFactory connectionFactory = new LdapConnectionFactory(
LdapConnectionTests.buildLdapSettings(OPEN_LDAP_URL, userTemplate, groupSearchBase, true));
String user = "blackwidow";
try (LdapConnection ldap = connectionFactory.open(user, SecuredStringTests.build(PASSWORD))) {
fail("OpenLDAP certificate does not contain the correct hostname/ip so hostname verification should fail on open");
}
}
}

View File

@ -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.authc.support.ldap;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
import org.elasticsearch.shield.authc.support.SecuredString;
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;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import static org.hamcrest.Matchers.equalTo;
public class ConnectionFactoryTests extends ElasticsearchTestCase {
@Test
public void testConfigure_1ldaps() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo(LdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldaps() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo(LdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldap() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test
public void testConfigure_1ldapsWithHostnameVerification() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo(HostnameVerifyingLdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldapsWithHostnameVerification() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo(HostnameVerifyingLdapSslSocketFactory.class.getName()));
}
@Test
public void testConfigure_2ldapWithHostnameVerification() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithHostnameVerification().configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(ConnectionFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldaps_1ldap() {
String[] urls = new String[] { "LDAPS://primary.example.com:636", "ldap://secondary.example.com:392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldap_1ldaps() {
String[] urls = new String[] { "ldap://primary.example.com:392", "ldaps://secondary.example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
createConnectionFactoryWithoutHostnameVerification().configureJndiSSL(urls, builder);
}
private ConnectionFactory createConnectionFactoryWithoutHostnameVerification() {
return new ConnectionFactory(ImmutableSettings.builder().put("hostname_verification", false).build()) {
@Override
public AbstractLdapConnection open(String user, SecuredString password) {
return null;
}
};
}
private ConnectionFactory createConnectionFactoryWithHostnameVerification() {
return new ConnectionFactory(ImmutableSettings.EMPTY) {
@Override
public AbstractLdapConnection open(String user, SecuredString password) {
return null;
}
};
}
}

View File

@ -1,87 +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.authc.support.ldap;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.shield.ShieldSettingsException;
import org.elasticsearch.shield.authc.ldap.LdapConnectionTests;
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;
import java.io.File;
import java.io.Serializable;
import java.net.URISyntaxException;
import static org.hamcrest.Matchers.equalTo;
public class LdapSslSocketFactoryTests extends ElasticsearchTestCase {
@BeforeClass
public static void setTrustStore() throws URISyntaxException {
File filename = new File(LdapConnectionTests.class.getResource("../support/ldap/ldaptrust.jks").toURI()).getAbsoluteFile();
LdapSslSocketFactory.init(new SSLService(ImmutableSettings.builder()
.put("shield.ssl.keystore.path", filename)
.put("shield.ssl.keystore.password", "changeit")
.build()));
}
@AfterClass
public static void clearTrustStore() {
LdapSslSocketFactory.clear();
}
@Test
public void testConfigure_1ldaps() {
String[] urls = new String[] { "ldaps://example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat((String) settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET),
equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2ldaps() {
String[] urls = new String[] { "ldaps://primary.example.com:636", "LDAPS://secondary.example.com:10636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), Matchers.<Serializable>equalTo("org.elasticsearch.shield.authc.support.ldap.LdapSslSocketFactory"));
}
@Test
public void testConfigure_2ldap() {
String[] urls = new String[] { "ldap://primary.example.com:392", "LDAP://secondary.example.com:10392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
ImmutableMap<String, Serializable> settings = builder.build();
assertThat(settings.get(LdapSslSocketFactory.JAVA_NAMING_LDAP_FACTORY_SOCKET), equalTo(null));
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldaps_1ldap() {
String[] urls = new String[] { "LDAPS://primary.example.com:636", "ldap://secondary.example.com:392" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
@Test(expected = ShieldSettingsException.class)
public void testConfigure_1ldap_1ldaps() {
String[] urls = new String[] { "ldap://primary.example.com:392", "ldaps://secondary.example.com:636" };
ImmutableMap.Builder<String, Serializable> builder = ImmutableMap.<String, Serializable>builder();
LdapSslSocketFactory.configureJndiSSL(urls, builder);
}
}

View File

@ -46,12 +46,21 @@ public abstract class LdapTest extends ElasticsearchTestCase {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch );
}
public static Settings buildLdapSettings(String ldapUrl, String userTemplate, String groupSearchBase, boolean isSubTreeSearch, boolean hostnameVerification) {
return buildLdapSettings( new String[]{ldapUrl}, new String[]{userTemplate}, groupSearchBase, isSubTreeSearch, hostnameVerification );
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch) {
return buildLdapSettings(ldapUrl, userTemplate, groupSearchBase, isSubTreeSearch, true);
}
public static Settings buildLdapSettings(String[] ldapUrl, String[] userTemplate, String groupSearchBase, boolean isSubTreeSearch, boolean hostnameVerification) {
return ImmutableSettings.builder()
.putArray(LdapConnectionFactory.URLS_SETTING, ldapUrl)
.putArray(LdapConnectionFactory.USER_DN_TEMPLATES_SETTING, userTemplate)
.put(LdapConnectionFactory.GROUP_SEARCH_BASEDN_SETTING, groupSearchBase)
.put(LdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch).build();
.put(LdapConnectionFactory.GROUP_SEARCH_SUBTREE_SETTING, isSubTreeSearch)
.put(LdapConnectionFactory.HOSTNAME_VERIFICATION_SETTING, hostnameVerification).build();
}
protected Settings buildNonCachingSettings() {

View File

@ -89,6 +89,7 @@ public class ServerTransportFilterIntegrationTest extends ShieldIntegrationTest
.put(ShieldSettingsSource.getSSLSettingsForStore("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode.jks", "testnode"))
.put("node.mode", "network")
.put("node.name", "my-test-node")
.put("network.host", "localhost")
.put("cluster.name", internalCluster().getClusterName())
.put("discovery.zen.ping.multicast.enabled", false)
.put("discovery.zen.ping.unicast.hosts", unicastHost)

View File

@ -0,0 +1,95 @@
/*
* 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.netty;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.NoNodeAvailableException;
import org.elasticsearch.client.transport.TransportClient;
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.test.ElasticsearchIntegrationTest.ClusterScope;
import org.elasticsearch.test.ElasticsearchIntegrationTest.Scope;
import org.elasticsearch.test.ShieldIntegrationTest;
import org.elasticsearch.transport.Transport;
import org.junit.Test;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.elasticsearch.common.settings.ImmutableSettings.settingsBuilder;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
@ClusterScope(scope = Scope.SUITE)
public class SslHostnameVerificationIntegrationTests extends ShieldIntegrationTest {
static Path keystore;
@Override
protected boolean sslTransportEnabled() {
return true;
}
@Override
protected Settings nodeSettings(int nodeOrdinal) {
ImmutableSettings.Builder settingsBuilder = settingsBuilder().put(super.nodeSettings(nodeOrdinal));
try {
/*
* This keystore uses a cert without any subject alternative names and a CN of "Elasticsearch Test Node No SAN"
* that will not resolve to a DNS name and will always cause hostname verification failures
*/
keystore = Paths.get(getClass().getResource("/org/elasticsearch/shield/transport/ssl/certs/simple/testnode-no-subjaltname.jks").toURI());
assertThat(Files.exists(keystore), is(true));
} catch (Exception e) {
throw new RuntimeException(e);
}
return settingsBuilder.put("shield.ssl.keystore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.keystore.password", "testnode-no-subjaltname")
.put("shield.ssl.truststore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.truststore.password", "testnode-no-subjaltname")
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, false) // disable hostname verification as this test uses non-localhost addresses
.build();
}
@Override
protected Settings transportClientSettings() {
return ImmutableSettings.builder().put(super.transportClientSettings())
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, false)
.put("shield.ssl.truststore.path", keystore.toAbsolutePath()) // settings for client truststore
.put("shield.ssl.truststore.password", "testnode-no-subjaltname")
.build();
}
@Test(expected = NoNodeAvailableException.class)
public void testThatHostnameMismatchDeniesTransportClientConnection() throws Exception {
Transport transport = internalCluster().getDataNodeInstance(Transport.class);
TransportAddress transportAddress = transport.boundAddress().publishAddress();
assertThat(transportAddress, instanceOf(InetSocketTransportAddress.class));
InetSocketAddress inetSocketAddress = ((InetSocketTransportAddress) transportAddress).address();
Settings settings = ImmutableSettings.builder().put(transportClientSettings())
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, true)
.build();
try (TransportClient client = new TransportClient(settings, false)) {
client.addTransportAddress(new InetSocketTransportAddress(inetSocketAddress.getHostName(), inetSocketAddress.getPort()));
client.admin().cluster().prepareHealth().get();
fail("Expected a NoNodeAvailableException due to hostname verification failures");
}
}
@Test
public void testTransportClientConnectionIgnoringHostnameVerification() throws Exception {
Client client = internalCluster().transportClient();
assertGreenClusterState(client);
}
}

View File

@ -21,6 +21,7 @@ import org.elasticsearch.shield.authc.esusers.ESUsersRealm;
import org.elasticsearch.shield.authc.support.SecuredString;
import org.elasticsearch.shield.authc.support.UsernamePasswordToken;
import org.elasticsearch.shield.signature.InternalSignatureService;
import org.elasticsearch.shield.transport.netty.NettySecuredTransport;
import org.elasticsearch.test.discovery.ClusterDiscoveryConfiguration;
import java.io.File;
@ -239,7 +240,8 @@ public class ShieldSettingsSource extends ClusterDiscoveryConfiguration.UnicastZ
if (sslTransportEnabled) {
builder.put("shield.ssl.keystore.path", store.getPath())
.put("shield.ssl.keystore.password", password);
.put("shield.ssl.keystore.password", password)
.put(NettySecuredTransport.HOSTNAME_VERIFICATION_SETTING, RandomizedTest.randomBoolean());
}
if (sslTransportEnabled && RandomizedTest.randomBoolean()) {

View File

@ -0,0 +1,69 @@
= Keystore Details
This document details the steps used to create the certificate and keystore files in this directory.
== Instructions on generating self-signed certificates
The certificates in this directory have been generated using the following openssl configuration and commands.
OpenSSL Configuration File
[source,shell]
-----------------------------------------------------------------------------------------------------------
[ req ]
default_bits = 2048 # Size of keys
default_keyfile = key.pem # name of generated keys
default_md = sha256 # message digest algorithm
string_mask = nombstr # permitted characters
distinguished_name = req_distinguished_name
req_extensions = v3_req
[ req_distinguished_name ]
0.organizationName = Organization Name (company)
organizationalUnitName = Organizational Unit Name (department, division)
emailAddress = Email Address
emailAddress_max = 40
localityName = Locality Name (city, district)
stateOrProvinceName = State or Province Name (full name)
countryName = Country Name (2 letter code)
countryName_min = 2
countryName_max = 2
commonName = Common Name (hostname, IP, or your name)
commonName_max = 64
[ v3_req ]
basicConstraints = CA:FALSE
subjectKeyIdentifier = hash
#subjectAltName = @alt_names
[ alt_names ]
DNS.1 = localhost
IP.1 = 127.0.0.1
IP.2 = ::2
-----------------------------------------------------------------------------------------------------------
NOTE: The `alt_names` section provides the Subject Alternative Names for each certificate. This is necessary for testing
with hostname verification enabled.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl req -new -x509 -extensions v3_req -out <NAME>.cert -keyout <NAME>.pem -days 1460 -config config.cnf
-----------------------------------------------------------------------------------------------------------
When prompted the password is always set to the value of <NAME>.
Because we intend to import these certificates into a Java Keystore file, they certificate and private key must be combined
in a PKCS12 certificate.
[source,shell]
-----------------------------------------------------------------------------------------------------------
openssl pkcs12 -export -name <NAME> -in <NAME>.cert -inkey <NAME>.pem -out <NAME>.p12
-----------------------------------------------------------------------------------------------------------
== Creating the Keystore
We need to create a keystore from the created PKCS12 certificate.
[source,shell]
-----------------------------------------------------------------------------------------------------------
keytool -importkeystore -destkeystore <NAME>.jks -srckeystore <NAME>.p12 -srcstoretype pkcs12 -alias <NAME>
-----------------------------------------------------------------------------------------------------------
The keystore is now created and has the private/public key pair. You can import additional trusted certificates using
`keytool -importcert`. When doing so make sure to specify an alias so that others can recreate the keystore if necessary.

View File

@ -1,18 +1,19 @@
-----BEGIN CERTIFICATE-----
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
hTWLIhU=
-----END CERTIFICATE-----

View File

@ -1,17 +1,21 @@
-----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==
MIIDcTCCAlmgAwIBAgIJANA/+zUU47fOMA0GCSqGSIb3DQEBCwUAMEoxDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAGA1UEAxMZRWxhc3Rp
Y3NlYXJjaCBUZXN0IENsaWVudDAeFw0xNDEyMTYxNzAyNTRaFw0xODEyMTUxNzAy
NTRaMEoxDDAKBgNVBAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEiMCAG
A1UEAxMZRWxhc3RpY3NlYXJjaCBUZXN0IENsaWVudDCCASIwDQYJKoZIhvcNAQEB
BQADggEPADCCAQoCggEBALceMGq3C3QP6uuCE/93MlwDP3NBVwl5eNL3JvygwQoU
YpoABTU51+3JWjHkWxMbinUFEQV67EMa+5Uja8m39MoHC2hlTuECufe17bAFN23I
SE/x8qaBPECvjR1Rf3Dt+rfJ7/n76aObIt7exsR1uxB/O2eRMNGXZSCsQrQy+n/o
T45gFDkY1CeMthsUxj4TkmeHK+wcDx00Te1mfA4J3Ef5ykIxAwDJxEBIaqGAv077
+02RdG1IHGEvJeTGwZ+GFem9U8zF3UVBX/jyqHY82YyBaXrwjlhDz4dmHVqTVQYC
Qn/SkrQyJknggnWCrCoGS31GfKyFbmrUWjhAtVkreRkCAwEAAaNaMFgwCQYDVR0T
BAIwADAdBgNVHQ4EFgQU4QdRaaIGD6uyQk/WiwEtExBI7hgwLAYDVR0RBCUwI4IJ
bG9jYWxob3N0hwR/AAABhxAAAAAAAAAAAAAAAAAAAAACMA0GCSqGSIb3DQEBCwUA
A4IBAQAexhtJBZ2jQrJ1M7HRFCFRVP6oi/5D139/0t8qG9KL9oQXlY//gQweSPk9
gCPywfQjBfY6Fck2iliCw6YR3K/Bax9lOX/rKJ/jWrcUUBKzP9ap8ijKa/1EJchp
VVf7M2g7V00pSR63fl+EnMzv8H/uTK4BMWiE9sl01oK/kyVDR/wd/GktnQpZYF5B
5FvuMi2ocM+hSpbejLVi4yW6/wN6rcjofon/u3ngzicwou59r99IavlW/qk5DHbI
HD2pK4tOqQX06pzdc8MHKQ16JxSt3dk/aljmvDUf93e/KNI9RSoZpydBqXlpaACi
52lEcHwQJVoe6L5VdHc6090U09K7
-----END CERTIFICATE-----

View File

@ -1,18 +1,19 @@
-----BEGIN CERTIFICATE-----
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
dF+PXJw=
-----END CERTIFICATE-----

View File

@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIJALL7dwEsWamvMA0GCSqGSIb3DQEBCwUAME8xDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEnMCUGA1UEAxMeRWxhc3Rp
Y3NlYXJjaCBUZXN0IE5vZGUgTm8gU0FOMB4XDTE0MTIxNjE5NTcyNloXDTE4MTIx
NTE5NTcyNlowTzEMMAoGA1UEChMDb3JnMRYwFAYDVQQLEw1lbGFzdGljc2VhcmNo
MScwJQYDVQQDEx5FbGFzdGljc2VhcmNoIFRlc3QgTm9kZSBObyBTQU4wggEiMA0G
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCkIGS7A/V6TesR34ajMyNYL3tB1OjW
Raq4KtF8FfW1H6nHGrWa/qXjZWPirczy1k2n6ZL7YOCcv/YeY8xAqC9mGQxvEuqo
EaqXq2cjRdAs/7zqzRkdPPi3Jw/p/RHrDfOAzOsMnBGc0G2Hrsj//aP44vp85pek
fM3t2kNAYZWYCzXUqWAIUoxBDK4DcQdsN8H4KTMIwQEEiRtcKnL/b8QGKsyGLfLq
36ZABHZ4kY2SmcP3bWxZtbFN4hamdwoAtYe+lS0/ee8/fOTLyZ3Ey+X6EEmGO1lk
WR4XLli15k1L2HBzWGG7zwxVEC5r2h3Sx1njYh/Jq3khIdSvDbiMmM+VAgMBAAGj
LDAqMAkGA1UdEwQCMAAwHQYDVR0OBBYEFGm8wrYF9mJweJ1vloDw19e0PUuIMA0G
CSqGSIb3DQEBCwUAA4IBAQBbEZ73weDphNIcmvN25v6NIfjBebqgm0/2grDFwmZe
Z1DibzRoVfoQ7WeUqbPS7SHUQ+KzIN1GdfHXhW9r6mmLbtzPv90Q/8zBcNv5HNZZ
YK+T2r9hoAWEY6nB1fiOJ4udkFMYfAi6LiSxave4IPWp/WIqd0IWtPtkPl+MmG41
TfRom8TnO+o+VsjgDkY5Q1JDsNQKy1BrtxzIZyz7d1zYKTQ+HXZ4yeYJoVoc3k4y
6w9eX2zAUZ6Z3d4an6CLr6Hew9Dj2VX1vqCj1a5/VvHZVyVxyh4hg8sHYm7tZOJX
wN3B5GcKwbbFjaMVBLaMlP62OdGg7tCh61evWm+l06S0
-----END CERTIFICATE-----

View File

@ -1,17 +1,21 @@
-----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
MIIDbTCCAlWgAwIBAgIJAJ+K5mGS3n/AMA0GCSqGSIb3DQEBCwUAMEgxDDAKBgNV
BAoTA29yZzEWMBQGA1UECxMNZWxhc3RpY3NlYXJjaDEgMB4GA1UEAxMXRWxhc3Rp
Y3NlYXJjaCBUZXN0IE5vZGUwHhcNMTQxMjE2MTcwNDQ1WhcNMTgxMjE1MTcwNDQ1
WjBIMQwwCgYDVQQKEwNvcmcxFjAUBgNVBAsTDWVsYXN0aWNzZWFyY2gxIDAeBgNV
BAMTF0VsYXN0aWNzZWFyY2ggVGVzdCBOb2RlMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAzhpW7iwkm+Og+HP7U00nbmh0Hy9Z2Ldp5i8tJSlSQwTxCCvO
rse6jwJQN98Dk1ApaSzimZrlKOotFyPV1L3fnOzJbTp1Yq/VsYP4zJkjWtID0qUf
8Rg8bLhjKAG+ZlLuai5XZqnLkdmqvQeR61VhpXWFm0Om153tWmAiHL18ywY71gXN
EnkeFo9OW4fDqkz6h7NJziYvU6URSKErZDEixk5GIPv9K9hiIfi0KQM6xaHp0d2w
VCyFVC0OUdugz6untURzJVx4U3X1bQcv/o2BoUotWh/5h8o5eeiiv2OGZ1XlO+33
1tweYI4wFjDwnAyHHRr/rk2ZIBiBYGaSzHnuhQIDAQABo1owWDAJBgNVHRMEAjAA
MB0GA1UdDgQWBBTwGg2LF8+mzsvBBWxJKv6VXv3dMTAsBgNVHREEJTAjgglsb2Nh
bGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAIwDQYJKoZIhvcNAQELBQADggEB
ABP4ufLToJhcUselVxV9LPD5VGPEHGLdIFqsUEix7DMsiNpR76X6a8qNQbZpdbd6
+qPKqoaMgC7znX7qZtCqRbIXTWbudZPxFkcHdiWx3SiALMQYabeUGetClX3sCndU
SUoV8f34i8dJxfNcqhLcsh4zpgxtmwsvs5OLMTBvm0Xo2zUFUjlmrt41pBrWEuq9
nkObc/cr6Syiz3sy4pYVJO1/YwHaZgE/URqjVlari70DR3ES4YnIUnLQajKx2Q0/
gXVgzjbe68KPOUGCz6GYiWq+d4tcWdHzLv1GsaqQ1MD9P21ArfrX4DpzgPDrO6MP
9Ppq5DQGa2q4mz3kipd5RIs=
-----END CERTIFICATE-----