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:
jaymode 2016-07-28 07:29:14 -04:00
parent 62353ff8bc
commit c82f1be386
27 changed files with 1069 additions and 778 deletions

View File

@ -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.ssl.ClientSSLService;
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.support.OptionalSettings;
import org.elasticsearch.xpack.security.transport.SecurityClientTransportService;
@ -185,8 +186,7 @@ public class Security implements ActionPlugin, IngestPlugin {
modules.add(b -> {
// for transport client we still must inject these ssl classes with guice
b.bind(ServerSSLService.class).toProvider(Providers.<ServerSSLService>of(null));
b.bind(ClientSSLService.class).toInstance(
new ClientSSLService(settings, null, new SSLConfiguration.Global(settings), null));
b.bind(ClientSSLService.class).toInstance(new ClientSSLService(settings, null, new SSLConfiguration.Global(settings)));
});
return modules;
@ -232,8 +232,12 @@ public class Security implements ActionPlugin, IngestPlugin {
components.add(securityContext);
final SSLConfiguration.Global globalSslConfig = new SSLConfiguration.Global(settings);
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, globalSslConfig, resourceWatcherService);
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, globalSslConfig, resourceWatcherService);
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, globalSslConfig);
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(serverSSLService);

View File

@ -6,44 +6,31 @@
package org.elasticsearch.xpack.security.authc.esnative;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import javax.net.ssl.HttpsURLConnection;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.cli.MultiCommand;
import org.elasticsearch.cli.SettingCommand;
import org.elasticsearch.cli.Terminal;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.Nullable;
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.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
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.file.FileUserPasswdStore;
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.UsernamePasswordToken;
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.ssl.ClientSSLService;
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.IOException;
@ -52,16 +39,13 @@ import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.security.Security.setting;
@ -151,7 +135,7 @@ public class ESNativeRealmMigrateTool extends MultiCommand {
if ("https".equalsIgnoreCase(uri.getScheme())) {
Settings sslSettings = settings.getByPrefix(setting("http.ssl."));
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();
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override

View File

@ -13,13 +13,10 @@ import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
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.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
@ -28,9 +25,12 @@ import java.net.InetAddress;
import java.net.Socket;
import java.util.ArrayList;
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.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
/**
@ -39,19 +39,18 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public abstract class AbstractSSLService extends AbstractComponent {
private final ConcurrentHashMap<SSLConfiguration, SSLContext> sslContexts = new ConcurrentHashMap<>();
private final SSLContextCacheLoader cacheLoader = new SSLContextCacheLoader();
private final ConcurrentHashMap<SSLConfiguration, SSLContext> sslContexts = new ConcurrentHashMap<>();
protected final SSLConfiguration globalSSLConfiguration;
protected final Environment env;
protected final ResourceWatcherService resourceWatcherService;
public AbstractSSLService(Settings settings, Environment environment, Global globalSSLConfiguration,
ResourceWatcherService resourceWatcherService) {
private Listener listener = Listener.NOOP;
AbstractSSLService(Settings settings, Environment environment, Global globalSSLConfiguration) {
super(settings);
this.env = environment;
this.globalSSLConfiguration = globalSSLConfiguration;
this.resourceWatcherService = resourceWatcherService;
}
public String[] supportedProtocols() {
@ -167,6 +166,27 @@ public abstract class AbstractSSLService extends AbstractComponent {
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 {
public SSLContext load(SSLConfiguration sslConfiguration) {
@ -175,15 +195,15 @@ public abstract class AbstractSSLService extends AbstractComponent {
logger.debug("using ssl settings [{}]", sslConfiguration);
}
ConfigRefreshListener configRefreshListener = new ConfigRefreshListener(sslConfiguration);
TrustManager[] trustManagers = sslConfiguration.trustConfig().trustManagers(env, resourceWatcherService, configRefreshListener);
KeyManager[] keyManagers = sslConfiguration.keyConfig().keyManagers(env, resourceWatcherService, configRefreshListener);
TrustManager[] trustManagers = sslConfiguration.trustConfig().trustManagers(env);
KeyManager[] keyManagers = sslConfiguration.keyConfig().keyManagers(env);
SSLContext sslContext = createSslContext(keyManagers, trustManagers, sslConfiguration.protocol(),
sslConfiguration.sessionCacheSize(), sslConfiguration.sessionCacheTimeout());
// check the supported ciphers and log them here
supportedCiphers(sslContext.getSupportedSSLParameters().getCipherSuites(),
sslConfiguration.ciphers().toArray(Strings.EMPTY_ARRAY), true);
listener.onSSLContextLoaded(sslConfiguration);
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
*/
@ -305,4 +294,14 @@ public abstract class AbstractSSLService extends AbstractComponent {
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) -> {};
}
}

View File

@ -30,6 +30,7 @@ import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.SuppressForbidden;
@ -48,6 +49,7 @@ import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.security.auth.x500.X500Principal;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.Reader;
import java.math.BigInteger;
import java.net.InetAddress;
@ -84,7 +86,7 @@ class CertUtils {
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.load(null, null);
// password must be non-null for keystore...
@ -92,19 +94,19 @@ class CertUtils {
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);
kmf.init(keyStore, password);
KeyManager[] keyManagers = kmf.getKeyManagers();
for (KeyManager keyManager : keyManagers) {
if (keyManager instanceof X509ExtendedKeyManager) {
return new X509ExtendedKeyManager[] { (X509ExtendedKeyManager) keyManager };
return (X509ExtendedKeyManager) keyManager;
}
}
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");
store.load(null, null);
int counter = 0;
@ -115,13 +117,26 @@ class CertUtils {
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);
tmf.init(keyStore);
TrustManager[] trustManagers = tmf.getTrustManagers();
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509ExtendedTrustManager) {
return new X509ExtendedTrustManager[] { (X509ExtendedTrustManager) trustManager };
return (X509ExtendedTrustManager) trustManager ;
}
}
throw new IllegalStateException("failed to find a X509ExtendedTrustManager");

View File

@ -7,14 +7,12 @@ package org.elasticsearch.xpack.security.ssl;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
public class ClientSSLService extends AbstractSSLService {
public ClientSSLService(Settings settings, Environment env, Global globalSSLConfiguration,
ResourceWatcherService resourceWatcherService) {
super(settings, env, globalSSLConfiguration, resourceWatcherService);
public ClientSSLService(Settings settings, Environment env, Global globalSSLConfiguration) {
super(settings, env, globalSSLConfiguration);
}
@Override

View File

@ -5,19 +5,12 @@
*/
package org.elasticsearch.xpack.security.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
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.X509ExtendedKeyManager;
import javax.net.ssl.X509ExtendedTrustManager;
import java.io.IOException;
import java.net.Socket;
import java.nio.file.Path;
import java.security.Principal;
@ -28,18 +21,20 @@ import java.util.List;
abstract class KeyConfig extends TrustConfig {
KeyConfig(boolean includeSystem, boolean reloadEnabled) {
super(includeSystem, reloadEnabled);
private X509ExtendedKeyManager[] keyManagers = null;
KeyConfig(boolean includeSystem) {
super(includeSystem);
}
static final KeyConfig NONE = new KeyConfig(false, false) {
static final KeyConfig NONE = new KeyConfig(false) {
@Override
X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment) {
X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment) {
return null;
}
@Override
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
return null;
}
@ -58,39 +53,48 @@ abstract class KeyConfig extends TrustConfig {
}
};
final KeyManager[] keyManagers(@Nullable Environment environment, @Nullable ResourceWatcherService resourceWatcherService,
@Nullable Listener listener) {
X509ExtendedKeyManager[] keyManagers = loadKeyManagers(environment);
if (reloadEnabled && resourceWatcherService != null && listener != null) {
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);
}
}
final synchronized X509ExtendedKeyManager[] keyManagers(@Nullable Environment environment) {
if (keyManagers == null) {
X509ExtendedKeyManager keyManager = loadKeyManager(environment);
setKeyManagers(keyManager);
}
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;
ReloadableX509KeyManager(X509ExtendedKeyManager keyManager, @Nullable Environment environment) {
ReloadableX509KeyManager(X509ExtendedKeyManager keyManager) {
this.keyManager = keyManager;
this.environment = environment;
}
@Override
@ -133,13 +137,13 @@ abstract class KeyConfig extends TrustConfig {
return keyManager.chooseEngineServerAlias(s, principals, engine);
}
public synchronized void reload() {
X509ExtendedKeyManager[] keyManagers = loadKeyManagers(environment);
this.keyManager = keyManagers[0];
}
synchronized void setKeyManager(X509ExtendedKeyManager x509ExtendedKeyManager) {
this.keyManager = x509ExtendedKeyManager;
}
// pkg-private accessor for testing
X509ExtendedKeyManager getKeyManager() {
return keyManager;
}
}
}

View File

@ -28,23 +28,27 @@ class PEMKeyConfig extends KeyConfig {
final String keyPassword;
final List<String> certPaths;
PEMKeyConfig(boolean includeSystem, boolean reloadEnabled, String keyPath, String keyPassword, List<String> certPaths) {
super(includeSystem, reloadEnabled);
PEMKeyConfig(boolean includeSystem, String keyPath, String keyPassword, List<String> certPaths) {
super(includeSystem);
this.keyPath = keyPath;
this.keyPassword = keyPassword;
this.certPaths = certPaths;
}
@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 {
PrivateKey privateKey = readPrivateKey(CertUtils.resolvePath(keyPath, 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);
} catch (Exception 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
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
try {
Certificate[] certificates = CertUtils.readCertificates(certPaths, environment);
return CertUtils.trustManagers(certificates);

View File

@ -20,13 +20,13 @@ class PEMTrustConfig extends TrustConfig {
final List<String> caPaths;
PEMTrustConfig(boolean includeSystem, boolean reloadEnabled, List<String> caPaths) {
super(includeSystem, reloadEnabled);
PEMTrustConfig(boolean includeSystem, List<String> caPaths) {
super(includeSystem);
this.caPaths = caPaths;
}
@Override
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
try {
Certificate[] certificates = CertUtils.readCertificates(caPaths, environment);
return CertUtils.trustManagers(certificates);

View File

@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.ssl;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -14,10 +16,12 @@ import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
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.support.OptionalSettings.createInt;
@ -43,6 +47,42 @@ public abstract class SSLConfiguration {
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
public boolean equals(Object o) {
if (this == o) return true;
@ -258,14 +298,14 @@ public abstract class SSLConfiguration {
if (certPaths == null) {
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 {
assert keyStorePath != null;
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
return new StoreKeyConfig(includeSystem, reloadEnabled, keyStorePath, keyStorePassword, keyStoreKeyPassword,
return new StoreKeyConfig(includeSystem, keyStorePath, keyStorePassword, keyStoreKeyPassword,
keyStoreAlgorithm, trustStoreAlgorithm);
}
}
@ -274,19 +314,18 @@ public abstract class SSLConfiguration {
String trustStorePath = TRUSTSTORE_PATH_SETTING.get(settings).orElse(null);
List<String> caPaths = getListOrNull(CA_PATHS_SETTING, settings);
boolean includeSystem = INCLUDE_JDK_CERTS_SETTING.get(settings);
boolean reloadEnabled = RELOAD_ENABLED_SETTING.get(settings);
if (trustStorePath != null && caPaths != null) {
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
} else if (caPaths != null) {
return new PEMTrustConfig(includeSystem, reloadEnabled, caPaths);
return new PEMTrustConfig(includeSystem, caPaths);
} else if (trustStorePath != null) {
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
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) {
return keyInfo;
} 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) {
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 {
assert keyStorePath != null;
String keyStorePassword = KEYSTORE_PASSWORD_SETTING.get(settings).orElse(null);
String keyStoreAlgorithm = KEYSTORE_ALGORITHM_SETTING.get(settings);
String keyStoreKeyPassword = KEYSTORE_KEY_PASSWORD_SETTING.get(settings).orElse(keyStorePassword);
String trustStoreAlgorithm = TRUSTSTORE_ALGORITHM_SETTING.get(settings);
return new StoreKeyConfig(includeSystem, reloadEnabled, keyStorePath, keyStorePassword, keyStoreKeyPassword,
return new StoreKeyConfig(includeSystem, keyStorePath, keyStorePassword, keyStoreKeyPassword,
keyStoreAlgorithm, trustStoreAlgorithm);
}
}
@ -431,11 +470,11 @@ public abstract class SSLConfiguration {
if (trustStorePath != null && caPaths != null) {
throw new IllegalArgumentException("you cannot specify a truststore and ca files");
} 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) {
String trustStorePassword = TRUSTSTORE_PASSWORD_SETTING.get(settings).orElse(null);
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);
} else if (keyConfig == global.keyConfig()) {
return global.trustConfig();

View File

@ -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));
}
}
}
}
}

View File

@ -7,14 +7,12 @@ package org.elasticsearch.xpack.security.ssl;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.env.Environment;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
public class ServerSSLService extends AbstractSSLService {
public ServerSSLService(Settings settings, Environment environment, Global globalSSLConfiguration,
ResourceWatcherService resourceWatcherService) {
super(settings, environment, globalSSLConfiguration, resourceWatcherService);
public ServerSSLService(Settings settings, Environment environment, Global globalSSLConfiguration) {
super(settings, environment, globalSSLConfiguration);
}
@Override

View File

@ -26,9 +26,9 @@ class StoreKeyConfig extends KeyConfig {
final String keyPassword;
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) {
super(includeSystem, reloadEnabled);
super(includeSystem);
this.keyStorePath = keyStorePath;
this.keyStorePassword = keyStorePassword;
this.keyPassword = keyPassword;
@ -37,7 +37,7 @@ class StoreKeyConfig extends KeyConfig {
}
@Override
X509ExtendedKeyManager[] loadKeyManagers(@Nullable Environment environment) {
X509ExtendedKeyManager loadKeyManager(@Nullable Environment environment) {
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
// TODO remove reliance on JKS since we can PKCS12 stores...
KeyStore ks = KeyStore.getInstance("jks");
@ -50,14 +50,9 @@ class StoreKeyConfig extends KeyConfig {
}
@Override
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(keyStorePath, environment))) {
// TODO remove reliance on JKS since we can PKCS12 stores...
KeyStore ks = KeyStore.getInstance("jks");
assert keyStorePassword != null;
ks.load(in, keyStorePassword.toCharArray());
return CertUtils.trustManagers(ks, trustStoreAlgorithm);
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
try {
return CertUtils.trustManagers(keyStorePath, keyStorePassword, trustStoreAlgorithm, environment);
} catch (Exception e) {
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
}

View File

@ -23,25 +23,20 @@ class StoreTrustConfig extends TrustConfig {
final String trustStorePassword;
final String trustStoreAlgorithm;
StoreTrustConfig(boolean includeSystem, boolean reloadEnabled, String trustStorePath, String trustStorePassword,
String trustStoreAlgorithm) {
super(includeSystem, reloadEnabled);
StoreTrustConfig(boolean includeSystem, String trustStorePath, String trustStorePassword, String trustStoreAlgorithm) {
super(includeSystem);
this.trustStorePath = trustStorePath;
this.trustStorePassword = trustStorePassword;
this.trustStoreAlgorithm = trustStoreAlgorithm;
}
@Override
X509ExtendedTrustManager[] nonSystemTrustManagers(@Nullable Environment environment) {
X509ExtendedTrustManager nonSystemTrustManager(@Nullable Environment environment) {
if (trustStorePath == null) {
return null;
}
try (InputStream in = Files.newInputStream(CertUtils.resolvePath(trustStorePath, environment))) {
// 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);
try {
return CertUtils.trustManagers(trustStorePath, trustStorePassword, trustStoreAlgorithm, environment);
} catch (Exception e) {
throw new ElasticsearchException("failed to initialize a TrustManagerFactory", e);
}

View File

@ -8,60 +8,53 @@ package org.elasticsearch.xpack.security.ssl;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.Nullable;
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.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
abstract class TrustConfig {
protected final boolean includeSystem;
protected final boolean reloadEnabled;
TrustConfig(boolean includeSystem, boolean reloadEnabled) {
X509ExtendedTrustManager[] trustManagers = null;
TrustConfig(boolean includeSystem) {
this.includeSystem = includeSystem;
this.reloadEnabled = reloadEnabled;
}
final TrustManager[] trustManagers(@Nullable Environment environment, @Nullable ResourceWatcherService resourceWatcherService,
@Nullable Listener listener) {
X509ExtendedTrustManager[] trustManagers = loadAndMergeIfNecessary(environment);
if (reloadEnabled && resourceWatcherService != null && listener != null) {
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);
}
final synchronized X509ExtendedTrustManager[] trustManagers(@Nullable Environment environment) {
if (trustManagers == null) {
X509ExtendedTrustManager loadedTrustManager = loadAndMergeIfNecessary(environment);
setTrustManagers(loadedTrustManager);
}
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();
@ -69,56 +62,47 @@ abstract class TrustConfig {
public abstract String toString();
private X509ExtendedTrustManager[] loadAndMergeIfNecessary(@Nullable Environment environment) {
X509ExtendedTrustManager[] nonSystemTrustManagers = nonSystemTrustManagers(environment);
final X509ExtendedTrustManager loadAndMergeIfNecessary(@Nullable Environment environment) {
X509ExtendedTrustManager trustManager = nonSystemTrustManager(environment);
if (includeSystem) {
return mergeWithSystem(nonSystemTrustManagers);
} else if (nonSystemTrustManagers == null || nonSystemTrustManagers.length == 0) {
return new X509ExtendedTrustManager[0];
trustManager = mergeWithSystem(trustManager);
} else if (trustManager == null) {
return null;
}
return nonSystemTrustManagers;
return trustManager;
}
private X509ExtendedTrustManager[] mergeWithSystem(X509ExtendedTrustManager[] nonSystemTrustManagers) {
private X509ExtendedTrustManager mergeWithSystem(X509ExtendedTrustManager nonSystemTrustManager) {
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init((KeyStore) null);
TrustManager[] systemTrustManagers = tmf.getTrustManagers();
X509ExtendedTrustManager system = findFirstX509TrustManager(systemTrustManagers);
if (nonSystemTrustManagers == null || nonSystemTrustManagers.length == 0) {
return new X509ExtendedTrustManager[] { system };
X509ExtendedTrustManager system = findFirstX509ExtendedTrustManager(systemTrustManagers);
if (nonSystemTrustManager == null) {
return system;
}
return new X509ExtendedTrustManager[] { new CombiningX509TrustManager(nonSystemTrustManagers[0], system) };
return new CombiningX509TrustManager(nonSystemTrustManager, system);
} catch (Exception 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;
for (TrustManager trustManager : trustManagers) {
if (trustManager instanceof X509TrustManager) {
if (trustManager instanceof X509ExtendedTrustManager) {
// first one wins like in the JDK
x509TrustManager = (X509ExtendedTrustManager) trustManager;
break;
}
}
if (x509TrustManager == null) {
throw new IllegalArgumentException("did not find a X509TrustManager");
throw new IllegalArgumentException("did not find a X509ExtendedTrustManager");
}
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 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;
ReloadableTrustManager(X509ExtendedTrustManager trustManager, @Nullable Environment environment) {
ReloadableTrustManager(X509ExtendedTrustManager trustManager) {
this.trustManager = trustManager;
this.environment = environment;
}
@Override
@ -241,59 +223,12 @@ abstract class TrustConfig {
return trustManager.getAcceptedIssuers();
}
public synchronized void reload() {
X509ExtendedTrustManager[] array = loadAndMergeIfNecessary(environment);
this.trustManager = array[0];
}
synchronized void setTrustManager(X509ExtendedTrustManager trustManager) {
this.trustManager = trustManager;
}
}
interface Reloadable {
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;
}
}
X509ExtendedTrustManager getTrustManager() {
return trustManager;
}
}
}

View File

@ -43,7 +43,7 @@ public class AbstractActiveDirectoryIntegTests extends ESTestCase {
}
globalSettings = builder.build();
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,

View File

@ -40,7 +40,7 @@ public abstract class GroupsResolverTestCase extends ESTestCase {
}
Settings settings = builder.build();
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());
LDAPConnectionOptions options = new LDAPConnectionOptions();

View File

@ -62,7 +62,7 @@ public class LdapUserSearchSessionFactoryTests extends LdapTestCase {
.put("xpack.security.ssl.keystore.path", keystore)
.put("xpack.security.ssl.keystore.password", "changeit")
.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();
}

View File

@ -52,7 +52,7 @@ public class OpenLdapTests extends ESTestCase {
}
globalSettings = builder.build();
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 {

View File

@ -59,7 +59,7 @@ public class ClientSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.truststore.path", testclientStore)
.put("xpack.security.ssl.truststore.password", "testclient")
.build();
ClientSSLService clientSSLService = new ClientSSLService(settings, null, new Global(settings), null);
ClientSSLService clientSSLService = new ClientSSLService(settings, null, new Global(settings));
clientSSLService.createSSLEngine();
fail("expected an exception");
} catch (ElasticsearchException e) {
@ -284,7 +284,6 @@ public class ClientSSLServiceTests extends ESTestCase {
}
private ClientSSLService createClientSSLService(Settings settings) {
ClientSSLService clientSSLService = new ClientSSLService(settings, env, new Global(settings), null);
return clientSSLService;
return new ClientSSLService(settings, env, new Global(settings));
}
}

View File

@ -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);
}
}
}

View File

@ -5,45 +5,18 @@
*/
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.unit.TimeValue;
import org.elasticsearch.env.Environment;
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Custom;
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.threadpool.TestThreadPool;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
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.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.instanceOf;
import static org.hamcrest.Matchers.is;
@ -273,10 +246,10 @@ public class SSLConfigurationTests extends ESTestCase {
SSLConfiguration config = new Global(settings);
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
KeyManager[] keyManagers = keyConfig.keyManagers(env, null, null);
KeyManager[] keyManagers = keyConfig.keyManagers(env);
assertThat(keyManagers.length, is(1));
assertThat(config.trustConfig(), sameInstance(keyConfig));
TrustManager[] trustManagers = keyConfig.trustManagers(env, null, null);
TrustManager[] trustManagers = keyConfig.trustManagers(env);
assertThat(trustManagers.length, is(1));
}
@ -296,464 +269,11 @@ public class SSLConfigurationTests extends ESTestCase {
SSLConfiguration config = new Global(settings);
assertThat(config.keyConfig(), instanceOf(PEMKeyConfig.class));
PEMKeyConfig keyConfig = (PEMKeyConfig) config.keyConfig();
KeyManager[] keyManagers = keyConfig.keyManagers(env, null, null);
KeyManager[] keyManagers = keyConfig.keyManagers(env);
assertThat(keyManagers.length, is(1));
assertThat(config.trustConfig(), not(sameInstance(keyConfig)));
assertThat(config.trustConfig(), instanceOf(PEMTrustConfig.class));
TrustManager[] trustManagers = keyConfig.trustManagers(env, null, null);
TrustManager[] trustManagers = keyConfig.trustManagers(env);
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);
}
}
}

View File

@ -53,7 +53,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.truststore.password", "testnode")
.build();
try {
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
fail("expected an exception");
} catch (ElasticsearchException e) {
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.password", "testnode")
.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()
.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.password", "testnode")
.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 cachedSslContext = sslService.sslContext();
@ -101,7 +101,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.put("xpack.security.ssl.keystore.key_password", "testnode1")
.build();
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
}
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.password", "testnode")
.build();
new ServerSSLService(settings, env, new Global(settings), null).createSSLEngine();
new ServerSSLService(settings, env, new Global(settings)).createSSLEngine();
fail("expected an exception");
} catch (ElasticsearchException e) {
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.password", "testnode")
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
SSLEngine engine = sslService.createSSLEngine();
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.password", "testnode")
.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();
assertThat(context.getSessionCacheSize(), equalTo(1000));
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_timeout", "600s")
.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();
assertThat(context.getSessionCacheSize(), equalTo(300));
assertThat(context.getSessionTimeout(), equalTo(600));
}
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 {
sslService.createSSLEngine();
fail("Expected IllegalArgumentException");
@ -168,7 +168,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.truststore.path", testnodeStore)
.put("xpack.security.ssl.truststore.password", "testnode")
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
try {
sslService.createSSLEngine();
fail("Expected IllegalArgumentException");
@ -183,7 +183,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.put("xpack.security.ssl.truststore.path", testnodeStore)
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
try {
sslService.sslContext();
fail("Expected IllegalArgumentException");
@ -196,7 +196,7 @@ public class ServerSSLServiceTests extends ESTestCase {
Settings settings = Settings.builder()
.put("xpack.security.ssl.keystore.path", testnodeStore)
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
try {
sslService.sslContext();
fail("Expected IllegalArgumentException");
@ -214,7 +214,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.putArray("xpack.security.ssl.ciphers", ciphers.toArray(new String[ciphers.size()]))
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
SSLEngine engine = sslService.createSSLEngine();
assertThat(engine, is(notNullValue()));
String[] enabledCiphers = engine.getEnabledCipherSuites();
@ -227,7 +227,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.putArray("xpack.security.ssl.ciphers", new String[] { "foo", "bar" })
.build();
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings), null);
ServerSSLService sslService = new ServerSSLService(settings, env, new Global(settings));
try {
sslService.createSSLEngine();
fail("Expected IllegalArgumentException");
@ -241,7 +241,7 @@ public class ServerSSLServiceTests extends ESTestCase {
.put("xpack.security.ssl.keystore.path", testnodeStore)
.put("xpack.security.ssl.keystore.password", "testnode")
.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);
final String[] ciphers = sslService.supportedCiphers(factory.getSupportedCipherSuites(), sslService.ciphers(), false);
assertThat(factory.getDefaultCipherSuites(), is(ciphers));

View File

@ -76,7 +76,7 @@ public class HandshakeWaitingHandlerTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.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();

View File

@ -43,7 +43,7 @@ public class SecurityNetty3HttpServerTransportTests extends ESTestCase {
.put("xpack.security.ssl.keystore.password", "testnode")
.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 {

View File

@ -43,8 +43,8 @@ public class SecurityNetty3TransportTests extends ESTestCase {
.build();
Environment env = new Environment(Settings.builder().put("path.home", createTempDir()).build());
Global globalSSLConfiguration = new Global(settings);
serverSSLService = new ServerSSLService(settings, env, globalSSLConfiguration, null);
clientSSLService = new ClientSSLService(settings, env, globalSSLConfiguration, null);
serverSSLService = new ServerSSLService(settings, env, globalSSLConfiguration);
clientSSLService = new ClientSSLService(settings, env, globalSSLConfiguration);
}
public void testThatSSLCanBeDisabledByProfile() throws Exception {

View File

@ -74,7 +74,7 @@ public class SslClientAuthTests extends SecurityIntegTestCase {
Settings settings = Settings.builder()
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
.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);
try (RestClient restClient = createRestClient(httpClientBuilder -> httpClientBuilder.setSSLStrategy(sessionStrategy), "https")) {
Response response = restClient.performRequest("GET", "/",

View File

@ -99,7 +99,7 @@ public class SslIntegrationTests extends SecurityIntegTestCase {
Settings settings = Settings.builder()
.put(getSSLSettingsForStore("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.jks", "testclient"))
.build();
ClientSSLService service = new ClientSSLService(settings, null, new Global(settings), null);
ClientSSLService service = new ClientSSLService(settings, null, new Global(settings));
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(nodeClientUsername(),