You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jo...@apache.org on 2020/03/16 16:48:09 UTC

[nifi] 01/04: NIFI-7223 - Fixed a minor issue where the OkHttpReplicationClient class loaded blank properties as empty string instead of an expected null value. Added a isNotBlank check. Added unit tests for replication client and HTTPNotificationService. NIFI-7223 - Renamed some variables and methods. NIFI-7223 - Removed unused dependency. Corrected security properties in administration-guide.

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

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

commit 7374361b5c6398a92577479d297e55dbe7ea2f63
Author: Nathan Gough <th...@gmail.com>
AuthorDate: Tue Mar 10 21:22:29 2020 +1100

    NIFI-7223 - Fixed a minor issue where the OkHttpReplicationClient class loaded blank properties as empty string instead of an expected null value. Added a isNotBlank check. Added unit tests for replication client and HTTPNotificationService.
    NIFI-7223 - Renamed some variables and methods.
    NIFI-7223 - Removed unused dependency. Corrected security properties in administration-guide.
---
 nifi-bootstrap/pom.xml                             |   5 +
 .../http/TestHttpNotificationServiceSSL.java       | 190 ++++++++++++++++++++-
 .../src/main/asciidoc/administration-guide.adoc    |   5 +-
 .../okhttp/OkHttpReplicationClient.java            |  11 +-
 .../okhttp/OkHttpReplicationClientTest.groovy      |  75 ++++++++
 .../src/test/resources/conf/keystore.jks           | Bin 0 -> 3088 bytes
 .../src/test/resources/conf/truststore.jks         | Bin 0 -> 911 bytes
 7 files changed, 273 insertions(+), 13 deletions(-)

diff --git a/nifi-bootstrap/pom.xml b/nifi-bootstrap/pom.xml
index c92a5fa..d1bfb09 100644
--- a/nifi-bootstrap/pom.xml
+++ b/nifi-bootstrap/pom.xml
@@ -73,5 +73,10 @@ language governing permissions and limitations under the License. -->
             <artifactId>jna-platform</artifactId>
             <version>4.4.0</version>
         </dependency>
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/http/TestHttpNotificationServiceSSL.java b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/http/TestHttpNotificationServiceSSL.java
index 5ab8cab..2f54f1c 100644
--- a/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/http/TestHttpNotificationServiceSSL.java
+++ b/nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/http/TestHttpNotificationServiceSSL.java
@@ -16,13 +16,21 @@
  */
 package org.apache.nifi.bootstrap.http;
 
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
 import okhttp3.mockwebserver.MockWebServer;
+import org.apache.nifi.bootstrap.NotificationServiceManager;
 import org.apache.nifi.security.util.SslContextFactory;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 import org.mockito.internal.util.io.IOUtil;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
+import ch.qos.logback.classic.Logger;
 
 import javax.net.ssl.SSLContext;
+import javax.xml.parsers.ParserConfigurationException;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -32,9 +40,12 @@ import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.util.List;
 
-public class TestHttpNotificationServiceSSL extends  TestHttpNotificationServiceCommon{
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
+public class TestHttpNotificationServiceSSL extends TestHttpNotificationServiceCommon {
 
     static final String CONFIGURATION_FILE_TEXT = "\n"+
             "<services>\n"+
@@ -47,14 +58,80 @@ public class TestHttpNotificationServiceSSL extends  TestHttpNotificationService
             "            <property name=\"Truststore Password\">passwordpassword</property>\n"+
             "            <property name=\"Keystore Filename\">./src/test/resources/keystore.jks</property>\n"+
             "            <property name=\"Keystore Type\">JKS</property>\n"+
+            "            <property name=\"Key Password\">passwordpassword</property>\n"+
             "            <property name=\"Keystore Password\">passwordpassword</property>\n"+
             "            <property name=\"testProp\">${literal('testing')}</property>\n"+
             "         </service>\n"+
             "</services>";
 
+    static final String CONFIGURATION_FILE_TEXT_NO_KEYSTORE_PASSWORD = "\n"+
+            "<services>\n"+
+            "         <service>\n"+
+            "            <id>http-notification</id>\n"+
+            "            <class>org.apache.nifi.bootstrap.notification.http.HttpNotificationService</class>\n"+
+            "            <property name=\"URL\">${test.server}</property>\n"+
+            "            <property name=\"Truststore Filename\">./src/test/resources/truststore.jks</property>\n"+
+            "            <property name=\"Truststore Type\">JKS</property>\n"+
+            "            <property name=\"Truststore Password\">passwordpassword</property>\n"+
+            "            <property name=\"Keystore Filename\">./src/test/resources/keystore.jks</property>\n"+
+            "            <property name=\"Keystore Type\">JKS</property>\n"+
+            "            <property name=\"Key Password\">passwordpassword</property>\n"+
+            "            <property name=\"testProp\">${literal('testing')}</property>\n"+
+            "         </service>\n"+
+            "</services>";
+
+    static final String CONFIGURATION_FILE_TEXT_NO_KEY_PASSWORD = "\n"+
+            "<services>\n"+
+            "         <service>\n"+
+            "            <id>http-notification</id>\n"+
+            "            <class>org.apache.nifi.bootstrap.notification.http.HttpNotificationService</class>\n"+
+            "            <property name=\"URL\">${test.server}</property>\n"+
+            "            <property name=\"Truststore Filename\">./src/test/resources/truststore.jks</property>\n"+
+            "            <property name=\"Truststore Type\">JKS</property>\n"+
+            "            <property name=\"Truststore Password\">passwordpassword</property>\n"+
+            "            <property name=\"Keystore Filename\">./src/test/resources/keystore.jks</property>\n"+
+            "            <property name=\"Keystore Type\">JKS</property>\n"+
+            "            <property name=\"Keystore Password\">passwordpassword</property>\n"+
+            "            <property name=\"testProp\">${literal('testing')}</property>\n"+
+            "         </service>\n"+
+            "</services>";
+
+    static final String CONFIGURATION_FILE_TEXT_BLANK_KEY_PASSWORD = "\n"+
+            "<services>\n"+
+            "         <service>\n"+
+            "            <id>http-notification</id>\n"+
+            "            <class>org.apache.nifi.bootstrap.notification.http.HttpNotificationService</class>\n"+
+            "            <property name=\"URL\">${test.server}</property>\n"+
+            "            <property name=\"Truststore Filename\">./src/test/resources/truststore.jks</property>\n"+
+            "            <property name=\"Truststore Type\">JKS</property>\n"+
+            "            <property name=\"Truststore Password\">passwordpassword</property>\n"+
+            "            <property name=\"Keystore Filename\">./src/test/resources/keystore.jks</property>\n"+
+            "            <property name=\"Keystore Type\">JKS</property>\n"+
+            "            <property name=\"Keystore Password\">passwordpassword</property>\n"+
+            "            <property name=\"Key Password\"></property>\n"+
+            "            <property name=\"testProp\">${literal('testing')}</property>\n"+
+            "         </service>\n"+
+            "</services>";
 
-    @BeforeClass
-    public static void startServer() throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
+    static final String CONFIGURATION_FILE_TEXT_BLANK_KEYSTORE_PASSWORD = "\n"+
+            "<services>\n"+
+            "         <service>\n"+
+            "            <id>http-notification</id>\n"+
+            "            <class>org.apache.nifi.bootstrap.notification.http.HttpNotificationService</class>\n"+
+            "            <property name=\"URL\">${test.server}</property>\n"+
+            "            <property name=\"Truststore Filename\">./src/test/resources/truststore.jks</property>\n"+
+            "            <property name=\"Truststore Type\">JKS</property>\n"+
+            "            <property name=\"Truststore Password\">passwordpassword</property>\n"+
+            "            <property name=\"Keystore Filename\">./src/test/resources/keystore.jks</property>\n"+
+            "            <property name=\"Keystore Type\">JKS</property>\n"+
+            "            <property name=\"Keystore Password\"></property>\n"+
+            "            <property name=\"Key Password\">passwordpassword</property>\n"+
+            "            <property name=\"testProp\">${literal('testing')}</property>\n"+
+            "         </service>\n"+
+            "</services>";
+
+    @Before
+    public void startServer() throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
         tempConfigFilePath = "./target/TestHttpNotificationService-config.xml";
 
         Files.deleteIfExists(Paths.get(tempConfigFilePath));
@@ -78,10 +155,109 @@ public class TestHttpNotificationServiceSSL extends  TestHttpNotificationService
         IOUtil.writeText(configFileOutput, new File(tempConfigFilePath));
     }
 
-    @AfterClass
-    public static void shutdownServer() throws IOException {
+    @After
+    public void shutdownServer() throws IOException {
         Files.deleteIfExists(Paths.get(tempConfigFilePath));
         mockWebServer.shutdown();
     }
 
+    @Test
+    public void testStartNotificationSucceedsNoKeystorePasswd() throws ParserConfigurationException, SAXException, IOException {
+        Logger notificationServiceLogger = (Logger) LoggerFactory.getLogger(NotificationServiceManager.class);
+        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+        listAppender.start();
+        notificationServiceLogger.addAppender(listAppender);
+
+        String configFileOutput = CONFIGURATION_FILE_TEXT_NO_KEYSTORE_PASSWORD.replace("${test.server}", String.valueOf(mockWebServer.url("/")));
+        IOUtil.writeText(configFileOutput, new File(tempConfigFilePath));
+
+        NotificationServiceManager notificationServiceManager = new NotificationServiceManager();
+        notificationServiceManager.setMaxNotificationAttempts(1);
+        notificationServiceManager.loadNotificationServices(new File(tempConfigFilePath));
+
+        List<ILoggingEvent> logsList = listAppender.list;
+        boolean notificationServiceFailed = false;
+        for(ILoggingEvent logMessage : logsList) {
+            if(logMessage.getFormattedMessage().contains("is not valid for the following reasons")) {
+                    notificationServiceFailed = true;
+            }
+        }
+
+        assertFalse(notificationServiceFailed);
+    }
+
+    @Test
+    public void testStartNotificationSucceedsNoKeyPasswd() throws ParserConfigurationException, SAXException, IOException {
+        Logger notificationServiceLogger = (Logger) LoggerFactory.getLogger(NotificationServiceManager.class);
+        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+        listAppender.start();
+        notificationServiceLogger.addAppender(listAppender);
+
+        String configFileOutput = CONFIGURATION_FILE_TEXT_NO_KEY_PASSWORD.replace("${test.server}", String.valueOf(mockWebServer.url("/")));
+        IOUtil.writeText(configFileOutput, new File(tempConfigFilePath));
+
+        NotificationServiceManager notificationServiceManager = new NotificationServiceManager();
+        notificationServiceManager.setMaxNotificationAttempts(1);
+        notificationServiceManager.loadNotificationServices(new File(tempConfigFilePath));
+
+        List<ILoggingEvent> logsList = listAppender.list;
+        boolean notificationServiceFailed = false;
+        for(ILoggingEvent logMessage : logsList) {
+            if(logMessage.getFormattedMessage().contains("is not valid for the following reasons")) {
+                notificationServiceFailed = true;
+            }
+        }
+
+        assertFalse(notificationServiceFailed);
+    }
+
+    @Test
+    public void testStartNotificationFailsBlankKeystorePasswdCorrectKeypasswd() throws ParserConfigurationException, SAXException, IOException {
+        Logger notificationServiceLogger = (Logger) LoggerFactory.getLogger(NotificationServiceManager.class);
+        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+        listAppender.start();
+        notificationServiceLogger.addAppender(listAppender);
+
+        String configFileOutput = CONFIGURATION_FILE_TEXT_BLANK_KEYSTORE_PASSWORD.replace("${test.server}", String.valueOf(mockWebServer.url("/")));
+        IOUtil.writeText(configFileOutput, new File(tempConfigFilePath));
+
+        NotificationServiceManager notificationServiceManager = new NotificationServiceManager();
+        notificationServiceManager.setMaxNotificationAttempts(1);
+        notificationServiceManager.loadNotificationServices(new File(tempConfigFilePath));
+
+        List<ILoggingEvent> logsList = listAppender.list;
+        boolean notificationServiceFailed = false;
+        for(ILoggingEvent logMessage : logsList) {
+            if(logMessage.getFormattedMessage().contains("'Keystore Password' validated against '' is invalid because Keystore Password cannot be empty")) {
+                notificationServiceFailed = true;
+            }
+        }
+
+        assertTrue(notificationServiceFailed);
+    }
+
+    @Test
+    public void testStartNotificationFailsCorrectKeystorePasswdBlankKeypasswd() throws ParserConfigurationException, SAXException, IOException {
+        Logger notificationServiceLogger = (Logger) LoggerFactory.getLogger(NotificationServiceManager.class);
+        ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
+        listAppender.start();
+        notificationServiceLogger.addAppender(listAppender);
+
+        String configFileOutput = CONFIGURATION_FILE_TEXT_BLANK_KEY_PASSWORD.replace("${test.server}", String.valueOf(mockWebServer.url("/")));
+        IOUtil.writeText(configFileOutput, new File(tempConfigFilePath));
+
+        NotificationServiceManager notificationServiceManager = new NotificationServiceManager();
+        notificationServiceManager.setMaxNotificationAttempts(1);
+        notificationServiceManager.loadNotificationServices(new File(tempConfigFilePath));
+
+        List<ILoggingEvent> logsList = listAppender.list;
+        boolean notificationServiceFailed = false;
+        for(ILoggingEvent logMessage : logsList) {
+            if(logMessage.getFormattedMessage().contains("'Key Password' validated against '' is invalid because Key Password cannot be empty")) {
+                notificationServiceFailed = true;
+            }
+        }
+
+        assertTrue(notificationServiceFailed);
+    }
 }
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 5c90725..c97aa89 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -2252,8 +2252,9 @@ It has the following properties available:
 |`Truststore Type`||The Type of the Truststore. Either `JKS` or `PKCS12`
 |`Truststore Password`||The password for the Truststore
 |`Keystore Filename`||The fully-qualified filename of the Keystore
-|`Keystore Type`||The password for the Keystore
-|`Keystore Password`||The password for the key. If this is not specified, but the Keystore Filename, Password, and Type are specified, then the Keystore Password will be assumed to be the same as the Key Password.
+|`Keystore Type`||The Type of the Keystore. Either `JKS` or `PKCS12`
+|`Keystore Password`||The password for the Keystore
+|`Key Password`||The password for the key. If this is not specified, but the Keystore Filename, Password, and Type are specified, then the Key Password will be assumed to be the same as the Keystore Password.
 |`SSL Protocol`||The algorithm to use for this SSL context. This can either be `SSL` or `TLS`.
 |====
 
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClient.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClient.java
index 2352026..ff63836 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClient.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClient.java
@@ -334,11 +334,11 @@ public class OkHttpReplicationClient implements HttpReplicationClient {
         try {
             Tuple<SSLContext, TrustManager[]> sslContextTuple = createTrustSslContextWithTrustManagers(
                     properties.getProperty(NiFiProperties.SECURITY_KEYSTORE),
-                    properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD) != null ? properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray() : null,
-                    properties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD) != null ? properties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray() : null,
+                    StringUtils.isNotBlank(properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)) ? properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray() : null,
+                    StringUtils.isNotBlank(properties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD)) ? properties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray() : null,
                     properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE),
                     properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
-                    properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD) != null ? properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray() : null,
+                    StringUtils.isNotBlank(properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)) ? properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray() : null,
                     properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE),
                     WANT,
                     sslContext.getProtocol());
@@ -346,7 +346,10 @@ public class OkHttpReplicationClient implements HttpReplicationClient {
                     .filter(trustManager -> trustManager instanceof X509TrustManager)
                     .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
             return new Tuple<>(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
-        } catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException e) {
+        } catch(UnrecoverableKeyException e) {
+            logger.error("Key password may be incorrect or not set. Check your keystore passwords." + e.getMessage());
+            return null;
+        } catch (CertificateException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException e) {
             return null;
         }
     }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy
index cad27f1..712f80a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy
@@ -26,6 +26,8 @@ import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
+import sun.security.ssl.DummyX509KeyManager
+import sun.security.ssl.SunX509KeyManagerImpl
 
 @RunWith(JUnit4.class)
 class OkHttpReplicationClientTest extends GroovyTestCase {
@@ -135,4 +137,77 @@ class OkHttpReplicationClientTest extends GroovyTestCase {
             assert headers."Content-Length" == "123"
         }
     }
+
+    @Test
+    void testShouldUseKeystorePasswdIfKeypasswdIsBlank() {
+        // Arrange
+        Map flowfileEncryptionProps = [
+                (NiFiProperties.SECURITY_TRUSTSTORE): "./src/test/resources/conf/truststore.jks",
+                (NiFiProperties.SECURITY_TRUSTSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "passwordpassword",
+                (NiFiProperties.SECURITY_KEYSTORE): "./src/test/resources/conf/keystore.jks",
+                (NiFiProperties.SECURITY_KEYSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_KEYSTORE_PASSWD): "passwordpassword",
+                (NiFiProperties.SECURITY_KEY_PASSWD): "",
+                (NiFiProperties.WEB_HTTPS_HOST): "localhost",
+                (NiFiProperties.WEB_HTTPS_PORT): "51552",
+        ]
+        NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(flowfileEncryptionProps))
+
+        // Act
+        OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties)
+
+        // Assert
+        assertNotNull(client.okHttpClient.sslSocketFactory)
+        assertEquals(SunX509KeyManagerImpl.class, client.okHttpClient.sslSocketFactory.context.getX509KeyManager().getClass())
+        assertNotNull(client.okHttpClient.sslSocketFactory.context.getX509KeyManager().credentialsMap["nifi-key"])
+    }
+
+    @Test
+    void testShouldFailIfKeyPasswdIsSetButKeystorePasswdIsBlank() {
+        // Arrange
+        Map flowfileEncryptionProps = [
+                (NiFiProperties.SECURITY_TRUSTSTORE): "./src/test/resources/conf/truststore.jks",
+                (NiFiProperties.SECURITY_TRUSTSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "passwordpassword",
+                (NiFiProperties.SECURITY_KEYSTORE): "./src/test/resources/conf/keystore.jks",
+                (NiFiProperties.SECURITY_KEYSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_KEYSTORE_PASSWD): "",
+                (NiFiProperties.SECURITY_KEY_PASSWD): "passwordpassword",
+                (NiFiProperties.WEB_HTTPS_HOST): "localhost",
+                (NiFiProperties.WEB_HTTPS_PORT): "51552",
+        ]
+        NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(flowfileEncryptionProps))
+
+        // Act
+        OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties)
+
+        // Assert
+        // The replication client will fail to initialize if the keystore password is missing, and will use
+        // a default empty DummyX509KeyManager instead. This is considered a failure to start the service.
+        assertSame(DummyX509KeyManager.class, client.okHttpClient.sslSocketFactory.context.getX509KeyManager().getClass())
+    }
+
+    @Test
+    void testShouldFailIfKeyPasswdIsBlankAndKeystorePasswd() {
+        // Arrange
+        Map flowfileEncryptionProps = [
+                (NiFiProperties.SECURITY_TRUSTSTORE): "./src/test/resources/conf/truststore.jks",
+                (NiFiProperties.SECURITY_TRUSTSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "passwordpassword",
+                (NiFiProperties.SECURITY_KEYSTORE): "./src/test/resources/conf/keystore.jks",
+                (NiFiProperties.SECURITY_KEYSTORE_TYPE): "JKS",
+                (NiFiProperties.SECURITY_KEYSTORE_PASSWD): "",
+                (NiFiProperties.SECURITY_KEY_PASSWD): "",
+                (NiFiProperties.WEB_HTTPS_HOST): "localhost",
+                (NiFiProperties.WEB_HTTPS_PORT): "51552",
+        ]
+        NiFiProperties mockNiFiProperties = new StandardNiFiProperties(new Properties(flowfileEncryptionProps))
+
+        // Act
+        OkHttpReplicationClient client = new OkHttpReplicationClient(mockNiFiProperties)
+
+        // Assert
+        assertEquals(DummyX509KeyManager.class, client.okHttpClient.sslSocketFactory.context.getX509KeyManager().getClass())
+    }
 }
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/keystore.jks b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/keystore.jks
new file mode 100644
index 0000000..246fe88
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/keystore.jks differ
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/truststore.jks b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/truststore.jks
new file mode 100644
index 0000000..87f4be1
Binary files /dev/null and b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/conf/truststore.jks differ