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:
parent
c052a8ca95
commit
cc9568d1bb
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 ImmutableSettings.builder()
|
||||
.put(ActiveDirectoryConnectionFactory.URLS_SETTING, ldapUrl)
|
||||
.put(ActiveDirectoryConnectionFactory.AD_DOMAIN_NAME_SETTING, adDomainName)
|
||||
.build();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
|
@ -1,18 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
hTWLIhU=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
Binary file not shown.
|
@ -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-----
|
||||
|
|
Binary file not shown.
|
@ -1,18 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
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
|
||||
dF+PXJw=
|
||||
-----END CERTIFICATE-----
|
||||
|
|
Binary file not shown.
|
@ -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-----
|
Binary file not shown.
|
@ -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-----
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue