security: move reloading of ssl configuration to its own class
This commit moves the reloading and monitoring of files from the trust/key configuration classes into a separate class that will reload for the whole SSLConfiguration object. SSLContexts are loaded lazily by most of security, so a listener interface was added to notify the reloader that there may be other paths to monitor. Original commit: elastic/x-pack-elasticsearch@1633cc14a7
This commit is contained in:
parent
62353ff8bc
commit
c82f1be386
|
@ -111,6 +111,7 @@ import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
|
||||||
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
|
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
|
||||||
import org.elasticsearch.xpack.security.ssl.ClientSSLService;
|
import org.elasticsearch.xpack.security.ssl.ClientSSLService;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration;
|
||||||
|
import org.elasticsearch.xpack.security.ssl.SSLConfigurationReloader;
|
||||||
import org.elasticsearch.xpack.security.ssl.ServerSSLService;
|
import org.elasticsearch.xpack.security.ssl.ServerSSLService;
|
||||||
import org.elasticsearch.xpack.security.support.OptionalSettings;
|
import org.elasticsearch.xpack.security.support.OptionalSettings;
|
||||||
import org.elasticsearch.xpack.security.transport.SecurityClientTransportService;
|
import org.elasticsearch.xpack.security.transport.SecurityClientTransportService;
|
||||||
|
@ -185,8 +186,7 @@ public class Security implements ActionPlugin, IngestPlugin {
|
||||||
modules.add(b -> {
|
modules.add(b -> {
|
||||||
// for transport client we still must inject these ssl classes with guice
|
// for transport client we still must inject these ssl classes with guice
|
||||||
b.bind(ServerSSLService.class).toProvider(Providers.<ServerSSLService>of(null));
|
b.bind(ServerSSLService.class).toProvider(Providers.<ServerSSLService>of(null));
|
||||||
b.bind(ClientSSLService.class).toInstance(
|
b.bind(ClientSSLService.class).toInstance(new ClientSSLService(settings, null, new SSLConfiguration.Global(settings)));
|
||||||
new ClientSSLService(settings, null, new SSLConfiguration.Global(settings), null));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return modules;
|
return modules;
|
||||||
|
@ -232,8 +232,12 @@ public class Security implements ActionPlugin, IngestPlugin {
|
||||||
components.add(securityContext);
|
components.add(securityContext);
|
||||||
|
|
||||||
final SSLConfiguration.Global globalSslConfig = new SSLConfiguration.Global(settings);
|
final SSLConfiguration.Global globalSslConfig = new SSLConfiguration.Global(settings);
|
||||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, globalSslConfig, resourceWatcherService);
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, globalSslConfig);
|
||||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, globalSslConfig, resourceWatcherService);
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, globalSslConfig);
|
||||||
|
// just create the reloader as it will register itself as a listener to the ssl service and nothing else depends on it
|
||||||
|
// IMPORTANT: if the reloader construction is moved to later, then it needs to be updated to ensure any SSLContexts that have been
|
||||||
|
// loaded by the services are also monitored by the reloader!
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
components.add(clientSSLService);
|
components.add(clientSSLService);
|
||||||
components.add(serverSSLService);
|
components.add(serverSSLService);
|
||||||
|
|
||||||
|
|
|
@ -6,44 +6,31 @@
|
||||||
package org.elasticsearch.xpack.security.authc.esnative;
|
package org.elasticsearch.xpack.security.authc.esnative;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Joiner;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
import joptsimple.OptionParser;
|
import joptsimple.OptionParser;
|
||||||
import joptsimple.OptionSet;
|
import joptsimple.OptionSet;
|
||||||
import joptsimple.OptionSpec;
|
import joptsimple.OptionSpec;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.ExceptionsHelper;
|
|
||||||
import org.elasticsearch.cli.MultiCommand;
|
import org.elasticsearch.cli.MultiCommand;
|
||||||
import org.elasticsearch.cli.SettingCommand;
|
import org.elasticsearch.cli.SettingCommand;
|
||||||
import org.elasticsearch.cli.Terminal;
|
import org.elasticsearch.cli.Terminal;
|
||||||
import org.elasticsearch.client.transport.TransportClient;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
|
||||||
import org.elasticsearch.common.logging.DeprecationLogger;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.node.internal.InternalSettingsPreparer;
|
|
||||||
import org.elasticsearch.xpack.security.action.role.PutRoleRequest;
|
|
||||||
import org.elasticsearch.xpack.security.action.user.PutUserRequest;
|
|
||||||
import org.elasticsearch.xpack.security.authc.Realms;
|
import org.elasticsearch.xpack.security.authc.Realms;
|
||||||
import org.elasticsearch.xpack.security.authc.file.FileUserPasswdStore;
|
import org.elasticsearch.xpack.security.authc.file.FileUserPasswdStore;
|
||||||
import org.elasticsearch.xpack.security.authc.file.FileUserRolesStore;
|
import org.elasticsearch.xpack.security.authc.file.FileUserRolesStore;
|
||||||
import org.elasticsearch.xpack.security.authc.support.Hasher;
|
|
||||||
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
import org.elasticsearch.xpack.security.authc.support.SecuredString;
|
||||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
import org.elasticsearch.xpack.security.authz.RoleDescriptor;
|
||||||
import org.elasticsearch.xpack.security.authz.permission.Permission;
|
|
||||||
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
|
import org.elasticsearch.xpack.security.authz.store.FileRolesStore;
|
||||||
import org.elasticsearch.xpack.security.ssl.ClientSSLService;
|
import org.elasticsearch.xpack.security.ssl.ClientSSLService;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration;
|
||||||
import org.elasticsearch.xpack.security.support.NoOpLogger;
|
|
||||||
import org.elasticsearch.xpack.security.support.Validation;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -52,16 +39,13 @@ import java.io.OutputStream;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.elasticsearch.xpack.security.Security.setting;
|
import static org.elasticsearch.xpack.security.Security.setting;
|
||||||
|
@ -151,7 +135,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand {
|
||||||
if ("https".equalsIgnoreCase(uri.getScheme())) {
|
if ("https".equalsIgnoreCase(uri.getScheme())) {
|
||||||
Settings sslSettings = settings.getByPrefix(setting("http.ssl."));
|
Settings sslSettings = settings.getByPrefix(setting("http.ssl."));
|
||||||
SSLConfiguration.Global globalConfig = new SSLConfiguration.Global(settings);
|
SSLConfiguration.Global globalConfig = new SSLConfiguration.Global(settings);
|
||||||
final ClientSSLService sslService = new ClientSSLService(sslSettings, env, globalConfig, null);
|
final ClientSSLService sslService = new ClientSSLService(sslSettings, env, globalConfig);
|
||||||
final HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
|
final HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
|
||||||
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
AccessController.doPrivileged(new PrivilegedAction<Void>() {
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -13,13 +13,10 @@ import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||||
import org.elasticsearch.xpack.security.ssl.TrustConfig.Reloadable.Listener;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.SSLSessionContext;
|
|
||||||
import javax.net.ssl.SSLSocket;
|
import javax.net.ssl.SSLSocket;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
|
@ -28,9 +25,12 @@ import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Enumeration;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -39,19 +39,18 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractSSLService extends AbstractComponent {
|
public abstract class AbstractSSLService extends AbstractComponent {
|
||||||
|
|
||||||
private final ConcurrentHashMap<SSLConfiguration, SSLContext> sslContexts = new ConcurrentHashMap<>();
|
|
||||||
private final SSLContextCacheLoader cacheLoader = new SSLContextCacheLoader();
|
private final SSLContextCacheLoader cacheLoader = new SSLContextCacheLoader();
|
||||||
|
private final ConcurrentHashMap<SSLConfiguration, SSLContext> sslContexts = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
protected final SSLConfiguration globalSSLConfiguration;
|
protected final SSLConfiguration globalSSLConfiguration;
|
||||||
protected final Environment env;
|
protected final Environment env;
|
||||||
protected final ResourceWatcherService resourceWatcherService;
|
|
||||||
|
|
||||||
public AbstractSSLService(Settings settings, Environment environment, Global globalSSLConfiguration,
|
private Listener listener = Listener.NOOP;
|
||||||
ResourceWatcherService resourceWatcherService) {
|
|
||||||
|
AbstractSSLService(Settings settings, Environment environment, Global globalSSLConfiguration) {
|
||||||
super(settings);
|
super(settings);
|
||||||
this.env = environment;
|
this.env = environment;
|
||||||
this.globalSSLConfiguration = globalSSLConfiguration;
|
this.globalSSLConfiguration = globalSSLConfiguration;
|
||||||
this.resourceWatcherService = resourceWatcherService;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] supportedProtocols() {
|
public String[] supportedProtocols() {
|
||||||
|
@ -167,6 +166,27 @@ public abstract class AbstractSSLService extends AbstractComponent {
|
||||||
return requestedCiphersList.toArray(new String[requestedCiphersList.size()]);
|
return requestedCiphersList.toArray(new String[requestedCiphersList.size()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the listener to the value provided. Must not be {@code null}
|
||||||
|
*/
|
||||||
|
void setListener(Listener listener) {
|
||||||
|
this.listener = Objects.requireNonNull(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the existing {@link SSLContext} for the configuration or {@code null}
|
||||||
|
*/
|
||||||
|
SSLContext getSSLContext(SSLConfiguration sslConfiguration) {
|
||||||
|
return sslContexts.get(sslConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accessor to the loaded ssl configuration objects at the current point in time. This is useful for testing
|
||||||
|
*/
|
||||||
|
Collection<SSLConfiguration> getLoadedSSLConfigurations() {
|
||||||
|
return Collections.unmodifiableSet(new HashSet<>(sslContexts.keySet()));
|
||||||
|
}
|
||||||
|
|
||||||
private class SSLContextCacheLoader {
|
private class SSLContextCacheLoader {
|
||||||
|
|
||||||
public SSLContext load(SSLConfiguration sslConfiguration) {
|
public SSLContext load(SSLConfiguration sslConfiguration) {
|
||||||
|
@ -175,15 +195,15 @@ public abstract class AbstractSSLService extends AbstractComponent {
|
||||||
logger.debug("using ssl settings [{}]", sslConfiguration);
|
logger.debug("using ssl settings [{}]", sslConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigRefreshListener configRefreshListener = new ConfigRefreshListener(sslConfiguration);
|
TrustManager[] trustManagers = sslConfiguration.trustConfig().trustManagers(env);
|
||||||
TrustManager[] trustManagers = sslConfiguration.trustConfig().trustManagers(env, resourceWatcherService, configRefreshListener);
|
KeyManager[] keyManagers = sslConfiguration.keyConfig().keyManagers(env);
|
||||||
KeyManager[] keyManagers = sslConfiguration.keyConfig().keyManagers(env, resourceWatcherService, configRefreshListener);
|
|
||||||
SSLContext sslContext = createSslContext(keyManagers, trustManagers, sslConfiguration.protocol(),
|
SSLContext sslContext = createSslContext(keyManagers, trustManagers, sslConfiguration.protocol(),
|
||||||
sslConfiguration.sessionCacheSize(), sslConfiguration.sessionCacheTimeout());
|
sslConfiguration.sessionCacheSize(), sslConfiguration.sessionCacheTimeout());
|
||||||
|
|
||||||
// check the supported ciphers and log them here
|
// check the supported ciphers and log them here
|
||||||
supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(),
|
supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(),
|
||||||
sslConfiguration.ciphers().toArray(Strings.EMPTY_ARRAY), true);
|
sslConfiguration.ciphers().toArray(Strings.EMPTY_ARRAY), true);
|
||||||
|
listener.onSSLContextLoaded(sslConfiguration);
|
||||||
return sslContext;
|
return sslContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,37 +222,6 @@ public abstract class AbstractSSLService extends AbstractComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ConfigRefreshListener implements Listener {
|
|
||||||
|
|
||||||
private final SSLConfiguration sslConfiguration;
|
|
||||||
|
|
||||||
ConfigRefreshListener(SSLConfiguration sslConfiguration) {
|
|
||||||
this.sslConfiguration = sslConfiguration;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onReload() {
|
|
||||||
SSLContext context = sslContexts.get(sslConfiguration);
|
|
||||||
if (context != null) {
|
|
||||||
invalidateSessions(context.getClientSessionContext());
|
|
||||||
invalidateSessions(context.getServerSessionContext());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void invalidateSessions(SSLSessionContext sslSessionContext) {
|
|
||||||
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
|
|
||||||
while (sessionIds.hasMoreElements()) {
|
|
||||||
byte[] sessionId = sessionIds.nextElement();
|
|
||||||
sslSessionContext.getSession(sessionId).invalidate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
logger.error("failed to load updated ssl context for [{}]", e, sslConfiguration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This socket factory set the protocols and ciphers on each SSLSocket after it is created
|
* This socket factory set the protocols and ciphers on each SSLSocket after it is created
|
||||||
*/
|
*/
|
||||||
|
@ -305,4 +294,14 @@ public abstract class AbstractSSLService extends AbstractComponent {
|
||||||
socket.setEnabledCipherSuites(ciphers);
|
socket.setEnabledCipherSuites(ciphers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
/**
|
||||||
|
* Called after a new SSLContext has been created
|
||||||
|
* @param sslConfiguration the configuration used to create the SSLContext
|
||||||
|
*/
|
||||||
|
void onSSLContextLoaded(SSLConfiguration sslConfiguration);
|
||||||
|
|
||||||
|
Listener NOOP = (s) -> {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ import org.bouncycastle.operator.ContentSigner;
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
|
||||||
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
|
||||||
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
@ -48,6 +49,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.security.auth.x500.X500Principal;
|
import javax.security.auth.x500.X500Principal;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@ -84,7 +86,7 @@ class CertUtils {
|
||||||
return PathUtils.get(Strings.cleanPath(path));
|
return PathUtils.get(Strings.cleanPath(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509ExtendedKeyManager[] keyManagers(Certificate[] certificateChain, PrivateKey privateKey, char[] password) throws Exception {
|
static X509ExtendedKeyManager keyManagers(Certificate[] certificateChain, PrivateKey privateKey, char[] password) throws Exception {
|
||||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||||
keyStore.load(null, null);
|
keyStore.load(null, null);
|
||||||
// password must be non-null for keystore...
|
// password must be non-null for keystore...
|
||||||
|
@ -92,19 +94,19 @@ class CertUtils {
|
||||||
return keyManagers(keyStore, password, KeyManagerFactory.getDefaultAlgorithm());
|
return keyManagers(keyStore, password, KeyManagerFactory.getDefaultAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509ExtendedKeyManager[] keyManagers(KeyStore keyStore, char[] password, String algorithm) throws Exception {
|
static X509ExtendedKeyManager keyManagers(KeyStore keyStore, char[] password, String algorithm) throws Exception {
|
||||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
|
KeyManagerFactory kmf = KeyManagerFactory.getInstance(algorithm);
|
||||||
kmf.init(keyStore, password);
|
kmf.init(keyStore, password);
|
||||||
KeyManager[] keyManagers = kmf.getKeyManagers();
|
KeyManager[] keyManagers = kmf.getKeyManagers();
|
||||||
for (KeyManager keyManager : keyManagers) {
|
for (KeyManager keyManager : keyManagers) {
|
||||||
if (keyManager instanceof X509ExtendedKeyManager) {
|
if (keyManager instanceof X509ExtendedKeyManager) {
|
||||||
return new X509ExtendedKeyManager[] { (X509ExtendedKeyManager) keyManager };
|
return (X509ExtendedKeyManager) keyManager;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("failed to find a X509ExtendedKeyManager");
|
throw new IllegalStateException("failed to find a X509ExtendedKeyManager");
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509ExtendedTrustManager[] trustManagers(Certificate[] certificates) throws Exception {
|
static X509ExtendedTrustManager trustManagers(Certificate[] certificates) throws Exception {
|
||||||
KeyStore store = KeyStore.getInstance("jks");
|
KeyStore store = KeyStore.getInstance("jks");
|
||||||
store.load(null, null);
|
store.load(null, null);
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
|
@ -115,13 +117,26 @@ class CertUtils {
|
||||||
return trustManagers(store, TrustManagerFactory.getDefaultAlgorithm());
|
return trustManagers(store, TrustManagerFactory.getDefaultAlgorithm());
|
||||||
}
|
}
|
||||||
|
|
||||||
static X509ExtendedTrustManager[] trustManagers(KeyStore keyStore, String algorithm) throws Exception {
|
static X509ExtendedTrustManager trustManagers(String trustStorePath, String trustStorePassword, String trustStoreAlgorithm,
|
||||||
|
Environment env) throws Exception {
|
||||||
|
try (InputStream in = Files.newInputStream(resolvePath(trustStorePath, env))) {
|
||||||
|
// TODO remove reliance on JKS since we can PKCS12 stores...
|
||||||
|
KeyStore trustStore = KeyStore.getInstance("jks");
|
||||||
|
assert trustStorePassword != null;
|
||||||
|
trustStore.load(in, trustStorePassword.toCharArray());
|
||||||
|
return CertUtils.trustManagers(trustStore, trustStoreAlgorithm);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static X509ExtendedTrustManager trustManagers(KeyStore keyStore, String algorithm) throws Exception {
|
||||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(algorithm);
|
||||||
tmf.init(keyStore);
|
tmf.init(keyStore);
|
||||||
TrustManager[] trustManagers = tmf.getTrustManagers();
|
TrustManager[] trustManagers = tmf.getTrustManagers();
|
||||||
for (TrustManager trustManager : trustManagers) {
|
for (TrustManager trustManager : trustManagers) {
|
||||||
if (trustManager instanceof X509ExtendedTrustManager) {
|
if (trustManager instanceof X509ExtendedTrustManager) {
|
||||||
return new X509ExtendedTrustManager[] { (X509ExtendedTrustManager) trustManager };
|
return (X509ExtendedTrustManager) trustManager ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
|
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");
|
||||||
|
|
|
@ -7,14 +7,12 @@ package org.elasticsearch.xpack.security.ssl;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||||
|
|
||||||
public class ClientSSLService extends AbstractSSLService {
|
public class ClientSSLService extends AbstractSSLService {
|
||||||
|
|
||||||
public ClientSSLService(Settings settings, Environment env, Global globalSSLConfiguration,
|
public ClientSSLService(Settings settings, Environment env, Global globalSSLConfiguration) {
|
||||||
ResourceWatcherService resourceWatcherService) {
|
super(settings, env, globalSSLConfiguration);
|
||||||
super(settings, env, globalSSLConfiguration, resourceWatcherService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -5,19 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.ssl;
|
package org.elasticsearch.xpack.security.ssl;
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.xpack.security.ssl.TrustConfig.Reloadable.Listener;
|
|
||||||
import org.elasticsearch.watcher.FileWatcher;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService.Frequency;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
@ -28,18 +21,20 @@ import java.util.List;
|
||||||
|
|
||||||
abstract class KeyConfig extends TrustConfig {
|
abstract class KeyConfig extends TrustConfig {
|
||||||
|
|
||||||
KeyConfig(boolean includeSystem, boolean reloadEnabled) {
|
private X509ExtendedKeyManager[] keyManagers = null;
|
||||||
super(includeSystem, reloadEnabled);
|
|
||||||
|
KeyConfig(boolean includeSystem) {
|
||||||
|
super(includeSystem);
|
||||||
}
|
}
|
||||||
|
|
||||||
static final KeyConfig NONE = new KeyConfig(false, false) {
|
static final KeyConfig NONE = new KeyConfig(false) {
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment) {
|
X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
|
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,39 +53,48 @@ abstract class KeyConfig extends TrustConfig {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final KeyManager[] keyManagers(@Nullable Environment environment, @Nullable ResourceWatcherService resourceWatcherService,
|
final synchronized X509ExtendedKeyManager[] keyManagers(@Nullable Environment environment) {
|
||||||
@Nullable Listener listener) {
|
if (keyManagers == null) {
|
||||||
X509ExtendedKeyManager[] keyManagers = loadKeyManagers(environment);
|
X509ExtendedKeyManager keyManager = loadKeyManager(environment);
|
||||||
if (reloadEnabled && resourceWatcherService != null && listener != null) {
|
setKeyManagers(keyManager);
|
||||||
ReloadableX509KeyManager reloadableX509KeyManager = new ReloadableX509KeyManager(keyManagers[0], environment);
|
|
||||||
List<Path> filesToMonitor = filesToMonitor(environment);
|
|
||||||
if (filesToMonitor.isEmpty() == false) {
|
|
||||||
ChangeListener changeListener = new ChangeListener(filesToMonitor, reloadableX509KeyManager, listener);
|
|
||||||
try {
|
|
||||||
for (Path dir : directoriesToMonitor(filesToMonitor)) {
|
|
||||||
FileWatcher fileWatcher = new FileWatcher(dir);
|
|
||||||
fileWatcher.addListener(changeListener);
|
|
||||||
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
|
|
||||||
}
|
|
||||||
return new X509ExtendedKeyManager[]{reloadableX509KeyManager};
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ElasticsearchException("failed to add file watcher", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return keyManagers;
|
return keyManagers;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment);
|
@Override
|
||||||
|
synchronized void reload(@Nullable Environment environment) {
|
||||||
|
if (trustManagers == null) {
|
||||||
|
// trust managers were never initialized... do it lazily!
|
||||||
|
X509ExtendedKeyManager loadedKeyManager = loadKeyManager(environment);
|
||||||
|
setKeyManagers(loadedKeyManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final class ReloadableX509KeyManager extends X509ExtendedKeyManager implements Reloadable {
|
X509ExtendedTrustManager loadedTrustManager = loadAndMergeIfNecessary(environment);
|
||||||
|
X509ExtendedKeyManager loadedKeyManager = loadKeyManager(environment);
|
||||||
|
setTrustManagers(loadedTrustManager);
|
||||||
|
setKeyManagers(loadedKeyManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
final synchronized void setKeyManagers(X509ExtendedKeyManager loadedKeyManager) {
|
||||||
|
if (loadedKeyManager == null) {
|
||||||
|
this.keyManagers = new X509ExtendedKeyManager[0];
|
||||||
|
} else if (this.keyManagers == null || this.keyManagers.length == 0) {
|
||||||
|
this.keyManagers = new X509ExtendedKeyManager[] { new ReloadableX509KeyManager(loadedKeyManager) };
|
||||||
|
} else {
|
||||||
|
assert this.keyManagers[0] instanceof ReloadableX509KeyManager;
|
||||||
|
((ReloadableX509KeyManager)this.keyManagers[0]).setKeyManager(loadedKeyManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment);
|
||||||
|
|
||||||
|
final class ReloadableX509KeyManager extends X509ExtendedKeyManager {
|
||||||
|
|
||||||
private final Environment environment;
|
|
||||||
private volatile X509ExtendedKeyManager keyManager;
|
private volatile X509ExtendedKeyManager keyManager;
|
||||||
|
|
||||||
ReloadableX509KeyManager(X509ExtendedKeyManager keyManager, @Nullable Environment environment) {
|
ReloadableX509KeyManager(X509ExtendedKeyManager keyManager) {
|
||||||
this.keyManager = keyManager;
|
this.keyManager = keyManager;
|
||||||
this.environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,13 +137,13 @@ abstract class KeyConfig extends TrustConfig {
|
||||||
return keyManager.chooseEngineServerAlias(s, principals, engine);
|
return keyManager.chooseEngineServerAlias(s, principals, engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reload() {
|
|
||||||
X509ExtendedKeyManager[] keyManagers = loadKeyManagers(environment);
|
|
||||||
this.keyManager = keyManagers[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setKeyManager(X509ExtendedKeyManager x509ExtendedKeyManager) {
|
synchronized void setKeyManager(X509ExtendedKeyManager x509ExtendedKeyManager) {
|
||||||
this.keyManager = x509ExtendedKeyManager;
|
this.keyManager = x509ExtendedKeyManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pkg-private accessor for testing
|
||||||
|
X509ExtendedKeyManager getKeyManager() {
|
||||||
|
return keyManager;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,23 +28,27 @@ class PEMKeyConfig extends KeyConfig {
|
||||||
final String keyPassword;
|
final String keyPassword;
|
||||||
final List<String> certPaths;
|
final List<String> certPaths;
|
||||||
|
|
||||||
PEMKeyConfig(boolean includeSystem, boolean reloadEnabled, String keyPath, String keyPassword, List<String> certPaths) {
|
PEMKeyConfig(boolean includeSystem, String keyPath, String keyPassword, List<String> certPaths) {
|
||||||
super(includeSystem, reloadEnabled);
|
super(includeSystem);
|
||||||
this.keyPath = keyPath;
|
this.keyPath = keyPath;
|
||||||
this.keyPassword = keyPassword;
|
this.keyPassword = keyPassword;
|
||||||
this.certPaths = certPaths;
|
this.certPaths = certPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment) {
|
X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment) {
|
||||||
|
// password must be non-null for keystore...
|
||||||
|
char[] password = keyPassword == null ? new char[0] : keyPassword.toCharArray();
|
||||||
try {
|
try {
|
||||||
PrivateKey privateKey = readPrivateKey(CertUtils.resolvePath(keyPath, environment));
|
PrivateKey privateKey = readPrivateKey(CertUtils.resolvePath(keyPath, environment));
|
||||||
Certificate[] certificateChain = CertUtils.readCertificates(certPaths, environment);
|
Certificate[] certificateChain = CertUtils.readCertificates(certPaths, environment);
|
||||||
// password must be non-null for keystore...
|
|
||||||
char[] password = keyPassword == null ? new char[0] : keyPassword.toCharArray();
|
|
||||||
return CertUtils.keyManagers(certificateChain, privateKey, password);
|
return CertUtils.keyManagers(certificateChain, privateKey, password);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
throw new ElasticsearchException("failed to initialize a KeyManagerFactory", e);
|
||||||
|
} finally {
|
||||||
|
if (password != null) {
|
||||||
|
Arrays.fill(password, (char) 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +64,7 @@ class PEMKeyConfig extends KeyConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
|
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
|
||||||
try {
|
try {
|
||||||
Certificate[] certificates = CertUtils.readCertificates(certPaths, environment);
|
Certificate[] certificates = CertUtils.readCertificates(certPaths, environment);
|
||||||
return CertUtils.trustManagers(certificates);
|
return CertUtils.trustManagers(certificates);
|
||||||
|
|
|
@ -20,13 +20,13 @@ class PEMTrustConfig extends TrustConfig {
|
||||||
|
|
||||||
final List<String> caPaths;
|
final List<String> caPaths;
|
||||||
|
|
||||||
PEMTrustConfig(boolean includeSystem, boolean reloadEnabled, List<String> caPaths) {
|
PEMTrustConfig(boolean includeSystem, List<String> caPaths) {
|
||||||
super(includeSystem, reloadEnabled);
|
super(includeSystem);
|
||||||
this.caPaths = caPaths;
|
this.caPaths = caPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
|
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
|
||||||
try {
|
try {
|
||||||
Certificate[] certificates = CertUtils.readCertificates(caPaths, environment);
|
Certificate[] certificates = CertUtils.readCertificates(caPaths, environment);
|
||||||
return CertUtils.trustManagers(certificates);
|
return CertUtils.trustManagers(certificates);
|
||||||
|
|
|
@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.ssl;
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -14,10 +16,12 @@ import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Setting.Property;
|
import org.elasticsearch.common.settings.Setting.Property;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.security.Security.setting;
|
import static org.elasticsearch.xpack.security.Security.setting;
|
||||||
import static org.elasticsearch.xpack.security.support.OptionalSettings.createInt;
|
import static org.elasticsearch.xpack.security.support.OptionalSettings.createInt;
|
||||||
|
@ -43,6 +47,42 @@ public abstract class SSLConfiguration {
|
||||||
|
|
||||||
public abstract List<String> supportedProtocols();
|
public abstract List<String> supportedProtocols();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the list of paths to files that back this configuration
|
||||||
|
*/
|
||||||
|
public List<Path> filesToMonitor(@Nullable Environment environment) {
|
||||||
|
if (keyConfig() == trustConfig()) {
|
||||||
|
return keyConfig().filesToMonitor(environment);
|
||||||
|
}
|
||||||
|
List<Path> paths = new ArrayList<>(keyConfig().filesToMonitor(environment));
|
||||||
|
paths.addAll(trustConfig().filesToMonitor(environment));
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads the portion of this configuration that makes use of the modified file
|
||||||
|
*/
|
||||||
|
public void reload(Path file, @Nullable Environment environment) {
|
||||||
|
if (keyConfig() == trustConfig()) {
|
||||||
|
keyConfig().reload(environment);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Path path : keyConfig().filesToMonitor(environment)) {
|
||||||
|
if (file.equals(path)) {
|
||||||
|
keyConfig().reload(environment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Path path : trustConfig().filesToMonitor(environment)) {
|
||||||
|
if (file.equals(path)) {
|
||||||
|
trustConfig().reload(environment);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -258,14 +298,14 @@ public abstract class SSLConfiguration {
|
||||||
if (certPaths == null) {
|
if (certPaths == null) {
|
||||||
throw new IllegalArgumentException("you must specify the certificates to use with the key");
|
throw new IllegalArgumentException("you must specify the certificates to use with the key");
|
||||||
}
|
}
|
||||||
return new PEMKeyConfig(includeSystem, reloadEnabled, keyPath, keyPassword, certPaths);
|
return new PEMKeyConfig(includeSystem, keyPath, keyPassword, certPaths);
|
||||||
} else {
|
} else {
|
||||||
assert keyStorePath != null;
|
assert keyStorePath != null;
|
||||||
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
||||||
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
|
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
|
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
|
||||||
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
return new StoreKeyConfig(includeSystem, reloadEnabled, keyStorePath, keyStorePassword, keyStoreKeyPassword,
|
return new StoreKeyConfig(includeSystem, keyStorePath, keyStorePassword, keyStoreKeyPassword,
|
||||||
keyStoreAlgorithm, trustStoreAlgorithm);
|
keyStoreAlgorithm, trustStoreAlgorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,19 +314,18 @@ public abstract class SSLConfiguration {
|
||||||
String trustStorePath = TRUSTSTORE_PATH_SETTING.get(settings).orElse(null);
|
String trustStorePath = TRUSTSTORE_PATH_SETTING.get(settings).orElse(null);
|
||||||
List<String> caPaths = getListOrNull(CA_PATHS_SETTING, settings);
|
List<String> caPaths = getListOrNull(CA_PATHS_SETTING, settings);
|
||||||
boolean includeSystem = INCLUDE_JDK_CERTS_SETTING.get(settings);
|
boolean includeSystem = INCLUDE_JDK_CERTS_SETTING.get(settings);
|
||||||
boolean reloadEnabled = RELOAD_ENABLED_SETTING.get(settings);
|
|
||||||
if (trustStorePath != null && caPaths != null) {
|
if (trustStorePath != null && caPaths != null) {
|
||||||
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
|
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
|
||||||
} else if (caPaths != null) {
|
} else if (caPaths != null) {
|
||||||
return new PEMTrustConfig(includeSystem, reloadEnabled, caPaths);
|
return new PEMTrustConfig(includeSystem, caPaths);
|
||||||
} else if (trustStorePath != null) {
|
} else if (trustStorePath != null) {
|
||||||
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
||||||
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
return new StoreTrustConfig(includeSystem, reloadEnabled, trustStorePath, trustStorePassword, trustStoreAlgorithm);
|
return new StoreTrustConfig(includeSystem, trustStorePath, trustStorePassword, trustStoreAlgorithm);
|
||||||
} else if (keyInfo != KeyConfig.NONE) {
|
} else if (keyInfo != KeyConfig.NONE) {
|
||||||
return keyInfo;
|
return keyInfo;
|
||||||
} else {
|
} else {
|
||||||
return new StoreTrustConfig(includeSystem, reloadEnabled, null, null, null);
|
return new StoreTrustConfig(includeSystem, null, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -413,14 +452,14 @@ public abstract class SSLConfiguration {
|
||||||
if (certPaths == null) {
|
if (certPaths == null) {
|
||||||
throw new IllegalArgumentException("you must specify the certificates to use with the key");
|
throw new IllegalArgumentException("you must specify the certificates to use with the key");
|
||||||
}
|
}
|
||||||
return new PEMKeyConfig(includeSystem, reloadEnabled, keyPath, keyPassword, certPaths);
|
return new PEMKeyConfig(includeSystem, keyPath, keyPassword, certPaths);
|
||||||
} else {
|
} else {
|
||||||
assert keyStorePath != null;
|
assert keyStorePath != null;
|
||||||
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
||||||
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
|
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
|
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
|
||||||
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
return new StoreKeyConfig(includeSystem, reloadEnabled, keyStorePath, keyStorePassword, keyStoreKeyPassword,
|
return new StoreKeyConfig(includeSystem, keyStorePath, keyStorePassword, keyStoreKeyPassword,
|
||||||
keyStoreAlgorithm, trustStoreAlgorithm);
|
keyStoreAlgorithm, trustStoreAlgorithm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,11 +470,11 @@ public abstract class SSLConfiguration {
|
||||||
if (trustStorePath != null && caPaths != null) {
|
if (trustStorePath != null && caPaths != null) {
|
||||||
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
|
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
|
||||||
} else if (caPaths != null) {
|
} else if (caPaths != null) {
|
||||||
return new PEMTrustConfig(INCLUDE_JDK_CERTS_SETTING.get(settings), RELOAD_ENABLED_SETTING.get(settings), caPaths);
|
return new PEMTrustConfig(INCLUDE_JDK_CERTS_SETTING.get(settings), caPaths);
|
||||||
} else if (trustStorePath != null) {
|
} else if (trustStorePath != null) {
|
||||||
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
|
||||||
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
|
||||||
return new StoreTrustConfig(INCLUDE_JDK_CERTS_SETTING.get(settings), RELOAD_ENABLED_SETTING.get(settings),
|
return new StoreTrustConfig(INCLUDE_JDK_CERTS_SETTING.get(settings),
|
||||||
trustStorePath, trustStorePassword, trustStoreAlgorithm);
|
trustStorePath, trustStorePassword, trustStoreAlgorithm);
|
||||||
} else if (keyConfig == global.keyConfig()) {
|
} else if (keyConfig == global.keyConfig()) {
|
||||||
return global.trustConfig();
|
return global.trustConfig();
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.security.ssl;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.watcher.FileChangesListener;
|
||||||
|
import org.elasticsearch.watcher.FileWatcher;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService.Frequency;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSessionContext;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that the files backing an {@link SSLConfiguration} are monitored for changes and the underlying key/trust material is reloaded
|
||||||
|
* and the {@link SSLContext} has existing sessions invalidated to force the use of the new key/trust material
|
||||||
|
*/
|
||||||
|
public class SSLConfigurationReloader extends AbstractComponent implements AbstractSSLService.Listener {
|
||||||
|
|
||||||
|
private final ConcurrentHashMap<Path, ChangeListener> pathToChangeListenerMap = new ConcurrentHashMap<>();
|
||||||
|
private final Environment environment;
|
||||||
|
private final ResourceWatcherService resourceWatcherService;
|
||||||
|
private final ServerSSLService serverSSLService;
|
||||||
|
private final ClientSSLService clientSSLService;
|
||||||
|
|
||||||
|
public SSLConfigurationReloader(Settings settings, Environment env, ServerSSLService serverSSLService,
|
||||||
|
ClientSSLService clientSSLService, ResourceWatcherService resourceWatcher) {
|
||||||
|
super(settings);
|
||||||
|
this.environment = env;
|
||||||
|
this.resourceWatcherService = resourceWatcher;
|
||||||
|
this.serverSSLService = serverSSLService;
|
||||||
|
this.clientSSLService = clientSSLService;
|
||||||
|
serverSSLService.setListener(this);
|
||||||
|
clientSSLService.setListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSSLContextLoaded(SSLConfiguration sslConfiguration) {
|
||||||
|
startWatching(Collections.singleton(sslConfiguration));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects all of the directories that need to be monitored for the provided {@link SSLConfiguration} instances and ensures that
|
||||||
|
* they are being watched for changes
|
||||||
|
*/
|
||||||
|
private void startWatching(Collection<SSLConfiguration> sslConfigurations) {
|
||||||
|
for (SSLConfiguration sslConfiguration : sslConfigurations) {
|
||||||
|
for (Path directory : directoriesToMonitor(sslConfiguration.filesToMonitor(environment))) {
|
||||||
|
pathToChangeListenerMap.compute(directory, (path, listener) -> {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.addSSLConfiguration(sslConfiguration);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChangeListener changeListener = new ChangeListener();
|
||||||
|
changeListener.addSSLConfiguration(sslConfiguration);
|
||||||
|
FileWatcher fileWatcher = new FileWatcher(path);
|
||||||
|
fileWatcher.addListener(changeListener);
|
||||||
|
try {
|
||||||
|
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
|
||||||
|
return changeListener;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error("failed to start watching directory [{}] for ssl configuration [{}]", path, sslConfiguration);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates all of the sessions in the provided {@link SSLContext}
|
||||||
|
*/
|
||||||
|
private static void invalidateAllSessions(SSLContext context) {
|
||||||
|
if (context != null) {
|
||||||
|
invalidateSessions(context.getClientSessionContext());
|
||||||
|
invalidateSessions(context.getServerSessionContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates the sessions in the provided {@link SSLSessionContext}
|
||||||
|
*/
|
||||||
|
private static void invalidateSessions(SSLSessionContext sslSessionContext) {
|
||||||
|
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
|
||||||
|
while (sessionIds.hasMoreElements()) {
|
||||||
|
byte[] sessionId = sessionIds.nextElement();
|
||||||
|
sslSessionContext.getSession(sessionId).invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a unique set of directories that need to be monitored based on the provided file paths
|
||||||
|
*/
|
||||||
|
private static Set<Path> directoriesToMonitor(List<Path> filePaths) {
|
||||||
|
Set<Path> paths = new HashSet<>();
|
||||||
|
for (Path path : filePaths) {
|
||||||
|
paths.add(path.getParent());
|
||||||
|
}
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChangeListener implements FileChangesListener {
|
||||||
|
|
||||||
|
private final CopyOnWriteArraySet<SSLConfiguration> sslConfigurations = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given ssl configuration to those that have files within the directory watched by this change listener
|
||||||
|
*/
|
||||||
|
private void addSSLConfiguration(SSLConfiguration sslConfiguration) {
|
||||||
|
sslConfigurations.add(sslConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileCreated(Path file) {
|
||||||
|
onFileChanged(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileDeleted(Path file) {
|
||||||
|
onFileChanged(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFileChanged(Path file) {
|
||||||
|
for (SSLConfiguration sslConfiguration : sslConfigurations) {
|
||||||
|
if (sslConfiguration.filesToMonitor(environment).contains(file)) {
|
||||||
|
sslConfiguration.reload(file, environment);
|
||||||
|
invalidateAllSessions(serverSSLService.getSSLContext(sslConfiguration));
|
||||||
|
invalidateAllSessions(clientSSLService.getSSLContext(sslConfiguration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,14 +7,12 @@ package org.elasticsearch.xpack.security.ssl;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||||
|
|
||||||
public class ServerSSLService extends AbstractSSLService {
|
public class ServerSSLService extends AbstractSSLService {
|
||||||
|
|
||||||
public ServerSSLService(Settings settings, Environment environment, Global globalSSLConfiguration,
|
public ServerSSLService(Settings settings, Environment environment, Global globalSSLConfiguration) {
|
||||||
ResourceWatcherService resourceWatcherService) {
|
super(settings, environment, globalSSLConfiguration);
|
||||||
super(settings, environment, globalSSLConfiguration, resourceWatcherService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -26,9 +26,9 @@ class StoreKeyConfig extends KeyConfig {
|
||||||
final String keyPassword;
|
final String keyPassword;
|
||||||
final String trustStoreAlgorithm;
|
final String trustStoreAlgorithm;
|
||||||
|
|
||||||
StoreKeyConfig(boolean includeSystem, boolean reloadEnabled, String keyStorePath, String keyStorePassword, String keyPassword,
|
StoreKeyConfig(boolean includeSystem, String keyStorePath, String keyStorePassword, String keyPassword,
|
||||||
String keyStoreAlgorithm, String trustStoreAlgorithm) {
|
String keyStoreAlgorithm, String trustStoreAlgorithm) {
|
||||||
super(includeSystem, reloadEnabled);
|
super(includeSystem);
|
||||||
this.keyStorePath = keyStorePath;
|
this.keyStorePath = keyStorePath;
|
||||||
this.keyStorePassword = keyStorePassword;
|
this.keyStorePassword = keyStorePassword;
|
||||||
this.keyPassword = keyPassword;
|
this.keyPassword = keyPassword;
|
||||||
|
@ -37,7 +37,7 @@ class StoreKeyConfig extends KeyConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment) {
|
X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment) {
|
||||||
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
|
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
|
||||||
// TODO remove reliance on JKS since we can PKCS12 stores...
|
// TODO remove reliance on JKS since we can PKCS12 stores...
|
||||||
KeyStore ks = KeyStore.getInstance("jks");
|
KeyStore ks = KeyStore.getInstance("jks");
|
||||||
|
@ -50,14 +50,9 @@ class StoreKeyConfig extends KeyConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
|
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
|
||||||
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
|
try {
|
||||||
// TODO remove reliance on JKS since we can PKCS12 stores...
|
return CertUtils.trustManagers(keyStorePath, keyStorePassword, trustStoreAlgorithm, environment);
|
||||||
KeyStore ks = KeyStore.getInstance("jks");
|
|
||||||
assert keyStorePassword != null;
|
|
||||||
ks.load(in, keyStorePassword.toCharArray());
|
|
||||||
|
|
||||||
return CertUtils.trustManagers(ks, trustStoreAlgorithm);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,20 @@ class StoreTrustConfig extends TrustConfig {
|
||||||
final String trustStorePassword;
|
final String trustStorePassword;
|
||||||
final String trustStoreAlgorithm;
|
final String trustStoreAlgorithm;
|
||||||
|
|
||||||
StoreTrustConfig(boolean includeSystem, boolean reloadEnabled, String trustStorePath, String trustStorePassword,
|
StoreTrustConfig(boolean includeSystem, String trustStorePath, String trustStorePassword, String trustStoreAlgorithm) {
|
||||||
String trustStoreAlgorithm) {
|
super(includeSystem);
|
||||||
super(includeSystem, reloadEnabled);
|
|
||||||
this.trustStorePath = trustStorePath;
|
this.trustStorePath = trustStorePath;
|
||||||
this.trustStorePassword = trustStorePassword;
|
this.trustStorePassword = trustStorePassword;
|
||||||
this.trustStoreAlgorithm = trustStoreAlgorithm;
|
this.trustStoreAlgorithm = trustStoreAlgorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
|
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
|
||||||
if (trustStorePath == null) {
|
if (trustStorePath == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(trustStorePath, environment))) {
|
try {
|
||||||
// TODO remove reliance on JKS since we can PKCS12 stores...
|
return CertUtils.trustManagers(trustStorePath, trustStorePassword, trustStoreAlgorithm, environment);
|
||||||
KeyStore trustStore = KeyStore.getInstance("jks");
|
|
||||||
assert trustStorePassword != null;
|
|
||||||
trustStore.load(in, trustStorePassword.toCharArray());
|
|
||||||
return CertUtils.trustManagers(trustStore, trustStoreAlgorithm);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,60 +8,53 @@ package org.elasticsearch.xpack.security.ssl;
|
||||||
import org.elasticsearch.ElasticsearchException;
|
import org.elasticsearch.ElasticsearchException;
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.xpack.security.ssl.TrustConfig.Reloadable.Listener;
|
|
||||||
import org.elasticsearch.watcher.FileChangesListener;
|
|
||||||
import org.elasticsearch.watcher.FileWatcher;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService.Frequency;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
import javax.net.ssl.SSLEngine;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
abstract class TrustConfig {
|
abstract class TrustConfig {
|
||||||
|
|
||||||
protected final boolean includeSystem;
|
protected final boolean includeSystem;
|
||||||
protected final boolean reloadEnabled;
|
|
||||||
|
|
||||||
TrustConfig(boolean includeSystem, boolean reloadEnabled) {
|
X509ExtendedTrustManager[] trustManagers = null;
|
||||||
|
|
||||||
|
TrustConfig(boolean includeSystem) {
|
||||||
this.includeSystem = includeSystem;
|
this.includeSystem = includeSystem;
|
||||||
this.reloadEnabled = reloadEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final TrustManager[] trustManagers(@Nullable Environment environment, @Nullable ResourceWatcherService resourceWatcherService,
|
final synchronized X509ExtendedTrustManager[] trustManagers(@Nullable Environment environment) {
|
||||||
@Nullable Listener listener) {
|
if (trustManagers == null) {
|
||||||
X509ExtendedTrustManager[] trustManagers = loadAndMergeIfNecessary(environment);
|
X509ExtendedTrustManager loadedTrustManager = loadAndMergeIfNecessary(environment);
|
||||||
if (reloadEnabled && resourceWatcherService != null && listener != null) {
|
setTrustManagers(loadedTrustManager);
|
||||||
ReloadableTrustManager reloadableTrustManager = new ReloadableTrustManager(trustManagers[0], environment);
|
|
||||||
try {
|
|
||||||
List<Path> filesToMonitor = filesToMonitor(environment);
|
|
||||||
ChangeListener changeListener = new ChangeListener(filesToMonitor, reloadableTrustManager, listener);
|
|
||||||
for (Path path : directoriesToMonitor(filesToMonitor)) {
|
|
||||||
FileWatcher fileWatcher = new FileWatcher(path);
|
|
||||||
fileWatcher.addListener(changeListener);
|
|
||||||
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
|
|
||||||
}
|
|
||||||
return new X509ExtendedTrustManager[] { reloadableTrustManager };
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ElasticsearchException("failed to add file watcher", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return trustManagers;
|
return trustManagers;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment);
|
synchronized void reload(@Nullable Environment environment) {
|
||||||
|
X509ExtendedTrustManager loadedTrustManager = loadAndMergeIfNecessary(environment);
|
||||||
|
setTrustManagers(loadedTrustManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
final synchronized void setTrustManagers(X509ExtendedTrustManager loadedTrustManager) {
|
||||||
|
if (loadedTrustManager == null) {
|
||||||
|
this.trustManagers = new X509ExtendedTrustManager[0];
|
||||||
|
} else if (this.trustManagers == null || this.trustManagers.length == 0) {
|
||||||
|
this.trustManagers = new X509ExtendedTrustManager[] { new ReloadableTrustManager(loadedTrustManager) };
|
||||||
|
} else {
|
||||||
|
assert this.trustManagers[0] instanceof ReloadableTrustManager;
|
||||||
|
((ReloadableTrustManager)this.trustManagers[0]).setTrustManager(loadedTrustManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment);
|
||||||
|
|
||||||
abstract void validate();
|
abstract void validate();
|
||||||
|
|
||||||
|
@ -69,56 +62,47 @@ abstract class TrustConfig {
|
||||||
|
|
||||||
public abstract String toString();
|
public abstract String toString();
|
||||||
|
|
||||||
private X509ExtendedTrustManager[] loadAndMergeIfNecessary(@Nullable Environment environment) {
|
final X509ExtendedTrustManager loadAndMergeIfNecessary(@Nullable Environment environment) {
|
||||||
X509ExtendedTrustManager[] nonSystemTrustManagers = nonSystemTrustManagers(environment);
|
X509ExtendedTrustManager trustManager = nonSystemTrustManager(environment);
|
||||||
if (includeSystem) {
|
if (includeSystem) {
|
||||||
return mergeWithSystem(nonSystemTrustManagers);
|
trustManager = mergeWithSystem(trustManager);
|
||||||
} else if (nonSystemTrustManagers == null || nonSystemTrustManagers.length == 0) {
|
} else if (trustManager == null) {
|
||||||
return new X509ExtendedTrustManager[0];
|
return null;
|
||||||
}
|
}
|
||||||
return nonSystemTrustManagers;
|
return trustManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private X509ExtendedTrustManager[] mergeWithSystem(X509ExtendedTrustManager[] nonSystemTrustManagers) {
|
private X509ExtendedTrustManager mergeWithSystem(X509ExtendedTrustManager nonSystemTrustManager) {
|
||||||
try {
|
try {
|
||||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
tmf.init((KeyStore) null);
|
tmf.init((KeyStore) null);
|
||||||
TrustManager[] systemTrustManagers = tmf.getTrustManagers();
|
TrustManager[] systemTrustManagers = tmf.getTrustManagers();
|
||||||
X509ExtendedTrustManager system = findFirstX509TrustManager(systemTrustManagers);
|
X509ExtendedTrustManager system = findFirstX509ExtendedTrustManager(systemTrustManagers);
|
||||||
if (nonSystemTrustManagers == null || nonSystemTrustManagers.length == 0) {
|
if (nonSystemTrustManager == null) {
|
||||||
return new X509ExtendedTrustManager[] { system };
|
return system;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new X509ExtendedTrustManager[] { new CombiningX509TrustManager(nonSystemTrustManagers[0], system) };
|
return new CombiningX509TrustManager(nonSystemTrustManager, system);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new ElasticsearchException("failed to initialize a trust managers", e);
|
throw new ElasticsearchException("failed to initialize a trust managers", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static X509ExtendedTrustManager findFirstX509TrustManager(TrustManager[] trustManagers) {
|
private static X509ExtendedTrustManager findFirstX509ExtendedTrustManager(TrustManager[] trustManagers) {
|
||||||
X509ExtendedTrustManager x509TrustManager = null;
|
X509ExtendedTrustManager x509TrustManager = null;
|
||||||
for (TrustManager trustManager : trustManagers) {
|
for (TrustManager trustManager : trustManagers) {
|
||||||
if (trustManager instanceof X509TrustManager) {
|
if (trustManager instanceof X509ExtendedTrustManager) {
|
||||||
// first one wins like in the JDK
|
// first one wins like in the JDK
|
||||||
x509TrustManager = (X509ExtendedTrustManager) trustManager;
|
x509TrustManager = (X509ExtendedTrustManager) trustManager;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (x509TrustManager == null) {
|
if (x509TrustManager == null) {
|
||||||
throw new IllegalArgumentException("did not find a X509TrustManager");
|
throw new IllegalArgumentException("did not find a X509ExtendedTrustManager");
|
||||||
}
|
}
|
||||||
return x509TrustManager;
|
return x509TrustManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Set<Path> directoriesToMonitor(List<Path> filePaths) {
|
|
||||||
Set<Path> paths = new HashSet<>();
|
|
||||||
for (Path path : filePaths) {
|
|
||||||
assert Files.isDirectory(path) == false;
|
|
||||||
paths.add(path.getParent());
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class CombiningX509TrustManager extends X509ExtendedTrustManager {
|
private static class CombiningX509TrustManager extends X509ExtendedTrustManager {
|
||||||
|
|
||||||
private final X509ExtendedTrustManager first;
|
private final X509ExtendedTrustManager first;
|
||||||
|
@ -196,14 +180,12 @@ abstract class TrustConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ReloadableTrustManager extends X509ExtendedTrustManager implements Reloadable {
|
final class ReloadableTrustManager extends X509ExtendedTrustManager {
|
||||||
|
|
||||||
private final Environment environment;
|
|
||||||
private volatile X509ExtendedTrustManager trustManager;
|
private volatile X509ExtendedTrustManager trustManager;
|
||||||
|
|
||||||
ReloadableTrustManager(X509ExtendedTrustManager trustManager, @Nullable Environment environment) {
|
ReloadableTrustManager(X509ExtendedTrustManager trustManager) {
|
||||||
this.trustManager = trustManager;
|
this.trustManager = trustManager;
|
||||||
this.environment = environment;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -241,59 +223,12 @@ abstract class TrustConfig {
|
||||||
return trustManager.getAcceptedIssuers();
|
return trustManager.getAcceptedIssuers();
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void reload() {
|
|
||||||
X509ExtendedTrustManager[] array = loadAndMergeIfNecessary(environment);
|
|
||||||
this.trustManager = array[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
synchronized void setTrustManager(X509ExtendedTrustManager trustManager) {
|
synchronized void setTrustManager(X509ExtendedTrustManager trustManager) {
|
||||||
this.trustManager = trustManager;
|
this.trustManager = trustManager;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
interface Reloadable {
|
X509ExtendedTrustManager getTrustManager() {
|
||||||
|
return trustManager;
|
||||||
void reload();
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
|
|
||||||
void onReload();
|
|
||||||
|
|
||||||
void onFailure(Exception e);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class ChangeListener implements FileChangesListener {
|
|
||||||
|
|
||||||
private final List<Path> paths;
|
|
||||||
private final Reloadable reloadable;
|
|
||||||
private final Listener listener;
|
|
||||||
|
|
||||||
protected ChangeListener(List<Path> paths, Reloadable reloadable, Listener listener) {
|
|
||||||
this.paths = paths;
|
|
||||||
this.reloadable = reloadable;
|
|
||||||
this.listener = listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileDeleted(Path file) {
|
|
||||||
onFileChanged(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFileChanged(Path file) {
|
|
||||||
for (Path path : paths) {
|
|
||||||
if (file.equals(path)) {
|
|
||||||
try {
|
|
||||||
reloadable.reload();
|
|
||||||
listener.onReload();
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class AbstractActiveDirectoryIntegTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
globalSettings = builder.build();
|
globalSettings = builder.build();
|
||||||
Environment environment = new Environment(globalSettings);
|
Environment environment = new Environment(globalSettings);
|
||||||
clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings), null);
|
clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, LdapSearchScope scope,
|
Settings buildAdSettings(String ldapUrl, String adDomainName, String userSearchDN, LdapSearchScope scope,
|
||||||
|
|
|
@ -40,7 +40,7 @@ public abstract class GroupsResolverTestCase extends ESTestCase {
|
||||||
}
|
}
|
||||||
Settings settings = builder.build();
|
Settings settings = builder.build();
|
||||||
Environment env = new Environment(settings);
|
Environment env = new Environment(settings);
|
||||||
ClientSSLService clientSSLService = new ClientSSLService(settings, env, new Global(settings), null);
|
ClientSSLService clientSSLService = new ClientSSLService(settings, env, new Global(settings));
|
||||||
|
|
||||||
LDAPURL ldapurl = new LDAPURL(ldapUrl());
|
LDAPURL ldapurl = new LDAPURL(ldapUrl());
|
||||||
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
LDAPConnectionOptions options = new LDAPConnectionOptions();
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", keystore)
|
.put("xpack.security.ssl.keystore.path", keystore)
|
||||||
.put("xpack.security.ssl.keystore.password", "changeit")
|
.put("xpack.security.ssl.keystore.password", "changeit")
|
||||||
.build();
|
.build();
|
||||||
clientSSLService = new ClientSSLService(settings, env, new Global(settings), null);
|
clientSSLService = new ClientSSLService(settings, env, new Global(settings));
|
||||||
|
|
||||||
globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
globalSettings = Settings.builder().put("path.home", createTempDir()).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ public class OpenLdapTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
globalSettings = builder.build();
|
globalSettings = builder.build();
|
||||||
Environment environment = new Environment(globalSettings);
|
Environment environment = new Environment(globalSettings);
|
||||||
clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings), null);
|
clientSSLService = new ClientSSLService(globalSettings, environment, new Global(globalSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConnect() throws Exception {
|
public void testConnect() throws Exception {
|
||||||
|
|
|
@ -59,7 +59,7 @@ public class ClientSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.truststore.path", testclientStore)
|
.put("xpack.security.ssl.truststore.path", testclientStore)
|
||||||
.put("xpack.security.ssl.truststore.password", "testclient")
|
.put("xpack.security.ssl.truststore.password", "testclient")
|
||||||
.build();
|
.build();
|
||||||
ClientSSLService clientSSLService = new ClientSSLService(settings, null, new Global(settings), null);
|
ClientSSLService clientSSLService = new ClientSSLService(settings, null, new Global(settings));
|
||||||
clientSSLService.createSSLEngine();
|
clientSSLService.createSSLEngine();
|
||||||
fail("expected an exception");
|
fail("expected an exception");
|
||||||
} catch (ElasticsearchException e) {
|
} catch (ElasticsearchException e) {
|
||||||
|
@ -284,7 +284,6 @@ public class ClientSSLServiceTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientSSLService createClientSSLService(Settings settings) {
|
private ClientSSLService createClientSSLService(Settings settings) {
|
||||||
ClientSSLService clientSSLService = new ClientSSLService(settings, env, new Global(settings), null);
|
return new ClientSSLService(settings, env, new Global(settings));
|
||||||
return clientSSLService;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,652 @@
|
||||||
|
/*
|
||||||
|
* 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.xpack.security.ssl;
|
||||||
|
|
||||||
|
import org.apache.lucene.util.SetOnce;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||||
|
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.threadpool.TestThreadPool;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
|
import org.elasticsearch.xpack.security.ssl.KeyConfig.ReloadableX509KeyManager;
|
||||||
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||||
|
import org.elasticsearch.xpack.security.ssl.TrustConfig.ReloadableTrustManager;
|
||||||
|
import org.hamcrest.Matcher;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.X509ExtendedKeyManager;
|
||||||
|
import javax.net.ssl.X509ExtendedTrustManager;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.AtomicMoveNotSupportedException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.PrivateKey;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
import static org.hamcrest.core.Is.is;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the reloading of SSL configuration
|
||||||
|
*/
|
||||||
|
public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||||
|
|
||||||
|
private ThreadPool threadPool;
|
||||||
|
private ResourceWatcherService resourceWatcherService;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
threadPool = new TestThreadPool("reload tests");
|
||||||
|
resourceWatcherService =
|
||||||
|
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
||||||
|
resourceWatcherService.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanup() throws Exception {
|
||||||
|
if (threadPool != null) {
|
||||||
|
terminate(threadPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests reloading a keystore. The contents of the keystore is used for both keystore and truststore material, so both key
|
||||||
|
* config and trust config is checked.
|
||||||
|
*/
|
||||||
|
public void testReloadingKeyStore() throws Exception {
|
||||||
|
final Path tempDir = createTempDir();
|
||||||
|
final Path keystorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
||||||
|
final Settings settings = Settings.builder()
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.put("xpack.security.ssl.keystore.path", keystorePath)
|
||||||
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
|
.build();
|
||||||
|
final Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks = (keyManager, config) -> {
|
||||||
|
// key manager checks
|
||||||
|
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||||
|
assertNotNull(aliases);
|
||||||
|
assertThat(aliases.length, is(1));
|
||||||
|
assertThat(aliases[0], is("testnode"));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||||
|
// trust manager checks
|
||||||
|
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||||
|
trustedCount.set(certificates.length);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
final Runnable modifier = () -> {
|
||||||
|
try {
|
||||||
|
// modify it
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||||
|
keyStore.load(null, null);
|
||||||
|
Path updated = tempDir.resolve("updated.jks");
|
||||||
|
try (OutputStream out = Files.newOutputStream(updated)) {
|
||||||
|
keyStore.store(out, "testnode".toCharArray());
|
||||||
|
}
|
||||||
|
atomicMoveIfPossible(updated, keystorePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("modification failed", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||||
|
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, (k, c) -> null,
|
||||||
|
trustManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of a PEM key config when the key is overwritten. The trust portion is not tested as it is not modified by this
|
||||||
|
* test.
|
||||||
|
*/
|
||||||
|
public void testPEMKeyConfigReloading() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path keyPath = tempDir.resolve("testnode.pem");
|
||||||
|
Path certPath = tempDir.resolve("testnode.crt");
|
||||||
|
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||||
|
final Settings settings = Settings.builder()
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.put("xpack.security.ssl.key.path", keyPath)
|
||||||
|
.put("xpack.security.ssl.key.password", "testnode")
|
||||||
|
.put("xpack.security.ssl.cert", certPath)
|
||||||
|
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
||||||
|
.build();
|
||||||
|
final Environment env = randomBoolean() ? null :
|
||||||
|
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||||
|
|
||||||
|
final SetOnce<PrivateKey> privateKey = new SetOnce<>();
|
||||||
|
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks = (keyManager, config) -> {
|
||||||
|
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||||
|
assertNotNull(aliases);
|
||||||
|
assertThat(aliases.length, is(1));
|
||||||
|
assertThat(aliases[0], is("key"));
|
||||||
|
privateKey.set(keyManager.getPrivateKey("key"));
|
||||||
|
assertNotNull(privateKey.get());
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
final KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
|
||||||
|
final Runnable modifier = () -> {
|
||||||
|
try {
|
||||||
|
// make sure we wait long enough to see a change. if time is within a second the file may not be seen as modified since the
|
||||||
|
// size is the same!
|
||||||
|
assertTrue(awaitBusy(() -> {
|
||||||
|
try {
|
||||||
|
BasicFileAttributes attributes = Files.readAttributes(keyPath, BasicFileAttributes.class);
|
||||||
|
return System.currentTimeMillis() - attributes.lastModifiedTime().toMillis() >= 1000L;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("io exception while checking time", e);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
||||||
|
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
||||||
|
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||||
|
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
||||||
|
writer.writeObject(keyPair,
|
||||||
|
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
||||||
|
}
|
||||||
|
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("failed to modify file", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks = (keyManager, config) -> {
|
||||||
|
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||||
|
assertNotNull(aliases);
|
||||||
|
assertThat(aliases.length, is(1));
|
||||||
|
assertThat(aliases[0], is("key"));
|
||||||
|
assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey)));
|
||||||
|
assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate())));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
validateKeyConfigurationIsReloaded(settings, env, keyManagerPreChecks, modifier, keyManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of the trust config when the trust store is modified. The key config is not tested as part of this test.
|
||||||
|
*/
|
||||||
|
public void testReloadingTrustStore() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path trustStorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
||||||
|
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
|
||||||
|
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||||
|
// trust manager checks
|
||||||
|
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||||
|
trustedCount.set(certificates.length);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
final Runnable modifier = () -> {
|
||||||
|
try {
|
||||||
|
Path updatedTruststore = tempDir.resolve("updated.jks");
|
||||||
|
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||||
|
keyStore.load(null, null);
|
||||||
|
try (OutputStream out = Files.newOutputStream(updatedTruststore)) {
|
||||||
|
keyStore.store(out, "testnode".toCharArray());
|
||||||
|
}
|
||||||
|
atomicMoveIfPossible(updatedTruststore, trustStorePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("failed to modify file", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||||
|
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the reloading of a trust config that is backed by PEM certificate files. The key config is not tested as we only care about the
|
||||||
|
* trust config in this test.
|
||||||
|
*/
|
||||||
|
public void testReloadingPEMTrustConfig() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), false)
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||||
|
// trust manager checks
|
||||||
|
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||||
|
assertThat(certificates.length, is(1));
|
||||||
|
assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client"));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
final Runnable modifier = () -> {
|
||||||
|
try {
|
||||||
|
Path updatedCert = tempDir.resolve("updated.crt");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), updatedCert,
|
||||||
|
StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("failed to modify file", e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||||
|
Certificate[] updatedCerts = updatedTrustManager.getAcceptedIssuers();
|
||||||
|
assertThat(updatedCerts.length, is(1));
|
||||||
|
assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node"));
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of a keystore when there is an exception during reloading. An exception is caused by truncating the keystore
|
||||||
|
* that is being monitored
|
||||||
|
*/
|
||||||
|
public void testReloadingKeyStoreException() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path keystorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.put("xpack.security.ssl.keystore.path", keystorePath)
|
||||||
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
final Global config = new Global(settings);
|
||||||
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [keystore reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [keystore reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final SSLConfigurationReloader reloader =
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
|
reloader.onSSLContextLoaded(config);
|
||||||
|
|
||||||
|
// key manager checks
|
||||||
|
X509ExtendedKeyManager keyManager = ((ReloadableX509KeyManager)config.keyConfig().keyManagers(env)[0]).getKeyManager();
|
||||||
|
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||||
|
assertNotNull(aliases);
|
||||||
|
assertThat(aliases.length, is(1));
|
||||||
|
assertThat(aliases[0], is("testnode"));
|
||||||
|
PrivateKey privateKey = keyManager.getPrivateKey("testnode");
|
||||||
|
|
||||||
|
// truncate the keystore
|
||||||
|
try (OutputStream out = Files.newOutputStream(keystorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
|
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
||||||
|
assertThat(keyManager.getPrivateKey("testnode"), is(equalTo(privateKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of a key config backed by pem files when there is an exception during reloading. An exception is caused by
|
||||||
|
* truncating the key file that is being monitored
|
||||||
|
*/
|
||||||
|
public void testReloadingPEMKeyConfigException() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path keyPath = tempDir.resolve("testnode.pem");
|
||||||
|
Path certPath = tempDir.resolve("testnode.crt");
|
||||||
|
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.put("xpack.security.ssl.key.path", keyPath)
|
||||||
|
.put("xpack.security.ssl.key.password", "testnode")
|
||||||
|
.put("xpack.security.ssl.cert", certPath)
|
||||||
|
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
final Global config = new Global(settings);
|
||||||
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [pem key reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [pem key reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final SSLConfigurationReloader reloader =
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
|
reloader.onSSLContextLoaded(config);
|
||||||
|
|
||||||
|
X509ExtendedKeyManager keyManager = ((ReloadableX509KeyManager)config.keyConfig().keyManagers(env)[0]).getKeyManager();
|
||||||
|
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||||
|
assertNotNull(aliases);
|
||||||
|
assertThat(aliases.length, is(1));
|
||||||
|
assertThat(aliases[0], is("key"));
|
||||||
|
PrivateKey privateKey = keyManager.getPrivateKey("key");
|
||||||
|
|
||||||
|
// truncate the file
|
||||||
|
try (OutputStream os = Files.newOutputStream(keyPath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
|
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
||||||
|
assertThat(keyManager.getPrivateKey("key"), is(equalTo(privateKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of a truststore when there is an exception during reloading. An exception is caused by truncating the truststore
|
||||||
|
* that is being monitored
|
||||||
|
*/
|
||||||
|
public void testTrustStoreReloadException() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path trustStorePath = tempDir.resolve("testnode.jks");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
||||||
|
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
final Global config = new Global(settings);
|
||||||
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [truststore reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [truststore reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final SSLConfigurationReloader reloader =
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
|
reloader.onSSLContextLoaded(config);
|
||||||
|
|
||||||
|
X509ExtendedTrustManager trustManager = ((ReloadableTrustManager)config.trustConfig().trustManagers(env)[0]).getTrustManager();
|
||||||
|
final Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||||
|
assertContainsCertificateWithMatchingName(certificates, containsString("Test Node"));
|
||||||
|
|
||||||
|
// truncate the truststore
|
||||||
|
try (OutputStream os = Files.newOutputStream(trustStorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
|
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
||||||
|
assertContainsCertificateWithMatchingName(trustManager.getAcceptedIssuers(), containsString("Test Node"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the reloading of a trust config backed by pem files when there is an exception during reloading. An exception is caused by
|
||||||
|
* truncating the certificate file that is being monitored
|
||||||
|
*/
|
||||||
|
public void testPEMTrustReloadException() throws Exception {
|
||||||
|
Path tempDir = createTempDir();
|
||||||
|
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||||
|
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||||
|
Settings settings = Settings.builder()
|
||||||
|
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
||||||
|
.put("path.home", createTempDir())
|
||||||
|
.build();
|
||||||
|
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||||
|
final Global config = new Global(settings);
|
||||||
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [pem trust reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||||
|
fail("get should not be called! [pem trust reload exception]");
|
||||||
|
return super.getSSLContext(configuration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final SSLConfigurationReloader reloader =
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
|
reloader.onSSLContextLoaded(config);
|
||||||
|
|
||||||
|
X509ExtendedTrustManager trustManager = ((ReloadableTrustManager)config.trustConfig().trustManagers(env)[0]).getTrustManager();
|
||||||
|
final Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||||
|
assertContainsCertificateWithMatchingName(certificates, containsString("Test Client"));
|
||||||
|
|
||||||
|
// write bad file
|
||||||
|
Path updatedCert = tempDir.resolve("updated.crt");
|
||||||
|
try (OutputStream os = Files.newOutputStream(updatedCert)) {
|
||||||
|
os.write(randomByte());
|
||||||
|
}
|
||||||
|
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||||
|
|
||||||
|
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||||
|
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
||||||
|
assertContainsCertificateWithMatchingName(trustManager.getAcceptedIssuers(), containsString("Test Client"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
||||||
|
*/
|
||||||
|
private void validateTrustConfigurationIsReloaded(Settings settings, Environment env,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||||
|
Runnable modificationFunction,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||||
|
throws Exception {
|
||||||
|
validateSSLConfigurationIsReloaded(settings, env, false, true, null, trustManagerPreChecks, modificationFunction, null,
|
||||||
|
trustManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
||||||
|
*/
|
||||||
|
private void validateKeyConfigurationIsReloaded(Settings settings, Environment env,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||||
|
Runnable modificationFunction,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks)
|
||||||
|
throws Exception {
|
||||||
|
validateSSLConfigurationIsReloaded(settings, env, true, false, keyManagerPreChecks, null, modificationFunction,
|
||||||
|
keyManagerPostChecks, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that both the key and trust configuration aspects of the SSLConfiguration are reloaded
|
||||||
|
*/
|
||||||
|
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||||
|
Runnable modificationFunction,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||||
|
throws Exception {
|
||||||
|
validateSSLConfigurationIsReloaded(settings, env, true, true, keyManagerPreChecks, trustManagerPreChecks, modificationFunction,
|
||||||
|
keyManagerPostChecks, trustManagerPostChecks);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env, boolean checkKeys, boolean checkTrust,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||||
|
Runnable modificationFunction,
|
||||||
|
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks,
|
||||||
|
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||||
|
throws Exception {
|
||||||
|
|
||||||
|
final AtomicInteger serverCounter = new AtomicInteger(0);
|
||||||
|
final AtomicInteger clientCounter = new AtomicInteger(0);
|
||||||
|
final Global config = new Global(settings);
|
||||||
|
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration sslConfiguration) {
|
||||||
|
serverCounter.incrementAndGet();
|
||||||
|
return super.getSSLContext(sslConfiguration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||||
|
@Override
|
||||||
|
SSLContext getSSLContext(SSLConfiguration sslConfiguration) {
|
||||||
|
clientCounter.incrementAndGet();
|
||||||
|
return super.getSSLContext(sslConfiguration);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final SSLConfigurationReloader reloader =
|
||||||
|
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||||
|
|
||||||
|
final X509ExtendedKeyManager keyManager;
|
||||||
|
final X509ExtendedKeyManager[] originalKeyManagers;
|
||||||
|
if (checkKeys) {
|
||||||
|
originalKeyManagers = config.keyConfig().keyManagers(env);
|
||||||
|
assertThat(originalKeyManagers.length, is(1));
|
||||||
|
keyManager = ((ReloadableX509KeyManager) originalKeyManagers[0]).getKeyManager();
|
||||||
|
} else {
|
||||||
|
originalKeyManagers = null;
|
||||||
|
keyManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final X509ExtendedTrustManager[] originalTrustManagers;
|
||||||
|
final X509ExtendedTrustManager trustManager;
|
||||||
|
if (checkTrust) {
|
||||||
|
originalTrustManagers = config.trustConfig().trustManagers(env);
|
||||||
|
assertThat(originalTrustManagers.length, is(1));
|
||||||
|
trustManager = ((ReloadableTrustManager) originalTrustManagers[0]).getTrustManager();
|
||||||
|
} else {
|
||||||
|
originalTrustManagers = null;
|
||||||
|
trustManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// register configuration with reloader
|
||||||
|
reloader.onSSLContextLoaded(config);
|
||||||
|
|
||||||
|
// key manager checks
|
||||||
|
if (checkKeys) {
|
||||||
|
assertKeyManagersSame(keyManager, config.keyConfig().keyManagers(env));
|
||||||
|
keyManagerPreChecks.apply(keyManager, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// trust manager checks
|
||||||
|
if (checkTrust) {
|
||||||
|
assertTrustManagersSame(trustManager, config.trustConfig().trustManagers(env));
|
||||||
|
trustManagerPreChecks.apply(trustManager, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, clientSSLService.getLoadedSSLConfigurations().size());
|
||||||
|
assertEquals(0, serverSSLService.getLoadedSSLConfigurations().size());
|
||||||
|
assertEquals("nothing should have called get", 0, clientCounter.get());
|
||||||
|
assertEquals("nothing should have called get", 0, serverCounter.get());
|
||||||
|
|
||||||
|
// modify
|
||||||
|
modificationFunction.run();
|
||||||
|
assertTrue(awaitBusy(() -> clientCounter.get() > 0 && serverCounter.get() > 0));
|
||||||
|
|
||||||
|
// check key manager
|
||||||
|
if (checkKeys) {
|
||||||
|
final X509ExtendedKeyManager[] updatedKeyManagers = config.keyConfig().keyManagers(env);
|
||||||
|
assertThat(updatedKeyManagers, sameInstance(originalKeyManagers));
|
||||||
|
final X509ExtendedKeyManager updatedKeyManager = ((ReloadableX509KeyManager) updatedKeyManagers[0]).getKeyManager();
|
||||||
|
keyManagerPostChecks.apply(updatedKeyManager, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check trust manager
|
||||||
|
if (checkTrust) {
|
||||||
|
final X509ExtendedTrustManager[] updatedTrustManagers = config.trustConfig().trustManagers(env);
|
||||||
|
assertThat(updatedTrustManagers, sameInstance(originalTrustManagers));
|
||||||
|
final X509ExtendedTrustManager updatedTrustManager = ((ReloadableTrustManager) updatedTrustManagers[0]).getTrustManager();
|
||||||
|
assertThat(updatedTrustManager, not(sameInstance(trustManager)));
|
||||||
|
trustManagerPostChecks.apply(updatedTrustManager, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertContainsCertificateWithMatchingName(Certificate[] certificates, Matcher<String> matcher) {
|
||||||
|
for (Certificate certificate : certificates) {
|
||||||
|
if (certificate instanceof X509Certificate) {
|
||||||
|
if (matcher.matches(((X509Certificate) certificate).getSubjectX500Principal().getName())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fail("no matching certificate could be found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertKeyManagersSame(X509ExtendedKeyManager original, X509ExtendedKeyManager[] other) {
|
||||||
|
assertEquals(1, other.length);
|
||||||
|
assertThat(other[0], instanceOf(ReloadableX509KeyManager.class));
|
||||||
|
X509ExtendedKeyManager otherKeyManager = ((ReloadableX509KeyManager) other[0]).getKeyManager();
|
||||||
|
assertThat(otherKeyManager, sameInstance(original));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTrustManagersSame(X509ExtendedTrustManager original, X509ExtendedTrustManager[] other) {
|
||||||
|
assertEquals(1, other.length);
|
||||||
|
assertThat(other[0], instanceOf(ReloadableTrustManager.class));
|
||||||
|
X509ExtendedTrustManager otherTrustManager = ((ReloadableTrustManager) other[0]).getTrustManager();
|
||||||
|
assertThat(otherTrustManager, sameInstance(original));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void atomicMoveIfPossible(Path source, Path target) throws IOException {
|
||||||
|
try {
|
||||||
|
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||||
|
} catch (AtomicMoveNotSupportedException e) {
|
||||||
|
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,45 +5,18 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security.ssl;
|
package org.elasticsearch.xpack.security.ssl;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
|
||||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.env.Environment;
|
import org.elasticsearch.env.Environment;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
|
||||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||||
import org.elasticsearch.xpack.security.ssl.TrustConfig.Reloadable.Listener;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.threadpool.TestThreadPool;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManager;
|
import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import javax.net.ssl.X509ExtendedKeyManager;
|
|
||||||
import javax.net.ssl.X509ExtendedTrustManager;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.OutputStreamWriter;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.AtomicMoveNotSupportedException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.StandardCopyOption;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
@ -273,10 +246,10 @@ public class SSLConfigurationTests extends ESTestCase {
|
||||||
SSLConfiguration config = new Global(settings);
|
SSLConfiguration config = new Global(settings);
|
||||||
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
||||||
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, null, null);
|
KeyManager[] keyManagers = keyConfig.keyManagers(env);
|
||||||
assertThat(keyManagers.length, is(1));
|
assertThat(keyManagers.length, is(1));
|
||||||
assertThat(config.trustConfig(), sameInstance(keyConfig));
|
assertThat(config.trustConfig(), sameInstance(keyConfig));
|
||||||
TrustManager[] trustManagers = keyConfig.trustManagers(env, null, null);
|
TrustManager[] trustManagers = keyConfig.trustManagers(env);
|
||||||
assertThat(trustManagers.length, is(1));
|
assertThat(trustManagers.length, is(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,464 +269,11 @@ public class SSLConfigurationTests extends ESTestCase {
|
||||||
SSLConfiguration config = new Global(settings);
|
SSLConfiguration config = new Global(settings);
|
||||||
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
||||||
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, null, null);
|
KeyManager[] keyManagers = keyConfig.keyManagers(env);
|
||||||
assertThat(keyManagers.length, is(1));
|
assertThat(keyManagers.length, is(1));
|
||||||
assertThat(config.trustConfig(), not(sameInstance(keyConfig)));
|
assertThat(config.trustConfig(), not(sameInstance(keyConfig)));
|
||||||
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
|
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
|
||||||
TrustManager[] trustManagers = keyConfig.trustManagers(env, null, null);
|
TrustManager[] trustManagers = keyConfig.trustManagers(env);
|
||||||
assertThat(trustManagers.length, is(1));
|
assertThat(trustManagers.length, is(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReloadingKeyStore() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path keystorePath = tempDir.resolve("testnode.jks");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.keystore.path", keystorePath)
|
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.keyConfig(), instanceOf(StoreKeyConfig.class));
|
|
||||||
StoreKeyConfig keyConfig = (StoreKeyConfig) config.keyConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(2);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(keyManagers.length, is(1));
|
|
||||||
assertThat(keyManagers[0], instanceOf(X509ExtendedKeyManager.class));
|
|
||||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertNotNull(aliases);
|
|
||||||
assertThat(aliases.length, is(1));
|
|
||||||
assertThat(aliases[0], is("testnode"));
|
|
||||||
TrustManager[] trustManagers = keyConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
final int trustedCount = certificates.length;
|
|
||||||
assertThat(latch.getCount(), is(2L));
|
|
||||||
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
|
||||||
keyStore.load(null, null);
|
|
||||||
Path updated = tempDir.resolve("updated.jks");
|
|
||||||
try (OutputStream out = Files.newOutputStream(updated)) {
|
|
||||||
keyStore.store(out, "testnode".toCharArray());
|
|
||||||
}
|
|
||||||
atomicMoveIfPossible(updated, keystorePath);
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), is(nullValue()));
|
|
||||||
aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertThat(aliases, is(nullValue()));
|
|
||||||
certificates = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(trustedCount - certificates.length, is(5));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReloadingPEMKeyConfig() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path keyPath = tempDir.resolve("testnode.pem");
|
|
||||||
Path certPath = tempDir.resolve("testnode.crt");
|
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.key.path", keyPath)
|
|
||||||
.put("xpack.security.ssl.key.password", "testnode")
|
|
||||||
.put("xpack.security.ssl.cert", certPath)
|
|
||||||
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
|
||||||
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(2);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload pem");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(keyManagers.length, is(1));
|
|
||||||
assertThat(keyManagers[0], instanceOf(X509ExtendedKeyManager.class));
|
|
||||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertThat(aliases, is(notNullValue()));
|
|
||||||
assertThat(aliases.length, is(1));
|
|
||||||
assertThat(aliases[0], is("key"));
|
|
||||||
PrivateKey privateKey = keyManager.getPrivateKey(aliases[0]);
|
|
||||||
TrustManager[] trustManagers = keyConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
final int trustedCount = certificates.length;
|
|
||||||
assertThat(latch.getCount(), is(2L));
|
|
||||||
|
|
||||||
// make sure we wait enough to see a change. if time is within a second the file may not be seen as modified since the size is
|
|
||||||
// the same!
|
|
||||||
awaitBusy(() -> {
|
|
||||||
try {
|
|
||||||
BasicFileAttributes attributes = Files.readAttributes(keyPath, BasicFileAttributes.class);
|
|
||||||
return System.currentTimeMillis() - attributes.lastModifiedTime().toMillis() >= 1000L;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ElasticsearchException("io exception while checking time", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
|
||||||
KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
|
|
||||||
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
|
||||||
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
|
||||||
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
|
||||||
writer.writeObject(keyPair,
|
|
||||||
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
|
||||||
}
|
|
||||||
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
|
||||||
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), is(nullValue()));
|
|
||||||
aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertThat(aliases, is(notNullValue()));
|
|
||||||
assertThat(aliases.length, is(1));
|
|
||||||
assertThat(aliases[0], is("key"));
|
|
||||||
assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey)));
|
|
||||||
assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate())));
|
|
||||||
certificates = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(trustedCount - certificates.length, is(0));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReloadingTrustStore() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path trustStorePath = tempDir.resolve("testnode.jks");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
|
||||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.trustConfig(), instanceOf(StoreTrustConfig.class));
|
|
||||||
StoreTrustConfig trustConfig = (StoreTrustConfig) config.trustConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
TrustManager[] trustManagers = trustConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
final int trustedCount = certificates.length;
|
|
||||||
|
|
||||||
assertThat(latch.getCount(), is(1L));
|
|
||||||
|
|
||||||
Path updatedTruststore = tempDir.resolve("updated.jks");
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
|
||||||
keyStore.load(null, null);
|
|
||||||
try (OutputStream out = Files.newOutputStream(updatedTruststore)) {
|
|
||||||
keyStore.store(out, "testnode".toCharArray());
|
|
||||||
}
|
|
||||||
atomicMoveIfPossible(updatedTruststore, trustStorePath);
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), is(nullValue()));
|
|
||||||
certificates = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(trustedCount - certificates.length, is(5));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReloadingPEMTrustConfig() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), false)
|
|
||||||
.build();
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
|
|
||||||
PEMTrustConfig trustConfig = (PEMTrustConfig) config.trustConfig();
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
TrustManager[] trustManagers = trustConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(certificates.length, is(1));
|
|
||||||
assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client"));
|
|
||||||
assertThat(latch.getCount(), is(1L));
|
|
||||||
|
|
||||||
Path updatedCert = tempDir.resolve("updated.crt");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), updatedCert,
|
|
||||||
StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), is(nullValue()));
|
|
||||||
Certificate[] updatedCerts = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(updatedCerts.length, is(1));
|
|
||||||
assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node"));
|
|
||||||
assertThat(updatedCerts[0], not(equalTo(certificates[0])));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReloadingKeyStoreException() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path keystorePath = tempDir.resolve("testnode.jks");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.keystore.path", keystorePath)
|
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.keyConfig(), instanceOf(StoreKeyConfig.class));
|
|
||||||
StoreKeyConfig keyConfig = (StoreKeyConfig) config.keyConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, resourceWatcherService, listener);
|
|
||||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertNotNull(aliases);
|
|
||||||
assertThat(aliases.length, is(1));
|
|
||||||
assertThat(aliases[0], is("testnode"));
|
|
||||||
assertThat(latch.getCount(), is(1L));
|
|
||||||
|
|
||||||
// truncate the keystore
|
|
||||||
try (OutputStream out = Files.newOutputStream(keystorePath)) {
|
|
||||||
}
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), notNullValue());
|
|
||||||
assertThat(exceptionRef.get(), instanceOf(ElasticsearchException.class));
|
|
||||||
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReloadingPEMKeyConfigException() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path keyPath = tempDir.resolve("testnode.pem");
|
|
||||||
Path certPath = tempDir.resolve("testnode.crt");
|
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.key.path", keyPath)
|
|
||||||
.put("xpack.security.ssl.key.password", "testnode")
|
|
||||||
.put("xpack.security.ssl.cert", certPath)
|
|
||||||
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
|
|
||||||
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload pem");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
KeyManager[] keyManagers = keyConfig.keyManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(keyManagers.length, is(1));
|
|
||||||
assertThat(keyManagers[0], instanceOf(X509ExtendedKeyManager.class));
|
|
||||||
X509ExtendedKeyManager keyManager = (X509ExtendedKeyManager) keyManagers[0];
|
|
||||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
|
||||||
assertThat(aliases, is(notNullValue()));
|
|
||||||
assertThat(aliases.length, is(1));
|
|
||||||
assertThat(aliases[0], is("key"));
|
|
||||||
PrivateKey privateKey = keyManager.getPrivateKey(aliases[0]);
|
|
||||||
assertThat(latch.getCount(), is(1L));
|
|
||||||
|
|
||||||
// pick a random file to truncate
|
|
||||||
Path toTruncate = randomFrom(keyPath, certPath);
|
|
||||||
|
|
||||||
// truncate the file
|
|
||||||
try (OutputStream os = Files.newOutputStream(toTruncate)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), is(instanceOf(ElasticsearchException.class)));
|
|
||||||
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
|
||||||
assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(privateKey)));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testTrustStoreReloadException() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path trustStorePath = tempDir.resolve("testnode.jks");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
|
||||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), randomBoolean())
|
|
||||||
.build();
|
|
||||||
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.trustConfig(), instanceOf(StoreTrustConfig.class));
|
|
||||||
StoreTrustConfig trustConfig = (StoreTrustConfig) config.trustConfig();
|
|
||||||
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
TrustManager[] trustManagers = trustConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
|
|
||||||
// truncate the truststore
|
|
||||||
try (OutputStream os = Files.newOutputStream(trustStorePath)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), instanceOf(ElasticsearchException.class));
|
|
||||||
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPEMTrustReloadException() throws Exception {
|
|
||||||
Environment env = randomBoolean() ? null :
|
|
||||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
|
||||||
Path tempDir = createTempDir();
|
|
||||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
|
||||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
|
||||||
Settings settings = Settings.builder()
|
|
||||||
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
|
||||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), false)
|
|
||||||
.build();
|
|
||||||
SSLConfiguration config = new Global(settings);
|
|
||||||
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
|
|
||||||
PEMTrustConfig trustConfig = (PEMTrustConfig) config.trustConfig();
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
AtomicReference<Exception> exceptionRef = new AtomicReference<>();
|
|
||||||
Listener listener = createRefreshListener(latch, exceptionRef);
|
|
||||||
|
|
||||||
ThreadPool threadPool = new TestThreadPool("reload");
|
|
||||||
try {
|
|
||||||
ResourceWatcherService resourceWatcherService =
|
|
||||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
|
||||||
resourceWatcherService.start();
|
|
||||||
TrustManager[] trustManagers = trustConfig.trustManagers(env, resourceWatcherService, listener);
|
|
||||||
assertThat(trustManagers.length, is(1));
|
|
||||||
assertThat(trustManagers[0], instanceOf(X509ExtendedTrustManager.class));
|
|
||||||
X509ExtendedTrustManager trustManager = (X509ExtendedTrustManager) trustManagers[0];
|
|
||||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
|
||||||
assertThat(certificates.length, is(1));
|
|
||||||
assertThat(((X509Certificate) certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client"));
|
|
||||||
assertThat(latch.getCount(), is(1L));
|
|
||||||
|
|
||||||
// write bad file
|
|
||||||
Path updatedCert = tempDir.resolve("updated.crt");
|
|
||||||
try (OutputStream os = Files.newOutputStream(updatedCert)) {
|
|
||||||
os.write(randomByte());
|
|
||||||
}
|
|
||||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
|
||||||
|
|
||||||
latch.await();
|
|
||||||
assertThat(exceptionRef.get(), instanceOf(ElasticsearchException.class));
|
|
||||||
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
|
||||||
} finally {
|
|
||||||
threadPool.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Listener createRefreshListener(CountDownLatch latch, AtomicReference<Exception> exceptionRef) {
|
|
||||||
return new Listener() {
|
|
||||||
@Override
|
|
||||||
public void onReload() {
|
|
||||||
logger.info("refresh called");
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
logger.error("exception " + e);
|
|
||||||
exceptionRef.set(e);
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void atomicMoveIfPossible(Path source, Path target) throws IOException {
|
|
||||||
try {
|
|
||||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
|
||||||
} catch (AtomicMoveNotSupportedException e) {
|
|
||||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
|
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
|
||||||
fail("expected an exception");
|
fail("expected an exception");
|
||||||
} catch (ElasticsearchException e) {
|
} catch (ElasticsearchException e) {
|
||||||
assertThat(e.getMessage(), containsString("failed to initialize the SSLContext"));
|
assertThat(e.getMessage(), containsString("failed to initialize the SSLContext"));
|
||||||
|
@ -67,7 +67,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
|
|
||||||
Settings.Builder settingsBuilder = Settings.builder()
|
Settings.Builder settingsBuilder = Settings.builder()
|
||||||
.put("truststore.path", testClientStore)
|
.put("truststore.path", testClientStore)
|
||||||
|
@ -85,7 +85,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
|
|
||||||
SSLContext sslContext = sslService.sslContext();
|
SSLContext sslContext = sslService.sslContext();
|
||||||
SSLContext cachedSslContext = sslService.sslContext();
|
SSLContext cachedSslContext = sslService.sslContext();
|
||||||
|
@ -101,7 +101,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.put("xpack.security.ssl.keystore.key_password", "testnode1")
|
.put("xpack.security.ssl.keystore.key_password", "testnode1")
|
||||||
.build();
|
.build();
|
||||||
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
|
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testIncorrectKeyPasswordThrowsException() throws Exception {
|
public void testIncorrectKeyPasswordThrowsException() throws Exception {
|
||||||
|
@ -112,7 +112,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", differentPasswordsStore)
|
.put("xpack.security.ssl.keystore.path", differentPasswordsStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
|
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
|
||||||
fail("expected an exception");
|
fail("expected an exception");
|
||||||
} catch (ElasticsearchException e) {
|
} catch (ElasticsearchException e) {
|
||||||
assertThat(e.getMessage(), containsString("failed to initialize a KeyManagerFactory"));
|
assertThat(e.getMessage(), containsString("failed to initialize a KeyManagerFactory"));
|
||||||
|
@ -124,7 +124,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
SSLEngine engine = sslService.createSSLEngine();
|
SSLEngine engine = sslService.createSSLEngine();
|
||||||
assertThat(Arrays.asList(engine.getEnabledProtocols()), not(hasItem("SSLv3")));
|
assertThat(Arrays.asList(engine.getEnabledProtocols()), not(hasItem("SSLv3")));
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
SSLSessionContext context = sslService.sslContext().getServerSessionContext();
|
SSLSessionContext context = sslService.sslContext().getServerSessionContext();
|
||||||
assertThat(context.getSessionCacheSize(), equalTo(1000));
|
assertThat(context.getSessionCacheSize(), equalTo(1000));
|
||||||
assertThat(context.getSessionTimeout(), equalTo((int) TimeValue.timeValueHours(24).seconds()));
|
assertThat(context.getSessionTimeout(), equalTo((int) TimeValue.timeValueHours(24).seconds()));
|
||||||
|
@ -147,14 +147,14 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.session.cache_size", "300")
|
.put("xpack.security.ssl.session.cache_size", "300")
|
||||||
.put("xpack.security.ssl.session.cache_timeout", "600s")
|
.put("xpack.security.ssl.session.cache_timeout", "600s")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
SSLSessionContext context = sslService.sslContext().getServerSessionContext();
|
SSLSessionContext context = sslService.sslContext().getServerSessionContext();
|
||||||
assertThat(context.getSessionCacheSize(), equalTo(300));
|
assertThat(context.getSessionCacheSize(), equalTo(300));
|
||||||
assertThat(context.getSessionTimeout(), equalTo(600));
|
assertThat(context.getSessionTimeout(), equalTo(600));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testThatCreateSSLEngineWithoutAnySettingsDoesNotWork() throws Exception {
|
public void testThatCreateSSLEngineWithoutAnySettingsDoesNotWork() throws Exception {
|
||||||
ServerSSLService sslService = new ServerSSLService(Settings.EMPTY, env, new Global(Settings.EMPTY), null);
|
ServerSSLService sslService = new ServerSSLService(Settings.EMPTY, env, new Global(Settings.EMPTY));
|
||||||
try {
|
try {
|
||||||
sslService.createSSLEngine();
|
sslService.createSSLEngine();
|
||||||
fail("Expected IllegalArgumentException");
|
fail("Expected IllegalArgumentException");
|
||||||
|
@ -168,7 +168,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.truststore.path", testnodeStore)
|
.put("xpack.security.ssl.truststore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
try {
|
try {
|
||||||
sslService.createSSLEngine();
|
sslService.createSSLEngine();
|
||||||
fail("Expected IllegalArgumentException");
|
fail("Expected IllegalArgumentException");
|
||||||
|
@ -183,7 +183,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.put("xpack.security.ssl.truststore.path", testnodeStore)
|
.put("xpack.security.ssl.truststore.path", testnodeStore)
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
try {
|
try {
|
||||||
sslService.sslContext();
|
sslService.sslContext();
|
||||||
fail("Expected IllegalArgumentException");
|
fail("Expected IllegalArgumentException");
|
||||||
|
@ -196,7 +196,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
try {
|
try {
|
||||||
sslService.sslContext();
|
sslService.sslContext();
|
||||||
fail("Expected IllegalArgumentException");
|
fail("Expected IllegalArgumentException");
|
||||||
|
@ -214,7 +214,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.putArray("xpack.security.ssl.ciphers", ciphers.toArray(new String[ciphers.size()]))
|
.putArray("xpack.security.ssl.ciphers", ciphers.toArray(new String[ciphers.size()]))
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
SSLEngine engine = sslService.createSSLEngine();
|
SSLEngine engine = sslService.createSSLEngine();
|
||||||
assertThat(engine, is(notNullValue()));
|
assertThat(engine, is(notNullValue()));
|
||||||
String[] enabledCiphers = engine.getEnabledCipherSuites();
|
String[] enabledCiphers = engine.getEnabledCipherSuites();
|
||||||
|
@ -227,7 +227,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.putArray("xpack.security.ssl.ciphers", new String[] { "foo", "bar" })
|
.putArray("xpack.security.ssl.ciphers", new String[] { "foo", "bar" })
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
try {
|
try {
|
||||||
sslService.createSSLEngine();
|
sslService.createSSLEngine();
|
||||||
fail("Expected IllegalArgumentException");
|
fail("Expected IllegalArgumentException");
|
||||||
|
@ -241,7 +241,7 @@ public class ServerSSLServiceTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
.put("xpack.security.ssl.keystore.path", testnodeStore)
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
SSLSocketFactory factory = sslService.sslSocketFactory(Settings.EMPTY);
|
SSLSocketFactory factory = sslService.sslSocketFactory(Settings.EMPTY);
|
||||||
final String[] ciphers = sslService.supportedCiphers(factory.getSupportedCipherSuites(), sslService.ciphers(), false);
|
final String[] ciphers = sslService.supportedCiphers(factory.getSupportedCipherSuites(), sslService.ciphers(), false);
|
||||||
assertThat(factory.getDefaultCipherSuites(), is(ciphers));
|
assertThat(factory.getDefaultCipherSuites(), is(ciphers));
|
||||||
|
|
|
@ -76,7 +76,7 @@ public class HandshakeWaitingHandlerTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||||
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
|
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
|
|
||||||
sslContext = sslService.sslContext();
|
sslContext = sslService.sslContext();
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ public class SecurityNetty3HttpServerTransportTests extends ESTestCase {
|
||||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||||
.build();
|
.build();
|
||||||
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||||
serverSSLService = new ServerSSLService(settings, env, new Global(settings), null);
|
serverSSLService = new ServerSSLService(settings, env, new Global(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testDefaultClientAuth() throws Exception {
|
public void testDefaultClientAuth() throws Exception {
|
||||||
|
|
|
@ -43,8 +43,8 @@ public class SecurityNetty3TransportTests extends ESTestCase {
|
||||||
.build();
|
.build();
|
||||||
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||||
Global globalSSLConfiguration = new Global(settings);
|
Global globalSSLConfiguration = new Global(settings);
|
||||||
serverSSLService = new ServerSSLService(settings, env, globalSSLConfiguration, null);
|
serverSSLService = new ServerSSLService(settings, env, globalSSLConfiguration);
|
||||||
clientSSLService = new ClientSSLService(settings, env, globalSSLConfiguration, null);
|
clientSSLService = new ClientSSLService(settings, env, globalSSLConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testThatSSLCanBeDisabledByProfile() throws Exception {
|
public void testThatSSLCanBeDisabledByProfile() throws Exception {
|
||||||
|
|
|
@ -74,7 +74,7 @@ public class SslClientAuthTests extends SecurityIntegTestCase {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
|
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
|
||||||
.build();
|
.build();
|
||||||
ClientSSLService sslService = new ClientSSLService(settings, null, new Global(settings), null);
|
ClientSSLService sslService = new ClientSSLService(settings, null, new Global(settings));
|
||||||
SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(sslService.sslContext(), NoopHostnameVerifier.INSTANCE);
|
SSLIOSessionStrategy sessionStrategy = new SSLIOSessionStrategy(sslService.sslContext(), NoopHostnameVerifier.INSTANCE);
|
||||||
try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) {
|
try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) {
|
||||||
Response response = restClient.performRequest("GET", "/",
|
Response response = restClient.performRequest("GET", "/",
|
||||||
|
|
|
@ -99,7 +99,7 @@ public class SslIntegrationTests extends SecurityIntegTestCase {
|
||||||
Settings settings = Settings.builder()
|
Settings settings = Settings.builder()
|
||||||
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
|
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
|
||||||
.build();
|
.build();
|
||||||
ClientSSLService service = new ClientSSLService(settings, null, new Global(settings), null);
|
ClientSSLService service = new ClientSSLService(settings, null, new Global(settings));
|
||||||
|
|
||||||
CredentialsProvider provider = new BasicCredentialsProvider();
|
CredentialsProvider provider = new BasicCredentialsProvider();
|
||||||
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(nodeClientUsername(),
|
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(nodeClientUsername(),
|
||||||
|
|
Loading…
Reference in New Issue