mirror of https://github.com/apache/nifi.git
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 commit is contained in:
parent
62c34d7e23
commit
7374361b5c
|
@ -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>
|
||||
|
|
|
@ -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,11 +40,47 @@ 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"+
|
||||
" <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=\"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"+
|
||||
|
@ -52,9 +96,42 @@ public class TestHttpNotificationServiceSSL extends TestHttpNotificationService
|
|||
" </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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`.
|
||||
|====
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue