Skip to content

Commit e306f59

Browse files
bolerioBorislav Iordanov
andauthored
HADOOP-16524. Reloading SSL keystore for both DataNode and NameNode (#2470)
Co-authored-by: Borislav Iordanov <biordanov@apple.com> Signed-off-by: stack <stack@apache.org>
1 parent 4176759 commit e306f59

File tree

7 files changed

+703
-194
lines changed

7 files changed

+703
-194
lines changed

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/http/HttpServer2.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,17 @@
2727
import java.net.MalformedURLException;
2828
import java.net.URI;
2929
import java.net.URL;
30-
import java.util.Arrays;
31-
import java.util.ArrayList;
32-
import java.util.Collections;
33-
import java.util.Enumeration;
34-
import java.util.HashMap;
30+
import java.nio.file.Paths;
3531
import java.util.List;
32+
import java.util.ArrayList;
3633
import java.util.Map;
34+
import java.util.HashMap;
35+
import java.util.Collections;
36+
import java.util.Optional;
3737
import java.util.Properties;
38+
import java.util.Enumeration;
39+
import java.util.Arrays;
40+
import java.util.Timer;
3841
import java.util.regex.Matcher;
3942
import java.util.regex.Pattern;
4043

@@ -74,6 +77,8 @@
7477
import org.apache.hadoop.security.authentication.server.PseudoAuthenticationHandler;
7578
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
7679
import org.apache.hadoop.security.authorize.AccessControlList;
80+
import org.apache.hadoop.security.ssl.FileBasedKeyStoresFactory;
81+
import org.apache.hadoop.security.ssl.FileMonitoringTimerTask;
7782
import org.apache.hadoop.security.ssl.SSLFactory;
7883
import org.apache.hadoop.util.ReflectionUtils;
7984
import org.apache.hadoop.util.Shell;
@@ -184,6 +189,7 @@ public final class HttpServer2 implements FilterContainer {
184189
static final String STATE_DESCRIPTION_ALIVE = " - alive";
185190
static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
186191
private final SignerSecretProvider secretProvider;
192+
private final Optional<java.util.Timer> configurationChangeMonitor;
187193
private XFrameOption xFrameOption;
188194
private boolean xFrameOptionIsEnabled;
189195
public static final String HTTP_HEADER_PREFIX = "hadoop.http.header.";
@@ -239,6 +245,8 @@ public static class Builder {
239245

240246
private boolean sniHostCheckEnabled;
241247

248+
private Optional<Timer> configurationChangeMonitor = Optional.empty();
249+
242250
public Builder setName(String name){
243251
this.name = name;
244252
return this;
@@ -569,12 +577,45 @@ private ServerConnector createHttpsChannelConnector(
569577
}
570578

571579
setEnabledProtocols(sslContextFactory);
580+
581+
long storesReloadInterval =
582+
conf.getLong(FileBasedKeyStoresFactory.SSL_STORES_RELOAD_INTERVAL_TPL_KEY,
583+
FileBasedKeyStoresFactory.DEFAULT_SSL_STORES_RELOAD_INTERVAL);
584+
585+
if (storesReloadInterval > 0) {
586+
this.configurationChangeMonitor = Optional.of(
587+
this.makeConfigurationChangeMonitor(storesReloadInterval, sslContextFactory));
588+
}
589+
572590
conn.addFirstConnectionFactory(new SslConnectionFactory(sslContextFactory,
573591
HttpVersion.HTTP_1_1.asString()));
574592

575593
return conn;
576594
}
577595

596+
private Timer makeConfigurationChangeMonitor(long reloadInterval,
597+
SslContextFactory.Server sslContextFactory) {
598+
Timer timer = new Timer("SSL Certificates Store Monitor", true);
599+
//
600+
// The Jetty SSLContextFactory provides a 'reload' method which will reload both
601+
// truststore and keystore certificates.
602+
//
603+
timer.schedule(new FileMonitoringTimerTask(
604+
Paths.get(keyStore),
605+
path -> {
606+
LOG.info("Reloading certificates from store keystore " + keyStore);
607+
try {
608+
sslContextFactory.reload(factory -> { });
609+
} catch (Exception ex) {
610+
LOG.error("Failed to reload SSL keystore certificates", ex);
611+
}
612+
},null),
613+
reloadInterval,
614+
reloadInterval
615+
);
616+
return timer;
617+
}
618+
578619
private void setEnabledProtocols(SslContextFactory sslContextFactory) {
579620
String enabledProtocols = conf.get(SSLFactory.SSL_ENABLED_PROTOCOLS_KEY,
580621
SSLFactory.SSL_ENABLED_PROTOCOLS_DEFAULT);
@@ -617,6 +658,7 @@ private HttpServer2(final Builder b) throws IOException {
617658
this.webAppContext = createWebAppContext(b, adminsAcl, appDir);
618659
this.xFrameOptionIsEnabled = b.xFrameEnabled;
619660
this.xFrameOption = b.xFrameOption;
661+
this.configurationChangeMonitor = b.configurationChangeMonitor;
620662

621663
try {
622664
this.secretProvider =
@@ -1384,6 +1426,16 @@ void openListeners() throws Exception {
13841426
*/
13851427
public void stop() throws Exception {
13861428
MultiException exception = null;
1429+
if (this.configurationChangeMonitor.isPresent()) {
1430+
try {
1431+
this.configurationChangeMonitor.get().cancel();
1432+
} catch (Exception e) {
1433+
LOG.error(
1434+
"Error while canceling configuration monitoring timer for webapp"
1435+
+ webAppContext.getDisplayName(), e);
1436+
exception = addMultiException(exception, e);
1437+
}
1438+
}
13871439
for (ServerConnector c : listeners) {
13881440
try {
13891441
c.close();

hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/ssl/FileBasedKeyStoresFactory.java

Lines changed: 137 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,20 @@
2929
import javax.net.ssl.KeyManagerFactory;
3030
import javax.net.ssl.TrustManager;
3131
import java.io.IOException;
32-
import java.io.InputStream;
33-
import java.nio.file.Files;
3432
import java.nio.file.Paths;
3533
import java.security.GeneralSecurityException;
3634
import java.security.KeyStore;
3735
import java.text.MessageFormat;
36+
import java.util.Timer;
3837

3938
/**
4039
* {@link KeyStoresFactory} implementation that reads the certificates from
4140
* keystore files.
4241
* <p>
43-
* if the trust certificates keystore file changes, the {@link TrustManager}
44-
* is refreshed with the new trust certificate entries (using a
45-
* {@link ReloadingX509TrustManager} trustmanager).
42+
* If either the truststore or the keystore certificates file changes, it
43+
* would be refreshed under the corresponding wrapper implementation -
44+
* {@link ReloadingX509KeystoreManager} or {@link ReloadingX509TrustManager}.
45+
* </p>
4646
*/
4747
@InterfaceAudience.Private
4848
@InterfaceStability.Evolving
@@ -51,6 +51,13 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
5151
private static final Logger LOG =
5252
LoggerFactory.getLogger(FileBasedKeyStoresFactory.class);
5353

54+
/**
55+
* The refresh interval used to check if either of the truststore or keystore
56+
* certificate file has changed.
57+
*/
58+
public static final String SSL_STORES_RELOAD_INTERVAL_TPL_KEY =
59+
"ssl.{0}.stores.reload.interval";
60+
5461
public static final String SSL_KEYSTORE_LOCATION_TPL_KEY =
5562
"ssl.{0}.keystore.location";
5663
public static final String SSL_KEYSTORE_PASSWORD_TPL_KEY =
@@ -77,14 +84,119 @@ public class FileBasedKeyStoresFactory implements KeyStoresFactory {
7784
public static final String DEFAULT_KEYSTORE_TYPE = "jks";
7885

7986
/**
80-
* Reload interval in milliseconds.
87+
* The default time interval in milliseconds used to check if either
88+
* of the truststore or keystore certificates file has changed and needs reloading.
8189
*/
82-
public static final int DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL = 10000;
90+
public static final int DEFAULT_SSL_STORES_RELOAD_INTERVAL = 10000;
8391

8492
private Configuration conf;
8593
private KeyManager[] keyManagers;
8694
private TrustManager[] trustManagers;
8795
private ReloadingX509TrustManager trustManager;
96+
private Timer fileMonitoringTimer;
97+
98+
99+
private void createTrustManagersFromConfiguration(SSLFactory.Mode mode,
100+
String truststoreType,
101+
String truststoreLocation,
102+
long storesReloadInterval)
103+
throws IOException, GeneralSecurityException {
104+
String passwordProperty = resolvePropertyName(mode,
105+
SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
106+
String truststorePassword = getPassword(conf, passwordProperty, "");
107+
if (truststorePassword.isEmpty()) {
108+
// An empty trust store password is legal; the trust store password
109+
// is only required when writing to a trust store. Otherwise it's
110+
// an optional integrity check.
111+
truststorePassword = null;
112+
}
113+
114+
// Check if obsolete truststore specific reload interval is present for backward compatible
115+
long truststoreReloadInterval =
116+
conf.getLong(
117+
resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
118+
storesReloadInterval);
119+
120+
if (LOG.isDebugEnabled()) {
121+
LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation +
122+
", reloading at " + truststoreReloadInterval + " millis.");
123+
}
124+
125+
trustManager = new ReloadingX509TrustManager(
126+
truststoreType,
127+
truststoreLocation,
128+
truststorePassword);
129+
130+
if (truststoreReloadInterval > 0) {
131+
fileMonitoringTimer.schedule(
132+
new FileMonitoringTimerTask(
133+
Paths.get(truststoreLocation),
134+
path -> trustManager.loadFrom(path),
135+
exception -> LOG.error(ReloadingX509TrustManager.RELOAD_ERROR_MESSAGE, exception)),
136+
truststoreReloadInterval,
137+
truststoreReloadInterval);
138+
}
139+
140+
if (LOG.isDebugEnabled()) {
141+
LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
142+
}
143+
trustManagers = new TrustManager[]{trustManager};
144+
}
145+
146+
/**
147+
* Implements logic of initializing the KeyManagers with the options
148+
* to reload keystores.
149+
* @param mode client or server
150+
* @param keystoreType The keystore type.
151+
* @param storesReloadInterval The interval to check if the keystore certificates
152+
* file has changed.
153+
*/
154+
private void createKeyManagersFromConfiguration(SSLFactory.Mode mode,
155+
String keystoreType, long storesReloadInterval)
156+
throws GeneralSecurityException, IOException {
157+
String locationProperty =
158+
resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
159+
String keystoreLocation = conf.get(locationProperty, "");
160+
if (keystoreLocation.isEmpty()) {
161+
throw new GeneralSecurityException("The property '" + locationProperty +
162+
"' has not been set in the ssl configuration file.");
163+
}
164+
String passwordProperty =
165+
resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
166+
String keystorePassword = getPassword(conf, passwordProperty, "");
167+
if (keystorePassword.isEmpty()) {
168+
throw new GeneralSecurityException("The property '" + passwordProperty +
169+
"' has not been set in the ssl configuration file.");
170+
}
171+
String keyPasswordProperty =
172+
resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
173+
// Key password defaults to the same value as store password for
174+
// compatibility with legacy configurations that did not use a separate
175+
// configuration property for key password.
176+
String keystoreKeyPassword = getPassword(
177+
conf, keyPasswordProperty, keystorePassword);
178+
if (LOG.isDebugEnabled()) {
179+
LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
180+
}
181+
182+
ReloadingX509KeystoreManager keystoreManager = new ReloadingX509KeystoreManager(
183+
keystoreType,
184+
keystoreLocation,
185+
keystorePassword,
186+
keystoreKeyPassword);
187+
188+
if (storesReloadInterval > 0) {
189+
fileMonitoringTimer.schedule(
190+
new FileMonitoringTimerTask(
191+
Paths.get(keystoreLocation),
192+
path -> keystoreManager.loadFrom(path),
193+
exception -> LOG.error(ReloadingX509KeystoreManager.RELOAD_ERROR_MESSAGE, exception)),
194+
storesReloadInterval,
195+
storesReloadInterval);
196+
}
197+
198+
keyManagers = new KeyManager[] { keystoreManager };
199+
}
88200

89201
/**
90202
* Resolves a property name to its client/server version if applicable.
@@ -139,56 +251,28 @@ public void init(SSLFactory.Mode mode)
139251
conf.getBoolean(SSLFactory.SSL_REQUIRE_CLIENT_CERT_KEY,
140252
SSLFactory.SSL_REQUIRE_CLIENT_CERT_DEFAULT);
141253

254+
long storesReloadInterval = conf.getLong(
255+
resolvePropertyName(mode, SSL_STORES_RELOAD_INTERVAL_TPL_KEY),
256+
DEFAULT_SSL_STORES_RELOAD_INTERVAL);
257+
258+
fileMonitoringTimer = new Timer("SSL Certificates Store Monitor", true);
259+
142260
// certificate store
143261
String keystoreType =
144-
conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
145-
DEFAULT_KEYSTORE_TYPE);
146-
KeyStore keystore = KeyStore.getInstance(keystoreType);
147-
String keystoreKeyPassword = null;
148-
if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
149-
String locationProperty =
150-
resolvePropertyName(mode, SSL_KEYSTORE_LOCATION_TPL_KEY);
151-
String keystoreLocation = conf.get(locationProperty, "");
152-
if (keystoreLocation.isEmpty()) {
153-
throw new GeneralSecurityException("The property '" + locationProperty +
154-
"' has not been set in the ssl configuration file.");
155-
}
156-
String passwordProperty =
157-
resolvePropertyName(mode, SSL_KEYSTORE_PASSWORD_TPL_KEY);
158-
String keystorePassword = getPassword(conf, passwordProperty, "");
159-
if (keystorePassword.isEmpty()) {
160-
throw new GeneralSecurityException("The property '" + passwordProperty +
161-
"' has not been set in the ssl configuration file.");
162-
}
163-
String keyPasswordProperty =
164-
resolvePropertyName(mode, SSL_KEYSTORE_KEYPASSWORD_TPL_KEY);
165-
// Key password defaults to the same value as store password for
166-
// compatibility with legacy configurations that did not use a separate
167-
// configuration property for key password.
168-
keystoreKeyPassword = getPassword(
169-
conf, keyPasswordProperty, keystorePassword);
170-
if (LOG.isDebugEnabled()) {
171-
LOG.debug(mode.toString() + " KeyStore: " + keystoreLocation);
172-
}
262+
conf.get(resolvePropertyName(mode, SSL_KEYSTORE_TYPE_TPL_KEY),
263+
DEFAULT_KEYSTORE_TYPE);
173264

174-
InputStream is = Files.newInputStream(Paths.get(keystoreLocation));
175-
try {
176-
keystore.load(is, keystorePassword.toCharArray());
177-
} finally {
178-
is.close();
179-
}
180-
if (LOG.isDebugEnabled()) {
181-
LOG.debug(mode.toString() + " Loaded KeyStore: " + keystoreLocation);
182-
}
265+
if (requireClientCert || mode == SSLFactory.Mode.SERVER) {
266+
createKeyManagersFromConfiguration(mode, keystoreType, storesReloadInterval);
183267
} else {
268+
KeyStore keystore = KeyStore.getInstance(keystoreType);
184269
keystore.load(null, null);
270+
KeyManagerFactory keyMgrFactory = KeyManagerFactory
271+
.getInstance(SSLFactory.SSLCERTIFICATE);
272+
273+
keyMgrFactory.init(keystore, null);
274+
keyManagers = keyMgrFactory.getKeyManagers();
185275
}
186-
KeyManagerFactory keyMgrFactory = KeyManagerFactory
187-
.getInstance(SSLFactory.SSLCERTIFICATE);
188-
189-
keyMgrFactory.init(keystore, (keystoreKeyPassword != null) ?
190-
keystoreKeyPassword.toCharArray() : null);
191-
keyManagers = keyMgrFactory.getKeyManagers();
192276

193277
//trust store
194278
String truststoreType =
@@ -199,33 +283,7 @@ public void init(SSLFactory.Mode mode)
199283
resolvePropertyName(mode, SSL_TRUSTSTORE_LOCATION_TPL_KEY);
200284
String truststoreLocation = conf.get(locationProperty, "");
201285
if (!truststoreLocation.isEmpty()) {
202-
String passwordProperty = resolvePropertyName(mode,
203-
SSL_TRUSTSTORE_PASSWORD_TPL_KEY);
204-
String truststorePassword = getPassword(conf, passwordProperty, "");
205-
if (truststorePassword.isEmpty()) {
206-
// An empty trust store password is legal; the trust store password
207-
// is only required when writing to a trust store. Otherwise it's
208-
// an optional integrity check.
209-
truststorePassword = null;
210-
}
211-
long truststoreReloadInterval =
212-
conf.getLong(
213-
resolvePropertyName(mode, SSL_TRUSTSTORE_RELOAD_INTERVAL_TPL_KEY),
214-
DEFAULT_SSL_TRUSTSTORE_RELOAD_INTERVAL);
215-
216-
if (LOG.isDebugEnabled()) {
217-
LOG.debug(mode.toString() + " TrustStore: " + truststoreLocation);
218-
}
219-
220-
trustManager = new ReloadingX509TrustManager(truststoreType,
221-
truststoreLocation,
222-
truststorePassword,
223-
truststoreReloadInterval);
224-
trustManager.init();
225-
if (LOG.isDebugEnabled()) {
226-
LOG.debug(mode.toString() + " Loaded TrustStore: " + truststoreLocation);
227-
}
228-
trustManagers = new TrustManager[]{trustManager};
286+
createTrustManagersFromConfiguration(mode, truststoreType, truststoreLocation, storesReloadInterval);
229287
} else {
230288
if (LOG.isDebugEnabled()) {
231289
LOG.debug("The property '" + locationProperty + "' has not been set, " +
@@ -256,7 +314,7 @@ String getPassword(Configuration conf, String alias, String defaultPass) {
256314
@Override
257315
public synchronized void destroy() {
258316
if (trustManager != null) {
259-
trustManager.destroy();
317+
fileMonitoringTimer.cancel();
260318
trustManager = null;
261319
keyManagers = null;
262320
trustManagers = null;

0 commit comments

Comments
 (0)