Revert "HADOOP-16524. Reloading SSL keystore for both DataNode and NameNode (#2470)"
This reverts commit e306f59421
.
This commit is contained in:
parent
1ec5c67b5e
commit
d4fd675a95
|
@ -27,17 +27,14 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Timer;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -77,8 +74,6 @@ import org.apache.hadoop.security.authentication.server.ProxyUserAuthenticationF
|
||||||
import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler;
|
import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler;
|
||||||
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
|
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
|
||||||
import org.apache.hadoop.security.authorize.AccessControlList;
|
import org.apache.hadoop.security.authorize.AccessControlList;
|
||||||
import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory;
|
|
||||||
import org.apache.hadoop.security.ssl.FileMonitoringTimerTask;
|
|
||||||
import org.apache.hadoop.security.ssl.SSLFactory;
|
import org.apache.hadoop.security.ssl.SSLFactory;
|
||||||
import org.apache.hadoop.util.ReflectionUtils;
|
import org.apache.hadoop.util.ReflectionUtils;
|
||||||
import org.apache.hadoop.util.Shell;
|
import org.apache.hadoop.util.Shell;
|
||||||
|
@ -189,7 +184,6 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
static final String STATE_DESCRIPTION_ALIVE = " - alive";
|
static final String STATE_DESCRIPTION_ALIVE = " - alive";
|
||||||
static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
|
static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
|
||||||
private final SignerSecretProvider secretProvider;
|
private final SignerSecretProvider secretProvider;
|
||||||
private final Optional<java.util.Timer> configurationChangeMonitor;
|
|
||||||
private XFrameOption xFrameOption;
|
private XFrameOption xFrameOption;
|
||||||
private boolean xFrameOptionIsEnabled;
|
private boolean xFrameOptionIsEnabled;
|
||||||
public static final String HTTP_HEADER_PREFIX = "hadoop.http.header.";
|
public static final String HTTP_HEADER_PREFIX = "hadoop.http.header.";
|
||||||
|
@ -245,8 +239,6 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
|
|
||||||
private boolean sniHostCheckEnabled;
|
private boolean sniHostCheckEnabled;
|
||||||
|
|
||||||
private Optional<Timer> configurationChangeMonitor = Optional.empty();
|
|
||||||
|
|
||||||
public Builder setName(String name){
|
public Builder setName(String name){
|
||||||
this.name = name;
|
this.name = name;
|
||||||
return this;
|
return this;
|
||||||
|
@ -577,45 +569,12 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnabledProtocols(sslContextFactory);
|
setEnabledProtocols(sslContextFactory);
|
||||||
|
|
||||||
long storesReloadInterval =
|
|
||||||
conf.getLong(FileBasedKeyStoresFactory.SSL_STORES_RELOAD_INTERVAL_TPL_KEY,
|
|
||||||
FileBasedKeyStoresFactory.DEFAULT_SSL_STORES_RELOAD_INTERVAL);
|
|
||||||
|
|
||||||
if (storesReloadInterval > 0) {
|
|
||||||
this.configurationChangeMonitor = Optional.of(
|
|
||||||
this.makeConfigurationChangeMonitor(storesReloadInterval, sslContextFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.addFirstConnectionFactory(new SslConnectionFactory(sslContextFactory,
|
conn.addFirstConnectionFactory(new SslConnectionFactory(sslContextFactory,
|
||||||
HttpVersion.HTTP_1_1.asString()));
|
HttpVersion.HTTP_1_1.asString()));
|
||||||
|
|
||||||
return conn;
|
return conn;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Timer makeConfigurationChangeMonitor(long reloadInterval,
|
|
||||||
SslContextFactory.Server sslContextFactory) {
|
|
||||||
Timer timer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
//
|
|
||||||
// The Jetty SSLContextFactory provides a 'reload' method which will reload both
|
|
||||||
// truststore and keystore certificates.
|
|
||||||
//
|
|
||||||
timer.schedule(new FileMonitoringTimerTask(
|
|
||||||
Paths.get(keyStore),
|
|
||||||
path -> {
|
|
||||||
LOG.info("Reloading certificates from store keystore " + keyStore);
|
|
||||||
try {
|
|
||||||
sslContextFactory.reload(factory -> { });
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Failed to reload SSL keystore certificates", ex);
|
|
||||||
}
|
|
||||||
},null),
|
|
||||||
reloadInterval,
|
|
||||||
reloadInterval
|
|
||||||
);
|
|
||||||
return timer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEnabledProtocols(SslContextFactory sslContextFactory) {
|
private void setEnabledProtocols(SslContextFactory sslContextFactory) {
|
||||||
String enabledProtocols = conf.get(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY,
|
String enabledProtocols = conf.get(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY,
|
||||||
SSLFactory.SSL_ENABLED_PROTOCOLS_DEFAULT);
|
SSLFactory.SSL_ENABLED_PROTOCOLS_DEFAULT);
|
||||||
|
@ -658,7 +617,6 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
this.webAppContext = createWebAppContext(b, adminsAcl, appDir);
|
this.webAppContext = createWebAppContext(b, adminsAcl, appDir);
|
||||||
this.xFrameOptionIsEnabled = b.xFrameEnabled;
|
this.xFrameOptionIsEnabled = b.xFrameEnabled;
|
||||||
this.xFrameOption = b.xFrameOption;
|
this.xFrameOption = b.xFrameOption;
|
||||||
this.configurationChangeMonitor = b.configurationChangeMonitor;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.secretProvider =
|
this.secretProvider =
|
||||||
|
@ -1426,16 +1384,6 @@ public final class HttpServer2 implements FilterContainer {
|
||||||
*/
|
*/
|
||||||
public void stop() throws Exception {
|
public void stop() throws Exception {
|
||||||
MultiException exception = null;
|
MultiException exception = null;
|
||||||
if (this.configurationChangeMonitor.isPresent()) {
|
|
||||||
try {
|
|
||||||
this.configurationChangeMonitor.get().cancel();
|
|
||||||
} catch (Exception e) {
|
|
||||||
LOG.error(
|
|
||||||
"Error while canceling configuration monitoring timer for webapp"
|
|
||||||
+ webAppContext.getDisplayName(), e);
|
|
||||||
exception = addMultiException(exception, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ServerConnector c : listeners) {
|
for (ServerConnector c : listeners) {
|
||||||
try {
|
try {
|
||||||
c.close();
|
c.close();
|
||||||
|
|
|
@ -29,20 +29,20 @@ import javax.net.ssl.KeyManager;
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
import javax.net.ssl.KeyManagerFactory;
|
||||||
import javax.net.ssl.TrustManager;
|
import javax.net.ssl.TrustManager;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
import java.util.Timer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link KeyStoresFactory} implementation that reads the certificates from
|
* {@link KeyStoresFactory} implementation that reads the certificates from
|
||||||
* keystore files.
|
* keystore files.
|
||||||
* <p>
|
* <p>
|
||||||
* If either the truststore or the keystore certificates file changes, it
|
* if the trust certificates keystore file changes, the {@link TrustManager}
|
||||||
* would be refreshed under the corresponding wrapper implementation -
|
* is refreshed with the new trust certificate entries (using a
|
||||||
* {@link ReloadingX509KeystoreManager} or {@link ReloadingX509TrustManager}.
|
* {@link ReloadingX509TrustManager} trustmanager).
|
||||||
* </p>
|
|
||||||
*/
|
*/
|
||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
@InterfaceStability.Evolving
|
@InterfaceStability.Evolving
|
||||||
|
@ -51,13 +51,6 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
LoggerFactory.getLogger(FileBasedKeyStoresFactory.class);
|
LoggerFactory.getLogger(FileBasedKeyStoresFactory.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* The refresh interval used to check if either of the truststore or keystore
|
|
||||||
* certificate file has changed.
|
|
||||||
*/
|
|
||||||
public static final String SSL_STORES_RELOAD_INTERVAL_TPL_KEY =
|
|
||||||
"ssl.{0}.stores.reload.interval";
|
|
||||||
|
|
||||||
public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
|
public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
|
||||||
"ssl.{0}.keystore.location";
|
"ssl.{0}.keystore.location";
|
||||||
public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
|
public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
|
||||||
|
@ -84,119 +77,14 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
|
||||||
public static final String DEFAULT_KEYSTORE_TYPE = "jks";
|
public static final String DEFAULT_KEYSTORE_TYPE = "jks";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default time interval in milliseconds used to check if either
|
* Reload interval in milliseconds.
|
||||||
* of the truststore or keystore certificates file has changed and needs reloading.
|
|
||||||
*/
|
*/
|
||||||
public static final int DEFAULT_SSL_STORES_RELOAD_INTERVAL = 10000;
|
public static final int DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL = 10000;
|
||||||
|
|
||||||
private Configuration conf;
|
private Configuration conf;
|
||||||
private KeyManager[] keyManagers;
|
private KeyManager[] keyManagers;
|
||||||
private TrustManager[] trustManagers;
|
private TrustManager[] trustManagers;
|
||||||
private ReloadingX509TrustManager trustManager;
|
private ReloadingX509TrustManager trustManager;
|
||||||
private Timer fileMonitoringTimer;
|
|
||||||
|
|
||||||
|
|
||||||
private void createTrustManagersFromConfiguration(SSLFactory.Mode mode,
|
|
||||||
String truststoreType,
|
|
||||||
String truststoreLocation,
|
|
||||||
long storesReloadInterval)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
String passwordProperty = resolvePropertyName(mode,
|
|
||||||
SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
|
|
||||||
String truststorePassword = getPassword(conf, passwordProperty, "");
|
|
||||||
if (truststorePassword.isEmpty()) {
|
|
||||||
// An empty trust store password is legal; the trust store password
|
|
||||||
// is only required when writing to a trust store. Otherwise it's
|
|
||||||
// an optional integrity check.
|
|
||||||
truststorePassword = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if obsolete truststore specific reload interval is present for backward compatible
|
|
||||||
long truststoreReloadInterval =
|
|
||||||
conf.getLong(
|
|
||||||
resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
|
|
||||||
storesReloadInterval);
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation +
|
|
||||||
", reloading at " + truststoreReloadInterval + " millis.");
|
|
||||||
}
|
|
||||||
|
|
||||||
trustManager = new ReloadingX509TrustManager(
|
|
||||||
truststoreType,
|
|
||||||
truststoreLocation,
|
|
||||||
truststorePassword);
|
|
||||||
|
|
||||||
if (truststoreReloadInterval > 0) {
|
|
||||||
fileMonitoringTimer.schedule(
|
|
||||||
new FileMonitoringTimerTask(
|
|
||||||
Paths.get(truststoreLocation),
|
|
||||||
path -> trustManager.loadFrom(path),
|
|
||||||
exception -> LOG.error(ReloadingX509TrustManager.RELOAD_ERROR_MESSAGE, exception)),
|
|
||||||
truststoreReloadInterval,
|
|
||||||
truststoreReloadInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
|
|
||||||
}
|
|
||||||
trustManagers = new TrustManager[]{trustManager};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements logic of initializing the KeyManagers with the options
|
|
||||||
* to reload keystores.
|
|
||||||
* @param mode client or server
|
|
||||||
* @param keystoreType The keystore type.
|
|
||||||
* @param storesReloadInterval The interval to check if the keystore certificates
|
|
||||||
* file has changed.
|
|
||||||
*/
|
|
||||||
private void createKeyManagersFromConfiguration(SSLFactory.Mode mode,
|
|
||||||
String keystoreType, long storesReloadInterval)
|
|
||||||
throws GeneralSecurityException, IOException {
|
|
||||||
String locationProperty =
|
|
||||||
resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
|
|
||||||
String keystoreLocation = conf.get(locationProperty, "");
|
|
||||||
if (keystoreLocation.isEmpty()) {
|
|
||||||
throw new GeneralSecurityException("The property '" + locationProperty +
|
|
||||||
"' has not been set in the ssl configuration file.");
|
|
||||||
}
|
|
||||||
String passwordProperty =
|
|
||||||
resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
|
|
||||||
String keystorePassword = getPassword(conf, passwordProperty, "");
|
|
||||||
if (keystorePassword.isEmpty()) {
|
|
||||||
throw new GeneralSecurityException("The property '" + passwordProperty +
|
|
||||||
"' has not been set in the ssl configuration file.");
|
|
||||||
}
|
|
||||||
String keyPasswordProperty =
|
|
||||||
resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
|
|
||||||
// Key password defaults to the same value as store password for
|
|
||||||
// compatibility with legacy configurations that did not use a separate
|
|
||||||
// configuration property for key password.
|
|
||||||
String keystoreKeyPassword = getPassword(
|
|
||||||
conf, keyPasswordProperty, keystorePassword);
|
|
||||||
if (LOG.isDebugEnabled()) {
|
|
||||||
LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
ReloadingX509KeystoreManager keystoreManager = new ReloadingX509KeystoreManager(
|
|
||||||
keystoreType,
|
|
||||||
keystoreLocation,
|
|
||||||
keystorePassword,
|
|
||||||
keystoreKeyPassword);
|
|
||||||
|
|
||||||
if (storesReloadInterval > 0) {
|
|
||||||
fileMonitoringTimer.schedule(
|
|
||||||
new FileMonitoringTimerTask(
|
|
||||||
Paths.get(keystoreLocation),
|
|
||||||
path -> keystoreManager.loadFrom(path),
|
|
||||||
exception -> LOG.error(ReloadingX509KeystoreManager.RELOAD_ERROR_MESSAGE, exception)),
|
|
||||||
storesReloadInterval,
|
|
||||||
storesReloadInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
keyManagers = new KeyManager[] { keystoreManager };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves a property name to its client/server version if applicable.
|
* Resolves a property name to its client/server version if applicable.
|
||||||
|
@ -251,28 +139,56 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
|
||||||
conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY,
|
conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY,
|
||||||
SSLFactory.SSL_REQUIRE_CLIENT_CERT_DEFAULT);
|
SSLFactory.SSL_REQUIRE_CLIENT_CERT_DEFAULT);
|
||||||
|
|
||||||
long storesReloadInterval = conf.getLong(
|
|
||||||
resolvePropertyName(mode, SSL_STORES_RELOAD_INTERVAL_TPL_KEY),
|
|
||||||
DEFAULT_SSL_STORES_RELOAD_INTERVAL);
|
|
||||||
|
|
||||||
fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
|
|
||||||
// certificate store
|
// certificate store
|
||||||
String keystoreType =
|
String keystoreType =
|
||||||
conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
|
conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
|
||||||
DEFAULT_KEYSTORE_TYPE);
|
DEFAULT_KEYSTORE_TYPE);
|
||||||
|
KeyStore keystore = KeyStore.getInstance(keystoreType);
|
||||||
|
String keystoreKeyPassword = null;
|
||||||
if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
|
if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
|
||||||
createKeyManagersFromConfiguration(mode, keystoreType, storesReloadInterval);
|
String locationProperty =
|
||||||
} else {
|
resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
|
||||||
KeyStore keystore = KeyStore.getInstance(keystoreType);
|
String keystoreLocation = conf.get(locationProperty, "");
|
||||||
keystore.load(null, null);
|
if (keystoreLocation.isEmpty()) {
|
||||||
KeyManagerFactory keyMgrFactory = KeyManagerFactory
|
throw new GeneralSecurityException("The property '" + locationProperty +
|
||||||
.getInstance(SSLFactory.SSLCERTIFICATE);
|
"' has not been set in the ssl configuration file.");
|
||||||
|
}
|
||||||
|
String passwordProperty =
|
||||||
|
resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
|
||||||
|
String keystorePassword = getPassword(conf, passwordProperty, "");
|
||||||
|
if (keystorePassword.isEmpty()) {
|
||||||
|
throw new GeneralSecurityException("The property '" + passwordProperty +
|
||||||
|
"' has not been set in the ssl configuration file.");
|
||||||
|
}
|
||||||
|
String keyPasswordProperty =
|
||||||
|
resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
|
||||||
|
// Key password defaults to the same value as store password for
|
||||||
|
// compatibility with legacy configurations that did not use a separate
|
||||||
|
// configuration property for key password.
|
||||||
|
keystoreKeyPassword = getPassword(
|
||||||
|
conf, keyPasswordProperty, keystorePassword);
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
|
||||||
|
}
|
||||||
|
|
||||||
keyMgrFactory.init(keystore, null);
|
InputStream is = Files.newInputStream(Paths.get(keystoreLocation));
|
||||||
keyManagers = keyMgrFactory.getKeyManagers();
|
try {
|
||||||
|
keystore.load(is, keystorePassword.toCharArray());
|
||||||
|
} finally {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug(mode.toString() + " Loaded KeyStore: " + keystoreLocation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
keystore.load(null, null);
|
||||||
}
|
}
|
||||||
|
KeyManagerFactory keyMgrFactory = KeyManagerFactory
|
||||||
|
.getInstance(SSLFactory.SSLCERTIFICATE);
|
||||||
|
|
||||||
|
keyMgrFactory.init(keystore, (keystoreKeyPassword != null) ?
|
||||||
|
keystoreKeyPassword.toCharArray() : null);
|
||||||
|
keyManagers = keyMgrFactory.getKeyManagers();
|
||||||
|
|
||||||
//trust store
|
//trust store
|
||||||
String truststoreType =
|
String truststoreType =
|
||||||
|
@ -283,7 +199,33 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
|
||||||
resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
|
resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
|
||||||
String truststoreLocation = conf.get(locationProperty, "");
|
String truststoreLocation = conf.get(locationProperty, "");
|
||||||
if (!truststoreLocation.isEmpty()) {
|
if (!truststoreLocation.isEmpty()) {
|
||||||
createTrustManagersFromConfiguration(mode, truststoreType, truststoreLocation, storesReloadInterval);
|
String passwordProperty = resolvePropertyName(mode,
|
||||||
|
SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
|
||||||
|
String truststorePassword = getPassword(conf, passwordProperty, "");
|
||||||
|
if (truststorePassword.isEmpty()) {
|
||||||
|
// An empty trust store password is legal; the trust store password
|
||||||
|
// is only required when writing to a trust store. Otherwise it's
|
||||||
|
// an optional integrity check.
|
||||||
|
truststorePassword = null;
|
||||||
|
}
|
||||||
|
long truststoreReloadInterval =
|
||||||
|
conf.getLong(
|
||||||
|
resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
|
||||||
|
DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL);
|
||||||
|
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
trustManager = new ReloadingX509TrustManager(truststoreType,
|
||||||
|
truststoreLocation,
|
||||||
|
truststorePassword,
|
||||||
|
truststoreReloadInterval);
|
||||||
|
trustManager.init();
|
||||||
|
if (LOG.isDebugEnabled()) {
|
||||||
|
LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
|
||||||
|
}
|
||||||
|
trustManagers = new TrustManager[]{trustManager};
|
||||||
} else {
|
} else {
|
||||||
if (LOG.isDebugEnabled()) {
|
if (LOG.isDebugEnabled()) {
|
||||||
LOG.debug("The property '" + locationProperty + "' has not been set, " +
|
LOG.debug("The property '" + locationProperty + "' has not been set, " +
|
||||||
|
@ -314,7 +256,7 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
|
||||||
@Override
|
@Override
|
||||||
public synchronized void destroy() {
|
public synchronized void destroy() {
|
||||||
if (trustManager != null) {
|
if (trustManager != null) {
|
||||||
fileMonitoringTimer.cancel();
|
trustManager.destroy();
|
||||||
trustManager = null;
|
trustManager = null;
|
||||||
keyManagers = null;
|
keyManagers = null;
|
||||||
trustManagers = null;
|
trustManagers = null;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.hadoop.security.ssl;
|
|
||||||
|
|
||||||
import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting;
|
|
||||||
import org.apache.hadoop.thirdparty.com.google.common.base.Preconditions;
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.TimerTask;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements basic logic to track when a file changes on disk and call the action
|
|
||||||
* passed to the constructor when it does. An exception handler can optionally also be specified
|
|
||||||
* in the constructor, otherwise any exception occurring during process will be logged
|
|
||||||
* using this class' logger.
|
|
||||||
*/
|
|
||||||
@InterfaceAudience.Private
|
|
||||||
public class FileMonitoringTimerTask extends TimerTask {
|
|
||||||
|
|
||||||
static final Logger LOG = LoggerFactory.getLogger(FileMonitoringTimerTask.class);
|
|
||||||
|
|
||||||
@VisibleForTesting
|
|
||||||
static final String PROCESS_ERROR_MESSAGE =
|
|
||||||
"Could not process file change : ";
|
|
||||||
|
|
||||||
final private Path filePath;
|
|
||||||
final private Consumer<Path> onFileChange;
|
|
||||||
final Consumer<Throwable> onChangeFailure;
|
|
||||||
private long lastProcessed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create file monitoring task to be scheduled using a standard Java {@link java.util.Timer}
|
|
||||||
* instance.
|
|
||||||
*
|
|
||||||
* @param filePath The path to the file to monitor.
|
|
||||||
* @param onFileChange The function to call when the file has changed.
|
|
||||||
* @param onChangeFailure The function to call when an exception is thrown during the
|
|
||||||
* file change processing.
|
|
||||||
*/
|
|
||||||
public FileMonitoringTimerTask(Path filePath, Consumer<Path> onFileChange,
|
|
||||||
Consumer<Throwable> onChangeFailure) {
|
|
||||||
Preconditions.checkNotNull(filePath, "path to monitor disk file is not set");
|
|
||||||
Preconditions.checkNotNull(onFileChange, "action to monitor disk file is not set");
|
|
||||||
|
|
||||||
this.filePath = filePath;
|
|
||||||
this.lastProcessed = filePath.toFile().lastModified();
|
|
||||||
this.onFileChange = onFileChange;
|
|
||||||
this.onChangeFailure = onChangeFailure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (lastProcessed != filePath.toFile().lastModified()) {
|
|
||||||
try {
|
|
||||||
onFileChange.accept(filePath);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
if (onChangeFailure != null) {
|
|
||||||
onChangeFailure.accept(t);
|
|
||||||
} else {
|
|
||||||
LOG.error(PROCESS_ERROR_MESSAGE + filePath.toString(), t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastProcessed = filePath.toFile().lastModified();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.hadoop.security.ssl;
|
|
||||||
|
|
||||||
import org.apache.hadoop.classification.InterfaceAudience;
|
|
||||||
import org.apache.hadoop.classification.InterfaceStability;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.Principal;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of <code>X509KeyManager</code> that exposes a method,
|
|
||||||
* {@link #loadFrom(Path)} to reload its configuration. Note that it is necessary
|
|
||||||
* to implement the <code>X509ExtendedKeyManager</code> to properly delegate
|
|
||||||
* the additional methods, otherwise the SSL handshake will fail.
|
|
||||||
*/
|
|
||||||
@InterfaceAudience.Private
|
|
||||||
@InterfaceStability.Evolving
|
|
||||||
public class ReloadingX509KeystoreManager extends X509ExtendedKeyManager {
|
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ReloadingX509TrustManager.class);
|
|
||||||
|
|
||||||
static final String RELOAD_ERROR_MESSAGE =
|
|
||||||
"Could not load keystore (keep using existing one) : ";
|
|
||||||
|
|
||||||
final private String type;
|
|
||||||
final private String storePassword;
|
|
||||||
final private String keyPassword;
|
|
||||||
private AtomicReference<X509ExtendedKeyManager> keyManagerRef;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a <code>Reloading509KeystoreManager</code>
|
|
||||||
*
|
|
||||||
* @param type type of keystore file, typically 'jks'.
|
|
||||||
* @param location local path to the keystore file.
|
|
||||||
* @param storePassword password of the keystore file.
|
|
||||||
* @param keyPassword The password of the key.
|
|
||||||
* @throws IOException
|
|
||||||
* @throws GeneralSecurityException
|
|
||||||
*/
|
|
||||||
public ReloadingX509KeystoreManager(String type, String location,
|
|
||||||
String storePassword, String keyPassword)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
this.type = type;
|
|
||||||
this.storePassword = storePassword;
|
|
||||||
this.keyPassword = keyPassword;
|
|
||||||
keyManagerRef = new AtomicReference<X509ExtendedKeyManager>();
|
|
||||||
keyManagerRef.set(loadKeyManager(Paths.get(location)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseEngineClientAlias(String[] strings, Principal[] principals,
|
|
||||||
SSLEngine sslEngine) {
|
|
||||||
return keyManagerRef.get().chooseEngineClientAlias(strings, principals, sslEngine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseEngineServerAlias(String s, Principal[] principals,
|
|
||||||
SSLEngine sslEngine) {
|
|
||||||
return keyManagerRef.get().chooseEngineServerAlias(s, principals, sslEngine);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getClientAliases(String s, Principal[] principals) {
|
|
||||||
return keyManagerRef.get().getClientAliases(s, principals);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseClientAlias(String[] strings, Principal[] principals,
|
|
||||||
Socket socket) {
|
|
||||||
return keyManagerRef.get().chooseClientAlias(strings, principals, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getServerAliases(String s, Principal[] principals) {
|
|
||||||
return keyManagerRef.get().getServerAliases(s, principals);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String chooseServerAlias(String s, Principal[] principals,
|
|
||||||
Socket socket) {
|
|
||||||
return keyManagerRef.get().chooseServerAlias(s, principals, socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getCertificateChain(String s) {
|
|
||||||
return keyManagerRef.get().getCertificateChain(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PrivateKey getPrivateKey(String s) {
|
|
||||||
return keyManagerRef.get().getPrivateKey(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReloadingX509KeystoreManager loadFrom(Path path) {
|
|
||||||
try {
|
|
||||||
this.keyManagerRef.set(loadKeyManager(path));
|
|
||||||
} catch (Exception ex) {
|
|
||||||
// The Consumer.accept interface forces us to convert to unchecked
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private X509ExtendedKeyManager loadKeyManager(Path path)
|
|
||||||
throws IOException, GeneralSecurityException {
|
|
||||||
|
|
||||||
X509ExtendedKeyManager keyManager = null;
|
|
||||||
KeyStore keystore = KeyStore.getInstance(type);
|
|
||||||
|
|
||||||
try (InputStream is = Files.newInputStream(path)) {
|
|
||||||
keystore.load(is, this.storePassword.toCharArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG.debug(" Loaded KeyStore: " + path.toFile().getAbsolutePath());
|
|
||||||
|
|
||||||
KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance(
|
|
||||||
SSLFactory.SSLCERTIFICATE);
|
|
||||||
keyMgrFactory.init(keystore,
|
|
||||||
(keyPassword != null) ? keyPassword.toCharArray() : null);
|
|
||||||
for (KeyManager candidate: keyMgrFactory.getKeyManagers()) {
|
|
||||||
if (candidate instanceof X509ExtendedKeyManager) {
|
|
||||||
keyManager = (X509ExtendedKeyManager)candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keyManager;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,8 +32,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
import java.security.cert.CertificateException;
|
import java.security.cert.CertificateException;
|
||||||
|
@ -41,23 +39,31 @@ import java.security.cert.X509Certificate;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link TrustManager} implementation that exposes a method, {@link #loadFrom(Path)}
|
* A {@link TrustManager} implementation that reloads its configuration when
|
||||||
* to reload its configuration for example when the truststore file on disk changes.
|
* the truststore file on disk changes.
|
||||||
*/
|
*/
|
||||||
@InterfaceAudience.Private
|
@InterfaceAudience.Private
|
||||||
@InterfaceStability.Evolving
|
@InterfaceStability.Evolving
|
||||||
public final class ReloadingX509TrustManager implements X509TrustManager {
|
public final class ReloadingX509TrustManager
|
||||||
|
implements X509TrustManager, Runnable {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
static final Logger LOG =
|
static final Logger LOG =
|
||||||
LoggerFactory.getLogger(ReloadingX509TrustManager.class);
|
LoggerFactory.getLogger(ReloadingX509TrustManager.class);
|
||||||
|
@VisibleForTesting
|
||||||
static final String RELOAD_ERROR_MESSAGE =
|
static final String RELOAD_ERROR_MESSAGE =
|
||||||
"Could not load truststore (keep using existing one) : ";
|
"Could not load truststore (keep using existing one) : ";
|
||||||
|
|
||||||
private String type;
|
private String type;
|
||||||
|
private File file;
|
||||||
private String password;
|
private String password;
|
||||||
|
private long lastLoaded;
|
||||||
|
private long reloadInterval;
|
||||||
private AtomicReference<X509TrustManager> trustManagerRef;
|
private AtomicReference<X509TrustManager> trustManagerRef;
|
||||||
|
|
||||||
|
private volatile boolean running;
|
||||||
|
private Thread reloader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a reloadable trustmanager. The trustmanager reloads itself
|
* Creates a reloadable trustmanager. The trustmanager reloads itself
|
||||||
* if the underlying trustore file has changed.
|
* if the underlying trustore file has changed.
|
||||||
|
@ -65,18 +71,49 @@ public final class ReloadingX509TrustManager implements X509TrustManager {
|
||||||
* @param type type of truststore file, typically 'jks'.
|
* @param type type of truststore file, typically 'jks'.
|
||||||
* @param location local path to the truststore file.
|
* @param location local path to the truststore file.
|
||||||
* @param password password of the truststore file.
|
* @param password password of the truststore file.
|
||||||
|
* @param reloadInterval interval to check if the truststore file has
|
||||||
* changed, in milliseconds.
|
* changed, in milliseconds.
|
||||||
* @throws IOException thrown if the truststore could not be initialized due
|
* @throws IOException thrown if the truststore could not be initialized due
|
||||||
* to an IO error.
|
* to an IO error.
|
||||||
* @throws GeneralSecurityException thrown if the truststore could not be
|
* @throws GeneralSecurityException thrown if the truststore could not be
|
||||||
* initialized due to a security error.
|
* initialized due to a security error.
|
||||||
*/
|
*/
|
||||||
public ReloadingX509TrustManager(String type, String location, String password)
|
public ReloadingX509TrustManager(String type, String location,
|
||||||
|
String password, long reloadInterval)
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
file = new File(location);
|
||||||
this.password = password;
|
this.password = password;
|
||||||
trustManagerRef = new AtomicReference<X509TrustManager>();
|
trustManagerRef = new AtomicReference<X509TrustManager>();
|
||||||
trustManagerRef.set(loadTrustManager(Paths.get(location)));
|
trustManagerRef.set(loadTrustManager());
|
||||||
|
this.reloadInterval = reloadInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the reloader thread.
|
||||||
|
*/
|
||||||
|
public void init() {
|
||||||
|
reloader = new Thread(this, "Truststore reloader thread");
|
||||||
|
reloader.setDaemon(true);
|
||||||
|
running = true;
|
||||||
|
reloader.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the reloader thread.
|
||||||
|
*/
|
||||||
|
public void destroy() {
|
||||||
|
running = false;
|
||||||
|
reloader.interrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the reload check interval.
|
||||||
|
*
|
||||||
|
* @return the reload check interval, in milliseconds.
|
||||||
|
*/
|
||||||
|
public long getReloadInterval() {
|
||||||
|
return reloadInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -114,24 +151,27 @@ public final class ReloadingX509TrustManager implements X509TrustManager {
|
||||||
return issuers;
|
return issuers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReloadingX509TrustManager loadFrom(Path path) {
|
boolean needsReload() {
|
||||||
try {
|
boolean reload = true;
|
||||||
this.trustManagerRef.set(loadTrustManager(path));
|
if (file.exists()) {
|
||||||
} catch (Exception ex) {
|
if (file.lastModified() == lastLoaded) {
|
||||||
// The Consumer.accept interface forces us to convert to unchecked
|
reload = false;
|
||||||
throw new RuntimeException(RELOAD_ERROR_MESSAGE, ex);
|
}
|
||||||
|
} else {
|
||||||
|
lastLoaded = 0;
|
||||||
}
|
}
|
||||||
return this;
|
return reload;
|
||||||
}
|
}
|
||||||
|
|
||||||
X509TrustManager loadTrustManager(Path path)
|
X509TrustManager loadTrustManager()
|
||||||
throws IOException, GeneralSecurityException {
|
throws IOException, GeneralSecurityException {
|
||||||
X509TrustManager trustManager = null;
|
X509TrustManager trustManager = null;
|
||||||
KeyStore ks = KeyStore.getInstance(type);
|
KeyStore ks = KeyStore.getInstance(type);
|
||||||
InputStream in = Files.newInputStream(path);
|
InputStream in = Files.newInputStream(file.toPath());
|
||||||
try {
|
try {
|
||||||
ks.load(in, (password == null) ? null : password.toCharArray());
|
ks.load(in, (password == null) ? null : password.toCharArray());
|
||||||
LOG.debug("Loaded truststore '" + path + "'");
|
lastLoaded = file.lastModified();
|
||||||
|
LOG.debug("Loaded truststore '" + file + "'");
|
||||||
} finally {
|
} finally {
|
||||||
in.close();
|
in.close();
|
||||||
}
|
}
|
||||||
|
@ -148,4 +188,23 @@ public final class ReloadingX509TrustManager implements X509TrustManager {
|
||||||
}
|
}
|
||||||
return trustManager;
|
return trustManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(reloadInterval);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
//NOP
|
||||||
|
}
|
||||||
|
if (running && needsReload()) {
|
||||||
|
try {
|
||||||
|
trustManagerRef.set(loadTrustManager());
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.warn(RELOAD_ERROR_MESSAGE + ex.toString(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,205 +0,0 @@
|
||||||
/**
|
|
||||||
* Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
* or more contributor license agreements. See the NOTICE file
|
|
||||||
* distributed with this work for additional information
|
|
||||||
* regarding copyright ownership. The ASF licenses this file
|
|
||||||
* to you under the Apache License, Version 2.0 (the
|
|
||||||
* "License"); you may not use this file except in compliance
|
|
||||||
* with the License. You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package org.apache.hadoop.security.ssl;
|
|
||||||
|
|
||||||
import org.apache.hadoop.thirdparty.com.google.common.base.Supplier;
|
|
||||||
import org.apache.hadoop.fs.FileUtil;
|
|
||||||
import org.apache.hadoop.test.GenericTestUtils;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
|
|
||||||
import static org.apache.hadoop.security.ssl.KeyStoreTestUtil.*;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
|
|
||||||
public class TestReloadingX509KeyManager {
|
|
||||||
|
|
||||||
private static final String BASEDIR = GenericTestUtils.getTempPath(
|
|
||||||
TestReloadingX509TrustManager.class.getSimpleName());
|
|
||||||
|
|
||||||
private final GenericTestUtils.LogCapturer reloaderLog = GenericTestUtils.LogCapturer.captureLogs(
|
|
||||||
FileMonitoringTimerTask.LOG);
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setUp() throws Exception {
|
|
||||||
File base = new File(BASEDIR);
|
|
||||||
FileUtil.fullyDelete(base);
|
|
||||||
base.mkdirs();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testLoadMissingKeyStore() throws Exception {
|
|
||||||
String keystoreLocation = BASEDIR + "/testmissing.jks";
|
|
||||||
|
|
||||||
ReloadingX509KeystoreManager tm =
|
|
||||||
new ReloadingX509KeystoreManager("jks", keystoreLocation,
|
|
||||||
"password",
|
|
||||||
"password");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testLoadCorruptKeyStore() throws Exception {
|
|
||||||
String keystoreLocation = BASEDIR + "/testcorrupt.jks";
|
|
||||||
OutputStream os = new FileOutputStream(keystoreLocation);
|
|
||||||
os.write(1);
|
|
||||||
os.close();
|
|
||||||
|
|
||||||
ReloadingX509KeystoreManager tm =
|
|
||||||
new ReloadingX509KeystoreManager("jks", keystoreLocation,
|
|
||||||
"password",
|
|
||||||
"password");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test (timeout = 3000000)
|
|
||||||
public void testReload() throws Exception {
|
|
||||||
KeyPair kp = generateKeyPair("RSA");
|
|
||||||
X509Certificate sCert = generateCertificate("CN=localhost, O=server", kp, 30,
|
|
||||||
"SHA1withRSA");
|
|
||||||
String keystoreLocation = BASEDIR + "/testreload.jks";
|
|
||||||
createKeyStore(keystoreLocation, "password", "cert1", kp.getPrivate(), sCert);
|
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
ReloadingX509KeystoreManager tm =
|
|
||||||
new ReloadingX509KeystoreManager("jks", keystoreLocation,
|
|
||||||
"password",
|
|
||||||
"password");
|
|
||||||
try {
|
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
|
||||||
Paths.get(keystoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(kp.getPrivate(), tm.getPrivateKey("cert1"));
|
|
||||||
|
|
||||||
// Wait so that the file modification time is different
|
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
|
||||||
|
|
||||||
// Change the certificate with a new keypair
|
|
||||||
final KeyPair anotherKP = generateKeyPair("RSA");
|
|
||||||
sCert = KeyStoreTestUtil.generateCertificate("CN=localhost, O=server", anotherKP, 30,
|
|
||||||
"SHA1withRSA");
|
|
||||||
createKeyStore(keystoreLocation, "password", "cert1", anotherKP.getPrivate(), sCert);
|
|
||||||
|
|
||||||
GenericTestUtils.waitFor(new Supplier<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean get() {
|
|
||||||
return tm.getPrivateKey("cert1").equals(kp.getPrivate());
|
|
||||||
}
|
|
||||||
}, (int) reloadInterval, 100000);
|
|
||||||
} finally {
|
|
||||||
fileMonitoringTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test (timeout = 30000)
|
|
||||||
public void testReloadMissingTrustStore() throws Exception {
|
|
||||||
KeyPair kp = generateKeyPair("RSA");
|
|
||||||
X509Certificate cert1 = generateCertificate("CN=Cert1", kp, 30, "SHA1withRSA");
|
|
||||||
String keystoreLocation = BASEDIR + "/testmissing.jks";
|
|
||||||
createKeyStore(keystoreLocation, "password", "cert1", kp.getPrivate(), cert1);
|
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
ReloadingX509KeystoreManager tm =
|
|
||||||
new ReloadingX509KeystoreManager("jks", keystoreLocation,
|
|
||||||
"password",
|
|
||||||
"password");
|
|
||||||
try {
|
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
|
||||||
Paths.get(keystoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(kp.getPrivate(), tm.getPrivateKey("cert1"));
|
|
||||||
|
|
||||||
assertFalse(reloaderLog.getOutput().contains(
|
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE));
|
|
||||||
|
|
||||||
// Wait for the first reload to happen so we actually detect a change after the delete
|
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
|
||||||
|
|
||||||
new File(keystoreLocation).delete();
|
|
||||||
|
|
||||||
// Wait for the reload to happen and log to get written to
|
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
|
||||||
|
|
||||||
waitForFailedReloadAtLeastOnce((int) reloadInterval);
|
|
||||||
|
|
||||||
assertEquals(kp.getPrivate(), tm.getPrivateKey("cert1"));
|
|
||||||
} finally {
|
|
||||||
reloaderLog.stopCapturing();
|
|
||||||
fileMonitoringTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Test (timeout = 30000)
|
|
||||||
public void testReloadCorruptTrustStore() throws Exception {
|
|
||||||
KeyPair kp = generateKeyPair("RSA");
|
|
||||||
X509Certificate cert1 = generateCertificate("CN=Cert1", kp, 30, "SHA1withRSA");
|
|
||||||
String keystoreLocation = BASEDIR + "/testmissing.jks";
|
|
||||||
createKeyStore(keystoreLocation, "password", "cert1", kp.getPrivate(), cert1);
|
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
ReloadingX509KeystoreManager tm =
|
|
||||||
new ReloadingX509KeystoreManager("jks", keystoreLocation,
|
|
||||||
"password",
|
|
||||||
"password");
|
|
||||||
try {
|
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
|
||||||
Paths.get(keystoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(kp.getPrivate(), tm.getPrivateKey("cert1"));
|
|
||||||
|
|
||||||
// Wait so that the file modification time is different
|
|
||||||
Thread.sleep((reloadInterval + 1000));
|
|
||||||
|
|
||||||
assertFalse(reloaderLog.getOutput().contains(
|
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE));
|
|
||||||
OutputStream os = new FileOutputStream(keystoreLocation);
|
|
||||||
os.write(1);
|
|
||||||
os.close();
|
|
||||||
|
|
||||||
waitForFailedReloadAtLeastOnce((int) reloadInterval);
|
|
||||||
|
|
||||||
assertEquals(kp.getPrivate(), tm.getPrivateKey("cert1"));
|
|
||||||
} finally {
|
|
||||||
reloaderLog.stopCapturing();
|
|
||||||
fileMonitoringTimer.cancel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**Wait for the reloader thread to load the configurations at least once
|
|
||||||
* by probing the log of the thread if the reload fails.
|
|
||||||
*/
|
|
||||||
private void waitForFailedReloadAtLeastOnce(int reloadInterval)
|
|
||||||
throws InterruptedException, TimeoutException {
|
|
||||||
GenericTestUtils.waitFor(new Supplier<Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean get() {
|
|
||||||
return reloaderLog.getOutput().contains(
|
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE);
|
|
||||||
}
|
|
||||||
}, reloadInterval, 10 * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -30,12 +30,10 @@ import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Timer;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -52,7 +50,7 @@ public class TestReloadingX509TrustManager {
|
||||||
private X509Certificate cert1;
|
private X509Certificate cert1;
|
||||||
private X509Certificate cert2;
|
private X509Certificate cert2;
|
||||||
private final LogCapturer reloaderLog = LogCapturer.captureLogs(
|
private final LogCapturer reloaderLog = LogCapturer.captureLogs(
|
||||||
FileMonitoringTimerTask.LOG);
|
ReloadingX509TrustManager.LOG);
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void setUp() throws Exception {
|
public static void setUp() throws Exception {
|
||||||
|
@ -66,7 +64,12 @@ public class TestReloadingX509TrustManager {
|
||||||
String truststoreLocation = BASEDIR + "/testmissing.jks";
|
String truststoreLocation = BASEDIR + "/testmissing.jks";
|
||||||
|
|
||||||
ReloadingX509TrustManager tm =
|
ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, "password");
|
new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
|
||||||
|
try {
|
||||||
|
tm.init();
|
||||||
|
} finally {
|
||||||
|
tm.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
@Test(expected = IOException.class)
|
||||||
|
@ -77,7 +80,12 @@ public class TestReloadingX509TrustManager {
|
||||||
os.close();
|
os.close();
|
||||||
|
|
||||||
ReloadingX509TrustManager tm =
|
ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, "password");
|
new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
|
||||||
|
try {
|
||||||
|
tm.init();
|
||||||
|
} finally {
|
||||||
|
tm.destroy();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test (timeout = 30000)
|
@Test (timeout = 30000)
|
||||||
|
@ -88,17 +96,14 @@ public class TestReloadingX509TrustManager {
|
||||||
String truststoreLocation = BASEDIR + "/testreload.jks";
|
String truststoreLocation = BASEDIR + "/testreload.jks";
|
||||||
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
final ReloadingX509TrustManager tm =
|
final ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, "password");
|
new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
|
||||||
try {
|
try {
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
tm.init();
|
||||||
Paths.get(truststoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
|
|
||||||
// Wait so that the file modification time is different
|
// Wait so that the file modification time is different
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
Thread.sleep((tm.getReloadInterval() + 1000));
|
||||||
|
|
||||||
// Add another cert
|
// Add another cert
|
||||||
Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();
|
Map<String, X509Certificate> certs = new HashMap<String, X509Certificate>();
|
||||||
|
@ -111,9 +116,9 @@ public class TestReloadingX509TrustManager {
|
||||||
public Boolean get() {
|
public Boolean get() {
|
||||||
return tm.getAcceptedIssuers().length == 2;
|
return tm.getAcceptedIssuers().length == 2;
|
||||||
}
|
}
|
||||||
}, (int) reloadInterval, 100000);
|
}, (int) tm.getReloadInterval(), 10000);
|
||||||
} finally {
|
} finally {
|
||||||
fileMonitoringTimer.cancel();
|
tm.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,38 +130,27 @@ public class TestReloadingX509TrustManager {
|
||||||
String truststoreLocation = BASEDIR + "/testmissing.jks";
|
String truststoreLocation = BASEDIR + "/testmissing.jks";
|
||||||
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
ReloadingX509TrustManager tm =
|
ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, "password");
|
new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
|
||||||
try {
|
try {
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
tm.init();
|
||||||
Paths.get(truststoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
X509Certificate cert = tm.getAcceptedIssuers()[0];
|
X509Certificate cert = tm.getAcceptedIssuers()[0];
|
||||||
|
|
||||||
assertFalse(reloaderLog.getOutput().contains(
|
assertFalse(reloaderLog.getOutput().contains(
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE));
|
ReloadingX509TrustManager.RELOAD_ERROR_MESSAGE));
|
||||||
|
|
||||||
// Wait for the first reload to happen so we actually detect a change after the delete
|
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
|
||||||
|
|
||||||
new File(truststoreLocation).delete();
|
new File(truststoreLocation).delete();
|
||||||
|
|
||||||
// Wait for the reload to happen and log to get written to
|
waitForFailedReloadAtLeastOnce((int) tm.getReloadInterval());
|
||||||
Thread.sleep((reloadInterval+ 1000));
|
|
||||||
|
|
||||||
waitForFailedReloadAtLeastOnce((int) reloadInterval);
|
|
||||||
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
assertEquals(cert, tm.getAcceptedIssuers()[0]);
|
assertEquals(cert, tm.getAcceptedIssuers()[0]);
|
||||||
} finally {
|
} finally {
|
||||||
reloaderLog.stopCapturing();
|
reloaderLog.stopCapturing();
|
||||||
fileMonitoringTimer.cancel();
|
tm.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test (timeout = 30000)
|
@Test (timeout = 30000)
|
||||||
public void testReloadCorruptTrustStore() throws Exception {
|
public void testReloadCorruptTrustStore() throws Exception {
|
||||||
KeyPair kp = generateKeyPair("RSA");
|
KeyPair kp = generateKeyPair("RSA");
|
||||||
|
@ -165,32 +159,29 @@ public class TestReloadingX509TrustManager {
|
||||||
String truststoreLocation = BASEDIR + "/testcorrupt.jks";
|
String truststoreLocation = BASEDIR + "/testcorrupt.jks";
|
||||||
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
||||||
|
|
||||||
long reloadInterval = 10;
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
ReloadingX509TrustManager tm =
|
ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, "password");
|
new ReloadingX509TrustManager("jks", truststoreLocation, "password", 10);
|
||||||
try {
|
try {
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
tm.init();
|
||||||
Paths.get(truststoreLocation), tm::loadFrom,null), reloadInterval, reloadInterval);
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
final X509Certificate cert = tm.getAcceptedIssuers()[0];
|
final X509Certificate cert = tm.getAcceptedIssuers()[0];
|
||||||
|
|
||||||
// Wait so that the file modification time is different
|
// Wait so that the file modification time is different
|
||||||
Thread.sleep((reloadInterval + 1000));
|
Thread.sleep((tm.getReloadInterval() + 1000));
|
||||||
|
|
||||||
assertFalse(reloaderLog.getOutput().contains(
|
assertFalse(reloaderLog.getOutput().contains(
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE));
|
ReloadingX509TrustManager.RELOAD_ERROR_MESSAGE));
|
||||||
OutputStream os = new FileOutputStream(truststoreLocation);
|
OutputStream os = new FileOutputStream(truststoreLocation);
|
||||||
os.write(1);
|
os.write(1);
|
||||||
os.close();
|
os.close();
|
||||||
|
|
||||||
waitForFailedReloadAtLeastOnce((int) reloadInterval);
|
waitForFailedReloadAtLeastOnce((int) tm.getReloadInterval());
|
||||||
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
assertEquals(cert, tm.getAcceptedIssuers()[0]);
|
assertEquals(cert, tm.getAcceptedIssuers()[0]);
|
||||||
} finally {
|
} finally {
|
||||||
reloaderLog.stopCapturing();
|
reloaderLog.stopCapturing();
|
||||||
fileMonitoringTimer.cancel();
|
tm.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,7 +194,7 @@ public class TestReloadingX509TrustManager {
|
||||||
@Override
|
@Override
|
||||||
public Boolean get() {
|
public Boolean get() {
|
||||||
return reloaderLog.getOutput().contains(
|
return reloaderLog.getOutput().contains(
|
||||||
FileMonitoringTimerTask.PROCESS_ERROR_MESSAGE);
|
ReloadingX509TrustManager.RELOAD_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}, reloadInterval, 10 * 1000);
|
}, reloadInterval, 10 * 1000);
|
||||||
}
|
}
|
||||||
|
@ -217,15 +208,13 @@ public class TestReloadingX509TrustManager {
|
||||||
String truststoreLocation = BASEDIR + "/testreload.jks";
|
String truststoreLocation = BASEDIR + "/testreload.jks";
|
||||||
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
createTrustStore(truststoreLocation, "password", "cert1", cert1);
|
||||||
|
|
||||||
Timer fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
|
|
||||||
final ReloadingX509TrustManager tm =
|
final ReloadingX509TrustManager tm =
|
||||||
new ReloadingX509TrustManager("jks", truststoreLocation, null);
|
new ReloadingX509TrustManager("jks", truststoreLocation, null, 10);
|
||||||
try {
|
try {
|
||||||
fileMonitoringTimer.schedule(new FileMonitoringTimerTask(
|
tm.init();
|
||||||
Paths.get(truststoreLocation), tm::loadFrom,null), 10, 10);
|
|
||||||
assertEquals(1, tm.getAcceptedIssuers().length);
|
assertEquals(1, tm.getAcceptedIssuers().length);
|
||||||
} finally {
|
} finally {
|
||||||
fileMonitoringTimer.cancel();
|
tm.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue