storm-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From etha...@apache.org
Subject [storm] branch master updated: [STORM-3636] Enable SSL credentials auto reload (#3269)
Date Thu, 14 May 2020 21:54:59 GMT
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) <ethanopensource@gmail.com>
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("/");


Mime
View raw message