This is an automated email from the ASF dual-hosted git repository.
ethanli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/storm.git
The following commit(s) were added to refs/heads/master by this push:
new 4aa4797 [STORM-3636] Enable SSL credentials auto reload (#3269)
4aa4797 is described below
commit 4aa4797787675e604571a5f6a9f596d9f6b13d83
Author: Meng Li (Ethan) <[email protected]>
AuthorDate: Thu May 14 16:54:45 2020 -0500
[STORM-3636] Enable SSL credentials auto reload (#3269)
---
.../main/java/org/apache/storm/DaemonConfig.java | 18 ++++
.../apache/storm/daemon/common/FileWatcher.java | 96 ++++++++++++++++++++++
.../daemon/common/ReloadableSslContextFactory.java | 84 +++++++++++++++++++
.../org/apache/storm/daemon/drpc/DRPCServer.java | 3 +-
.../storm/daemon/logviewer/LogviewerServer.java | 4 +-
.../java/org/apache/storm/daemon/ui/UIHelpers.java | 15 ++--
.../java/org/apache/storm/daemon/ui/UIServer.java | 3 +-
7 files changed, 214 insertions(+), 9 deletions(-)
diff --git a/storm-server/src/main/java/org/apache/storm/DaemonConfig.java
b/storm-server/src/main/java/org/apache/storm/DaemonConfig.java
index 0918a72..96b6e21 100644
--- a/storm-server/src/main/java/org/apache/storm/DaemonConfig.java
+++ b/storm-server/src/main/java/org/apache/storm/DaemonConfig.java
@@ -483,6 +483,12 @@ public class DaemonConfig implements Validated {
public static final String LOGVIEWER_HTTPS_NEED_CLIENT_AUTH =
"logviewer.https.need.client.auth";
/**
+ * If set to true, keystore and truststore for Logviewer will be
automatically reloaded when modified.
+ */
+ @IsBoolean
+ public static final String LOGVIEWER_HTTPS_ENABLE_SSL_RELOAD =
"logviewer.https.enable.ssl.reload";
+
+ /**
* A list of users allowed to view logs via the Log Viewer.
*/
@IsStringOrStringList
@@ -603,6 +609,12 @@ public class DaemonConfig implements Validated {
public static final String UI_HTTPS_NEED_CLIENT_AUTH =
"ui.https.need.client.auth";
/**
+ * If set to true, keystore and truststore for UI will be automatically
reloaded when modified.
+ */
+ @IsBoolean
+ public static final String UI_HTTPS_ENABLE_SSL_RELOAD =
"ui.https.enable.ssl.reload";
+
+ /**
* The maximum number of threads that should be used by the Pacemaker.
When Pacemaker gets loaded it will spawn new threads, up to this
* many total, to handle the load.
*/
@@ -688,6 +700,12 @@ public class DaemonConfig implements Validated {
public static final String DRPC_HTTPS_NEED_CLIENT_AUTH =
"drpc.https.need.client.auth";
/**
+ * If set to true, keystore and truststore for DRPC Server will be
automatically reloaded when modified.
+ */
+ @IsBoolean
+ public static final String DRPC_HTTPS_ENABLE_SSL_RELOAD =
"drpc.https.enable.ssl.reload";
+
+ /**
* Class name for authorization plugin for DRPC client.
*/
@IsString
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/common/FileWatcher.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/common/FileWatcher.java
new file mode 100644
index 0000000..b9926f3
--- /dev/null
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/common/FileWatcher.java
@@ -0,0 +1,96 @@
+/*
+ * 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.storm.daemon.common;
+
+import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.WatchEvent;
+import java.nio.file.WatchKey;
+import java.nio.file.WatchService;
+import java.util.Collections;
+import java.util.List;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FileWatcher implements Runnable {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(FileWatcher.class);
+
+ private final WatchService watcher;
+ private volatile boolean stopped = false;
+ private final Path watchedFile;
+ private final Callback callback;
+ List<WatchEvent.Kind<Path>> kinds;
+
+ public FileWatcher(final Path watchedFile, Callback callback) throws
IOException {
+ this(watchedFile, callback, Collections.singletonList(ENTRY_MODIFY));
+ }
+
+ public FileWatcher(final Path watchedFile, Callback callback,
List<WatchEvent.Kind<Path>> kinds) throws IOException {
+ this.watchedFile = watchedFile;
+ this.callback = callback;
+ Path parent = watchedFile.getParent();
+ this.watcher = parent.getFileSystem().newWatchService();
+ this.kinds = kinds;
+ parent.register(watcher, this.kinds.toArray(new WatchEvent.Kind[0]));
+ }
+
+ public void start() {
+ Thread t = new Thread(this, "FileWatcher-" +
watchedFile.getFileName());
+ t.setDaemon(true);
+ LOG.info("Starting FileWatcher on {}", watchedFile);
+ t.start();
+ }
+
+ public void stop() {
+ LOG.info("Stopping FileWatcher on {}", watchedFile);
+ this.stopped = true;
+ }
+
+ @Override
+ public void run() {
+ while (!stopped) {
+ WatchKey watchKey;
+ try {
+ watchKey = watcher.take();
+ } catch (InterruptedException ex) {
+ LOG.warn("FileWatch for {} is interrupted", watchedFile, ex);
+ Thread.currentThread().interrupt();
+ return;
+ }
+ for (WatchEvent<?> event : watchKey.pollEvents()) {
+ if (this.kinds.contains(event.kind()) &&
event.context().equals(watchedFile.getFileName())) {
+ try {
+ LOG.info("Event {} on {}; invoking callback",
event.kind(), watchedFile);
+ callback.run();
+ } catch (Exception ex) {
+ LOG.error("Error invoking FileWatcher callback for
{}", watchedFile, ex);
+ }
+ }
+ }
+ watchKey.reset();
+ }
+ }
+
+ public interface Callback {
+ void run() throws Exception;
+ }
+}
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/common/ReloadableSslContextFactory.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/common/ReloadableSslContextFactory.java
new file mode 100644
index 0000000..067b13d
--- /dev/null
+++
b/storm-webapp/src/main/java/org/apache/storm/daemon/common/ReloadableSslContextFactory.java
@@ -0,0 +1,84 @@
+/*
+ * 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.storm.daemon.common;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ReloadableSslContextFactory extends SslContextFactory {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ReloadableSslContextFactory.class);
+
+ private boolean enableSslReload;
+ private FileWatcher keyStoreWatcher;
+ private FileWatcher trustStoreWatcher;
+
+ public ReloadableSslContextFactory() {
+ this(false);
+ }
+
+ public ReloadableSslContextFactory(boolean enableSslReload) {
+ this.enableSslReload = enableSslReload;
+ }
+
+ @Override
+ protected void doStart() throws Exception {
+ super.doStart();
+ if (enableSslReload) {
+ LOG.info("Enabling reloading of SSL credentials without server
restart");
+
+ String keyStorePathStr = getKeyStorePath();
+ if (keyStorePathStr != null) {
+ Path keyStorePath =
Paths.get(URI.create(keyStorePathStr).getPath());
+ FileWatcher.Callback keyStoreWatcherCallback = () ->
+ ReloadableSslContextFactory.this.reload((scf) ->
LOG.info("Reloading SslContextFactory due to keystore change"));
+ keyStoreWatcher = new FileWatcher(keyStorePath,
keyStoreWatcherCallback);
+ keyStoreWatcher.start();
+ } else {
+ LOG.warn("KeyStore is null; it won't be watched/reloaded");
+ }
+
+ String trustStorePathStr = getTrustStorePath();
+ if (trustStorePathStr != null) {
+ Path trustStorePath =
Paths.get(URI.create(trustStorePathStr).getPath());
+ FileWatcher.Callback trustStoreWatcherCallback = () ->
+ ReloadableSslContextFactory.this.reload((scf) ->
LOG.info("Reloading SslContextFactory due to truststore change"));
+ trustStoreWatcher = new FileWatcher(trustStorePath,
trustStoreWatcherCallback);
+ trustStoreWatcher.start();
+ } else {
+ LOG.warn("TrustStore is null; it won't be watched/reloaded");
+ }
+ }
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ if (keyStoreWatcher != null) {
+ keyStoreWatcher.stop();
+ }
+ if (trustStoreWatcher != null) {
+ trustStoreWatcher.stop();
+ }
+ super.doStop();
+ }
+}
\ No newline at end of file
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/DRPCServer.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/DRPCServer.java
index e87ec52..54ad7c3 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/DRPCServer.java
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/drpc/DRPCServer.java
@@ -104,13 +104,14 @@ public class DRPCServer implements AutoCloseable {
final Boolean httpsWantClientAuth = (Boolean)
(conf.get(DaemonConfig.DRPC_HTTPS_WANT_CLIENT_AUTH));
final Boolean httpsNeedClientAuth = (Boolean)
(conf.get(DaemonConfig.DRPC_HTTPS_NEED_CLIENT_AUTH));
final Boolean disableHttpBinding = (Boolean)
(conf.get(DaemonConfig.DRPC_DISABLE_HTTP_BINDING));
+ final boolean enableSslReload =
ObjectReader.getBoolean(conf.get(DaemonConfig.DRPC_HTTPS_ENABLE_SSL_RELOAD),
false);
//TODO a better way to do this would be great.
DRPCApplication.setup(drpc, metricsRegistry);
ret = UIHelpers.jettyCreateServer(drpcHttpPort, null, httpsPort,
disableHttpBinding);
UIHelpers.configSsl(ret, httpsPort, httpsKsPath, httpsKsPassword,
httpsKsType, httpsKeyPassword,
- httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth);
+ httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth, enableSslReload);
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.NO_SESSIONS);
context.setContextPath("/");
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/logviewer/LogviewerServer.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/logviewer/LogviewerServer.java
index 46b8fbb..b60d496 100644
---
a/storm-webapp/src/main/java/org/apache/storm/daemon/logviewer/LogviewerServer.java
+++
b/storm-webapp/src/main/java/org/apache/storm/daemon/logviewer/LogviewerServer.java
@@ -85,12 +85,14 @@ public class LogviewerServer implements AutoCloseable {
final Boolean httpsWantClientAuth = (Boolean)
(conf.get(DaemonConfig.LOGVIEWER_HTTPS_WANT_CLIENT_AUTH));
final Boolean httpsNeedClientAuth = (Boolean)
(conf.get(DaemonConfig.LOGVIEWER_HTTPS_NEED_CLIENT_AUTH));
final Boolean disableHttpBinding = (Boolean)
(conf.get(DaemonConfig.LOGVIEWER_DISABLE_HTTP_BINDING));
+ final boolean enableSslReload =
ObjectReader.getBoolean(conf.get(DaemonConfig.LOGVIEWER_HTTPS_ENABLE_SSL_RELOAD),
false);
+
LogviewerApplication.setup(conf, metricsRegistry);
ret = UIHelpers.jettyCreateServer(logviewerHttpPort, null,
httpsPort, disableHttpBinding);
UIHelpers.configSsl(ret, httpsPort, httpsKsPath, httpsKsPassword,
httpsKsType, httpsKeyPassword,
- httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth);
+ httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth, enableSslReload);
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.NO_SESSIONS);
try {
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java
index 784ba0c..a025b95 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIHelpers.java
@@ -47,6 +47,7 @@ import javax.ws.rs.core.SecurityContext;
import org.apache.storm.Config;
import org.apache.storm.Constants;
import org.apache.storm.DaemonConfig;
+import org.apache.storm.daemon.common.ReloadableSslContextFactory;
import org.apache.storm.generated.Bolt;
import org.apache.storm.generated.BoltAggregateStats;
import org.apache.storm.generated.ClusterSummary;
@@ -230,8 +231,8 @@ public class UIHelpers {
String keyPassword, String
tsPath,
String tsPassword, String
tsType,
Boolean needClientAuth,
Boolean wantClientAuth,
- Integer headerBufferSize) {
- SslContextFactory factory = new SslContextFactory();
+ Integer headerBufferSize,
boolean enableSslReload) {
+ SslContextFactory factory = new
ReloadableSslContextFactory(enableSslReload);
factory.setExcludeCipherSuites("SSL_RSA_WITH_RC4_128_MD5",
"SSL_RSA_WITH_RC4_128_SHA");
factory.setExcludeProtocols("SSLv3");
factory.setRenegotiationAllowed(false);
@@ -270,9 +271,9 @@ public class UIHelpers {
String ksPassword, String ksType,
String keyPassword, String tsPath,
String tsPassword, String tsType,
- Boolean needClientAuth, Boolean
wantClientAuth) {
+ Boolean needClientAuth, Boolean
wantClientAuth, boolean enableSslReload) {
configSsl(server, port, ksPath, ksPassword, ksType, keyPassword,
- tsPath, tsPassword, tsType, needClientAuth, wantClientAuth,
null);
+ tsPath, tsPassword, tsType, needClientAuth, wantClientAuth,
null, enableSslReload);
}
/**
@@ -289,19 +290,21 @@ public class UIHelpers {
* @param needClientAuth needClientAuth
* @param wantClientAuth wantClientAuth
* @param headerBufferSize headerBufferSize
+ * @param enableSslReload enable ssl reload
*/
public static void configSsl(Server server, Integer port, String ksPath,
String ksPassword, String ksType,
String keyPassword, String tsPath,
String tsPassword, String tsType,
Boolean needClientAuth,
- Boolean wantClientAuth, Integer
headerBufferSize) {
+ Boolean wantClientAuth, Integer
headerBufferSize,
+ boolean enableSslReload) {
if (port > 0) {
server.addConnector(
mkSslConnector(
server, port, ksPath, ksPassword, ksType,
keyPassword,
tsPath, tsPassword, tsType,
- needClientAuth, wantClientAuth, headerBufferSize
+ needClientAuth, wantClientAuth, headerBufferSize,
enableSslReload
)
);
}
diff --git
a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIServer.java
b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIServer.java
index 0b292a4..71caf39 100644
--- a/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIServer.java
+++ b/storm-webapp/src/main/java/org/apache/storm/daemon/ui/UIServer.java
@@ -96,13 +96,14 @@ public class UIServer {
final Boolean httpsWantClientAuth = (Boolean)
(conf.get(DaemonConfig.UI_HTTPS_WANT_CLIENT_AUTH));
final Boolean httpsNeedClientAuth = (Boolean)
(conf.get(DaemonConfig.UI_HTTPS_NEED_CLIENT_AUTH));
final Boolean disableHttpBinding = (Boolean)
(conf.get(DaemonConfig.UI_DISABLE_HTTP_BINDING));
+ final boolean enableSslReload =
ObjectReader.getBoolean(conf.get(DaemonConfig.UI_HTTPS_ENABLE_SSL_RELOAD),
false);
Server jettyServer =
UIHelpers.jettyCreateServer(
(int) conf.get(DaemonConfig.UI_PORT), null, httpsPort,
headerBufferSize, disableHttpBinding);
UIHelpers.configSsl(jettyServer, httpsPort, httpsKsPath,
httpsKsPassword, httpsKsType, httpsKeyPassword,
- httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth);
+ httpsTsPath, httpsTsPassword, httpsTsType,
httpsNeedClientAuth, httpsWantClientAuth, enableSslReload);
ServletContextHandler context = new
ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");