This is an automated email from the ASF dual-hosted git repository.

exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git


The following commit(s) were added to refs/heads/main by this push:
     new 54a0e27  NIFI-7134: Adding auto-reloading of Keystore and Truststore
54a0e27 is described below

commit 54a0e27c937aeef98e17e999a6e61591a46bf91c
Author: Joe Gresock <[email protected]>
AuthorDate: Thu Apr 29 07:41:04 2021 -0400

    NIFI-7134: Adding auto-reloading of Keystore and Truststore
    
    - NIFI-7261 Included TrustStoreScanner for auto-reloading of truststore
    
    This closes #4991
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../java/org/apache/nifi/util/NiFiProperties.java  |  19 +++
 .../src/main/asciidoc/administration-guide.adoc    |  15 ++
 .../nifi-framework/nifi-resources/pom.xml          |   2 +
 .../src/main/resources/conf/nifi.properties        |   2 +
 .../org/apache/nifi/web/server/JettyServer.java    |  28 ++++
 .../nifi/web/server/util/TrustStoreScanner.java    | 151 +++++++++++++++++++++
 .../web/server/util/TrustStoreScannerTest.java     |  94 +++++++++++++
 7 files changed, 311 insertions(+)

diff --git 
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
 
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index d4da56e..7370fa6 100644
--- 
a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ 
b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -153,6 +153,8 @@ public abstract class NiFiProperties {
     public static final String SECURITY_TRUSTSTORE = 
"nifi.security.truststore";
     public static final String SECURITY_TRUSTSTORE_TYPE = 
"nifi.security.truststoreType";
     public static final String SECURITY_TRUSTSTORE_PASSWD = 
"nifi.security.truststorePasswd";
+    public static final String SECURITY_AUTO_RELOAD_ENABLED = 
"nifi.security.autoreload.enabled";
+    public static final String SECURITY_AUTO_RELOAD_INTERVAL = 
"nifi.security.autoreload.interval";
     public static final String SECURITY_USER_AUTHORIZER = 
"nifi.security.user.authorizer";
     public static final String SECURITY_ANONYMOUS_AUTHENTICATION = 
"nifi.security.allow.anonymous.authentication";
     public static final String SECURITY_USER_LOGIN_IDENTITY_PROVIDER = 
"nifi.security.user.login.identity.provider";
@@ -328,6 +330,7 @@ public abstract class NiFiProperties {
     public static final String DEFAULT_ZOOKEEPER_AUTH_TYPE = "default";
     public static final String 
DEFAULT_ZOOKEEPER_KERBEROS_REMOVE_HOST_FROM_PRINCIPAL = "true";
     public static final String 
DEFAULT_ZOOKEEPER_KERBEROS_REMOVE_REALM_FROM_PRINCIPAL = "true";
+    public static final String DEFAULT_SECURITY_AUTO_RELOAD_INTERVAL = "10 
secs";
     public static final String DEFAULT_SITE_TO_SITE_HTTP_TRANSACTION_TTL = "30 
secs";
     public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_ENABLED = 
"true";
     public static final String DEFAULT_FLOW_CONFIGURATION_ARCHIVE_MAX_TIME = 
"30 days";
@@ -762,6 +765,22 @@ public abstract class NiFiProperties {
         return getProperty(UI_AUTO_REFRESH_INTERVAL);
     }
 
+    /**
+     * Returns true if auto reload of the keystore and truststore is enabled.
+     * @return true if auto reload of the keystore and truststore is enabled.
+     */
+    public boolean isSecurityAutoReloadEnabled() {
+        return this.getProperty(SECURITY_AUTO_RELOAD_ENABLED, 
Boolean.FALSE.toString()).equals(Boolean.TRUE.toString());
+    }
+
+    /**
+     * Returns the auto reload interval of the keystore and truststore.
+     * @return The interval over which the keystore and truststore should 
auto-reload.
+     */
+    public String getSecurityAutoReloadInterval() {
+        return getProperty(SECURITY_AUTO_RELOAD_INTERVAL, 
DEFAULT_SECURITY_AUTO_RELOAD_INTERVAL);
+    }
+
     // getters for cluster protocol properties //
     public String getClusterProtocolHeartbeatInterval() {
         return getProperty(CLUSTER_PROTOCOL_HEARTBEAT_INTERVAL,
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc 
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index e4ab517..2c306bf 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -199,6 +199,19 @@ Now that the User Interface has been secured, we can 
easily secure Site-to-Site
 accomplished by setting the `nifi.remote.input.secure` and 
`nifi.cluster.protocol.is.secure` properties, respectively, to `true`. These 
communications
 will always REQUIRE two way SSL as the nodes will use their configured 
keystore/truststore for authentication.
 
+Automatic refreshing of NiFi's web SSL context factory can be enabled using 
the following properties:
+
+[options="header,footer"]
+|==================================================================================================================================================
+| Property Name | Description
+|`nifi.security.autoreload.enabled`|Specifies whether the SSL context factory 
should be automatically reloaded if updates to the keystore and truststore are 
detected. By default, it is set to `false`.
+|`nifi.security.autoreload.interval`|Specifies the interval at which the 
keystore and truststore are checked for updates. Only applies if 
`nifi.security.autoreload.enabled` is set to `true`. The default value is `10 
secs`.
+|==================================================================================================================================================
+
+Once the `nifi.security.autoreload.enabled` property is set to `true`, any 
valid changes to the configured keystore and truststore will cause NiFi's SSL 
context factory to be reloaded, allowing clients to pick up the changes.  This 
is intended to allow expired certificates to be updated in the keystore and new 
trusted certificates to be added in the truststore, all without having to 
restart the NiFi server.
+
+NOTE: Changes to any of the `nifi.security.keystore*` or 
`nifi.security.truststore*` properties will not be picked up by the 
auto-refreshing logic, which assumes the passwords and store paths will remain 
the same.
+
 [[tls_generation_toolkit]]
 === TLS Generation Toolkit
 
@@ -3560,6 +3573,8 @@ These properties pertain to various security features in 
NiFi. Many of these pro
 |`nifi.sensitive.props.algorithm`|The algorithm used to encrypt sensitive 
properties. The default value is `PBEWITHMD5AND256BITAES-CBC-OPENSSL`.
 |`nifi.sensitive.props.provider`|The sensitive property provider. The default 
value is `BC`.
 |`nifi.sensitive.props.additional.keys`|The comma separated list of properties 
in _nifi.properties_ to encrypt in addition to the default sensitive properties 
(see <<encrypt-config_tool>>).
+|`nifi.security.autoreload.enabled`|Specifies whether the SSL context factory 
should be automatically reloaded if updates to the keystore and truststore are 
detected. By default, it is set to `false`.
+|`nifi.security.autoreload.interval`|Specifies the interval at which the 
keystore and truststore are checked for updates. Only applies if 
`nifi.security.autoreload.enabled` is set to `true`. The default value is `10 
secs`.
 |`nifi.security.keystore`*|The full path and name of the keystore. It is blank 
by default.
 |`nifi.security.keystoreType`|The keystore type. It is blank by default.
 |`nifi.security.keystorePasswd`|The keystore password. It is blank by default.
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 43eed61..29831d5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -148,6 +148,8 @@
         <nifi.web.request.ip.whitelist />
         
<nifi.web.should.send.server.version>true</nifi.web.should.send.server.version>
         <!-- nifi.properties: security properties -->
+        
<nifi.security.autoreload.enabled>false</nifi.security.autoreload.enabled>
+        <nifi.security.autoreload.interval>10 
secs</nifi.security.autoreload.interval>
         <!-- Use these values once we change default configuration to be HTTPS
         <nifi.security.keystore>./conf/keystore.p12</nifi.security.keystore>
         <nifi.security.keystoreType>PKCS12</nifi.security.keystoreType> -->
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index e0a47d3..12baa86 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -178,6 +178,8 @@ 
nifi.sensitive.props.algorithm=${nifi.sensitive.props.algorithm}
 nifi.sensitive.props.provider=${nifi.sensitive.props.provider}
 nifi.sensitive.props.additional.keys=${nifi.sensitive.props.additional.keys}
 
+nifi.security.autoreload.enabled=${nifi.security.autoreload.enabled}
+nifi.security.autoreload.interval=${nifi.security.autoreload.interval}
 nifi.security.keystore=${nifi.security.keystore}
 nifi.security.keystoreType=${nifi.security.keystoreType}
 nifi.security.keystorePasswd=${nifi.security.keystorePasswd}
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index 16fa44c..3804ea7 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -58,6 +58,7 @@ import 
org.apache.nifi.web.security.headers.XContentTypeOptionsFilter;
 import org.apache.nifi.web.security.headers.XFrameOptionsFilter;
 import org.apache.nifi.web.security.headers.XSSProtectionFilter;
 import org.apache.nifi.web.security.requests.ContentLengthFilter;
+import org.apache.nifi.web.server.util.TrustStoreScanner;
 import org.eclipse.jetty.annotations.AnnotationConfiguration;
 import org.eclipse.jetty.deploy.App;
 import org.eclipse.jetty.deploy.DeploymentManager;
@@ -77,6 +78,7 @@ import org.eclipse.jetty.servlet.DefaultServlet;
 import org.eclipse.jetty.servlet.FilterHolder;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.eclipse.jetty.servlets.DoSFilter;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
 import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.webapp.Configuration;
@@ -150,6 +152,7 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
     private ExtensionMapping extensionMapping;
     private NarAutoLoader narAutoLoader;
     private DiagnosticsFactory diagnosticsFactory;
+    private SslContextFactory.Server sslContextFactory;
 
     private WebAppContext webApiContext;
     private WebAppContext webDocsContext;
@@ -315,6 +318,7 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
         return gzip(webAppContextHandlers);
     }
 
+
     @Override
     public void loadExtensionUis(final Set<Bundle> bundles) {
         // Find and load any WARs contained within the set of bundles...
@@ -880,6 +884,29 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
         ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc 
= (s, c) -> createUnconfiguredSslServerConnector(s, c, port);
 
         configureGenericConnector(server, httpConfiguration, hostname, port, 
connectorLabel, httpsNetworkInterfaces, scc);
+
+        if (props.isSecurityAutoReloadEnabled()) {
+            configureSslContextFactoryReloading(server);
+        }
+    }
+
+    /**
+     * Configures a KeyStoreScanner and TrustStoreScanner at the configured 
reload intervals.  This will
+     * reload the SSLContextFactory if any changes are detected to the 
keystore or truststore.
+     * @param server The Jetty server
+     */
+    private void configureSslContextFactoryReloading(Server server) {
+        final int scanIntervalSeconds = 
Double.valueOf(FormatUtils.getPreciseTimeDuration(
+                props.getSecurityAutoReloadInterval(), TimeUnit.SECONDS))
+                .intValue();
+
+        final KeyStoreScanner keyStoreScanner = new 
KeyStoreScanner(sslContextFactory);
+        keyStoreScanner.setScanInterval(scanIntervalSeconds);
+        server.addBean(keyStoreScanner);
+
+        final TrustStoreScanner trustStoreScanner = new 
TrustStoreScanner(sslContextFactory);
+        trustStoreScanner.setScanInterval(scanIntervalSeconds);
+        server.addBean(trustStoreScanner);
     }
 
     /**
@@ -1008,6 +1035,7 @@ public class JettyServer implements NiFiServer, 
ExtensionUiLoader {
     private SslContextFactory createSslContextFactory() {
         final SslContextFactory.Server serverContextFactory = new 
SslContextFactory.Server();
         configureSslContextFactory(serverContextFactory, props);
+        this.sslContextFactory = serverContextFactory;
         return serverContextFactory;
     }
 
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/util/TrustStoreScanner.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/util/TrustStoreScanner.java
new file mode 100644
index 0000000..22913ed
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/util/TrustStoreScanner.java
@@ -0,0 +1,151 @@
+/*
+ * 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.nifi.web.server.util;
+
+import org.eclipse.jetty.util.Scanner;
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedOperation;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collections;
+
+/**
+ * <p>The {@link TrustStoreScanner} is used to monitor the TrustStore file 
used by the {@link SslContextFactory}.
+ * It will reload the {@link SslContextFactory} if it detects that the 
TrustStore file has been modified.</p>
+ * <p>
+ * Though it would have been more ideal to simply extend KeyStoreScanner and 
override the keystore resource
+ * with the truststore resource, KeyStoreScanner's constructor was written in 
a way that doesn't make this possible.
+ */
+public class TrustStoreScanner extends ContainerLifeCycle implements 
Scanner.DiscreteListener {
+    private static final Logger LOG = Log.getLogger(TrustStoreScanner.class);
+
+    private final SslContextFactory sslContextFactory;
+    private final File truststoreFile;
+    private final Scanner _scanner;
+
+    public TrustStoreScanner(SslContextFactory sslContextFactory) {
+        this.sslContextFactory = sslContextFactory;
+        try {
+            Resource truststoreResource = 
sslContextFactory.getTrustStoreResource();
+            File monitoredFile = truststoreResource.getFile();
+            if (monitoredFile == null || !monitoredFile.exists()) {
+                throw new IllegalArgumentException("truststore file does not 
exist");
+            }
+            if (monitoredFile.isDirectory()) {
+                throw new IllegalArgumentException("expected truststore file 
not directory");
+            }
+
+            if (truststoreResource.getAlias() != null) {
+                // this resource has an alias, use the alias, as that's what's 
returned in the Scanner
+                monitoredFile = new File(truststoreResource.getAlias());
+            }
+
+            truststoreFile = monitoredFile;
+            if (LOG.isDebugEnabled()) {
+                LOG.debug("Monitored Truststore File: {}", monitoredFile);
+            }
+        } catch (IOException e) {
+            throw new IllegalArgumentException("could not obtain truststore 
file", e);
+        }
+
+        File parentFile = truststoreFile.getParentFile();
+        if (!parentFile.exists() || !parentFile.isDirectory()) {
+            throw new IllegalArgumentException("error obtaining truststore 
dir");
+        }
+
+        _scanner = new Scanner();
+        _scanner.setScanDirs(Collections.singletonList(parentFile));
+        _scanner.setScanInterval(1);
+        _scanner.setReportDirs(false);
+        _scanner.setReportExistingFilesOnStartup(false);
+        _scanner.setScanDepth(1);
+        _scanner.addListener(this);
+        addBean(_scanner);
+    }
+
+    @Override
+    public void fileAdded(String filename) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("added {}", filename);
+        }
+
+        if (truststoreFile.toPath().toString().equals(filename)) {
+            reload();
+        }
+    }
+
+    @Override
+    public void fileChanged(String filename) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("changed {}", filename);
+        }
+
+        if (truststoreFile.toPath().toString().equals(filename)) {
+            reload();
+        }
+    }
+
+    @Override
+    public void fileRemoved(String filename) {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("removed {}", filename);
+        }
+
+        if (truststoreFile.toPath().toString().equals(filename)) {
+            reload();
+        }
+    }
+
+    @ManagedOperation(value = "Scan for changes in the SSL Truststore", impact 
= "ACTION")
+    public void scan() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("scanning");
+        }
+
+        _scanner.scan();
+        _scanner.scan();
+    }
+
+    @ManagedOperation(value = "Reload the SSL Truststore", impact = "ACTION")
+    public void reload() {
+        if (LOG.isDebugEnabled()) {
+            LOG.debug("reloading truststore file {}", truststoreFile);
+        }
+
+        try {
+            sslContextFactory.reload(scf -> {
+            });
+        } catch (Throwable t) {
+            LOG.warn("Truststore Reload Failed", t);
+        }
+    }
+
+    @ManagedAttribute("scanning interval to detect changes which need 
reloaded")
+    public int getScanInterval() {
+        return _scanner.getScanInterval();
+    }
+
+    public void setScanInterval(int scanInterval) {
+        _scanner.setScanInterval(scanInterval);
+    }
+}
\ No newline at end of file
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/util/TrustStoreScannerTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/util/TrustStoreScannerTest.java
new file mode 100644
index 0000000..fe7e021
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/util/TrustStoreScannerTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.nifi.web.server.util;
+
+import org.apache.nifi.security.util.KeyStoreUtils;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.eclipse.jetty.util.resource.Resource;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.GeneralSecurityException;
+import java.util.function.Consumer;
+
+public class TrustStoreScannerTest {
+
+    private TrustStoreScanner scanner;
+    private SslContextFactory sslContextFactory;
+    private static File keyStoreFile;
+    private static File trustStoreFile;
+
+    @BeforeClass
+    public static void initClass() throws GeneralSecurityException, 
IOException {
+        TlsConfiguration tlsConfiguration = 
KeyStoreUtils.createTlsConfigAndNewKeystoreTruststore();
+        keyStoreFile = Paths.get(tlsConfiguration.getKeystorePath()).toFile();
+        trustStoreFile = 
Paths.get(tlsConfiguration.getTruststorePath()).toFile();
+    }
+
+    @Before
+    public void init() throws IOException {
+        sslContextFactory = Mockito.mock(SslContextFactory.class);
+        Resource trustStoreResource = Mockito.mock(Resource.class);
+        Mockito.when(trustStoreResource.getFile()).thenReturn(trustStoreFile);
+        
Mockito.when(sslContextFactory.getTrustStoreResource()).thenReturn(trustStoreResource);
+
+        scanner = new TrustStoreScanner(sslContextFactory);
+    }
+
+    @Test
+    public void fileAdded() throws Exception {
+        scanner.fileAdded(trustStoreFile.getAbsolutePath());
+
+        
Mockito.verify(sslContextFactory).reload(ArgumentMatchers.any(Consumer.class));
+    }
+
+    @Test
+    public void fileChanged() throws Exception {
+        scanner.fileChanged(trustStoreFile.getAbsolutePath());
+
+        
Mockito.verify(sslContextFactory).reload(ArgumentMatchers.any(Consumer.class));
+    }
+
+    @Test
+    public void fileRemoved() throws Exception {
+        scanner.fileRemoved(trustStoreFile.getAbsolutePath());
+
+        
Mockito.verify(sslContextFactory).reload(ArgumentMatchers.any(Consumer.class));
+    }
+
+    @Test
+    public void reload() throws Exception {
+        scanner.reload();
+
+        
Mockito.verify(sslContextFactory).reload(ArgumentMatchers.any(Consumer.class));
+    }
+
+    @AfterClass
+    public static void tearDown() throws IOException {
+        Files.deleteIfExists(keyStoreFile.toPath());
+        Files.deleteIfExists(trustStoreFile.toPath());
+    }
+}

Reply via email to