security: move reloading of ssl configuration to its own class
This commit moves the reloading and monitoring of files from the trust/key configuration classes into a separate class that will reload for the whole SSLConfiguration object. SSLContexts are loaded lazily by most of security, so a listener interface was added to notify the reloader that there may be other paths to monitor. Original commit: elastic/x-pack-elasticsearch@1633cc14a7
This commit is contained in:
parent
62353ff8bc
commit
c82f1be386
|
@ -111,6 +111,7 @@ import org.elasticsearch.xpack.security.rest.action.user.RestGetUsersAction;
|
|||
import org.elasticsearch.xpack.security.rest.action.user.RestPutUserAction;
|
||||
import org.elasticsearch.xpack.security.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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) -> {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.ssl;
|
||||
|
||||
import org.elasticsearch.common.component.AbstractComponent;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.watcher.FileChangesListener;
|
||||
import org.elasticsearch.watcher.FileWatcher;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService.Frequency;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSessionContext;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
/**
|
||||
* Ensures that the files backing an {@link SSLConfiguration} are monitored for changes and the underlying key/trust material is reloaded
|
||||
* and the {@link SSLContext} has existing sessions invalidated to force the use of the new key/trust material
|
||||
*/
|
||||
public class SSLConfigurationReloader extends AbstractComponent implements AbstractSSLService.Listener {
|
||||
|
||||
private final ConcurrentHashMap<Path, ChangeListener> pathToChangeListenerMap = new ConcurrentHashMap<>();
|
||||
private final Environment environment;
|
||||
private final ResourceWatcherService resourceWatcherService;
|
||||
private final ServerSSLService serverSSLService;
|
||||
private final ClientSSLService clientSSLService;
|
||||
|
||||
public SSLConfigurationReloader(Settings settings, Environment env, ServerSSLService serverSSLService,
|
||||
ClientSSLService clientSSLService, ResourceWatcherService resourceWatcher) {
|
||||
super(settings);
|
||||
this.environment = env;
|
||||
this.resourceWatcherService = resourceWatcher;
|
||||
this.serverSSLService = serverSSLService;
|
||||
this.clientSSLService = clientSSLService;
|
||||
serverSSLService.setListener(this);
|
||||
clientSSLService.setListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSSLContextLoaded(SSLConfiguration sslConfiguration) {
|
||||
startWatching(Collections.singleton(sslConfiguration));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all of the directories that need to be monitored for the provided {@link SSLConfiguration} instances and ensures that
|
||||
* they are being watched for changes
|
||||
*/
|
||||
private void startWatching(Collection<SSLConfiguration> sslConfigurations) {
|
||||
for (SSLConfiguration sslConfiguration : sslConfigurations) {
|
||||
for (Path directory : directoriesToMonitor(sslConfiguration.filesToMonitor(environment))) {
|
||||
pathToChangeListenerMap.compute(directory, (path, listener) -> {
|
||||
if (listener != null) {
|
||||
listener.addSSLConfiguration(sslConfiguration);
|
||||
return listener;
|
||||
}
|
||||
|
||||
ChangeListener changeListener = new ChangeListener();
|
||||
changeListener.addSSLConfiguration(sslConfiguration);
|
||||
FileWatcher fileWatcher = new FileWatcher(path);
|
||||
fileWatcher.addListener(changeListener);
|
||||
try {
|
||||
resourceWatcherService.add(fileWatcher, Frequency.HIGH);
|
||||
return changeListener;
|
||||
} catch (IOException e) {
|
||||
logger.error("failed to start watching directory [{}] for ssl configuration [{}]", path, sslConfiguration);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates all of the sessions in the provided {@link SSLContext}
|
||||
*/
|
||||
private static void invalidateAllSessions(SSLContext context) {
|
||||
if (context != null) {
|
||||
invalidateSessions(context.getClientSessionContext());
|
||||
invalidateSessions(context.getServerSessionContext());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the sessions in the provided {@link SSLSessionContext}
|
||||
*/
|
||||
private static void invalidateSessions(SSLSessionContext sslSessionContext) {
|
||||
Enumeration<byte[]> sessionIds = sslSessionContext.getIds();
|
||||
while (sessionIds.hasMoreElements()) {
|
||||
byte[] sessionId = sessionIds.nextElement();
|
||||
sslSessionContext.getSession(sessionId).invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a unique set of directories that need to be monitored based on the provided file paths
|
||||
*/
|
||||
private static Set<Path> directoriesToMonitor(List<Path> filePaths) {
|
||||
Set<Path> paths = new HashSet<>();
|
||||
for (Path path : filePaths) {
|
||||
paths.add(path.getParent());
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
private class ChangeListener implements FileChangesListener {
|
||||
|
||||
private final CopyOnWriteArraySet<SSLConfiguration> sslConfigurations = new CopyOnWriteArraySet<>();
|
||||
|
||||
/**
|
||||
* Adds the given ssl configuration to those that have files within the directory watched by this change listener
|
||||
*/
|
||||
private void addSSLConfiguration(SSLConfiguration sslConfiguration) {
|
||||
sslConfigurations.add(sslConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileCreated(Path file) {
|
||||
onFileChanged(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileDeleted(Path file) {
|
||||
onFileChanged(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFileChanged(Path file) {
|
||||
for (SSLConfiguration sslConfiguration : sslConfigurations) {
|
||||
if (sslConfiguration.filesToMonitor(environment).contains(file)) {
|
||||
sslConfiguration.reload(file, environment);
|
||||
invalidateAllSessions(serverSSLService.getSSLContext(sslConfiguration));
|
||||
invalidateAllSessions(clientSSLService.getSSLContext(sslConfiguration));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,14 +7,12 @@ package org.elasticsearch.xpack.security.ssl;
|
|||
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,652 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
package org.elasticsearch.xpack.security.ssl;
|
||||
|
||||
import org.apache.lucene.util.SetOnce;
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
||||
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.TestThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
import org.elasticsearch.xpack.security.ssl.KeyConfig.ReloadableX509KeyManager;
|
||||
import org.elasticsearch.xpack.security.ssl.SSLConfiguration.Global;
|
||||
import org.elasticsearch.xpack.security.ssl.TrustConfig.ReloadableTrustManager;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.X509ExtendedKeyManager;
|
||||
import javax.net.ssl.X509ExtendedTrustManager;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.hamcrest.core.Is.is;
|
||||
|
||||
/**
|
||||
* Unit tests for the reloading of SSL configuration
|
||||
*/
|
||||
public class SSLConfigurationReloaderTests extends ESTestCase {
|
||||
|
||||
private ThreadPool threadPool;
|
||||
private ResourceWatcherService resourceWatcherService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
threadPool = new TestThreadPool("reload tests");
|
||||
resourceWatcherService =
|
||||
new ResourceWatcherService(Settings.builder().put("resource.reload.interval.high", "1s").build(), threadPool);
|
||||
resourceWatcherService.start();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() throws Exception {
|
||||
if (threadPool != null) {
|
||||
terminate(threadPool);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reloading a keystore. The contents of the keystore is used for both keystore and truststore material, so both key
|
||||
* config and trust config is checked.
|
||||
*/
|
||||
public void testReloadingKeyStore() throws Exception {
|
||||
final Path tempDir = createTempDir();
|
||||
final Path keystorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
||||
final Settings settings = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.security.ssl.keystore.path", keystorePath)
|
||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||
.build();
|
||||
final Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
|
||||
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks = (keyManager, config) -> {
|
||||
// key manager checks
|
||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||
assertNotNull(aliases);
|
||||
assertThat(aliases.length, is(1));
|
||||
assertThat(aliases[0], is("testnode"));
|
||||
return null;
|
||||
};
|
||||
|
||||
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||
// trust manager checks
|
||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||
trustedCount.set(certificates.length);
|
||||
return null;
|
||||
};
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
// modify it
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
Path updated = tempDir.resolve("updated.jks");
|
||||
try (OutputStream out = Files.newOutputStream(updated)) {
|
||||
keyStore.store(out, "testnode".toCharArray());
|
||||
}
|
||||
atomicMoveIfPossible(updated, keystorePath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("modification failed", e);
|
||||
}
|
||||
};
|
||||
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
|
||||
return null;
|
||||
};
|
||||
validateSSLConfigurationIsReloaded(settings, env, keyManagerPreChecks, trustManagerPreChecks, modifier, (k, c) -> null,
|
||||
trustManagerPostChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of a PEM key config when the key is overwritten. The trust portion is not tested as it is not modified by this
|
||||
* test.
|
||||
*/
|
||||
public void testPEMKeyConfigReloading() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path keyPath = tempDir.resolve("testnode.pem");
|
||||
Path certPath = tempDir.resolve("testnode.crt");
|
||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||
final Settings settings = Settings.builder()
|
||||
.put("path.home", createTempDir())
|
||||
.put("xpack.security.ssl.key.path", keyPath)
|
||||
.put("xpack.security.ssl.key.password", "testnode")
|
||||
.put("xpack.security.ssl.cert", certPath)
|
||||
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
||||
.build();
|
||||
final Environment env = randomBoolean() ? null :
|
||||
new Environment(Settings.builder().put("path.home", createTempDir()).build());
|
||||
|
||||
final SetOnce<PrivateKey> privateKey = new SetOnce<>();
|
||||
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks = (keyManager, config) -> {
|
||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||
assertNotNull(aliases);
|
||||
assertThat(aliases.length, is(1));
|
||||
assertThat(aliases[0], is("key"));
|
||||
privateKey.set(keyManager.getPrivateKey("key"));
|
||||
assertNotNull(privateKey.get());
|
||||
return null;
|
||||
};
|
||||
|
||||
final KeyPair keyPair = CertUtils.generateKeyPair(randomFrom(1024, 2048));
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
// make sure we wait long enough to see a change. if time is within a second the file may not be seen as modified since the
|
||||
// size is the same!
|
||||
assertTrue(awaitBusy(() -> {
|
||||
try {
|
||||
BasicFileAttributes attributes = Files.readAttributes(keyPath, BasicFileAttributes.class);
|
||||
return System.currentTimeMillis() - attributes.lastModifiedTime().toMillis() >= 1000L;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("io exception while checking time", e);
|
||||
}
|
||||
}));
|
||||
Path updatedKeyPath = tempDir.resolve("updated.pem");
|
||||
try (OutputStream os = Files.newOutputStream(updatedKeyPath);
|
||||
OutputStreamWriter osWriter = new OutputStreamWriter(os, StandardCharsets.UTF_8);
|
||||
JcaPEMWriter writer = new JcaPEMWriter(osWriter)) {
|
||||
writer.writeObject(keyPair,
|
||||
new JcePEMEncryptorBuilder("DES-EDE3-CBC").setProvider(CertUtils.BC_PROV).build("testnode".toCharArray()));
|
||||
}
|
||||
atomicMoveIfPossible(updatedKeyPath, keyPath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
};
|
||||
|
||||
final BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks = (keyManager, config) -> {
|
||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||
assertNotNull(aliases);
|
||||
assertThat(aliases.length, is(1));
|
||||
assertThat(aliases[0], is("key"));
|
||||
assertThat(keyManager.getPrivateKey(aliases[0]), not(equalTo(privateKey)));
|
||||
assertThat(keyManager.getPrivateKey(aliases[0]), is(equalTo(keyPair.getPrivate())));
|
||||
return null;
|
||||
};
|
||||
validateKeyConfigurationIsReloaded(settings, env, keyManagerPreChecks, modifier, keyManagerPostChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of the trust config when the trust store is modified. The key config is not tested as part of this test.
|
||||
*/
|
||||
public void testReloadingTrustStore() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path trustStorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
||||
Settings settings = Settings.builder()
|
||||
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
|
||||
final SetOnce<Integer> trustedCount = new SetOnce<>();
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||
// trust manager checks
|
||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||
trustedCount.set(certificates.length);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
Path updatedTruststore = tempDir.resolve("updated.jks");
|
||||
KeyStore keyStore = KeyStore.getInstance("jks");
|
||||
keyStore.load(null, null);
|
||||
try (OutputStream out = Files.newOutputStream(updatedTruststore)) {
|
||||
keyStore.store(out, "testnode".toCharArray());
|
||||
}
|
||||
atomicMoveIfPossible(updatedTruststore, trustStorePath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
};
|
||||
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||
assertThat(trustedCount.get() - updatedTrustManager.getAcceptedIssuers().length, is(5));
|
||||
return null;
|
||||
};
|
||||
|
||||
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the reloading of a trust config that is backed by PEM certificate files. The key config is not tested as we only care about the
|
||||
* trust config in this test.
|
||||
*/
|
||||
public void testReloadingPEMTrustConfig() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||
Settings settings = Settings.builder()
|
||||
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.put(Global.INCLUDE_JDK_CERTS_SETTING.getKey(), false)
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks = (trustManager, config) -> {
|
||||
// trust manager checks
|
||||
Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||
assertThat(certificates.length, is(1));
|
||||
assertThat(((X509Certificate)certificates[0]).getSubjectX500Principal().getName(), containsString("Test Client"));
|
||||
return null;
|
||||
};
|
||||
|
||||
final Runnable modifier = () -> {
|
||||
try {
|
||||
Path updatedCert = tempDir.resolve("updated.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), updatedCert,
|
||||
StandardCopyOption.REPLACE_EXISTING);
|
||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("failed to modify file", e);
|
||||
}
|
||||
};
|
||||
|
||||
final BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks = (updatedTrustManager, config) -> {
|
||||
Certificate[] updatedCerts = updatedTrustManager.getAcceptedIssuers();
|
||||
assertThat(updatedCerts.length, is(1));
|
||||
assertThat(((X509Certificate)updatedCerts[0]).getSubjectX500Principal().getName(), containsString("Test Node"));
|
||||
return null;
|
||||
};
|
||||
|
||||
validateTrustConfigurationIsReloaded(settings, env, trustManagerPreChecks, modifier, trustManagerPostChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of a keystore when there is an exception during reloading. An exception is caused by truncating the keystore
|
||||
* that is being monitored
|
||||
*/
|
||||
public void testReloadingKeyStoreException() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path keystorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), keystorePath);
|
||||
Settings settings = Settings.builder()
|
||||
.put("xpack.security.ssl.keystore.path", keystorePath)
|
||||
.put("xpack.security.ssl.keystore.password", "testnode")
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
final Global config = new Global(settings);
|
||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [keystore reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [keystore reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final SSLConfigurationReloader reloader =
|
||||
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||
reloader.onSSLContextLoaded(config);
|
||||
|
||||
// key manager checks
|
||||
X509ExtendedKeyManager keyManager = ((ReloadableX509KeyManager)config.keyConfig().keyManagers(env)[0]).getKeyManager();
|
||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||
assertNotNull(aliases);
|
||||
assertThat(aliases.length, is(1));
|
||||
assertThat(aliases[0], is("testnode"));
|
||||
PrivateKey privateKey = keyManager.getPrivateKey("testnode");
|
||||
|
||||
// truncate the keystore
|
||||
try (OutputStream out = Files.newOutputStream(keystorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
}
|
||||
|
||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
||||
assertThat(keyManager.getPrivateKey("testnode"), is(equalTo(privateKey)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of a key config backed by pem files when there is an exception during reloading. An exception is caused by
|
||||
* truncating the key file that is being monitored
|
||||
*/
|
||||
public void testReloadingPEMKeyConfigException() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path keyPath = tempDir.resolve("testnode.pem");
|
||||
Path certPath = tempDir.resolve("testnode.crt");
|
||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.pem"), keyPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.crt"), certPath);
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||
Settings settings = Settings.builder()
|
||||
.put("xpack.security.ssl.key.path", keyPath)
|
||||
.put("xpack.security.ssl.key.password", "testnode")
|
||||
.put("xpack.security.ssl.cert", certPath)
|
||||
.putArray("xpack.security.ssl.ca", certPath.toString(), clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
final Global config = new Global(settings);
|
||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [pem key reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [pem key reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final SSLConfigurationReloader reloader =
|
||||
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||
reloader.onSSLContextLoaded(config);
|
||||
|
||||
X509ExtendedKeyManager keyManager = ((ReloadableX509KeyManager)config.keyConfig().keyManagers(env)[0]).getKeyManager();
|
||||
String[] aliases = keyManager.getServerAliases("RSA", null);
|
||||
assertNotNull(aliases);
|
||||
assertThat(aliases.length, is(1));
|
||||
assertThat(aliases[0], is("key"));
|
||||
PrivateKey privateKey = keyManager.getPrivateKey("key");
|
||||
|
||||
// truncate the file
|
||||
try (OutputStream os = Files.newOutputStream(keyPath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
}
|
||||
|
||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||
assertThat(keyManager.getServerAliases("RSA", null), equalTo(aliases));
|
||||
assertThat(keyManager.getPrivateKey("key"), is(equalTo(privateKey)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of a truststore when there is an exception during reloading. An exception is caused by truncating the truststore
|
||||
* that is being monitored
|
||||
*/
|
||||
public void testTrustStoreReloadException() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path trustStorePath = tempDir.resolve("testnode.jks");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testnode.jks"), trustStorePath);
|
||||
Settings settings = Settings.builder()
|
||||
.put("xpack.security.ssl.truststore.path", trustStorePath)
|
||||
.put("xpack.security.ssl.truststore.password", "testnode")
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
final Global config = new Global(settings);
|
||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [truststore reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [truststore reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final SSLConfigurationReloader reloader =
|
||||
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||
reloader.onSSLContextLoaded(config);
|
||||
|
||||
X509ExtendedTrustManager trustManager = ((ReloadableTrustManager)config.trustConfig().trustManagers(env)[0]).getTrustManager();
|
||||
final Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||
assertContainsCertificateWithMatchingName(certificates, containsString("Test Node"));
|
||||
|
||||
// truncate the truststore
|
||||
try (OutputStream os = Files.newOutputStream(trustStorePath, StandardOpenOption.TRUNCATE_EXISTING)) {
|
||||
}
|
||||
|
||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
||||
assertContainsCertificateWithMatchingName(trustManager.getAcceptedIssuers(), containsString("Test Node"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the reloading of a trust config backed by pem files when there is an exception during reloading. An exception is caused by
|
||||
* truncating the certificate file that is being monitored
|
||||
*/
|
||||
public void testPEMTrustReloadException() throws Exception {
|
||||
Path tempDir = createTempDir();
|
||||
Path clientCertPath = tempDir.resolve("testclient.crt");
|
||||
Files.copy(getDataPath("/org/elasticsearch/xpack/security/transport/ssl/certs/simple/testclient.crt"), clientCertPath);
|
||||
Settings settings = Settings.builder()
|
||||
.putArray("xpack.security.ssl.ca", clientCertPath.toString())
|
||||
.put("path.home", createTempDir())
|
||||
.build();
|
||||
Environment env = randomBoolean() ? null : new Environment(settings);
|
||||
final Global config = new Global(settings);
|
||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [pem trust reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration configuration) {
|
||||
fail("get should not be called! [pem trust reload exception]");
|
||||
return super.getSSLContext(configuration);
|
||||
}
|
||||
};
|
||||
final SSLConfigurationReloader reloader =
|
||||
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||
reloader.onSSLContextLoaded(config);
|
||||
|
||||
X509ExtendedTrustManager trustManager = ((ReloadableTrustManager)config.trustConfig().trustManagers(env)[0]).getTrustManager();
|
||||
final Certificate[] certificates = trustManager.getAcceptedIssuers();
|
||||
assertContainsCertificateWithMatchingName(certificates, containsString("Test Client"));
|
||||
|
||||
// write bad file
|
||||
Path updatedCert = tempDir.resolve("updated.crt");
|
||||
try (OutputStream os = Files.newOutputStream(updatedCert)) {
|
||||
os.write(randomByte());
|
||||
}
|
||||
atomicMoveIfPossible(updatedCert, clientCertPath);
|
||||
|
||||
// we intentionally don't wait here as we rely on concurrency to catch a failure
|
||||
assertThat(trustManager.getAcceptedIssuers(), equalTo(certificates));
|
||||
assertContainsCertificateWithMatchingName(trustManager.getAcceptedIssuers(), containsString("Test Client"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
||||
*/
|
||||
private void validateTrustConfigurationIsReloaded(Settings settings, Environment env,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||
Runnable modificationFunction,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||
throws Exception {
|
||||
validateSSLConfigurationIsReloaded(settings, env, false, true, null, trustManagerPreChecks, modificationFunction, null,
|
||||
trustManagerPostChecks);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the trust configuration aspect of the SSLConfiguration is reloaded
|
||||
*/
|
||||
private void validateKeyConfigurationIsReloaded(Settings settings, Environment env,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||
Runnable modificationFunction,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks)
|
||||
throws Exception {
|
||||
validateSSLConfigurationIsReloaded(settings, env, true, false, keyManagerPreChecks, null, modificationFunction,
|
||||
keyManagerPostChecks, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that both the key and trust configuration aspects of the SSLConfiguration are reloaded
|
||||
*/
|
||||
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||
Runnable modificationFunction,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||
throws Exception {
|
||||
validateSSLConfigurationIsReloaded(settings, env, true, true, keyManagerPreChecks, trustManagerPreChecks, modificationFunction,
|
||||
keyManagerPostChecks, trustManagerPostChecks);
|
||||
}
|
||||
|
||||
private void validateSSLConfigurationIsReloaded(Settings settings, Environment env, boolean checkKeys, boolean checkTrust,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPreChecks,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPreChecks,
|
||||
Runnable modificationFunction,
|
||||
BiFunction<X509ExtendedKeyManager, SSLConfiguration, Void> keyManagerPostChecks,
|
||||
BiFunction<X509ExtendedTrustManager, SSLConfiguration, Void> trustManagerPostChecks)
|
||||
throws Exception {
|
||||
|
||||
final AtomicInteger serverCounter = new AtomicInteger(0);
|
||||
final AtomicInteger clientCounter = new AtomicInteger(0);
|
||||
final Global config = new Global(settings);
|
||||
final ServerSSLService serverSSLService = new ServerSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration sslConfiguration) {
|
||||
serverCounter.incrementAndGet();
|
||||
return super.getSSLContext(sslConfiguration);
|
||||
}
|
||||
};
|
||||
final ClientSSLService clientSSLService = new ClientSSLService(settings, env, config) {
|
||||
@Override
|
||||
SSLContext getSSLContext(SSLConfiguration sslConfiguration) {
|
||||
clientCounter.incrementAndGet();
|
||||
return super.getSSLContext(sslConfiguration);
|
||||
}
|
||||
};
|
||||
|
||||
final SSLConfigurationReloader reloader =
|
||||
new SSLConfigurationReloader(settings, env, serverSSLService, clientSSLService, resourceWatcherService);
|
||||
|
||||
final X509ExtendedKeyManager keyManager;
|
||||
final X509ExtendedKeyManager[] originalKeyManagers;
|
||||
if (checkKeys) {
|
||||
originalKeyManagers = config.keyConfig().keyManagers(env);
|
||||
assertThat(originalKeyManagers.length, is(1));
|
||||
keyManager = ((ReloadableX509KeyManager) originalKeyManagers[0]).getKeyManager();
|
||||
} else {
|
||||
originalKeyManagers = null;
|
||||
keyManager = null;
|
||||
}
|
||||
|
||||
final X509ExtendedTrustManager[] originalTrustManagers;
|
||||
final X509ExtendedTrustManager trustManager;
|
||||
if (checkTrust) {
|
||||
originalTrustManagers = config.trustConfig().trustManagers(env);
|
||||
assertThat(originalTrustManagers.length, is(1));
|
||||
trustManager = ((ReloadableTrustManager) originalTrustManagers[0]).getTrustManager();
|
||||
} else {
|
||||
originalTrustManagers = null;
|
||||
trustManager = null;
|
||||
}
|
||||
|
||||
// register configuration with reloader
|
||||
reloader.onSSLContextLoaded(config);
|
||||
|
||||
// key manager checks
|
||||
if (checkKeys) {
|
||||
assertKeyManagersSame(keyManager, config.keyConfig().keyManagers(env));
|
||||
keyManagerPreChecks.apply(keyManager, config);
|
||||
}
|
||||
|
||||
// trust manager checks
|
||||
if (checkTrust) {
|
||||
assertTrustManagersSame(trustManager, config.trustConfig().trustManagers(env));
|
||||
trustManagerPreChecks.apply(trustManager, config);
|
||||
}
|
||||
|
||||
assertEquals(0, clientSSLService.getLoadedSSLConfigurations().size());
|
||||
assertEquals(0, serverSSLService.getLoadedSSLConfigurations().size());
|
||||
assertEquals("nothing should have called get", 0, clientCounter.get());
|
||||
assertEquals("nothing should have called get", 0, serverCounter.get());
|
||||
|
||||
// modify
|
||||
modificationFunction.run();
|
||||
assertTrue(awaitBusy(() -> clientCounter.get() > 0 && serverCounter.get() > 0));
|
||||
|
||||
// check key manager
|
||||
if (checkKeys) {
|
||||
final X509ExtendedKeyManager[] updatedKeyManagers = config.keyConfig().keyManagers(env);
|
||||
assertThat(updatedKeyManagers, sameInstance(originalKeyManagers));
|
||||
final X509ExtendedKeyManager updatedKeyManager = ((ReloadableX509KeyManager) updatedKeyManagers[0]).getKeyManager();
|
||||
keyManagerPostChecks.apply(updatedKeyManager, config);
|
||||
}
|
||||
|
||||
// check trust manager
|
||||
if (checkTrust) {
|
||||
final X509ExtendedTrustManager[] updatedTrustManagers = config.trustConfig().trustManagers(env);
|
||||
assertThat(updatedTrustManagers, sameInstance(originalTrustManagers));
|
||||
final X509ExtendedTrustManager updatedTrustManager = ((ReloadableTrustManager) updatedTrustManagers[0]).getTrustManager();
|
||||
assertThat(updatedTrustManager, not(sameInstance(trustManager)));
|
||||
trustManagerPostChecks.apply(updatedTrustManager, config);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertContainsCertificateWithMatchingName(Certificate[] certificates, Matcher<String> matcher) {
|
||||
for (Certificate certificate : certificates) {
|
||||
if (certificate instanceof X509Certificate) {
|
||||
if (matcher.matches(((X509Certificate) certificate).getSubjectX500Principal().getName())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
fail("no matching certificate could be found");
|
||||
}
|
||||
|
||||
private void assertKeyManagersSame(X509ExtendedKeyManager original, X509ExtendedKeyManager[] other) {
|
||||
assertEquals(1, other.length);
|
||||
assertThat(other[0], instanceOf(ReloadableX509KeyManager.class));
|
||||
X509ExtendedKeyManager otherKeyManager = ((ReloadableX509KeyManager) other[0]).getKeyManager();
|
||||
assertThat(otherKeyManager, sameInstance(original));
|
||||
}
|
||||
|
||||
private void assertTrustManagersSame(X509ExtendedTrustManager original, X509ExtendedTrustManager[] other) {
|
||||
assertEquals(1, other.length);
|
||||
assertThat(other[0], instanceOf(ReloadableTrustManager.class));
|
||||
X509ExtendedTrustManager otherTrustManager = ((ReloadableTrustManager) other[0]).getTrustManager();
|
||||
assertThat(otherTrustManager, sameInstance(original));
|
||||
}
|
||||
|
||||
private static void atomicMoveIfPossible(Path source, Path target) throws IOException {
|
||||
try {
|
||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
|
||||
} catch (AtomicMoveNotSupportedException e) {
|
||||
Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,45 +5,18 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.ssl;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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", "/",
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue