VYPR
High severityNVD Advisory· Published Oct 1, 2020· Updated Aug 4, 2024

CVE-2020-9491

CVE-2020-9491

Description

In Apache NiFi 1.2.0 to 1.11.4, the NiFi UI and API were protected by mandating TLS v1.2, as well as listening connections established by processors like ListenHTTP, HandleHttpRequest, etc. However intracluster communication such as cluster request replication, Site-to-Site, and load balanced queues continued to support TLS v1.0 or v1.1.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Apache NiFi 1.2.0-1.11.4 allowed weaker TLS 1.0/1.1 for intracluster communication, while UI/API required TLS 1.2, enabling downgrade attacks.

In Apache NiFi versions 1.2.0 through 1.11.4, the user interface and API were protected by mandating TLS v1.2, and listening connections from processors like ListenHTTP and HandleHttpRequest also enforced TLS v1.2. However, intracluster communication channels—including cluster request replication, Site-to-Site, and load-balanced queues—continued to support TLS v1.0 or v1.1 [1]. This inconsistency meant that internal cluster traffic could be negotiated using weaker, deprecated TLS versions.

An attacker with network access to the cluster's internal traffic could exploit this by forcing a downgrade to TLS 1.0 or 1.1. These older protocols have known cryptographic weaknesses (e.g., POODLE, BEAST) that make them susceptible to man-in-the-middle attacks. No authentication bypass is required; the attacker only needs to be positioned to intercept or manipulate intracluster communications [1].

Successful exploitation could allow an attacker to decrypt or tamper with data exchanged between NiFi nodes, compromising the confidentiality and integrity of data flows within the cluster. This could lead to exposure of sensitive information or corruption of data in transit [1].

The vulnerability was addressed in Apache NiFi 1.12.0 by explicitly configuring SSLContextFactory references to use TLSv1.2 instead of the generic "TLS" protocol, as shown in the commit that replaced references to "TLS" with "TLSv1.2" [2]. Users are advised to upgrade to NiFi 1.12.0 or later, or to manually enforce TLS 1.2 for all intracluster connections if an immediate upgrade is not possible.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
org.apache.nifi:nifiMaven
>= 1.2.0, < 1.12.0-RC11.12.0-RC1

Affected products

3

Patches

1
441781cec50f

NIFI-7407 Replaced SSLContextFactory references to "TLS" with "TLSv1.2" (in shared constant).

https://github.com/apache/nifiAndy LoPrestoApr 29, 2020via ghsa
165 files changed · +4793 3774
  • nifi-bootstrap/src/main/java/org/apache/nifi/bootstrap/notification/http/HttpNotificationService.java+39 41 modified
    @@ -16,6 +16,13 @@
      */
     package org.apache.nifi.bootstrap.notification.http;
     
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.net.ssl.SSLSocketFactory;
    +import javax.net.ssl.X509TrustManager;
     import okhttp3.Call;
     import okhttp3.MediaType;
     import okhttp3.OkHttpClient;
    @@ -32,18 +39,7 @@
     import org.apache.nifi.expression.ExpressionLanguageScope;
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.security.util.SslContextFactory;
    -import org.apache.nifi.util.Tuple;
    -
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.X509TrustManager;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -import java.util.stream.Collectors;
    +import org.apache.nifi.security.util.TlsConfiguration;
     
     public class HttpNotificationService extends AbstractNotificationService {
     
    @@ -139,6 +135,7 @@ public class HttpNotificationService extends AbstractNotificationService {
         private final AtomicReference<String> urlReference = new AtomicReference<>();
     
         private static final List<PropertyDescriptor> supportedProperties;
    +
         static {
             supportedProperties = new ArrayList<>();
             supportedProperties.add(PROP_URL);
    @@ -160,7 +157,7 @@ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
         }
     
         @Override
    -    protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName){
    +    protected PropertyDescriptor getSupportedDynamicPropertyDescriptor(final String propertyDescriptorName) {
             return new PropertyDescriptor.Builder()
                     .required(false)
                     .name(propertyDescriptorName)
    @@ -193,25 +190,15 @@ protected void init(final NotificationInitializationContext context) {
             // check if the keystore is set and add the factory if so
             if (url.toLowerCase().startsWith("https")) {
                 try {
    -                Tuple<SSLContext, TrustManager[]> sslContextTuple = SslContextFactory.createTrustSslContextWithTrustManagers(
    -                        context.getProperty(HttpNotificationService.PROP_KEYSTORE).getValue(),
    -                        context.getProperty(HttpNotificationService.PROP_KEYSTORE_PASSWORD).isSet()
    -                                ? context.getProperty(HttpNotificationService.PROP_KEYSTORE_PASSWORD).getValue().toCharArray() : null,
    -                        context.getProperty(HttpNotificationService.PROP_KEY_PASSWORD).isSet()
    -                                ? context.getProperty(HttpNotificationService.PROP_KEY_PASSWORD).getValue().toCharArray() : null,
    -                        context.getProperty(HttpNotificationService.PROP_KEYSTORE_TYPE).getValue(),
    -                        context.getProperty(HttpNotificationService.PROP_TRUSTSTORE).getValue(),
    -                        context.getProperty(HttpNotificationService.PROP_TRUSTSTORE_PASSWORD).isSet()
    -                                ? context.getProperty(HttpNotificationService.PROP_TRUSTSTORE_PASSWORD).getValue().toCharArray() : null,
    -                        context.getProperty(HttpNotificationService.PROP_TRUSTSTORE_TYPE).getValue(),
    -                        SslContextFactory.ClientAuth.REQUIRED,
    -                        context.getProperty(HttpNotificationService.SSL_ALGORITHM).getValue()
    -                );
    -                // Find the first X509TrustManager
    -                List<X509TrustManager> x509TrustManagers = Arrays.stream(sslContextTuple.getValue())
    -                        .filter(trustManager -> trustManager instanceof X509TrustManager)
    -                        .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
    -                okHttpClientBuilder.sslSocketFactory(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
    +                TlsConfiguration tlsConfiguration = createTlsConfigurationFromContext(context);
    +                final SSLSocketFactory sslSocketFactory = SslContextFactory.createSSLSocketFactory(tlsConfiguration);
    +                final X509TrustManager x509TrustManager = SslContextFactory.getX509TrustManager(tlsConfiguration);
    +                if (sslSocketFactory != null && x509TrustManager != null) {
    +                    okHttpClientBuilder.sslSocketFactory(sslSocketFactory, x509TrustManager);
    +                } else {
    +                    // If the TLS config couldn't be parsed, throw an exception
    +                    throw new IllegalStateException("The HTTP notification service URL indicates HTTPS but the TLS properties are not valid");
    +                }
                 } catch (Exception e) {
                     throw new IllegalStateException(e);
                 }
    @@ -220,6 +207,17 @@ protected void init(final NotificationInitializationContext context) {
             httpClientReference.set(okHttpClientBuilder.build());
         }
     
    +    private static TlsConfiguration createTlsConfigurationFromContext(NotificationInitializationContext context) {
    +        String keystorePath = context.getProperty(HttpNotificationService.PROP_KEYSTORE).getValue();
    +        String keystorePassword = context.getProperty(HttpNotificationService.PROP_KEYSTORE_PASSWORD).getValue();
    +        String keyPassword = context.getProperty(HttpNotificationService.PROP_KEY_PASSWORD).getValue();
    +        String keystoreType = context.getProperty(HttpNotificationService.PROP_KEYSTORE_TYPE).getValue();
    +        String truststorePath = context.getProperty(HttpNotificationService.PROP_TRUSTSTORE).getValue();
    +        String truststorePassword = context.getProperty(HttpNotificationService.PROP_TRUSTSTORE_PASSWORD).getValue();
    +        String truststoreType = context.getProperty(HttpNotificationService.PROP_TRUSTSTORE_TYPE).getValue();
    +        return new TlsConfiguration(keystorePath, keystorePassword, keyPassword, keystoreType, truststorePath, truststorePassword, truststoreType);
    +    }
    +
         @Override
         public void notify(NotificationContext context, NotificationType notificationType, String subject, String message) throws NotificationFailedException {
             try {
    @@ -231,7 +229,7 @@ public void notify(NotificationContext context, NotificationType notificationTyp
     
                 Map<PropertyDescriptor, String> configuredProperties = context.getProperties();
     
    -            for(PropertyDescriptor propertyDescriptor: configuredProperties.keySet()) {
    +            for (PropertyDescriptor propertyDescriptor : configuredProperties.keySet()) {
                     if (propertyDescriptor.isDynamic()) {
                         String propertyValue = context.getProperty(propertyDescriptor).evaluateAttributeExpressions().getValue();
                         requestBuilder = requestBuilder.addHeader(propertyDescriptor.getDisplayName(), propertyValue);
    @@ -246,14 +244,14 @@ public void notify(NotificationContext context, NotificationType notificationTyp
                 final OkHttpClient httpClient = httpClientReference.get();
     
                 final Call call = httpClient.newCall(request);
    -             try (final Response response = call.execute()) {
    -
    -                 if (!response.isSuccessful()) {
    -                     throw new NotificationFailedException("Failed to send Http Notification. Received an unsuccessful status code response '" + response.code() + "'. The message was '" +
    -                             response.message() + "'");
    -                 }
    -             }
    -        } catch (NotificationFailedException e){
    +            try (final Response response = call.execute()) {
    +
    +                if (!response.isSuccessful()) {
    +                    throw new NotificationFailedException("Failed to send Http Notification. Received an unsuccessful status code response '" + response.code() + "'. The message was '" +
    +                            response.message() + "'");
    +                }
    +            }
    +        } catch (NotificationFailedException e) {
                 throw e;
             } catch (Exception e) {
                 throw new NotificationFailedException("Failed to send Http Notification", e);
    
  • nifi-bootstrap/src/test/java/org/apache/nifi/bootstrap/http/TestHttpNotificationServiceSSL.java+100 111 modified
    @@ -16,139 +16,128 @@
      */
     package org.apache.nifi.bootstrap.http;
     
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertTrue;
    +
    +import ch.qos.logback.classic.Logger;
     import ch.qos.logback.classic.spi.ILoggingEvent;
     import ch.qos.logback.core.read.ListAppender;
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Paths;
    +import java.util.List;
    +import javax.net.ssl.SSLContext;
    +import javax.xml.parsers.ParserConfigurationException;
     import okhttp3.mockwebserver.MockWebServer;
     import org.apache.nifi.bootstrap.NotificationServiceManager;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     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;
    -import java.nio.file.Paths;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.List;
    -
    -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"+
    +    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"+
    +    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"+
    +    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"+
    +    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>";
     
    -    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"+
    +    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 {
    +    public void startServer() throws IOException, TlsException {
             tempConfigFilePath = "./target/TestHttpNotificationService-config.xml";
     
             Files.deleteIfExists(Paths.get(tempConfigFilePath));
     
             mockWebServer = new MockWebServer();
     
    -        final SSLContext sslContext = SslContextFactory.createSslContext(
    -                "./src/test/resources/keystore.jks",
    -                "passwordpassword".toCharArray(),
    -                null,
    -                "JKS",
    -                "./src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "JKS",
    -                SslContextFactory.ClientAuth.REQUIRED,
    -                "TLS");
    -
    +        TlsConfiguration tlsConfiguration = new TlsConfiguration("./src/test/resources/keystore.jks", "passwordpassword", null, "JKS",
    +                "./src/test/resources/truststore.jks", "passwordpassword", "JKS", CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        final SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.REQUIRED);
             mockWebServer.useHttps(sslContext.getSocketFactory(), false);
     
             String configFileOutput = CONFIGURATION_FILE_TEXT.replace("${test.server}", String.valueOf(mockWebServer.url("/")));
    @@ -177,9 +166,9 @@ public void testStartNotificationSucceedsNoKeystorePasswd() throws ParserConfigu
     
             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;
    +        for (ILoggingEvent logMessage : logsList) {
    +            if (logMessage.getFormattedMessage().contains("is not valid for the following reasons")) {
    +                notificationServiceFailed = true;
                 }
             }
     
    @@ -202,8 +191,8 @@ public void testStartNotificationSucceedsNoKeyPasswd() throws ParserConfiguratio
     
             List<ILoggingEvent> logsList = listAppender.list;
             boolean notificationServiceFailed = false;
    -        for(ILoggingEvent logMessage : logsList) {
    -            if(logMessage.getFormattedMessage().contains("is not valid for the following reasons")) {
    +        for (ILoggingEvent logMessage : logsList) {
    +            if (logMessage.getFormattedMessage().contains("is not valid for the following reasons")) {
                     notificationServiceFailed = true;
                 }
             }
    @@ -227,8 +216,8 @@ public void testStartNotificationFailsBlankKeystorePasswdCorrectKeypasswd() thro
     
             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")) {
    +        for (ILoggingEvent logMessage : logsList) {
    +            if (logMessage.getFormattedMessage().contains("'Keystore Password' validated against '' is invalid because Keystore Password cannot be empty")) {
                     notificationServiceFailed = true;
                 }
             }
    @@ -252,8 +241,8 @@ public void testStartNotificationFailsCorrectKeystorePasswdBlankKeypasswd() thro
     
             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")) {
    +        for (ILoggingEvent logMessage : logsList) {
    +            if (logMessage.getFormattedMessage().contains("'Key Password' validated against '' is invalid because Key Password cannot be empty")) {
                     notificationServiceFailed = true;
                 }
             }
    
  • nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java+71 20 modified
    @@ -1672,10 +1672,28 @@ public String getDefaultBackPressureDataSizeThreshold() {
          * framework for default property loading behavior or helpful in tests
          * needing to create specific instances of NiFiProperties. If properties
          * file specified cannot be found/read a runtime exception will be thrown.
    -     * If one is not specified no properties will be loaded by default.
    +     * If one is not specified an empty object will be returned.
          *
          * @param propertiesFilePath   if provided properties will be loaded from
    -     *                             given file; else will be loaded from System property. Can be null.
    +     *                             given file; else will be loaded from System property.
    +     *                             Can be null. Passing {@code ""} skips any attempt to load from the file system.
    +     * @return NiFiProperties
    +     */
    +    public static NiFiProperties createBasicNiFiProperties(final String propertiesFilePath) {
    +        return createBasicNiFiProperties(propertiesFilePath, new Properties());
    +    }
    +
    +    /**
    +     * Creates an instance of NiFiProperties. This should likely not be called
    +     * by any classes outside of the NiFi framework but can be useful by the
    +     * framework for default property loading behavior or helpful in tests
    +     * needing to create specific instances of NiFiProperties. If properties
    +     * file specified cannot be found/read a runtime exception will be thrown.
    +     * If one is not specified, only the provided properties will be returned.
    +     *
    +     * @param propertiesFilePath   if provided properties will be loaded from
    +     *                             given file; else will be loaded from System property.
    +     *                             Can be null. Passing {@code ""} skips any attempt to load from the file system.
          * @param additionalProperties allows overriding of properties with the
          *                             supplied values. these will be applied after loading from any properties
          *                             file. Can be null or empty.
    @@ -1684,18 +1702,65 @@ public String getDefaultBackPressureDataSizeThreshold() {
         public static NiFiProperties createBasicNiFiProperties(final String propertiesFilePath, final Map<String, String> additionalProperties) {
             final Map<String, String> addProps = (additionalProperties == null) ? Collections.EMPTY_MAP : additionalProperties;
             final Properties properties = new Properties();
    +        addProps.forEach(properties::put);
    +
    +        return createBasicNiFiProperties(propertiesFilePath, properties);
    +    }
    +
    +    /**
    +     * Creates an instance of NiFiProperties. This should likely not be called
    +     * by any classes outside of the NiFi framework but can be useful by the
    +     * framework for default property loading behavior or helpful in tests
    +     * needing to create specific instances of NiFiProperties. If properties
    +     * file specified cannot be found/read a runtime exception will be thrown.
    +     * If one is not specified, only the provided properties will be returned.
    +     *
    +     * @param propertiesFilePath   if provided properties will be loaded from
    +     *                             given file; else will be loaded from System property.
    +     *                             Can be null. Passing {@code ""} skips any attempt to load from the file system.
    +     * @param additionalProperties allows overriding of properties with the
    +     *                             supplied values. these will be applied after loading from any properties
    +     *                             file. Can be null or empty.
    +     * @return NiFiProperties
    +     */
    +    public static NiFiProperties createBasicNiFiProperties(final String propertiesFilePath, final Properties additionalProperties) {
    +        final Properties properties = new Properties();
    +
    +        // If the provided file path is null or provided, load from file. If it is "", skip this
    +        if (propertiesFilePath == null || StringUtils.isNotBlank(propertiesFilePath)) {
    +            readFromPropertiesFile(propertiesFilePath, properties);
    +        }
    +
    +        // The Properties(Properties) constructor does NOT inherit the provided values, just uses them as default values
    +        if (additionalProperties != null) {
    +            additionalProperties.forEach(properties::put);
    +        }
    +        return new NiFiProperties() {
    +            @Override
    +            public String getProperty(String key) {
    +                return properties.getProperty(key);
    +            }
    +
    +            @Override
    +            public Set<String> getPropertyKeys() {
    +                return properties.stringPropertyNames();
    +            }
    +        };
    +    }
    +
    +    private static void readFromPropertiesFile(String propertiesFilePath, Properties properties) {
             final String nfPropertiesFilePath = (propertiesFilePath == null)
                     ? System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
                     : propertiesFilePath;
             if (nfPropertiesFilePath != null) {
                 final File propertiesFile = new File(nfPropertiesFilePath.trim());
                 if (!propertiesFile.exists()) {
    -                throw new RuntimeException("Properties file doesn't exist \'"
    -                        + propertiesFile.getAbsolutePath() + "\'");
    +                throw new RuntimeException("Properties file doesn't exist '"
    +                        + propertiesFile.getAbsolutePath() + "'");
                 }
                 if (!propertiesFile.canRead()) {
    -                throw new RuntimeException("Properties file exists but cannot be read \'"
    -                        + propertiesFile.getAbsolutePath() + "\'");
    +                throw new RuntimeException("Properties file exists but cannot be read '"
    +                        + propertiesFile.getAbsolutePath() + "'");
                 }
                 InputStream inStream = null;
                 try {
    @@ -1716,20 +1781,6 @@ public static NiFiProperties createBasicNiFiProperties(final String propertiesFi
                     }
                 }
             }
    -        addProps.entrySet().stream().forEach((entry) -> {
    -            properties.setProperty(entry.getKey(), entry.getValue());
    -        });
    -        return new NiFiProperties() {
    -            @Override
    -            public String getProperty(String key) {
    -                return properties.getProperty(key);
    -            }
    -
    -            @Override
    -            public Set<String> getPropertyKeys() {
    -                return properties.stringPropertyNames();
    -            }
    -        };
         }
     
         /**
    
  • nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/StringUtils.java+8 0 modified
    @@ -41,10 +41,18 @@ public static boolean isBlank(final String str) {
             return true;
         }
     
    +    public static boolean isNotBlank(final String str) {
    +        return !isBlank(str);
    +    }
    +
         public static boolean isEmpty(final String str) {
             return str == null || str.isEmpty();
         }
     
    +    public static boolean isNotEmpty(final String str) {
    +        return !isEmpty(str);
    +    }
    +
         public static boolean startsWith(final String str, final String prefix) {
             if (str == null || prefix == null) {
                 return (str == null && prefix == null);
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/CertificateUtils.java+95 25 modified
    @@ -38,9 +38,12 @@
     import java.util.List;
     import java.util.Map;
     import java.util.concurrent.TimeUnit;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
     import javax.naming.InvalidNameException;
     import javax.naming.ldap.LdapName;
     import javax.naming.ldap.Rdn;
    +import javax.net.ssl.SSLException;
     import javax.net.ssl.SSLPeerUnverifiedException;
     import javax.net.ssl.SSLSocket;
     import org.apache.commons.lang3.StringUtils;
    @@ -79,6 +82,11 @@ public final class CertificateUtils {
         private static final String PEER_NOT_AUTHENTICATED_MSG = "peer not authenticated";
         private static final Map<ASN1ObjectIdentifier, Integer> dnOrderMap = createDnOrderMap();
     
    +    public static final String JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.2";
    +    public static final String JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION = "TLSv1.3";
    +    public static final String[] JAVA_8_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
    +    public static final String[] JAVA_11_SUPPORTED_TLS_PROTOCOL_VERSIONS = new String[]{JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION, JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION};
    +
         static {
             Security.addProvider(new BouncyCastleProvider());
         }
    @@ -113,25 +121,6 @@ private static Map<ASN1ObjectIdentifier, Integer> createDnOrderMap() {
             return Collections.unmodifiableMap(orderMap);
         }
     
    -    public enum ClientAuth {
    -        NONE(0, "none"),
    -        WANT(1, "want"),
    -        NEED(2, "need");
    -
    -        private int value;
    -        private String description;
    -
    -        ClientAuth(int value, String description) {
    -            this.value = value;
    -            this.description = description;
    -        }
    -
    -        @Override
    -        public String toString() {
    -            return "Client Auth: " + this.description + " (" + this.value + ")";
    -        }
    -    }
    -
         /**
          * Extracts the username from the specified DN. If the username cannot be extracted because the CN is in an unrecognized format, the entire CN is returned. If the CN cannot be extracted because
          * the DN is in an unrecognized format, the entire DN is returned.
    @@ -211,7 +200,7 @@ public static String extractPeerDNFromSSLSocket(Socket socket) throws Certificat
     
                 boolean clientMode = sslSocket.getUseClientMode();
                 logger.debug("SSL Socket in {} mode", clientMode ? "client" : "server");
    -            ClientAuth clientAuth = getClientAuthStatus(sslSocket);
    +            SslContextFactory.ClientAuth clientAuth = getClientAuthStatus(sslSocket);
                 logger.debug("SSL Socket client auth status: {}", clientAuth);
     
                 if (clientMode) {
    @@ -244,10 +233,10 @@ private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) thro
              * This method should throw an exception if none are provided for need, return null if none are provided for want, and return null (without checking) for none.
              */
     
    -        ClientAuth clientAuth = getClientAuthStatus(sslSocket);
    +        SslContextFactory.ClientAuth clientAuth = getClientAuthStatus(sslSocket);
             logger.debug("SSL Socket client auth status: {}", clientAuth);
     
    -        if (clientAuth != ClientAuth.NONE) {
    +        if (clientAuth != SslContextFactory.ClientAuth.NONE) {
                 try {
                     final Certificate[] certChains = sslSocket.getSession().getPeerCertificates();
                     if (certChains != null && certChains.length > 0) {
    @@ -260,7 +249,7 @@ private static String extractPeerDNFromClientSSLSocket(SSLSocket sslSocket) thro
                         logger.error("The incoming request did not contain client certificates and thus the DN cannot" +
                                 " be extracted. Check that the other endpoint is providing a complete client certificate chain");
                     }
    -                if (clientAuth == ClientAuth.WANT) {
    +                if (clientAuth == SslContextFactory.ClientAuth.WANT) {
                         logger.warn("Suppressing missing client certificate exception because client auth is set to 'want'");
                         return dn;
                     }
    @@ -299,8 +288,8 @@ private static String extractPeerDNFromServerSSLSocket(Socket socket) throws Cer
             return dn;
         }
     
    -    private static ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
    -        return sslSocket.getNeedClientAuth() ? ClientAuth.NEED : sslSocket.getWantClientAuth() ? ClientAuth.WANT : ClientAuth.NONE;
    +    private static SslContextFactory.ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
    +        return sslSocket.getNeedClientAuth() ? SslContextFactory.ClientAuth.REQUIRED : sslSocket.getWantClientAuth() ? SslContextFactory.ClientAuth.WANT : SslContextFactory.ClientAuth.NONE;
         }
     
         /**
    @@ -311,6 +300,7 @@ private static ClientAuth getClientAuthStatus(SSLSocket sslSocket) {
          * @return a new {@code java.security.cert.X509Certificate}
          * @throws CertificateException if there is an error generating the new certificate
          */
    +    @SuppressWarnings("deprecation")
         public static X509Certificate convertLegacyX509Certificate(javax.security.cert.X509Certificate legacyCertificate) throws CertificateException {
             if (legacyCertificate == null) {
                 throw new IllegalArgumentException("The X.509 certificate cannot be null");
    @@ -616,6 +606,86 @@ public static Extensions getExtensionsFromCSR(JcaPKCS10CertificationRequest csr)
             return null;
         }
     
    +    /**
    +     * Returns {@code true} if this exception is due to a TLS problem (either directly or because of its cause, if present). Traverses the cause chain recursively.
    +     *
    +     * @param e the exception to evaluate
    +     * @return true if the direct or indirect cause of this exception was TLS-related
    +     */
    +    public static boolean isTlsError(Throwable e) {
    +        if (e == null) {
    +            return false;
    +        } else {
    +            if (e instanceof CertificateException || e instanceof TlsException || e instanceof SSLException) {
    +                return true;
    +            } else if (e.getCause() != null) {
    +                return isTlsError(e.getCause());
    +            } else {
    +                return false;
    +            }
    +        }
    +    }
    +
    +    /**
    +     * Returns the JVM Java major version based on the System properties (e.g. {@code JVM 1.8.0.231} -> {code 8}).
    +     *
    +     * @return the Java major version
    +     */
    +    public static int getJavaVersion() {
    +        String version = System.getProperty("java.version");
    +        return parseJavaVersion(version);
    +    }
    +
    +    /**
    +     * Returns the major version parsed from the provided Java version string (e.g. {@code "1.8.0.231"} -> {@code 8}).
    +     *
    +     * @param version the Java version string
    +     * @return the major version as an int
    +     */
    +    public static int parseJavaVersion(String version) {
    +        String majorVersion;
    +        if (version.startsWith("1.")) {
    +            majorVersion = version.substring(2, 3);
    +        } else {
    +            Pattern majorVersion9PlusPattern = Pattern.compile("(\\d+).*");
    +            Matcher m = majorVersion9PlusPattern.matcher(version);
    +            if (m.find()) {
    +                majorVersion = m.group(1);
    +            } else {
    +                throw new IllegalArgumentException("Could not detect major version of " + version);
    +            }
    +        }
    +        return Integer.parseInt(majorVersion);
    +    }
    +
    +    /**
    +     * Returns a {@code String[]} of supported TLS protocol versions based on the current Java platform version.
    +     *
    +     * @return the supported TLS protocol version(s)
    +     */
    +    public static String[] getCurrentSupportedTlsProtocolVersions() {
    +        int javaMajorVersion = getJavaVersion();
    +        if (javaMajorVersion < 11) {
    +            return JAVA_8_SUPPORTED_TLS_PROTOCOL_VERSIONS;
    +        } else {
    +            return JAVA_11_SUPPORTED_TLS_PROTOCOL_VERSIONS;
    +        }
    +    }
    +
    +    /**
    +     * Returns the highest supported TLS protocol version based on the current Java platform version.
    +     *
    +     * @return the TLS protocol (e.g. {@code "TLSv1.2"})
    +     */
    +    public static String getHighestCurrentSupportedTlsProtocolVersion() {
    +        int javaMajorVersion = getJavaVersion();
    +        if (javaMajorVersion < 11) {
    +            return JAVA_8_MAX_SUPPORTED_TLS_PROTOCOL_VERSION;
    +        } else {
    +            return JAVA_11_MAX_SUPPORTED_TLS_PROTOCOL_VERSION;
    +        }
    +    }
    +
         private CertificateUtils() {
         }
     }
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeystoreType.java+33 2 modified
    @@ -16,11 +16,42 @@
      */
     package org.apache.nifi.security.util;
     
    +import java.util.Arrays;
    +import java.util.stream.Collectors;
    +import org.apache.nifi.util.StringUtils;
    +
     /**
      * Keystore types.
      */
     public enum KeystoreType {
    +    PKCS12("PKCS12", "A PKCS12 Keystore"),
    +    JKS("JKS", "A Java Keystore");
    +
    +    private final String type;
    +    private final String description;
    +
    +    KeystoreType(String type, String description) {
    +        this.type = type;
    +        this.description = description;
    +    }
    +
    +    public String getType() {
    +        return this.type;
    +    }
    +
    +    public String getDescription() {
    +        return this.description;
    +    }
    +
    +    @Override
    +    public String toString() {
    +        return getType();
    +    }
     
    -    PKCS12,
    -    JKS;
    +    public static boolean isValidKeystoreType(String type) {
    +        if (StringUtils.isBlank(type)) {
    +            return false;
    +        }
    +        return (Arrays.stream(values()).map(kt -> kt.getType().toLowerCase()).collect(Collectors.toList()).contains(type.toLowerCase()));
    +    }
     }
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/KeyStoreUtils.java+182 0 modified
    @@ -18,14 +18,24 @@
     package org.apache.nifi.security.util;
     
     import java.io.BufferedInputStream;
    +import java.io.FileInputStream;
     import java.io.IOException;
    +import java.io.InputStream;
     import java.net.URL;
     import java.security.Key;
     import java.security.KeyStore;
     import java.security.KeyStoreException;
    +import java.security.NoSuchAlgorithmException;
     import java.security.Security;
     import java.security.UnrecoverableKeyException;
    +import java.security.cert.CertificateException;
    +import javax.net.ssl.KeyManagerFactory;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.SSLParameters;
    +import javax.net.ssl.SSLServerSocket;
    +import javax.net.ssl.TrustManagerFactory;
     import org.apache.commons.lang3.StringUtils;
    +import org.apache.commons.lang3.builder.ToStringBuilder;
     import org.bouncycastle.jce.provider.BouncyCastleProvider;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
    @@ -88,6 +98,154 @@ public static KeyStore getTrustStore(String trustStoreType) throws KeyStoreExcep
             return getKeyStore(trustStoreType);
         }
     
    +    /**
    +     * Returns a loaded {@link KeyStore} given the provided configuration values.
    +     *
    +     * @param keystorePath     the file path to the keystore
    +     * @param keystorePassword the keystore password
    +     * @param keystoreType     the keystore type ({@code JKS} or {@code PKCS12})
    +     * @return the loaded keystore
    +     * @throws TlsException if there is a problem loading the keystore
    +     */
    +    public static KeyStore loadKeyStore(String keystorePath, char[] keystorePassword, String keystoreType) throws TlsException {
    +        final KeyStore keyStore;
    +        try {
    +            keyStore = KeyStoreUtils.getKeyStore(keystoreType);
    +            try (final InputStream keyStoreStream = new FileInputStream(keystorePath)) {
    +                keyStore.load(keyStoreStream, keystorePassword);
    +            }
    +            return keyStore;
    +        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
    +            logger.error("Encountered an error loading keystore: {}", e.getLocalizedMessage());
    +            throw new TlsException("Error loading keystore", e);
    +        }
    +    }
    +
    +    /**
    +     * Returns the {@link KeyManagerFactory} from the provided {@link KeyStore} object, initialized with the key or keystore password.
    +     *
    +     * @param keyStore         the loaded keystore
    +     * @param keystorePassword the keystore password
    +     * @param keyPassword      the key password
    +     * @return the key manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the keystore
    +     */
    +    public static KeyManagerFactory getKeyManagerFactoryFromKeyStore(KeyStore keyStore, char[] keystorePassword, char[] keyPassword) throws TlsException {
    +        try {
    +            final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    +            if (keyPassword == null) {
    +                keyManagerFactory.init(keyStore, keystorePassword);
    +            } else {
    +                keyManagerFactory.init(keyStore, keyPassword);
    +            }
    +            return keyManagerFactory;
    +        } catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException e) {
    +            logger.error("Encountered an error loading keystore: {}", e.getLocalizedMessage());
    +            throw new TlsException("Error loading keystore", e);
    +        }
    +    }
    +
    +    /**
    +     * Returns the intialized {@link KeyManagerFactory}.
    +     *
    +     * @param tlsConfiguration the TLS configuration
    +     * @return the initialized key manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the keystore
    +     */
    +    public static KeyManagerFactory loadKeyManagerFactory(TlsConfiguration tlsConfiguration) throws TlsException {
    +        return loadKeyManagerFactory(tlsConfiguration.getKeystorePath(), tlsConfiguration.getKeystorePassword(),
    +                tlsConfiguration.getFunctionalKeyPassword(), tlsConfiguration.getKeystoreType().getType());
    +    }
    +
    +    /**
    +     * Returns the initialized {@link KeyManagerFactory}.
    +     *
    +     * @param keystorePath     the file path to the keystore
    +     * @param keystorePassword the keystore password
    +     * @param keyPassword      the key password
    +     * @param keystoreType     the keystore type ({@code JKS} or {@code PKCS12})
    +     * @return the initialized key manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the keystore
    +     */
    +    public static KeyManagerFactory loadKeyManagerFactory(String keystorePath, String keystorePassword, String keyPassword, String keystoreType) throws TlsException {
    +        if (StringUtils.isEmpty(keystorePassword)) {
    +            throw new IllegalArgumentException("The keystore password cannot be null or empty");
    +        }
    +        final char[] keystorePasswordChars = keystorePassword.toCharArray();
    +        final char[] keyPasswordChars = (StringUtils.isNotEmpty(keyPassword)) ? keyPassword.toCharArray() : keystorePasswordChars;
    +        KeyStore keyStore = loadKeyStore(keystorePath, keystorePasswordChars, keystoreType);
    +        return getKeyManagerFactoryFromKeyStore(keyStore, keystorePasswordChars, keyPasswordChars);
    +    }
    +
    +
    +    /**
    +     * Returns a loaded {@link KeyStore} (acting as a truststore) given the provided configuration values.
    +     *
    +     * @param truststorePath     the file path to the truststore
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type ({@code JKS} or {@code PKCS12})
    +     * @return the loaded truststore
    +     * @throws TlsException if there is a problem loading the truststore
    +     */
    +    public static KeyStore loadTrustStore(String truststorePath, char[] truststorePassword, String truststoreType) throws TlsException {
    +        final KeyStore trustStore;
    +        try {
    +            trustStore = KeyStoreUtils.getTrustStore(truststoreType);
    +            try (final InputStream trustStoreStream = new FileInputStream(truststorePath)) {
    +                trustStore.load(trustStoreStream, truststorePassword);
    +            }
    +            return trustStore;
    +        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException e) {
    +            logger.error("Encountered an error loading truststore: {}", e.getLocalizedMessage());
    +            throw new TlsException("Error loading truststore", e);
    +        }
    +    }
    +
    +    /**
    +     * Returns the {@link TrustManagerFactory} from the provided {@link KeyStore} object, initialized.
    +     *
    +     * @param trustStore the loaded truststore
    +     * @return the trust manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the truststore
    +     */
    +    public static TrustManagerFactory getTrustManagerFactoryFromTrustStore(KeyStore trustStore) throws TlsException {
    +        try {
    +            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    +            trustManagerFactory.init(trustStore);
    +            return trustManagerFactory;
    +        } catch (NoSuchAlgorithmException | KeyStoreException e) {
    +            logger.error("Encountered an error loading truststore: {}", e.getLocalizedMessage());
    +            throw new TlsException("Error loading truststore", e);
    +        }
    +    }
    +
    +    /**
    +     * Returns the intialized {@link TrustManagerFactory}.
    +     *
    +     * @param tlsConfiguration the TLS configuration
    +     * @return the initialized trust manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the truststore
    +     */
    +    public static TrustManagerFactory loadTrustManagerFactory(TlsConfiguration tlsConfiguration) throws TlsException {
    +        return loadTrustManagerFactory(tlsConfiguration.getTruststorePath(), tlsConfiguration.getTruststorePassword(), tlsConfiguration.getTruststoreType().getType());
    +    }
    +
    +    /**
    +     * Returns the initialized {@link TrustManagerFactory}.
    +     *
    +     * @param truststorePath     the file path to the truststore
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type ({@code JKS} or {@code PKCS12})
    +     * @return the initialized trust manager factory
    +     * @throws TlsException if there is a problem initializing or reading from the truststore
    +     */
    +    public static TrustManagerFactory loadTrustManagerFactory(String truststorePath, String truststorePassword, String truststoreType) throws TlsException {
    +        // Legacy truststore passwords can be empty
    +        final char[] truststorePasswordChars = StringUtils.isNotBlank(truststorePassword) ? truststorePassword.toCharArray() : null;
    +        KeyStore trustStore = loadTrustStore(truststorePath, truststorePasswordChars, truststoreType);
    +        return getTrustManagerFactoryFromTrustStore(trustStore);
    +    }
    +
         /**
          * Returns true if the given keystore can be loaded using the given keystore type and password. Returns false otherwise.
          *
    @@ -180,4 +338,28 @@ public static boolean isKeyPasswordCorrect(final URL keystore, final KeystoreTyp
                 }
             }
         }
    +
    +    public static String sslContextToString(SSLContext sslContext) {
    +        return new ToStringBuilder(sslContext)
    +                .append("protocol", sslContext.getProtocol())
    +                .append("provider", sslContext.getProvider().toString())
    +                .toString();
    +    }
    +
    +    public static String sslParametersToString(SSLParameters sslParameters) {
    +        return new ToStringBuilder(sslParameters)
    +                .append("protocols", sslParameters.getProtocols())
    +                .append("wantClientAuth", sslParameters.getWantClientAuth())
    +                .append("needClientAuth", sslParameters.getNeedClientAuth())
    +                .toString();
    +    }
    +
    +    public static String sslServerSocketToString(SSLServerSocket sslServerSocket) {
    +        return new ToStringBuilder(sslServerSocket)
    +                .append("enabledProtocols", sslServerSocket.getEnabledProtocols())
    +                .append("needClientAuth", sslServerSocket.getNeedClientAuth())
    +                .append("wantClientAuth", sslServerSocket.getWantClientAuth())
    +                .append("useClientMode", sslServerSocket.getUseClientMode())
    +                .toString();
    +    }
     }
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SecurityStoreTypes.java+0 144 removed
    @@ -1,144 +0,0 @@
    -/*
    - * 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.security.util;
    -
    -import java.io.PrintWriter;
    -import java.io.Writer;
    -
    -/**
    - * Types of security stores and their related Java system properties.
    - */
    -public enum SecurityStoreTypes {
    -
    -    TRUSTSTORE(
    -            "javax.net.ssl.trustStore",
    -            "javax.net.ssl.trustStorePassword",
    -            "javax.net.ssl.trustStoreType"),
    -    KEYSTORE(
    -            "javax.net.ssl.keyStore",
    -            "javax.net.ssl.keyStorePassword",
    -            "javax.net.ssl.keyStoreType");
    -
    -    /**
    -     * Logs the keystore and truststore Java system property values to the given
    -     * writer. It logPasswords is true, then the keystore and truststore
    -     * password property values are logged.
    -     *
    -     * @param writer a writer to log to
    -     *
    -     * @param logPasswords true if passwords should be logged; false otherwise
    -     */
    -    public static void logProperties(final Writer writer,
    -            final boolean logPasswords) {
    -        if (writer == null) {
    -            return;
    -        }
    -
    -        PrintWriter pw = new PrintWriter(writer);
    -
    -        // keystore properties
    -        pw.println(
    -                KEYSTORE.getStoreProperty() + " = " + System.getProperty(KEYSTORE.getStoreProperty()));
    -
    -        if (logPasswords) {
    -            pw.println(
    -                    KEYSTORE.getStorePasswordProperty() + " = "
    -                    + System.getProperty(KEYSTORE.getStoreProperty()));
    -        }
    -
    -        pw.println(
    -                KEYSTORE.getStoreTypeProperty() + " = "
    -                + System.getProperty(KEYSTORE.getStoreTypeProperty()));
    -
    -        // truststore properties
    -        pw.println(
    -                TRUSTSTORE.getStoreProperty() + " = "
    -                + System.getProperty(TRUSTSTORE.getStoreProperty()));
    -
    -        if (logPasswords) {
    -            pw.println(
    -                    TRUSTSTORE.getStorePasswordProperty() + " = "
    -                    + System.getProperty(TRUSTSTORE.getStoreProperty()));
    -        }
    -
    -        pw.println(
    -                TRUSTSTORE.getStoreTypeProperty() + " = "
    -                + System.getProperty(TRUSTSTORE.getStoreTypeProperty()));
    -        pw.flush();
    -    }
    -
    -    /**
    -     * the Java system property for setting the keystore (or truststore) path
    -     */
    -    private String storeProperty = "";
    -
    -    /**
    -     * the Java system property for setting the keystore (or truststore)
    -     * password
    -     */
    -    private String storePasswordProperty = "";
    -
    -    /**
    -     * the Java system property for setting the keystore (or truststore) type
    -     */
    -    private String storeTypeProperty = "";
    -
    -    /**
    -     * Creates an instance.
    -     *
    -     * @param storeProperty the Java system property for setting the keystore or
    -     * truststore path
    -     * @param storePasswordProperty the Java system property for setting the
    -     * keystore or truststore path
    -     * @param storeTypeProperty the Java system property for setting the
    -     * keystore or truststore type
    -     */
    -    SecurityStoreTypes(final String storeProperty,
    -            final String storePasswordProperty,
    -            final String storeTypeProperty) {
    -        this.storeProperty = storeProperty;
    -        this.storePasswordProperty = storePasswordProperty;
    -        this.storeTypeProperty = storeTypeProperty;
    -    }
    -
    -    /**
    -     * Returns the keystore (or truststore) property.
    -     *
    -     * @return the keystore (or truststore) property
    -     */
    -    public String getStoreProperty() {
    -        return storeProperty;
    -    }
    -
    -    /**
    -     * Returns the keystore (or truststore) password property.
    -     *
    -     * @return the keystore (or truststore) password property
    -     */
    -    public String getStorePasswordProperty() {
    -        return storePasswordProperty;
    -    }
    -
    -    /**
    -     * Returns the keystore (or truststore) type property.
    -     *
    -     * @return the keystore (or truststore) type property
    -     */
    -    public String getStoreTypeProperty() {
    -        return storeTypeProperty;
    -    }
    -}
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/SslContextFactory.java+186 286 modified
    @@ -16,351 +16,251 @@
      */
     package org.apache.nifi.security.util;
     
    -import org.apache.nifi.util.Tuple;
    -
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
     import java.security.KeyManagementException;
    -import java.security.KeyStore;
    -import java.security.KeyStoreException;
     import java.security.NoSuchAlgorithmException;
     import java.security.SecureRandom;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -
    +import java.util.Arrays;
    +import java.util.Optional;
    +import java.util.stream.Collectors;
     import javax.net.ssl.KeyManager;
     import javax.net.ssl.KeyManagerFactory;
     import javax.net.ssl.SSLContext;
    +import javax.net.ssl.SSLSocketFactory;
     import javax.net.ssl.TrustManager;
     import javax.net.ssl.TrustManagerFactory;
    +import javax.net.ssl.X509TrustManager;
    +import org.apache.commons.lang3.builder.ToStringBuilder;
    +import org.apache.commons.lang3.builder.ToStringStyle;
    +import org.apache.nifi.util.StringUtils;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     /**
    - * A factory for creating SSL contexts using the application's security
    - * properties.
    - *
    + * A factory for creating SSL contexts using the application's security properties. By requiring callers to bundle
    + * the properties in a {@link TlsConfiguration} container object, much better validation and property matching can
    + * occur. The {@code public} methods are designed for easy use, while the {@code protected} methods provide more
    + * granular (but less common) access to intermediate objects if required.
      */
     public final class SslContextFactory {
    -
    -    public static enum ClientAuth {
    -
    -        WANT,
    -        REQUIRED,
    -        NONE
    -    }
    +    private static final Logger logger = LoggerFactory.getLogger(SslContextFactory.class);
     
         /**
    -     * Creates an SSLContext instance using the given information. The password for the key is assumed to be the same
    -     * as the password for the keystore. If this is not the case, the {@link #createSslContext(String, char[], char[], String, String, char[], String, ClientAuth, String)}
    -     * method should be used instead
    -     *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param clientAuth the type of client authentication
    -     * @param protocol         the protocol to use for the SSL connection
    -     *
    -     * @return an SSLContext instance
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * This enum is used to indicate the three possible options for a server requesting a client certificate during TLS handshake negotiation.
          */
    -    public static SSLContext createSslContext(
    -            final String keystore, final char[] keystorePasswd, final String keystoreType,
    -            final String truststore, final char[] truststorePasswd, final String truststoreType,
    -            final ClientAuth clientAuth, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    +    public enum ClientAuth {
    +        WANT("Want", "Requests the client certificate on handshake and validates if present but does not require it"),
    +        REQUIRED("Required", "Requests the client certificate on handshake and rejects the connection if it is not present and valid"),
    +        NONE("None", "Does not request the client certificate on handshake");
     
    -        // Pass the keystore password as both the keystore password and the key password.
    -        return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, truststore, truststorePasswd, truststoreType, clientAuth, protocol);
    +        private final String type;
    +        private final String description;
    +
    +        ClientAuth(String type, String description) {
    +            this.type = type;
    +            this.description = description;
    +        }
    +
    +        public String getType() {
    +            return this.type;
    +        }
    +
    +        public String getDescription() {
    +            return this.description;
    +        }
    +
    +        @Override
    +        public String toString() {
    +            final ToStringBuilder builder = new ToStringBuilder(this);
    +            ToStringBuilder.setDefaultStyle(ToStringStyle.SHORT_PREFIX_STYLE);
    +            builder.append("Type", type);
    +            builder.append("Description", description);
    +            return builder.toString();
    +        }
    +
    +        public static boolean isValidClientAuthType(String type) {
    +            if (StringUtils.isBlank(type)) {
    +                return false;
    +            }
    +            return (Arrays.stream(values()).map(ca -> ca.getType().toLowerCase()).collect(Collectors.toList()).contains(type.toLowerCase()));
    +        }
         }
     
         /**
    -     * Creates an SSLContext instance using the given information.
    -     *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keyPasswd the password for the key within the keystore
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param clientAuth the type of client authentication
    -     * @param protocol         the protocol to use for the SSL connection
    +     * Returns a configured {@link SSLContext} from the provided TLS configuration. Hardcodes the
    +     * client auth setting to {@link ClientAuth#REQUIRED} because this method is usually used when
    +     * creating a context for a client, which ignores the setting anyway.
          *
    -     * @return an SSLContext instance
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object
    +     * @return the configured SSLContext
    +     * @throws TlsException if there is a problem configuring the SSLContext
          */
    -    public static SSLContext createSslContext(
    -            final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType,
    -            final String truststore, final char[] truststorePasswd, final String truststoreType,
    -            final ClientAuth clientAuth, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -        return createSslContextWithTrustManagers(keystore, keystorePasswd, keyPasswd, keystoreType, truststore,
    -                truststorePasswd, truststoreType, clientAuth, protocol).getKey();
    +    public static SSLContext createSslContext(TlsConfiguration tlsConfiguration) throws TlsException {
    +        return createSslContext(tlsConfiguration, ClientAuth.REQUIRED);
         }
     
         /**
    -     * Creates an SSLContext instance paired with its TrustManager instances using the given information.
    +     * Returns a configured {@link SSLContext} from the provided TLS configuration.
          *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keyPasswd the password for the key within the keystore
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param clientAuth the type of client authentication
    -     * @param protocol         the protocol to use for the SSL connection
    -     *
    -     * @return a {@link Tuple} pairing an SSLContext instance with its TrustManagers
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object
    +     * @param clientAuth       the {@link ClientAuth} setting
    +     * @return the configured SSLContext
    +     * @throws TlsException if there is a problem configuring the SSLContext
          */
    -    public static Tuple<SSLContext, TrustManager[]> createSslContextWithTrustManagers(
    -            final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType,
    -            final String truststore, final char[] truststorePasswd, final String truststoreType,
    -            final ClientAuth clientAuth, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -        // prepare the keystore
    -        final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType);
    -        try (final InputStream keyStoreStream = new FileInputStream(keystore)) {
    -            keyStore.load(keyStoreStream, keystorePasswd);
    -        }
    -        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    -        if (keyPasswd == null) {
    -            keyManagerFactory.init(keyStore, keystorePasswd);
    -        } else {
    -            keyManagerFactory.init(keyStore, keyPasswd);
    +    public static SSLContext createSslContext(TlsConfiguration tlsConfiguration, ClientAuth clientAuth) throws TlsException {
    +        // If the object is null or neither keystore nor truststore properties are present, return null
    +        if (TlsConfiguration.isEmpty(tlsConfiguration)) {
    +            logger.debug("Cannot create SSLContext from empty TLS configuration; returning null");
    +            return null;
             }
     
    -        // prepare the truststore
    -        final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType);
    -        try (final InputStream trustStoreStream = new FileInputStream(truststore)) {
    -            trustStore.load(trustStoreStream, truststorePasswd);
    +        // If the keystore properties are present, truststore properties are required to be present as well
    +        if (tlsConfiguration.isKeystorePopulated() && !tlsConfiguration.isTruststorePopulated()) {
    +            logger.error("The TLS config keystore properties were populated but the truststore properties were not");
    +            if (logger.isDebugEnabled()) {
    +                logger.debug("Provided TLS config: {}", tlsConfiguration);
    +            }
    +            throw new TlsException("Truststore properties are required if keystore properties are present");
             }
    -        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    -        trustManagerFactory.init(trustStore);
     
    -        // initialize the ssl context
    -        final SSLContext sslContext = SSLContext.getInstance(protocol);
    -        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
    -        if (ClientAuth.REQUIRED == clientAuth) {
    -            sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
    -        } else if (ClientAuth.WANT == clientAuth) {
    -            sslContext.getDefaultSSLParameters().setWantClientAuth(true);
    -        } else {
    -            sslContext.getDefaultSSLParameters().setWantClientAuth(false);
    +        if (clientAuth == null) {
    +            clientAuth = ClientAuth.REQUIRED;
    +            logger.debug("ClientAuth was null so defaulting to {}", clientAuth);
             }
     
    -        return new Tuple<>(sslContext, trustManagerFactory.getTrustManagers());
    -
    -    }
    +        // Create the keystore components
    +        KeyManager[] keyManagers = getKeyManagers(tlsConfiguration);
     
    -    /**
    -     * Creates an SSLContext instance using the given information. This method assumes that the key password is
    -     * the same as the keystore password. If this is not the case, use the {@link #createSslContext(String, char[], char[], String, String)}
    -     * method instead.
    -     *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param protocol the protocol to use for the SSL connection
    -     *
    -     * @return an SSLContext instance
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    -     */
    -    public static SSLContext createSslContext(
    -        final String keystore, final char[] keystorePasswd, final String keystoreType, final String protocol)
    -        throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -        UnrecoverableKeyException, KeyManagementException {
    +        // Create the truststore components
    +        TrustManager[] trustManagers = getTrustManagers(tlsConfiguration);
     
    -        // create SSL Context passing keystore password as the key password
    -        return createSslContext(keystore, keystorePasswd, keystorePasswd, keystoreType, protocol);
    +        // Initialize the ssl context
    +        return initializeSSLContext(tlsConfiguration, clientAuth, keyManagers, trustManagers);
         }
     
         /**
    -     * Creates an SSLContext instance using the given information.
    +     * Returns a configured {@link X509TrustManager} for the provided configuration. Useful for
    +     * constructing HTTP clients which require their own trust management rather than an
    +     * {@link SSLContext}. Filters and removes any trust managers that are not
    +     * {@link javax.net.ssl.X509TrustManager} implementations, and returns the <em>first</em>
    +     * X.509 trust manager.
          *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keyPasswd the password for the key within the keystore
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param protocol the protocol to use for the SSL connection
    -     *
    -     * @return an SSLContext instance
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object
    +     * @return an X.509 TrustManager (can be {@code null})
    +     * @throws TlsException if there is a problem reading the truststore to create the trust managers
          */
    -    public static SSLContext createSslContext(
    -        final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -        return createSslContextWithTrustManagers(keystore, keystorePasswd, keyPasswd, keystoreType, protocol).getKey();
    +    public static X509TrustManager getX509TrustManager(TlsConfiguration tlsConfiguration) throws TlsException {
    +        TrustManager[] trustManagers = getTrustManagers(tlsConfiguration);
    +        if (trustManagers == null) {
    +            return null;
    +        }
    +        Optional<X509TrustManager> x509TrustManager = Arrays.stream(trustManagers)
    +                .filter(tm -> tm instanceof X509TrustManager)
    +                .map(tm -> (X509TrustManager) tm)
    +                .findFirst();
    +        return x509TrustManager.orElse(null);
         }
     
         /**
    -     * Creates an SSLContext instance paired with its TrustManager instances using the given information.
    +     * Convenience method to return the {@link SSLSocketFactory} from the created {@link SSLContext}
    +     * because that is what most callers of {@link #createSslContext(TlsConfiguration, ClientAuth)}
    +     * actually need and don't know what to provide for the {@link ClientAuth} parameter.
          *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keyPasswd the password for the key within the keystore
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param protocol the protocol to use for the SSL connection
    -     *
    -     * @return a {@link Tuple} pairing an SSLContext instance paired with its TrustManager instances
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object
    +     * @return the configured SSLSocketFactory (can be {@code null})
    +     * @throws TlsException if there is a problem creating the SSLContext or SSLSocketFactory
          */
    -    public static Tuple<SSLContext, TrustManager[]> createSslContextWithTrustManagers(
    -            final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -
    -        // prepare the keystore
    -        final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType);
    -        try (final InputStream keyStoreStream = new FileInputStream(keystore)) {
    -            keyStore.load(keyStoreStream, keystorePasswd);
    -        }
    -        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    -        if (keyPasswd == null) {
    -            keyManagerFactory.init(keyStore, keystorePasswd);
    -        } else {
    -            keyManagerFactory.init(keyStore, keyPasswd);
    +    public static SSLSocketFactory createSSLSocketFactory(TlsConfiguration tlsConfiguration) throws TlsException {
    +        SSLContext sslContext = createSslContext(tlsConfiguration, ClientAuth.REQUIRED);
    +        if (sslContext == null) {
    +            // Only display an error in the log if the provided config wasn't empty
    +            if (!TlsConfiguration.isEmpty(tlsConfiguration)) {
    +                logger.error("The SSLContext could not be formed from the provided TLS configuration. Check the provided keystore and truststore properties");
    +            }
    +            return null;
             }
    -
    -        // initialize the ssl context
    -        final SSLContext ctx = SSLContext.getInstance(protocol);
    -        TrustManager[] trustManagers = new TrustManager[0];
    -        ctx.init(keyManagerFactory.getKeyManagers(), trustManagers, new SecureRandom());
    -
    -        return new Tuple<>(ctx, trustManagers);
    +        return sslContext.getSocketFactory();
         }
     
         /**
    -     * Creates an SSLContext instance using the given information.
    -     *
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param protocol the protocol to use for the SSL connection
    +     * Returns an array of {@link KeyManager}s for the provided configuration. Useful for constructing
    +     * HTTP clients which require their own key management rather than an {@link SSLContext}. The result can be
    +     * {@code null} or empty. If an empty configuration is provided, {@code null} is returned. However, if a partially-populated
    +     * but invalid configuration is provided, a {@link TlsException} is thrown.
          *
    -     * @return an SSLContext instance
    -     * @throws java.security.KeyStoreException if any issues accessing the keystore
    -     * @throws java.io.IOException for any problems loading the keystores
    -     * @throws java.security.NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws java.security.cert.CertificateException if there is an issue with the certificate
    -     * @throws java.security.UnrecoverableKeyException if the key is insufficient
    -     * @throws java.security.KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object with keystore properties
    +     * @return an array of KeyManagers (can be {@code null})
    +     * @throws TlsException if there is a problem reading the keystore to create the key managers
          */
    -    public static SSLContext createTrustSslContext(
    -            final String truststore, final char[] truststorePasswd, final String truststoreType, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -
    -        return createTrustSslContextWithTrustManagers(truststore, truststorePasswd, truststoreType, protocol).getKey();
    -
    +    @SuppressWarnings("RedundantCast")
    +    protected static KeyManager[] getKeyManagers(TlsConfiguration tlsConfiguration) throws TlsException {
    +        KeyManager[] keyManagers = null;
    +        if (tlsConfiguration.isKeystoreValid()) {
    +            KeyManagerFactory keyManagerFactory = KeyStoreUtils.loadKeyManagerFactory(tlsConfiguration);
    +            keyManagers = keyManagerFactory.getKeyManagers();
    +        } else {
    +            // If some keystore properties were populated but the key managers are empty, throw an exception to inform the caller
    +            if (tlsConfiguration.isAnyKeystorePopulated()) {
    +                logger.warn("Some keystore properties are populated ({}, {}, {}, {}) but not valid", (Object[]) tlsConfiguration.getKeystorePropertiesForLogging());
    +                throw new TlsException("The keystore properties are not valid");
    +            } else {
    +                // If they are empty, the caller was not expecting a valid response
    +                logger.debug("The keystore properties are not populated");
    +            }
    +        }
    +        return keyManagers;
         }
     
         /**
    -     * Creates an SSLContext instance paired with its TrustManager instances using the given information.
    -     *
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param protocol the protocol to use for the SSL connection
    +     * Returns an array of {@link TrustManager} implementations based on the provided truststore configurations. The result can be
    +     * {@code null} or empty. If an empty configuration is provided, {@code null} is returned. However, if a partially-populated
    +     * but invalid configuration is provided, a {@link TlsException} is thrown.
    +     * <p>
    +     * Most callers do not need the full array and can use {@link #getX509TrustManager(TlsConfiguration)} directly.
          *
    -     * @return a {@link Tuple} pairing an SSLContext instance paired with its TrustManager instances
    -     * @throws KeyStoreException if any issues accessing the keystore
    -     * @throws IOException for any problems loading the keystores
    -     * @throws NoSuchAlgorithmException if an algorithm is found to be used but is unknown
    -     * @throws CertificateException if there is an issue with the certificate
    -     * @throws UnrecoverableKeyException if the key is insufficient
    -     * @throws KeyManagementException if unable to manage the key
    +     * @param tlsConfiguration the TLS configuration container object with truststore properties
    +     * @return the loaded trust managers
    +     * @throws TlsException if there is a problem reading from the truststore
          */
    -    public static Tuple<SSLContext, TrustManager[]> createTrustSslContextWithTrustManagers(
    -            final String truststore, final char[] truststorePasswd, final String truststoreType, final String protocol)
    -            throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -        // prepare the truststore
    -        final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType);
    -        try (final InputStream trustStoreStream = new FileInputStream(truststore)) {
    -            trustStore.load(trustStoreStream, truststorePasswd);
    +    @SuppressWarnings("RedundantCast")
    +    protected static TrustManager[] getTrustManagers(TlsConfiguration tlsConfiguration) throws TlsException {
    +        TrustManager[] trustManagers = null;
    +        if (tlsConfiguration.isTruststoreValid()) {
    +            TrustManagerFactory trustManagerFactory = KeyStoreUtils.loadTrustManagerFactory(tlsConfiguration);
    +            trustManagers = trustManagerFactory.getTrustManagers();
    +        } else {
    +            // If some truststore properties were populated but the trust managers are empty, throw an exception to inform the caller
    +            if (tlsConfiguration.isAnyTruststorePopulated()) {
    +                logger.warn("Some truststore properties are populated ({}, {}, {}) but not valid", (Object[]) tlsConfiguration.getTruststorePropertiesForLogging());
    +                throw new TlsException("The truststore properties are not valid");
    +            } else {
    +                // If they are empty, the caller was not expecting a valid response
    +                logger.debug("The truststore properties are not populated");
    +            }
             }
    -        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    -        trustManagerFactory.init(trustStore);
    -
    -        // initialize the ssl context
    -        final SSLContext ctx = SSLContext.getInstance(protocol);
    -        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    -        ctx.init(new KeyManager[0], trustManagers, new SecureRandom());
    -
    -        return new Tuple<>(ctx, trustManagers);
    +        return trustManagers;
         }
     
    -    /**
    -     * Creates an SSLContext instance paired with its TrustManager instances using the given information.
    -     *
    -     * @param keystore the full path to the keystore
    -     * @param keystorePasswd the keystore password
    -     * @param keyPasswd the password for the key within the keystore
    -     * @param keystoreType the type of keystore (e.g., PKCS12, JKS)
    -     * @param truststore the full path to the truststore
    -     * @param truststorePasswd the truststore password
    -     * @param truststoreType the type of truststore (e.g., PKCS12, JKS)
    -     * @param clientAuth the type of client authentication
    -     * @param protocol         the protocol to use for the SSL connection
    -     *
    -     * @return a {@link Tuple} pairing an SSLSocketFactory instance with its TrustManagers
    -     *
    -     */
    -    public static Tuple<SSLContext, TrustManager[]> createTrustSslContextWithTrustManagers(
    -            final String keystore, final char[] keystorePasswd, final char[] keyPasswd, final String keystoreType,
    -            final String truststore, final char[] truststorePasswd, final String truststoreType,
    -            final ClientAuth clientAuth, final String protocol) throws CertificateException, UnrecoverableKeyException,
    -            NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException {
    -
    -            final Tuple<SSLContext, TrustManager[]> sslContextWithTrustManagers;
    -            if (keystore == null) {
    -                sslContextWithTrustManagers = createTrustSslContextWithTrustManagers(truststore, truststorePasswd, truststoreType, protocol);
    -            } else if (truststore == null) {
    -                sslContextWithTrustManagers = createSslContextWithTrustManagers(keystore, keystorePasswd, keyPasswd, keystoreType, protocol);
    -            } else {
    -                sslContextWithTrustManagers = createSslContextWithTrustManagers(keystore, keystorePasswd, keyPasswd, keystoreType, truststore,
    -                        truststorePasswd, truststoreType, clientAuth, protocol);
    +    private static SSLContext initializeSSLContext(TlsConfiguration tlsConfiguration, ClientAuth clientAuth, KeyManager[] keyManagers, TrustManager[] trustManagers) throws TlsException {
    +        final SSLContext sslContext;
    +        try {
    +            sslContext = SSLContext.getInstance(tlsConfiguration.getProtocol());
    +            sslContext.init(keyManagers, trustManagers, new SecureRandom());
    +            switch (clientAuth) {
    +                case REQUIRED:
    +                    sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
    +                    break;
    +                case WANT:
    +                    sslContext.getDefaultSSLParameters().setWantClientAuth(true);
    +                    break;
    +                case NONE:
    +                default:
    +                    sslContext.getDefaultSSLParameters().setWantClientAuth(false);
                 }
    -            return new Tuple<>(sslContextWithTrustManagers.getKey(), sslContextWithTrustManagers.getValue());
     
    +            return sslContext;
    +        } catch (NoSuchAlgorithmException | KeyManagementException e) {
    +            logger.error("Encountered an error creating SSLContext from TLS configuration ({}): {}", tlsConfiguration.toString(), e.getLocalizedMessage());
    +            throw new TlsException("Error creating SSL context", e);
    +        }
         }
     }
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TlsConfiguration.java+480 0 added
    @@ -0,0 +1,480 @@
    +/*
    + * 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.security.util;
    +
    +import java.io.File;
    +import java.net.MalformedURLException;
    +import java.util.Objects;
    +import org.apache.commons.lang3.builder.ToStringBuilder;
    +import org.apache.nifi.util.NiFiProperties;
    +import org.apache.nifi.util.StringUtils;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +/**
    + * This class serves as an immutable domain object (acting as an internal DTO) for the various keystore and truststore configuration settings necessary for building {@link javax.net.ssl.SSLContext}s.
    + */
    +public class TlsConfiguration {
    +    private static final Logger logger = LoggerFactory.getLogger(TlsConfiguration.class);
    +
    +    private static final String TLS_PROTOCOL_VERSION = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion();
    +    private static final String MASKED_PASSWORD_LOG = "********";
    +    private static final String NULL_LOG = "null";
    +
    +    private final String keystorePath;
    +    private final String keystorePassword;
    +    private final String keyPassword;
    +    private final KeystoreType keystoreType;
    +
    +    private final String truststorePath;
    +    private final String truststorePassword;
    +    private final KeystoreType truststoreType;
    +
    +    private final String protocol;
    +
    +    /**
    +     * Default constructor present for testing and completeness.
    +     */
    +    public TlsConfiguration() {
    +        this(null, null, null, "", null, null, "", null);
    +    }
    +
    +    /**
    +     * Instantiates a container object with the given configuration values.
    +     *
    +     * @param keystorePath       the keystore path
    +     * @param keystorePassword   the keystore password
    +     * @param keystoreType       the keystore type
    +     * @param truststorePath     the truststore path
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type
    +     */
    +    public TlsConfiguration(String keystorePath, String keystorePassword, KeystoreType keystoreType, String truststorePath, String truststorePassword, KeystoreType truststoreType) {
    +        this(keystorePath, keystorePassword, keystorePassword, keystoreType, truststorePath, truststorePassword, truststoreType, TLS_PROTOCOL_VERSION);
    +    }
    +
    +    /**
    +     * Instantiates a container object with the given configuration values.
    +     *
    +     * @param keystorePath       the keystore path
    +     * @param keystorePassword   the keystore password
    +     * @param keyPassword        the key password
    +     * @param keystoreType       the keystore type
    +     * @param truststorePath     the truststore path
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type
    +     */
    +    public TlsConfiguration(String keystorePath, String keystorePassword, String keyPassword,
    +                            KeystoreType keystoreType, String truststorePath, String truststorePassword, KeystoreType truststoreType) {
    +        this(keystorePath, keystorePassword, keyPassword, keystoreType, truststorePath, truststorePassword, truststoreType, TLS_PROTOCOL_VERSION);
    +    }
    +
    +    /**
    +     * Instantiates a container object with the given configuration values.
    +     *
    +     * @param keystorePath       the keystore path
    +     * @param keystorePassword   the keystore password
    +     * @param keyPassword        the key password
    +     * @param keystoreType       the keystore type as a String
    +     * @param truststorePath     the truststore path
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type as a String
    +     */
    +    public TlsConfiguration(String keystorePath, String keystorePassword, String keyPassword,
    +                            String keystoreType, String truststorePath, String truststorePassword, String truststoreType) {
    +        this(keystorePath, keystorePassword, keyPassword,
    +                (KeystoreType.isValidKeystoreType(keystoreType) ? KeystoreType.valueOf(keystoreType.toUpperCase()) : null),
    +                truststorePath, truststorePassword,
    +                (KeystoreType.isValidKeystoreType(truststoreType) ? KeystoreType.valueOf(truststoreType.toUpperCase()) : null),
    +                TLS_PROTOCOL_VERSION);
    +    }
    +
    +    /**
    +     * Instantiates a container object with the given configuration values.
    +     *
    +     * @param keystorePath       the keystore path
    +     * @param keystorePassword   the keystore password
    +     * @param keyPassword        the (optional) key password -- if {@code null}, the keystore password is assumed the same for the individual key
    +     * @param keystoreType       the keystore type as a String
    +     * @param truststorePath     the truststore path
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type as a String
    +     * @param protocol           the TLS protocol version string
    +     */
    +    public TlsConfiguration(String keystorePath, String keystorePassword, String keyPassword,
    +                            String keystoreType, String truststorePath, String truststorePassword, String truststoreType, String protocol) {
    +        this(keystorePath, keystorePassword, keyPassword,
    +                (KeystoreType.isValidKeystoreType(keystoreType) ? KeystoreType.valueOf(keystoreType.toUpperCase()) : null),
    +                truststorePath, truststorePassword,
    +                (KeystoreType.isValidKeystoreType(truststoreType) ? KeystoreType.valueOf(truststoreType.toUpperCase()) : null),
    +                protocol);
    +    }
    +
    +    /**
    +     * Instantiates a container object with the given configuration values.
    +     *
    +     * @param keystorePath       the keystore path
    +     * @param keystorePassword   the keystore password
    +     * @param keyPassword        the (optional) key password -- if {@code null}, the keystore password is assumed the same for the individual key
    +     * @param keystoreType       the keystore type
    +     * @param truststorePath     the truststore path
    +     * @param truststorePassword the truststore password
    +     * @param truststoreType     the truststore type
    +     * @param protocol           the TLS protocol version string
    +     */
    +    public TlsConfiguration(String keystorePath, String keystorePassword, String keyPassword,
    +                            KeystoreType keystoreType, String truststorePath, String truststorePassword, KeystoreType truststoreType, String protocol) {
    +        this.keystorePath = keystorePath;
    +        this.keystorePassword = keystorePassword;
    +        this.keyPassword = keyPassword;
    +        this.keystoreType = keystoreType;
    +        this.truststorePath = truststorePath;
    +        this.truststorePassword = truststorePassword;
    +        this.truststoreType = truststoreType;
    +        this.protocol = protocol;
    +    }
    +
    +    /**
    +     * Instantiates a container object with a deep copy of the given configuration values.
    +     *
    +     * @param other the configuration to copy
    +     */
    +    public TlsConfiguration(TlsConfiguration other) {
    +        this.keystorePath = other.keystorePath;
    +        this.keystorePassword = other.keystorePassword;
    +        this.keyPassword = other.keyPassword;
    +        this.keystoreType = other.keystoreType;
    +        this.truststorePath = other.truststorePath;
    +        this.truststorePassword = other.truststorePassword;
    +        this.truststoreType = other.truststoreType;
    +        this.protocol = other.protocol;
    +    }
    +
    +    // Static factory method from NiFiProperties
    +
    +    /**
    +     * Returns a {@link TlsConfiguration} instantiated from the relevant {@link NiFiProperties} properties.
    +     *
    +     * @param niFiProperties the NiFi properties
    +     * @return a populated TlsConfiguration container object
    +     */
    +    public static TlsConfiguration fromNiFiProperties(NiFiProperties niFiProperties) {
    +        if (niFiProperties == null) {
    +            throw new IllegalArgumentException("The NiFi properties cannot be null");
    +        }
    +
    +        String keystorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE);
    +        String keystorePassword = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD);
    +        String keyPassword = niFiProperties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD);
    +        String keystoreType = niFiProperties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
    +        String truststorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE);
    +        String truststorePassword = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD);
    +        String truststoreType = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
    +        String protocol = TLS_PROTOCOL_VERSION;
    +
    +        final TlsConfiguration tlsConfiguration = new TlsConfiguration(keystorePath, keystorePassword, keyPassword,
    +                keystoreType, truststorePath, truststorePassword,
    +                truststoreType, protocol);
    +        if (logger.isDebugEnabled()) {
    +            logger.debug("Instantiating TlsConfiguration from NiFi properties: {}, {}, {}, {}, {}, {}, {}, {}",
    +                    keystorePath, tlsConfiguration.getKeystorePasswordForLogging(), tlsConfiguration.getKeyPasswordForLogging(), keystoreType,
    +                    truststorePath, tlsConfiguration.getTruststorePasswordForLogging(), truststoreType, protocol);
    +        }
    +
    +        return tlsConfiguration;
    +    }
    +
    +    /**
    +     * Returns a {@link TlsConfiguration} instantiated from the relevant {@link NiFiProperties} properties for the truststore <em>only</em>. No keystore properties are read or used.
    +     *
    +     * @param niFiProperties the NiFi properties
    +     * @return a populated TlsConfiguration container object
    +     */
    +    public static TlsConfiguration fromNiFiPropertiesTruststoreOnly(NiFiProperties niFiProperties) {
    +        if (niFiProperties == null) {
    +            throw new IllegalArgumentException("The NiFi properties cannot be null");
    +        }
    +
    +        String truststorePath = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE);
    +        String truststorePassword = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD);
    +        String truststoreType = niFiProperties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
    +        String protocol = TLS_PROTOCOL_VERSION;
    +
    +        final TlsConfiguration tlsConfiguration = new TlsConfiguration(null, null, null, null, truststorePath, truststorePassword,
    +                truststoreType, protocol);
    +        if (logger.isDebugEnabled()) {
    +            logger.debug("Instantiating TlsConfiguration from NiFi properties: null x4, {}, {}, {}, {}",
    +                    truststorePath, tlsConfiguration.getTruststorePasswordForLogging(), truststoreType, protocol);
    +        }
    +
    +        return tlsConfiguration;
    +    }
    +
    +    /**
    +     * Returns {@code true} if the provided TlsConfiguration is {@code null} or <em>empty</em>
    +     * (i.e. neither any of the keystore nor truststore properties are populated).
    +     *
    +     * @param tlsConfiguration the container object to check
    +     * @return true if this container is empty or null
    +     */
    +    public static boolean isEmpty(TlsConfiguration tlsConfiguration) {
    +        return tlsConfiguration == null || !(tlsConfiguration.isAnyKeystorePopulated() || tlsConfiguration.isAnyTruststorePopulated());
    +    }
    +
    +    // Getters & setters
    +
    +    public String getKeystorePath() {
    +        return keystorePath;
    +    }
    +
    +    public String getKeystorePassword() {
    +        return keystorePassword;
    +    }
    +
    +    /**
    +     * Returns {@code "********"} if the keystore password is populated, {@code "null"} if not.
    +     *
    +     * @return a loggable String representation of the keystore password
    +     */
    +    public String getKeystorePasswordForLogging() {
    +        return maskPasswordForLog(keystorePassword);
    +    }
    +
    +    public String getKeyPassword() {
    +        return keyPassword;
    +    }
    +
    +    /**
    +     * Returns {@code "********"} if the key password is populated, {@code "null"} if not.
    +     *
    +     * @return a loggable String representation of the key password
    +     */
    +    public String getKeyPasswordForLogging() {
    +        return maskPasswordForLog(keyPassword);
    +    }
    +
    +    /**
    +     * Returns the "working" key password -- if the key password is populated, it is returned; otherwise the {@link #getKeystorePassword()} is returned.
    +     *
    +     * @return the key or keystore password actually populated
    +     */
    +    public String getFunctionalKeyPassword() {
    +        return StringUtils.isNotBlank(keyPassword) ? keyPassword : keystorePassword;
    +    }
    +
    +    /**
    +     * Returns {@code "********"} if the functional key password is populated, {@code "null"} if not.
    +     *
    +     * @return a loggable String representation of the functional key password
    +     */
    +    public String getFunctionalKeyPasswordForLogging() {
    +        return maskPasswordForLog(getFunctionalKeyPassword());
    +    }
    +
    +    public KeystoreType getKeystoreType() {
    +        return keystoreType;
    +    }
    +
    +    public String getTruststorePath() {
    +        return truststorePath;
    +    }
    +
    +    public String getTruststorePassword() {
    +        return truststorePassword;
    +    }
    +
    +    /**
    +     * Returns {@code "********"} if the truststore password is populated, {@code "null"} if not.
    +     *
    +     * @return a loggable String representation of the truststore password
    +     */
    +    public String getTruststorePasswordForLogging() {
    +        return maskPasswordForLog(truststorePassword);
    +    }
    +
    +    public KeystoreType getTruststoreType() {
    +        return truststoreType;
    +    }
    +
    +    public String getProtocol() {
    +        return protocol;
    +    }
    +
    +    // Boolean validators for keystore & truststore
    +
    +    /**
    +     * Returns {@code true} if the necessary properties are populated to instantiate a <strong>keystore</strong>. This does <em>not</em> validate the values (see {@link #isKeystoreValid()}).
    +     *
    +     * @return true if the path, password, and type are present
    +     */
    +    public boolean isKeystorePopulated() {
    +        return isStorePopulated(keystorePath, keystorePassword, keystoreType, "keystore");
    +    }
    +
    +    /**
    +     * Returns {@code true} if <em>any</em> of the keystore properties is populated, indicating that the caller expects a valid keystore to be generated.
    +     *
    +     * @return true if any keystore properties are present
    +     */
    +    public boolean isAnyKeystorePopulated() {
    +        return isAnyPopulated(keystorePath, keystorePassword, keystoreType);
    +    }
    +
    +    /**
    +     * Returns {@code true} if the necessary properties are populated and the keystore can be successfully instantiated (i.e. the path is valid and the password(s) are correct).
    +     *
    +     * @return true if the keystore properties are valid
    +     */
    +    public boolean isKeystoreValid() {
    +        boolean simpleCheck = isStoreValid(keystorePath, keystorePassword, keystoreType, "keystore");
    +        if (simpleCheck) {
    +            return true;
    +        } else if (StringUtils.isNotBlank(keyPassword) && !keystorePassword.equals(keyPassword)) {
    +            logger.debug("Simple keystore validity check failed; trying with separate key password");
    +            try {
    +                return isKeystorePopulated()
    +                        && KeyStoreUtils.isKeyPasswordCorrect(new File(keystorePath).toURI().toURL(), keystoreType, keystorePassword.toCharArray(),
    +                        getFunctionalKeyPassword().toCharArray());
    +            } catch (MalformedURLException e) {
    +                logger.error("Encountered an error validating the keystore: " + e.getLocalizedMessage());
    +                return false;
    +            }
    +        } else {
    +            return false;
    +        }
    +    }
    +
    +    /**
    +     * Returns {@code true} if the necessary properties are populated to instantiate a <strong>truststore</strong>. This does <em>not</em> validate the values (see {@link #isTruststoreValid()}).
    +     *
    +     * @return true if the path, password, and type are present
    +     */
    +    public boolean isTruststorePopulated() {
    +        return isStorePopulated(truststorePath, truststorePassword, truststoreType, "truststore");
    +    }
    +
    +    /**
    +     * Returns {@code true} if <em>any</em> of the truststore properties is populated, indicating that the caller expects a valid truststore to be generated.
    +     *
    +     * @return true if any truststore properties are present
    +     */
    +    public boolean isAnyTruststorePopulated() {
    +        return isAnyPopulated(truststorePath, truststorePassword, truststoreType);
    +    }
    +
    +    /**
    +     * Returns {@code true} if the necessary properties are populated and the truststore can be successfully instantiated (i.e. the path is valid and the password is correct).
    +     *
    +     * @return true if the truststore properties are valid
    +     */
    +    public boolean isTruststoreValid() {
    +        return isStoreValid(truststorePath, truststorePassword, truststoreType, "truststore");
    +    }
    +
    +    /**
    +     * Returns a {@code String[]} containing the keystore properties for logging. The order is
    +     * {@link #getKeystorePath()}, {@link #getKeystorePasswordForLogging()},
    +     * {@link #getFunctionalKeyPasswordForLogging()}, {@link #getKeystoreType()} (using the type or "null").
    +     *
    +     * @return a loggable String[]
    +     */
    +    public String[] getKeystorePropertiesForLogging() {
    +        return new String[]{getKeystorePath(), getKeystorePasswordForLogging(), getFunctionalKeyPasswordForLogging(), getKeystoreType() != null ? getKeystoreType().getType() : NULL_LOG};
    +    }
    +
    +    /**
    +     * Returns a {@code String[]} containing the truststore properties for logging. The order is
    +     * {@link #getTruststorePath()}, {@link #getTruststorePasswordForLogging()},
    +     * {@link #getTruststoreType()} (using the type or "null").
    +     *
    +     * @return a loggable String[]
    +     */
    +    public String[] getTruststorePropertiesForLogging() {
    +        return new String[]{getTruststorePath(), getTruststorePasswordForLogging(), getKeystoreType() != null ? getTruststoreType().getType() : NULL_LOG};
    +    }
    +
    +    @Override
    +    public String toString() {
    +        return new ToStringBuilder(this)
    +                .append("keystorePath", keystorePath)
    +                .append("keystorePassword", getKeystorePasswordForLogging())
    +                .append("keyPassword", getKeyPasswordForLogging())
    +                .append("keystoreType", keystoreType)
    +                .append("truststorePath", truststorePath)
    +                .append("truststorePassword", getTruststorePasswordForLogging())
    +                .append("truststoreType", truststoreType)
    +                .append("protocol", protocol)
    +                .toString();
    +    }
    +
    +    @Override
    +    public boolean equals(Object o) {
    +        if (this == o) return true;
    +        if (o == null || getClass() != o.getClass()) return false;
    +        TlsConfiguration that = (TlsConfiguration) o;
    +        return Objects.equals(keystorePath, that.keystorePath)
    +                && Objects.equals(keystorePassword, that.keystorePassword)
    +                && Objects.equals(keyPassword, that.keyPassword)
    +                && keystoreType == that.keystoreType
    +                && Objects.equals(truststorePath, that.truststorePath)
    +                && Objects.equals(truststorePassword, that.truststorePassword)
    +                && truststoreType == that.truststoreType
    +                && Objects.equals(protocol, that.protocol);
    +    }
    +
    +    @Override
    +    public int hashCode() {
    +        return Objects.hash(keystorePath, keystorePassword, keyPassword, keystoreType, truststorePath, truststorePassword, truststoreType, protocol);
    +    }
    +
    +    private static String maskPasswordForLog(String password) {
    +        return StringUtils.isNotBlank(password) ? MASKED_PASSWORD_LOG : NULL_LOG;
    +    }
    +
    +    private boolean isAnyPopulated(String path, String password, KeystoreType type) {
    +        return StringUtils.isNotBlank(path) || StringUtils.isNotBlank(password) || type != null;
    +    }
    +
    +    private boolean isStorePopulated(String path, String password, KeystoreType type, String label) {
    +        boolean isPopulated;
    +        String passwordForLogging;
    +
    +        // Legacy truststores can be populated without a password; only check the path and type
    +        isPopulated = StringUtils.isNotBlank(path) && type != null;
    +        if ("truststore".equalsIgnoreCase(label)) {
    +            passwordForLogging = getTruststorePasswordForLogging();
    +        } else {
    +            // Keystores require a password
    +            isPopulated = isPopulated && StringUtils.isNotBlank(password);
    +            passwordForLogging = getKeystorePasswordForLogging();
    +        }
    +
    +        if (logger.isDebugEnabled()) {
    +            logger.debug("TLS config {} is {}: {}, {}, {}", label, isPopulated ? "populated" : "not populated", path, passwordForLogging, type);
    +        }
    +        return isPopulated;
    +    }
    +
    +    private boolean isStoreValid(String path, String password, KeystoreType type, String label) {
    +        try {
    +            return isStorePopulated(path, password, type, label) && KeyStoreUtils.isStoreValid(new File(path).toURI().toURL(), type, password.toCharArray());
    +        } catch (MalformedURLException e) {
    +            logger.error("Encountered an error validating the " + label + ": " + e.getLocalizedMessage());
    +            return false;
    +        }
    +    }
    +}
    
  • nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/security/util/TlsException.java+14 13 renamed
    @@ -14,27 +14,28 @@
      * See the License for the specific language governing permissions and
      * limitations under the License.
      */
    -package org.apache.nifi.framework.security.util;
    +package org.apache.nifi.security.util;
    +
    +import java.security.GeneralSecurityException;
     
     /**
    - * Represents the exceptional case when a SslSocketFactory failed creation.
    - *
    + * A common exception for any TLS-configuration or -operation related exceptions in NiFi framework
    + * and extensions. The nested cause can be examined if the caller can resolve various root causes,
    + * or the top-level exception can be handled generically if no recovery is available.
      */
    -public class SslSocketFactoryCreationException extends SslException {
    -
    -    public SslSocketFactoryCreationException(Throwable cause) {
    -        super(cause);
    -    }
    -
    -    public SslSocketFactoryCreationException(String message, Throwable cause) {
    -        super(message, cause);
    +public class TlsException extends GeneralSecurityException {
    +    public TlsException() {
         }
     
    -    public SslSocketFactoryCreationException(String message) {
    +    public TlsException(String message) {
             super(message);
         }
     
    -    public SslSocketFactoryCreationException() {
    +    public TlsException(String message, Throwable cause) {
    +        super(message, cause);
         }
     
    +    public TlsException(Throwable cause) {
    +        super(cause);
    +    }
     }
    
  • nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/CertificateUtilsTest.groovy+103 34 modified
    @@ -31,6 +31,7 @@ import org.junit.runners.JUnit4
     import org.slf4j.Logger
     import org.slf4j.LoggerFactory
     
    +import javax.net.ssl.SSLException
     import javax.net.ssl.SSLPeerUnverifiedException
     import javax.net.ssl.SSLSession
     import javax.net.ssl.SSLSocket
    @@ -51,7 +52,6 @@ import java.util.concurrent.Future
     import java.util.concurrent.TimeUnit
     import java.util.concurrent.atomic.AtomicBoolean
     
    -import static org.junit.Assert.assertEquals
     import static org.junit.Assert.assertTrue
     
     @RunWith(JUnit4.class)
    @@ -104,13 +104,7 @@ class CertificateUtilsTest extends GroovyTestCase {
          *
          * @param dn the DN
          * @return the certificate
    -     * @throws IOException
    -     * @throws NoSuchAlgorithmException
    -     * @throws java.security.cert.CertificateException
    -     * @throws java.security.NoSuchProviderException
    -     * @throws java.security.SignatureException
    -     * @throws java.security.InvalidKeyException
    -     * @throws OperatorCreationException
    +     * @throws IOException* @throws NoSuchAlgorithmException* @throws java.security.cert.CertificateException* @throws java.security.NoSuchProviderException* @throws java.security.SignatureException* @throws java.security.InvalidKeyException* @throws OperatorCreationException
          */
         private
         static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    @@ -125,13 +119,7 @@ class CertificateUtilsTest extends GroovyTestCase {
          * @param issuerDn the issuer DN
          * @param issuerKey the issuer private key
          * @return the certificate
    -     * @throws IOException
    -     * @throws NoSuchAlgorithmException
    -     * @throws CertificateException
    -     * @throws NoSuchProviderException
    -     * @throws SignatureException
    -     * @throws InvalidKeyException
    -     * @throws OperatorCreationException
    +     * @throws IOException* @throws NoSuchAlgorithmException* @throws CertificateException* @throws NoSuchProviderException* @throws SignatureException* @throws InvalidKeyException* @throws OperatorCreationException
          */
         private
         static X509Certificate generateIssuedCertificate(String dn, X509Certificate issuer, KeyPair issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    @@ -147,6 +135,7 @@ class CertificateUtilsTest extends GroovyTestCase {
             [certificate, issuerCertificate] as X509Certificate[]
         }
     
    +    @SuppressWarnings("deprecation")
         private static javax.security.cert.X509Certificate generateLegacyCertificate(X509Certificate x509Certificate) {
             return javax.security.cert.X509Certificate.getInstance(x509Certificate.getEncoded())
         }
    @@ -205,20 +194,19 @@ class CertificateUtilsTest extends GroovyTestCase {
             SSLSocket noneSocket = [getNeedClientAuth: { -> false }, getWantClientAuth: { -> false }] as SSLSocket
     
             // Act
    -        CertificateUtils.ClientAuth needClientAuthStatus = CertificateUtils.getClientAuthStatus(needSocket)
    +        SslContextFactory.ClientAuth needClientAuthStatus = CertificateUtils.getClientAuthStatus(needSocket)
             logger.info("Client auth (needSocket): ${needClientAuthStatus}")
    -        CertificateUtils.ClientAuth wantClientAuthStatus = CertificateUtils.getClientAuthStatus(wantSocket)
    +        SslContextFactory.ClientAuth wantClientAuthStatus = CertificateUtils.getClientAuthStatus(wantSocket)
             logger.info("Client auth (wantSocket): ${wantClientAuthStatus}")
    -        CertificateUtils.ClientAuth noneClientAuthStatus = CertificateUtils.getClientAuthStatus(noneSocket)
    +        SslContextFactory.ClientAuth noneClientAuthStatus = CertificateUtils.getClientAuthStatus(noneSocket)
             logger.info("Client auth (noneSocket): ${noneClientAuthStatus}")
     
             // Assert
    -        assert needClientAuthStatus == CertificateUtils.ClientAuth.NEED
    -        assert wantClientAuthStatus == CertificateUtils.ClientAuth.WANT
    -        assert noneClientAuthStatus == CertificateUtils.ClientAuth.NONE
    +        assert needClientAuthStatus == SslContextFactory.ClientAuth.REQUIRED
    +        assert wantClientAuthStatus == SslContextFactory.ClientAuth.WANT
    +        assert noneClientAuthStatus == SslContextFactory.ClientAuth.NONE
         }
     
    -
         @Test
         void testShouldExtractClientCertificatesFromSSLServerSocketWithAnyClientAuth() {
             final String EXPECTED_DN = "CN=ncm.nifi.apache.org,OU=Security,O=Apache,ST=CA,C=US"
    @@ -436,7 +424,7 @@ class CertificateUtilsTest extends GroovyTestCase {
         }
     
         @Test
    -    public void testShouldGenerateSelfSignedCert() throws Exception {
    +    void testShouldGenerateSelfSignedCert() throws Exception {
             String dn = "CN=testDN,O=testOrg"
     
             int days = 365
    @@ -458,8 +446,8 @@ class CertificateUtilsTest extends GroovyTestCase {
         }
     
         @Test
    -    public void testIssueCert() throws Exception {
    -        int days = 365;
    +    void testIssueCert() throws Exception {
    +        int days = 365
             KeyPair issuerKeyPair = generateKeyPair()
             X509Certificate issuer = CertificateUtils.generateSelfSignedX509Certificate(issuerKeyPair, "CN=testCa,O=testOrg", SIGNATURE_ALGORITHM, days)
     
    @@ -486,7 +474,7 @@ class CertificateUtilsTest extends GroovyTestCase {
         }
     
         @Test
    -    public void reorderShouldPutElementsInCorrectOrder() {
    +    void reorderShouldPutElementsInCorrectOrder() {
             String cn = "CN=testcn"
             String l = "L=testl"
             String st = "ST=testst"
    @@ -504,8 +492,8 @@ class CertificateUtilsTest extends GroovyTestCase {
         }
     
         @Test
    -    public void testUniqueSerialNumbers() {
    -        def running = new AtomicBoolean(true);
    +    void testUniqueSerialNumbers() {
    +        def running = new AtomicBoolean(true)
             def executorService = Executors.newCachedThreadPool()
             def serialNumbers = Collections.newSetFromMap(new ConcurrentHashMap())
             try {
    @@ -514,7 +502,7 @@ class CertificateUtilsTest extends GroovyTestCase {
                     futures.add(executorService.submit(new Callable<Integer>() {
                         @Override
                         Integer call() throws Exception {
    -                        int count = 0;
    +                        int count = 0
                             while (running.get()) {
                                 def before = System.currentTimeMillis()
                                 def serialNumber = CertificateUtils.getUniqueSerialNumber()
    @@ -523,23 +511,23 @@ class CertificateUtilsTest extends GroovyTestCase {
                                 assertTrue(serialNumberMillis >= before)
                                 assertTrue(serialNumberMillis <= after)
                                 assertTrue(serialNumbers.add(serialNumber))
    -                            count++;
    +                            count++
                             }
    -                        return count;
    +                        return count
                         }
    -                }));
    +                }))
                 }
     
                 Thread.sleep(1000)
     
                 running.set(false)
     
    -            def totalRuns = 0;
    +            def totalRuns = 0
                 for (int i = 0; i < futures.size(); i++) {
                     try {
                         def numTimes = futures.get(i).get()
                         logger.info("future $i executed $numTimes times")
    -                    totalRuns += numTimes;
    +                    totalRuns += numTimes
                     } catch (ExecutionException e) {
                         throw e.getCause()
                     }
    @@ -586,4 +574,85 @@ class CertificateUtilsTest extends GroovyTestCase {
             assert certificate.getSubjectAlternativeNames().size() == SANS.size()
             assert certificate.getSubjectAlternativeNames()*.last().containsAll(SANS)
         }
    +
    +    @Test
    +    void testShouldDetectTlsErrors() {
    +        // Arrange
    +        final String msg = "Test exception"
    +
    +        // SSLPeerUnverifiedException isn't specifically defined in the method, but is a subclass of SSLException so it should be caught
    +        List<Throwable> directErrors = [new TlsException(msg), new SSLPeerUnverifiedException(msg), new CertificateException(msg), new SSLException(msg)]
    +        List<Throwable> causedErrors = directErrors.collect { Throwable cause -> new Exception(msg, cause) } + [
    +                new Exception(msg,
    +                        new Exception("Nested $msg",
    +                                new Exception("Double nested $msg",
    +                                        new TlsException("Triple nested $msg"))))]
    +        List<Throwable> unrelatedErrors = [new Exception(msg), new IllegalArgumentException(msg), new NullPointerException(msg)]
    +
    +        // Act
    +        def directResults = directErrors.collect { Throwable e -> CertificateUtils.isTlsError(e) }
    +        def causedResults = causedErrors.collect { Throwable e -> CertificateUtils.isTlsError(e) }
    +        def unrelatedResults = unrelatedErrors.collect { Throwable e -> CertificateUtils.isTlsError(e) }
    +
    +        logger.info("Direct results: ${directResults}")
    +        logger.info("Caused results: ${causedResults}")
    +        logger.info("Unrelated results: ${unrelatedResults}")
    +
    +        // Assert
    +        assert directResults.every()
    +        assert causedResults.every()
    +        assert !unrelatedResults.any()
    +    }
    +
    +    @Test
    +    void testShouldParseJavaVersion() {
    +        // Arrange
    +        def possibleVersions = ["1.5.0", "1.6.0", "1.7.0.123", "1.8.0.231", "9.0.1", "10.1.2", "11.2.3", "12.3.456"]
    +
    +        // Act
    +        def majorVersions = possibleVersions.collect { String version ->
    +            logger.debug("Attempting to determine major version of ${version}")
    +            CertificateUtils.parseJavaVersion(version)
    +        }
    +        logger.info("Major versions: ${majorVersions}")
    +
    +        // Assert
    +        assert majorVersions == (5..12)
    +    }
    +
    +    @Test
    +    void testShouldGetCurrentSupportedTlsProtocolVersions() {
    +        // Arrange
    +        int javaMajorVersion = CertificateUtils.getJavaVersion()
    +        logger.debug("Running on Java version: ${javaMajorVersion}")
    +
    +        // Act
    +        def tlsVersions = CertificateUtils.getCurrentSupportedTlsProtocolVersions()
    +        logger.info("Supported protocol versions for ${javaMajorVersion}: ${tlsVersions}")
    +
    +        // Assert
    +        if (javaMajorVersion < 11) {
    +            assert tlsVersions == ["TLSv1.2"] as String[]
    +        } else {
    +            assert tlsVersions == ["TLSv1.3", "TLSv1.2"] as String[]
    +        }
    +    }
    +
    +    @Test
    +    void testShouldGetMaxCurrentSupportedTlsProtocolVersion() {
    +        // Arrange
    +        int javaMajorVersion = CertificateUtils.getJavaVersion()
    +        logger.debug("Running on Java version: ${javaMajorVersion}")
    +
    +        // Act
    +        def tlsVersion = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion()
    +        logger.info("Highest supported protocol version for ${javaMajorVersion}: ${tlsVersion}")
    +
    +        // Assert
    +        if (javaMajorVersion < 11) {
    +            assert tlsVersion == "TLSv1.2"
    +        } else {
    +            assert tlsVersion == "TLSv1.3"
    +        }
    +    }
     }
    
  • nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/SslContextFactoryTest.groovy+297 0 added
    @@ -0,0 +1,297 @@
    +/*
    + * 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.security.util
    +
    +import org.apache.nifi.util.NiFiProperties
    +import org.bouncycastle.jce.provider.BouncyCastleProvider
    +import org.junit.After
    +import org.junit.Before
    +import org.junit.BeforeClass
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.JUnit4
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +import javax.net.ssl.SSLContext
    +import javax.net.ssl.SSLServerSocket
    +import java.security.Security
    +
    +@RunWith(JUnit4.class)
    +class SslContextFactoryTest extends GroovyTestCase {
    +    private static final Logger logger = LoggerFactory.getLogger(SslContextFactoryTest.class)
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/TlsConfigurationKeystore.jks"
    +    private static final String KEYSTORE_PASSWORD = "keystorepassword"
    +    private static final String KEY_PASSWORD = "keypassword"
    +    private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/TlsConfigurationTruststore.jks"
    +    private static final String TRUSTSTORE_PASSWORD = "truststorepassword"
    +    private static final KeystoreType TRUSTSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String PROTOCOL = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion()
    +
    +    // The default TLS protocol versions for different Java versions
    +    private static final List<String> JAVA_8_TLS_PROTOCOL_VERSIONS = ["TLSv1.2", "TLSv1.1", "TLSv1"]
    +    private static final List<String> JAVA_11_TLS_PROTOCOL_VERSIONS = ["TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"]
    +
    +    private static final Map<String, String> DEFAULT_PROPS = [
    +            (NiFiProperties.SECURITY_KEYSTORE)         : KEYSTORE_PATH,
    +            (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : KEYSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_KEY_PASSWD)       : KEY_PASSWORD,
    +            (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : KEYSTORE_TYPE.getType(),
    +            (NiFiProperties.SECURITY_TRUSTSTORE)       : TRUSTSTORE_PATH,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): TRUSTSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : TRUSTSTORE_TYPE.getType(),
    +    ]
    +
    +    private NiFiProperties mockNiFiProperties = NiFiProperties.createBasicNiFiProperties("", DEFAULT_PROPS)
    +
    +    private TlsConfiguration tlsConfiguration
    +
    +    @BeforeClass
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
    +
    +        logger.metaClass.methodMissing = { String name, args ->
    +            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    +        }
    +    }
    +
    +    @Before
    +    void setUp() {
    +        tlsConfiguration = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +    }
    +
    +    @After
    +    void tearDown() {
    +
    +    }
    +
    +    static List<String> getCurrentTlsProtocolVersions() {
    +        if (CertificateUtils.getJavaVersion() < 11) {
    +            return JAVA_8_TLS_PROTOCOL_VERSIONS
    +        } else {
    +            return JAVA_11_TLS_PROTOCOL_VERSIONS
    +        }
    +    }
    +
    +    /**
    +     * Asserts that the protocol versions are correct. In recent versions of Java, this enforces order as well, but in older versions, it just enforces presence.
    +     *
    +     * @param enabledProtocols the actual protocols, either in {@code String[]} or {@code Collection<String>} form
    +     * @param expectedProtocols the specific protocol versions to be present (ordered as desired)
    +     */
    +    void assertProtocolVersions(def enabledProtocols, def expectedProtocols) {
    +        if (CertificateUtils.getJavaVersion() > 8) {
    +            assert enabledProtocols == expectedProtocols as String[]
    +        } else {
    +            assert enabledProtocols as Set == expectedProtocols as Set
    +        }
    +    }
    +
    +    @Test
    +    void testShouldCreateSslContextFromTlsConfiguration() {
    +        // Arrange
    +        logger.info("Creating SSL Context from TLS Configuration: ${tlsConfiguration}")
    +
    +        // Act
    +        SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.NONE)
    +        logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +
    +        // Assert
    +        assert sslContext.protocol == tlsConfiguration.protocol
    +
    +        def defaultSSLParameters = sslContext.defaultSSLParameters
    +        logger.info("Default SSL Parameters: ${KeyStoreUtils.sslParametersToString(defaultSSLParameters)}" as String)
    +        assertProtocolVersions(defaultSSLParameters.protocols, getCurrentTlsProtocolVersions())
    +        assert !defaultSSLParameters.needClientAuth
    +        assert !defaultSSLParameters.wantClientAuth
    +
    +        // Check a socket created from this context
    +        assertSocketProtocols(sslContext)
    +    }
    +
    +    @Test
    +    void testCreateSslContextFromTlsConfigurationShouldHandleEmptyKeyPassword() {
    +        // Arrange
    +
    +        // Change the keystore to one with the same keystore and key password, but don't provide the key password
    +        Map missingKeyPasswordProps = DEFAULT_PROPS + [
    +                (NiFiProperties.SECURITY_KEYSTORE)  : "src/test/resources/samepassword.jks",
    +                (NiFiProperties.SECURITY_KEY_PASSWD): "",
    +        ]
    +        NiFiProperties propertiesWithoutKeyPassword = NiFiProperties.createBasicNiFiProperties("", missingKeyPasswordProps)
    +        TlsConfiguration configWithoutKeyPassword = TlsConfiguration.fromNiFiProperties(propertiesWithoutKeyPassword)
    +        logger.info("Creating SSL Context from TLS Configuration: ${configWithoutKeyPassword}")
    +
    +        // Act
    +        SSLContext sslContext = SslContextFactory.createSslContext(configWithoutKeyPassword, SslContextFactory.ClientAuth.NONE)
    +        logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +
    +        // Assert
    +        assert sslContext.protocol == configWithoutKeyPassword.protocol
    +
    +        def defaultSSLParameters = sslContext.defaultSSLParameters
    +        logger.info("Default SSL Parameters: ${KeyStoreUtils.sslParametersToString(defaultSSLParameters)}" as String)
    +        assertProtocolVersions(defaultSSLParameters.protocols, getCurrentTlsProtocolVersions())
    +        assert !defaultSSLParameters.needClientAuth
    +        assert !defaultSSLParameters.wantClientAuth
    +
    +        // Check a socket created from this context
    +        assertSocketProtocols(sslContext)
    +    }
    +
    +    /**
    +     * This test ensures that silent failures don't occur -- if some keystore/truststore properties
    +     * are populated but not enough to be valid, throw an exception on failure.
    +     */
    +    @Test
    +    void testCreateSslContextFromTlsConfigurationShouldFailOnInvalidProperties() {
    +        // Arrange
    +
    +        // Set up configurations missing the keystore path and truststore path
    +        Map missingKeystorePathProps = DEFAULT_PROPS + [
    +                (NiFiProperties.SECURITY_KEYSTORE): "",
    +        ]
    +        NiFiProperties propsNoKeystorePath = NiFiProperties.createBasicNiFiProperties("", missingKeystorePathProps)
    +        TlsConfiguration configNoKeystorePath = TlsConfiguration.fromNiFiProperties(propsNoKeystorePath)
    +        logger.info("Creating SSL Context from TLS Configuration: ${configNoKeystorePath}")
    +
    +        Map missingTruststorePathProps = DEFAULT_PROPS + [
    +                (NiFiProperties.SECURITY_TRUSTSTORE)     : "",
    +                // Remove the keystore properties to ensure the right conditional is tested
    +                (NiFiProperties.SECURITY_KEYSTORE)       : "",
    +                (NiFiProperties.SECURITY_KEYSTORE_PASSWD): "",
    +                (NiFiProperties.SECURITY_KEY_PASSWD)     : "",
    +                (NiFiProperties.SECURITY_KEYSTORE_TYPE)  : "",
    +        ]
    +        NiFiProperties propsNoTruststorePath = NiFiProperties.createBasicNiFiProperties("", missingTruststorePathProps)
    +        TlsConfiguration configNoTruststorePath = TlsConfiguration.fromNiFiProperties(propsNoTruststorePath)
    +        logger.info("Creating SSL Context from TLS Configuration: ${configNoTruststorePath}")
    +
    +        // Act
    +        def noKeystorePathMsg = shouldFail(TlsException) {
    +            SSLContext sslContext = SslContextFactory.createSslContext(configNoKeystorePath, SslContextFactory.ClientAuth.NONE)
    +            logger.info("Created SSL Context missing keystore path: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +        }
    +
    +        def noTruststorePathMsg = shouldFail(TlsException) {
    +            SSLContext sslContext = SslContextFactory.createSslContext(configNoTruststorePath, SslContextFactory.ClientAuth.NONE)
    +            logger.info("Created SSL Context missing truststore path: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +        }
    +
    +        // Assert
    +        assert noKeystorePathMsg =~ "The keystore properties are not valid"
    +        assert noTruststorePathMsg =~ "The truststore properties are not valid"
    +    }
    +
    +    /**
    +     * This is a regression test to ensure that a truststore without a password is allowed (legacy truststores did not require a password).
    +     */
    +    @Test
    +    void testCreateSslContextFromTlsConfigurationShouldHandleEmptyTruststorePassword() {
    +        // Arrange
    +
    +        // Change the truststore to one with no password
    +        Map truststoreNoPasswordProps = DEFAULT_PROPS + [
    +                (NiFiProperties.SECURITY_TRUSTSTORE)       : "src/test/resources/no-password-truststore.jks",
    +                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "",
    +        ]
    +        NiFiProperties propertiesNoTruststorePassword = NiFiProperties.createBasicNiFiProperties("", truststoreNoPasswordProps)
    +        TlsConfiguration configNoTruststorePassword = TlsConfiguration.fromNiFiProperties(propertiesNoTruststorePassword)
    +        logger.info("Creating SSL Context from TLS Configuration: ${configNoTruststorePassword}")
    +
    +        // Act
    +        SSLContext sslContext = SslContextFactory.createSslContext(configNoTruststorePassword, SslContextFactory.ClientAuth.NONE)
    +        logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +
    +        // Assert
    +        assert sslContext.protocol == configNoTruststorePassword.protocol
    +
    +        def defaultSSLParameters = sslContext.defaultSSLParameters
    +        logger.info("Default SSL Parameters: ${KeyStoreUtils.sslParametersToString(defaultSSLParameters)}" as String)
    +        assertProtocolVersions(defaultSSLParameters.protocols, getCurrentTlsProtocolVersions())
    +        assert !defaultSSLParameters.needClientAuth
    +        assert !defaultSSLParameters.wantClientAuth
    +
    +        // Check a socket created from this context
    +        assertSocketProtocols(sslContext)
    +    }
    +
    +    /**
    +     * This test is for legacy expectations in nifi-framework-core. That {@code SslContextFactory}
    +     * implementation threw an exception if the keystore properties were present and the truststore
    +     * properties were not.
    +     */
    +    @Test
    +    void testCreateSslContextFromTlsConfigurationShouldHandleKeystorePropsWithoutTruststoreProps() {
    +        // Arrange
    +
    +        // Change the keystore to one with the same keystore and key password, but don't provide the key password
    +        Map keystoreOnlyProps = DEFAULT_PROPS.findAll { k, v -> k.contains("keystore") }
    +        NiFiProperties keystoreNiFiProperties = NiFiProperties.createBasicNiFiProperties("", keystoreOnlyProps)
    +        TlsConfiguration keystoreOnlyConfig = TlsConfiguration.fromNiFiProperties(keystoreNiFiProperties)
    +        logger.info("Creating SSL Context from TLS Configuration: ${keystoreOnlyConfig}")
    +
    +        // Act
    +        def msg = shouldFail(TlsException) {
    +            SSLContext sslContext = SslContextFactory.createSslContext(keystoreOnlyConfig, SslContextFactory.ClientAuth.NONE)
    +            logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +        }
    +        logger.expected(msg)
    +
    +        // Assert
    +        assert msg =~ "Truststore properties are required if keystore properties are present"
    +    }
    +
    +    /**
    +     * This test is for legacy expectations in nifi-framework-core. That {@code SslContextFactory}
    +     * implementation returned {@code null} if none of the properties were populated.
    +     */
    +    @Test
    +    void testCreateSslContextFromTlsConfigurationShouldHandleEmptyConfiguration() {
    +        // Arrange
    +        TlsConfiguration emptyConfig = new TlsConfiguration()
    +        logger.info("Creating SSL Context from TLS Configuration: ${emptyConfig}")
    +
    +        // Act
    +        SSLContext sslContext = SslContextFactory.createSslContext(emptyConfig, SslContextFactory.ClientAuth.NONE)
    +
    +        // Assert
    +        assert !sslContext
    +    }
    +
    +    /**
    +     * Asserts an {@link SSLServerSocket} from the provided {@link SSLContext} has the proper TLS protocols set.
    +     *
    +     * @param sslContext the context under test
    +     */
    +    void assertSocketProtocols(SSLContext sslContext) {
    +        SSLServerSocket sslSocket = sslContext.serverSocketFactory.createServerSocket() as SSLServerSocket
    +        logger.info("Created SSL (server) socket: ${sslSocket}")
    +        assert sslSocket.enabledProtocols.contains("TLSv1.2")
    +
    +        // Override the SSL parameters protocol version
    +        SSLServerSocket customSslSocket = sslContext.serverSocketFactory.createServerSocket() as SSLServerSocket
    +        def customParameters = customSslSocket.getSSLParameters()
    +        customParameters.setProtocols(["TLSv1.2"] as String[])
    +        customSslSocket.setSSLParameters(customParameters)
    +        assertProtocolVersions(customSslSocket.enabledProtocols, ["TLSv1.2"])
    +    }
    +}
    \ No newline at end of file
    
  • nifi-commons/nifi-security-utils/src/test/groovy/org/apache/nifi/security/util/TlsConfigurationTest.groovy+212 0 added
    @@ -0,0 +1,212 @@
    +/*
    + * 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.security.util
    +
    +
    +import org.apache.nifi.util.NiFiProperties
    +import org.bouncycastle.jce.provider.BouncyCastleProvider
    +import org.junit.After
    +import org.junit.Before
    +import org.junit.BeforeClass
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.JUnit4
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +import java.security.Security
    +
    +@RunWith(JUnit4.class)
    +class TlsConfigurationTest extends GroovyTestCase {
    +    private static final Logger logger = LoggerFactory.getLogger(TlsConfigurationTest.class)
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/TlsConfigurationKeystore.jks"
    +    private static final String KEYSTORE_PASSWORD = "keystorepassword"
    +    private static final String KEY_PASSWORD = "keypassword"
    +    private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/TlsConfigurationTruststore.jks"
    +    private static final String TRUSTSTORE_PASSWORD = "truststorepassword"
    +    private static final KeystoreType TRUSTSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final Map<String, String> DEFAULT_PROPS = [
    +            (NiFiProperties.SECURITY_KEYSTORE)         : KEYSTORE_PATH,
    +            (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : KEYSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_KEY_PASSWD)       : KEY_PASSWORD,
    +            (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : KEYSTORE_TYPE.getType(),
    +            (NiFiProperties.SECURITY_TRUSTSTORE)       : TRUSTSTORE_PATH,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): TRUSTSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : TRUSTSTORE_TYPE.getType(),
    +    ]
    +
    +    private NiFiProperties mockNiFiProperties = NiFiProperties.createBasicNiFiProperties("", DEFAULT_PROPS)
    +
    +    private TlsConfiguration tlsConfiguration
    +
    +    @BeforeClass
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
    +
    +        logger.metaClass.methodMissing = { String name, args ->
    +            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    +        }
    +    }
    +
    +    @Before
    +    void setUp() throws Exception {
    +        tlsConfiguration = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +    }
    +
    +    @After
    +    void tearDown() throws Exception {
    +    }
    +
    +    @Test
    +    void testShouldCreateFromNiFiProperties() {
    +        // Arrange
    +
    +        // Act
    +        TlsConfiguration fromProperties = TlsConfiguration.fromNiFiProperties(mockNiFiProperties)
    +        logger.info("Created TlsConfiguration: ${fromProperties}")
    +
    +        // Assert
    +        assert fromProperties == tlsConfiguration
    +    }
    +
    +    @Test
    +    void testCreateFromNiFiPropertiesShouldHandleNullKeystoreTypes() {
    +        // Arrange
    +        def noKeystoreTypesProps = NiFiProperties.createBasicNiFiProperties("", DEFAULT_PROPS +
    +                [(NiFiProperties.SECURITY_KEYSTORE_TYPE)  : "",
    +                 (NiFiProperties.SECURITY_TRUSTSTORE_TYPE): ""
    +                ])
    +
    +        // Act
    +        TlsConfiguration fromProperties = TlsConfiguration.fromNiFiProperties(noKeystoreTypesProps)
    +        logger.info("Created TlsConfiguration: ${fromProperties}")
    +
    +        // Assert
    +        assert fromProperties.keystoreType == null
    +        assert fromProperties.truststoreType == null
    +    }
    +
    +    @Test
    +    void testShouldGetFunctionalKeyPassword() {
    +        // Arrange
    +        TlsConfiguration withKeyPassword = tlsConfiguration
    +
    +        // A container where the keystore password is explicitly set as the key password as well
    +        TlsConfiguration withoutKeyPassword = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +
    +        // A container where null is explicitly set as the key password
    +        TlsConfiguration withNullPassword = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, null, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +
    +        // Act
    +        String actualKeyPassword = withKeyPassword.getKeyPassword()
    +        String functionalKeyPassword = withKeyPassword.getFunctionalKeyPassword()
    +
    +        String duplicateKeystorePassword = withoutKeyPassword.getKeyPassword()
    +        String duplicateFunctionalKeyPassword = withoutKeyPassword.getFunctionalKeyPassword()
    +
    +        String missingKeyPassword = withNullPassword.getKeyPassword()
    +        String keystorePassword = withNullPassword.getFunctionalKeyPassword()
    +
    +        // Assert
    +        assert actualKeyPassword == KEY_PASSWORD
    +        assert functionalKeyPassword == KEY_PASSWORD
    +
    +        assert duplicateKeystorePassword == KEYSTORE_PASSWORD
    +        assert duplicateFunctionalKeyPassword == KEYSTORE_PASSWORD
    +
    +        assert missingKeyPassword == null
    +        assert keystorePassword == KEYSTORE_PASSWORD
    +    }
    +
    +    @Test
    +    void testShouldCheckKeystorePopulation() {
    +        // Arrange
    +        TlsConfiguration empty = new TlsConfiguration()
    +        TlsConfiguration noKeystorePassword = new TlsConfiguration(KEYSTORE_PATH, "", KEY_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +
    +        // Act
    +        boolean normalIsPopulated = tlsConfiguration.isKeystorePopulated()
    +        boolean emptyIsPopulated = empty.isKeystorePopulated()
    +        boolean noPasswordIsPopulated = noKeystorePassword.isKeystorePopulated()
    +
    +        // Assert
    +        assert normalIsPopulated
    +        assert !emptyIsPopulated
    +        assert !noPasswordIsPopulated
    +    }
    +
    +    @Test
    +    void testShouldCheckTruststorePopulation() {
    +        // Arrange
    +        TlsConfiguration empty = new TlsConfiguration()
    +        TlsConfiguration noTruststorePassword = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, "", TRUSTSTORE_TYPE)
    +
    +        // Act
    +        boolean normalIsPopulated = tlsConfiguration.isTruststorePopulated()
    +        boolean emptyIsPopulated = empty.isTruststorePopulated()
    +        boolean noPasswordIsPopulated = noTruststorePassword.isTruststorePopulated()
    +
    +        // Assert
    +        assert normalIsPopulated
    +        assert !emptyIsPopulated
    +        assert noPasswordIsPopulated
    +    }
    +
    +    @Test
    +    void testShouldValidateKeystoreConfiguration() {
    +        // Arrange
    +        TlsConfiguration empty = new TlsConfiguration()
    +        TlsConfiguration wrongPassword = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD.reverse(), KEY_PASSWORD.reverse(), KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD.reverse(), TRUSTSTORE_TYPE)
    +        TlsConfiguration invalid = new TlsConfiguration(KEYSTORE_PATH.reverse(), KEYSTORE_PASSWORD.reverse(), KEY_PASSWORD.reverse(), KEYSTORE_TYPE, TRUSTSTORE_PATH.reverse(), TRUSTSTORE_PASSWORD.reverse(), TRUSTSTORE_TYPE)
    +
    +        // Act
    +        boolean normalIsValid = tlsConfiguration.isKeystoreValid()
    +        boolean emptyIsValid = empty.isKeystoreValid()
    +        boolean wrongPasswordIsValid = wrongPassword.isKeystoreValid()
    +        boolean invalidIsValid = invalid.isKeystoreValid()
    +
    +        // Assert
    +        assert normalIsValid
    +        assert !emptyIsValid
    +        assert !wrongPasswordIsValid
    +        assert !invalidIsValid
    +    }
    +
    +    @Test
    +    void testShouldValidateTruststoreConfiguration() {
    +        // Arrange
    +        TlsConfiguration empty = new TlsConfiguration()
    +        TlsConfiguration wrongPassword = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD.reverse(), KEY_PASSWORD.reverse(), KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD.reverse(), TRUSTSTORE_TYPE)
    +        TlsConfiguration invalid = new TlsConfiguration(KEYSTORE_PATH.reverse(), KEYSTORE_PASSWORD.reverse(), KEY_PASSWORD.reverse(), KEYSTORE_TYPE, TRUSTSTORE_PATH.reverse(), TRUSTSTORE_PASSWORD.reverse(), TRUSTSTORE_TYPE)
    +
    +        // Act
    +        boolean normalIsValid = tlsConfiguration.isTruststoreValid()
    +        boolean emptyIsValid = empty.isTruststoreValid()
    +        boolean wrongPasswordIsValid = wrongPassword.isTruststoreValid()
    +        boolean invalidIsValid = invalid.isTruststoreValid()
    +
    +        // Assert
    +        assert normalIsValid
    +        assert !emptyIsValid
    +        assert !wrongPasswordIsValid
    +        assert !invalidIsValid
    +    }
    +}
    
  • nifi-commons/nifi-security-utils/src/test/resources/logback-test.xml+1 0 modified
    @@ -30,6 +30,7 @@
     
     
         <logger name="org.apache.nifi" level="INFO"/>
    +    <logger name="org.apache.nifi.security.util" level="DEBUG"/>
         <logger name="org.apache.nifi.security.util.crypto" level="DEBUG"/>
     
         <root level="INFO">
    
  • nifi-commons/nifi-security-utils/src/test/resources/no-password-truststore.jks+0 0 renamed
  • nifi-commons/nifi-security-utils/src/test/resources/samepassword.jks+0 0 renamed
  • nifi-commons/nifi-security-utils/src/test/resources/TlsConfigurationKeystore.jks+0 0 added
  • nifi-commons/nifi-security-utils/src/test/resources/TlsConfigurationTruststore.jks+0 0 added
  • nifi-commons/nifi-site-to-site-client/src/main/java/org/apache/nifi/remote/client/SiteToSiteClient.java+17 17 modified
    @@ -16,6 +16,21 @@
      */
     package org.apache.nifi.remote.client;
     
    +import java.io.Closeable;
    +import java.io.File;
    +import java.io.FileInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.Serializable;
    +import java.net.InetAddress;
    +import java.security.KeyStore;
    +import java.security.SecureRandom;
    +import java.util.LinkedHashSet;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.KeyManagerFactory;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.TrustManagerFactory;
     import org.apache.nifi.components.state.StateManager;
     import org.apache.nifi.events.EventReporter;
     import org.apache.nifi.remote.Transaction;
    @@ -29,24 +44,9 @@
     import org.apache.nifi.remote.protocol.DataPacket;
     import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
     import org.apache.nifi.remote.protocol.http.HttpProxy;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.KeyStoreUtils;
     
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManagerFactory;
    -import java.io.Closeable;
    -import java.io.File;
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.Serializable;
    -import java.net.InetAddress;
    -import java.security.KeyStore;
    -import java.security.SecureRandom;
    -import java.util.LinkedHashSet;
    -import java.util.Set;
    -import java.util.concurrent.TimeUnit;
    -
     /**
      * <p>
      * The SiteToSiteClient provides a mechanism for sending data to a remote
    @@ -919,7 +919,7 @@ public SSLContext getSslContext() {
                 if (keyManagerFactory != null && trustManagerFactory != null) {
                     try {
                         // initialize the ssl context
    -                    final SSLContext sslContext = SSLContext.getInstance("TLS");
    +                    final SSLContext sslContext = SSLContext.getInstance(CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
                         sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
                         sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
     
    
  • nifi-commons/nifi-site-to-site-client/src/test/java/org/apache/nifi/remote/client/http/TestHttpClient.java+4 0 modified
    @@ -65,6 +65,7 @@
     import org.apache.nifi.remote.protocol.http.HttpHeaders;
     import org.apache.nifi.remote.protocol.http.HttpProxy;
     import org.apache.nifi.remote.util.StandardDataPacket;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.stream.io.StreamUtils;
     import org.apache.nifi.web.api.dto.ControllerDTO;
     import org.apache.nifi.web.api.dto.PortDTO;
    @@ -456,6 +457,8 @@ public static void setup() throws Exception {
             sslContextFactory.setKeyStorePath("src/test/resources/certs/keystore.jks");
             sslContextFactory.setKeyStorePassword("passwordpassword");
             sslContextFactory.setKeyStoreType("JKS");
    +        sslContextFactory.setProtocol(CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        sslContextFactory.setExcludeProtocols("TLS", "TLSv1", "TLSv1.1");
     
             httpConnector = new ServerConnector(server);
     
    @@ -464,6 +467,7 @@ public static void setup() throws Exception {
             sslConnector = new ServerConnector(server,
                     new SslConnectionFactory(sslContextFactory, "http/1.1"),
                     new HttpConnectionFactory(https));
    +        logger.info("SSL Connector: " + sslConnector.dump());
     
             server.setConnectors(new Connector[] { httpConnector, sslConnector });
     
    
  • nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/ServerSocketConfiguration.java+9 14 modified
    @@ -16,34 +16,29 @@
      */
     package org.apache.nifi.io.socket;
     
    -import java.io.FileNotFoundException;
    -import java.io.IOException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -
     import javax.net.ssl.SSLContext;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     
     public final class ServerSocketConfiguration {
     
         private boolean needClientAuth;
         private Integer socketTimeout;
         private Boolean reuseAddress;
         private Integer receiveBufferSize;
    -    private SSLContextFactory sslContextFactory;
    +    private TlsConfiguration tlsConfiguration;
     
         public ServerSocketConfiguration() {
         }
     
    -    public SSLContext createSSLContext()
    -            throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
    -        return sslContextFactory == null ? null : sslContextFactory.createSslContext();
    +    public SSLContext createSSLContext() throws TlsException {
    +        // ClientAuth was hardcoded to REQUIRED in removed SSLContextFactory and overridden in SocketUtils when the socket is created
    +        return SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.REQUIRED);
         }
     
    -    public void setSSLContextFactory(final SSLContextFactory sslContextFactory) {
    -        this.sslContextFactory = sslContextFactory;
    +    public void setTlsConfiguration(final TlsConfiguration tlsConfiguration) {
    +        this.tlsConfiguration = tlsConfiguration;
         }
     
         public Integer getSocketTimeout() {
    
  • nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketConfiguration.java+9 14 modified
    @@ -16,15 +16,10 @@
      */
     package org.apache.nifi.io.socket;
     
    -import java.io.FileNotFoundException;
    -import java.io.IOException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -
     import javax.net.ssl.SSLContext;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     
     public final class SocketConfiguration {
     
    @@ -36,15 +31,15 @@ public final class SocketConfiguration {
         private Boolean oobInline;
         private Boolean tcpNoDelay;
         private Integer trafficClass;
    -    private SSLContextFactory sslContextFactory;
    +    private TlsConfiguration tlsConfiguration;
     
    -    public SSLContext createSSLContext()
    -            throws KeyManagementException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException, FileNotFoundException, IOException {
    -        return sslContextFactory == null ? null : sslContextFactory.createSslContext();
    +    public SSLContext createSSLContext() throws TlsException {
    +        // This is only used for client sockets, so the client auth setting is ignored
    +        return SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.NONE);
         }
     
    -    public void setSSLContextFactory(final SSLContextFactory sslContextFactory) {
    -        this.sslContextFactory = sslContextFactory;
    +    public void setTlsConfiguration(final TlsConfiguration tlsConfiguration) {
    +        this.tlsConfiguration = tlsConfiguration;
         }
     
         public Integer getSocketTimeout() {
    
  • nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketListener.java+2 7 modified
    @@ -21,18 +21,13 @@
     import java.net.Socket;
     import java.net.SocketException;
     import java.net.SocketTimeoutException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
     import java.util.concurrent.ExecutorService;
     import java.util.concurrent.Executors;
     import java.util.concurrent.ThreadFactory;
     import java.util.concurrent.TimeUnit;
     import java.util.concurrent.atomic.AtomicInteger;
     import java.util.concurrent.atomic.AtomicLong;
    -
    +import org.apache.nifi.security.util.TlsException;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    @@ -83,7 +78,7 @@ public void start() throws IOException {
     
             try {
                 serverSocket = SocketUtils.createServerSocket(port, configuration);
    -        } catch (KeyManagementException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | CertificateException e) {
    +        } catch (TlsException e) {
                 throw new IOException(e);
             }
     
    
  • nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SocketUtils.java+60 16 modified
    @@ -20,25 +20,27 @@
     import java.net.InetSocketAddress;
     import java.net.ServerSocket;
     import java.net.Socket;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -
     import javax.net.ssl.SSLContext;
     import javax.net.ssl.SSLServerSocket;
     import javax.net.ssl.SSLSocket;
    -
     import org.apache.nifi.logging.NiFiLog;
    -
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.apache.nifi.security.util.TlsException;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
     public final class SocketUtils {
     
         private static final Logger logger = new NiFiLog(LoggerFactory.getLogger(SocketUtils.class));
     
    +    /**
    +     * Returns a {@link Socket} (effectively used as a client socket) for the given address and configuration.
    +     *
    +     * @param address   the {@link InetSocketAddress} for the socket (used for hostname and port)
    +     * @param config the {@link SocketConfiguration}
    +     * @return the socket (can be configured for SSL)
    +     * @throws IOException  if there is a problem creating the socket
    +     */
         public static Socket createSocket(final InetSocketAddress address, final SocketConfiguration config) throws IOException {
             if (address == null) {
                 throw new IllegalArgumentException("Socket address may not be null.");
    @@ -58,7 +60,14 @@ public static Socket createSocket(final InetSocketAddress address, final SocketC
             if (sslContext == null) {
                 socket = new Socket(address.getHostName(), address.getPort());
             } else {
    -            socket = sslContext.getSocketFactory().createSocket(address.getHostName(), address.getPort());
    +            /* This would ideally be refactored to a shared create method but Socket and ServerSocket
    +             * do not share a common interface; Socket is effectively "client socket" in this context
    +             */
    +            Socket tempSocket = sslContext.getSocketFactory().createSocket(address.getHostName(), address.getPort());
    +            final SSLSocket sslSocket = (SSLSocket) tempSocket;
    +            // Enforce custom protocols on socket
    +            sslSocket.setEnabledProtocols(CertificateUtils.getCurrentSupportedTlsProtocolVersions());
    +            socket = sslSocket;
             }
     
             if (config.getSocketTimeout() != null) {
    @@ -96,8 +105,17 @@ public static Socket createSocket(final InetSocketAddress address, final SocketC
             return socket;
         }
     
    +    /**
    +     * Returns a {@link ServerSocket} for the given port and configuration.
    +     *
    +     * @param port   the port for the socket
    +     * @param config the {@link ServerSocketConfiguration}
    +     * @return the server socket (can be configured for SSL)
    +     * @throws IOException  if there is a problem creating the socket
    +     * @throws TlsException if there is a problem creating the socket
    +     */
         public static ServerSocket createServerSocket(final int port, final ServerSocketConfiguration config)
    -            throws IOException, KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, CertificateException {
    +            throws IOException, TlsException {
             if (config == null) {
                 throw new NullPointerException("Configuration may not be null.");
             }
    @@ -108,7 +126,10 @@ public static ServerSocket createServerSocket(final int port, final ServerSocket
                 serverSocket = new ServerSocket(port);
             } else {
                 serverSocket = sslContext.getServerSocketFactory().createServerSocket(port);
    -            ((SSLServerSocket) serverSocket).setNeedClientAuth(config.getNeedClientAuth());
    +            final SSLServerSocket sslServerSocket = (SSLServerSocket) serverSocket;
    +            sslServerSocket.setNeedClientAuth(config.getNeedClientAuth());
    +            // Enforce custom protocols on socket
    +            sslServerSocket.setEnabledProtocols(CertificateUtils.getCurrentSupportedTlsProtocolVersions());
             }
     
             if (config.getSocketTimeout() != null) {
    @@ -126,24 +147,47 @@ public static ServerSocket createServerSocket(final int port, final ServerSocket
             return serverSocket;
         }
     
    +    /**
    +     * Returns a {@link SSLServerSocket} for the given port and configuration.
    +     *
    +     * @param port                      the port for the socket
    +     * @param serverSocketConfiguration the {@link ServerSocketConfiguration}
    +     * @return the SSL server socket
    +     * @throws TlsException if there was a problem creating the socket
    +     */
    +    public static SSLServerSocket createSSLServerSocket(final int port, final ServerSocketConfiguration serverSocketConfiguration) throws TlsException {
    +        try {
    +            ServerSocket serverSocket = createServerSocket(port, serverSocketConfiguration);
    +            if (serverSocket instanceof SSLServerSocket) {
    +                return ((SSLServerSocket) serverSocket);
    +            } else {
    +                throw new TlsException("Created server socket does not support SSL/TLS");
    +            }
    +        } catch (IOException e) {
    +            logger.error("Encountered an error creating SSLServerSocket: {}", e.getLocalizedMessage());
    +            throw new TlsException("Error creating SSLServerSocket", e);
    +        }
    +
    +    }
    +
         public static void closeQuietly(final Socket socket) {
             if (socket == null) {
                 return;
             }
     
             try {
                 try {
    -                // can't shudown input/output individually with secure sockets
    -                if ((socket instanceof SSLSocket) == false) {
    -                    if (socket.isInputShutdown() == false) {
    +                // Can't shutdown input/output individually with secure sockets
    +                if (!(socket instanceof SSLSocket)) {
    +                    if (!socket.isInputShutdown()) {
                             socket.shutdownInput();
                         }
    -                    if (socket.isOutputShutdown() == false) {
    +                    if (!socket.isOutputShutdown()) {
                             socket.shutdownOutput();
                         }
                     }
                 } finally {
    -                if (socket.isClosed() == false) {
    +                if (!socket.isClosed()) {
                         socket.close();
                     }
                 }
    
  • nifi-commons/nifi-socket-utils/src/main/java/org/apache/nifi/io/socket/SSLContextFactory.java+0 124 removed
    @@ -1,124 +0,0 @@
    -/*
    - * 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.io.socket;
    -
    -import java.io.FileInputStream;
    -import java.io.FileNotFoundException;
    -import java.io.IOException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStore;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.SecureRandom;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.Arrays;
    -
    -import javax.net.ssl.KeyManager;
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.TrustManagerFactory;
    -
    -import org.apache.nifi.security.util.KeyStoreUtils;
    -import org.apache.nifi.util.NiFiProperties;
    -import org.apache.nifi.util.file.FileUtils;
    -
    -public class SSLContextFactory {
    -
    -    private final String keystore;
    -    private final char[] keystorePass;
    -    private final String keystoreType;
    -    private final char[] keyPassword;
    -    private final String truststore;
    -    private final char[] truststorePass;
    -    private final String truststoreType;
    -
    -    private final KeyManager[] keyManagers;
    -    private final TrustManager[] trustManagers;
    -
    -    public SSLContextFactory(final NiFiProperties properties) throws NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, KeyStoreException, UnrecoverableKeyException {
    -        keystore = properties.getProperty(NiFiProperties.SECURITY_KEYSTORE);
    -        keystorePass = getPass(properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD));
    -        keystoreType = properties.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
    -        keyPassword = getPass(properties.getProperty(NiFiProperties.SECURITY_KEY_PASSWD));
    -
    -        truststore = properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE);
    -        truststorePass = getPass(properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
    -        truststoreType = properties.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
    -
    -        // prepare the keystore
    -        final KeyStore keyStore = KeyStoreUtils.getKeyStore(keystoreType);
    -        final FileInputStream keyStoreStream = new FileInputStream(keystore);
    -        try {
    -            keyStore.load(keyStoreStream, keystorePass);
    -        } finally {
    -            FileUtils.closeQuietly(keyStoreStream);
    -        }
    -        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    -        if (isKeyAndKeystorePasswordDifferent()) {
    -            keyManagerFactory.init(keyStore, keyPassword);
    -        } else {
    -            keyManagerFactory.init(keyStore, keystorePass);
    -        }
    -
    -        // prepare the truststore
    -        final KeyStore trustStore = KeyStoreUtils.getTrustStore(truststoreType);
    -        final FileInputStream trustStoreStream = new FileInputStream(truststore);
    -        try {
    -            trustStore.load(trustStoreStream, truststorePass);
    -        } finally {
    -            FileUtils.closeQuietly(trustStoreStream);
    -        }
    -        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    -        trustManagerFactory.init(trustStore);
    -
    -        keyManagers = keyManagerFactory.getKeyManagers();
    -        trustManagers = trustManagerFactory.getTrustManagers();
    -    }
    -
    -    private boolean isKeyAndKeystorePasswordDifferent() {
    -        return keyPassword != null && !(new String(keyPassword).trim().isEmpty()) && !Arrays.equals(keyPassword, keystorePass);
    -    }
    -
    -    private static char[] getPass(final String password) {
    -        return password == null ? null : password.toCharArray();
    -    }
    -
    -    /**
    -     * Creates a SSLContext instance using the given information.
    -     *
    -     * @return a SSLContext instance
    -     * @throws java.security.KeyStoreException         if problem with keystore
    -     * @throws java.io.IOException                     if unable to create context
    -     * @throws java.security.NoSuchAlgorithmException  if algorithm isn't known
    -     * @throws java.security.cert.CertificateException if certificate is invalid
    -     * @throws java.security.UnrecoverableKeyException if the key cannot be recovered
    -     * @throws java.security.KeyManagementException    if the key is improper
    -     */
    -    public SSLContext createSslContext() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException,
    -            UnrecoverableKeyException, KeyManagementException {
    -
    -        // initialize the ssl context
    -        final SSLContext sslContext = SSLContext.getInstance("TLS");
    -        sslContext.init(keyManagers, trustManagers, new SecureRandom());
    -        sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
    -
    -        return sslContext;
    -
    -    }
    -}
    
  • nifi-commons/nifi-socket-utils/src/test/groovy/org/apache/nifi/io/socket/SocketUtilsTest.groovy+121 0 added
    @@ -0,0 +1,121 @@
    +/*
    + * 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.io.socket
    +
    +import org.apache.nifi.security.util.CertificateUtils
    +import org.apache.nifi.security.util.KeystoreType
    +import org.apache.nifi.security.util.TlsConfiguration
    +import org.apache.nifi.util.NiFiProperties
    +import org.bouncycastle.jce.provider.BouncyCastleProvider
    +import org.junit.After
    +import org.junit.Before
    +import org.junit.BeforeClass
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.JUnit4
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +import javax.net.ssl.SSLServerSocket
    +import java.security.Security
    +
    +@RunWith(JUnit4.class)
    +class SocketUtilsTest extends GroovyTestCase {
    +    private static final Logger logger = LoggerFactory.getLogger(SocketUtilsTest.class)
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/TlsConfigurationKeystore.jks"
    +    private static final String KEYSTORE_PASSWORD = "keystorepassword"
    +    private static final String KEY_PASSWORD = "keypassword"
    +    private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/TlsConfigurationTruststore.jks"
    +    private static final String TRUSTSTORE_PASSWORD = "truststorepassword"
    +    private static final KeystoreType TRUSTSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String PROTOCOL = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion()
    +
    +    private static final Map<String, String> DEFAULT_PROPS = [
    +            (NiFiProperties.SECURITY_KEYSTORE)         : KEYSTORE_PATH,
    +            (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : KEYSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_KEY_PASSWD)       : KEY_PASSWORD,
    +            (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : KEYSTORE_TYPE.getType(),
    +            (NiFiProperties.SECURITY_TRUSTSTORE)       : TRUSTSTORE_PATH,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): TRUSTSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : TRUSTSTORE_TYPE.getType(),
    +    ]
    +
    +    private NiFiProperties mockNiFiProperties = NiFiProperties.createBasicNiFiProperties(null, DEFAULT_PROPS)
    +
    +    // A static TlsConfiguration referencing the test resource keystore and truststore
    +//    private static final TlsConfiguration TLS_CONFIGURATION = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEY_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, PROTOCOL)
    +//    private static final SSLContext sslContext = SslContextFactory.createSslContext(TLS_CONFIGURATION, SslContextFactory.ClientAuth.NONE)
    +
    +    @BeforeClass
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
    +
    +        logger.metaClass.methodMissing = { String name, args ->
    +            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    +        }
    +    }
    +
    +    @Before
    +    void setUp() {
    +
    +    }
    +
    +    @After
    +    void tearDown() {
    +
    +    }
    +
    +    @Test
    +    void testCreateSSLServerSocketShouldRestrictTlsProtocols() {
    +        // Arrange
    +        ServerSocketConfiguration mockServerSocketConfiguration = new ServerSocketConfiguration()
    +        mockServerSocketConfiguration.setTlsConfiguration(TlsConfiguration.fromNiFiProperties(mockNiFiProperties))
    +
    +        // Act
    +        SSLServerSocket sslServerSocket = SocketUtils.createSSLServerSocket(0, mockServerSocketConfiguration)
    +        logger.info("Created SSL server socket: ${sslServerSocket}")
    +
    +        // Assert
    +        String[] enabledProtocols = sslServerSocket.getEnabledProtocols()
    +        logger.info("Enabled protocols: ${enabledProtocols}")
    +        assert enabledProtocols == CertificateUtils.getCurrentSupportedTlsProtocolVersions()
    +        assert !enabledProtocols.contains("TLSv1")
    +        assert !enabledProtocols.contains("TLSv1.1")
    +    }
    +
    +    @Test
    +    void testCreateServerSocketShouldRestrictTlsProtocols() {
    +        // Arrange
    +        ServerSocketConfiguration mockServerSocketConfiguration = new ServerSocketConfiguration()
    +        mockServerSocketConfiguration.setTlsConfiguration(TlsConfiguration.fromNiFiProperties(mockNiFiProperties))
    +
    +        // Act
    +        SSLServerSocket sslServerSocket = SocketUtils.createServerSocket(0, mockServerSocketConfiguration) as SSLServerSocket
    +        logger.info("Created SSL server socket: ${sslServerSocket}")
    +
    +        // Assert
    +        String[] enabledProtocols = sslServerSocket.getEnabledProtocols()
    +        logger.info("Enabled protocols: ${enabledProtocols}")
    +        assert enabledProtocols == CertificateUtils.getCurrentSupportedTlsProtocolVersions()
    +        assert !enabledProtocols.contains("TLSv1")
    +        assert !enabledProtocols.contains("TLSv1.1")
    +    }
    +}
    \ No newline at end of file
    
  • nifi-commons/nifi-socket-utils/src/test/groovy/org/apache/nifi/io/socket/SSLContextFactoryTest.groovy+0 174 removed
    @@ -1,174 +0,0 @@
    -/*
    - * 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.io.socket
    -
    -import org.apache.nifi.util.NiFiProperties
    -import org.bouncycastle.jce.provider.BouncyCastleProvider
    -import org.junit.After
    -import org.junit.AfterClass
    -import org.junit.Before
    -import org.junit.BeforeClass
    -import org.junit.Test
    -import org.junit.runner.RunWith
    -import org.junit.runners.JUnit4
    -import org.slf4j.Logger
    -import org.slf4j.LoggerFactory
    -
    -import javax.net.ssl.SSLContext
    -import java.security.Security
    -
    -@RunWith(JUnit4.class)
    -class SSLContextFactoryTest extends GroovyTestCase {
    -    private static final Logger logger = LoggerFactory.getLogger(SSLContextFactoryTest.class)
    -
    -    private static String NF_PROPS_FILE = null
    -
    -    @BeforeClass
    -    static void setUpOnce() throws Exception {
    -        Security.addProvider(new BouncyCastleProvider())
    -
    -        if (System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)) {
    -            NF_PROPS_FILE = System.getProperty(NiFiProperties.PROPERTIES_FILE_PATH)
    -            System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, null)
    -        }
    -
    -        logger.metaClass.methodMissing = { String name, args ->
    -            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    -        }
    -    }
    -
    -    @Before
    -    void setUp() throws Exception {
    -
    -    }
    -
    -    @After
    -    void tearDown() throws Exception {
    -    }
    -
    -    @AfterClass
    -    static void tearDownOnce() throws Exception {
    -        if (NF_PROPS_FILE) {
    -            System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, NF_PROPS_FILE)
    -        }
    -    }
    -
    -    /**
    -     * Returns a {@link NiFiProperties} object configured with default values for accessing
    -     * keystores and truststores. The values can be overridden by providing a map parameter.
    -     *
    -     * @param overrides an optional Map of overriding configuration values
    -     * @return the configured NiFiProperties object
    -     */
    -    private static NiFiProperties buildNiFiProperties(Map<String, String> overrides = [:]) {
    -        final Map DEFAULTS = [
    -                (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : "keystorepassword",
    -                (NiFiProperties.SECURITY_KEYSTORE)         : "src/test/resources/samepassword.jks",
    -                (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : "JKS",
    -                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "changeit",
    -                (NiFiProperties.SECURITY_TRUSTSTORE)       : buildCacertsPath(),
    -                (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : "JKS",
    -        ]
    -        DEFAULTS.putAll(overrides)
    -        NiFiProperties.createBasicNiFiProperties(null, DEFAULTS)
    -    }
    -
    -    /**
    -     * Returns the file path to the {@code cacerts} default JRE truststore. Handles Java 8
    -     * and earlier as well as Java 9 and later directory structures.
    -     *
    -     * @return the path to cacerts
    -     */
    -    private static String buildCacertsPath() {
    -        String javaHome = System.getenv("JAVA_HOME")
    -        if (System.getProperty("java.version").startsWith("1.")) {
    -            javaHome + "/jre/lib/security/cacerts"
    -        } else {
    -            javaHome + "/lib/security/cacerts"
    -        }
    -    }
    -
    -    @Test
    -    void testShouldVerifyKeystoreWithSameKeyPassword() throws Exception {
    -        // Arrange
    -
    -        // Set up the keystore configuration as NiFiProperties object
    -        NiFiProperties np = buildNiFiProperties()
    -
    -        // Create the SSLContextFactory with the config
    -        SSLContextFactory sslcf = new SSLContextFactory(np)
    -
    -        // Act
    -
    -        // Access the SSLContextFactory to create an SSLContext
    -        SSLContext sslContext = sslcf.createSslContext()
    -
    -        // Assert
    -
    -        // The SSLContext was accessible and correct
    -        assert sslContext
    -    }
    -
    -    @Test
    -    void testShouldVerifyKeystoreWithDifferentKeyPassword() throws Exception {
    -        // Arrange
    -
    -        // Set up the keystore configuration as NiFiProperties object
    -        // (prior to NIFI-6830, an UnrecoverableKeyException was thrown due to the wrong password being provided)
    -        NiFiProperties np = buildNiFiProperties([
    -                (NiFiProperties.SECURITY_KEYSTORE)  : "src/test/resources/differentpassword.jks",
    -                (NiFiProperties.SECURITY_KEY_PASSWD): "keypassword",
    -        ])
    -
    -        // Create the SSLContextFactory with the config
    -        SSLContextFactory sslcf = new SSLContextFactory(np)
    -
    -        // Act
    -
    -        // Access the SSLContextFactory to create an SSLContext
    -        SSLContext sslContext = sslcf.createSslContext()
    -
    -        // Assert
    -
    -        // The SSLContext was accessible and correct
    -        assert sslContext
    -    }
    -
    -    @Test
    -    void testShouldVerifyKeystoreWithEmptyKeyPassword() throws Exception {
    -        // Arrange
    -
    -        // Set up the keystore configuration as NiFiProperties object
    -        // (re-opened NIFI-6830, an UnrecoverableKeyException was thrown due to an empty password being provided)
    -        NiFiProperties np = buildNiFiProperties([
    -                (NiFiProperties.SECURITY_KEY_PASSWD): ""
    -        ])
    -
    -        // Create the SSLContextFactory with the config
    -        SSLContextFactory sslcf = new SSLContextFactory(np)
    -
    -        // Act
    -
    -        // Access the SSLContextFactory to create an SSLContext
    -        SSLContext sslContext = sslcf.createSslContext()
    -
    -        // Assert
    -
    -        // The SSLContext was accessible and correct
    -        assert sslContext
    -    }
    -}
    
  • nifi-commons/nifi-socket-utils/src/test/resources/differentpassword.jks+0 0 removed
  • nifi-commons/nifi-socket-utils/src/test/resources/TlsConfigurationKeystore.jks+0 0 added
  • nifi-commons/nifi-socket-utils/src/test/resources/TlsConfigurationTruststore.jks+0 0 added
  • nifi-commons/nifi-web-utils/pom.xml+5 0 modified
    @@ -91,5 +91,10 @@
                 <version>4.5.6</version>
                 <scope>compile</scope>
             </dependency>
    +        <dependency>
    +            <groupId>com.squareup.okhttp3</groupId>
    +            <artifactId>okhttp</artifactId>
    +            <version>3.14.4</version>
    +        </dependency>
         </dependencies>
     </project>
    
  • nifi-commons/nifi-web-utils/src/main/java/org/apache/nifi/security/util/OkHttpClientUtils.java+60 0 added
    @@ -0,0 +1,60 @@
    +/*
    + * 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.security.util;
    +
    +import java.security.UnrecoverableKeyException;
    +import javax.net.ssl.SSLSocketFactory;
    +import javax.net.ssl.X509TrustManager;
    +import okhttp3.OkHttpClient;
    +import org.apache.nifi.processor.exception.ProcessException;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
    +
    +/**
    + * This class contains utility methods for working with the {@link OkHttpClient} that many components use for external HTTP communication.
    + */
    +public class OkHttpClientUtils {
    +    private static final Logger logger = LoggerFactory.getLogger(OkHttpClientUtils.class);
    +
    +    /**
    +     * If the {@link TlsConfiguration} contains valid properties to configure an
    +     * {@link SSLSocketFactory}, parses the necessary values and applies the config to the client
    +     * builder. If the properties are not populated, no action is taken.
    +     *
    +     * @param tlsConfiguration the TLS configuration container object
    +     * @param okHttpClient     the OkHttp client builder
    +     * @return true if the TLS configuration was applied to the builder
    +     */
    +    public static boolean applyTlsToOkHttpClientBuilder(TlsConfiguration tlsConfiguration, OkHttpClient.Builder okHttpClient) {
    +        try {
    +            final SSLSocketFactory sslSocketFactory = SslContextFactory.createSSLSocketFactory(tlsConfiguration);
    +            final X509TrustManager x509TrustManager = SslContextFactory.getX509TrustManager(tlsConfiguration);
    +            if (sslSocketFactory != null && x509TrustManager != null) {
    +                okHttpClient.sslSocketFactory(sslSocketFactory, x509TrustManager);
    +                return true;
    +            }
    +        } catch (TlsException e) {
    +            if (e.getCause() instanceof UnrecoverableKeyException) {
    +                logger.error("Key password may be incorrect or not set. Check your keystore passwords." + e.getMessage());
    +            } else {
    +                logger.error("Encountered an error configuring TLS: {}", e.getLocalizedMessage());
    +                throw new ProcessException("Error configuring TLS", e);
    +            }
    +        }
    +        return false;
    +    }
    +}
    
  • nifi-framework-api/src/main/java/org/apache/nifi/authentication/LoginIdentityProviderConfigurationContext.java+6 5 modified
    @@ -17,11 +17,12 @@
     package org.apache.nifi.authentication;
     
     import java.util.Map;
    +import org.apache.nifi.configuration.NonComponentConfigurationContext;
     
     /**
      *
      */
    -public interface LoginIdentityProviderConfigurationContext {
    +public interface LoginIdentityProviderConfigurationContext extends NonComponentConfigurationContext {
     
         /**
          * @return identifier for the authority provider
    @@ -39,10 +40,10 @@ public interface LoginIdentityProviderConfigurationContext {
         Map<String, String> getProperties();
     
         /**
    -     * @param property to lookup the descriptor and value of
    -     * @return the value the component currently understands for the given
    -     * PropertyDescriptor. This method does not substitute default
    -     * PropertyDescriptor values, so the value returned will be null if not set
    +     * Returns the value of the provided property. This method does not substitute default values, so the value returned will be {@code null} if not set.
    +     *
    +     * @param property the property to retrieve
    +     * @return the current property value (can be null)
          */
         String getProperty(String property);
     }
    
  • nifi-framework-api/src/main/java/org/apache/nifi/configuration/NonComponentConfigurationContext.java+50 0 added
    @@ -0,0 +1,50 @@
    +/*
    + * 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.configuration;
    +
    +import java.util.Map;
    +import org.apache.nifi.controller.ConfigurationContext;
    +
    +/**
    + * Shared interface for various feature-specific configuration contexts which allows common code to
    + * handle property retrieval without awareness of the specific implementation.
    + * <p>
    + * Note: This interface is <em>not</em> used as the basis for component-specific configuration contexts (see {@link ConfigurationContext}).
    + */
    +public interface NonComponentConfigurationContext {
    +    /**
    +     * @return identifier for the caller entity
    +     */
    +    String getIdentifier();
    +
    +    /**
    +     * Returns all properties the configuration context contains regardless
    +     * of whether a value has been set for them or not. If no value is present
    +     * then its value is {@code null}.
    +     *
    +     * @return Map of all properties
    +     */
    +    Map<String, String> getProperties();
    +
    +    /**
    +     * Returns the value of the provided property. This method does not substitute default values, so the value returned will be {@code null} if not set.
    +     *
    +     * @param property the property to retrieve
    +     * @return the current property value (can be null)
    +     */
    +    String getProperty(String property);
    +}
    
  • nifi-nar-bundles/nifi-amqp-bundle/nifi-amqp-processors/src/main/java/org/apache/nifi/amqp/processors/AbstractAMQPProcessor.java+10 11 modified
    @@ -19,6 +19,12 @@
     import com.rabbitmq.client.Connection;
     import com.rabbitmq.client.ConnectionFactory;
     import com.rabbitmq.client.DefaultSaslConfig;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +import java.util.concurrent.BlockingQueue;
    +import java.util.concurrent.LinkedBlockingQueue;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.lifecycle.OnStopped;
     import org.apache.nifi.components.PropertyDescriptor;
    @@ -31,13 +37,6 @@
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.util.ArrayList;
    -import java.util.Collections;
    -import java.util.List;
    -import java.util.concurrent.BlockingQueue;
    -import java.util.concurrent.LinkedBlockingQueue;
    -
     
     /**
      * Base processor that uses RabbitMQ client API
    @@ -118,7 +117,7 @@ abstract class AbstractAMQPProcessor<T extends AMQPWorker> extends AbstractProce
                         + "Possible values are REQUIRED, WANT, NONE. This property is only used when an SSL Context "
                         + "has been defined and enabled.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
                 .defaultValue("REQUIRED")
                 .build();
     
    @@ -227,12 +226,12 @@ protected Connection createConnection(ProcessContext context) {
             final String rawClientAuth = context.getProperty(CLIENT_AUTH).getValue();
     
             if (sslService != null) {
    -            final SSLContextService.ClientAuth clientAuth;
    +            final SslContextFactory.ClientAuth clientAuth;
                 if (StringUtils.isBlank(rawClientAuth)) {
    -                clientAuth = SSLContextService.ClientAuth.REQUIRED;
    +                clientAuth = SslContextFactory.ClientAuth.REQUIRED;
                 } else {
                     try {
    -                    clientAuth = SSLContextService.ClientAuth.valueOf(rawClientAuth);
    +                    clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
                     } catch (final IllegalArgumentException iae) {
                         throw new IllegalStateException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
                                 rawClientAuth, StringUtils.join(SslContextFactory.ClientAuth.values(), ", ")));
    
  • nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java+3 1 modified
    @@ -20,6 +20,7 @@
     import com.amazonaws.ClientConfiguration;
     import com.amazonaws.Protocol;
     import com.amazonaws.auth.AWSCredentials;
    +import com.amazonaws.auth.AWSCredentialsProvider;
     import com.amazonaws.auth.AnonymousAWSCredentials;
     import com.amazonaws.auth.BasicAWSCredentials;
     import com.amazonaws.auth.PropertiesCredentials;
    @@ -57,6 +58,7 @@
     import org.apache.nifi.processors.aws.credentials.provider.factory.CredentialPropertyDescriptors;
     import org.apache.nifi.proxy.ProxyConfiguration;
     import org.apache.nifi.proxy.ProxySpec;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
     /**
    @@ -225,7 +227,7 @@ protected ClientConfiguration createConfiguration(final ProcessContext context)
             if(this.getSupportedPropertyDescriptors().contains(SSL_CONTEXT_SERVICE)) {
                 final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
                 if (sslContextService != null) {
    -                final SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE);
    +                final SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE);
                     // NIFI-3788: Changed hostnameVerifier from null to DHV (BrowserCompatibleHostnameVerifier is deprecated)
                     SdkTLSSocketFactory sdkTLSSocketFactory = new SdkTLSSocketFactory(sslContext, new DefaultHostnameVerifier());
                     config.getApacheHttpClientConfig().setSslSocketFactory(sdkTLSSocketFactory);
    
  • nifi-nar-bundles/nifi-beats-bundle/nifi-beats-processors/src/main/java/org/apache/nifi/processors/beats/ListenBeats.java+4 6 modified
    @@ -26,11 +26,8 @@
     import java.util.List;
     import java.util.Map;
     import java.util.concurrent.BlockingQueue;
    -
     import javax.net.ssl.SSLContext;
    -import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.commons.lang3.StringUtils;
    -
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.WritesAttribute;
     import org.apache.nifi.annotation.behavior.WritesAttributes;
    @@ -60,6 +57,7 @@
     import org.apache.nifi.processors.beats.handler.BeatsSocketChannelHandlerFactory;
     import org.apache.nifi.processors.beats.response.BeatsChannelResponse;
     import org.apache.nifi.processors.beats.response.BeatsResponse;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    @@ -92,8 +90,8 @@ public class ListenBeats extends AbstractListenEventBatchingProcessor<BeatsEvent
             .displayName("Client Auth")
             .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
             .required(false)
    -        .allowableValues(RestrictedSSLContextService.ClientAuth.values())
    -        .defaultValue(RestrictedSSLContextService.ClientAuth.REQUIRED.name())
    +        .allowableValues(SslContextFactory.ClientAuth.values())
    +        .defaultValue(SslContextFactory.ClientAuth.REQUIRED.name())
             .build();
     
         @Override
    @@ -157,7 +155,7 @@ protected ChannelDispatcher createDispatcher(final ProcessContext context, final
             final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslContextService != null) {
                 final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuthValue));
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuthValue));
                 clientAuth = SslContextFactory.ClientAuth.valueOf(clientAuthValue);
     
             }
    
  • nifi-nar-bundles/nifi-cassandra-bundle/nifi-cassandra-processors/src/main/java/org/apache/nifi/processors/cassandra/AbstractCassandraProcessor.java+13 15 modified
    @@ -28,6 +28,14 @@
     import com.datastax.driver.core.TypeCodec;
     import com.datastax.driver.core.exceptions.AuthenticationException;
     import com.datastax.driver.core.exceptions.NoHostAvailableException;
    +import java.net.InetSocketAddress;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.net.ssl.SSLContext;
     import org.apache.avro.Schema;
     import org.apache.avro.SchemaBuilder;
     import org.apache.commons.lang3.StringUtils;
    @@ -47,16 +55,6 @@
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.net.InetSocketAddress;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Set;
    -import java.util.concurrent.atomic.AtomicReference;
    -
     /**
      * AbstractCassandraProcessor is a base class for Cassandra processors and contains logic and variables common to most
      * processors integrating with Apache Cassandra.
    @@ -109,7 +107,7 @@ public abstract class AbstractCassandraProcessor extends AbstractProcessor {
                         + "Possible values are REQUIRED, WANT, NONE. This property is only used when an SSL Context "
                         + "has been defined and enabled.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
                 .defaultValue("REQUIRED")
                 .build();
     
    @@ -258,13 +256,13 @@ void connectToCassandra(ProcessContext context) {
                 final SSLContext sslContext;
     
                 if (sslService != null) {
    -                final SSLContextService.ClientAuth clientAuth;
    +                final SslContextFactory.ClientAuth clientAuth;
     
                     if (StringUtils.isBlank(rawClientAuth)) {
    -                    clientAuth = SSLContextService.ClientAuth.REQUIRED;
    +                    clientAuth = SslContextFactory.ClientAuth.REQUIRED;
                     } else {
                         try {
    -                        clientAuth = SSLContextService.ClientAuth.valueOf(rawClientAuth);
    +                        clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
                         } catch (final IllegalArgumentException iae) {
                             throw new IllegalStateException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
                                     rawClientAuth, StringUtils.join(SslContextFactory.ClientAuth.values(), ", ")));
    @@ -534,7 +532,7 @@ protected List<InetSocketAddress> getContactPoints(String contactPointList) {
             if (contactPointList == null) {
                 return null;
             }
    -        final List<String> contactPointStringList = Arrays.asList(contactPointList.split(","));
    +        final String[] contactPointStringList = contactPointList.split(",");
             List<InetSocketAddress> contactPoints = new ArrayList<>();
     
             for (String contactPointEntry : contactPointStringList) {
    
  • nifi-nar-bundles/nifi-cassandra-bundle/nifi-cassandra-services/src/main/java/org/apache/nifi/service/CassandraSessionProvider.java+9 11 modified
    @@ -22,6 +22,10 @@
     import com.datastax.driver.core.Metadata;
     import com.datastax.driver.core.ProtocolOptions;
     import com.datastax.driver.core.Session;
    +import java.net.InetSocketAddress;
    +import java.util.ArrayList;
    +import java.util.List;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
    @@ -42,12 +46,6 @@
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.net.InetSocketAddress;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.List;
    -
     @Tags({"cassandra", "dbcp", "database", "connection", "pooling"})
     @CapabilityDescription("Provides connection session for Cassandra processors to work with Apache Cassandra.")
     public class CassandraSessionProvider extends AbstractControllerService implements CassandraSessionProviderService {
    @@ -90,7 +88,7 @@ public class CassandraSessionProvider extends AbstractControllerService implemen
                         + "Possible values are REQUIRED, WANT, NONE. This property is only used when an SSL Context "
                         + "has been defined and enabled.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
                 .defaultValue("REQUIRED")
                 .build();
     
    @@ -212,12 +210,12 @@ private void connectToCassandra(ConfigurationContext context) {
                 final SSLContext sslContext;
     
                 if (sslService != null) {
    -                final SSLContextService.ClientAuth clientAuth;
    +                final SslContextFactory.ClientAuth clientAuth;
                     if (StringUtils.isBlank(rawClientAuth)) {
    -                    clientAuth = SSLContextService.ClientAuth.REQUIRED;
    +                    clientAuth = SslContextFactory.ClientAuth.REQUIRED;
                     } else {
                         try {
    -                        clientAuth = SSLContextService.ClientAuth.valueOf(rawClientAuth);
    +                        clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
                         } catch (final IllegalArgumentException iae) {
                             throw new ProviderCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
                                     rawClientAuth, StringUtils.join(SslContextFactory.ClientAuth.values(), ", ")));
    @@ -264,7 +262,7 @@ private List<InetSocketAddress> getContactPoints(String contactPointList) {
                 return null;
             }
     
    -        final List<String> contactPointStringList = Arrays.asList(contactPointList.split(","));
    +        final String[] contactPointStringList = contactPointList.split(",");
             List<InetSocketAddress> contactPoints = new ArrayList<>();
     
             for (String contactPointEntry : contactPointStringList) {
    
  • nifi-nar-bundles/nifi-confluent-platform-bundle/nifi-confluent-schema-registry-service/src/main/java/org/apache/nifi/confluent/schemaregistry/ConfluentSchemaRegistry.java+14 15 modified
    @@ -17,6 +17,19 @@
     
     package org.apache.nifi.confluent.schemaregistry;
     
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.EnumSet;
    +import java.util.List;
    +import java.util.Optional;
    +import java.util.OptionalLong;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import java.util.stream.Collectors;
    +import java.util.stream.Stream;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
     import org.apache.nifi.annotation.lifecycle.OnEnabled;
    @@ -34,24 +47,10 @@
     import org.apache.nifi.schema.access.SchemaField;
     import org.apache.nifi.schema.access.SchemaNotFoundException;
     import org.apache.nifi.schemaregistry.services.SchemaRegistry;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.serialization.record.RecordSchema;
     import org.apache.nifi.serialization.record.SchemaIdentifier;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
    -
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.EnumSet;
    -import java.util.List;
    -import java.util.Optional;
    -import java.util.OptionalLong;
    -import java.util.Set;
    -import java.util.concurrent.TimeUnit;
    -import java.util.stream.Collectors;
    -import java.util.stream.Stream;
     
     
     
    
  • nifi-nar-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-client-service/src/main/java/org/apache/nifi/elasticsearch/ElasticSearchClientServiceImpl.java+19 18 modified
    @@ -19,6 +19,20 @@
     
     import com.fasterxml.jackson.core.JsonProcessingException;
     import com.fasterxml.jackson.databind.ObjectMapper;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.MalformedURLException;
    +import java.net.URL;
    +import java.nio.charset.Charset;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.http.HttpEntity;
     import org.apache.http.HttpHost;
    @@ -36,27 +50,14 @@
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.reporting.InitializationException;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.util.StopWatch;
     import org.apache.nifi.util.StringUtils;
     import org.elasticsearch.client.Response;
     import org.elasticsearch.client.RestClient;
     import org.elasticsearch.client.RestClientBuilder;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.net.MalformedURLException;
    -import java.net.URL;
    -import java.nio.charset.Charset;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.TimeUnit;
    -
     public class ElasticSearchClientServiceImpl extends AbstractControllerService implements ElasticSearchClientService {
         private ObjectMapper mapper = new ObjectMapper();
     
    @@ -125,7 +126,7 @@ private void setupClient(ConfigurationContext context) throws MalformedURLExcept
             final SSLContext sslContext;
             try {
                 sslContext = (sslService != null && (sslService.isKeyStoreConfigured() || sslService.isTrustStoreConfigured()))
    -                ? sslService.createSSLContext(SSLContextService.ClientAuth.NONE) : null;
    +                ? sslService.createSSLContext(SslContextFactory.ClientAuth.NONE) : null;
             } catch (Exception e) {
                 getLogger().error("Error building up SSL Context from the supplied configuration.", e);
                 throw new InitializationException(e);
    @@ -260,7 +261,7 @@ public IndexOperationResponse bulk(List<IndexOperationRequest> operations) {
                 Response response = client.performRequest("POST", "/_bulk", Collections.emptyMap(), entity);
                 watch.stop();
     
    -            String rawResponse = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
    +            String rawResponse = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
     
                 if (getLogger().isDebugEnabled()) {
                     getLogger().debug(String.format("Response was: %s", rawResponse));
    @@ -303,7 +304,7 @@ public DeleteOperationResponse deleteById(String index, String type, List<String
     
                 if (getLogger().isDebugEnabled()) {
                     getLogger().debug(String.format("Response for bulk delete: %s",
    -                        IOUtils.toString(response.getEntity().getContent(), "UTF-8")));
    +                        IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)));
                 }
     
                 DeleteOperationResponse dor = new DeleteOperationResponse(watch.getDuration(TimeUnit.MILLISECONDS));
    @@ -335,7 +336,7 @@ public Map<String, Object> get(String index, String type, String id) {
                 endpoint.append("/").append(id);
                 Response response = client.performRequest("GET", endpoint.toString(), new BasicHeader("Content-Type", "application/json"));
     
    -            String body = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
    +            String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
     
                 return (Map<String, Object>) mapper.readValue(body, Map.class).get("_source");
             } catch (Exception ex) {
    
  • nifi-nar-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-processors/pom.xml+6 0 modified
    @@ -126,6 +126,12 @@ language governing permissions and limitations under the License. -->
                 <artifactId>nifi-standard-record-utils</artifactId>
                 <version>1.12.0-SNAPSHOT</version>
             </dependency>
    +        <dependency>
    +            <groupId>org.apache.nifi</groupId>
    +            <artifactId>nifi-web-utils</artifactId>
    +            <version>1.12.0-SNAPSHOT</version>
    +            <scope>compile</scope>
    +        </dependency>
         </dependencies>
     
         <build>
    
  • nifi-nar-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-processors/src/main/java/org/apache/nifi/processors/elasticsearch/AbstractElasticsearchHttpProcessor.java+20 51 modified
    @@ -18,6 +18,16 @@
     
     import com.fasterxml.jackson.databind.JsonNode;
     import com.fasterxml.jackson.databind.ObjectMapper;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.Proxy;
    +import java.net.URL;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.List;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
     import okhttp3.Authenticator;
     import okhttp3.Credentials;
     import okhttp3.OkHttpClient;
    @@ -36,31 +46,10 @@
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.proxy.ProxyConfiguration;
     import org.apache.nifi.proxy.ProxySpec;
    -import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.OkHttpClientUtils;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.util.StringUtils;
    -import org.apache.nifi.util.Tuple;
    -
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.X509TrustManager;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.net.Proxy;
    -import java.net.URL;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.List;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -import java.util.stream.Collectors;
     
     /**
      * A base class for Elasticsearch processors that use the HTTP API
    @@ -200,11 +189,11 @@ protected void createElasticsearchClient(ProcessContext context) throws ProcessE
                 final Proxy proxy = proxyConfig.createProxy();
                 okHttpClient.proxy(proxy);
     
    -            if (proxyConfig.hasCredential()){
    +            if (proxyConfig.hasCredential()) {
                     okHttpClient.proxyAuthenticator(new Authenticator() {
                         @Override
                         public Request authenticate(Route route, Response response) throws IOException {
    -                        final String credential=Credentials.basic(proxyConfig.getProxyUserName(), proxyConfig.getProxyUserPassword());
    +                        final String credential = Credentials.basic(proxyConfig.getProxyUserName(), proxyConfig.getProxyUserPassword());
                             return response.request().newBuilder()
                                     .header("Proxy-Authorization", credential)
                                     .build();
    @@ -213,35 +202,15 @@ public Request authenticate(Route route, Response response) throws IOException {
                 }
             }
     
    -
             // Set timeouts
             okHttpClient.connectTimeout((context.getProperty(CONNECT_TIMEOUT).evaluateAttributeExpressions().asTimePeriod(TimeUnit.MILLISECONDS).intValue()), TimeUnit.MILLISECONDS);
             okHttpClient.readTimeout(context.getProperty(RESPONSE_TIMEOUT).evaluateAttributeExpressions().asTimePeriod(TimeUnit.MILLISECONDS).intValue(), TimeUnit.MILLISECONDS);
     
    +        // Apply the TLS configuration if present
             final SSLContextService sslService = context.getProperty(PROP_SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
    -        final SSLContext sslContext = sslService == null ? null : sslService.createSSLContext(SSLContextService.ClientAuth.NONE);
    -
    -        // check if the ssl context is set and add the factory if so
    -        if (sslContext != null) {
    -            try {
    -                Tuple<SSLContext, TrustManager[]> sslContextTuple = SslContextFactory.createTrustSslContextWithTrustManagers(
    -                        sslService.getKeyStoreFile(),
    -                        sslService.getKeyStorePassword() != null ? sslService.getKeyStorePassword().toCharArray() : null,
    -                        sslService.getKeyPassword() != null ? sslService.getKeyPassword().toCharArray() : null,
    -                        sslService.getKeyStoreType(),
    -                        sslService.getTrustStoreFile(),
    -                        sslService.getTrustStorePassword() != null ? sslService.getTrustStorePassword().toCharArray() : null,
    -                        sslService.getTrustStoreType(),
    -                        SslContextFactory.ClientAuth.WANT,
    -                        sslService.getSslAlgorithm()
    -                );
    -                List<X509TrustManager> x509TrustManagers = Arrays.stream(sslContextTuple.getValue())
    -                        .filter(trustManager -> trustManager instanceof X509TrustManager)
    -                        .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
    -                okHttpClient.sslSocketFactory(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
    -            } catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException e) {
    -                throw new ProcessException(e);
    -            }
    +        if (sslService != null) {
    +            final TlsConfiguration tlsConfiguration = sslService.createTlsConfiguration();
    +            OkHttpClientUtils.applyTlsToOkHttpClientBuilder(tlsConfiguration, okHttpClient);
             }
     
             okHttpClientAtomicReference.set(okHttpClient.build());
    @@ -250,7 +219,7 @@ public Request authenticate(Route route, Response response) throws IOException {
         @Override
         protected Collection<ValidationResult> customValidate(ValidationContext validationContext) {
             List<ValidationResult> results = new ArrayList<>(super.customValidate(validationContext));
    -        if(validationContext.getProperty(PROXY_HOST).isSet() != validationContext.getProperty(PROXY_PORT).isSet()) {
    +        if (validationContext.getProperty(PROXY_HOST).isSet() != validationContext.getProperty(PROXY_PORT).isSet()) {
                 results.add(new ValidationResult.Builder()
                         .valid(false)
                         .explanation("Proxy Host and Proxy Port must be both set or empty")
    @@ -286,7 +255,7 @@ protected Response sendRequestToElasticsearch(OkHttpClient client, URL url, Stri
                 throw new IllegalArgumentException("Elasticsearch REST API verb not supported by this processor: " + verb);
             }
     
    -        if(!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
    +        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
                 String credential = Credentials.basic(username, password);
                 requestBuilder = requestBuilder.header("Authorization", credential);
             }
    
  • nifi-nar-bundles/nifi-email-bundle/nifi-email-processors/src/main/java/org/apache/nifi/processors/email/ListenSMTP.java+4 5 modified
    @@ -26,11 +26,9 @@
     import java.util.List;
     import java.util.Set;
     import java.util.concurrent.TimeUnit;
    -
     import javax.net.ssl.SSLContext;
     import javax.net.ssl.SSLSocket;
     import javax.net.ssl.SSLSocketFactory;
    -
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.TriggerSerially;
     import org.apache.nifi.annotation.behavior.WritesAttribute;
    @@ -51,6 +49,7 @@
     import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.processors.email.smtp.SmtpConsumer;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     import org.springframework.util.StringUtils;
    @@ -134,7 +133,7 @@ public class ListenSMTP extends AbstractSessionFactoryProcessor {
                 .displayName("Client Auth")
                 .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.NONE.toString(), SSLContextService.ClientAuth.REQUIRED.toString())
    +            .allowableValues(SslContextFactory.ClientAuth.NONE.name(), SslContextFactory.ClientAuth.REQUIRED.name())
                 .build();
     
         protected static final PropertyDescriptor SMTP_HOSTNAME = new PropertyDescriptor.Builder()
    @@ -250,12 +249,12 @@ private SMTPServer prepareServer(final ProcessContext context, final ProcessSess
                 public SSLSocket createSSLSocket(Socket socket) throws IOException {
                     InetSocketAddress remoteAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
                     String clientAuth = context.getProperty(CLIENT_AUTH).getValue();
    -                SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuth));
    +                SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuth));
                     SSLSocketFactory socketFactory = sslContext.getSocketFactory();
                     SSLSocket sslSocket = (SSLSocket) (socketFactory.createSocket(socket, remoteAddress.getHostName(), socket.getPort(), true));
                     sslSocket.setUseClientMode(false);
     
    -                if (SSLContextService.ClientAuth.REQUIRED.toString().equals(clientAuth)) {
    +                if (SslContextFactory.ClientAuth.REQUIRED.toString().equals(clientAuth)) {
                         this.setRequireTLS(true);
                         sslSocket.setNeedClientAuth(true);
                     }
    
  • nifi-nar-bundles/nifi-email-bundle/nifi-email-processors/src/test/java/org/apache/nifi/processors/email/TestListenSMTP.java+6 6 modified
    @@ -22,11 +22,10 @@
     import java.util.concurrent.Executors;
     import java.util.concurrent.ScheduledExecutorService;
     import java.util.concurrent.TimeUnit;
    -
     import org.apache.commons.mail.Email;
    -import org.apache.commons.mail.EmailException;
     import org.apache.commons.mail.SimpleEmail;
     import org.apache.nifi.remote.io.socket.NetworkUtils;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
     import org.apache.nifi.ssl.StandardSSLContextService;
    @@ -51,7 +50,7 @@ public void after() {
         }
     
         @Test
    -    public void validateSuccessfulInteraction() throws Exception, EmailException {
    +    public void validateSuccessfulInteraction() throws Exception {
             int port = NetworkUtils.availablePort();
     
             TestRunner runner = TestRunners.newTestRunner(ListenSMTP.class);
    @@ -90,7 +89,8 @@ public void validateSuccessfulInteraction() throws Exception, EmailException {
         }
     
         @Test
    -    public void validateSuccessfulInteractionWithTls() throws Exception, EmailException {
    +    public void validateSuccessfulInteractionWithTls() throws Exception {
    +        // TODO: Setting system properties without cleaning/restoring at the end of a test is an anti-pattern and can have side effects
             System.setProperty("mail.smtp.ssl.trust", "*");
             System.setProperty("javax.net.ssl.keyStore", "src/test/resources/keystore.jks");
             System.setProperty("javax.net.ssl.keyStorePassword", "passwordpassword");
    @@ -113,7 +113,7 @@ public void validateSuccessfulInteractionWithTls() throws Exception, EmailExcept
     
             // and add the SSL context to the runner
             runner.setProperty(ListenSMTP.SSL_CONTEXT_SERVICE, "ssl-context");
    -        runner.setProperty(ListenSMTP.CLIENT_AUTH, SSLContextService.ClientAuth.NONE.name());
    +        runner.setProperty(ListenSMTP.CLIENT_AUTH, SslContextFactory.ClientAuth.NONE.name());
             runner.assertValid();
     
             int messageCount = 5;
    @@ -152,7 +152,7 @@ public void validateSuccessfulInteractionWithTls() throws Exception, EmailExcept
         }
     
         @Test
    -    public void validateTooLargeMessage() throws Exception, EmailException {
    +    public void validateTooLargeMessage() throws Exception {
             int port = NetworkUtils.availablePort();
     
             TestRunner runner = TestRunners.newTestRunner(ListenSMTP.class);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/pom.xml+0 4 modified
    @@ -43,10 +43,6 @@
                 <artifactId>nifi-socket-utils</artifactId>
                 <version>1.12.0-SNAPSHOT</version>
             </dependency>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-security</artifactId>
    -        </dependency>
             <dependency>
                 <groupId>org.apache.nifi</groupId>
                 <artifactId>nifi-framework-core-api</artifactId>
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/impl/SocketProtocolListener.java+66 26 modified
    @@ -16,6 +16,21 @@
      */
     package org.apache.nifi.cluster.protocol.impl;
     
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.Socket;
    +import java.security.cert.Certificate;
    +import java.security.cert.CertificateException;
    +import java.security.cert.X509Certificate;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.Set;
    +import java.util.UUID;
    +import java.util.concurrent.CopyOnWriteArrayList;
    +import java.util.stream.Collectors;
    +import javax.net.ssl.SSLPeerUnverifiedException;
    +import javax.net.ssl.SSLSession;
    +import javax.net.ssl.SSLSocket;
     import org.apache.nifi.cluster.protocol.NodeIdentifier;
     import org.apache.nifi.cluster.protocol.ProtocolContext;
     import org.apache.nifi.cluster.protocol.ProtocolException;
    @@ -24,10 +39,10 @@
     import org.apache.nifi.cluster.protocol.ProtocolMessageMarshaller;
     import org.apache.nifi.cluster.protocol.ProtocolMessageUnmarshaller;
     import org.apache.nifi.cluster.protocol.message.ConnectionRequestMessage;
    -import org.apache.nifi.cluster.protocol.message.OffloadMessage;
     import org.apache.nifi.cluster.protocol.message.DisconnectMessage;
     import org.apache.nifi.cluster.protocol.message.FlowRequestMessage;
     import org.apache.nifi.cluster.protocol.message.HeartbeatMessage;
    +import org.apache.nifi.cluster.protocol.message.OffloadMessage;
     import org.apache.nifi.cluster.protocol.message.ProtocolMessage;
     import org.apache.nifi.cluster.protocol.message.ReconnectionRequestMessage;
     import org.apache.nifi.events.BulletinFactory;
    @@ -41,25 +56,8 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.net.ssl.SSLPeerUnverifiedException;
    -import javax.net.ssl.SSLSession;
    -import javax.net.ssl.SSLSocket;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.net.Socket;
    -import java.security.cert.Certificate;
    -import java.security.cert.CertificateException;
    -import java.security.cert.X509Certificate;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.Set;
    -import java.util.UUID;
    -import java.util.concurrent.CopyOnWriteArrayList;
    -import java.util.stream.Collectors;
    -
     /**
      * Implements a listener for protocol messages sent over unicast socket.
    - *
      */
     public class SocketProtocolListener extends SocketListener implements ProtocolListener {
     
    @@ -68,6 +66,9 @@ public class SocketProtocolListener extends SocketListener implements ProtocolLi
         private final Collection<ProtocolHandler> handlers = new CopyOnWriteArrayList<>();
         private volatile BulletinRepository bulletinRepository;
     
    +    private static int EXCEPTION_THRESHOLD_MILLIS = 10_000;
    +    private volatile long tlsErrorLastSeen = -1;
    +
         public SocketProtocolListener(
                 final int numThreads,
                 final int port,
    @@ -190,17 +191,56 @@ public void dispatchRequest(final Socket socket) {
                 final NodeIdentifier nodeId = getNodeIdentifier(request);
                 final String from = nodeId == null ? hostname : nodeId.toString();
                 logger.info("Finished processing request {} (type={}, length={} bytes) from {} in {}",
    -                requestId, request.getType(), countingIn.getBytesRead(), from, stopWatch.getDuration());
    +                    requestId, request.getType(), countingIn.getBytesRead(), from, stopWatch.getDuration());
             } catch (final IOException | ProtocolException e) {
    -            logger.warn("Failed processing protocol message from " + hostname + " due to " + e, e);
    -
    -            if (bulletinRepository != null) {
    -                final Bulletin bulletin = BulletinFactory.createBulletin("Clustering", "WARNING", String.format("Failed to process protocol message from %s due to: %s", hostname, e.toString()));
    -                bulletinRepository.addBulletin(bulletin);
    +            String msg = "Failed processing protocol message from " + hostname + " due to ";
    +            // Suppress repeated TLS errors
    +            if (CertificateUtils.isTlsError(e)) {
    +                boolean printedAsWarning = handleTlsError(msg, e);
    +
    +                // TODO: Move into handleTlsError and refactor shared behavior
    +                // If the error was printed as a warning, reset the last seen timer
    +                if (printedAsWarning) {
    +                    tlsErrorLastSeen = System.currentTimeMillis();
    +                }
    +            } else {
    +                logger.warn(msg + e, e);
    +                publishBulletinWarning(msg + e);
                 }
             }
         }
     
    +    private boolean handleTlsError(String msg, Throwable e) {
    +        final String populatedMessage = msg + e.getLocalizedMessage();
    +        if (tlsErrorRecentlySeen()) {
    +            logger.debug(populatedMessage);
    +            return false;
    +        } else {
    +            logger.warn(populatedMessage);
    +            publishBulletinWarning(populatedMessage);
    +            return true;
    +        }
    +    }
    +
    +    private void publishBulletinWarning(String message) {
    +        if (bulletinRepository != null) {
    +            final Bulletin bulletin = BulletinFactory.createBulletin("Clustering", "WARNING", message);
    +            bulletinRepository.addBulletin(bulletin);
    +        }
    +    }
    +
    +    /**
    +     * Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
    +     * {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
    +     * simply checks the last time the timestamp was updated.
    +     *
    +     * @return true if the time since the last similar exception occurred is below the threshold
    +     */
    +    private boolean tlsErrorRecentlySeen() {
    +        long now = System.currentTimeMillis();
    +        return now - tlsErrorLastSeen < EXCEPTION_THRESHOLD_MILLIS;
    +    }
    +
         private NodeIdentifier getNodeIdentifier(final ProtocolMessage message) {
             if (message == null) {
                 return null;
    @@ -247,8 +287,8 @@ private Set<String> getCertificateIdentities(final SSLSession sslSession) throws
             cert.checkValidity();
     
             final Set<String> identities = CertificateUtils.getSubjectAlternativeNames(cert).stream()
    -            .map(CertificateUtils::extractUsername)
    -            .collect(Collectors.toSet());
    +                .map(CertificateUtils::extractUsername)
    +                .collect(Collectors.toSet());
     
             return identities;
         }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/spring/ServerSocketConfigurationFactoryBean.java+8 6 modified
    @@ -16,14 +16,13 @@
      */
     package org.apache.nifi.cluster.protocol.spring;
     
    -import org.apache.nifi.io.socket.SSLContextFactory;
    +import java.util.concurrent.TimeUnit;
     import org.apache.nifi.io.socket.ServerSocketConfiguration;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.util.FormatUtils;
     import org.apache.nifi.util.NiFiProperties;
     import org.springframework.beans.factory.FactoryBean;
     
    -import java.util.concurrent.TimeUnit;
    -
     /**
      * Factory bean for creating a singleton ServerSocketConfiguration instance.
      */
    @@ -38,11 +37,14 @@ public ServerSocketConfiguration getObject() throws Exception {
                 configuration = new ServerSocketConfiguration();
                 configuration.setNeedClientAuth(true);
     
    -            final int timeout = (int) FormatUtils.getTimeDuration(properties.getClusterNodeReadTimeout(), TimeUnit.MILLISECONDS);
    +            final int timeout = (int) FormatUtils.getPreciseTimeDuration(properties.getClusterNodeReadTimeout(), TimeUnit.MILLISECONDS);
                 configuration.setSocketTimeout(timeout);
                 configuration.setReuseAddress(true);
    -            if (Boolean.valueOf(properties.getProperty(NiFiProperties.CLUSTER_PROTOCOL_IS_SECURE))) {
    -                configuration.setSSLContextFactory(new SSLContextFactory(properties));
    +
    +            // If the cluster protocol is marked as secure
    +            if (Boolean.parseBoolean(properties.getProperty(NiFiProperties.CLUSTER_PROTOCOL_IS_SECURE))) {
    +                // Parse the TLS configuration from the properties
    +                configuration.setTlsConfiguration(TlsConfiguration.fromNiFiProperties(properties));
                 }
             }
             return configuration;
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster-protocol/src/main/java/org/apache/nifi/cluster/protocol/spring/SocketConfigurationFactoryBean.java+8 6 modified
    @@ -16,14 +16,13 @@
      */
     package org.apache.nifi.cluster.protocol.spring;
     
    -import org.apache.nifi.io.socket.SSLContextFactory;
    +import java.util.concurrent.TimeUnit;
     import org.apache.nifi.io.socket.SocketConfiguration;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.util.FormatUtils;
     import org.apache.nifi.util.NiFiProperties;
     import org.springframework.beans.factory.FactoryBean;
     
    -import java.util.concurrent.TimeUnit;
    -
     /**
      * Factory bean for creating a singleton SocketConfiguration instance.
      */
    @@ -38,11 +37,14 @@ public SocketConfiguration getObject() throws Exception {
             if (configuration == null) {
                 configuration = new SocketConfiguration();
     
    -            final int timeout = (int) FormatUtils.getTimeDuration(properties.getClusterNodeReadTimeout(), TimeUnit.MILLISECONDS);
    +            final int timeout = (int) FormatUtils.getPreciseTimeDuration(properties.getClusterNodeReadTimeout(), TimeUnit.MILLISECONDS);
                 configuration.setSocketTimeout(timeout);
                 configuration.setReuseAddress(true);
    -            if (Boolean.valueOf(properties.getProperty(NiFiProperties.CLUSTER_PROTOCOL_IS_SECURE))) {
    -                configuration.setSSLContextFactory(new SSLContextFactory(properties));
    +
    +            // If the cluster protocol is marked as secure
    +            if (Boolean.parseBoolean(properties.getProperty(NiFiProperties.CLUSTER_PROTOCOL_IS_SECURE))) {
    +                // Parse the TLS configuration from the properties
    +                configuration.setTlsConfiguration(TlsConfiguration.fromNiFiProperties(properties));
                 }
             }
             return configuration;
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClient.java+12 51 modified
    @@ -17,9 +17,6 @@
     
     package org.apache.nifi.cluster.coordination.http.replication.okhttp;
     
    -import static org.apache.nifi.security.util.SslContextFactory.ClientAuth.WANT;
    -import static org.apache.nifi.security.util.SslContextFactory.createTrustSslContextWithTrustManagers;
    -
     import com.fasterxml.jackson.annotation.JsonInclude.Include;
     import com.fasterxml.jackson.annotation.JsonInclude.Value;
     import com.fasterxml.jackson.databind.ObjectMapper;
    @@ -30,12 +27,6 @@
     import java.io.InputStream;
     import java.io.OutputStream;
     import java.net.URI;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.Arrays;
     import java.util.HashMap;
     import java.util.List;
     import java.util.Map;
    @@ -46,10 +37,6 @@
     import java.util.stream.Collectors;
     import java.util.stream.Stream;
     import java.util.zip.GZIPInputStream;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.SSLSocketFactory;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.X509TrustManager;
     import javax.ws.rs.HttpMethod;
     import javax.ws.rs.core.MultivaluedHashMap;
     import javax.ws.rs.core.MultivaluedMap;
    @@ -65,12 +52,12 @@
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.cluster.coordination.http.replication.HttpReplicationClient;
     import org.apache.nifi.cluster.coordination.http.replication.PreparedRequest;
    -import org.apache.nifi.framework.security.util.SslContextFactory;
     import org.apache.nifi.remote.protocol.http.HttpHeaders;
    +import org.apache.nifi.security.util.OkHttpClientUtils;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.stream.io.GZIPOutputStream;
     import org.apache.nifi.util.FormatUtils;
     import org.apache.nifi.util.NiFiProperties;
    -import org.apache.nifi.util.Tuple;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     import org.springframework.util.StreamUtils;
    @@ -318,9 +305,9 @@ private boolean isUseGzip(final Map<String, String> headers) {
     
         private OkHttpClient createOkHttpClient(final NiFiProperties properties) {
             final String connectionTimeout = properties.getClusterNodeConnectionTimeout();
    -        final long connectionTimeoutMs = FormatUtils.getTimeDuration(connectionTimeout, TimeUnit.MILLISECONDS);
    +        final long connectionTimeoutMs = (long) FormatUtils.getPreciseTimeDuration(connectionTimeout, TimeUnit.MILLISECONDS);
             final String readTimeout = properties.getClusterNodeReadTimeout();
    -        final long readTimeoutMs = FormatUtils.getTimeDuration(readTimeout, TimeUnit.MILLISECONDS);
    +        final long readTimeoutMs = (long) FormatUtils.getPreciseTimeDuration(readTimeout, TimeUnit.MILLISECONDS);
     
             OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient().newBuilder();
             okHttpClientBuilder.connectTimeout(connectionTimeoutMs, TimeUnit.MILLISECONDS);
    @@ -329,42 +316,16 @@ private OkHttpClient createOkHttpClient(final NiFiProperties properties) {
             final int connectionPoolSize = properties.getClusterNodeMaxConcurrentRequests();
             okHttpClientBuilder.connectionPool(new ConnectionPool(connectionPoolSize, 5, TimeUnit.MINUTES));
     
    -        final Tuple<SSLSocketFactory, X509TrustManager> tuple = createSslSocketFactory(properties);
    -        if (tuple != null) {
    -            okHttpClientBuilder.sslSocketFactory(tuple.getKey(), tuple.getValue());
    -            tlsConfigured = true;
    +        // Apply the TLS configuration, if present
    +        try {
    +            TlsConfiguration tlsConfiguration = TlsConfiguration.fromNiFiProperties(properties);
    +            tlsConfigured = OkHttpClientUtils.applyTlsToOkHttpClientBuilder(tlsConfiguration, okHttpClientBuilder);
    +        } catch (Exception e) {
    +            // Legacy expectations around this client are that it does not throw an exception on invalid TLS configuration
    +            // TODO: The only current use of this class is ThreadPoolRequestReplicatorFactoryBean#getObject() which should be evaluated to see if that can change
    +            tlsConfigured = false;
             }
     
             return okHttpClientBuilder.build();
         }
    -
    -    private Tuple<SSLSocketFactory, X509TrustManager> createSslSocketFactory(final NiFiProperties properties) {
    -        final SSLContext sslContext = SslContextFactory.createSslContext(properties);
    -
    -        if (sslContext == null) {
    -            return null;
    -        }
    -
    -        try {
    -            Tuple<SSLContext, TrustManager[]> sslContextTuple = createTrustSslContextWithTrustManagers(
    -                    properties.getProperty(NiFiProperties.SECURITY_KEYSTORE),
    -                    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),
    -                    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());
    -            List<X509TrustManager> x509TrustManagers = Arrays.stream(sslContextTuple.getValue())
    -                    .filter(trustManager -> trustManager instanceof X509TrustManager)
    -                    .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
    -            return new Tuple<>(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
    -        } 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;
    -        }
    -    }
     }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/groovy/org/apache/nifi/cluster/coordination/http/replication/okhttp/OkHttpReplicationClientTest.groovy+5 5 modified
    @@ -212,7 +212,7 @@ class OkHttpReplicationClientTest extends GroovyTestCase {
         }
     
         @Test
    -    void testShouldFailIfKeyPasswordIsBlankAndKeystorePassword() {
    +    void testShouldFailIfKeyPasswordAndKeystorePasswordAreBlank() {
             // Arrange
             Map propsMap = [
                     (NiFiProperties.SECURITY_TRUSTSTORE)       : "./src/test/resources/conf/truststore.jks",
    @@ -242,13 +242,13 @@ class OkHttpReplicationClientTest extends GroovyTestCase {
                             (NiFiProperties.WEB_HTTPS_PORT): "51552",]
     
             Map tlsPropsMap = [
    -                (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.SECURITY_KEYSTORE_TYPE)    : "JKS",
    +                (NiFiProperties.SECURITY_TRUSTSTORE)       : "./src/test/resources/conf/truststore.jks",
    +                (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): "passwordpassword",
    +                (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : "JKS",
             ] + propsMap
     
     
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java+4 6 modified
    @@ -36,13 +36,11 @@
     import java.util.concurrent.CountDownLatch;
     import java.util.concurrent.TimeUnit;
     import java.util.concurrent.atomic.AtomicInteger;
    -
     import javax.ws.rs.HttpMethod;
     import javax.ws.rs.ProcessingException;
     import javax.ws.rs.core.MultivaluedHashMap;
     import javax.ws.rs.core.Response;
     import javax.ws.rs.core.Response.Status;
    -
     import org.apache.nifi.authorization.user.NiFiUser;
     import org.apache.nifi.authorization.user.NiFiUserDetails;
     import org.apache.nifi.authorization.user.NiFiUserUtils;
    @@ -232,7 +230,7 @@ public void testMultipleRequestWithTwoPhaseCommit() {
             when(coordinator.getConnectionStatus(Mockito.any(NodeIdentifier.class))).thenReturn(new NodeConnectionStatus(nodeId, NodeConnectionState.CONNECTED));
     
             final AtomicInteger requestCount = new AtomicInteger(0);
    -        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
    +        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
     
             final MockReplicationClient client = new MockReplicationClient();
             final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
    @@ -309,7 +307,7 @@ public void testMutableRequestRequiresAllNodesConnected() throws URISyntaxExcept
             nodeMap.put(NodeConnectionState.CONNECTING, otherState);
     
             when(coordinator.getConnectionStates()).thenReturn(nodeMap);
    -        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
    +        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
     
             final MockReplicationClient client = new MockReplicationClient();
             final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
    @@ -372,7 +370,7 @@ public void testOneNodeRejectsTwoPhaseCommit() {
     
             final ClusterCoordinator coordinator = createClusterCoordinator();
             final AtomicInteger requestCount = new AtomicInteger(0);
    -        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null, null);
    +        final NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
     
             final MockReplicationClient client = new MockReplicationClient();
             final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
    @@ -587,7 +585,7 @@ private void withReplicator(final WithReplicator function, final Status status,
     
         private void withReplicator(final WithReplicator function, final Status status, final long delayMillis, final RuntimeException failure, final String expectedRequestChain) {
             final ClusterCoordinator coordinator = createClusterCoordinator();
    -        final NiFiProperties nifiProps = NiFiProperties.createBasicNiFiProperties(null, null);
    +        final NiFiProperties nifiProps = NiFiProperties.createBasicNiFiProperties(null);
             final MockReplicationClient client = new MockReplicationClient();
             final RequestCompletionCallback requestCompletionCallback = (uri, method, responses) -> {
             };
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/resources/logback-test.xml+2 0 modified
    @@ -23,6 +23,8 @@
         
         <!-- valid logging levels: TRACE, DEBUG, INFO, WARN, ERROR -->
         <logger name="org.apache.nifi" level="INFO"/>
    +    <logger name="org.apache.nifi.security.util" level="DEBUG"/>
    +    <logger name="org.apache.nifi.cluster.coordination.http.replication.okhttp" level="DEBUG"/>
         <logger name="org.apache.nifi.engine.FlowEngine" level="OFF" />
         <logger name="org.apache.nifi.cluster.coordination.node" level="DEBUG" />
         <logger name="org.apache.curator.framework.recipes.leader.LeaderSelector" level="OFF" />
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml+0 4 modified
    @@ -113,10 +113,6 @@
                 <groupId>org.glassfish.jersey.core</groupId>
                 <artifactId>jersey-client</artifactId>
             </dependency>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-security</artifactId>
    -        </dependency>
             <dependency>
                 <groupId>org.springframework</groupId>
                 <artifactId>spring-core</artifactId>
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java+13 36 modified
    @@ -169,7 +169,6 @@
     import org.apache.nifi.events.EventReporter;
     import org.apache.nifi.flowfile.FlowFilePrioritizer;
     import org.apache.nifi.flowfile.attributes.CoreAttributes;
    -import org.apache.nifi.framework.security.util.SslContextFactory;
     import org.apache.nifi.groups.ProcessGroup;
     import org.apache.nifi.groups.RemoteProcessGroup;
     import org.apache.nifi.groups.StandardProcessGroup;
    @@ -208,6 +207,9 @@
     import org.apache.nifi.reporting.StandardEventAccess;
     import org.apache.nifi.reporting.UserAwareEventAccess;
     import org.apache.nifi.scheduling.SchedulingStrategy;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.services.FlowService;
     import org.apache.nifi.stream.io.LimitingInputStream;
     import org.apache.nifi.stream.io.StreamUtils;
    @@ -223,39 +225,6 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.management.NotificationEmitter;
    -import javax.net.ssl.SSLContext;
    -import java.io.ByteArrayInputStream;
    -import java.io.File;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.OutputStream;
    -import java.lang.management.GarbageCollectorMXBean;
    -import java.lang.management.ManagementFactory;
    -import java.net.InetSocketAddress;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.Date;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.UUID;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.ConcurrentMap;
    -import java.util.concurrent.ScheduledExecutorService;
    -import java.util.concurrent.ScheduledFuture;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicBoolean;
    -import java.util.concurrent.atomic.AtomicInteger;
    -import java.util.concurrent.atomic.AtomicReference;
    -import java.util.concurrent.locks.ReentrantReadWriteLock;
    -import java.util.stream.Collectors;
    -
    -import static java.util.Objects.requireNonNull;
    -
     public class FlowController implements ReportingTaskProvider, Authorizable, NodeTypeProvider {
     
         // default repository implementations
    @@ -490,14 +459,22 @@ private FlowController(
             this.nifiProperties = nifiProperties;
             this.heartbeatMonitor = heartbeatMonitor;
             this.leaderElectionManager = leaderElectionManager;
    -        this.sslContext = SslContextFactory.createSslContext(nifiProperties);
             this.extensionManager = extensionManager;
             this.clusterCoordinator = clusterCoordinator;
             this.authorizer = authorizer;
             this.auditService = auditService;
             this.configuredForClustering = configuredForClustering;
             this.flowRegistryClient = flowRegistryClient;
     
    +        try {
    +            // Form the container object from the properties
    +            TlsConfiguration tlsConfiguration = TlsConfiguration.fromNiFiProperties(nifiProperties);
    +            this.sslContext = SslContextFactory.createSslContext(tlsConfiguration);
    +        } catch (TlsException e) {
    +            LOG.error("Unable to start the flow controller because the TLS configuration was invalid: {}", e.getLocalizedMessage());
    +            throw new IllegalStateException("Flow controller TLS configuration is invalid", e);
    +        }
    +
             timerDrivenEngineRef = new AtomicReference<>(new FlowEngine(maxTimerDrivenThreads.get(), "Timer-Driven Process"));
             eventDrivenEngineRef = new AtomicReference<>(new FlowEngine(maxEventDrivenThreads.get(), "Event-Driven Process"));
     
    @@ -635,7 +612,7 @@ private FlowController(
                     zooKeeperStateServer = ZooKeeperStateServer.create(nifiProperties);
                     zooKeeperStateServer.start();
                 } catch (final IOException | ConfigException e) {
    -                throw new IllegalStateException("Unable to initailize Flow because NiFi was configured to start an Embedded Zookeeper server but failed to do so", e);
    +                throw new IllegalStateException("Unable to initialize Flow because NiFi was configured to start an Embedded Zookeeper server but failed to do so", e);
                 }
             } else {
                 zooKeeperStateServer = null;
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServer.java+70 15 modified
    @@ -17,13 +17,6 @@
     
     package org.apache.nifi.controller.queue.clustered.server;
     
    -import org.apache.nifi.events.EventReporter;
    -import org.apache.nifi.reporting.Severity;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.SSLServerSocket;
     import java.io.BufferedInputStream;
     import java.io.BufferedOutputStream;
     import java.io.IOException;
    @@ -38,6 +31,14 @@
     import java.util.Iterator;
     import java.util.List;
     import java.util.concurrent.atomic.AtomicLong;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.SSLPeerUnverifiedException;
    +import javax.net.ssl.SSLServerSocket;
    +import org.apache.nifi.events.EventReporter;
    +import org.apache.nifi.reporting.Severity;
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     public class ConnectionLoadBalanceServer {
         private static final Logger logger = LoggerFactory.getLogger(ConnectionLoadBalanceServer.class);
    @@ -113,24 +114,31 @@ private ServerSocket createServerSocket() throws IOException {
             if (sslContext == null) {
                 return new ServerSocket(port, 50, InetAddress.getByName(hostname));
             } else {
    -            final ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(port, 50, inetAddress);
    -            ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
    +            final SSLServerSocket serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(port, 50, inetAddress);
    +            serverSocket.setNeedClientAuth(true);
    +            // Enforce custom protocols on socket
    +            serverSocket.setEnabledProtocols(CertificateUtils.getCurrentSupportedTlsProtocolVersions());
                 return serverSocket;
             }
         }
     
    -
    -    private class CommunicateAction implements Runnable {
    +    // Use a static nested class and pass the ER in the constructor to avoid instantiation issues in tests
    +    protected static class CommunicateAction implements Runnable {
             private final LoadBalanceProtocol loadBalanceProtocol;
             private final Socket socket;
             private final InputStream in;
             private final OutputStream out;
    +        private final EventReporter eventReporter;
     
             private volatile boolean stopped = false;
     
    -        public CommunicateAction(final LoadBalanceProtocol loadBalanceProtocol, final Socket socket) throws IOException {
    +        private static int EXCEPTION_THRESHOLD_MILLIS = 10_000;
    +        private volatile long tlsErrorLastSeen = -1;
    +
    +        public CommunicateAction(final LoadBalanceProtocol loadBalanceProtocol, final Socket socket, final EventReporter eventReporter) throws IOException {
                 this.loadBalanceProtocol = loadBalanceProtocol;
                 this.socket = socket;
    +            this.eventReporter = eventReporter;
     
                 this.in = new BufferedInputStream(socket.getInputStream());
                 this.out = new BufferedOutputStream(socket.getOutputStream());
    @@ -164,12 +172,59 @@ public void run() {
                             }
                         }
     
    -                    logger.error("Failed to communicate with Peer {}", peerDescription, e);
    -                    eventReporter.reportEvent(Severity.ERROR, "Load Balanced Connection", "Failed to receive FlowFiles for Load Balancing due to " + e);
    +                    /* The exceptions can fill the log very quickly and make it difficult to use. SSLPeerUnverifiedExceptions
    +                    especially repeat and have a long stacktrace, and are not likely to be resolved instantaneously. Suppressing
    +                    them for a period of time is helpful */
    +                    if (CertificateUtils.isTlsError(e)) {
    +                        handleTlsError(peerDescription, e);
    +                    } else {
    +                        logger.error("Failed to communicate with Peer {}", peerDescription, e);
    +                        eventReporter.reportEvent(Severity.ERROR, "Load Balanced Connection", "Failed to receive FlowFiles for Load Balancing due to " + e);
    +                    }
                         return;
                     }
                 }
             }
    +
    +        /**
    +         * Determines how to record the TLS-related error
    +         * ({@link org.apache.nifi.security.util.TlsException}, {@link SSLPeerUnverifiedException},
    +         * {@link java.security.cert.CertificateException}, etc.) to the log, based on how recently it was last seen.
    +         *
    +         * @param peerDescription the peer's String representation for the log message
    +         * @param e               the exception
    +         * @return true if the error was printed at ERROR severity and reported to the event reporter
    +         */
    +        private boolean handleTlsError(String peerDescription, Throwable e) {
    +            final String populatedMessage = "Failed to communicate with Peer " + peerDescription + " due to " + e.getLocalizedMessage();
    +            // If the exception has been seen recently, log as debug
    +            if (tlsErrorRecentlySeen()) {
    +                logger.debug(populatedMessage);
    +                return false;
    +            } else {
    +                // If this is the first exception in X seconds, log as error
    +                logger.error(populatedMessage);
    +                logger.info("\tPrinted above error because it has been {} ms since the last printing", System.currentTimeMillis() - tlsErrorLastSeen);
    +                eventReporter.reportEvent(Severity.ERROR, "Load Balanced Connection", populatedMessage);
    +
    +                // Reset the timer
    +                tlsErrorLastSeen = System.currentTimeMillis();
    +                return true;
    +            }
    +        }
    +
    +
    +        /**
    +         * Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
    +         * {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
    +         * simply checks the last time the timestamp was updated.
    +         *
    +         * @return true if the time since the last similar exception occurred is below the threshold
    +         */
    +        private boolean tlsErrorRecentlySeen() {
    +            long now = System.currentTimeMillis();
    +            return now - tlsErrorLastSeen < EXCEPTION_THRESHOLD_MILLIS;
    +        }
         }
     
     
    @@ -204,7 +259,7 @@ public void run() {
     
                         socket.setSoTimeout(connectionTimeoutMillis);
     
    -                    final CommunicateAction communicateAction = new CommunicateAction(loadBalanceProtocol, socket);
    +                    final CommunicateAction communicateAction = new CommunicateAction(loadBalanceProtocol, socket, eventReporter);
                         final Thread commsThread = new Thread(communicateAction);
                         commsThread.setName("Load-Balance Server Thread-" + threadCounter.getAndIncrement());
                         commsThread.start();
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java+22 14 modified
    @@ -17,8 +17,16 @@
     
     package org.apache.nifi.controller.state.manager;
     
    +import java.io.File;
    +import java.io.IOException;
    +import java.util.Collection;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.ConcurrentMap;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.ArrayUtils;
    -import org.apache.nifi.parameter.ParameterLookup;
     import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.components.PropertyDescriptor;
    @@ -37,30 +45,23 @@
     import org.apache.nifi.controller.state.StandardStateProviderInitializationContext;
     import org.apache.nifi.controller.state.config.StateManagerConfiguration;
     import org.apache.nifi.controller.state.config.StateProviderConfiguration;
    -import org.apache.nifi.framework.security.util.SslContextFactory;
     import org.apache.nifi.logging.ComponentLog;
     import org.apache.nifi.nar.ExtensionManager;
     import org.apache.nifi.nar.NarCloseable;
    +import org.apache.nifi.parameter.ExpressionLanguageAwareParameterParser;
    +import org.apache.nifi.parameter.ParameterLookup;
     import org.apache.nifi.parameter.ParameterParser;
     import org.apache.nifi.parameter.ParameterTokenList;
    -import org.apache.nifi.parameter.ExpressionLanguageAwareParameterParser;
     import org.apache.nifi.processor.SimpleProcessLogger;
     import org.apache.nifi.processor.StandardValidationContext;
     import org.apache.nifi.registry.VariableRegistry;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.util.NiFiProperties;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.File;
    -import java.io.IOException;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.ConcurrentMap;
    -
     public class StandardStateManagerProvider implements StateManagerProvider{
         private static final Logger logger = LoggerFactory.getLogger(StandardStateManagerProvider.class);
     
    @@ -215,7 +216,14 @@ private static StateProvider createStateProvider(final File configFile, final Sc
                 propertyMap.put(descriptor, new StandardPropertyValue(entry.getValue(),null, parameterLookup, variableRegistry));
             }
     
    -        final SSLContext sslContext = SslContextFactory.createSslContext(properties);
    +        final SSLContext sslContext;
    +        try {
    +            sslContext = SslContextFactory.createSslContext(TlsConfiguration.fromNiFiProperties(properties));
    +        } catch (TlsException e) {
    +            logger.error("Encountered an error configuring TLS for state manager: ", e);
    +            throw new IllegalStateException("Error configuring TLS for state manager", e);
    +        }
    +
             final ComponentLog logger = new SimpleProcessLogger(providerId, provider);
             final StateProviderInitializationContext initContext = new StandardStateProviderInitializationContext(providerId, propertyMap, sslContext, logger);
     
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/registry/flow/StandardFlowRegistryClient.java+18 11 modified
    @@ -17,15 +17,16 @@
     
     package org.apache.nifi.registry.flow;
     
    -import org.apache.nifi.framework.security.util.SslContextFactory;
    -import org.apache.nifi.util.NiFiProperties;
    -
    -import javax.net.ssl.SSLContext;
     import java.net.URI;
     import java.net.URISyntaxException;
     import java.util.Set;
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.ConcurrentMap;
    +import javax.net.ssl.SSLContext;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
    +import org.apache.nifi.util.NiFiProperties;
     
     public class StandardFlowRegistryClient implements FlowRegistryClient {
         private NiFiProperties nifiProperties;
    @@ -75,15 +76,21 @@ public FlowRegistry addFlowRegistry(final String registryId, final String regist
     
             final FlowRegistry registry;
             if (uriScheme.equalsIgnoreCase("http") || uriScheme.equalsIgnoreCase("https")) {
    -            final SSLContext sslContext = SslContextFactory.createSslContext(nifiProperties);
    -            if (sslContext == null && uriScheme.equalsIgnoreCase("https")) {
    +            try {
    +                final SSLContext sslContext = SslContextFactory.createSslContext(TlsConfiguration.fromNiFiProperties(nifiProperties));
    +
    +                if (sslContext == null && uriScheme.equalsIgnoreCase("https")) {
    +                    throw new IllegalStateException("Failed to create Flow Registry for URI " + registryUrl
    +                            + " because this NiFi is not configured with a Keystore/Truststore, so it is not capable of communicating with a secure Registry. "
    +                            + "Please populate NiFi's Keystore/Truststore properties or connect to a NiFi Registry over http instead of https.");
    +                }
    +
    +                registry = new RestBasedFlowRegistry(this, registryId, registryBaseUrl, sslContext, registryName);
    +                registry.setDescription(description);
    +            } catch (TlsException e) {
                     throw new IllegalStateException("Failed to create Flow Registry for URI " + registryUrl
    -                    + " because this NiFi is not configured with a Keystore/Truststore, so it is not capable of communicating with a secure Registry. "
    -                    + "Please populate NiFi's Keystore/Truststore properties or connect to a NiFi Registry over http instead of https.");
    +                        + " because this NiFi instance has an invalid TLS configuration", e);
                 }
    -
    -            registry = new RestBasedFlowRegistry(this, registryId, registryBaseUrl, sslContext, registryName);
    -            registry.setDescription(description);
             } else {
                 throw new IllegalArgumentException("Cannot create Flow Registry with URI of " + registryUrl
                     + " because there are no known implementations of Flow Registries that can handle URIs of scheme " + uriScheme);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/groovy/org/apache/nifi/controller/queue/clustered/server/ConnectionLoadBalanceServerTest.groovy+197 0 added
    @@ -0,0 +1,197 @@
    +/*
    + * 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.controller.queue.clustered.server
    +
    +import org.apache.nifi.events.EventReporter
    +import org.apache.nifi.reporting.Severity
    +import org.apache.nifi.security.util.CertificateUtils
    +import org.apache.nifi.security.util.KeyStoreUtils
    +import org.apache.nifi.security.util.KeystoreType
    +import org.apache.nifi.security.util.SslContextFactory
    +import org.apache.nifi.security.util.TlsConfiguration
    +import org.bouncycastle.jce.provider.BouncyCastleProvider
    +import org.junit.After
    +import org.junit.Before
    +import org.junit.BeforeClass
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.JUnit4
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +import javax.net.ssl.SSLContext
    +import javax.net.ssl.SSLPeerUnverifiedException
    +import javax.net.ssl.SSLServerSocket
    +import java.security.Security
    +
    +@RunWith(JUnit4.class)
    +class ConnectionLoadBalanceServerTest extends GroovyTestCase {
    +    private static final Logger logger = LoggerFactory.getLogger(ConnectionLoadBalanceServerTest.class)
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/localhost-ks.jks"
    +    private static final String KEYSTORE_PASSWORD = "OI7kMpWzzVNVx/JGhTL/0uO4+PWpGJ46uZ/pfepbkwI"
    +    private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/localhost-ts.jks"
    +    private static final String TRUSTSTORE_PASSWORD = "wAOR0nQJ2EXvOP0JZ2EaqA/n7W69ILS4sWAHghmIWCc"
    +    private static final KeystoreType TRUSTSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String HOSTNAME = "localhost"
    +    private static final int PORT = 54321
    +    private static final int NUM_THREADS = 1
    +    private static final int TIMEOUT_MS = 1000
    +
    +    private static TlsConfiguration tlsConfiguration
    +    private static SSLContext sslContext
    +
    +    private ConnectionLoadBalanceServer lbServer
    +
    +    @BeforeClass
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
    +
    +        logger.metaClass.methodMissing = { String name, args ->
    +            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    +        }
    +
    +        tlsConfiguration = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +        sslContext = SslContextFactory.createSslContext(tlsConfiguration)
    +    }
    +
    +    @Before
    +    void setUp() {
    +    }
    +
    +    @After
    +    void tearDown() {
    +        if (lbServer) {
    +            lbServer.stop()
    +        }
    +    }
    +
    +    /**
    +     * Asserts that the protocol versions in the parameters object are correct. In recent versions of Java, this enforces order as well, but in older versions, it just enforces presence.
    +     *
    +     * @param enabledProtocols the actual protocols, either in {@code String[]} or {@code Collection<String>} form
    +     * @param expectedProtocols the specific protocol versions to be present (ordered as desired)
    +     */
    +    void assertProtocolVersions(def enabledProtocols, def expectedProtocols) {
    +        if (CertificateUtils.getJavaVersion() > 8) {
    +            assert enabledProtocols == expectedProtocols as String[]
    +        } else {
    +            assert enabledProtocols as Set == expectedProtocols as Set
    +        }
    +    }
    +
    +    @Test
    +    void testRequestPeerListShouldUseTLS() {
    +        // Arrange
    +        logger.info("Creating SSL Context from TLS Configuration: ${tlsConfiguration}")
    +        SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.NONE)
    +        logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +
    +        def mockLBP = [
    +                receiveFlowFiles: { Socket s, InputStream i, OutputStream o -> null }
    +        ] as LoadBalanceProtocol
    +        def mockER = [:] as EventReporter
    +
    +        lbServer = new ConnectionLoadBalanceServer(HOSTNAME, PORT, sslContext, NUM_THREADS, mockLBP, mockER, TIMEOUT_MS)
    +
    +        // Act
    +        lbServer.start()
    +
    +        // Assert
    +
    +        // Assert that the default parameters (which can't be modified) still have legacy protocols and no client auth
    +        def defaultSSLParameters = sslContext.defaultSSLParameters
    +        logger.info("Default SSL Parameters: ${KeyStoreUtils.sslParametersToString(defaultSSLParameters)}" as String)
    +        assertProtocolVersions(defaultSSLParameters.protocols, CertificateUtils.getCurrentSupportedTlsProtocolVersions() + ["TLSv1.1", "TLSv1"])
    +        assert !defaultSSLParameters.needClientAuth
    +
    +        // Assert that the actual socket is set correctly due to the override in the LB server
    +        SSLServerSocket socket = lbServer.serverSocket as SSLServerSocket
    +        logger.info("Created SSL server socket: ${KeyStoreUtils.sslServerSocketToString(socket)}" as String)
    +        assertProtocolVersions(socket.enabledProtocols, CertificateUtils.getCurrentSupportedTlsProtocolVersions())
    +        assert socket.needClientAuth
    +
    +        // Clean up
    +        lbServer.stop()
    +    }
    +
    +    @Test
    +    void testShouldHandleSSLPeerUnverifiedException() {
    +        // Arrange
    +        final long testStartMillis = System.currentTimeMillis()
    +        final int CONNECTION_ATTEMPTS = 100
    +        // If this test takes longer than 3 seconds, it's likely because of external delays, which would invalidate the assertions
    +        final long MAX_TEST_DURATION_MILLIS = 3000
    +        final String peerDescription = "Test peer"
    +        final SSLPeerUnverifiedException e = new SSLPeerUnverifiedException("Test exception")
    +
    +        InputStream socketInputStream = new ByteArrayInputStream("This is the socket input stream".bytes)
    +        OutputStream socketOutputStream = new ByteArrayOutputStream()
    +
    +        Socket mockSocket = [
    +                getInputStream : { -> socketInputStream },
    +                getOutputStream: { -> socketOutputStream },
    +        ] as Socket
    +        LoadBalanceProtocol mockLBProtocol = [
    +                receiveFlowFiles: { Socket s, InputStream i, OutputStream o -> null }
    +        ] as LoadBalanceProtocol
    +        EventReporter mockER = [
    +                reportEvent: { Severity s, String c, String m -> logger.mock("${s}: ${c} | ${m}") }
    +        ] as EventReporter
    +
    +        def output = [debug: 0, error: 0]
    +
    +        ConnectionLoadBalanceServer.CommunicateAction communicateAction = new ConnectionLoadBalanceServer.CommunicateAction(mockLBProtocol, mockSocket, mockER)
    +
    +        // Override the threshold to 100 ms
    +        communicateAction.EXCEPTION_THRESHOLD_MILLIS = 100
    +
    +        long listenerStart = System.currentTimeMillis()
    +
    +        // Act
    +        CONNECTION_ATTEMPTS.times { int i ->
    +            long now = System.currentTimeMillis()
    +            logger.debug("Attempting connection ${i + 1} at ${now} [${now - listenerStart}]")
    +            boolean printedError = communicateAction.handleTlsError(peerDescription, e)
    +            if (printedError) {
    +                output.error++
    +            } else {
    +                output.debug++
    +            }
    +            sleep(10)
    +        }
    +        logger.info("After ${CONNECTION_ATTEMPTS} attempts, debug: ${output.debug}, error: ${output.error}")
    +
    +        // Assert
    +        logger.info("output.debug (${output.debug}) > output.error (${output.error}): ${output.debug > output.error}")
    +
    +        // Only enforce if the test completed in a reasonable amount of time (i.e. external delays did not influence the timing)
    +        long testStopMillis = System.currentTimeMillis()
    +        long testDurationMillis = testStopMillis - testStartMillis
    +        if (testDurationMillis > MAX_TEST_DURATION_MILLIS) {
    +            logger.warn("The test took ${testDurationMillis} ms, which is longer than the max duration ${MAX_TEST_DURATION_MILLIS} ms, so the timing may be suspect and the assertion will not be enforced")
    +        } else {
    +            assert output.debug > output.error
    +        }
    +
    +        // Clean up
    +        communicateAction.stop()
    +    }
    +}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/queue/clustered/LoadBalancedQueueIT.java+50 46 modified
    @@ -17,6 +17,42 @@
     
     package org.apache.nifi.controller.queue.clustered;
     
    +import static org.junit.Assert.assertArrayEquals;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertTrue;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.anyCollection;
    +import static org.mockito.Mockito.doAnswer;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.security.KeyManagementException;
    +import java.security.KeyStoreException;
    +import java.security.NoSuchAlgorithmException;
    +import java.security.UnrecoverableKeyException;
    +import java.security.cert.CertificateException;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Objects;
    +import java.util.Set;
    +import java.util.UUID;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.ConcurrentMap;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.cluster.coordination.ClusterCoordinator;
     import org.apache.nifi.cluster.coordination.ClusterTopologyEventListener;
     import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
    @@ -56,50 +92,17 @@
     import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager;
     import org.apache.nifi.events.EventReporter;
     import org.apache.nifi.provenance.ProvenanceRepository;
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.apache.nifi.security.util.KeystoreType;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.junit.Before;
     import org.junit.Test;
     import org.mockito.Mockito;
     import org.mockito.invocation.InvocationOnMock;
     import org.mockito.stubbing.Answer;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.ByteArrayInputStream;
    -import java.io.ByteArrayOutputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.OutputStream;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Objects;
    -import java.util.Set;
    -import java.util.UUID;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.ConcurrentMap;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -
    -import static org.junit.Assert.assertArrayEquals;
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertTrue;
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.ArgumentMatchers.anyCollection;
    -import static org.mockito.Mockito.doAnswer;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     public class LoadBalancedQueueIT {
         private final LoadBalanceAuthorizer ALWAYS_AUTHORIZED = (sslSocket) -> sslSocket == null ? null : "authorized.mydomain.com";
         private final LoadBalanceAuthorizer NEVER_AUTHORIZED = (sslSocket) -> {
    @@ -137,7 +140,7 @@ public class LoadBalancedQueueIT {
         private final AtomicReference<LoadBalanceCompression> compressionReference = new AtomicReference<>();
     
         @Before
    -    public void setup() throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    +    public void setup() throws IOException, UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, TlsException {
             compressionReference.set(LoadBalanceCompression.DO_NOT_COMPRESS);
     
             nodeIdentifiers = new HashSet<>();
    @@ -189,9 +192,9 @@ public Object answer(final InvocationOnMock invocation) {
             final String keyPass = keystorePass;
             final String truststore = "src/test/resources/localhost-ts.jks";
             final String truststorePass = "wAOR0nQJ2EXvOP0JZ2EaqA/n7W69ILS4sWAHghmIWCc";
    -        sslContext = SslContextFactory.createSslContext(keystore, keystorePass.toCharArray(), keyPass.toCharArray(), "JKS",
    -                truststore, truststorePass.toCharArray(), "JKS",
    -                SslContextFactory.ClientAuth.REQUIRED, "TLS");
    +        TlsConfiguration tlsConfiguration = new TlsConfiguration(keystore, keystorePass, keyPass, KeystoreType.JKS,
    +                truststore, truststorePass, KeystoreType.JKS, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        sslContext = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.REQUIRED);
         }
     
     
    @@ -262,7 +265,7 @@ public void testNewNodeAdded() throws IOException, InterruptedException {
     
                     clusterEventListeners.forEach(listener -> listener.onNodeAdded(nodeId));
     
    -                for (int j=0; j < 2; j++) {
    +                for (int j = 0; j < 2; j++) {
                         final Map<String, String> attributes = new HashMap<>();
                         attributes.put("greeting", "hello");
     
    @@ -531,7 +534,7 @@ public void testContentNotFound() throws IOException, InterruptedException {
                 clientThread.start();
     
                 final SocketLoadBalancedFlowFileQueue flowFileQueue = new SocketLoadBalancedFlowFileQueue(queueId, new NopConnectionEventListener(), processScheduler, clientFlowFileRepo, clientProvRepo,
    -                clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
    +                    clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
                 flowFileQueue.setFlowFilePartitioner(new RoundRobinPartitioner());
     
                 try {
    @@ -602,7 +605,7 @@ public void testTransferToRemoteNodeAttributeCompression() throws IOException, I
                 clientThread.start();
     
                 final SocketLoadBalancedFlowFileQueue flowFileQueue = new SocketLoadBalancedFlowFileQueue(queueId, new NopConnectionEventListener(), processScheduler, clientFlowFileRepo, clientProvRepo,
    -                clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
    +                    clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
                 flowFileQueue.setFlowFilePartitioner(new RoundRobinPartitioner());
                 flowFileQueue.setLoadBalanceCompression(LoadBalanceCompression.COMPRESS_ATTRIBUTES_ONLY);
     
    @@ -692,7 +695,7 @@ public void testTransferToRemoteNodeContentCompression() throws IOException, Int
                 clientThread.start();
     
                 final SocketLoadBalancedFlowFileQueue flowFileQueue = new SocketLoadBalancedFlowFileQueue(queueId, new NopConnectionEventListener(), processScheduler, clientFlowFileRepo, clientProvRepo,
    -                clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
    +                    clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
                 flowFileQueue.setFlowFilePartitioner(new RoundRobinPartitioner());
                 flowFileQueue.setLoadBalanceCompression(LoadBalanceCompression.COMPRESS_ATTRIBUTES_AND_CONTENT);
     
    @@ -1087,6 +1090,7 @@ public QueuePartition getPartition(final FlowFileRecord flowFile, final QueuePar
                     public boolean isRebalanceOnClusterResize() {
                         return true;
                     }
    +
                     @Override
                     public boolean isRebalanceOnFailure() {
                         return true;
    @@ -1242,7 +1246,7 @@ public void testDestinationNodeQueueFull() throws IOException, InterruptedExcept
                 clientThread.start();
     
                 final SocketLoadBalancedFlowFileQueue flowFileQueue = new SocketLoadBalancedFlowFileQueue(queueId, new NopConnectionEventListener(), processScheduler, clientFlowFileRepo, clientProvRepo,
    -                clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
    +                    clientContentRepo, resourceClaimManager, clusterCoordinator, clientRegistry, flowFileSwapManager, swapThreshold, eventReporter);
                 flowFileQueue.setFlowFilePartitioner(new RoundRobinPartitioner());
     
                 try {
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/reporting/TestStandardReportingContext.java+8 10 modified
    @@ -16,6 +16,14 @@
      */
     package org.apache.nifi.controller.reporting;
     
    +import static org.junit.Assert.assertEquals;
    +
    +import java.io.File;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.LinkedHashSet;
    +import java.util.Map;
    +import java.util.Set;
     import org.apache.commons.io.FileUtils;
     import org.apache.nifi.admin.service.AuditService;
     import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
    @@ -47,15 +55,6 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import java.io.File;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.LinkedHashSet;
    -import java.util.Map;
    -import java.util.Set;
    -
    -import static org.junit.Assert.assertEquals;
    -
     public class TestStandardReportingContext {
     
         private static final String DEFAULT_SENSITIVE_PROPS_KEY = "nififtw!";
    @@ -74,7 +73,6 @@ public class TestStandardReportingContext {
     
         @Before
         public void setup() {
    -
             flowFileEventRepo = Mockito.mock(FlowFileEventRepository.class);
             auditService = Mockito.mock(AuditService.class);
             final Map<String, String> otherProps = new HashMap<>();
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/claim/TestContentClaimWriteCache.java+1 3 modified
    @@ -24,7 +24,6 @@
     import java.io.IOException;
     import java.io.InputStream;
     import java.io.OutputStream;
    -
     import org.apache.nifi.controller.repository.FileSystemRepository;
     import org.apache.nifi.controller.repository.TestFileSystemRepository;
     import org.apache.nifi.controller.repository.util.DiskUtils;
    @@ -44,8 +43,7 @@ public class TestContentClaimWriteCache {
     
         @Before
         public void setup() throws IOException {
    -        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile());
    -        nifiProperties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        nifiProperties = NiFiProperties.createBasicNiFiProperties(TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile());
             if (rootFile.exists()) {
                 DiskUtils.deleteRecursively(rootFile);
             }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestFileSystemRepository.java+27 29 modified
    @@ -16,29 +16,17 @@
      */
     package org.apache.nifi.controller.repository;
     
    +import static org.junit.Assert.assertArrayEquals;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertNotSame;
    +import static org.junit.Assert.assertTrue;
    +
     import ch.qos.logback.classic.Level;
     import ch.qos.logback.classic.Logger;
     import ch.qos.logback.classic.spi.ILoggingEvent;
     import ch.qos.logback.core.read.ListAppender;
    -import org.apache.commons.lang3.SystemUtils;
    -import org.apache.nifi.controller.repository.claim.ContentClaim;
    -import org.apache.nifi.controller.repository.claim.ResourceClaim;
    -import org.apache.nifi.controller.repository.claim.StandardContentClaim;
    -import org.apache.nifi.controller.repository.claim.StandardResourceClaim;
    -import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager;
    -import org.apache.nifi.controller.repository.util.DiskUtils;
    -import org.apache.nifi.processor.DataUnit;
    -import org.apache.nifi.stream.io.StreamUtils;
    -import org.apache.nifi.util.NiFiProperties;
    -import org.junit.After;
    -import org.junit.Assert;
    -import org.junit.Assume;
    -import org.junit.Before;
    -import org.junit.BeforeClass;
    -import org.junit.Ignore;
    -import org.junit.Test;
    -import org.slf4j.LoggerFactory;
    -
     import java.io.ByteArrayInputStream;
     import java.io.ByteArrayOutputStream;
     import java.io.File;
    @@ -62,13 +50,24 @@
     import java.util.Map;
     import java.util.Random;
     import java.util.concurrent.TimeUnit;
    -
    -import static org.junit.Assert.assertArrayEquals;
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertNotNull;
    -import static org.junit.Assert.assertNotSame;
    -import static org.junit.Assert.assertTrue;
    +import org.apache.commons.lang3.SystemUtils;
    +import org.apache.nifi.controller.repository.claim.ContentClaim;
    +import org.apache.nifi.controller.repository.claim.ResourceClaim;
    +import org.apache.nifi.controller.repository.claim.StandardContentClaim;
    +import org.apache.nifi.controller.repository.claim.StandardResourceClaim;
    +import org.apache.nifi.controller.repository.claim.StandardResourceClaimManager;
    +import org.apache.nifi.controller.repository.util.DiskUtils;
    +import org.apache.nifi.processor.DataUnit;
    +import org.apache.nifi.stream.io.StreamUtils;
    +import org.apache.nifi.util.NiFiProperties;
    +import org.junit.After;
    +import org.junit.Assert;
    +import org.junit.Assume;
    +import org.junit.Before;
    +import org.junit.BeforeClass;
    +import org.junit.Ignore;
    +import org.junit.Test;
    +import org.slf4j.LoggerFactory;
     
     public class TestFileSystemRepository {
     
    @@ -88,8 +87,7 @@ public static void setupClass() {
     
         @Before
         public void setup() throws IOException {
    -        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile());
    -        nifiProperties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        nifiProperties = NiFiProperties.createBasicNiFiProperties(TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile());
             if (rootFile.exists()) {
                 DiskUtils.deleteRecursively(rootFile);
             }
    @@ -149,7 +147,7 @@ public void testMinimalArchiveCleanupIntervalHonoredAndLogged() throws Exception
             root.addAppender(testAppender);
             final Map<String, String> addProps = new HashMap<>();
             addProps.put(NiFiProperties.CONTENT_ARCHIVE_CLEANUP_FREQUENCY, "1 millis");
    -        final NiFiProperties localProps = NiFiProperties.createBasicNiFiProperties(null, addProps);
    +        final NiFiProperties localProps = NiFiProperties.createBasicNiFiProperties(TestFileSystemRepository.class.getResource("/conf/nifi.properties").getFile(), addProps);
             repository = new FileSystemRepository(localProps);
             repository.initialize(new StandardResourceClaimManager());
             repository.purge();
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/repository/TestWriteAheadFlowFileRepository.java+39 36 modified
    @@ -16,6 +16,28 @@
      */
     package org.apache.nifi.controller.repository;
     
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertTrue;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.Mockito.doAnswer;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.File;
    +import java.io.IOException;
    +import java.nio.file.Files;
    +import java.nio.file.Path;
    +import java.nio.file.Paths;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.UUID;
    +import java.util.concurrent.TimeUnit;
     import org.apache.nifi.connectable.Connectable;
     import org.apache.nifi.connectable.Connection;
     import org.apache.nifi.controller.queue.DropFlowFileStatus;
    @@ -45,7 +67,6 @@
     import org.junit.After;
     import org.junit.Assert;
     import org.junit.Before;
    -import org.junit.BeforeClass;
     import org.junit.Ignore;
     import org.junit.Test;
     import org.mockito.Mockito;
    @@ -54,39 +75,22 @@
     import org.wali.MinimalLockingWriteAheadLog;
     import org.wali.WriteAheadRepository;
     
    -import java.io.File;
    -import java.io.IOException;
    -import java.nio.file.Files;
    -import java.nio.file.Path;
    -import java.nio.file.Paths;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.UUID;
    -import java.util.concurrent.TimeUnit;
    -
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertNotNull;
    -import static org.junit.Assert.assertTrue;
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.Mockito.doAnswer;
    -import static org.mockito.Mockito.when;
    -
     @SuppressWarnings("deprecation")
     public class TestWriteAheadFlowFileRepository {
     
    -    @BeforeClass
    -    public static void setupProperties() {
    -        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestWriteAheadFlowFileRepository.class.getResource("/conf/nifi.properties").getFile());
    -    }
    +    private static NiFiProperties niFiProperties;
     
         @Before
    +    public void setUp() throws Exception {
    +        niFiProperties = NiFiProperties.createBasicNiFiProperties(TestWriteAheadFlowFileRepository.class.getResource("/conf/nifi.properties").getFile());
    +        clearRepo();
    +    }
    +
         @After
    +    public void tearDown() throws Exception {
    +        clearRepo();
    +    }
    +
         public void clearRepo() throws IOException {
             final File target = new File("target");
             final File testRepo = new File(target, "test-repo");
    @@ -95,7 +99,6 @@ public void clearRepo() throws IOException {
             }
         }
     
    -
         @Test
         @Ignore("Intended only for local performance testing before/after making changes")
         public void testUpdatePerformance() throws IOException, InterruptedException {
    @@ -418,7 +421,7 @@ public void testSwapLocationsRestored() throws IOException {
                 FileUtils.deleteFile(path.toFile(), true);
             }
     
    -        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(niFiProperties);
             repo.initialize(new StandardResourceClaimManager());
     
             final TestQueueProvider queueProvider = new TestQueueProvider();
    @@ -447,7 +450,7 @@ public void testSwapLocationsRestored() throws IOException {
             repo.close();
     
             // restore
    -        final WriteAheadFlowFileRepository repo2 = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final WriteAheadFlowFileRepository repo2 = new WriteAheadFlowFileRepository(niFiProperties);
             repo2.initialize(new StandardResourceClaimManager());
             repo2.loadFlowFiles(queueProvider);
             assertTrue(repo2.isValidSwapLocationSuffix("swap123"));
    @@ -462,7 +465,7 @@ public void testSwapLocationsUpdatedOnRepoUpdate() throws IOException {
                 FileUtils.deleteFile(path.toFile(), true);
             }
     
    -        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(niFiProperties);
             repo.initialize(new StandardResourceClaimManager());
     
             final TestQueueProvider queueProvider = new TestQueueProvider();
    @@ -521,7 +524,7 @@ public void testResourceClaimsIncremented() throws IOException {
             // Create a flowfile repo, update it once with a FlowFile that points to one resource claim. Then,
             // indicate that a FlowFile was swapped out. We should then be able to recover these FlowFiles and the
             // resource claims' counts should be updated for both the swapped out FlowFile and the non-swapped out FlowFile
    -        try (final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null))) {
    +        try (final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(niFiProperties)) {
                 repo.initialize(claimManager);
                 repo.loadFlowFiles(queueProvider);
     
    @@ -556,7 +559,7 @@ public void testResourceClaimsIncremented() throws IOException {
             }
     
             final ResourceClaimManager recoveryClaimManager = new StandardResourceClaimManager();
    -        try (final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null))) {
    +        try (final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(niFiProperties)) {
                 repo.initialize(recoveryClaimManager);
                 final long largestId = repo.loadFlowFiles(queueProvider);
     
    @@ -587,7 +590,7 @@ public void testRestartWithOneRecord() throws IOException {
                 FileUtils.deleteFile(path.toFile(), true);
             }
     
    -        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final WriteAheadFlowFileRepository repo = new WriteAheadFlowFileRepository(niFiProperties);
             repo.initialize(new StandardResourceClaimManager());
     
             final TestQueueProvider queueProvider = new TestQueueProvider();
    @@ -641,7 +644,7 @@ public Object answer(final InvocationOnMock invocation) throws Throwable {
             repo.close();
     
             // restore
    -        final WriteAheadFlowFileRepository repo2 = new WriteAheadFlowFileRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final WriteAheadFlowFileRepository repo2 = new WriteAheadFlowFileRepository(niFiProperties);
             repo2.initialize(new StandardResourceClaimManager());
             repo2.loadFlowFiles(queueProvider);
     
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderIT.java+10 11 modified
    @@ -17,6 +17,15 @@
     
     package org.apache.nifi.controller.service;
     
    +import static org.junit.Assert.assertTrue;
    +
    +import java.beans.PropertyDescriptor;
    +import java.util.Collections;
    +import java.util.HashSet;
    +import java.util.LinkedHashMap;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.ExecutionException;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.bundle.BundleCoordinate;
     import org.apache.nifi.components.state.StateManager;
    @@ -43,16 +52,6 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import java.beans.PropertyDescriptor;
    -import java.util.Collections;
    -import java.util.HashSet;
    -import java.util.LinkedHashMap;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.ExecutionException;
    -
    -import static org.junit.Assert.assertTrue;
    -
     public class StandardControllerServiceProviderIT {
         private static Bundle systemBundle;
         private static NiFiProperties niFiProperties;
    @@ -85,7 +84,7 @@ public void onComponentRemoved(final String componentId) {
         @BeforeClass
         public static void setNiFiProps() {
             System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestStandardControllerServiceProvider.class.getResource("/conf/nifi.properties").getFile());
    -        niFiProperties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        niFiProperties = NiFiProperties.createBasicNiFiProperties(null);
     
             // load the system bundle
             systemBundle = SystemBundle.create(niFiProperties);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java+2 5 modified
    @@ -16,6 +16,7 @@
      */
     package org.apache.nifi.controller.service;
     
    +import java.util.Collections;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.bundle.BundleCoordinate;
     import org.apache.nifi.components.state.StateManagerProvider;
    @@ -38,8 +39,6 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import java.util.Collections;
    -
     
     
     public class StandardControllerServiceProviderTest {
    @@ -54,8 +53,7 @@ public class StandardControllerServiceProviderTest {
     
         @BeforeClass
         public static void setupSuite() {
    -        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, StandardControllerServiceProviderTest.class.getResource("/conf/nifi.properties").getFile());
    -        nifiProperties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        nifiProperties = NiFiProperties.createBasicNiFiProperties(StandardControllerServiceProviderTest.class.getResource("/conf/nifi.properties").getFile());
     
             // load the system bundle
             systemBundle = SystemBundle.create(nifiProperties);
    @@ -68,7 +66,6 @@ public static void setupSuite() {
             Mockito.when(flowController.getExtensionManager()).thenReturn(extensionManager);
         }
     
    -
         @Before
         public void setup() throws Exception {
             String id = "id";
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java+15 17 modified
    @@ -17,6 +17,20 @@
      */
     package org.apache.nifi.controller.service;
     
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertSame;
    +import static org.junit.Assert.assertTrue;
    +
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.LinkedHashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.UUID;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.ConcurrentMap;
    +import java.util.concurrent.TimeUnit;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.bundle.BundleCoordinate;
     import org.apache.nifi.components.PropertyDescriptor;
    @@ -63,21 +77,6 @@
     import org.mockito.invocation.InvocationOnMock;
     import org.mockito.stubbing.Answer;
     
    -import java.util.Arrays;
    -import java.util.Collections;
    -import java.util.LinkedHashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.UUID;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.ConcurrentMap;
    -import java.util.concurrent.TimeUnit;
    -
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertSame;
    -import static org.junit.Assert.assertTrue;
    -
     public class TestStandardControllerServiceProvider {
     
         private static StateManagerProvider stateManagerProvider = new StateManagerProvider() {
    @@ -111,8 +110,7 @@ public void onComponentRemoved(final String componentId) {
     
         @BeforeClass
         public static void setNiFiProps() {
    -        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, TestStandardControllerServiceProvider.class.getResource("/conf/nifi.properties").getFile());
    -        niFiProperties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        niFiProperties = NiFiProperties.createBasicNiFiProperties(TestStandardControllerServiceProvider.class.getResource("/conf/nifi.properties").getFile());
     
             // load the system bundle
             systemBundle = SystemBundle.create(niFiProperties);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardFlowServiceTest.java+9 10 modified
    @@ -16,6 +16,14 @@
      */
     package org.apache.nifi.controller;
     
    +import static org.junit.Assert.fail;
    +import static org.mockito.Mockito.mock;
    +
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.HashSet;
    +import java.util.List;
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.admin.service.AuditService;
     import org.apache.nifi.authorization.Authorizer;
    @@ -48,15 +56,6 @@
     import org.junit.Test;
     import org.w3c.dom.Document;
     
    -import java.io.ByteArrayOutputStream;
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.HashSet;
    -import java.util.List;
    -
    -import static org.junit.Assert.fail;
    -import static org.mockito.Mockito.mock;
    -
     /**
      */
     @Ignore
    @@ -80,7 +79,7 @@ public static void setupSuite() {
     
         @Before
         public void setup() throws Exception {
    -        properties = NiFiProperties.createBasicNiFiProperties(null, null);
    +        properties = NiFiProperties.createBasicNiFiProperties(null);
     
     
     
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/TestStandardProcessorNode.java+27 28 modified
    @@ -17,6 +17,32 @@
     
     package org.apache.nifi.controller;
     
    +import static org.hamcrest.CoreMatchers.containsString;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertThat;
    +import static org.junit.Assert.assertTrue;
    +import static org.junit.Assert.fail;
    +import static org.junit.Assume.assumeTrue;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.File;
    +import java.net.MalformedURLException;
    +import java.net.URL;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.LinkedHashMap;
    +import java.util.LinkedHashSet;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.UUID;
    +import java.util.concurrent.Callable;
    +import java.util.concurrent.Future;
    +import java.util.concurrent.ScheduledExecutorService;
    +import java.util.concurrent.atomic.AtomicReference;
     import org.apache.nifi.admin.service.AuditService;
     import org.apache.nifi.annotation.lifecycle.OnScheduled;
     import org.apache.nifi.annotation.lifecycle.OnStopped;
    @@ -69,33 +95,6 @@
     import org.junit.Before;
     import org.junit.Test;
     
    -import java.io.File;
    -import java.net.MalformedURLException;
    -import java.net.URL;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.LinkedHashMap;
    -import java.util.LinkedHashSet;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.UUID;
    -import java.util.concurrent.Callable;
    -import java.util.concurrent.Future;
    -import java.util.concurrent.ScheduledExecutorService;
    -import java.util.concurrent.atomic.AtomicReference;
    -
    -import static org.hamcrest.CoreMatchers.containsString;
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertThat;
    -import static org.junit.Assert.assertTrue;
    -import static org.junit.Assert.fail;
    -import static org.junit.Assume.assumeTrue;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     public class TestStandardProcessorNode {
     
         private MockVariableRegistry variableRegistry;
    @@ -108,7 +107,7 @@ public class TestStandardProcessorNode {
         @Before
         public void setup() {
             variableRegistry = new MockVariableRegistry();
    -        niFiProperties = NiFiProperties.createBasicNiFiProperties("src/test/resources/conf/nifi.properties", null);
    +        niFiProperties = NiFiProperties.createBasicNiFiProperties("src/test/resources/conf/nifi.properties");
     
             systemBundle = SystemBundle.create(niFiProperties);
             extensionManager = new StandardExtensionDiscoveringManager() {
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/test/java/org/apache/nifi/nar/NarThreadContextClassLoaderTest.java+8 9 modified
    @@ -16,6 +16,12 @@
      */
     package org.apache.nifi.nar;
     
    +import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertTrue;
    +
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.Map;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.processor.AbstractProcessor;
     import org.apache.nifi.processor.ProcessContext;
    @@ -24,18 +30,11 @@
     import org.apache.nifi.util.NiFiProperties;
     import org.junit.Test;
     
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.Map;
    -
    -import static org.junit.Assert.assertNotNull;
    -import static org.junit.Assert.assertTrue;
    -
     public class NarThreadContextClassLoaderTest {
     
         @Test
         public void validateWithPropertiesConstructor() throws Exception {
    -        NiFiProperties properties = NiFiProperties.createBasicNiFiProperties("src/test/resources/nifi.properties", null);
    +        NiFiProperties properties = NiFiProperties.createBasicNiFiProperties("src/test/resources/nifi.properties");
             Bundle systemBundle = SystemBundle.create(properties);
             ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
             extensionManager.discoverExtensions(systemBundle, Collections.emptySet());
    @@ -60,7 +59,7 @@ public void validateWithPropertiesConstructorInstantiationFailure() throws Excep
     
         @Test
         public void validateWithDefaultConstructor() throws Exception {
    -        NiFiProperties properties = NiFiProperties.createBasicNiFiProperties("src/test/resources/nifi.properties", null);
    +        NiFiProperties properties = NiFiProperties.createBasicNiFiProperties("src/test/resources/nifi.properties");
             Bundle systemBundle = SystemBundle.create(properties);
             ExtensionDiscoveringManager extensionManager = new StandardExtensionDiscoveringManager();
             extensionManager.discoverExtensions(systemBundle, Collections.emptySet());
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-properties-loader/src/test/groovy/org/apache/nifi/properties/StandardNiFiPropertiesGroovyTest.groovy+33 0 modified
    @@ -958,4 +958,37 @@ class StandardNiFiPropertiesGroovyTest extends GroovyTestCase {
             // Assert
             assert hosts.size() == 0
         }
    +
    +    @Test
    +    void testStaticFactoryMethodShouldAcceptRawProperties() throws Exception {
    +        // Arrange
    +        Properties rawProperties = new Properties()
    +        rawProperties.setProperty("key", "value")
    +        logger.info("rawProperties has ${rawProperties.size()} properties: ${rawProperties.stringPropertyNames()}")
    +        assert rawProperties.size() == 1
    +
    +        // Act
    +        NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties("", rawProperties)
    +        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
    +
    +        // Assert
    +        assert niFiProperties.size() == 1
    +        assert niFiProperties.getPropertyKeys() == ["key"] as Set
    +    }
    +
    +    @Test
    +    void testStaticFactoryMethodShouldAcceptMap() throws Exception {
    +        // Arrange
    +        def mapProperties = ["key": "value"]
    +        logger.info("rawProperties has ${mapProperties.size()} properties: ${mapProperties.keySet()}")
    +        assert mapProperties.size() == 1
    +
    +        // Act
    +        NiFiProperties niFiProperties = NiFiProperties.createBasicNiFiProperties("", mapProperties)
    +        logger.info("niFiProperties has ${niFiProperties.size()} properties: ${niFiProperties.getPropertyKeys()}")
    +
    +        // Assert
    +        assert niFiProperties.size() == 1
    +        assert niFiProperties.getPropertyKeys() == ["key"] as Set
    +    }
     }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/pom.xml+0 40 removed
    @@ -1,40 +0,0 @@
    -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    -    <!--
    -      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.
    -    -->
    -    <modelVersion>4.0.0</modelVersion>
    -    <parent>
    -        <groupId>org.apache.nifi</groupId>
    -        <artifactId>nifi-framework</artifactId>
    -        <version>1.12.0-SNAPSHOT</version>
    -    </parent>
    -    <artifactId>nifi-security</artifactId>
    -    <description>Contains security functionality common to NiFi.</description>
    -    <dependencies>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-properties</artifactId>
    -        </dependency>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-security-utils</artifactId>
    -            <version>1.12.0-SNAPSHOT</version>
    -        </dependency>
    -        <dependency>
    -            <groupId>commons-io</groupId>
    -            <artifactId>commons-io</artifactId>
    -        </dependency>
    -    </dependencies>
    -</project>
    -
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextCreationException.java+0 40 removed
    @@ -1,40 +0,0 @@
    -/*
    - * 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.framework.security.util;
    -
    -/**
    - * Represents the exceptional case when a SSL context failed creation.
    - *
    - */
    -public class SslContextCreationException extends SslException {
    -
    -    public SslContextCreationException(Throwable cause) {
    -        super(cause);
    -    }
    -
    -    public SslContextCreationException(String message, Throwable cause) {
    -        super(message, cause);
    -    }
    -
    -    public SslContextCreationException(String message) {
    -        super(message);
    -    }
    -
    -    public SslContextCreationException() {
    -    }
    -
    -}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslContextFactory.java+0 104 removed
    @@ -1,104 +0,0 @@
    -/*
    - * 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.framework.security.util;
    -
    -import org.apache.commons.lang3.StringUtils;
    -import org.apache.nifi.security.util.KeyStoreUtils;
    -import org.apache.nifi.util.NiFiProperties;
    -
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManagerFactory;
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.security.KeyManagementException;
    -import java.security.KeyStore;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -
    -/**
    - * A factory for creating SSL contexts using the application's security
    - * properties.
    - *
    - */
    -public final class SslContextFactory {
    -
    -    public static SSLContext createSslContext(final NiFiProperties props)
    -            throws SslContextCreationException {
    -
    -        if (hasKeystoreProperties(props) == false) {
    -            return null;
    -        } else if (hasTruststoreProperties(props) == false) {
    -            throw new SslContextCreationException("SSL context cannot be created because truststore properties have not been configured.");
    -        }
    -
    -        try {
    -            // prepare the trust store
    -            final KeyStore trustStore;
    -            if (hasTruststoreProperties(props)) {
    -                trustStore = KeyStoreUtils.getTrustStore(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE));
    -                try (final InputStream trustStoreStream = new FileInputStream(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))) {
    -                    trustStore.load(trustStoreStream, props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray());
    -                }
    -            } else {
    -                trustStore = null;
    -            }
    -            final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
    -            trustManagerFactory.init(trustStore);
    -
    -            // prepare the key store
    -            final KeyStore keyStore = KeyStoreUtils.getKeyStore(props.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE));
    -            try (final InputStream keyStoreStream = new FileInputStream(props.getProperty(NiFiProperties.SECURITY_KEYSTORE))) {
    -                keyStore.load(keyStoreStream, props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray());
    -            }
    -            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    -
    -            // if the key password is provided, try to use that - otherwise default to the keystore password
    -            if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEY_PASSWD))) {
    -                keyManagerFactory.init(keyStore, props.getProperty(NiFiProperties.SECURITY_KEY_PASSWD).toCharArray());
    -            } else {
    -                keyManagerFactory.init(keyStore, props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD).toCharArray());
    -            }
    -
    -            // initialize the ssl context
    -            final SSLContext sslContext = SSLContext.getInstance("TLS");
    -            sslContext.init(keyManagerFactory.getKeyManagers(),
    -                    trustManagerFactory.getTrustManagers(), null);
    -            sslContext.getDefaultSSLParameters().setNeedClientAuth(true);
    -
    -            return sslContext;
    -
    -        } catch (final KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException e) {
    -            throw new SslContextCreationException(e);
    -        }
    -    }
    -
    -    private static boolean hasKeystoreProperties(final NiFiProperties props) {
    -        return (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE))
    -                && StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD))
    -                && StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE)));
    -    }
    -
    -    private static boolean hasTruststoreProperties(final NiFiProperties props) {
    -        return (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))
    -                && StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)));
    -    }
    -
    -}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslException.java+0 40 removed
    @@ -1,40 +0,0 @@
    -/*
    - * 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.framework.security.util;
    -
    -/**
    - * Base class for SSL related exceptions.
    - *
    - */
    -public class SslException extends RuntimeException {
    -
    -    public SslException(Throwable cause) {
    -        super(cause);
    -    }
    -
    -    public SslException(String message, Throwable cause) {
    -        super(message, cause);
    -    }
    -
    -    public SslException(String message) {
    -        super(message);
    -    }
    -
    -    public SslException() {
    -    }
    -
    -}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/main/java/org/apache/nifi/framework/security/util/SslServerSocketFactoryCreationException.java+0 41 removed
    @@ -1,41 +0,0 @@
    -/*
    - * 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.framework.security.util;
    -
    -/**
    - * Represents the exceptional case when a SslServerSocketFactory failed
    - * creation.
    - *
    - */
    -public class SslServerSocketFactoryCreationException extends SslException {
    -
    -    public SslServerSocketFactoryCreationException(Throwable cause) {
    -        super(cause);
    -    }
    -
    -    public SslServerSocketFactoryCreationException(String message, Throwable cause) {
    -        super(message, cause);
    -    }
    -
    -    public SslServerSocketFactoryCreationException(String message) {
    -        super(message);
    -    }
    -
    -    public SslServerSocketFactoryCreationException() {
    -    }
    -
    -}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/java/org/apache/nifi/framework/security/util/SslContextFactoryTest.java+0 80 removed
    @@ -1,80 +0,0 @@
    -/*
    - * 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.framework.security.util;
    -
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
    -import java.io.File;
    -import org.apache.nifi.security.util.KeystoreType;
    -import org.apache.nifi.util.NiFiProperties;
    -import org.junit.Assert;
    -import org.junit.Before;
    -import org.junit.Test;
    -
    -/**
    - */
    -public class SslContextFactoryTest {
    -
    -    private NiFiProperties mutualAuthProps;
    -    private NiFiProperties authProps;
    -    private NiFiProperties noPasswordTruststore;
    -
    -    @Before
    -    public void setUp() throws Exception {
    -
    -        final File ksFile = new File(SslContextFactoryTest.class.getResource("/keystore.jks").toURI());
    -        final File trustFile = new File(SslContextFactoryTest.class.getResource("/truststore.jks").toURI());
    -        final File noPasswordTrustFile = new File(SslContextFactoryTest.class.getResource("/no-password-truststore.jks").toURI());
    -
    -        authProps = mock(NiFiProperties.class);
    -        when(authProps.getProperty(NiFiProperties.SECURITY_KEYSTORE)).thenReturn(ksFile.getAbsolutePath());
    -        when(authProps.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
    -        when(authProps.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)).thenReturn("passwordpassword");
    -
    -        mutualAuthProps = mock(NiFiProperties.class);
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_KEYSTORE)).thenReturn(ksFile.getAbsolutePath());
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)).thenReturn("passwordpassword");
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_TRUSTSTORE)).thenReturn(trustFile.getAbsolutePath());
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
    -        when(mutualAuthProps.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)).thenReturn("passwordpassword");
    -
    -        noPasswordTruststore = mock(NiFiProperties.class);
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE)).thenReturn(ksFile.getAbsolutePath());
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD)).thenReturn("passwordpassword");
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE)).thenReturn(noPasswordTrustFile.getAbsolutePath());
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE)).thenReturn(KeystoreType.JKS.toString());
    -        when(noPasswordTruststore.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD)).thenReturn("");
    -    }
    -
    -    @Test
    -    public void testCreateSslContextWithMutualAuth() {
    -        Assert.assertNotNull(SslContextFactory.createSslContext(mutualAuthProps));
    -    }
    -
    -    @Test(expected = SslContextCreationException.class)
    -    public void testCreateSslContextWithNoMutualAuth() {
    -        SslContextFactory.createSslContext(authProps);
    -    }
    -
    -    @Test
    -    public void testCreateSslContextWithNoPasswordTruststore() {
    -        Assert.assertNotNull(SslContextFactory.createSslContext(noPasswordTruststore));
    -    }
    -}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/keystore.jks+0 0 removed
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/log4j.properties+0 21 removed
    @@ -1,21 +0,0 @@
    -# 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.
    -
    -log4j.rootLogger=DEBUG,console
    -
    -log4j.appender.console=org.apache.log4j.ConsoleAppender
    -log4j.appender.console.target=System.err
    -log4j.appender.console.layout=org.apache.log4j.PatternLayout
    -log4j.appender.console.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n
    \ No newline at end of file
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-security/src/test/resources/truststore.jks+0 0 removed
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/pom.xml+0 4 modified
    @@ -22,10 +22,6 @@
         </parent>
         <artifactId>nifi-site-to-site</artifactId>
         <dependencies>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-security</artifactId>
    -        </dependency>
             <dependency>
                 <groupId>org.apache.nifi</groupId>
                 <artifactId>nifi-security-utils</artifactId>
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/main/java/org/apache/nifi/remote/SocketRemoteSiteListener.java+63 24 modified
    @@ -16,25 +16,6 @@
      */
     package org.apache.nifi.remote;
     
    -import org.apache.nifi.groups.ProcessGroup;
    -import org.apache.nifi.remote.cluster.ClusterNodeInformation;
    -import org.apache.nifi.remote.cluster.NodeInformant;
    -import org.apache.nifi.remote.cluster.NodeInformation;
    -import org.apache.nifi.remote.exception.BadRequestException;
    -import org.apache.nifi.remote.exception.HandshakeException;
    -import org.apache.nifi.remote.exception.NotAuthorizedException;
    -import org.apache.nifi.remote.exception.RequestExpiredException;
    -import org.apache.nifi.remote.io.socket.SocketCommunicationsSession;
    -import org.apache.nifi.remote.protocol.CommunicationsSession;
    -import org.apache.nifi.remote.protocol.RequestType;
    -import org.apache.nifi.remote.protocol.ServerProtocol;
    -import org.apache.nifi.security.util.CertificateUtils;
    -import org.apache.nifi.util.NiFiProperties;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.SSLServerSocket;
     import java.io.DataInputStream;
     import java.io.DataOutputStream;
     import java.io.EOFException;
    @@ -51,6 +32,24 @@
     import java.util.Optional;
     import java.util.concurrent.atomic.AtomicBoolean;
     import java.util.concurrent.atomic.AtomicReference;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.SSLServerSocket;
    +import org.apache.nifi.groups.ProcessGroup;
    +import org.apache.nifi.remote.cluster.ClusterNodeInformation;
    +import org.apache.nifi.remote.cluster.NodeInformant;
    +import org.apache.nifi.remote.cluster.NodeInformation;
    +import org.apache.nifi.remote.exception.BadRequestException;
    +import org.apache.nifi.remote.exception.HandshakeException;
    +import org.apache.nifi.remote.exception.NotAuthorizedException;
    +import org.apache.nifi.remote.exception.RequestExpiredException;
    +import org.apache.nifi.remote.io.socket.SocketCommunicationsSession;
    +import org.apache.nifi.remote.protocol.CommunicationsSession;
    +import org.apache.nifi.remote.protocol.RequestType;
    +import org.apache.nifi.remote.protocol.ServerProtocol;
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.apache.nifi.util.NiFiProperties;
    +import org.slf4j.Logger;
    +import org.slf4j.LoggerFactory;
     
     public class SocketRemoteSiteListener implements RemoteSiteListener {
     
    @@ -61,6 +60,9 @@ public class SocketRemoteSiteListener implements RemoteSiteListener {
         private final NiFiProperties nifiProperties;
         private final PeerDescriptionModifier peerDescriptionModifier;
     
    +    private static int EXCEPTION_THRESHOLD_MILLIS = 10_000;
    +    private volatile long tlsErrorLastSeen = -1;
    +
         private final AtomicBoolean stopped = new AtomicBoolean(false);
     
         private static final Logger LOG = LoggerFactory.getLogger(SocketRemoteSiteListener.class);
    @@ -168,9 +170,22 @@ public void run() {
                                     dn = null;
                                 }
                             } catch (final Exception e) {
    -                            LOG.error("RemoteSiteListener Unable to accept connection from {} due to {}", socket, e.toString());
    -                            if (LOG.isDebugEnabled()) {
    -                                LOG.error("", e);
    +                            // TODO: Add SocketProtocolListener#handleTlsError logic here
    +                            String msg = String.format("RemoteSiteListener Unable to accept connection from {} due to {}", socket, e.getLocalizedMessage());
    +                            // Suppress repeated TLS errors
    +                            if (CertificateUtils.isTlsError(e)) {
    +                                boolean printedAsWarning = handleTlsError(msg);
    +
    +                                // TODO: Move into handleTlsError and refactor shared behavior
    +                                // If the error was printed as a warning, reset the last seen timer
    +                                if (printedAsWarning) {
    +                                    tlsErrorLastSeen = System.currentTimeMillis();
    +                                }
    +                            } else {
    +                                LOG.error(msg);
    +                                if (LOG.isDebugEnabled()) {
    +                                    LOG.error("", e);
    +                                }
                                 }
                                 return;
                             }
    @@ -304,10 +319,34 @@ public void run() {
             listenerThread.start();
         }
     
    +    private boolean handleTlsError(String msg) {
    +        if (tlsErrorRecentlySeen()) {
    +            LOG.debug(msg);
    +            return false;
    +        } else {
    +            LOG.error(msg);
    +            return true;
    +        }
    +    }
    +
    +    /**
    +     * Returns {@code true} if any related exception (determined by {@link CertificateUtils#isTlsError(Throwable)}) has occurred within the last
    +     * {@link #EXCEPTION_THRESHOLD_MILLIS} milliseconds. Does not evaluate the error locally,
    +     * simply checks the last time the timestamp was updated.
    +     *
    +     * @return true if the time since the last similar exception occurred is below the threshold
    +     */
    +    private boolean tlsErrorRecentlySeen() {
    +        long now = System.currentTimeMillis();
    +        return now - tlsErrorLastSeen < EXCEPTION_THRESHOLD_MILLIS;
    +    }
    +
         private ServerSocket createServerSocket() throws IOException {
             if (sslContext != null) {
    -            final ServerSocket serverSocket = sslContext.getServerSocketFactory().createServerSocket(socketPort);
    -            ((SSLServerSocket) serverSocket).setNeedClientAuth(true);
    +            final SSLServerSocket serverSocket = (SSLServerSocket) sslContext.getServerSocketFactory().createServerSocket(socketPort);
    +            serverSocket.setNeedClientAuth(true);
    +            // Enforce custom protocols on socket
    +            serverSocket.setEnabledProtocols(CertificateUtils.getCurrentSupportedTlsProtocolVersions());
                 return serverSocket;
             } else {
                 return new ServerSocket(socketPort);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/groovy/org/apache/nifi/remote/SocketRemoteSiteListenerTest.groovy+137 0 added
    @@ -0,0 +1,137 @@
    +/*
    + * 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.remote
    +
    +import org.apache.nifi.security.util.CertificateUtils
    +import org.apache.nifi.security.util.KeyStoreUtils
    +import org.apache.nifi.security.util.KeystoreType
    +import org.apache.nifi.security.util.SslContextFactory
    +import org.apache.nifi.security.util.TlsConfiguration
    +import org.apache.nifi.util.NiFiProperties
    +import org.bouncycastle.jce.provider.BouncyCastleProvider
    +import org.junit.After
    +import org.junit.Before
    +import org.junit.BeforeClass
    +import org.junit.Test
    +import org.junit.runner.RunWith
    +import org.junit.runners.JUnit4
    +import org.slf4j.Logger
    +import org.slf4j.LoggerFactory
    +
    +import javax.net.ssl.SSLContext
    +import javax.net.ssl.SSLServerSocket
    +import java.security.Security
    +
    +@RunWith(JUnit4.class)
    +class SocketRemoteSiteListenerTest extends GroovyTestCase {
    +    private static final Logger logger = LoggerFactory.getLogger(SocketRemoteSiteListenerTest.class)
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/localhost-ks.jks"
    +    private static final String KEYSTORE_PASSWORD = "OI7kMpWzzVNVx/JGhTL/0uO4+PWpGJ46uZ/pfepbkwI"
    +    private static final KeystoreType KEYSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/localhost-ts.jks"
    +    private static final String TRUSTSTORE_PASSWORD = "wAOR0nQJ2EXvOP0JZ2EaqA/n7W69ILS4sWAHghmIWCc"
    +    private static final KeystoreType TRUSTSTORE_TYPE = KeystoreType.JKS
    +
    +    private static final String HOSTNAME = "localhost"
    +    private static final int PORT = 0
    +
    +    // The nifi.properties in src/test/resources has 0.x properties and should be removed or updated
    +    private static final Map<String, String> DEFAULT_PROPS = [
    +            (NiFiProperties.SECURITY_KEYSTORE)         : KEYSTORE_PATH,
    +            (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : KEYSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : KEYSTORE_TYPE.getType(),
    +            (NiFiProperties.SECURITY_TRUSTSTORE)       : TRUSTSTORE_PATH,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): TRUSTSTORE_PASSWORD,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : TRUSTSTORE_TYPE.getType(),
    +            (NiFiProperties.REMOTE_INPUT_HOST): HOSTNAME,
    +            (NiFiProperties.REMOTE_INPUT_PORT): PORT as String,
    +            "nifi.remote.input.secure": "true"
    +    ]
    +
    +    private NiFiProperties mockNiFiProperties = NiFiProperties.createBasicNiFiProperties("", DEFAULT_PROPS)
    +
    +    private static TlsConfiguration tlsConfiguration
    +    private static SSLContext sslContext
    +
    +    private SocketRemoteSiteListener srsListener
    +
    +    @BeforeClass
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
    +
    +        logger.metaClass.methodMissing = { String name, args ->
    +            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
    +        }
    +
    +        tlsConfiguration = new TlsConfiguration(KEYSTORE_PATH, KEYSTORE_PASSWORD, KEYSTORE_TYPE, TRUSTSTORE_PATH, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE)
    +        sslContext = SslContextFactory.createSslContext(tlsConfiguration)
    +    }
    +
    +    @Before
    +    void setUp() {
    +    }
    +
    +    @After
    +    void tearDown() {
    +        if (srsListener) {
    +            srsListener.stop()
    +        }
    +    }
    +
    +    /**
    +     * Asserts that the protocol versions in the parameters object are correct. In recent versions of Java, this enforces order as well, but in older versions, it just enforces presence.
    +     *
    +     * @param enabledProtocols the actual protocols, either in {@code String[]} or {@code Collection<String>} form
    +     * @param expectedProtocols the specific protocol versions to be present (ordered as desired)
    +     */
    +    void assertProtocolVersions(def enabledProtocols, def expectedProtocols) {
    +        if (CertificateUtils.getJavaVersion() > 8) {
    +            assert enabledProtocols == expectedProtocols as String[]
    +        } else {
    +            assert enabledProtocols as Set == expectedProtocols as Set
    +        }
    +    }
    +
    +    @Test
    +    void testShouldCreateSecureServer() {
    +        // Arrange
    +        logger.info("Creating SSL Context from TLS Configuration: ${tlsConfiguration}")
    +        SSLContext sslContext = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.NONE)
    +        logger.info("Created SSL Context: ${KeyStoreUtils.sslContextToString(sslContext)}")
    +
    +        srsListener = new SocketRemoteSiteListener(PORT, sslContext, mockNiFiProperties)
    +
    +        // Act
    +        srsListener.start()
    +
    +        // Assert
    +
    +        // serverSocket isn't instance field like CLBS so have to use private method invocation to verify
    +        SSLServerSocket sslServerSocket = srsListener.createServerSocket() as SSLServerSocket
    +        logger.info("Created SSL server socket: ${KeyStoreUtils.sslServerSocketToString(sslServerSocket)}" as String)
    +        assertProtocolVersions(sslServerSocket.enabledProtocols, CertificateUtils.getCurrentSupportedTlsProtocolVersions())
    +        assert sslServerSocket.needClientAuth
    +
    +        // Assert that the default parameters (which can't be modified) still have legacy protocols and no client auth
    +        def defaultSSLParameters = sslContext.defaultSSLParameters
    +        logger.info("Default SSL Parameters: ${KeyStoreUtils.sslParametersToString(defaultSSLParameters)}" as String)
    +        assertProtocolVersions(defaultSSLParameters.getProtocols(), CertificateUtils.getCurrentSupportedTlsProtocolVersions().sort().reverse() + ["TLSv1.1", "TLSv1"])
    +        assert !defaultSSLParameters.needClientAuth
    +    }
    +}
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/protocol/http/TestHttpFlowFileServerProtocol.java+31 31 modified
    @@ -16,6 +16,32 @@
      */
     package org.apache.nifi.remote.protocol.http;
     
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertTrue;
    +import static org.junit.Assert.fail;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.eq;
    +import static org.mockito.Mockito.doReturn;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.ByteArrayInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.atomic.AtomicLong;
    +import java.util.function.Function;
    +import java.util.stream.Collectors;
    +import java.util.stream.IntStream;
     import org.apache.nifi.connectable.Connection;
     import org.apache.nifi.controller.queue.FlowFileQueue;
     import org.apache.nifi.flowfile.attributes.CoreAttributes;
    @@ -25,6 +51,7 @@
     import org.apache.nifi.processor.ProcessSession;
     import org.apache.nifi.processor.Processor;
     import org.apache.nifi.processor.Relationship;
    +import org.apache.nifi.properties.StandardNiFiProperties;
     import org.apache.nifi.provenance.ProvenanceEventRecord;
     import org.apache.nifi.provenance.ProvenanceEventType;
     import org.apache.nifi.remote.HttpRemoteSiteListener;
    @@ -39,8 +66,8 @@
     import org.apache.nifi.remote.io.http.HttpInput;
     import org.apache.nifi.remote.io.http.HttpServerCommunicationsSession;
     import org.apache.nifi.remote.protocol.DataPacket;
    -import org.apache.nifi.remote.protocol.ResponseCode;
     import org.apache.nifi.remote.protocol.HandshakeProperty;
    +import org.apache.nifi.remote.protocol.ResponseCode;
     import org.apache.nifi.remote.util.StandardDataPacket;
     import org.apache.nifi.util.MockFlowFile;
     import org.apache.nifi.util.MockProcessContext;
    @@ -50,33 +77,6 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    -import java.io.ByteArrayInputStream;
    -import java.io.ByteArrayOutputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.OutputStream;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.atomic.AtomicLong;
    -import java.util.function.Function;
    -import java.util.stream.Collectors;
    -import java.util.stream.IntStream;
    -
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertTrue;
    -import static org.junit.Assert.fail;
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.ArgumentMatchers.eq;
    -import static org.mockito.Mockito.doReturn;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     public class TestHttpFlowFileServerProtocol {
     
         private SharedSessionState sessionState;
    @@ -107,7 +107,7 @@ private Peer getDefaultPeer(final String transactionId) {
     
         private HttpFlowFileServerProtocol getDefaultHttpFlowFileServerProtocol() {
             final StandardVersionNegotiator versionNegotiator = new StandardVersionNegotiator(5, 4, 3, 2, 1);
    -        return new StandardHttpFlowFileServerProtocol(versionNegotiator, NiFiProperties.createBasicNiFiProperties(null, null));
    +        return new StandardHttpFlowFileServerProtocol(versionNegotiator, new StandardNiFiProperties());
         }
     
         @Test
    @@ -365,7 +365,7 @@ private Peer transferFlowFiles(final HttpFlowFileServerProtocol serverProtocol,
                 sessionState.getFlowFileQueue().offer(flowFile);
             }
     
    -        final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties());
     
             serverProtocol.handshake(peer);
             assertTrue(serverProtocol.isHandshakeSuccessful());
    @@ -520,7 +520,7 @@ public void testReceiveOneFileBadChecksum() throws Exception {
         }
     
         private void receiveFlowFiles(final HttpFlowFileServerProtocol serverProtocol, final String transactionId, final Peer peer, final DataPacket ... dataPackets) throws IOException {
    -        final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener remoteSiteListener = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties());
             final HttpServerCommunicationsSession commsSession = (HttpServerCommunicationsSession) peer.getCommunicationsSession();
     
             serverProtocol.handshake(peer);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestHttpRemoteSiteListener.java+9 8 modified
    @@ -16,19 +16,20 @@
      */
     package org.apache.nifi.remote;
     
    +import static org.junit.Assert.assertFalse;
    +import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertTrue;
    +import static org.junit.Assert.fail;
    +
     import org.apache.nifi.processor.ProcessSession;
    +import org.apache.nifi.properties.StandardNiFiProperties;
     import org.apache.nifi.remote.protocol.FlowFileTransaction;
     import org.apache.nifi.remote.protocol.HandshakeProperties;
     import org.apache.nifi.util.NiFiProperties;
     import org.junit.BeforeClass;
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import static org.junit.Assert.assertFalse;
    -import static org.junit.Assert.assertNotNull;
    -import static org.junit.Assert.assertTrue;
    -import static org.junit.Assert.fail;
    -
     public class TestHttpRemoteSiteListener {
     
         @BeforeClass
    @@ -39,7 +40,7 @@ public static void setup() {
     
         @Test
         public void testNormalTransactionProgress() {
    -        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties());
             String transactionId = transactionManager.createTransaction();
     
             assertTrue("Transaction should be active.", transactionManager.isTransactionActive(transactionId));
    @@ -59,7 +60,7 @@ public void testNormalTransactionProgress() {
     
         @Test
         public void testDuplicatedTransactionId() {
    -        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties());
             String transactionId = transactionManager.createTransaction();
     
             assertTrue("Transaction should be active.", transactionManager.isTransactionActive(transactionId));
    @@ -78,7 +79,7 @@ public void testDuplicatedTransactionId() {
     
         @Test
         public void testNoneExistingTransaction() {
    -        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(new StandardNiFiProperties());
     
             String transactionId = "does-not-exist-1";
             assertFalse("Transaction should not be active.", transactionManager.isTransactionActive(transactionId));
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRemoteGroupPort.java+24 25 modified
    @@ -16,6 +16,27 @@
      */
     package org.apache.nifi.remote;
     
    +import static org.junit.Assert.assertEquals;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.eq;
    +import static org.mockito.Matchers.anyString;
    +import static org.mockito.Mockito.doAnswer;
    +import static org.mockito.Mockito.doReturn;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.spy;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.ByteArrayInputStream;
    +import java.util.ArrayList;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicInteger;
    +import java.util.concurrent.atomic.AtomicLong;
    +import java.util.stream.IntStream;
     import org.apache.nifi.connectable.ConnectableType;
     import org.apache.nifi.controller.ProcessScheduler;
     import org.apache.nifi.events.EventReporter;
    @@ -26,6 +47,7 @@
     import org.apache.nifi.groups.RemoteProcessGroup;
     import org.apache.nifi.processor.Processor;
     import org.apache.nifi.processor.Relationship;
    +import org.apache.nifi.properties.StandardNiFiProperties;
     import org.apache.nifi.provenance.ProvenanceEventRecord;
     import org.apache.nifi.provenance.ProvenanceEventType;
     import org.apache.nifi.remote.client.SiteToSiteClient;
    @@ -39,34 +61,11 @@
     import org.apache.nifi.util.MockFlowFile;
     import org.apache.nifi.util.MockProcessContext;
     import org.apache.nifi.util.MockProcessSession;
    +import org.apache.nifi.util.NiFiProperties;
     import org.apache.nifi.util.SharedSessionState;
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    -import java.io.ByteArrayInputStream;
    -import java.util.ArrayList;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.atomic.AtomicLong;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicInteger;
    -import java.util.stream.IntStream;
    -
    -import org.apache.nifi.util.NiFiProperties;
    -
    -import static org.junit.Assert.assertEquals;
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.ArgumentMatchers.eq;
    -import static org.mockito.Matchers.anyString;
    -import static org.mockito.Mockito.doAnswer;
    -import static org.mockito.Mockito.doReturn;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.spy;
    -import static org.mockito.Mockito.when;
    -
     public class TestStandardRemoteGroupPort {
     
         private static final String ID = "remote-group-port-id";
    @@ -121,7 +120,7 @@ private void setupMock(final SiteToSiteTransportProtocol protocol,
             }
     
             port = spy(new StandardRemoteGroupPort(ID, ID, NAME,
    -                remoteGroup, direction, connectableType, null, scheduler, NiFiProperties.createBasicNiFiProperties(null, null)));
    +                remoteGroup, direction, connectableType, null, scheduler, new StandardNiFiProperties()));
     
             doReturn(true).when(remoteGroup).isTransmitting();
             doReturn(protocol).when(remoteGroup).getTransportProtocol();
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/resources/localhost-ks.jks+0 0 added
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/resources/localhost-ts.jks+0 0 added
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-stateless/src/main/java/org/apache/nifi/stateless/core/StatelessFlow.java+5 2 modified
    @@ -54,7 +54,9 @@
     import org.apache.nifi.registry.flow.VersionedRemoteGroupPort;
     import org.apache.nifi.registry.flow.VersionedRemoteProcessGroup;
     import org.apache.nifi.reporting.InitializationException;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.stateless.bootstrap.ExtensionDiscovery;
     import org.apache.nifi.stateless.bootstrap.InMemoryFlowFile;
     import org.apache.nifi.stateless.bootstrap.RunnableFlow;
    @@ -370,8 +372,9 @@ public static SSLContext getSSLContext(final JsonObject config) {
                 final String truststoreType = sslObject.get(TRUSTSTORE_TYPE).getAsString();
     
                 try {
    -                return SslContextFactory.createSslContext(keystore, keystorePass.toCharArray(), keyPass.toCharArray(), keystoreType,
    -                    truststore, truststorePass.toCharArray(), truststoreType, SslContextFactory.ClientAuth.REQUIRED, "TLS");
    +                TlsConfiguration tlsConfiguration = new TlsConfiguration(keystore, keystorePass, keyPass, keystoreType,
    +                        truststore, truststorePass, truststoreType, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +                return SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.REQUIRED);
                 } catch (final Exception e) {
                     throw new RuntimeException("Failed to create Keystore", e);
                 }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml+0 5 modified
    @@ -52,11 +52,6 @@
                 <artifactId>nifi-properties</artifactId>
                 <scope>compile</scope>
             </dependency>
    -        <dependency>
    -            <groupId>org.apache.nifi</groupId>
    -            <artifactId>nifi-security</artifactId>
    -            <scope>compile</scope>
    -        </dependency>
             <dependency>
                 <groupId>org.eclipse.jetty</groupId>
                 <artifactId>jetty-server</artifactId>
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java+38 33 modified
    @@ -18,6 +18,37 @@
     
     import com.google.common.base.Strings;
     import com.google.common.collect.Lists;
    +import java.io.BufferedReader;
    +import java.io.BufferedWriter;
    +import java.io.File;
    +import java.io.FileFilter;
    +import java.io.IOException;
    +import java.io.InputStreamReader;
    +import java.io.OutputStream;
    +import java.io.OutputStreamWriter;
    +import java.net.InetAddress;
    +import java.net.NetworkInterface;
    +import java.net.SocketException;
    +import java.nio.file.Paths;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.EnumSet;
    +import java.util.Enumeration;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Objects;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import java.util.jar.JarEntry;
    +import java.util.jar.JarFile;
    +import java.util.stream.Collectors;
    +import javax.servlet.DispatcherType;
    +import javax.servlet.Filter;
    +import javax.servlet.ServletContext;
     import org.apache.commons.collections4.CollectionUtils;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.NiFiServer;
    @@ -42,6 +73,7 @@
     import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
     import org.apache.nifi.nar.StandardNarLoader;
     import org.apache.nifi.processor.DataUnit;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.KeyStoreUtils;
     import org.apache.nifi.services.FlowService;
     import org.apache.nifi.ui.extension.UiExtension;
    @@ -51,11 +83,11 @@
     import org.apache.nifi.web.ContentAccess;
     import org.apache.nifi.web.NiFiWebConfigurationContext;
     import org.apache.nifi.web.UiExtensionType;
    -import org.apache.nifi.web.security.requests.ContentLengthFilter;
     import org.apache.nifi.web.security.headers.ContentSecurityPolicyFilter;
     import org.apache.nifi.web.security.headers.StrictTransportSecurityFilter;
     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.eclipse.jetty.annotations.AnnotationConfiguration;
     import org.eclipse.jetty.deploy.App;
     import org.eclipse.jetty.deploy.DeploymentManager;
    @@ -88,38 +120,6 @@
     import org.springframework.web.context.WebApplicationContext;
     import org.springframework.web.context.support.WebApplicationContextUtils;
     
    -import javax.servlet.DispatcherType;
    -import javax.servlet.Filter;
    -import javax.servlet.ServletContext;
    -import java.io.BufferedReader;
    -import java.io.BufferedWriter;
    -import java.io.File;
    -import java.io.FileFilter;
    -import java.io.IOException;
    -import java.io.InputStreamReader;
    -import java.io.OutputStream;
    -import java.io.OutputStreamWriter;
    -import java.net.InetAddress;
    -import java.net.NetworkInterface;
    -import java.net.SocketException;
    -import java.nio.file.Paths;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.EnumSet;
    -import java.util.Enumeration;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Objects;
    -import java.util.Set;
    -import java.util.concurrent.TimeUnit;
    -import java.util.jar.JarEntry;
    -import java.util.jar.JarFile;
    -import java.util.stream.Collectors;
    -
     /**
      * Encapsulates the Jetty instance.
      */
    @@ -930,6 +930,11 @@ protected static void configureSslContextFactory(SslContextFactory contextFactor
             // Previous to Jetty 9.4.15.v20190215, this defaulted to null, and now defaults to "HTTPS".
             contextFactory.setEndpointIdentificationAlgorithm(null);
     
    +        // Explicitly exclude legacy TLS protocol versions
    +        // contextFactory.setProtocol(CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        contextFactory.setIncludeProtocols(CertificateUtils.getCurrentSupportedTlsProtocolVersions());
    +        contextFactory.setExcludeProtocols("TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3");
    +
             // require client auth when not supporting login, Kerberos service, or anonymous access
             if (props.isClientAuthRequiredForRestApi()) {
                 contextFactory.setNeedClientAuth(true);
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy+184 19 modified
    @@ -20,14 +20,19 @@ import org.apache.log4j.AppenderSkeleton
     import org.apache.log4j.spi.LoggingEvent
     import org.apache.nifi.bundle.Bundle
     import org.apache.nifi.properties.StandardNiFiProperties
    +import org.apache.nifi.security.util.CertificateUtils
    +import org.apache.nifi.security.util.TlsConfiguration
     import org.apache.nifi.util.NiFiProperties
     import org.bouncycastle.jce.provider.BouncyCastleProvider
     import org.eclipse.jetty.server.Connector
     import org.eclipse.jetty.server.HttpConfiguration
     import org.eclipse.jetty.server.Server
     import org.eclipse.jetty.server.ServerConnector
    +import org.eclipse.jetty.server.SslConnectionFactory
    +import org.eclipse.jetty.util.ssl.SslContextFactory
     import org.junit.After
     import org.junit.AfterClass
    +import org.junit.Assume
     import org.junit.Before
     import org.junit.BeforeClass
     import org.junit.Rule
    @@ -41,6 +46,9 @@ import org.junit.runners.JUnit4
     import org.slf4j.Logger
     import org.slf4j.LoggerFactory
     
    +import javax.net.ssl.SSLSocket
    +import javax.net.ssl.SSLSocketFactory
    +import java.nio.charset.StandardCharsets
     import java.security.Security
     
     @RunWith(JUnit4.class)
    @@ -56,6 +64,36 @@ class JettyServerGroovyTest extends GroovyTestCase {
         @Rule
         public final SystemErrRule systemErrRule = new SystemErrRule().enableLog()
     
    +    private static final int HTTPS_PORT = 8443
    +    private static final String HTTPS_HOSTNAME = "localhost"
    +
    +    private static final String KEYSTORE_PATH = "src/test/resources/keystore.jks"
    +    private static final String TRUSTSTORE_PATH = "src/test/resources/truststore.jks"
    +    private static final String STORE_PASSWORD = "passwordpassword"
    +    private static final String STORE_TYPE = "JKS"
    +
    +    private static final String TLS_1_2_PROTOCOL = "TLSv1.2"
    +    private static final String TLS_1_3_PROTOCOL = "TLSv1.3"
    +    private static final List<String> TLS_1_3_CIPHER_SUITES = ["TLS_AES_128_GCM_SHA256"]
    +
    +    // Depending if the test is run on Java 8 or Java 11, these values change (TLSv1.2 vs. TLSv1.3)
    +    private static final CURRENT_TLS_PROTOCOL_VERSION = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion()
    +    private static final List<String> CURRENT_TLS_PROTOCOL_VERSIONS = CertificateUtils.getCurrentSupportedTlsProtocolVersions()
    +
    +    // These protocol versions should not ever be supported
    +    static private final List<String> LEGACY_TLS_PROTOCOLS = ["TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3"]
    +
    +    NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([
    +            (NiFiProperties.WEB_HTTPS_PORT)            : HTTPS_PORT as String,
    +            (NiFiProperties.WEB_HTTPS_HOST)            : HTTPS_HOSTNAME,
    +            (NiFiProperties.SECURITY_KEYSTORE)         : KEYSTORE_PATH,
    +            (NiFiProperties.SECURITY_KEYSTORE_PASSWD)  : STORE_PASSWORD,
    +            (NiFiProperties.SECURITY_KEYSTORE_TYPE)    : STORE_TYPE,
    +            (NiFiProperties.SECURITY_TRUSTSTORE)       : TRUSTSTORE_PATH,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_PASSWD): STORE_PASSWORD,
    +            (NiFiProperties.SECURITY_TRUSTSTORE_TYPE)  : STORE_TYPE,
    +    ]))
    +
         @BeforeClass
         static void setUpOnce() throws Exception {
             Security.addProvider(new BouncyCastleProvider())
    @@ -168,16 +206,16 @@ class JettyServerGroovyTest extends GroovyTestCase {
                     (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
             ]
             NiFiProperties mockProps = [
    -                getPort    : { -> 8080 },
    -                getSslPort : { -> 8443 },
    -                getProperty: { String prop ->
    +                getPort            : { -> 8080 },
    +                getSslPort         : { -> 8443 },
    +                getProperty        : { String prop ->
                         String value = badProps[prop] ?: "no_value"
                         logger.mock("getProperty(${prop}) -> ${value}")
                         value
                     },
    -                getWebThreads: { -> NiFiProperties.DEFAULT_WEB_THREADS },
    +                getWebThreads      : { -> NiFiProperties.DEFAULT_WEB_THREADS },
                     getWebMaxHeaderSize: { -> NiFiProperties.DEFAULT_WEB_MAX_HEADER_SIZE },
    -                isHTTPSConfigured: { -> true }
    +                isHTTPSConfigured  : { -> true }
             ] as StandardNiFiProperties
     
             // The web server should fail to start and exit Java
    @@ -187,11 +225,11 @@ class JettyServerGroovyTest extends GroovyTestCase {
                     final String standardErr = systemErrRule.getLog()
                     List<String> errLines = standardErr.split("\n")
     
    -                assert errLines.any { it =~ "Failed to start web server: "}
    -                assert errLines.any { it =~ "Shutting down..."}
    +                assert errLines.any { it =~ "Failed to start web server: " }
    +                assert errLines.any { it =~ "Shutting down..." }
                 }
             })
    -        
    +
             // Act
             JettyServer jettyServer = new JettyServer(mockProps, [] as Set<Bundle>)
     
    @@ -203,25 +241,152 @@ class JettyServerGroovyTest extends GroovyTestCase {
         @Test
         void testShouldConfigureHTTPSConnector() {
             // Arrange
    -       NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([
    -//               (NiFiProperties.WEB_HTTP_PORT): null,
    -//               (NiFiProperties.WEB_HTTP_HOST): null,
    -               (NiFiProperties.WEB_HTTPS_PORT): "8443",
    -               (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com",
    -       ]))
    -        
    +        final String externalHostname = "secure.host.com"
    +
    +        NiFiProperties httpsProps = new StandardNiFiProperties(rawProperties: new Properties([
    +                (NiFiProperties.WEB_HTTPS_PORT): HTTPS_PORT as String,
    +                (NiFiProperties.WEB_HTTPS_HOST): externalHostname,
    +        ]))
    +
    +        Server internalServer = new Server()
    +        JettyServer jetty = new JettyServer(internalServer, httpsProps)
    +
    +        // Act
    +        jetty.configureHttpsConnector(internalServer, new HttpConfiguration())
    +        List<Connector> connectors = Arrays.asList(internalServer.connectors)
    +
    +        // Assert
    +
    +        // Set the expected TLS protocols to null because no actual keystore/truststore is loaded here
    +        assertServerConnector(connectors, "TLS", null, null, externalHostname, HTTPS_PORT)
    +    }
    +
    +    @Test
    +    void testShouldSupportTLSv1_3OnJava11() {
    +        // Arrange
    +        Assume.assumeTrue("This test should only run on Java 11+", CertificateUtils.getJavaVersion() >= 11)
    +
    +        Server internalServer = new Server()
    +        JettyServer jetty = new JettyServer(internalServer, httpsProps)
    +
    +        jetty.configureConnectors(internalServer)
    +        List<Connector> connectors = Arrays.asList(internalServer.connectors)
    +        internalServer.start()
    +
    +        // Create a (client) socket which only supports TLSv1.3
    +        TlsConfiguration tls13ClientConf = TlsConfiguration.fromNiFiProperties(httpsProps)
    +        SSLSocketFactory socketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tls13ClientConf)
    +
    +        SSLSocket socket = (SSLSocket) socketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
    +        socket.setEnabledProtocols([TLS_1_3_PROTOCOL] as String[])
    +        socket.setEnabledCipherSuites(TLS_1_3_CIPHER_SUITES as String[])
    +
    +        // Act
    +        String response = makeTLSRequest(socket, "This is a TLS 1.3 request")
    +
    +        // Assert
    +        assert response =~ "HTTP/1.1 400"
    +
    +        // Assert that the connector prefers TLSv1.3 but the JVM supports TLSv1.2 as well
    +        assertServerConnector(connectors, "TLS", [CURRENT_TLS_PROTOCOL_VERSION], CURRENT_TLS_PROTOCOL_VERSIONS)
    +
    +        // Clean up
    +        internalServer.stop()
    +    }
    +
    +    @Test
    +    void testShouldNotSupportTLSv1_3OnJava8() {
    +        // Arrange
    +        Assume.assumeTrue("This test should only run on Java 8", CertificateUtils.getJavaVersion() <= 8)
    +
             Server internalServer = new Server()
             JettyServer jetty = new JettyServer(internalServer, httpsProps)
     
    +        jetty.configureConnectors(internalServer)
    +        List<Connector> connectors = Arrays.asList(internalServer.connectors)
    +        internalServer.start()
    +
    +        TlsConfiguration tlsConfiguration = TlsConfiguration.fromNiFiProperties(httpsProps)
    +
    +        // Create a "default" (client) socket (which supports TLSv1.2)
    +        SSLSocketFactory defaultSocketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tlsConfiguration)
    +        SSLSocket defaultSocket = (SSLSocket) defaultSocketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
    +
             // Act
    -       jetty.configureHttpsConnector(internalServer, new HttpConfiguration())
    -       List<Connector> connectors = Arrays.asList(internalServer.connectors)
    +        String tls12Response = makeTLSRequest(defaultSocket, "This is a default socket request")
    +
    +        def msg = shouldFail(IllegalArgumentException) {
    +            // Create a (client) socket which only supports TLSv1.3
    +            SSLSocketFactory tls13SocketFactory = org.apache.nifi.security.util.SslContextFactory.createSSLSocketFactory(tlsConfiguration)
    +
    +            SSLSocket tls13Socket = (SSLSocket) tls13SocketFactory.createSocket(HTTPS_HOSTNAME, HTTPS_PORT)
    +            tls13Socket.setEnabledProtocols([TLS_1_3_PROTOCOL] as String[])
    +            tls13Socket.setEnabledCipherSuites(TLS_1_3_CIPHER_SUITES as String[])
    +
    +            String tls13Response = makeTLSRequest(tls13Socket, "This is a TLSv1.3 socket request")
    +        }
    +        // The IAE message is just the invalid argument (i.e. "TLSv1.3")
    +        logger.expected(msg)
     
             // Assert
    +        assert tls12Response =~ "HTTP"
    +        assert msg == "TLSv1.3"
    +
    +        // Assert that the connector only accepts TLSv1.2
    +        assertServerConnector(connectors, "TLS", [CURRENT_TLS_PROTOCOL_VERSION], CURRENT_TLS_PROTOCOL_VERSIONS)
    +
    +        // Clean up
    +        internalServer.stop()
    +    }
    +
    +    /**
    +     * Returns the server's response body as a String. Closes the socket connection.
    +     *
    +     * @param socket
    +     * @param requestMessage
    +     * @return
    +     */
    +    private static String makeTLSRequest(Socket socket, String requestMessage) {
    +        InputStream socketInputStream = new BufferedInputStream(socket.getInputStream())
    +        OutputStream socketOutputStream = new BufferedOutputStream(socket.getOutputStream())
    +
    +        socketOutputStream.write(requestMessage.getBytes())
    +        socketOutputStream.flush()
    +
    +        byte[] data = new byte[2048]
    +        int len = socketInputStream.read(data)
    +        if (len <= 0) {
    +            throw new IOException("no data received")
    +        }
    +        final String trimmedResponse = new String(data, 0, len, StandardCharsets.UTF_8)
    +        logger.info("Client received ${len} bytes from server: \n${trimmedResponse}\n----End of response----")
    +        socket.close()
    +        trimmedResponse
    +    }
    +
    +    private static void assertServerConnector(List<Connector> connectors,
    +                                              String EXPECTED_TLS_PROTOCOL = "TLS",
    +                                              List<String> EXPECTED_INCLUDED_PROTOCOLS = CertificateUtils.getCurrentSupportedTlsProtocolVersions(),
    +                                              List<String> EXPECTED_SELECTED_PROTOCOLS = CertificateUtils.getCurrentSupportedTlsProtocolVersions(),
    +                                              String EXPECTED_HOSTNAME = HTTPS_HOSTNAME,
    +                                              int EXPECTED_PORT = HTTPS_PORT) {
    +        // Assert the server connector is correct
             assert connectors.size() == 1
             ServerConnector connector = connectors.first() as ServerConnector
    -        assert connector.host == "secure.host.com"
    -        assert connector.port == 8443
    +        assert connector.host == EXPECTED_HOSTNAME
    +        assert connector.port == EXPECTED_PORT
    +        assert connector.getProtocols() == ['ssl', 'http/1.1']
    +
    +        // This kind of testing is not ideal as it breaks encapsulation, but is necessary to enforce verification of the TLS protocol versions specified
    +        SslConnectionFactory connectionFactory = connector.getConnectionFactory("ssl") as SslConnectionFactory
    +        SslContextFactory sslContextFactory = connectionFactory._sslContextFactory as SslContextFactory
    +        logger.debug("SSL Context Factory: ${sslContextFactory.dump()}")
    +
    +        // Using the getters is subject to NPE due to blind array copies
    +        assert sslContextFactory._sslProtocol == EXPECTED_TLS_PROTOCOL
    +        assert sslContextFactory._includeProtocols.containsAll(EXPECTED_INCLUDED_PROTOCOLS ?: Collections.emptySet())
    +        assert (sslContextFactory._excludeProtocols as List<String>).containsAll(LEGACY_TLS_PROTOCOLS)
    +        assert sslContextFactory._selectedProtocols == EXPECTED_SELECTED_PROTOCOLS as String[]
         }
     }
     
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/keystore.jks+0 0 added
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties+1 1 modified
    @@ -15,7 +15,7 @@
     # limitations under the License.
     #
     
    -log4j.rootLogger=DEBUG,console,test
    +log4j.rootLogger=INFO,console,test
     
     log4j.appender.console=org.apache.log4j.ConsoleAppender
     log4j.appender.console.Target=System.err
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/logback-test.xml+38 0 added
    @@ -0,0 +1,38 @@
    +<?xml version="1.0" encoding="UTF-8"?>
    +<!--
    +  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.
    +-->
    +
    +<configuration>
    +    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    +        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    +            <pattern>%-4r [%t] %-5p %c - %m%n</pattern>
    +        </encoder>
    +    </appender>
    +
    +    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    +        <file>./target/log</file>
    +        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
    +            <pattern>%date %level [%thread] %logger{40} %msg%n</pattern>
    +        </encoder>
    +    </appender>
    +
    +
    +    <logger name="org.apache.nifi" level="INFO"/>
    +    <logger name="org.apache.nifi.web.server" level="DEBUG"/>
    +    <logger name="org.eclipse.jetty.server" level="DEBUG"/>
    +    <root level="INFO">
    +        <appender-ref ref="CONSOLE"/>
    +    </root>
    +</configuration>
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/truststore.jks+0 0 added
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java+7 9 modified
    @@ -16,6 +16,12 @@
      */
     package org.apache.nifi.integration.accesscontrol;
     
    +import static org.junit.Assert.assertEquals;
    +
    +import java.io.File;
    +import java.nio.file.Files;
    +import java.nio.file.StandardCopyOption;
    +import javax.ws.rs.core.Response;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.integration.NiFiWebApiTest;
     import org.apache.nifi.integration.util.NiFiTestAuthorizer;
    @@ -30,13 +36,6 @@
     import org.apache.nifi.util.NiFiProperties;
     import org.apache.nifi.util.StringUtils;
     
    -import javax.ws.rs.core.Response;
    -import java.io.File;
    -import java.nio.file.Files;
    -import java.nio.file.StandardCopyOption;
    -
    -import static org.junit.Assert.assertEquals;
    -
     /**
      * Access control test for the dfm user.
      */
    @@ -69,8 +68,7 @@ public AccessControlHelper(final String nifiPropertiesPath) throws Exception {
             // configure the location of the nifi properties
             File nifiPropertiesFile = new File(nifiPropertiesPath);
             System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
    -
    -        NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null);
    +        NiFiProperties props = NiFiProperties.createBasicNiFiProperties(null);
             flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);
     
             final File libTargetDir = new File("target/test-classes/access-control/lib");
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ITAccessTokenEndpoint.java+8 7 modified
    @@ -16,6 +16,12 @@
      */
     package org.apache.nifi.integration.accesscontrol;
     
    +import java.util.Calendar;
    +import java.util.HashMap;
    +import java.util.LinkedHashMap;
    +import java.util.Map;
    +import java.util.StringJoiner;
    +import javax.ws.rs.core.Response;
     import net.minidev.json.JSONObject;
     import org.apache.nifi.integration.util.SourceTestProcessor;
     import org.apache.nifi.web.api.dto.AccessConfigurationDTO;
    @@ -31,13 +37,6 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    -import javax.ws.rs.core.Response;
    -import java.util.Calendar;
    -import java.util.HashMap;
    -import java.util.LinkedHashMap;
    -import java.util.Map;
    -import java.util.StringJoiner;
    -
     /**
      * Access token endpoint test.
      */
    @@ -220,6 +219,8 @@ public void testRequestAccessUsingToken() throws Exception {
             Assert.assertEquals("ACTIVE", accessStatus.getStatus());
         }
     
    +    // // TODO: Revisit the HTTP status codes in this test after logout functionality change
    +    // @Ignore("This test is failing before refactoring")
         @Test
         public void testLogOutSuccess() throws Exception {
             String accessStatusUrl = helper.getBaseUrl() + "/access";
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OneWaySslAccessControlHelper.java+10 14 modified
    @@ -16,6 +16,10 @@
      */
     package org.apache.nifi.integration.accesscontrol;
     
    +import java.io.File;
    +import java.nio.file.Files;
    +import java.nio.file.StandardCopyOption;
    +import javax.ws.rs.client.Client;
     import org.apache.commons.io.FileUtils;
     import org.apache.nifi.bundle.Bundle;
     import org.apache.nifi.integration.util.NiFiTestServer;
    @@ -27,15 +31,10 @@
     import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
     import org.apache.nifi.nar.SystemBundle;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.util.NiFiProperties;
     import org.apache.nifi.web.util.WebUtils;
     
    -import javax.net.ssl.SSLContext;
    -import javax.ws.rs.client.Client;
    -import java.io.File;
    -import java.nio.file.Files;
    -import java.nio.file.StandardCopyOption;
    -
     /**
      * Access control test for the dfm user.
      */
    @@ -58,7 +57,7 @@ public OneWaySslAccessControlHelper(final String nifiPropertiesPath) throws Exce
             File nifiPropertiesFile = new File(nifiPropertiesPath);
             System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, nifiPropertiesFile.getAbsolutePath());
     
    -        NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath, null);
    +        NiFiProperties props = NiFiProperties.createBasicNiFiProperties(nifiPropertiesPath);
             flowXmlPath = props.getProperty(NiFiProperties.FLOW_CONFIGURATION_FILE);
     
             // delete the database directory to avoid issues with re-registration in testRequestAccessUsingToken
    @@ -90,8 +89,11 @@ public OneWaySslAccessControlHelper(final String nifiPropertiesPath) throws Exce
             // get the base url
             baseUrl = server.getBaseUrl() + CONTEXT_PATH;
     
    +        // Create a TlsConfiguration for the truststore properties only
    +        TlsConfiguration trustOnlyTlsConfiguration = TlsConfiguration.fromNiFiPropertiesTruststoreOnly(props);
    +
             // create the user
    -        final Client client = WebUtils.createClient(null, createTrustContext(props));
    +        final Client client = WebUtils.createClient(null, SslContextFactory.createSslContext(trustOnlyTlsConfiguration));
             user = new NiFiTestUser(client, null);
         }
     
    @@ -103,12 +105,6 @@ public String getBaseUrl() {
             return baseUrl;
         }
     
    -    private static SSLContext createTrustContext(final NiFiProperties props) throws Exception {
    -        return SslContextFactory.createTrustSslContext(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE),
    -                props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD).toCharArray(),
    -                props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE), "TLS");
    -    }
    -
         public void cleanup() throws Exception {
             // shutdown the server
             server.shutdownServer();
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestServer.java+9 8 modified
    @@ -16,8 +16,13 @@
      */
     package org.apache.nifi.integration.util;
     
    +import java.io.File;
    +import java.util.Collections;
    +import javax.servlet.ServletContext;
    +import javax.ws.rs.client.Client;
     import org.apache.commons.lang3.StringUtils;
    -import org.apache.nifi.framework.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.services.FlowService;
     import org.apache.nifi.ui.extension.UiExtensionMapping;
     import org.apache.nifi.util.NiFiProperties;
    @@ -34,11 +39,6 @@
     import org.springframework.web.context.WebApplicationContext;
     import org.springframework.web.context.support.WebApplicationContextUtils;
     
    -import javax.servlet.ServletContext;
    -import javax.ws.rs.client.Client;
    -import java.io.File;
    -import java.util.Collections;
    -
     /**
      * Creates an embedded server for testing the NiFi REST API.
      */
    @@ -75,6 +75,7 @@ private Server createServer() {
             return jetty;
         }
     
    +    // TODO: Refactor this method to use proper factory methods
         private void createSecureConnector() {
             org.eclipse.jetty.util.ssl.SslContextFactory contextFactory = new org.eclipse.jetty.util.ssl.SslContextFactory();
     
    @@ -171,8 +172,8 @@ public String getBaseUrl() {
             return "https://localhost:" + getPort();
         }
     
    -    public Client getClient() {
    -        return WebUtils.createClient(null, SslContextFactory.createSslContext(properties));
    +    public Client getClient() throws TlsException {
    +        return WebUtils.createClient(null, org.apache.nifi.security.util.SslContextFactory.createSslContext(TlsConfiguration.fromNiFiProperties(properties)));
         }
     
         /**
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestDataTransferResource.java+34 35 modified
    @@ -16,6 +16,31 @@
      */
     package org.apache.nifi.web.api;
     
    +import static org.apache.nifi.web.api.ApplicationResource.PROXY_HOST_HTTP_HEADER;
    +import static org.apache.nifi.web.api.ApplicationResource.PROXY_PORT_HTTP_HEADER;
    +import static org.apache.nifi.web.api.ApplicationResource.PROXY_SCHEME_HTTP_HEADER;
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertTrue;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.eq;
    +import static org.mockito.Mockito.doAnswer;
    +import static org.mockito.Mockito.doReturn;
    +import static org.mockito.Mockito.doThrow;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.InputStream;
    +import java.lang.reflect.Field;
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import java.net.URL;
    +import javax.servlet.ServletContext;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +import javax.ws.rs.core.Response;
    +import javax.ws.rs.core.StreamingOutput;
    +import javax.ws.rs.core.UriBuilder;
    +import javax.ws.rs.core.UriInfo;
     import org.apache.nifi.authorization.AuthorizableLookup;
     import org.apache.nifi.authorization.resource.ResourceType;
     import org.apache.nifi.remote.HttpRemoteSiteListener;
    @@ -33,32 +58,6 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    -import javax.servlet.ServletContext;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -import javax.ws.rs.core.Response;
    -import javax.ws.rs.core.StreamingOutput;
    -import javax.ws.rs.core.UriBuilder;
    -import javax.ws.rs.core.UriInfo;
    -import java.io.InputStream;
    -import java.lang.reflect.Field;
    -import java.net.URI;
    -import java.net.URISyntaxException;
    -import java.net.URL;
    -
    -import static org.apache.nifi.web.api.ApplicationResource.PROXY_HOST_HTTP_HEADER;
    -import static org.apache.nifi.web.api.ApplicationResource.PROXY_PORT_HTTP_HEADER;
    -import static org.apache.nifi.web.api.ApplicationResource.PROXY_SCHEME_HTTP_HEADER;
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertTrue;
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.ArgumentMatchers.eq;
    -import static org.mockito.Mockito.doAnswer;
    -import static org.mockito.Mockito.doReturn;
    -import static org.mockito.Mockito.doThrow;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     public class TestDataTransferResource {
     
         @BeforeClass
    @@ -231,7 +230,7 @@ public void testExtendTransaction() throws Exception {
             final UriInfo uriInfo = mockUriInfo(locationUriStr);
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.extendPortTransactionTTL("input-ports", "port-id", transactionId, req, res, context, uriInfo, inputStream);
    @@ -262,7 +261,7 @@ public void testReceiveFlowFiles() throws Exception {
             final ServletContext context = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.receiveFlowFiles("port-id", transactionId, req, context, inputStream);
    @@ -289,7 +288,7 @@ public void testReceiveZeroFlowFiles() throws Exception {
             final ServletContext context = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.receiveFlowFiles("port-id", transactionId, req, context, inputStream);
    @@ -308,7 +307,7 @@ public void testCommitInputPortTransaction() throws Exception {
             final ServletContext context = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.commitInputPortTransaction(ResponseCode.CONFIRM_TRANSACTION.getCode(), "port-id", transactionId, req, context, inputStream);
    @@ -331,7 +330,7 @@ public void testTransferFlowFiles() throws Exception {
             final HttpServletResponse res = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.transferFlowFiles("port-id", transactionId, req, res, context, inputStream);
    @@ -353,7 +352,7 @@ public void testCommitOutputPortTransaction() throws Exception {
             final ServletContext context = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.commitOutputPortTransaction(ResponseCode.CONFIRM_TRANSACTION.getCode(),
    @@ -379,7 +378,7 @@ public void testCommitOutputPortTransactionBadChecksum() throws Exception {
             final ServletContext context = null;
             final InputStream inputStream = null;
     
    -        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null, null));
    +        final HttpRemoteSiteListener transactionManager = HttpRemoteSiteListener.getInstance(NiFiProperties.createBasicNiFiProperties(null));
             final String transactionId = transactionManager.createTransaction();
     
             final Response response = resource.commitOutputPortTransaction(ResponseCode.CONFIRM_TRANSACTION.getCode(),
    @@ -397,7 +396,7 @@ private DataTransferResource getDataTransferResource() {
             final NiFiServiceFacade serviceFacade = mock(NiFiServiceFacade.class);
     
             final HttpFlowFileServerProtocol serverProtocol = mock(HttpFlowFileServerProtocol.class);
    -        final DataTransferResource resource = new DataTransferResource(NiFiProperties.createBasicNiFiProperties(null, null)) {
    +        final DataTransferResource resource = new DataTransferResource(NiFiProperties.createBasicNiFiProperties(null)) {
                 @Override
                 protected void authorizeDataTransfer(AuthorizableLookup lookup, ResourceType resourceType, String identifier) {
                 }
    @@ -407,7 +406,7 @@ HttpFlowFileServerProtocol getHttpFlowFileServerProtocol(VersionNegotiator versi
                     return serverProtocol;
                 }
             };
    -        resource.setProperties(NiFiProperties.createBasicNiFiProperties(null, null));
    +        resource.setProperties(NiFiProperties.createBasicNiFiProperties(null));
             resource.setServiceFacade(serviceFacade);
             return resource;
         }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidator.java+25 24 modified
    @@ -20,9 +20,31 @@
     import com.google.common.cache.CacheLoader;
     import com.google.common.cache.LoadingCache;
     import com.google.common.util.concurrent.UncheckedExecutionException;
    +import java.io.FileInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.math.BigInteger;
    +import java.net.URI;
    +import java.security.KeyStore;
    +import java.security.cert.CertificateException;
    +import java.security.cert.CertificateFactory;
    +import java.security.cert.X509Certificate;
    +import java.util.HashMap;
    +import java.util.Map;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.TrustManager;
    +import javax.net.ssl.TrustManagerFactory;
    +import javax.net.ssl.X509TrustManager;
    +import javax.security.auth.x500.X500Principal;
    +import javax.ws.rs.ProcessingException;
    +import javax.ws.rs.client.Client;
    +import javax.ws.rs.client.Entity;
    +import javax.ws.rs.client.WebTarget;
    +import javax.ws.rs.core.Response;
     import org.apache.commons.lang3.StringUtils;
    -import org.apache.nifi.framework.security.util.SslContextFactory;
     import org.apache.nifi.security.util.KeyStoreUtils;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.util.FormatUtils;
     import org.apache.nifi.util.NiFiProperties;
     import org.apache.nifi.web.security.x509.ocsp.OcspStatus.ValidationStatus;
    @@ -53,28 +75,6 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.TrustManagerFactory;
    -import javax.net.ssl.X509TrustManager;
    -import javax.security.auth.x500.X500Principal;
    -import javax.ws.rs.ProcessingException;
    -import javax.ws.rs.client.Client;
    -import javax.ws.rs.client.Entity;
    -import javax.ws.rs.client.WebTarget;
    -import javax.ws.rs.core.Response;
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.math.BigInteger;
    -import java.net.URI;
    -import java.security.KeyStore;
    -import java.security.cert.CertificateException;
    -import java.security.cert.CertificateFactory;
    -import java.security.cert.X509Certificate;
    -import java.util.HashMap;
    -import java.util.Map;
    -import java.util.concurrent.TimeUnit;
    -
     public class OcspCertificateValidator {
     
         private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidator.class);
    @@ -107,7 +107,8 @@ public OcspCertificateValidator(final NiFiProperties properties) {
     
                     // initialize the client
                     if (HTTPS.equalsIgnoreCase(validationAuthorityURI.getScheme())) {
    -                    client = WebUtils.createClient(clientConfig, SslContextFactory.createSslContext(properties));
    +                    TlsConfiguration tlsConfiguration = TlsConfiguration.fromNiFiProperties(properties);
    +                    client = WebUtils.createClient(clientConfig, SslContextFactory.createSslContext(tlsConfiguration));
                     } else {
                         client = WebUtils.createClient(clientConfig);
                     }
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/x509/ocsp/OcspCertificateValidatorGroovyTest.groovy+87 87 modified
    @@ -22,10 +22,10 @@ import com.google.common.cache.LoadingCache
     import org.apache.nifi.util.NiFiProperties
     import org.bouncycastle.asn1.x500.X500Name
     import org.bouncycastle.asn1.x509.ExtendedKeyUsage
    +import org.bouncycastle.asn1.x509.Extension
     import org.bouncycastle.asn1.x509.KeyPurposeId
     import org.bouncycastle.asn1.x509.KeyUsage
     import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
    -import org.bouncycastle.asn1.x509.Extension
     import org.bouncycastle.cert.X509CertificateHolder
     import org.bouncycastle.cert.X509v3CertificateBuilder
     import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
    @@ -57,31 +57,31 @@ import java.security.cert.X509Certificate
     import static groovy.test.GroovyAssert.shouldFail
     import static org.junit.Assert.fail
     
    -public class OcspCertificateValidatorGroovyTest {
    -    private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidatorGroovyTest.class);
    +class OcspCertificateValidatorGroovyTest {
    +    private static final Logger logger = LoggerFactory.getLogger(OcspCertificateValidatorGroovyTest.class)
     
    -    private static final int KEY_SIZE = 2048;
    +    private static final int KEY_SIZE = 2048
     
    -    private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000;
    -    private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000;
    -    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
    -    private static final String PROVIDER = "BC";
    +    private static final long YESTERDAY = System.currentTimeMillis() - 24 * 60 * 60 * 1000
    +    private static final long ONE_YEAR_FROM_NOW = System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000
    +    private static final String SIGNATURE_ALGORITHM = "SHA256withRSA"
    +    private static final String PROVIDER = "BC"
     
    -    private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US";
    -    private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US";
    +    private static final String SUBJECT_DN = "CN=NiFi Test Server,OU=Security,O=Apache,ST=CA,C=US"
    +    private static final String ISSUER_DN = "CN=NiFi Test CA,OU=Security,O=Apache,ST=CA,C=US"
     
         private NiFiProperties mockProperties
     
         // System under test
         OcspCertificateValidator certificateValidator
     
         @BeforeClass
    -    public static void setUpOnce() throws Exception {
    -        Security.addProvider(new BouncyCastleProvider());
    +    static void setUpOnce() throws Exception {
    +        Security.addProvider(new BouncyCastleProvider())
         }
     
         @Before
    -    public void setUp() throws Exception {
    +    void setUp() throws Exception {
             mockProperties = new NiFiProperties() {
                 @Override
                 String getProperty(String key) {
    @@ -96,7 +96,7 @@ public class OcspCertificateValidatorGroovyTest {
         }
     
         @After
    -    public void tearDown() throws Exception {
    +    void tearDown() throws Exception {
             certificateValidator?.metaClass = null
         }
     
    @@ -107,9 +107,9 @@ public class OcspCertificateValidatorGroovyTest {
          * @throws NoSuchAlgorithmException if the RSA algorithm is not available
          */
         private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
    -        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    -        keyPairGenerator.initialize(KEY_SIZE);
    -        return keyPairGenerator.generateKeyPair();
    +        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA")
    +        keyPairGenerator.initialize(KEY_SIZE)
    +        return keyPairGenerator.generateKeyPair()
         }
     
         /**
    @@ -127,8 +127,8 @@ public class OcspCertificateValidatorGroovyTest {
          */
         private
         static X509Certificate generateCertificate(String dn) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    -        KeyPair keyPair = generateKeyPair();
    -        return generateCertificate(dn, keyPair);
    +        KeyPair keyPair = generateKeyPair()
    +        return generateCertificate(dn, keyPair)
         }
     
         /**
    @@ -147,34 +147,34 @@ public class OcspCertificateValidatorGroovyTest {
          */
         private
         static X509Certificate generateCertificate(String dn, KeyPair keyPair) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    -        PrivateKey privateKey = keyPair.getPrivate();
    -        ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey);
    -        SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded());
    -        Date startDate = new Date(YESTERDAY);
    -        Date endDate = new Date(ONE_YEAR_FROM_NOW);
    +        PrivateKey privateKey = keyPair.getPrivate()
    +        ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(privateKey)
    +        SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded())
    +        Date startDate = new Date(YESTERDAY)
    +        Date endDate = new Date(ONE_YEAR_FROM_NOW)
     
             X509v3CertificateBuilder certBuilder = new X509v3CertificateBuilder(
                     new X500Name(dn),
                     BigInteger.valueOf(System.currentTimeMillis()),
                     startDate, endDate,
                     new X500Name(dn),
    -                subPubKeyInfo);
    +                subPubKeyInfo)
     
             // Set certificate extensions
             // (1) digitalSignature extension
             certBuilder.addExtension(Extension.keyUsage, true,
    -                new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement));
    +                new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment | KeyUsage.dataEncipherment | KeyUsage.keyAgreement))
     
             // (2) extendedKeyUsage extension
    -        Vector<KeyPurposeId> ekUsages = new Vector<>();
    -        ekUsages.add(KeyPurposeId.id_kp_clientAuth);
    -        ekUsages.add(KeyPurposeId.id_kp_serverAuth);
    -        certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages));
    +        Vector<KeyPurposeId> ekUsages = new Vector<>()
    +        ekUsages.add(KeyPurposeId.id_kp_clientAuth)
    +        ekUsages.add(KeyPurposeId.id_kp_serverAuth)
    +        certBuilder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(ekUsages))
     
             // Sign the certificate
    -        X509CertificateHolder certificateHolder = certBuilder.build(sigGen);
    +        X509CertificateHolder certificateHolder = certBuilder.build(sigGen)
             return new JcaX509CertificateConverter().setProvider(PROVIDER)
    -                .getCertificate(certificateHolder);
    +                .getCertificate(certificateHolder)
         }
     
         /**
    @@ -194,8 +194,8 @@ public class OcspCertificateValidatorGroovyTest {
          */
         private
         static X509Certificate generateIssuedCertificate(String dn, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    -        KeyPair keyPair = generateKeyPair();
    -        return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey);
    +        KeyPair keyPair = generateKeyPair()
    +        return generateIssuedCertificate(dn, keyPair.getPublic(), issuerDn, issuerKey)
         }
     
         /**
    @@ -216,98 +216,98 @@ public class OcspCertificateValidatorGroovyTest {
          */
         private
         static X509Certificate generateIssuedCertificate(String dn, PublicKey publicKey, String issuerDn, PrivateKey issuerKey) throws IOException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, SignatureException, InvalidKeyException, OperatorCreationException {
    -        ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey);
    -        SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
    -        Date startDate = new Date(YESTERDAY);
    -        Date endDate = new Date(ONE_YEAR_FROM_NOW);
    +        ContentSigner sigGen = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM).setProvider(PROVIDER).build(issuerKey)
    +        SubjectPublicKeyInfo subPubKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded())
    +        Date startDate = new Date(YESTERDAY)
    +        Date endDate = new Date(ONE_YEAR_FROM_NOW)
     
             X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
                     new X500Name(issuerDn),
                     BigInteger.valueOf(System.currentTimeMillis()),
                     startDate, endDate,
                     new X500Name(dn),
    -                subPubKeyInfo);
    +                subPubKeyInfo)
     
    -        X509CertificateHolder certificateHolder = v3CertGen.build(sigGen);
    +        X509CertificateHolder certificateHolder = v3CertGen.build(sigGen)
             return new JcaX509CertificateConverter().setProvider(PROVIDER)
    -                .getCertificate(certificateHolder);
    +                .getCertificate(certificateHolder)
         }
     
         private static X509Certificate[] generateCertificateChain(String dn = SUBJECT_DN, String issuerDn = ISSUER_DN) {
    -        final KeyPair issuerKeyPair = generateKeyPair();
    -        final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
    +        final KeyPair issuerKeyPair = generateKeyPair()
    +        final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate()
     
    -        final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
    -        final X509Certificate certificate = generateIssuedCertificate(dn, issuerDn, issuerPrivateKey);
    +        final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair)
    +        final X509Certificate certificate = generateIssuedCertificate(dn, issuerDn, issuerPrivateKey)
             [certificate, issuerCertificate] as X509Certificate[]
         }
     
         @Test
    -    public void testShouldGenerateCertificate() throws Exception {
    +    void testShouldGenerateCertificate() throws Exception {
             // Arrange
    -        final String testDn = "CN=This is a test";
    +        final String testDn = "CN=This is a test"
     
             // Act
    -        X509Certificate certificate = generateCertificate(testDn);
    -        logger.info("Generated certificate: \n{}", certificate);
    +        X509Certificate certificate = generateCertificate(testDn)
    +        logger.info("Generated certificate: \n{}", certificate)
     
             // Assert
    -        assert certificate.getSubjectDN().getName().equals(testDn);
    -        assert certificate.getIssuerDN().getName().equals(testDn);
    -        certificate.verify(certificate.getPublicKey());
    +        assert certificate.getSubjectDN().getName() == testDn
    +        assert certificate.getIssuerDN().getName() == testDn
    +        certificate.verify(certificate.getPublicKey())
         }
     
         @Test
    -    public void testShouldGenerateCertificateFromKeyPair() throws Exception {
    +    void testShouldGenerateCertificateFromKeyPair() throws Exception {
             // Arrange
    -        final String testDn = "CN=This is a test";
    -        final KeyPair keyPair = generateKeyPair();
    +        final String testDn = "CN=This is a test"
    +        final KeyPair keyPair = generateKeyPair()
     
             // Act
    -        X509Certificate certificate = generateCertificate(testDn, keyPair);
    -        logger.info("Generated certificate: \n{}", certificate);
    +        X509Certificate certificate = generateCertificate(testDn, keyPair)
    +        logger.info("Generated certificate: \n{}", certificate)
     
             // Assert
    -        assert certificate.getPublicKey().equals(keyPair.getPublic());
    -        assert certificate.getSubjectDN().getName().equals(testDn);
    -        assert certificate.getIssuerDN().getName().equals(testDn);
    -        certificate.verify(certificate.getPublicKey());
    +        assert certificate.getPublicKey() == keyPair.getPublic()
    +        assert certificate.getSubjectDN().getName() == testDn
    +        assert certificate.getIssuerDN().getName() == testDn
    +        certificate.verify(certificate.getPublicKey())
         }
     
         @Test
    -    public void testShouldGenerateIssuedCertificate() throws Exception {
    +    void testShouldGenerateIssuedCertificate() throws Exception {
             // Arrange
    -        final String testDn = "CN=This is a signed test";
    -        final String issuerDn = "CN=Issuer CA";
    -        final KeyPair issuerKeyPair = generateKeyPair();
    -        final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate();
    +        final String testDn = "CN=This is a signed test"
    +        final String issuerDn = "CN=Issuer CA"
    +        final KeyPair issuerKeyPair = generateKeyPair()
    +        final PrivateKey issuerPrivateKey = issuerKeyPair.getPrivate()
     
    -        final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair);
    -        logger.info("Generated issuer certificate: \n{}", issuerCertificate);
    +        final X509Certificate issuerCertificate = generateCertificate(issuerDn, issuerKeyPair)
    +        logger.info("Generated issuer certificate: \n{}", issuerCertificate)
     
             // Act
    -        X509Certificate certificate = generateIssuedCertificate(testDn, issuerDn, issuerPrivateKey);
    -        logger.info("Generated signed certificate: \n{}", certificate);
    +        X509Certificate certificate = generateIssuedCertificate(testDn, issuerDn, issuerPrivateKey)
    +        logger.info("Generated signed certificate: \n{}", certificate)
     
             // Assert
    -        assert issuerCertificate.getPublicKey().equals(issuerKeyPair.getPublic());
    -        assert certificate.getSubjectX500Principal().getName().equals(testDn);
    -        assert certificate.getIssuerX500Principal().getName().equals(issuerDn);
    -        certificate.verify(issuerCertificate.getPublicKey());
    +        assert issuerCertificate.getPublicKey() == issuerKeyPair.getPublic()
    +        assert certificate.getSubjectX500Principal().getName() == testDn
    +        assert certificate.getIssuerX500Principal().getName() == issuerDn
    +        certificate.verify(issuerCertificate.getPublicKey())
     
             try {
    -            certificate.verify(certificate.getPublicKey());
    -            fail("Should have thrown exception");
    +            certificate.verify(certificate.getPublicKey())
    +            fail("Should have thrown exception")
             } catch (Exception e) {
    -            assert e instanceof SignatureException;
    -            assert e.getMessage().contains("certificate does not verify with supplied key");
    +            assert e instanceof SignatureException
    +            assert e.getMessage().contains("certificate does not verify with supplied key")
             }
         }
     
         @Test
    -    public void testShouldValidateCertificate() throws Exception {
    +    void testShouldValidateCertificate() throws Exception {
             // Arrange
    -        X509Certificate[] certificateChain = generateCertificateChain();
    +        X509Certificate[] certificateChain = generateCertificateChain()
     
             certificateValidator = new OcspCertificateValidator(mockProperties)
     
    @@ -333,14 +333,14 @@ public class OcspCertificateValidatorGroovyTest {
         // TODO - NIFI-1364
         @Ignore("To be implemented with Groovy test")
         @Test
    -    public void testShouldNotValidateEmptyCertificate() throws Exception {
    +    void testShouldNotValidateEmptyCertificate() throws Exception {
     
         }
     
         @Test
    -    public void testShouldNotValidateRevokedCertificate() throws Exception {
    +    void testShouldNotValidateRevokedCertificate() throws Exception {
             // Arrange
    -        X509Certificate[] certificateChain = generateCertificateChain();
    +        X509Certificate[] certificateChain = generateCertificateChain()
     
             certificateValidator = new OcspCertificateValidator(mockProperties)
     
    @@ -368,24 +368,24 @@ public class OcspCertificateValidatorGroovyTest {
         LoadingCache<OcspRequest, OcspStatus> buildCacheWithContents(Map map) {
             CacheBuilder.newBuilder().build(new CacheLoader<OcspRequest, OcspStatus>() {
                 @Override
    -            public OcspStatus load(OcspRequest ocspRequest) throws Exception {
    +            OcspStatus load(OcspRequest ocspRequest) throws Exception {
                     logger.info("Mock cache implementation load(${ocspRequest}) returns ${map.get(ocspRequest)}")
                     return map.get(ocspRequest) as OcspStatus
                 }
    -        });
    +        })
         }
     
         // TODO - NIFI-1364
         @Ignore("To be implemented with Groovy test")
         @Test
    -    public void testValidateShouldHandleUnsignedResponse() throws Exception {
    +    void testValidateShouldHandleUnsignedResponse() throws Exception {
     
         }
     
         // TODO - NIFI-1364
         @Ignore("To be implemented with Groovy test")
         @Test
    -    public void testValidateShouldHandleResponseWithIncorrectNonce() throws Exception {
    +    void testValidateShouldHandleResponseWithIncorrectNonce() throws Exception {
     
         }
     }
    \ No newline at end of file
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/x509/X509AuthenticationProviderTest.java+2 2 modified
    @@ -95,7 +95,7 @@ public void setup() {
                 return AuthorizationResult.approved();
             });
     
    -        x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, NiFiProperties.createBasicNiFiProperties(null, null));
    +        x509AuthenticationProvider = new X509AuthenticationProvider(certificateIdentityProvider, authorizer, NiFiProperties.createBasicNiFiProperties(null));
         }
     
         @Test(expected = InvalidAuthenticationException.class)
    @@ -322,4 +322,4 @@ private X509Certificate getX509Certificate(final String identity) {
             return certificate;
         }
     
    -}
    +}
    \ No newline at end of file
    
  • nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml+0 1 modified
    @@ -27,7 +27,6 @@
             <module>nifi-client-dto</module>
             <module>nifi-nar-utils</module>
             <module>nifi-runtime</module>
    -        <module>nifi-security</module>
             <module>nifi-site-to-site</module>
             <module>nifi-repository-models</module>
             <module>nifi-flowfile-repo-serialization</module>
    
  • nifi-nar-bundles/nifi-grpc-bundle/nifi-grpc-processors/src/main/java/org/apache/nifi/processors/grpc/InvokeGRPC.java+23 26 modified
    @@ -17,7 +17,27 @@
     package org.apache.nifi.processors.grpc;
     
     import com.google.protobuf.ByteString;
    -
    +import io.grpc.CompressorRegistry;
    +import io.grpc.DecompressorRegistry;
    +import io.grpc.ManagedChannel;
    +import io.grpc.netty.GrpcSslContexts;
    +import io.grpc.netty.NettyChannelBuilder;
    +import io.netty.handler.ssl.SslContextBuilder;
    +import java.io.FileInputStream;
    +import java.io.InputStream;
    +import java.net.InetAddress;
    +import java.net.UnknownHostException;
    +import java.security.KeyStore;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.net.ssl.KeyManagerFactory;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.TrustManagerFactory;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.EventDriven;
     import org.apache.nifi.annotation.behavior.InputRequirement;
    @@ -38,32 +58,9 @@
     import org.apache.nifi.processor.Relationship;
     import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.processor.util.StandardValidators;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import java.io.FileInputStream;
    -import java.io.InputStream;
    -import java.net.InetAddress;
    -import java.net.UnknownHostException;
    -import java.security.KeyStore;
    -import java.util.Arrays;
    -import java.util.Collections;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Set;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManagerFactory;
    -
    -import io.grpc.CompressorRegistry;
    -import io.grpc.DecompressorRegistry;
    -import io.grpc.ManagedChannel;
    -import io.grpc.netty.GrpcSslContexts;
    -import io.grpc.netty.NettyChannelBuilder;
    -import io.netty.handler.ssl.SslContextBuilder;
    -
     @EventDriven
     @SupportsBatching
     @Tags({"grpc", "rpc", "client"})
    @@ -243,7 +240,7 @@ public void initializeClient(final ProcessContext context) throws Exception {
             // configure whether or not we're using secure comms
             final boolean useSecure = context.getProperty(PROP_USE_SECURE).asBoolean();
             final SSLContextService sslContextService = context.getProperty(PROP_SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
    -        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE);
    +        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE);
     
             if (useSecure && sslContext != null) {
                 SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
    
  • nifi-nar-bundles/nifi-grpc-bundle/nifi-grpc-processors/src/main/java/org/apache/nifi/processors/grpc/ListenGRPC.java+28 31 modified
    @@ -17,7 +17,32 @@
     package org.apache.nifi.processors.grpc;
     
     import com.google.common.collect.Sets;
    -
    +import io.grpc.CompressorRegistry;
    +import io.grpc.DecompressorRegistry;
    +import io.grpc.Server;
    +import io.grpc.ServerInterceptors;
    +import io.grpc.netty.GrpcSslContexts;
    +import io.grpc.netty.NettyServerBuilder;
    +import io.netty.handler.ssl.ClientAuth;
    +import io.netty.handler.ssl.SslContextBuilder;
    +import java.io.FileInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.security.KeyStore;
    +import java.security.KeyStoreException;
    +import java.security.NoSuchAlgorithmException;
    +import java.security.UnrecoverableKeyException;
    +import java.security.cert.CertificateException;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import java.util.regex.Pattern;
    +import javax.net.ssl.KeyManagerFactory;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.TrustManagerFactory;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.WritesAttribute;
    @@ -35,38 +60,10 @@
     import org.apache.nifi.processor.Relationship;
     import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.processor.util.StandardValidators;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.security.KeyStore;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.Arrays;
    -import java.util.Collections;
    -import java.util.List;
    -import java.util.Set;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -import java.util.regex.Pattern;
    -
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManagerFactory;
    -
    -import io.grpc.CompressorRegistry;
    -import io.grpc.DecompressorRegistry;
    -import io.grpc.Server;
    -import io.grpc.ServerInterceptors;
    -import io.grpc.netty.GrpcSslContexts;
    -import io.grpc.netty.NettyServerBuilder;
    -import io.netty.handler.ssl.ClientAuth;
    -import io.netty.handler.ssl.SslContextBuilder;
    -
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @CapabilityDescription("Starts a gRPC server and listens on the given port to transform the incoming messages into FlowFiles." +
             " The message format is defined by the standard gRPC protobuf IDL provided by NiFi. gRPC isn't intended to carry large payloads," +
    @@ -174,7 +171,7 @@ public void startServer(final ProcessContext context) throws NoSuchAlgorithmExce
             final Integer flowControlWindow = context.getProperty(PROP_FLOW_CONTROL_WINDOW).asDataSize(DataUnit.B).intValue();
             final Integer maxMessageSize = context.getProperty(PROP_MAX_MESSAGE_SIZE).asDataSize(DataUnit.B).intValue();
             final SSLContextService sslContextService = context.getProperty(PROP_SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
    -        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE);
    +        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE);
             final Pattern authorizedDnPattern = Pattern.compile(context.getProperty(PROP_AUTHORIZED_DN_PATTERN).getValue());
             final FlowFileIngestServiceInterceptor callInterceptor = new FlowFileIngestServiceInterceptor(getLogger());
             callInterceptor.enforceDNPattern(authorizedDnPattern);
    
  • nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JMSConnectionFactoryHandler.java+12 13 modified
    @@ -16,24 +16,23 @@
      */
     package org.apache.nifi.jms.cf;
     
    +import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_BROKER_URI;
    +import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_CONNECTION_FACTORY_IMPL;
    +import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_SSL_CONTEXT_SERVICE;
    +
    +import java.lang.reflect.Method;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.Set;
    +import javax.jms.ConnectionFactory;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.components.PropertyDescriptor;
     import org.apache.nifi.context.PropertyContext;
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.logging.ComponentLog;
     import org.apache.nifi.processor.ProcessContext;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
    -
    -import javax.jms.ConnectionFactory;
    -import javax.net.ssl.SSLContext;
    -import java.lang.reflect.Method;
    -import java.util.ArrayList;
    -import java.util.List;
    -import java.util.Set;
    -
    -import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_BROKER_URI;
    -import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_CONNECTION_FACTORY_IMPL;
    -import static org.apache.nifi.jms.cf.JMSConnectionFactoryProperties.JMS_SSL_CONTEXT_SERVICE;
     
     /**
      * Handler class to create a JMS Connection Factory by instantiating the vendor specific javax.jms.ConnectionFactory
    @@ -241,7 +240,7 @@ void setProperty(String propertyName, Object propertyValue) {
          * 'queueManager' property will correspond to setQueueManager method name
          */
         private String toMethodName(String propertyName) {
    -        char c[] = propertyName.toCharArray();
    +        char[] c = propertyName.toCharArray();
             c[0] = Character.toUpperCase(c[0]);
             return "set" + new String(c);
         }
    
  • nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/LdapProvider.java+18 51 modified
    @@ -16,6 +16,11 @@
      */
     package org.apache.nifi.ldap;
     
    +import java.util.HashMap;
    +import java.util.Map;
    +import java.util.concurrent.TimeUnit;
    +import javax.naming.Context;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.authentication.AuthenticationResponse;
     import org.apache.nifi.authentication.LoginCredentials;
    @@ -26,8 +31,11 @@
     import org.apache.nifi.authentication.exception.InvalidLoginCredentialsException;
     import org.apache.nifi.authentication.exception.ProviderCreationException;
     import org.apache.nifi.authentication.exception.ProviderDestructionException;
    +import org.apache.nifi.configuration.NonComponentConfigurationContext;
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.util.FormatUtils;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
    @@ -47,18 +55,6 @@
     import org.springframework.security.ldap.search.LdapUserSearch;
     import org.springframework.security.ldap.userdetails.LdapUserDetails;
     
    -import javax.naming.Context;
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.HashMap;
    -import java.util.Map;
    -import java.util.concurrent.TimeUnit;
    -
     /**
      * Abstract LDAP based implementation of a login identity provider.
      */
    @@ -240,62 +236,33 @@ private void setTimeout(final LoginIdentityProviderConfigurationContext configur
             final String rawTimeout = configurationContext.getProperty(configurationProperty);
             if (StringUtils.isNotBlank(rawTimeout)) {
                 try {
    -                final Long timeout = FormatUtils.getTimeDuration(rawTimeout, TimeUnit.MILLISECONDS);
    -                baseEnvironment.put(environmentKey, timeout.toString());
    +                final long timeout = (long) FormatUtils.getPreciseTimeDuration(rawTimeout, TimeUnit.MILLISECONDS);
    +                baseEnvironment.put(environmentKey, timeout);
                 } catch (final IllegalArgumentException iae) {
                     throw new ProviderCreationException(String.format("The %s '%s' is not a valid time duration", configurationProperty, rawTimeout));
                 }
             }
         }
     
    -    private SSLContext getConfiguredSslContext(final LoginIdentityProviderConfigurationContext configurationContext) {
    +    public static SSLContext getConfiguredSslContext(final NonComponentConfigurationContext configurationContext) {
             final String rawKeystore = configurationContext.getProperty("TLS - Keystore");
             final String rawKeystorePassword = configurationContext.getProperty("TLS - Keystore Password");
    +        // TODO: Should support different key password
             final String rawKeystoreType = configurationContext.getProperty("TLS - Keystore Type");
             final String rawTruststore = configurationContext.getProperty("TLS - Truststore");
             final String rawTruststorePassword = configurationContext.getProperty("TLS - Truststore Password");
             final String rawTruststoreType = configurationContext.getProperty("TLS - Truststore Type");
             final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth");
             final String rawProtocol = configurationContext.getProperty("TLS - Protocol");
     
    -        // create the ssl context
    -        final SSLContext sslContext;
             try {
    -            if (StringUtils.isBlank(rawKeystore) && StringUtils.isBlank(rawTruststore)) {
    -                sslContext = null;
    -            } else {
    -                // ensure the protocol is specified
    -                if (StringUtils.isBlank(rawProtocol)) {
    -                    throw new ProviderCreationException("TLS - Protocol must be specified.");
    -                }
    -
    -                if (StringUtils.isBlank(rawKeystore)) {
    -                    sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol);
    -                } else if (StringUtils.isBlank(rawTruststore)) {
    -                    sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol);
    -                } else {
    -                    // determine the client auth if specified
    -                    final ClientAuth clientAuth;
    -                    if (StringUtils.isBlank(rawClientAuth)) {
    -                        clientAuth = ClientAuth.NONE;
    -                    } else {
    -                        try {
    -                            clientAuth = ClientAuth.valueOf(rawClientAuth);
    -                        } catch (final IllegalArgumentException iae) {
    -                            throw new ProviderCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
    -                                    rawClientAuth, StringUtils.join(ClientAuth.values(), ", ")));
    -                        }
    -                    }
    -
    -                    sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType,
    -                            rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol);
    -                }
    -            }
    -        } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) {
    -            throw new ProviderCreationException(e.getMessage(), e);
    +            TlsConfiguration tlsConfiguration = new TlsConfiguration(rawKeystore, rawKeystorePassword, null, rawKeystoreType, rawTruststore, rawTruststorePassword, rawTruststoreType, rawProtocol);
    +            ClientAuth clientAuth = ClientAuth.isValidClientAuthType(rawClientAuth) ? ClientAuth.valueOf(rawClientAuth) : ClientAuth.NONE;
    +            return SslContextFactory.createSslContext(tlsConfiguration, clientAuth);
    +        } catch (TlsException e) {
    +            logger.error("Encountered an error configuring TLS for LDAP identity provider: {}", e.getLocalizedMessage());
    +            throw new ProviderCreationException("Error configuring TLS for LDAP identity provider", e);
             }
    -
    -        return sslContext;
         }
     
         @Override
    
  • nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java+27 61 modified
    @@ -16,7 +16,26 @@
      */
     package org.apache.nifi.ldap.tenants;
     
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.Executors;
    +import java.util.concurrent.ScheduledExecutorService;
    +import java.util.concurrent.ThreadFactory;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import javax.naming.Context;
    +import javax.naming.NamingEnumeration;
    +import javax.naming.NamingException;
    +import javax.naming.directory.Attribute;
    +import javax.naming.directory.SearchControls;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
    +import org.apache.nifi.authentication.exception.ProviderCreationException;
     import org.apache.nifi.authentication.exception.ProviderDestructionException;
     import org.apache.nifi.authorization.AuthorizerConfigurationContext;
     import org.apache.nifi.authorization.Group;
    @@ -35,6 +54,8 @@
     import org.apache.nifi.ldap.ReferralStrategy;
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.util.FormatUtils;
     import org.apache.nifi.util.NiFiProperties;
     import org.slf4j.Logger;
    @@ -56,31 +77,6 @@
     import org.springframework.ldap.filter.EqualsFilter;
     import org.springframework.ldap.filter.HardcodedFilter;
     
    -import javax.naming.Context;
    -import javax.naming.NamingEnumeration;
    -import javax.naming.NamingException;
    -import javax.naming.directory.Attribute;
    -import javax.naming.directory.SearchControls;
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.ArrayList;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.Executors;
    -import java.util.concurrent.ScheduledExecutorService;
    -import java.util.concurrent.ThreadFactory;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -
     /**
      * Abstract LDAP based implementation of a login identity provider.
      */
    @@ -827,44 +823,14 @@ private SSLContext getConfiguredSslContext(final AuthorizerConfigurationContext
             final String rawClientAuth = configurationContext.getProperty("TLS - Client Auth").getValue();
             final String rawProtocol = configurationContext.getProperty("TLS - Protocol").getValue();
     
    -        // create the ssl context
    -        final SSLContext sslContext;
             try {
    -            if (StringUtils.isBlank(rawKeystore) && StringUtils.isBlank(rawTruststore)) {
    -                sslContext = null;
    -            } else {
    -                // ensure the protocol is specified
    -                if (StringUtils.isBlank(rawProtocol)) {
    -                    throw new AuthorizerCreationException("TLS - Protocol must be specified.");
    -                }
    -
    -                if (StringUtils.isBlank(rawKeystore)) {
    -                    sslContext = SslContextFactory.createTrustSslContext(rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, rawProtocol);
    -                } else if (StringUtils.isBlank(rawTruststore)) {
    -                    sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType, rawProtocol);
    -                } else {
    -                    // determine the client auth if specified
    -                    final ClientAuth clientAuth;
    -                    if (StringUtils.isBlank(rawClientAuth)) {
    -                        clientAuth = ClientAuth.NONE;
    -                    } else {
    -                        try {
    -                            clientAuth = ClientAuth.valueOf(rawClientAuth);
    -                        } catch (final IllegalArgumentException iae) {
    -                            throw new AuthorizerCreationException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
    -                                    rawClientAuth, StringUtils.join(ClientAuth.values(), ", ")));
    -                        }
    -                    }
    -
    -                    sslContext = SslContextFactory.createSslContext(rawKeystore, rawKeystorePassword.toCharArray(), rawKeystoreType,
    -                            rawTruststore, rawTruststorePassword.toCharArray(), rawTruststoreType, clientAuth, rawProtocol);
    -                }
    -            }
    -        } catch (final KeyStoreException | NoSuchAlgorithmException | CertificateException | UnrecoverableKeyException | KeyManagementException | IOException e) {
    -            throw new AuthorizerCreationException(e.getMessage(), e);
    +            TlsConfiguration tlsConfiguration = new TlsConfiguration(rawKeystore, rawKeystorePassword, null, rawKeystoreType, rawTruststore, rawTruststorePassword, rawTruststoreType, rawProtocol);
    +            ClientAuth clientAuth = ClientAuth.isValidClientAuthType(rawClientAuth) ? ClientAuth.valueOf(rawClientAuth) : ClientAuth.NONE;
    +            return SslContextFactory.createSslContext(tlsConfiguration, clientAuth);
    +        } catch (TlsException e) {
    +            logger.error("Encountered an error configuring TLS for LDAP user group provider: {}", e.getLocalizedMessage());
    +            throw new ProviderCreationException("Error configuring TLS for LDAP user group provider", e);
             }
    -
    -        return sslContext;
         }
     
     }
    
  • nifi-nar-bundles/nifi-lumberjack-bundle/nifi-lumberjack-processors/src/main/java/org/apache/nifi/processors/lumberjack/ListenLumberjack.java+3 5 modified
    @@ -16,6 +16,7 @@
      */
     package org.apache.nifi.processors.lumberjack;
     
    +import com.google.gson.Gson;
     import java.io.IOException;
     import java.nio.ByteBuffer;
     import java.nio.charset.Charset;
    @@ -26,9 +27,7 @@
     import java.util.List;
     import java.util.Map;
     import java.util.concurrent.BlockingQueue;
    -
     import javax.net.ssl.SSLContext;
    -
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.WritesAttribute;
     import org.apache.nifi.annotation.behavior.WritesAttributes;
    @@ -58,11 +57,10 @@
     import org.apache.nifi.processors.lumberjack.handler.LumberjackSocketChannelHandlerFactory;
     import org.apache.nifi.processors.lumberjack.response.LumberjackChannelResponse;
     import org.apache.nifi.processors.lumberjack.response.LumberjackResponse;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import com.google.gson.Gson;
    -
     @Deprecated
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @Tags({"listen", "lumberjack", "tcp", "logs"})
    @@ -143,7 +141,7 @@ protected ChannelDispatcher createDispatcher(final ProcessContext context, final
             SSLContext sslContext = null;
             final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslContextService != null) {
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
             }
     
             // if we decide to support SSL then get the context and pass it in here
    
  • nifi-nar-bundles/nifi-mongodb-bundle/nifi-mongodb-client-service-api/src/main/java/org/apache/nifi/mongodb/MongoDBClientService.java+2 1 modified
    @@ -24,6 +24,7 @@
     import org.apache.nifi.controller.ControllerService;
     import org.apache.nifi.expression.ExpressionLanguageScope;
     import org.apache.nifi.processor.util.StandardValidators;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.bson.Document;
     
    @@ -58,7 +59,7 @@ public interface MongoDBClientService extends ControllerService {
                         + "Possible values are REQUIRED, WANT, NONE. This property is only used when an SSL Context "
                         + "has been defined and enabled.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
                 .defaultValue("REQUIRED")
                 .build();
     
    
  • nifi-nar-bundles/nifi-mongodb-bundle/nifi-mongodb-processors/src/main/java/org/apache/nifi/processors/mongodb/AbstractMongoProcessor.java+16 17 modified
    @@ -26,6 +26,18 @@
     import com.mongodb.WriteConcern;
     import com.mongodb.client.MongoCollection;
     import com.mongodb.client.MongoDatabase;
    +import java.io.ByteArrayInputStream;
    +import java.io.IOException;
    +import java.io.UnsupportedEncodingException;
    +import java.text.DateFormat;
    +import java.text.SimpleDateFormat;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.Date;
    +import java.util.List;
    +import java.util.Map;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.lifecycle.OnScheduled;
     import org.apache.nifi.annotation.lifecycle.OnStopped;
    @@ -46,19 +58,6 @@
     import org.apache.nifi.ssl.SSLContextService;
     import org.bson.Document;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.ByteArrayInputStream;
    -import java.io.IOException;
    -import java.io.UnsupportedEncodingException;
    -import java.text.DateFormat;
    -import java.text.SimpleDateFormat;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.Date;
    -import java.util.List;
    -import java.util.Map;
    -
     public abstract class AbstractMongoProcessor extends AbstractProcessor {
         static final String WRITE_CONCERN_ACKNOWLEDGED = "ACKNOWLEDGED";
         static final String WRITE_CONCERN_UNACKNOWLEDGED = "UNACKNOWLEDGED";
    @@ -136,7 +135,7 @@ public abstract class AbstractMongoProcessor extends AbstractProcessor {
                         + "Possible values are REQUIRED, WANT, NONE. This property is only used when an SSL Context "
                         + "has been defined and enabled.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
                 .defaultValue("REQUIRED")
                 .build();
     
    @@ -246,12 +245,12 @@ public final void createClient(ProcessContext context) throws IOException {
             final SSLContext sslContext;
     
             if (sslService != null) {
    -            final SSLContextService.ClientAuth clientAuth;
    +            final SslContextFactory.ClientAuth clientAuth;
                 if (StringUtils.isBlank(rawClientAuth)) {
    -                clientAuth = SSLContextService.ClientAuth.REQUIRED;
    +                clientAuth = SslContextFactory.ClientAuth.REQUIRED;
                 } else {
                     try {
    -                    clientAuth = SSLContextService.ClientAuth.valueOf(rawClientAuth);
    +                    clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
                     } catch (final IllegalArgumentException iae) {
                         throw new IllegalStateException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
                                 rawClientAuth, StringUtils.join(SslContextFactory.ClientAuth.values(), ", ")));
    
  • nifi-nar-bundles/nifi-mongodb-bundle/nifi-mongodb-processors/src/test/java/org/apache/nifi/processors/mongodb/AbstractMongoProcessorTest.java+7 8 modified
    @@ -16,25 +16,24 @@
      */
     package org.apache.nifi.processors.mongodb;
     
    +import static org.junit.Assert.assertNotNull;
    +import static org.mockito.Mockito.any;
    +import static org.mockito.Mockito.mock;
    +import static org.mockito.Mockito.when;
    +
     import com.mongodb.MongoClientOptions;
     import com.mongodb.MongoClientOptions.Builder;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.processor.ProcessContext;
     import org.apache.nifi.processor.ProcessSession;
     import org.apache.nifi.processor.exception.ProcessException;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     import org.apache.nifi.util.TestRunner;
     import org.apache.nifi.util.TestRunners;
     import org.junit.Before;
     import org.junit.Test;
     
    -import javax.net.ssl.SSLContext;
    -
    -import static org.junit.Assert.assertNotNull;
    -import static org.mockito.Mockito.any;
    -import static org.mockito.Mockito.mock;
    -import static org.mockito.Mockito.when;
    -
     public class AbstractMongoProcessorTest {
     
         MockAbstractMongoProcessor processor;
    
  • nifi-nar-bundles/nifi-mongodb-bundle/nifi-mongodb-services/src/main/java/org/apache/nifi/mongodb/MongoDBControllerService.java+6 7 modified
    @@ -22,6 +22,9 @@
     import com.mongodb.MongoClientURI;
     import com.mongodb.WriteConcern;
     import com.mongodb.client.MongoDatabase;
    +import java.util.ArrayList;
    +import java.util.List;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
    @@ -34,10 +37,6 @@
     import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.util.ArrayList;
    -import java.util.List;
    -
     @Tags({"mongo", "mongodb", "service"})
     @CapabilityDescription(
             "Provides a controller service that configures a connection to MongoDB and provides access to that connection to " +
    @@ -75,12 +74,12 @@ protected final void createClient(ConfigurationContext context) {
             final SSLContext sslContext;
     
             if (sslService != null) {
    -            final SSLContextService.ClientAuth clientAuth;
    +            final SslContextFactory.ClientAuth clientAuth;
                 if (StringUtils.isBlank(rawClientAuth)) {
    -                clientAuth = SSLContextService.ClientAuth.REQUIRED;
    +                clientAuth = SslContextFactory.ClientAuth.REQUIRED;
                 } else {
                     try {
    -                    clientAuth = SSLContextService.ClientAuth.valueOf(rawClientAuth);
    +                    clientAuth = SslContextFactory.ClientAuth.valueOf(rawClientAuth);
                     } catch (final IllegalArgumentException iae) {
                         throw new IllegalStateException(String.format("Unrecognized client auth '%s'. Possible values are [%s]",
                                 rawClientAuth, StringUtils.join(SslContextFactory.ClientAuth.values(), ", ")));
    
  • nifi-nar-bundles/nifi-provenance-repository-bundle/nifi-volatile-provenance-repository/src/test/java/org/apache/nifi/provenance/TestVolatileProvenanceRepository.java+11 12 modified
    @@ -16,14 +16,7 @@
      */
     package org.apache.nifi.provenance;
     
    -import org.apache.nifi.authorization.user.NiFiUser;
    -import org.apache.nifi.flowfile.FlowFile;
    -import org.apache.nifi.provenance.search.Query;
    -import org.apache.nifi.provenance.search.QuerySubmission;
    -import org.apache.nifi.provenance.search.SearchTerms;
    -import org.apache.nifi.util.NiFiProperties;
    -import org.junit.BeforeClass;
    -import org.junit.Test;
    +import static org.junit.Assert.assertEquals;
     
     import java.io.IOException;
     import java.util.Collections;
    @@ -32,8 +25,14 @@
     import java.util.Map;
     import java.util.Set;
     import java.util.UUID;
    -
    -import static org.junit.Assert.assertEquals;
    +import org.apache.nifi.authorization.user.NiFiUser;
    +import org.apache.nifi.flowfile.FlowFile;
    +import org.apache.nifi.provenance.search.Query;
    +import org.apache.nifi.provenance.search.QuerySubmission;
    +import org.apache.nifi.provenance.search.SearchTerms;
    +import org.apache.nifi.util.NiFiProperties;
    +import org.junit.BeforeClass;
    +import org.junit.Test;
     
     public class TestVolatileProvenanceRepository {
     
    @@ -46,7 +45,7 @@ public static void setup() {
     
         @Test
         public void testAddAndGet() throws IOException, InterruptedException {
    -        repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null));
     
             final Map<String, String> attributes = new HashMap<>();
             attributes.put("abc", "xyz");
    @@ -79,7 +78,7 @@ public void testAddAndGet() throws IOException, InterruptedException {
     
         @Test
         public void testIndexAndCompressOnRolloverAndSubsequentSearchAsync() throws InterruptedException {
    -        repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null, null));
    +        repo = new VolatileProvenanceRepository(NiFiProperties.createBasicNiFiProperties(null));
     
             final String uuid = "00000000-0000-0000-0000-000000000000";
             final Map<String, String> attributes = new HashMap<>();
    
  • nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/AbstractSiteToSiteReportingTask.java+2 4 modified
    @@ -29,11 +29,9 @@
     import java.util.List;
     import java.util.Map;
     import java.util.function.Supplier;
    -
     import javax.json.JsonArray;
     import javax.json.JsonObjectBuilder;
     import javax.json.JsonValue;
    -
     import org.apache.nifi.annotation.lifecycle.OnStopped;
     import org.apache.nifi.components.PropertyDescriptor;
     import org.apache.nifi.flowfile.attributes.CoreAttributes;
    @@ -118,7 +116,7 @@ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
     
         public void setup(final ReportingContext reportContext) throws IOException {
             if (siteToSiteClient == null) {
    -            siteToSiteClient = SiteToSiteUtils.getClient(reportContext, getLogger());
    +            siteToSiteClient = SiteToSiteUtils.getClient(reportContext, getLogger(), null);
             }
         }
     
    @@ -269,7 +267,7 @@ public RecordSchema getSchema() throws MalformedRecordException {
                 return recordSchema;
             }
     
    -        private JsonNode getNextJsonNode() throws JsonParseException, IOException, MalformedRecordException {
    +        private JsonNode getNextJsonNode() throws IOException, MalformedRecordException {
                 if (!firstObjectConsumed) {
                     firstObjectConsumed = true;
                     return firstJsonNode;
    
  • nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/s2s/SiteToSiteUtils.java+12 6 modified
    @@ -16,10 +16,14 @@
      */
     package org.apache.nifi.reporting.s2s;
     
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.components.PropertyDescriptor;
     import org.apache.nifi.components.ValidationContext;
     import org.apache.nifi.components.ValidationResult;
     import org.apache.nifi.components.Validator;
    +import org.apache.nifi.components.state.StateManager;
    +import org.apache.nifi.context.PropertyContext;
     import org.apache.nifi.events.EventReporter;
     import org.apache.nifi.expression.ExpressionLanguageScope;
     import org.apache.nifi.logging.ComponentLog;
    @@ -29,13 +33,11 @@
     import org.apache.nifi.remote.protocol.http.HttpProxy;
     import org.apache.nifi.remote.util.SiteToSiteRestApiClient;
     import org.apache.nifi.reporting.ReportingContext;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.util.StringUtils;
     
    -import javax.net.ssl.SSLContext;
    -import java.util.concurrent.TimeUnit;
    -
     public class SiteToSiteUtils {
     
         public static final PropertyDescriptor DESTINATION_URL = new PropertyDescriptor.Builder()
    @@ -143,9 +145,9 @@ public class SiteToSiteUtils {
                 .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
                 .build();
     
    -    public static SiteToSiteClient getClient(ReportingContext reportContext, ComponentLog logger) {
    +    public static SiteToSiteClient getClient(PropertyContext reportContext, ComponentLog logger, StateManager stateManager) {
             final SSLContextService sslContextService = reportContext.getProperty(SiteToSiteUtils.SSL_CONTEXT).asControllerService(SSLContextService.class);
    -        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +        final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
             final EventReporter eventReporter = (EventReporter) (severity, category, message) -> {
                 switch (severity) {
                     case WARNING:
    @@ -165,6 +167,10 @@ public static SiteToSiteClient getClient(ReportingContext reportContext, Compone
                     : new HttpProxy(reportContext.getProperty(SiteToSiteUtils.HTTP_PROXY_HOSTNAME).getValue(), reportContext.getProperty(SiteToSiteUtils.HTTP_PROXY_PORT).asInteger(),
                     reportContext.getProperty(SiteToSiteUtils.HTTP_PROXY_USERNAME).getValue(), reportContext.getProperty(SiteToSiteUtils.HTTP_PROXY_PASSWORD).getValue());
     
    +        // If no state manager was provided and this context supports retrieving it, do so
    +        if (stateManager == null && reportContext instanceof ReportingContext) {
    +            stateManager = ((ReportingContext) reportContext).getStateManager();
    +        }
             return new SiteToSiteClient.Builder()
                     .urls(SiteToSiteRestApiClient.parseClusterUrls(destinationUrl))
                     .portName(reportContext.getProperty(SiteToSiteUtils.PORT_NAME).getValue())
    @@ -174,7 +180,7 @@ public static SiteToSiteClient getClient(ReportingContext reportContext, Compone
                     .timeout(reportContext.getProperty(SiteToSiteUtils.TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
                     .transportProtocol(mode)
                     .httpProxy(httpProxy)
    -                .stateManager(reportContext.getStateManager())
    +                .stateManager(stateManager)
                     .build();
         }
     
    
  • nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/sink/SiteToSiteReportingRecordSink.java+7 48 modified
    @@ -16,6 +16,12 @@
      */
     package org.apache.nifi.reporting.sink;
     
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +import java.util.Map;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
     import org.apache.nifi.annotation.lifecycle.OnDisabled;
    @@ -25,16 +31,12 @@
     import org.apache.nifi.controller.AbstractControllerService;
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.controller.ControllerServiceInitializationContext;
    -import org.apache.nifi.events.EventReporter;
     import org.apache.nifi.flowfile.attributes.CoreAttributes;
     import org.apache.nifi.logging.ComponentLog;
     import org.apache.nifi.record.sink.RecordSinkService;
     import org.apache.nifi.remote.Transaction;
     import org.apache.nifi.remote.TransferDirection;
     import org.apache.nifi.remote.client.SiteToSiteClient;
    -import org.apache.nifi.remote.protocol.SiteToSiteTransportProtocol;
    -import org.apache.nifi.remote.protocol.http.HttpProxy;
    -import org.apache.nifi.remote.util.SiteToSiteRestApiClient;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.reporting.s2s.SiteToSiteUtils;
     import org.apache.nifi.serialization.RecordSetWriter;
    @@ -43,17 +45,6 @@
     import org.apache.nifi.serialization.record.Record;
     import org.apache.nifi.serialization.record.RecordSchema;
     import org.apache.nifi.serialization.record.RecordSet;
    -import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.util.StringUtils;
    -
    -import javax.net.ssl.SSLContext;
    -import java.io.ByteArrayOutputStream;
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Collections;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.TimeUnit;
     
     @Tags({ "db", "s2s", "site", "record"})
     @CapabilityDescription("Provides a service to write records using a configured RecordSetWriter over a Site-to-Site connection.")
    @@ -92,40 +83,8 @@ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
         @OnEnabled
         public void onEnabled(final ConfigurationContext context) throws InitializationException {
             try {
    -            final SSLContextService sslContextService = context.getProperty(SiteToSiteUtils.SSL_CONTEXT).asControllerService(SSLContextService.class);
    -            final SSLContext sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
                 final ComponentLog logger = getLogger();
    -            final EventReporter eventReporter = (EventReporter) (severity, category, message) -> {
    -                switch (severity) {
    -                    case WARNING:
    -                        logger.warn(message);
    -                        break;
    -                    case ERROR:
    -                        logger.error(message);
    -                        break;
    -                    default:
    -                        break;
    -                }
    -            };
    -
    -            final String destinationUrl = context.getProperty(SiteToSiteUtils.DESTINATION_URL).evaluateAttributeExpressions().getValue();
    -
    -            final SiteToSiteTransportProtocol mode = SiteToSiteTransportProtocol.valueOf(context.getProperty(SiteToSiteUtils.TRANSPORT_PROTOCOL).getValue());
    -            final HttpProxy httpProxy = mode.equals(SiteToSiteTransportProtocol.RAW) || StringUtils.isEmpty(context.getProperty(SiteToSiteUtils.HTTP_PROXY_HOSTNAME).getValue()) ? null
    -                    : new HttpProxy(context.getProperty(SiteToSiteUtils.HTTP_PROXY_HOSTNAME).getValue(), context.getProperty(SiteToSiteUtils.HTTP_PROXY_PORT).asInteger(),
    -                    context.getProperty(SiteToSiteUtils.HTTP_PROXY_USERNAME).getValue(), context.getProperty(SiteToSiteUtils.HTTP_PROXY_PASSWORD).getValue());
    -
    -            siteToSiteClient = new SiteToSiteClient.Builder()
    -                    .urls(SiteToSiteRestApiClient.parseClusterUrls(destinationUrl))
    -                    .portName(context.getProperty(SiteToSiteUtils.PORT_NAME).getValue())
    -                    .useCompression(context.getProperty(SiteToSiteUtils.COMPRESS).asBoolean())
    -                    .eventReporter(eventReporter)
    -                    .sslContext(sslContext)
    -                    .stateManager(stateManager)
    -                    .timeout(context.getProperty(SiteToSiteUtils.TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS), TimeUnit.MILLISECONDS)
    -                    .transportProtocol(mode)
    -                    .httpProxy(httpProxy)
    -                    .build();
    +            siteToSiteClient = SiteToSiteUtils.getClient(context, logger, stateManager);
     
                 writerFactory = context.getProperty(RECORD_WRITER_FACTORY).asControllerService(RecordSetWriterFactory.class);
             } catch(Exception e) {
    
  • nifi-nar-bundles/nifi-solr-bundle/nifi-solr-processors/src/main/java/org/apache/nifi/processors/solr/SolrUtils.java+26 27 modified
    @@ -18,14 +18,33 @@
      */
    
     package org.apache.nifi.processors.solr;
    
     
    
    +import java.io.IOException;
    
    +import java.io.OutputStream;
    
    +import java.nio.charset.StandardCharsets;
    
    +import java.time.Instant;
    
    +import java.time.LocalDate;
    
    +import java.time.LocalDateTime;
    
    +import java.time.ZoneId;
    
    +import java.time.format.DateTimeFormatter;
    
    +import java.util.ArrayList;
    
    +import java.util.Arrays;
    
    +import java.util.HashMap;
    
    +import java.util.LinkedHashMap;
    
    +import java.util.List;
    
    +import java.util.Map;
    
    +import java.util.Optional;
    
    +import java.util.SortedMap;
    
    +import java.util.TreeMap;
    
    +import java.util.concurrent.TimeUnit;
    
    +import javax.net.ssl.SSLContext;
    
     import org.apache.commons.io.IOUtils;
    
     import org.apache.commons.lang3.StringUtils;
    
     import org.apache.http.client.HttpClient;
    
     import org.apache.http.config.Registry;
    
     import org.apache.http.config.RegistryBuilder;
    
    -import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    
     import org.apache.http.conn.socket.ConnectionSocketFactory;
    
     import org.apache.http.conn.socket.PlainConnectionSocketFactory;
    
    +import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    
     import org.apache.nifi.components.AllowableValue;
    
     import org.apache.nifi.components.PropertyDescriptor;
    
     import org.apache.nifi.context.PropertyContext;
    
    @@ -36,6 +55,7 @@
     import org.apache.nifi.processor.ProcessContext;
    
     import org.apache.nifi.processor.io.OutputStreamCallback;
    
     import org.apache.nifi.processor.util.StandardValidators;
    
    +import org.apache.nifi.security.util.SslContextFactory;
    
     import org.apache.nifi.serialization.RecordSetWriterFactory;
    
     import org.apache.nifi.serialization.record.DataType;
    
     import org.apache.nifi.serialization.record.ListRecordSet;
    
    @@ -61,27 +81,6 @@
     import org.slf4j.Logger;
    
     import org.slf4j.LoggerFactory;
    
     
    
    -import javax.net.ssl.SSLContext;
    
    -import java.io.IOException;
    
    -import java.io.OutputStream;
    
    -import java.math.BigInteger;
    
    -import java.nio.charset.StandardCharsets;
    
    -import java.time.Instant;
    
    -import java.time.LocalDate;
    
    -import java.time.LocalDateTime;
    
    -import java.time.ZoneId;
    
    -import java.time.format.DateTimeFormatter;
    
    -import java.util.ArrayList;
    
    -import java.util.Arrays;
    
    -import java.util.HashMap;
    
    -import java.util.LinkedHashMap;
    
    -import java.util.List;
    
    -import java.util.Map;
    
    -import java.util.Optional;
    
    -import java.util.SortedMap;
    
    -import java.util.TreeMap;
    
    -import java.util.concurrent.TimeUnit;
    
    -
    
     public class SolrUtils {
    
     
    
         static final Logger LOGGER = LoggerFactory.getLogger(SolrUtils.class);
    
    @@ -252,7 +251,7 @@ public static synchronized SolrClient createSolrClient(final PropertyContext con
             }
    
     
    
             if (sslContextService != null) {
    
    -            final SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    
    +            final SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
    
                 final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);
    
                 HttpClientUtil.setSchemaRegistryProvider(new HttpClientUtil.SchemaRegistryProvider() {
    
                     @Override
    
    @@ -277,7 +276,7 @@ public Registry<ConnectionSocketFactory> getSchemaRegistry() {
                 return new HttpSolrClient.Builder(solrLocation).withHttpClient(httpClient).build();
    
             } else {
    
                 // CloudSolrClient.Builder now requires a List of ZK addresses and znode for solr as separate parameters
    
    -            final String zk[] = solrLocation.split("/");
    
    +            final String[] zk = solrLocation.split("/");
    
                 final List zkList = Arrays.asList(zk[0].split(","));
    
                 String zkRoot = "/";
    
                 if (zk.length > 1 && ! zk[1].isEmpty()) {
    
    @@ -458,9 +457,9 @@ private static void writeValue(final SolrInputDocument inputDocument, final Obje
                     break;
    
                 case BIGINT:
    
                     if (coercedValue instanceof Long) {
    
    -                    addFieldToSolrDocument(inputDocument,fieldName,(Long) coercedValue,fieldsToIndex);
    
    +                    addFieldToSolrDocument(inputDocument,fieldName, coercedValue,fieldsToIndex);
    
                     } else {
    
    -                    addFieldToSolrDocument(inputDocument,fieldName,(BigInteger)coercedValue,fieldsToIndex);
    
    +                    addFieldToSolrDocument(inputDocument,fieldName, coercedValue,fieldsToIndex);
    
                     }
    
                     break;
    
                 case BOOLEAN:
    
    @@ -497,7 +496,7 @@ private static void writeValue(final SolrInputDocument inputDocument, final Obje
         }
    
     
    
         private static void addFieldToSolrDocument(SolrInputDocument inputDocument,String fieldName,Object fieldValue,List<String> fieldsToIndex){
    
    -        if ((!fieldsToIndex.isEmpty() && fieldsToIndex.contains(fieldName)) || fieldsToIndex.isEmpty()){
    
    +        if (fieldsToIndex.isEmpty() || fieldsToIndex.contains(fieldName)){
    
                 inputDocument.addField(fieldName, fieldValue);
    
             }
    
         }
    
    
  • nifi-nar-bundles/nifi-solr-bundle/nifi-solr-processors/src/test/java/org/apache/nifi/processors/solr/MockSSLContextService.java+91 0 added
    @@ -0,0 +1,91 @@
    +/*
    + * 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.processors.solr;
    +
    +import javax.net.ssl.SSLContext;
    +import org.apache.nifi.controller.AbstractControllerService;
    +import org.apache.nifi.processor.exception.ProcessException;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.ssl.SSLContextService;
    +
    +/**
    + * Mock implementation (copied from unit and integration tests) so we don't need to have a real keystore/truststore available for testing.
    + *
    + * // TODO: Remove and use regular mocking or Groovy rather than shell implementation
    + */
    +public class MockSSLContextService extends AbstractControllerService implements SSLContextService {
    +    @Override
    +    public TlsConfiguration createTlsConfiguration() {
    +        return null;
    +    }
    +
    +    @Override
    +    public SSLContext createSSLContext(SslContextFactory.ClientAuth clientAuth) throws ProcessException {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getTrustStoreFile() {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getTrustStoreType() {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getTrustStorePassword() {
    +        return null;
    +    }
    +
    +    @Override
    +    public boolean isTrustStoreConfigured() {
    +        return false;
    +    }
    +
    +    @Override
    +    public String getKeyStoreFile() {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getKeyStoreType() {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getKeyStorePassword() {
    +        return null;
    +    }
    +
    +    @Override
    +    public String getKeyPassword() {
    +        return null;
    +    }
    +
    +    @Override
    +    public boolean isKeyStoreConfigured() {
    +        return false;
    +    }
    +
    +    @Override
    +    public String getSslAlgorithm() {
    +        return null;
    +    }
    +}
    \ No newline at end of file
    
  • nifi-nar-bundles/nifi-solr-bundle/nifi-solr-processors/src/test/java/org/apache/nifi/processors/solr/QuerySolrIT.java+20 85 modified
    @@ -19,11 +19,28 @@
     
    
     package org.apache.nifi.processors.solr;
    
     
    
    +import static org.hamcrest.MatcherAssert.assertThat;
    
    +import static org.junit.Assert.assertEquals;
    
    +import static org.junit.Assert.assertFalse;
    
    +
    
     import com.google.gson.stream.JsonReader;
    
    -import org.apache.nifi.controller.AbstractControllerService;
    
    +import java.io.ByteArrayInputStream;
    
    +import java.io.IOException;
    
    +import java.io.InputStreamReader;
    
    +import java.nio.file.Files;
    
    +import java.nio.file.Path;
    
    +import java.nio.file.Paths;
    
    +import java.text.SimpleDateFormat;
    
    +import java.util.Collections;
    
    +import java.util.Date;
    
    +import java.util.HashMap;
    
    +import java.util.List;
    
    +import java.util.Locale;
    
    +import java.util.Map;
    
    +import java.util.Optional;
    
    +import java.util.TimeZone;
    
     import org.apache.nifi.json.JsonRecordSetWriter;
    
     import org.apache.nifi.processor.ProcessContext;
    
    -import org.apache.nifi.processor.exception.ProcessException;
    
     import org.apache.nifi.reporting.InitializationException;
    
     import org.apache.nifi.schema.access.SchemaAccessUtils;
    
     import org.apache.nifi.ssl.SSLContextService;
    
    @@ -42,27 +59,6 @@
     import org.mockito.Mockito;
    
     import org.xmlunit.matchers.CompareMatcher;
    
     
    
    -import javax.net.ssl.SSLContext;
    
    -import java.io.ByteArrayInputStream;
    
    -import java.io.IOException;
    
    -import java.io.InputStreamReader;
    
    -import java.nio.file.Files;
    
    -import java.nio.file.Path;
    
    -import java.nio.file.Paths;
    
    -import java.text.SimpleDateFormat;
    
    -import java.util.Collections;
    
    -import java.util.Date;
    
    -import java.util.HashMap;
    
    -import java.util.List;
    
    -import java.util.Locale;
    
    -import java.util.Map;
    
    -import java.util.Optional;
    
    -import java.util.TimeZone;
    
    -
    
    -import static org.hamcrest.MatcherAssert.assertThat;
    
    -import static org.junit.Assert.assertEquals;
    
    -import static org.junit.Assert.assertFalse;
    
    -
    
     public class QuerySolrIT {
    
         /*
    
     
    
    @@ -648,7 +644,7 @@ public void testSslContextService() throws IOException, InitializationException
     
    
             runner.setProperty(SolrUtils.SSL_CONTEXT_SERVICE, "ssl-context");
    
             proc.onScheduled(runner.getProcessContext());
    
    -        Mockito.verify(proc, Mockito.times(1)).createSolrClient(Mockito.any(ProcessContext.class), Mockito.eq((String)SOLR_LOCATION));
    
    +        Mockito.verify(proc, Mockito.times(1)).createSolrClient(Mockito.any(ProcessContext.class), Mockito.eq(SOLR_LOCATION));
    
     
    
         }
    
     
    
    @@ -664,65 +660,4 @@ protected SolrClient createSolrClient(ProcessContext context, String solrLocatio
                 return solrClient;
    
             }
    
         }
    
    -
    
    -    /**
    
    -     * Mock implementation so we don't need to have a real keystore/truststore available for testing.
    
    -     */
    
    -    private class MockSSLContextService extends AbstractControllerService implements SSLContextService {
    
    -
    
    -        @Override
    
    -        public SSLContext createSSLContext(ClientAuth clientAuth) throws ProcessException {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getTrustStoreFile() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getTrustStoreType() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getTrustStorePassword() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public boolean isTrustStoreConfigured() {
    
    -            return false;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getKeyStoreFile() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getKeyStoreType() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getKeyStorePassword() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getKeyPassword() {
    
    -            return null;
    
    -        }
    
    -
    
    -        @Override
    
    -        public boolean isKeyStoreConfigured() {
    
    -            return false;
    
    -        }
    
    -
    
    -        @Override
    
    -        public String getSslAlgorithm() {
    
    -            return null;
    
    -        }
    
    -    }
    
     }
    
    
  • nifi-nar-bundles/nifi-solr-bundle/nifi-solr-processors/src/test/java/org/apache/nifi/processors/solr/TestPutSolrContentStream.java+24 87 modified
    @@ -16,10 +16,26 @@
      */
     package org.apache.nifi.processors.solr;
     
    +import static org.mockito.Mockito.any;
    +import static org.mockito.Mockito.eq;
    +import static org.mockito.Mockito.times;
    +import static org.mockito.Mockito.verify;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.FileInputStream;
    +import java.io.IOException;
    +import java.nio.charset.StandardCharsets;
    +import java.security.PrivilegedActionException;
    +import java.security.PrivilegedExceptionAction;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.Date;
    +import java.util.HashMap;
    +import java.util.Map;
    +import javax.security.auth.login.LoginException;
     import org.apache.nifi.controller.AbstractControllerService;
     import org.apache.nifi.kerberos.KerberosCredentialsService;
     import org.apache.nifi.processor.ProcessContext;
    -import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.security.krb.KerberosKeytabUser;
     import org.apache.nifi.security.krb.KerberosUser;
    @@ -40,24 +56,6 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import javax.net.ssl.SSLContext;
    -import javax.security.auth.login.LoginException;
    -import java.io.FileInputStream;
    -import java.io.IOException;
    -import java.security.PrivilegedActionException;
    -import java.security.PrivilegedExceptionAction;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.Date;
    -import java.util.HashMap;
    -import java.util.Map;
    -
    -import static org.mockito.Mockito.any;
    -import static org.mockito.Mockito.eq;
    -import static org.mockito.Mockito.times;
    -import static org.mockito.Mockito.verify;
    -import static org.mockito.Mockito.when;
    -
     /**
      * Test for PutSolr processor.
      */
    @@ -239,7 +237,7 @@ public void testDeleteWithXml() throws IOException, SolrServerException {
             Assert.assertEquals(1, qResponse.getResults().getNumFound());
     
             // run the processor with a delete-by-query command
    -        runner.enqueue("<delete><query>first:bob</query></delete>".getBytes("UTF-8"));
    +        runner.enqueue("<delete><query>first:bob</query></delete>".getBytes(StandardCharsets.UTF_8));
             runner.run(1, false);
     
             // prove the document got deleted
    @@ -280,7 +278,7 @@ public void testSolrServerExceptionShouldRouteToFailure() throws IOException, So
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrContentStream.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }
         }
     
    @@ -296,7 +294,7 @@ public void testSolrServerExceptionCausedByIOExceptionShouldRouteToConnectionFai
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrContentStream.REL_CONNECTION_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }
         }
     
    @@ -312,7 +310,7 @@ public void testSolrExceptionShouldRouteToFailure() throws IOException, SolrServ
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrContentStream.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }
         }
     
    @@ -329,7 +327,7 @@ public void testRemoteSolrExceptionShouldRouteToFailure() throws IOException, So
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrContentStream.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }
         }
     
    @@ -345,7 +343,7 @@ public void testIOExceptionShouldRouteToConnectionFailure() throws IOException,
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrContentStream.REL_CONNECTION_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }
         }
     
    @@ -588,67 +586,6 @@ public String getPrincipal() {
             }
         }
     
    -    /**
    -     * Mock implementation so we don't need to have a real keystore/truststore available for testing.
    -     */
    -    private class MockSSLContextService extends AbstractControllerService implements SSLContextService {
    -
    -        @Override
    -        public SSLContext createSSLContext(ClientAuth clientAuth) throws ProcessException {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStoreFile() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStoreType() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStorePassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public boolean isTrustStoreConfigured() {
    -            return false;
    -        }
    -
    -        @Override
    -        public String getKeyStoreFile() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyStoreType() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyStorePassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyPassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public boolean isKeyStoreConfigured() {
    -            return false;
    -        }
    -
    -        @Override
    -        public String getSslAlgorithm() {
    -            return null;
    -        }
    -    }
    -
         // Override the createSolrClient method to inject a custom SolrClient.
         private class CollectionVerifyingProcessor extends PutSolrContentStream {
     
    @@ -695,7 +632,7 @@ protected SolrClient createSolrClient(ProcessContext context, String solrLocatio
                 mockSolrClient = Mockito.mock(SolrClient.class);
                 try {
                     when(mockSolrClient.request(any(SolrRequest.class),
    -                        eq((String)null))).thenThrow(throwable);
    +                        eq(null))).thenThrow(throwable);
                 } catch (SolrServerException e) {
                     Assert.fail(e.getMessage());
                 } catch (IOException e) {
    
  • nifi-nar-bundles/nifi-solr-bundle/nifi-solr-processors/src/test/java/org/apache/nifi/processors/solr/TestPutSolrRecord.java+21 89 modified
    @@ -18,9 +18,22 @@
      */
     package org.apache.nifi.processors.solr;
     
    -import org.apache.nifi.controller.AbstractControllerService;
    +import static org.mockito.ArgumentMatchers.any;
    +import static org.mockito.ArgumentMatchers.eq;
    +import static org.mockito.Mockito.times;
    +import static org.mockito.Mockito.verify;
    +import static org.mockito.Mockito.when;
    +
    +import java.io.IOException;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.stream.Collectors;
    +import java.util.stream.Stream;
     import org.apache.nifi.processor.ProcessContext;
    -import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.serialization.SimpleRecordSchema;
     import org.apache.nifi.serialization.record.MapRecord;
    @@ -45,23 +58,6 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.stream.Collectors;
    -import java.util.stream.Stream;
    -
    -import static org.mockito.ArgumentMatchers.any;
    -import static org.mockito.ArgumentMatchers.eq;
    -import static org.mockito.Mockito.times;
    -import static org.mockito.Mockito.verify;
    -import static org.mockito.Mockito.when;
    -
     /**
      * Test for PutSolrRecord Processor
      */
    @@ -363,7 +359,7 @@ public void testSolrServerExceptionShouldRouteToFailure() throws IOException, So
                 runner.run(1,false);
     
                 runner.assertAllFlowFilesTransferred(PutSolrRecord.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }finally {
                 try {
                     proc.getSolrClient().close();
    @@ -396,7 +392,7 @@ public void testSolrServerExceptionCausedByIOExceptionShouldRouteToConnectionFai
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrRecord.REL_CONNECTION_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }finally {
                 try {
                     proc.getSolrClient().close();
    @@ -428,7 +424,7 @@ public void testSolrExceptionShouldRouteToFailure() throws IOException, SolrServ
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrRecord.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }finally {
                 try {
                     proc.getSolrClient().close();
    @@ -461,7 +457,7 @@ public void testRemoteSolrExceptionShouldRouteToFailure() throws IOException, So
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrRecord.REL_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }finally {
                 try {
                     proc.getSolrClient().close();
    @@ -492,7 +488,7 @@ public void testIOExceptionShouldRouteToConnectionFailure() throws IOException,
                 runner.run();
     
                 runner.assertAllFlowFilesTransferred(PutSolrRecord.REL_CONNECTION_FAILURE, 1);
    -            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq((String)null));
    +            verify(proc.getSolrClient(), times(1)).request(any(SolrRequest.class), eq(null));
             }finally {
                 try {
                     proc.getSolrClient().close();
    @@ -721,76 +717,12 @@ protected SolrClient createSolrClient(ProcessContext context, String solrLocatio
                 mockSolrClient = Mockito.mock(SolrClient.class);
                 try {
                     when(mockSolrClient.request(any(SolrRequest.class),
    -                        eq((String)null))).thenThrow(throwable);
    +                        eq(null))).thenThrow(throwable);
                 } catch (SolrServerException|IOException e) {
                     Assert.fail(e.getMessage());
                 }
                 return mockSolrClient;
             }
     
         }
    -
    -
    -    /**
    -     * Mock implementation so we don't need to have a real keystore/truststore available for testing.
    -     */
    -    private class MockSSLContextService extends AbstractControllerService implements SSLContextService {
    -
    -        @Override
    -        public SSLContext createSSLContext(ClientAuth clientAuth) throws ProcessException {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStoreFile() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStoreType() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getTrustStorePassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public boolean isTrustStoreConfigured() {
    -            return false;
    -        }
    -
    -        @Override
    -        public String getKeyStoreFile() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyStoreType() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyStorePassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public String getKeyPassword() {
    -            return null;
    -        }
    -
    -        @Override
    -        public boolean isKeyStoreConfigured() {
    -            return false;
    -        }
    -
    -        @Override
    -        public String getSslAlgorithm() {
    -            return null;
    -        }
    -    }
    -
    -
     }
    
  • nifi-nar-bundles/nifi-spark-bundle/nifi-livy-controller-service/src/main/java/org/apache/nifi/controller/livy/LivySessionController.java+7 9 modified
    @@ -39,7 +39,9 @@
     import java.util.concurrent.ConcurrentHashMap;
     import java.util.concurrent.TimeUnit;
     import java.util.stream.Collectors;
    -
    +import javax.net.ssl.KeyManagerFactory;
    +import javax.net.ssl.SSLContext;
    +import javax.net.ssl.TrustManagerFactory;
     import org.apache.commons.io.IOUtils;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.http.HttpEntity;
    @@ -66,24 +68,20 @@
     import org.apache.nifi.controller.AbstractControllerService;
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.controller.ControllerServiceInitializationContext;
    +import org.apache.nifi.controller.api.livy.LivySessionService;
     import org.apache.nifi.controller.api.livy.exception.SessionManagerException;
    +import org.apache.nifi.expression.ExpressionLanguageScope;
     import org.apache.nifi.hadoop.KerberosKeytabCredentials;
     import org.apache.nifi.hadoop.KerberosKeytabSPNegoAuthSchemeProvider;
     import org.apache.nifi.kerberos.KerberosCredentialsService;
     import org.apache.nifi.logging.ComponentLog;
     import org.apache.nifi.processor.util.StandardValidators;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.codehaus.jackson.map.ObjectMapper;
     import org.codehaus.jettison.json.JSONException;
     import org.codehaus.jettison.json.JSONObject;
     
    -import org.apache.nifi.controller.api.livy.LivySessionService;
    -import org.apache.nifi.expression.ExpressionLanguageScope;
    -
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManagerFactory;
    -
     @Tags({"Livy", "REST", "Spark", "http"})
     @CapabilityDescription("Manages pool of Spark sessions over HTTP")
     public class LivySessionController extends AbstractControllerService implements LivySessionService {
    @@ -227,7 +225,7 @@ public void onConfigured(final ConfigurationContext context) {
             final String jars = context.getProperty(JARS).evaluateAttributeExpressions().getValue();
             final String files = context.getProperty(FILES).evaluateAttributeExpressions().getValue();
             sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
    -        sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE);
    +        sslContext = sslContextService == null ? null : sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE);
             connectTimeout = Math.toIntExact(context.getProperty(CONNECT_TIMEOUT).asTimePeriod(TimeUnit.MILLISECONDS));
             credentialsService = context.getProperty(KERBEROS_CREDENTIALS_SERVICE).asControllerService(KerberosCredentialsService.class);
     
    
  • nifi-nar-bundles/nifi-splunk-bundle/nifi-splunk-processors/src/main/java/org/apache/nifi/processors/splunk/PutSplunk.java+14 14 modified
    @@ -16,6 +16,18 @@
      */
     package org.apache.nifi.processors.splunk;
     
    +import java.io.BufferedInputStream;
    +import java.io.ByteArrayOutputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.List;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicLong;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
    @@ -33,24 +45,12 @@
     import org.apache.nifi.processor.io.InputStreamCallback;
     import org.apache.nifi.processor.util.put.AbstractPutEventProcessor;
     import org.apache.nifi.processor.util.put.sender.ChannelSender;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.stream.io.ByteCountingInputStream;
     import org.apache.nifi.stream.io.StreamUtils;
     import org.apache.nifi.stream.io.util.NonThreadSafeCircularBuffer;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.BufferedInputStream;
    -import java.io.ByteArrayOutputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.nio.charset.StandardCharsets;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.List;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicLong;
    -
     @InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
     @Tags({"splunk", "logs", "tcp", "udp"})
     @TriggerWhenEmpty // because we have a queue of sessions that are ready to be committed
    @@ -120,7 +120,7 @@ protected ChannelSender createSender(ProcessContext context) throws IOException
     
             SSLContext sslContext = null;
             if (sslContextService != null) {
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
             }
     
             return createSender(protocol, host, port, timeout, maxSendBuffer, sslContext);
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/pom.xml+6 0 modified
    @@ -384,6 +384,12 @@
                 <version>${nifi.groovy.version}</version>
                 <scope>test</scope>
             </dependency>
    +        <dependency>
    +            <groupId>org.apache.nifi</groupId>
    +            <artifactId>nifi-web-utils</artifactId>
    +            <version>1.12.0-SNAPSHOT</version>
    +            <scope>compile</scope>
    +        </dependency>
         </dependencies>
         <build>
             <plugins>
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/GetHTTP.java+1 1 modified
    @@ -99,8 +99,8 @@
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.processors.standard.util.HTTPUtils;
     import org.apache.nifi.security.util.KeyStoreUtils;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     import org.apache.nifi.util.StopWatch;
     import org.apache.nifi.util.Tuple;
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/InvokeHTTP.java+12 112 modified
    @@ -18,8 +18,12 @@
     
     import static org.apache.commons.lang3.StringUtils.trimToEmpty;
     
    +import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
    +import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
    +import com.burgstaller.okhttp.digest.CachingAuthenticator;
    +import com.burgstaller.okhttp.digest.DigestAuthenticator;
    +import com.google.common.io.Files;
     import java.io.File;
    -import java.io.FileInputStream;
     import java.io.IOException;
     import java.io.InputStream;
     import java.net.Proxy;
    @@ -28,7 +32,6 @@
     import java.nio.charset.Charset;
     import java.nio.charset.StandardCharsets;
     import java.security.KeyManagementException;
    -import java.security.KeyStore;
     import java.security.KeyStoreException;
     import java.security.NoSuchAlgorithmException;
     import java.security.Principal;
    @@ -50,23 +53,9 @@
     import java.util.concurrent.atomic.AtomicReference;
     import java.util.regex.Matcher;
     import java.util.regex.Pattern;
    -import java.util.stream.Collectors;
     import javax.annotation.Nullable;
     import javax.net.ssl.HostnameVerifier;
    -import javax.net.ssl.KeyManager;
    -import javax.net.ssl.KeyManagerFactory;
    -import javax.net.ssl.SSLContext;
     import javax.net.ssl.SSLSession;
    -import javax.net.ssl.SSLSocketFactory;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.TrustManagerFactory;
    -import javax.net.ssl.X509TrustManager;
    -
    -import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
    -import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
    -import com.burgstaller.okhttp.digest.CachingAuthenticator;
    -import com.burgstaller.okhttp.digest.DigestAuthenticator;
    -import com.google.common.io.Files;
     import okhttp3.Cache;
     import okhttp3.Credentials;
     import okhttp3.MediaType;
    @@ -110,11 +99,10 @@
     import org.apache.nifi.processors.standard.util.SoftLimitBoundedByteArrayOutputStream;
     import org.apache.nifi.proxy.ProxyConfiguration;
     import org.apache.nifi.proxy.ProxySpec;
    -import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.OkHttpClientUtils;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     import org.apache.nifi.stream.io.StreamUtils;
    -import org.apache.nifi.util.Tuple;
     import org.joda.time.format.DateTimeFormat;
     import org.joda.time.format.DateTimeFormatter;
     
    @@ -731,26 +719,12 @@ public void setUpClient(final ProcessContext context) throws IOException, Unreco
             // Set whether to follow redirects
             okHttpClientBuilder.followRedirects(context.getProperty(PROP_FOLLOW_REDIRECTS).asBoolean());
     
    +        // Apply the TLS configuration if present
             final SSLContextService sslService = context.getProperty(PROP_SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
    -        final SSLContext sslContext = sslService == null ? null : sslService.createSSLContext(ClientAuth.NONE);
    -
    -        // check if the ssl context is set and add the factory if so
    -        if (sslContext != null) {
    -            Tuple<SSLContext, TrustManager[]> sslContextTuple =SslContextFactory.createTrustSslContextWithTrustManagers(
    -                        sslService.getKeyStoreFile(),
    -                        sslService.getKeyStorePassword() != null ? sslService.getKeyStorePassword().toCharArray() : null,
    -                        sslService.getKeyPassword() != null ? sslService.getKeyPassword().toCharArray() : null,
    -                        sslService.getKeyStoreType(),
    -                        sslService.getTrustStoreFile(),
    -                        sslService.getTrustStorePassword() != null ? sslService.getTrustStorePassword().toCharArray() : null,
    -                        sslService.getTrustStoreType(),
    -                        SslContextFactory.ClientAuth.NONE,
    -                        sslService.getSslAlgorithm());
    -            List<X509TrustManager> x509TrustManagers = Arrays.stream(sslContextTuple.getValue())
    -                    .filter(trustManager -> trustManager instanceof X509TrustManager)
    -                    .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
    -            okHttpClientBuilder.sslSocketFactory(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
    -            }
    +        if (sslService != null) {
    +            final TlsConfiguration tlsConfiguration = sslService.createTlsConfiguration();
    +            OkHttpClientUtils.applyTlsToOkHttpClientBuilder(tlsConfiguration, okHttpClientBuilder);
    +        }
     
             setAuthenticator(okHttpClientBuilder, context);
     
    @@ -759,80 +733,6 @@ public void setUpClient(final ProcessContext context) throws IOException, Unreco
             okHttpClientAtomicReference.set(okHttpClientBuilder.build());
         }
     
    -    /*
    -        Overall, this method is based off of examples from OkHttp3 documentation:
    -            https://square.github.io/okhttp/3.x/okhttp/okhttp3/OkHttpClient.Builder.html#sslSocketFactory-javax.net.ssl.SSLSocketFactory-javax.net.ssl.X509TrustManager-
    -            https://github.com/square/okhttp/blob/master/samples/guide/src/main/java/okhttp3/recipes/CustomTrust.java#L156
    -
    -        In-depth documentation on Java Secure Socket Extension (JSSE) Classes and interfaces:
    -            https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#JSSEClasses
    -     */
    -    private void setSslSocketFactory(OkHttpClient.Builder okHttpClientBuilder, SSLContextService sslService, SSLContext sslContext, boolean setAsSocketFactory)
    -            throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
    -
    -        final KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    -        final TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
    -        // initialize the KeyManager array to null and we will overwrite later if a keystore is loaded
    -        KeyManager[] keyManagers = null;
    -
    -        // we will only initialize the keystore if properties have been supplied by the SSLContextService
    -        if (sslService.isKeyStoreConfigured()) {
    -            final String keystoreLocation = sslService.getKeyStoreFile();
    -            final String keystorePass = sslService.getKeyStorePassword();
    -            final String keystoreType = sslService.getKeyStoreType();
    -
    -            // prepare the keystore
    -            final KeyStore keyStore = KeyStore.getInstance(keystoreType);
    -
    -            try (FileInputStream keyStoreStream = new FileInputStream(keystoreLocation)) {
    -                keyStore.load(keyStoreStream, keystorePass.toCharArray());
    -            }
    -
    -            keyManagerFactory.init(keyStore, keystorePass.toCharArray());
    -            keyManagers = keyManagerFactory.getKeyManagers();
    -        }
    -
    -        // we will only initialize the truststure if properties have been supplied by the SSLContextService
    -        if (sslService.isTrustStoreConfigured()) {
    -            // load truststore
    -            final String truststoreLocation = sslService.getTrustStoreFile();
    -            final String truststorePass = sslService.getTrustStorePassword();
    -            final String truststoreType = sslService.getTrustStoreType();
    -
    -            char[] truststorePasswordChars = new char[0];
    -            if (StringUtils.isNotBlank(truststorePass)) {
    -                truststorePasswordChars = truststorePass.toCharArray();
    -            }
    -
    -            KeyStore truststore = KeyStore.getInstance(truststoreType);
    -            truststore.load(new FileInputStream(truststoreLocation), truststorePasswordChars);
    -            trustManagerFactory.init(truststore);
    -        }
    -
    -         /*
    -            TrustManagerFactory.getTrustManagers returns a trust manager for each type of trust material. Since we are getting a trust manager factory that uses "X509"
    -            as it's trust management algorithm, we are able to grab the first (and thus the most preferred) and use it as our x509 Trust Manager
    -
    -            https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/TrustManagerFactory.html#getTrustManagers--
    -         */
    -        final X509TrustManager x509TrustManager;
    -        TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
    -        if (trustManagers[0] != null) {
    -            x509TrustManager = (X509TrustManager) trustManagers[0];
    -        } else {
    -            throw new IllegalStateException("List of trust managers is null");
    -        }
    -
    -        // if keystore properties were not supplied, the keyManagers array will be null
    -        sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
    -
    -        final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
    -        okHttpClientBuilder.sslSocketFactory(sslSocketFactory, x509TrustManager);
    -        if (setAsSocketFactory) {
    -            okHttpClientBuilder.socketFactory(sslSocketFactory);
    -        }
    -    }
    -
         private void setAuthenticator(OkHttpClient.Builder okHttpClientBuilder, ProcessContext context) {
             final String authUser = trimToEmpty(context.getProperty(PROP_BASIC_AUTH_USERNAME).getValue());
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenRELP.java+14 15 modified
    @@ -16,6 +16,17 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.nio.ByteBuffer;
    +import java.nio.charset.Charset;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.BlockingQueue;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.WritesAttribute;
    @@ -50,18 +61,6 @@
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.nio.ByteBuffer;
    -import java.nio.charset.Charset;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.BlockingQueue;
    -
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @Tags({"listen", "relp", "tcp", "logs"})
     @CapabilityDescription("Listens for RELP messages being sent to a given port over TCP. Each message will be " +
    @@ -91,8 +90,8 @@ public class ListenRELP extends AbstractListenEventBatchingProcessor<RELPEvent>
                 .displayName("Client Auth")
                 .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    -            .defaultValue(SSLContextService.ClientAuth.REQUIRED.name())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
    +            .defaultValue(SslContextFactory.ClientAuth.REQUIRED.name())
                 .build();
     
         private volatile RELPEncoder relpEncoder;
    @@ -145,7 +144,7 @@ protected ChannelDispatcher createDispatcher(final ProcessContext context, final
             final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslContextService != null) {
                 final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuthValue));
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuthValue));
                 clientAuth = SslContextFactory.ClientAuth.valueOf(clientAuthValue);
     
             }
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenSyslog.java+25 26 modified
    @@ -16,6 +16,28 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import static org.apache.nifi.processor.util.listen.ListenerProperties.NETWORK_INTF_NAME;
    +
    +import java.io.IOException;
    +import java.io.OutputStream;
    +import java.net.InetAddress;
    +import java.net.NetworkInterface;
    +import java.nio.ByteBuffer;
    +import java.nio.channels.SelectableChannel;
    +import java.nio.channels.SocketChannel;
    +import java.nio.charset.Charset;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.BlockingQueue;
    +import java.util.concurrent.LinkedBlockingQueue;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.SupportsBatching;
    @@ -55,29 +77,6 @@
     import org.apache.nifi.syslog.events.SyslogEvent;
     import org.apache.nifi.syslog.parsers.SyslogParser;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.io.OutputStream;
    -import java.net.InetAddress;
    -import java.net.NetworkInterface;
    -import java.nio.ByteBuffer;
    -import java.nio.channels.SelectableChannel;
    -import java.nio.channels.SocketChannel;
    -import java.nio.charset.Charset;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.BlockingQueue;
    -import java.util.concurrent.LinkedBlockingQueue;
    -import java.util.concurrent.TimeUnit;
    -
    -import static org.apache.nifi.processor.util.listen.ListenerProperties.NETWORK_INTF_NAME;
    -
     @SupportsBatching
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @Tags({"syslog", "listen", "udp", "tcp", "logs"})
    @@ -185,8 +184,8 @@ public class ListenSyslog extends AbstractSyslogProcessor {
             .displayName("Client Auth")
             .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
             .required(false)
    -        .allowableValues(SSLContextService.ClientAuth.values())
    -        .defaultValue(SSLContextService.ClientAuth.REQUIRED.name())
    +        .allowableValues(SslContextFactory.ClientAuth.values())
    +        .defaultValue(SslContextFactory.ClientAuth.REQUIRED.name())
             .build();
     
         public static final Relationship REL_SUCCESS = new Relationship.Builder()
    @@ -350,7 +349,7 @@ protected ChannelDispatcher createChannelReader(final ProcessContext context, fi
     
                 if (sslContextService != null) {
                     final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
    -                sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuthValue));
    +                sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuthValue));
                     clientAuth = SslContextFactory.ClientAuth.valueOf(clientAuthValue);
                 }
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenTCP.java+15 16 modified
    @@ -16,6 +16,18 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.nio.ByteBuffer;
    +import java.nio.channels.SocketChannel;
    +import java.nio.charset.Charset;
    +import java.util.ArrayList;
    +import java.util.Arrays;
    +import java.util.Collection;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.concurrent.BlockingQueue;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.SupportsBatching;
    @@ -41,19 +53,6 @@
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.nio.ByteBuffer;
    -import java.nio.channels.SocketChannel;
    -import java.nio.charset.Charset;
    -import java.util.ArrayList;
    -import java.util.Arrays;
    -import java.util.Collection;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.concurrent.BlockingQueue;
    -
     @SupportsBatching
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @Tags({"listen", "tcp", "tls", "ssl"})
    @@ -80,8 +79,8 @@ public class ListenTCP extends AbstractListenEventBatchingProcessor<StandardEven
                 .name("Client Auth")
                 .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    -            .defaultValue(SSLContextService.ClientAuth.REQUIRED.name())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
    +            .defaultValue(SslContextFactory.ClientAuth.REQUIRED.name())
                 .build();
     
         @Override
    @@ -127,7 +126,7 @@ protected ChannelDispatcher createDispatcher(final ProcessContext context, final
             final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslContextService != null) {
                 final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuthValue));
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuthValue));
                 clientAuth = SslContextFactory.ClientAuth.valueOf(clientAuthValue);
             }
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/ListenTCPRecord.java+24 25 modified
    @@ -16,6 +16,27 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import static org.apache.nifi.processor.util.listen.ListenerProperties.NETWORK_INTF_NAME;
    +
    +import java.io.IOException;
    +import java.io.OutputStream;
    +import java.net.InetAddress;
    +import java.net.InetSocketAddress;
    +import java.net.NetworkInterface;
    +import java.net.SocketTimeoutException;
    +import java.nio.channels.ServerSocketChannel;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Set;
    +import java.util.concurrent.BlockingQueue;
    +import java.util.concurrent.LinkedBlockingQueue;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
    @@ -54,28 +75,6 @@
     import org.apache.nifi.ssl.RestrictedSSLContextService;
     import org.apache.nifi.ssl.SSLContextService;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.io.OutputStream;
    -import java.net.InetAddress;
    -import java.net.InetSocketAddress;
    -import java.net.NetworkInterface;
    -import java.net.SocketTimeoutException;
    -import java.nio.channels.ServerSocketChannel;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Set;
    -import java.util.concurrent.BlockingQueue;
    -import java.util.concurrent.LinkedBlockingQueue;
    -import java.util.concurrent.TimeUnit;
    -
    -import static org.apache.nifi.processor.util.listen.ListenerProperties.NETWORK_INTF_NAME;
    -
     @SupportsBatching
     @InputRequirement(InputRequirement.Requirement.INPUT_FORBIDDEN)
     @Tags({"listen", "tcp", "record", "tls", "ssl"})
    @@ -191,8 +190,8 @@ public class ListenTCPRecord extends AbstractProcessor {
                 .displayName("Client Auth")
                 .description("The client authentication policy to use for the SSL Context. Only used if an SSL Context Service is provided.")
                 .required(false)
    -            .allowableValues(SSLContextService.ClientAuth.values())
    -            .defaultValue(SSLContextService.ClientAuth.REQUIRED.name())
    +            .allowableValues(SslContextFactory.ClientAuth.values())
    +            .defaultValue(SslContextFactory.ClientAuth.REQUIRED.name())
                 .build();
     
         static final Relationship REL_SUCCESS = new Relationship.Builder()
    @@ -281,7 +280,7 @@ public void onScheduled(final ProcessContext context) throws IOException {
             final SSLContextService sslContextService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslContextService != null) {
                 final String clientAuthValue = context.getProperty(CLIENT_AUTH).getValue();
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.valueOf(clientAuthValue));
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.valueOf(clientAuthValue));
                 clientAuth = SslContextFactory.ClientAuth.valueOf(clientAuthValue);
             }
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutSyslog.java+17 17 modified
    @@ -16,6 +16,21 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.nio.charset.Charset;
    +import java.util.ArrayList;
    +import java.util.Collection;
    +import java.util.Collections;
    +import java.util.HashSet;
    +import java.util.List;
    +import java.util.Set;
    +import java.util.concurrent.BlockingQueue;
    +import java.util.concurrent.LinkedBlockingQueue;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicReference;
    +import java.util.regex.Matcher;
    +import java.util.regex.Pattern;
    +import javax.net.ssl.SSLContext;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.TriggerWhenEmpty;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
    @@ -39,26 +54,11 @@
     import org.apache.nifi.processor.util.put.sender.DatagramChannelSender;
     import org.apache.nifi.processor.util.put.sender.SSLSocketChannelSender;
     import org.apache.nifi.processor.util.put.sender.SocketChannelSender;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.syslog.parsers.SyslogParser;
     import org.apache.nifi.util.StopWatch;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.nio.charset.Charset;
    -import java.util.ArrayList;
    -import java.util.Collection;
    -import java.util.Collections;
    -import java.util.HashSet;
    -import java.util.List;
    -import java.util.Set;
    -import java.util.concurrent.BlockingQueue;
    -import java.util.concurrent.LinkedBlockingQueue;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicReference;
    -import java.util.regex.Matcher;
    -import java.util.regex.Pattern;
    -
     @InputRequirement(InputRequirement.Requirement.INPUT_REQUIRED)
     @TriggerWhenEmpty
     @Tags({"syslog", "put", "udp", "tcp", "logs"})
    @@ -249,7 +249,7 @@ protected ChannelSender createSender(final SSLContextService sslContextService,
             } else {
                 // if an SSLContextService is provided then we make a secure sender
                 if (sslContextService != null) {
    -                final SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +                final SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
                     sender = new SSLSocketChannelSender(host, port, maxSendBufferSize, sslContext, getLogger());
                 } else {
                     sender = new SocketChannelSender(host, port, maxSendBufferSize, getLogger());
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/main/java/org/apache/nifi/processors/standard/PutTCP.java+11 11 modified
    @@ -16,6 +16,15 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.BufferedInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.io.OutputStream;
    +import java.nio.charset.Charset;
    +import java.util.Arrays;
    +import java.util.List;
    +import java.util.concurrent.TimeUnit;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.annotation.behavior.InputRequirement;
     import org.apache.nifi.annotation.behavior.InputRequirement.Requirement;
    @@ -33,19 +42,10 @@
     import org.apache.nifi.processor.util.put.AbstractPutEventProcessor;
     import org.apache.nifi.processor.util.put.sender.ChannelSender;
     import org.apache.nifi.processor.util.put.sender.SocketChannelSender;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.util.StopWatch;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.BufferedInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.OutputStream;
    -import java.nio.charset.Charset;
    -import java.util.Arrays;
    -import java.util.List;
    -import java.util.concurrent.TimeUnit;
    -
     /**
      * <p>
      * The PutTCP processor receives a FlowFile and transmits the FlowFile content over a TCP connection to the configured TCP server. By default, the FlowFiles are transmitted over the same TCP
    @@ -115,7 +115,7 @@ protected ChannelSender createSender(final ProcessContext context) throws IOExce
     
             SSLContext sslContext = null;
             if (sslContextService != null) {
    -            sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +            sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
             }
     
             return createSender(protocol, hostname, port, timeout, bufferSize, sslContext);
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestGetHTTPGroovy.groovy+3 2 modified
    @@ -366,8 +366,9 @@ class TestGetHTTPGroovy extends GroovyTestCase {
             runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE)
             runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, protocol)
             runner.enableControllerService(sslContextService)
    -        logger.info("GetHTTP supported protocols: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).protocol}")
    -        logger.info("GetHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}")
    +        def sslContext = sslContextService.createSSLContext(org.apache.nifi.security.util.SslContextFactory.ClientAuth.NONE)
    +        logger.info("GetHTTP supported protocols: ${sslContext.protocol}")
    +        logger.info("GetHTTP supported cipher suites: ${sslContext.supportedSSLParameters.cipherSuites}")
         }
     
         /**
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/groovy/org/apache/nifi/processors/standard/TestPostHTTPGroovy.groovy+3 2 modified
    @@ -330,8 +330,9 @@ class TestPostHTTPGroovy extends GroovyTestCase {
             runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, KEYSTORE_TYPE)
             runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, protocol)
             runner.enableControllerService(sslContextService)
    -        logger.info("PostHTTP supported protocols: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).protocol}")
    -        logger.info("PostHTTP supported cipher suites: ${sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE).supportedSSLParameters.cipherSuites}")
    +        def sslContext = sslContextService.createSSLContext(org.apache.nifi.security.util.SslContextFactory.ClientAuth.NONE)
    +        logger.info("PostHTTP supported protocols: ${sslContext.protocol}")
    +        logger.info("PostHTTP supported cipher suites: ${sslContext.supportedSSLParameters.cipherSuites}")
         }
     
         /**
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/ITestHandleHttpRequest.java+178 171 modified
    @@ -16,10 +16,36 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import static org.junit.Assert.assertEquals;
    +import static org.junit.Assert.assertTrue;
    +
     import com.google.api.client.util.Charsets;
     import com.google.common.base.Optional;
     import com.google.common.collect.Iterables;
     import com.google.common.io.Files;
    +import java.io.File;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.HttpURLConnection;
    +import java.net.URL;
    +import java.util.ArrayList;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Random;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.ConcurrentMap;
    +import java.util.concurrent.CountDownLatch;
    +import java.util.concurrent.Executors;
    +import java.util.concurrent.Future;
    +import java.util.concurrent.TimeUnit;
    +import java.util.concurrent.atomic.AtomicInteger;
    +import java.util.stream.IntStream;
    +import javax.net.ssl.HttpsURLConnection;
    +import javax.net.ssl.SSLContext;
    +import javax.servlet.AsyncContext;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
     import okhttp3.Call;
     import okhttp3.Callback;
     import okhttp3.MediaType;
    @@ -33,7 +59,9 @@
     import org.apache.nifi.processor.ProcessContext;
     import org.apache.nifi.processors.standard.util.HTTPUtils;
     import org.apache.nifi.reporting.InitializationException;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
     import org.apache.nifi.ssl.StandardSSLContextService;
    @@ -42,65 +70,42 @@
     import org.apache.nifi.util.TestRunners;
     import org.junit.After;
     import org.junit.Assert;
    +import org.junit.Before;
     import org.junit.Test;
     
    -import javax.net.ssl.HttpsURLConnection;
    -import javax.net.ssl.SSLContext;
    -import javax.servlet.AsyncContext;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -import java.io.File;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.net.HttpURLConnection;
    -import java.net.MalformedURLException;
    -import java.net.URL;
    -import java.util.ArrayList;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Random;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.ConcurrentMap;
    -import java.util.concurrent.CountDownLatch;
    -import java.util.concurrent.Executors;
    -import java.util.concurrent.Future;
    -import java.util.concurrent.TimeUnit;
    -import java.util.concurrent.atomic.AtomicInteger;
    -import java.util.stream.IntStream;
    -
    -import static org.junit.Assert.assertEquals;
    -import static org.junit.Assert.assertTrue;
    -
     public class ITestHandleHttpRequest {
     
    +    private static final String KEYSTORE = "src/test/resources/keystore.jks";
    +    private static final String KEYSTORE_PASSWORD = "passwordpassword";
    +    private static final String KEYSTORE_TYPE = "JKS";
    +    private static final String TRUSTSTORE = "src/test/resources/truststore.jks";
    +    private static final String TRUSTSTORE_PASSWORD = "passwordpassword";
    +    private static final String TRUSTSTORE_TYPE = "JKS";
    +    private static final String CLIENT_KEYSTORE = "src/test/resources/client-keystore.p12";
    +    private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
    +
         private HandleHttpRequest processor;
     
    +    private TlsConfiguration clientTlsConfiguration;
    +    private TlsConfiguration trustOnlyTlsConfiguration;
    +
         private static Map<String, String> getTruststoreProperties() {
             final Map<String, String> props = new HashMap<>();
    -        props.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/truststore.jks");
    -        props.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "passwordpassword");
    -        props.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");
    +        props.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE);
    +        props.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), TRUSTSTORE_PASSWORD);
    +        props.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), TRUSTSTORE_TYPE);
             return props;
         }
     
         private static Map<String, String> getServerKeystoreProperties() {
             final Map<String, String> properties = new HashMap<>();
    -        properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/keystore.jks");
    -        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "passwordpassword");
    -        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "JKS");
    -        return properties;
    -    }
    -
    -    private static Map<String, String> getClientKeystoreProperties() {
    -        final Map<String, String> properties = new HashMap<>();
    -        properties.put(StandardSSLContextService.KEYSTORE.getName(), "src/test/resources/client-keystore.p12");
    -        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), "passwordpassword");
    -        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), "PKCS12");
    +        properties.put(StandardSSLContextService.KEYSTORE.getName(), KEYSTORE);
    +        properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_PASSWORD);
    +        properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), KEYSTORE_TYPE);
             return properties;
         }
     
    -    private static SSLContext useSSLContextService(final TestRunner controller, final Map<String, String> sslProperties, SSLContextService.ClientAuth clientAuth) {
    +    private static SSLContext useSSLContextService(final TestRunner controller, final Map<String, String> sslProperties, SslContextFactory.ClientAuth clientAuth) {
             final SSLContextService service = new StandardRestrictedSSLContextService();
             try {
                 controller.addControllerService("ssl-service", service, sslProperties);
    @@ -114,15 +119,23 @@ private static SSLContext useSSLContextService(final TestRunner controller, fina
             return service.createSSLContext(clientAuth);
         }
     
    +    @Before
    +    public void setUp() throws Exception {
    +        clientTlsConfiguration = new TlsConfiguration(CLIENT_KEYSTORE, KEYSTORE_PASSWORD, null, CLIENT_KEYSTORE_TYPE,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        trustOnlyTlsConfiguration = new TlsConfiguration(null, null, null, null,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +    }
    +
         @After
         public void tearDown() throws Exception {
             if (processor != null) {
                 processor.shutdown();
             }
         }
     
    -    @Test(timeout=30000)
    -    public void testRequestAddedToService() throws InitializationException, MalformedURLException, IOException, InterruptedException {
    +    @Test(timeout = 30000)
    +    public void testRequestAddedToService() throws InitializationException, IOException, InterruptedException {
             CountDownLatch serverReady = new CountDownLatch(1);
             CountDownLatch requestSent = new CountDownLatch(1);
     
    @@ -176,8 +189,8 @@ public void run() {
             mff.assertAttributeEquals("http.headers.header3", "apple=orange");
         }
     
    -    @Test(timeout=30000)
    -    public void testMultipartFormDataRequest() throws InitializationException, MalformedURLException, IOException, InterruptedException {
    +    @Test(timeout = 30000)
    +    public void testMultipartFormDataRequest() throws InitializationException, IOException, InterruptedException {
             CountDownLatch serverReady = new CountDownLatch(1);
             CountDownLatch requestSent = new CountDownLatch(1);
     
    @@ -191,37 +204,40 @@ public void testMultipartFormDataRequest() throws InitializationException, Malfo
             runner.setProperty(HandleHttpRequest.HTTP_CONTEXT_MAP, "http-context-map");
     
             final Thread httpThread = new Thread(new Runnable() {
    -          @Override
    -          public void run() {
    -            try {
    -              serverReady.await();
    -
    -              final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
    -
    -              MultipartBody multipartBody = new MultipartBody.Builder()
    -                .setType(MultipartBody.FORM)
    -                .addFormDataPart("p1", "v1")
    -                .addFormDataPart("p2", "v2")
    -                .addFormDataPart("file1", "my-file-text.txt", RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    -                .addFormDataPart("file2", "my-file-data.json", RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    -                .addFormDataPart("file3", "my-file-binary.bin", RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    -                .build();
    -
    -              Request request = new Request.Builder()
    -              .url(String.format("http://localhost:%s/my/path", port))
    -              .post(multipartBody).build();
    -
    -              OkHttpClient client =
    -                  new OkHttpClient.Builder()
    -                    .readTimeout(3000, TimeUnit.MILLISECONDS)
    -                    .writeTimeout(3000, TimeUnit.MILLISECONDS)
    -                  .build();
    -
    -                sendRequest(client, request, requestSent);
    -            } catch (Exception e) {
    -                // Do nothing as HandleHttpRequest doesn't respond normally
    +            @Override
    +            public void run() {
    +                try {
    +                    serverReady.await();
    +
    +                    final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
    +
    +                    MultipartBody multipartBody = new MultipartBody.Builder()
    +                            .setType(MultipartBody.FORM)
    +                            .addFormDataPart("p1", "v1")
    +                            .addFormDataPart("p2", "v2")
    +                            .addFormDataPart("file1", "my-file-text.txt",
    +                                    RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    +                            .addFormDataPart("file2", "my-file-data.json",
    +                                    RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    +                            .addFormDataPart("file3", "my-file-binary.bin",
    +                                    RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    +                            .build();
    +
    +                    Request request = new Request.Builder()
    +                            .url(String.format("http://localhost:%s/my/path", port))
    +                            .post(multipartBody).build();
    +
    +                    OkHttpClient client =
    +                            new OkHttpClient.Builder()
    +                                    .readTimeout(3000, TimeUnit.MILLISECONDS)
    +                                    .writeTimeout(3000, TimeUnit.MILLISECONDS)
    +                                    .build();
    +
    +                    sendRequest(client, request, requestSent);
    +                } catch (Exception e) {
    +                    // Do nothing as HandleHttpRequest doesn't respond normally
    +                }
                 }
    -          }
             });
     
             httpThread.start();
    @@ -286,8 +302,8 @@ public void run() {
             mff.assertAttributeExists("http.headers.multipart.content-disposition");
         }
     
    -    @Test(timeout=30000)
    -    public void testMultipartFormDataRequestFailToRegisterContext() throws InitializationException, MalformedURLException, IOException, InterruptedException {
    +    @Test(timeout = 30000)
    +    public void testMultipartFormDataRequestFailToRegisterContext() throws InitializationException, IOException, InterruptedException {
             CountDownLatch serverReady = new CountDownLatch(1);
             CountDownLatch requestSent = new CountDownLatch(1);
             CountDownLatch resultReady = new CountDownLatch(1);
    @@ -304,49 +320,52 @@ public void testMultipartFormDataRequestFailToRegisterContext() throws Initializ
     
             AtomicInteger responseCode = new AtomicInteger(0);
             final Thread httpThread = new Thread(new Runnable() {
    -          @Override
    -          public void run() {
    -            try {
    -              serverReady.await();
    -
    -              final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
    -
    -              MultipartBody multipartBody = new MultipartBody.Builder()
    -                .setType(MultipartBody.FORM)
    -                .addFormDataPart("p1", "v1")
    -                .addFormDataPart("p2", "v2")
    -                .addFormDataPart("file1", "my-file-text.txt", RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    -                .addFormDataPart("file2", "my-file-data.json", RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    -                .addFormDataPart("file3", "my-file-binary.bin", RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    -                .build();
    -
    -              Request request = new Request.Builder()
    -              .url(String.format("http://localhost:%s/my/path", port))
    -              .post(multipartBody).build();
    -
    -              OkHttpClient client =
    -                  new OkHttpClient.Builder()
    -                    .readTimeout(20000, TimeUnit.MILLISECONDS)
    -                    .writeTimeout(20000, TimeUnit.MILLISECONDS)
    -                  .build();
    -
    -                Callback callback = new Callback() {
    -                    @Override
    -                    public void onFailure(Call call, IOException e) {
    -                        // Not going to happen
    -                    }
    +            @Override
    +            public void run() {
    +                try {
    +                    serverReady.await();
     
    -                    @Override
    -                    public void onResponse(Call call, Response response) throws IOException {
    -                        responseCode.set(response.code());
    -                        resultReady.countDown();
    -                    }
    -                };
    -                sendRequest(client, request, callback, requestSent);
    -            } catch (final Throwable t) {
    -                // Do nothing as HandleHttpRequest doesn't respond normally
    +                    final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
    +
    +                    MultipartBody multipartBody = new MultipartBody.Builder()
    +                            .setType(MultipartBody.FORM)
    +                            .addFormDataPart("p1", "v1")
    +                            .addFormDataPart("p2", "v2")
    +                            .addFormDataPart("file1", "my-file-text.txt",
    +                                    RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    +                            .addFormDataPart("file2", "my-file-data.json",
    +                                    RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    +                            .addFormDataPart("file3", "my-file-binary.bin",
    +                                    RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    +                            .build();
    +
    +                    Request request = new Request.Builder()
    +                            .url(String.format("http://localhost:%s/my/path", port))
    +                            .post(multipartBody).build();
    +
    +                    OkHttpClient client =
    +                            new OkHttpClient.Builder()
    +                                    .readTimeout(20000, TimeUnit.MILLISECONDS)
    +                                    .writeTimeout(20000, TimeUnit.MILLISECONDS)
    +                                    .build();
    +
    +                    Callback callback = new Callback() {
    +                        @Override
    +                        public void onFailure(Call call, IOException e) {
    +                            // Not going to happen
    +                        }
    +
    +                        @Override
    +                        public void onResponse(Call call, Response response) throws IOException {
    +                            responseCode.set(response.code());
    +                            resultReady.countDown();
    +                        }
    +                    };
    +                    sendRequest(client, request, callback, requestSent);
    +                } catch (final Throwable t) {
    +                    // Do nothing as HandleHttpRequest doesn't respond normally
    +                }
                 }
    -          }
             });
     
             httpThread.start();
    @@ -359,31 +378,31 @@ public void onResponse(Call call, Response response) throws IOException {
         }
     
         private byte[] generateRandomBinaryData(int i) {
    -      byte[] bytes = new byte[100];
    -      new Random().nextBytes(bytes);
    -      return bytes;
    +        byte[] bytes = new byte[100];
    +        new Random().nextBytes(bytes);
    +        return bytes;
         }
     
     
         private File createTextFile(String fileName, String... lines) throws IOException {
    -      File file = new File(fileName);
    -      file.deleteOnExit();
    -      for (String string : lines) {
    -        Files.append(string, file, Charsets.UTF_8);
    -      }
    -      return file;
    +        File file = new File(fileName);
    +        file.deleteOnExit();
    +        for (String string : lines) {
    +            Files.append(string, file, Charsets.UTF_8);
    +        }
    +        return file;
         }
     
     
         protected MockFlowFile findFlowFile(List<MockFlowFile> flowFilesForRelationship, String attributeName, String attributeValue) {
    -      Optional<MockFlowFile> optional = Iterables.tryFind(flowFilesForRelationship, ff -> ff.getAttribute(attributeName).equals(attributeValue));
    -      Assert.assertTrue(optional.isPresent());
    -      return optional.get();
    +        Optional<MockFlowFile> optional = Iterables.tryFind(flowFilesForRelationship, ff -> ff.getAttribute(attributeName).equals(attributeValue));
    +        Assert.assertTrue(optional.isPresent());
    +        return optional.get();
         }
     
     
    -    @Test(timeout=30000)
    -    public void testFailToRegister() throws InitializationException, MalformedURLException, IOException, InterruptedException {
    +    @Test(timeout = 30000)
    +    public void testFailToRegister() throws InitializationException, IOException, InterruptedException {
             CountDownLatch serverReady = new CountDownLatch(1);
             CountDownLatch requestSent = new CountDownLatch(1);
             CountDownLatch resultReady = new CountDownLatch(1);
    @@ -420,7 +439,7 @@ public void run() {
     
                         sendRequest(connection, requestSent);
                     } catch (final Throwable t) {
    -                    if(connection != null ) {
    +                    if (connection != null) {
                             try {
                                 responseCode[0] = connection.getResponseCode();
                             } catch (IOException e) {
    @@ -450,7 +469,7 @@ public void testCleanup() throws Exception {
     
             CountDownLatch serverReady = new CountDownLatch(1);
             CountDownLatch requestSent = new CountDownLatch(nrOfRequests);
    -        CountDownLatch cleanupDone = new CountDownLatch(nrOfRequests-1);
    +        CountDownLatch cleanupDone = new CountDownLatch(nrOfRequests - 1);
     
             processor = new HandleHttpRequest() {
                 @Override
    @@ -483,10 +502,10 @@ public void run() {
                         final int port = ((HandleHttpRequest) runner.getProcessor()).getPort();
     
                         OkHttpClient client =
    -                        new OkHttpClient.Builder()
    -                            .readTimeout(3000, TimeUnit.MILLISECONDS)
    -                            .writeTimeout(3000, TimeUnit.MILLISECONDS)
    -                            .build();
    +                            new OkHttpClient.Builder()
    +                                    .readTimeout(3000, TimeUnit.MILLISECONDS)
    +                                    .writeTimeout(3000, TimeUnit.MILLISECONDS)
    +                                    .build();
                         client.dispatcher().setMaxRequests(nrOfRequests);
                         client.dispatcher().setMaxRequestsPerHost(nrOfRequests);
     
    @@ -503,13 +522,13 @@ public void onResponse(Call call, Response response) throws IOException {
                             }
                         };
                         IntStream.rangeClosed(1, nrOfRequests).forEach(
    -                        requestCounter -> {
    -                            Request request = new Request.Builder()
    -                                .url(String.format("http://localhost:%s/my/" + requestCounter , port))
    -                                .get()
    -                                .build();
    -                            sendRequest(client, request, callback, requestSent);
    -                        }
    +                            requestCounter -> {
    +                                Request request = new Request.Builder()
    +                                        .url(String.format("http://localhost:%s/my/" + requestCounter, port))
    +                                        .get()
    +                                        .build();
    +                                sendRequest(client, request, callback, requestSent);
    +                            }
                         );
                     } catch (final Throwable t) {
                         // Do nothing as HandleHttpRequest doesn't respond normally
    @@ -529,7 +548,7 @@ public void onResponse(Call call, Response response) throws IOException {
     
             assertEquals(1, contextMap.size());
             assertEquals(0, nrOfPendingRequests);
    -        assertEquals(responses.size(), nrOfRequests-1);
    +        assertEquals(responses.size(), nrOfRequests - 1);
             for (Response response : responses) {
                 assertEquals(HttpServletResponse.SC_SERVICE_UNAVAILABLE, response.code());
                 assertTrue("Unexpected HTTP response for rejected requests", new String(response.body().bytes()).contains("Processor is shutting down"));
    @@ -561,8 +580,8 @@ private void secureTest(boolean twoWaySsl) throws Exception {
     
             final Map<String, String> sslProperties = getServerKeystoreProperties();
             sslProperties.putAll(getTruststoreProperties());
    -        sslProperties.put(StandardSSLContextService.SSL_ALGORITHM.getName(), "TLSv1.2");
    -        useSSLContextService(runner, sslProperties, twoWaySsl ? SSLContextService.ClientAuth.WANT : SSLContextService.ClientAuth.NONE);
    +        sslProperties.put(StandardSSLContextService.SSL_ALGORITHM.getName(), CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        useSSLContextService(runner, sslProperties, twoWaySsl ? SslContextFactory.ClientAuth.REQUIRED : SslContextFactory.ClientAuth.NONE);
     
             final Thread httpThread = new Thread(new Runnable() {
                 @Override
    @@ -574,27 +593,15 @@ public void run() {
                         final HttpsURLConnection connection = (HttpsURLConnection) new URL("https://localhost:"
                                 + port + "/my/path?query=true&value1=value1&value2=&value3&value4=apple=orange").openConnection();
     
    +                    SSLContext clientSslContext;
                         if (twoWaySsl) {
    -                        // use a client certificate, do not reuse the server's keystore
    -                        SSLContext clientSslContext = SslContextFactory.createSslContext(
    -                                getClientKeystoreProperties().get(StandardSSLContextService.KEYSTORE.getName()),
    -                                getClientKeystoreProperties().get(StandardSSLContextService.KEYSTORE_PASSWORD.getName()).toCharArray(),
    -                                "JKS",
    -                                getTruststoreProperties().get(StandardSSLContextService.TRUSTSTORE.getName()),
    -                                getTruststoreProperties().get(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName()).toCharArray(),
    -                                "JKS",
    -                                null,
    -                                "TLSv1.2");
    -                        connection.setSSLSocketFactory(clientSslContext.getSocketFactory());
    +                        // Use a client certificate, do not reuse the server's keystore
    +                        clientSslContext = SslContextFactory.createSslContext(clientTlsConfiguration);
                         } else {
    -                        // with one-way SSL, the client still needs a truststore
    -                        SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                                getTruststoreProperties().get(StandardSSLContextService.TRUSTSTORE.getName()),
    -                                getTruststoreProperties().get(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName()).toCharArray(),
    -                                "JKS",
    -                                "TLSv1.2");
    -                        connection.setSSLSocketFactory(clientSslContext.getSocketFactory());
    +                        // With one-way SSL, the client still needs a truststore
    +                        clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
                         }
    +                    connection.setSSLSocketFactory(clientSslContext.getSocketFactory());
                         connection.setDoOutput(false);
                         connection.setRequestMethod("GET");
                         connection.setRequestProperty("header1", "value1");
    @@ -635,7 +642,7 @@ synchronized void initializeServer(ProcessContext context) throws Exception {
                     serverReady.countDown();
     
                     requestSent.await();
    -                while (getRequestQueueSize()  == 0) {
    +                while (getRequestQueueSize() == 0) {
                         Thread.sleep(200);
                     }
                 }
    @@ -649,7 +656,7 @@ void rejectPendingRequests() {
     
         private void sendRequest(HttpURLConnection connection, CountDownLatch requestSent) throws Exception {
             Future<InputStream> executionFuture = Executors.newSingleThreadExecutor()
    -            .submit(() -> connection.getInputStream());
    +                .submit(() -> connection.getInputStream());
     
             requestSent.countDown();
     
    @@ -685,7 +692,7 @@ private static class MockHttpContextMap extends AbstractControllerService implem
     
             @Override
             public boolean register(final String identifier, final HttpServletRequest request, final HttpServletResponse response, final AsyncContext context) {
    -            if(registerSuccessfully) {
    +            if (registerSuccessfully) {
                     responseMap.put(identifier, response);
                 }
                 return registerSuccessfully;
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/ITListenAndPutSyslog.java+7 4 modified
    @@ -16,6 +16,8 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.nio.charset.StandardCharsets;
     import org.apache.nifi.processor.ProcessContext;
     import org.apache.nifi.processor.ProcessSessionFactory;
     import org.apache.nifi.reporting.InitializationException;
    @@ -31,9 +33,6 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import java.io.IOException;
    -import java.nio.charset.Charset;
    -
     /**
      * Tests PutSyslog sending messages to ListenSyslog to simulate a syslog server forwarding
      * to ListenSyslog, or PutSyslog sending to a syslog server.
    @@ -42,6 +41,9 @@ public class ITListenAndPutSyslog {
     
         static final Logger LOGGER = LoggerFactory.getLogger(ITListenAndPutSyslog.class);
     
    +    // TODO: The NiFi SSL classes don't yet support TLSv1.3, so set the CS version explicitly
    +    private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
    +
         private ListenSyslog listenSyslog;
         private TestRunner listenSyslogRunner;
     
    @@ -110,6 +112,7 @@ private SSLContextService configureSSLContextService(TestRunner runner) throws I
             runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, "src/test/resources/keystore.jks");
             runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, "passwordpassword");
             runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
    +        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, TLS_PROTOCOL_VERSION);
             runner.enableControllerService(sslContextService);
             return sslContextService;
         }
    @@ -150,7 +153,7 @@ private void run(String protocol, int numMessages, int expectedMessages) throws
     
             // send the messages
             for (int i=0; i < numMessages; i++) {
    -            putSyslogRunner.enqueue("incoming data".getBytes(Charset.forName("UTF-8")));
    +            putSyslogRunner.enqueue("incoming data".getBytes(StandardCharsets.UTF_8));
             }
             putSyslogRunner.run(numMessages, false);
     
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestInvokeHTTP.java+18 18 modified
    @@ -16,12 +16,25 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import static org.junit.Assert.assertNotNull;
    +import static org.junit.Assert.assertNull;
    +
    +import java.io.IOException;
    +import java.io.PrintWriter;
    +import java.lang.reflect.Field;
    +import java.net.URL;
    +import java.nio.charset.StandardCharsets;
    +import java.util.HashMap;
    +import java.util.Map;
    +import javax.servlet.ServletException;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
     import org.apache.commons.lang3.SystemUtils;
     import org.apache.nifi.processors.standard.util.TestInvokeHttpCommon;
    -import org.apache.nifi.web.util.TestServer;
     import org.apache.nifi.ssl.StandardSSLContextService;
     import org.apache.nifi.util.MockFlowFile;
     import org.apache.nifi.util.TestRunners;
    +import org.apache.nifi.web.util.TestServer;
     import org.eclipse.jetty.server.Request;
     import org.eclipse.jetty.server.handler.AbstractHandler;
     import org.junit.After;
    @@ -32,19 +45,6 @@
     import org.junit.BeforeClass;
     import org.junit.Test;
     
    -import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -import java.io.IOException;
    -import java.io.PrintWriter;
    -import java.lang.reflect.Field;
    -import java.net.URL;
    -import java.util.HashMap;
    -import java.util.Map;
    -
    -import static org.junit.Assert.assertNotNull;
    -import static org.junit.Assert.assertNull;
    -
     
     public class TestInvokeHTTP extends TestInvokeHttpCommon {
     
    @@ -121,7 +121,7 @@ public void testSslSetHttpRequest() throws Exception {
             // expected in request status.code and status.message
             // original flow file (+attributes)
             final MockFlowFile bundle = runner.getFlowFilesForRelationship(InvokeHTTP.REL_SUCCESS_REQ).get(0);
    -        bundle.assertContentEquals("Hello".getBytes("UTF-8"));
    +        bundle.assertContentEquals("Hello".getBytes(StandardCharsets.UTF_8));
             bundle.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
             bundle.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
             bundle.assertAttributeEquals("Foo", "Bar");
    @@ -130,7 +130,7 @@ public void testSslSetHttpRequest() throws Exception {
             // status code, status message, all headers from server response --> ff attributes
             // server response message body into payload of ff
             final MockFlowFile bundle1 = runner.getFlowFilesForRelationship(InvokeHTTP.REL_RESPONSE).get(0);
    -        bundle1.assertContentEquals("/status/200".getBytes("UTF-8"));
    +        bundle1.assertContentEquals("/status/200".getBytes(StandardCharsets.UTF_8));
             bundle1.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
             bundle1.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
             bundle1.assertAttributeEquals("Foo", "Bar");
    @@ -183,7 +183,7 @@ public void testProxy() throws Exception {
             //expected in request status.code and status.message
             //original flow file (+attributes)
             final MockFlowFile bundle = runner.getFlowFilesForRelationship(InvokeHTTP.REL_SUCCESS_REQ).get(0);
    -        bundle.assertContentEquals("Hello".getBytes("UTF-8"));
    +        bundle.assertContentEquals("Hello".getBytes(StandardCharsets.UTF_8));
             bundle.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
             bundle.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
             bundle.assertAttributeEquals("Foo", "Bar");
    @@ -192,7 +192,7 @@ public void testProxy() throws Exception {
             //status code, status message, all headers from server response --> ff attributes
             //server response message body into payload of ff
             final MockFlowFile bundle1 = runner.getFlowFilesForRelationship(InvokeHTTP.REL_RESPONSE).get(0);
    -        bundle1.assertContentEquals("http://nifi.apache.org/".getBytes("UTF-8"));
    +        bundle1.assertContentEquals("http://nifi.apache.org/".getBytes(StandardCharsets.UTF_8));
             bundle1.assertAttributeEquals(InvokeHTTP.STATUS_CODE, "200");
             bundle1.assertAttributeEquals(InvokeHTTP.STATUS_MESSAGE, "OK");
             bundle1.assertAttributeEquals("Foo", "Bar");
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenHTTP.java+185 176 modified
    @@ -16,37 +16,13 @@
      */
     package org.apache.nifi.processors.standard;
     
    -import org.apache.commons.lang3.SystemUtils;
    -import org.apache.nifi.processor.ProcessContext;
    -import org.apache.nifi.processor.ProcessSessionFactory;
    -import org.apache.nifi.remote.io.socket.NetworkUtils;
    -import org.apache.nifi.reporting.InitializationException;
    -import org.apache.nifi.security.util.SslContextFactory;
    -import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
    -import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.StandardSSLContextService;
    -import org.apache.nifi.util.MockFlowFile;
    -import org.apache.nifi.util.TestRunner;
    -import org.apache.nifi.util.TestRunners;
    -import org.junit.After;
    -import org.junit.Assert;
    -import org.junit.Assume;
    -import org.junit.Before;
    -import org.junit.BeforeClass;
    -import org.junit.Test;
    +import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS;
    +import static org.junit.Assert.fail;
     
     import com.google.common.base.Charsets;
     import com.google.common.base.Optional;
     import com.google.common.collect.Iterables;
     import com.google.common.io.Files;
    -
    -import okhttp3.MediaType;
    -import okhttp3.MultipartBody;
    -import okhttp3.OkHttpClient;
    -import okhttp3.Request;
    -import okhttp3.RequestBody;
    -import okhttp3.Response;
    -
     import java.io.DataOutputStream;
     import java.io.File;
     import java.io.IOException;
    @@ -56,13 +32,36 @@
     import java.util.List;
     import java.util.Random;
     import java.util.concurrent.TimeUnit;
    -
     import javax.net.ssl.HttpsURLConnection;
     import javax.net.ssl.SSLContext;
     import javax.servlet.http.HttpServletResponse;
    -
    -import static org.apache.nifi.processors.standard.ListenHTTP.RELATIONSHIP_SUCCESS;
    -import static org.junit.Assert.fail;
    +import okhttp3.MediaType;
    +import okhttp3.MultipartBody;
    +import okhttp3.OkHttpClient;
    +import okhttp3.Request;
    +import okhttp3.RequestBody;
    +import okhttp3.Response;
    +import org.apache.commons.lang3.SystemUtils;
    +import org.apache.nifi.processor.ProcessContext;
    +import org.apache.nifi.processor.ProcessSessionFactory;
    +import org.apache.nifi.remote.io.socket.NetworkUtils;
    +import org.apache.nifi.reporting.InitializationException;
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
    +import org.apache.nifi.ssl.SSLContextService;
    +import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
    +import org.apache.nifi.ssl.StandardSSLContextService;
    +import org.apache.nifi.util.MockFlowFile;
    +import org.apache.nifi.util.TestRunner;
    +import org.apache.nifi.util.TestRunners;
    +import org.junit.After;
    +import org.junit.Assert;
    +import org.junit.Assume;
    +import org.junit.Before;
    +import org.junit.BeforeClass;
    +import org.junit.Test;
     
     public class TestListenHTTP {
     
    @@ -77,6 +76,18 @@ public class TestListenHTTP {
         private final static String BASEPATH_VARIABLE = "HTTP_BASEPATH";
         private final static String HTTP_SERVER_BASEPATH_EL = "${" + BASEPATH_VARIABLE + "}";
     
    +    private static final String KEYSTORE = "src/test/resources/keystore.jks";
    +    private static final String KEYSTORE_PASSWORD = "passwordpassword";
    +    private static final String KEYSTORE_TYPE = "JKS";
    +    private static final String TRUSTSTORE = "src/test/resources/truststore.jks";
    +    private static final String TRUSTSTORE_PASSWORD = "passwordpassword";
    +    private static final String TRUSTSTORE_TYPE = "JKS";
    +    private static final String CLIENT_KEYSTORE = "src/test/resources/client-keystore.p12";
    +    private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
    +
    +    private static TlsConfiguration clientTlsConfiguration;
    +    private static TlsConfiguration trustOnlyTlsConfiguration;
    +
         private ListenHTTP proc;
         private TestRunner runner;
     
    @@ -95,6 +106,10 @@ public void setup() throws IOException {
             runner.setVariable(PORT_VARIABLE, Integer.toString(availablePort));
             runner.setVariable(BASEPATH_VARIABLE, HTTP_BASE_PATH);
     
    +        clientTlsConfiguration = new TlsConfiguration(CLIENT_KEYSTORE, KEYSTORE_PASSWORD, null, CLIENT_KEYSTORE_TYPE,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +        trustOnlyTlsConfiguration = new TlsConfiguration(null, null, null, null,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
         }
     
         @After
    @@ -142,7 +157,7 @@ public void testPOSTRequestsReturnCodeReceivedWithEL() throws Exception {
         @Test
         public void testSecurePOSTRequestsReceivedWithoutEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(false);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -155,7 +170,7 @@ public void testSecurePOSTRequestsReceivedWithoutEL() throws Exception {
         @Test
         public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(false);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -169,7 +184,7 @@ public void testSecurePOSTRequestsReturnCodeReceivedWithoutEL() throws Exception
         @Test
         public void testSecurePOSTRequestsReceivedWithEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(false);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
    @@ -182,7 +197,7 @@ public void testSecurePOSTRequestsReceivedWithEL() throws Exception {
         @Test
         public void testSecurePOSTRequestsReturnCodeReceivedWithEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(false);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -196,7 +211,7 @@ public void testSecurePOSTRequestsReturnCodeReceivedWithEL() throws Exception {
         @Test
         public void testSecureTwoWaySslPOSTRequestsReceivedWithoutEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(true);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -209,7 +224,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithoutEL() throws Exception
         @Test
         public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithoutEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(true);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -223,7 +238,7 @@ public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithoutEL() throws
         @Test
         public void testSecureTwoWaySslPOSTRequestsReceivedWithEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(true);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
    @@ -236,7 +251,7 @@ public void testSecureTwoWaySslPOSTRequestsReceivedWithEL() throws Exception {
         @Test
         public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithEL() throws Exception {
             SSLContextService sslContextService = configureProcessorSslContextService(true);
    -        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardRestrictedSSLContextService.RESTRICTED_SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    @@ -250,7 +265,7 @@ public void testSecureTwoWaySslPOSTRequestsReturnCodeReceivedWithEL() throws Exc
         @Test
         public void testSecureInvalidSSLConfiguration() throws Exception {
             SSLContextService sslContextService = configureInvalidProcessorSslContextService();
    -        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenHTTP.PORT, HTTP_SERVER_PORT_EL);
    @@ -264,29 +279,7 @@ private int executePOST(String message, boolean secure, boolean twoWaySsl) throw
             HttpURLConnection connection;
     
             if (secure) {
    -            final HttpsURLConnection sslCon = (HttpsURLConnection) url.openConnection();
    -            if (twoWaySsl) {
    -                // use a client certificate, do not reuse the server's keystore
    -                SSLContext clientSslContext = SslContextFactory.createSslContext(
    -                        "src/test/resources/client-keystore.p12",
    -                        "passwordpassword".toCharArray(),
    -                        "PKCS12",
    -                        "src/test/resources/truststore.jks",
    -                        "passwordpassword".toCharArray(),
    -                        "JKS",
    -                        null,
    -                        "TLSv1.2");
    -                sslCon.setSSLSocketFactory(clientSslContext.getSocketFactory());
    -            } else {
    -                // with one-way SSL, the client still needs a truststore
    -                SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                        "src/test/resources/truststore.jks",
    -                        "passwordpassword".toCharArray(),
    -                        "JKS",
    -                        "TLSv1.2");
    -                sslCon.setSSLSocketFactory(clientSslContext.getSocketFactory());
    -            }
    -            connection = sslCon;
    +            connection = buildSecureConnection(twoWaySsl, url);
             } else {
                 connection = (HttpURLConnection) url.openConnection();
             }
    @@ -303,8 +296,22 @@ private int executePOST(String message, boolean secure, boolean twoWaySsl) throw
             return connection.getResponseCode();
         }
     
    +    private static HttpsURLConnection buildSecureConnection(boolean twoWaySsl, URL url) throws IOException, TlsException {
    +        final HttpsURLConnection sslCon = (HttpsURLConnection) url.openConnection();
    +        SSLContext clientSslContext;
    +        if (twoWaySsl) {
    +            // Use a client certificate, do not reuse the server's keystore
    +            clientSslContext = SslContextFactory.createSslContext(clientTlsConfiguration);
    +        } else {
    +            // With one-way SSL, the client still needs a truststore
    +            clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
    +        }
    +        sslCon.setSSLSocketFactory(clientSslContext.getSocketFactory());
    +        return sslCon;
    +    }
    +
         private String buildUrl(final boolean secure) {
    -      return String.format("%s://localhost:%s/%s", secure ? "https" : "http" , availablePort,  HTTP_BASE_PATH);
    +        return String.format("%s://localhost:%s/%s", secure ? "https" : "http", availablePort, HTTP_BASE_PATH);
         }
     
         private void testPOSTRequestsReceived(int returnCode, boolean secure, boolean twoWaySsl) throws Exception {
    @@ -326,23 +333,23 @@ private void testPOSTRequestsReceived(int returnCode, boolean secure, boolean tw
         }
     
         private void startWebServerAndSendRequests(Runnable sendRequestToWebserver, int numberOfExpectedFlowFiles, int returnCode) throws Exception {
    -      final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory();
    -      final ProcessContext context = runner.getProcessContext();
    -      proc.createHttpServer(context);
    +        final ProcessSessionFactory processSessionFactory = runner.getProcessSessionFactory();
    +        final ProcessContext context = runner.getProcessContext();
    +        proc.createHttpServer(context);
     
    -      new Thread(sendRequestToWebserver).start();
    +        new Thread(sendRequestToWebserver).start();
     
    -      long responseTimeout = 10000;
    +        long responseTimeout = 10000;
     
    -      int numTransferred = 0;
    -      long startTime = System.currentTimeMillis();
    -      while (numTransferred < numberOfExpectedFlowFiles && (System.currentTimeMillis() - startTime < responseTimeout)) {
    -          proc.onTrigger(context, processSessionFactory);
    -          numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size();
    -          Thread.sleep(100);
    -      }
    +        int numTransferred = 0;
    +        long startTime = System.currentTimeMillis();
    +        while (numTransferred < numberOfExpectedFlowFiles && (System.currentTimeMillis() - startTime < responseTimeout)) {
    +            proc.onTrigger(context, processSessionFactory);
    +            numTransferred = runner.getFlowFilesForRelationship(RELATIONSHIP_SUCCESS).size();
    +            Thread.sleep(100);
    +        }
     
    -      runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, numberOfExpectedFlowFiles);
    +        runner.assertTransferCount(ListenHTTP.RELATIONSHIP_SUCCESS, numberOfExpectedFlowFiles);
         }
     
         private void startWebServerAndSendMessages(final List<String> messages, int returnCode, boolean secure, boolean twoWaySsl)
    @@ -398,109 +405,111 @@ private SSLContextService configureInvalidProcessorSslContextService() throws In
         @Test(/*timeout=10000*/)
         public void testMultipartFormDataRequest() throws Exception {
     
    -      runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    -      runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
    -      runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_OK));
    -
    -      final SSLContextService sslContextService = runner.getControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, SSLContextService.class);
    -      final boolean isSecure = (sslContextService != null);
    -
    -      Runnable sendRequestToWebserver = () -> {
    -        try {
    -          MultipartBody multipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
    -              .addFormDataPart("p1", "v1")
    -              .addFormDataPart("p2", "v2")
    -              .addFormDataPart("file1", "my-file-text.txt", RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    -              .addFormDataPart("file2", "my-file-data.json", RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    -              .addFormDataPart("file3", "my-file-binary.bin", RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    -              .build();
    -
    -          Request request =
    -              new Request.Builder()
    -                .url(buildUrl(isSecure))
    -                .post(multipartBody)
    -                .build();
    -
    -          int timeout = 3000;
    -          OkHttpClient client = new OkHttpClient.Builder()
    -                .readTimeout(timeout, TimeUnit.MILLISECONDS)
    -                .writeTimeout(timeout, TimeUnit.MILLISECONDS)
    -                .build();
    -
    -          try (Response response = client.newCall(request).execute()) {
    -            Assert.assertTrue(String.format("Unexpected code: %s, body: %s", response.code(), response.body().string()), response.isSuccessful());
    -          }
    -        } catch (final Throwable t) {
    -          t.printStackTrace();
    -          Assert.fail(t.toString());
    -        }
    -      };
    -
    -
    -      startWebServerAndSendRequests(sendRequestToWebserver, 5, 200);
    -
    -      runner.assertAllFlowFilesTransferred(ListenHTTP.RELATIONSHIP_SUCCESS, 5);
    -      List<MockFlowFile> flowFilesForRelationship = runner.getFlowFilesForRelationship(ListenHTTP.RELATIONSHIP_SUCCESS);
    -      // Part fragments are not processed in the order we submitted them.
    -      // We cannot rely on the order we sent them in.
    -      MockFlowFile mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "p1");
    -      mff.assertAttributeEquals("http.multipart.name", "p1");
    -      mff.assertAttributeExists("http.multipart.size");
    -      mff.assertAttributeEquals("http.multipart.fragments.sequence.number", "1");
    -      mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    -      mff.assertAttributeExists("http.headers.multipart.content-disposition");
    -
    -      mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "p2");
    -      mff.assertAttributeEquals("http.multipart.name", "p2");
    -      mff.assertAttributeExists("http.multipart.size");
    -      mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    -      mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    -      mff.assertAttributeExists("http.headers.multipart.content-disposition");
    -
    -      mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file1");
    -      mff.assertAttributeEquals("http.multipart.name", "file1");
    -      mff.assertAttributeEquals("http.multipart.filename", "my-file-text.txt");
    -      mff.assertAttributeEquals("http.headers.multipart.content-type", "text/plain");
    -      mff.assertAttributeExists("http.multipart.size");
    -      mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    -      mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    -      mff.assertAttributeExists("http.headers.multipart.content-disposition");
    -
    -      mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file2");
    -      mff.assertAttributeEquals("http.multipart.name", "file2");
    -      mff.assertAttributeEquals("http.multipart.filename", "my-file-data.json");
    -      mff.assertAttributeEquals("http.headers.multipart.content-type", "application/json");
    -      mff.assertAttributeExists("http.multipart.size");
    -      mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    -      mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    -      mff.assertAttributeExists("http.headers.multipart.content-disposition");
    -
    -      mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file3");
    -      mff.assertAttributeEquals("http.multipart.name", "file3");
    -      mff.assertAttributeEquals("http.multipart.filename", "my-file-binary.bin");
    -      mff.assertAttributeEquals("http.headers.multipart.content-type", "application/octet-stream");
    -      mff.assertAttributeExists("http.multipart.size");
    -      mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    -      mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    -      mff.assertAttributeExists("http.headers.multipart.content-disposition");
    +        runner.setProperty(ListenHTTP.PORT, Integer.toString(availablePort));
    +        runner.setProperty(ListenHTTP.BASE_PATH, HTTP_BASE_PATH);
    +        runner.setProperty(ListenHTTP.RETURN_CODE, Integer.toString(HttpServletResponse.SC_OK));
    +
    +        final SSLContextService sslContextService = runner.getControllerService(SSL_CONTEXT_SERVICE_IDENTIFIER, SSLContextService.class);
    +        final boolean isSecure = (sslContextService != null);
    +
    +        Runnable sendRequestToWebserver = () -> {
    +            try {
    +                MultipartBody multipartBody = new MultipartBody.Builder().setType(MultipartBody.FORM)
    +                        .addFormDataPart("p1", "v1")
    +                        .addFormDataPart("p2", "v2")
    +                        .addFormDataPart("file1", "my-file-text.txt", RequestBody.create(MediaType.parse("text/plain"), createTextFile("my-file-text.txt", "Hello", "World")))
    +                        .addFormDataPart("file2", "my-file-data.json", RequestBody.create(MediaType.parse("application/json"), createTextFile("my-file-text.txt", "{ \"name\":\"John\", \"age\":30 }")))
    +                        .addFormDataPart("file3", "my-file-binary.bin", RequestBody.create(MediaType.parse("application/octet-stream"), generateRandomBinaryData(100)))
    +                        .build();
    +
    +                Request request =
    +                        new Request.Builder()
    +                                .url(buildUrl(isSecure))
    +                                .post(multipartBody)
    +                                .build();
    +
    +                int timeout = 3000;
    +                OkHttpClient client = new OkHttpClient.Builder()
    +                        .readTimeout(timeout, TimeUnit.MILLISECONDS)
    +                        .writeTimeout(timeout, TimeUnit.MILLISECONDS)
    +                        .build();
    +
    +                try (Response response = client.newCall(request).execute()) {
    +                    Assert.assertTrue(String.format("Unexpected code: %s, body: %s", response.code(), response.body().string()), response.isSuccessful());
    +                }
    +            } catch (final Throwable t) {
    +                t.printStackTrace();
    +                Assert.fail(t.toString());
    +            }
    +        };
    +
    +
    +        startWebServerAndSendRequests(sendRequestToWebserver, 5, 200);
    +
    +        runner.assertAllFlowFilesTransferred(ListenHTTP.RELATIONSHIP_SUCCESS, 5);
    +        List<MockFlowFile> flowFilesForRelationship = runner.getFlowFilesForRelationship(ListenHTTP.RELATIONSHIP_SUCCESS);
    +        // Part fragments are not processed in the order we submitted them.
    +        // We cannot rely on the order we sent them in.
    +        MockFlowFile mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "p1");
    +        mff.assertAttributeEquals("http.multipart.name", "p1");
    +        mff.assertAttributeExists("http.multipart.size");
    +        mff.assertAttributeEquals("http.multipart.fragments.sequence.number", "1");
    +        mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    +        mff.assertAttributeExists("http.headers.multipart.content-disposition");
    +
    +        mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "p2");
    +        mff.assertAttributeEquals("http.multipart.name", "p2");
    +        mff.assertAttributeExists("http.multipart.size");
    +        mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    +        mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    +        mff.assertAttributeExists("http.headers.multipart.content-disposition");
    +
    +        mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file1");
    +        mff.assertAttributeEquals("http.multipart.name", "file1");
    +        mff.assertAttributeEquals("http.multipart.filename", "my-file-text.txt");
    +        mff.assertAttributeEquals("http.headers.multipart.content-type", "text/plain");
    +        mff.assertAttributeExists("http.multipart.size");
    +        mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    +        mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    +        mff.assertAttributeExists("http.headers.multipart.content-disposition");
    +
    +        mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file2");
    +        mff.assertAttributeEquals("http.multipart.name", "file2");
    +        mff.assertAttributeEquals("http.multipart.filename", "my-file-data.json");
    +        mff.assertAttributeEquals("http.headers.multipart.content-type", "application/json");
    +        mff.assertAttributeExists("http.multipart.size");
    +        mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    +        mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    +        mff.assertAttributeExists("http.headers.multipart.content-disposition");
    +
    +        mff = findFlowFile(flowFilesForRelationship, "http.multipart.name", "file3");
    +        mff.assertAttributeEquals("http.multipart.name", "file3");
    +        mff.assertAttributeEquals("http.multipart.filename", "my-file-binary.bin");
    +        mff.assertAttributeEquals("http.headers.multipart.content-type", "application/octet-stream");
    +        mff.assertAttributeExists("http.multipart.size");
    +        mff.assertAttributeExists("http.multipart.fragments.sequence.number");
    +        mff.assertAttributeEquals("http.multipart.fragments.total.number", "5");
    +        mff.assertAttributeExists("http.headers.multipart.content-disposition");
         }
     
    -     private byte[] generateRandomBinaryData(int i) {
    -      byte[] bytes = new byte[100];
    -      new Random().nextBytes(bytes);
    -      return bytes;
    +    private byte[] generateRandomBinaryData(int i) {
    +        byte[] bytes = new byte[100];
    +        new Random().nextBytes(bytes);
    +        return bytes;
         }
    -     private File createTextFile(String fileName, String... lines) throws IOException {
    -      File file = new File("target/" + fileName);
    -      file.deleteOnExit();
    -      for (String string : lines) {
    -        Files.append(string, file, Charsets.UTF_8);
    -      }
    -      return file;
    +
    +    private File createTextFile(String fileName, String... lines) throws IOException {
    +        File file = new File("target/" + fileName);
    +        file.deleteOnExit();
    +        for (String string : lines) {
    +            Files.append(string, file, Charsets.UTF_8);
    +        }
    +        return file;
         }
    -     protected MockFlowFile findFlowFile(List<MockFlowFile> flowFilesForRelationship, String attributeName, String attributeValue) {
    -      Optional<MockFlowFile> optional = Iterables.tryFind(flowFilesForRelationship, ff -> ff.getAttribute(attributeName).equals(attributeValue));
    -      Assert.assertTrue(optional.isPresent());
    -      return optional.get();
    +
    +    protected MockFlowFile findFlowFile(List<MockFlowFile> flowFilesForRelationship, String attributeName, String attributeValue) {
    +        Optional<MockFlowFile> optional = Iterables.tryFind(flowFilesForRelationship, ff -> ff.getAttribute(attributeName).equals(attributeValue));
    +        Assert.assertTrue(optional.isPresent());
    +        return optional.get();
         }
     }
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenRELP.java+15 12 modified
    @@ -16,6 +16,14 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.net.Socket;
    +import java.nio.channels.SocketChannel;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.List;
    +import java.util.concurrent.BlockingQueue;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.commons.lang3.StringUtils;
     import org.apache.nifi.annotation.lifecycle.OnScheduled;
    @@ -30,6 +38,7 @@
     import org.apache.nifi.provenance.ProvenanceEventRecord;
     import org.apache.nifi.provenance.ProvenanceEventType;
     import org.apache.nifi.reporting.InitializationException;
    +import org.apache.nifi.security.util.SslContextFactory;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.ssl.StandardSSLContextService;
     import org.apache.nifi.util.MockFlowFile;
    @@ -40,17 +49,11 @@
     import org.junit.Test;
     import org.mockito.Mockito;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.net.Socket;
    -import java.nio.channels.SocketChannel;
    -import java.nio.charset.StandardCharsets;
    -import java.util.ArrayList;
    -import java.util.List;
    -import java.util.concurrent.BlockingQueue;
    -
     public class TestListenRELP {
     
    +    // TODO: The NiFi SSL classes don't yet support TLSv1.3, so set the CS version explicitly
    +    private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
    +
         public static final String OPEN_FRAME_DATA = "relp_version=0\nrelp_software=librelp,1.2.7,http://librelp.adiscon.com\ncommands=syslog";
         public static final String SYSLOG_FRAME_DATA = "this is a syslog message here";
     
    @@ -152,7 +155,7 @@ public void testBatching() throws IOException, InterruptedException {
         public void testTLS() throws InitializationException, IOException, InterruptedException {
             final SSLContextService sslContextService = new StandardSSLContextService();
             runner.addControllerService("ssl-context", sslContextService);
    -        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, TLS_PROTOCOL_VERSION);
             runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, "src/test/resources/truststore.jks");
             runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, "passwordpassword");
             runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, "JKS");
    @@ -161,7 +164,7 @@ public void testTLS() throws InitializationException, IOException, InterruptedEx
             runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
             runner.enableControllerService(sslContextService);
     
    -        runner.setProperty(PostHTTP.SSL_CONTEXT_SERVICE, "ssl-context");
    +        runner.setProperty(ListenRELP.SSL_CONTEXT_SERVICE, "ssl-context");
     
             final List<RELPFrame> frames = new ArrayList<>();
             frames.add(OPEN_FRAME);
    @@ -223,7 +226,7 @@ protected void run(final List<RELPFrame> frames, final int expectedTransferred,
     
                 // create either a regular socket or ssl socket based on context being passed in
                 if (sslContextService != null) {
    -                final SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.REQUIRED);
    +                final SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.REQUIRED);
                     socket = sslContext.getSocketFactory().createSocket("localhost", realPort);
                 } else {
                     socket = new Socket("localhost", realPort);
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCP.java+48 50 modified
    @@ -16,11 +16,19 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.IOException;
    +import java.net.Socket;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.List;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.processor.ProcessContext;
     import org.apache.nifi.processor.ProcessSessionFactory;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.ssl.StandardRestrictedSSLContextService;
     import org.apache.nifi.ssl.StandardSSLContextService;
    @@ -31,20 +39,23 @@
     import org.junit.Before;
     import org.junit.Test;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.IOException;
    -import java.net.Socket;
    -import java.nio.charset.StandardCharsets;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.ArrayList;
    -import java.util.List;
    -
     public class TestListenTCP {
     
    +    private static final String KEYSTORE = "src/test/resources/keystore.jks";
    +    private static final String KEYSTORE_PASSWORD = "passwordpassword";
    +    private static final String KEYSTORE_TYPE = "JKS";
    +    private static final String TRUSTSTORE = "src/test/resources/truststore.jks";
    +    private static final String TRUSTSTORE_PASSWORD = "passwordpassword";
    +    private static final String TRUSTSTORE_TYPE = "JKS";
    +    private static final String CLIENT_KEYSTORE = "src/test/resources/client-keystore.p12";
    +    private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
    +
    +    // TODO: The NiFi SSL classes don't yet support TLSv1.3, so set the CS version explicitly
    +    private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
    +
    +    private static TlsConfiguration clientTlsConfiguration;
    +    private static TlsConfiguration trustOnlyTlsConfiguration;
    +
         private ListenTCP proc;
         private TestRunner runner;
     
    @@ -53,6 +64,11 @@ public void setup() {
             proc = new ListenTCP();
             runner = TestRunners.newTestRunner(proc);
             runner.setProperty(ListenTCP.PORT, "0");
    +
    +        clientTlsConfiguration = new TlsConfiguration(CLIENT_KEYSTORE, KEYSTORE_PASSWORD, null, CLIENT_KEYSTORE_TYPE,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, TLS_PROTOCOL_VERSION);
    +        trustOnlyTlsConfiguration = new TlsConfiguration(null, null, null, null,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, TLS_PROTOCOL_VERSION);
         }
     
         @Test
    @@ -80,7 +96,7 @@ public void testListenTCP() throws IOException, InterruptedException {
             runTCP(messages, messages.size(), null);
     
             List<MockFlowFile> mockFlowFiles = runner.getFlowFilesForRelationship(ListenTCP.REL_SUCCESS);
    -        for (int i=0; i < mockFlowFiles.size(); i++) {
    +        for (int i = 0; i < mockFlowFiles.size(); i++) {
                 mockFlowFiles.get(i).assertContentEquals("This is message " + (i + 1));
             }
         }
    @@ -109,9 +125,9 @@ public void testListenTCPBatching() throws IOException, InterruptedException {
     
         @Test
         public void testTLSClientAuthRequiredAndClientCertProvided() throws InitializationException, IOException, InterruptedException,
    -            UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    +            TlsException {
     
    -        runner.setProperty(ListenTCP.CLIENT_AUTH, SSLContextService.ClientAuth.REQUIRED.name());
    +        runner.setProperty(ListenTCP.CLIENT_AUTH, SslContextFactory.ClientAuth.REQUIRED.name());
             configureProcessorSslContextService();
     
             final List<String> messages = new ArrayList<>();
    @@ -122,29 +138,20 @@ public void testTLSClientAuthRequiredAndClientCertProvided() throws Initializati
             messages.add("This is message 5\n");
     
             // Make an SSLContext with a key and trust store to send the test messages
    -        final SSLContext clientSslContext = SslContextFactory.createSslContext(
    -                "src/test/resources/keystore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                org.apache.nifi.security.util.SslContextFactory.ClientAuth.valueOf("NONE"),
    -                "TLSv1.2");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(clientTlsConfiguration, SslContextFactory.ClientAuth.NONE);
     
             runTCP(messages, messages.size(), clientSslContext);
     
             List<MockFlowFile> mockFlowFiles = runner.getFlowFilesForRelationship(ListenTCP.REL_SUCCESS);
    -        for (int i=0; i < mockFlowFiles.size(); i++) {
    +        for (int i = 0; i < mockFlowFiles.size(); i++) {
                 mockFlowFiles.get(i).assertContentEquals("This is message " + (i + 1));
             }
         }
     
         @Test
    -    public void testTLSClientAuthRequiredAndClientCertNotProvided() throws InitializationException, IOException, InterruptedException,
    -            UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    +    public void testTLSClientAuthRequiredAndClientCertNotProvided() throws InitializationException, TlsException {
     
    -        runner.setProperty(ListenTCP.CLIENT_AUTH, SSLContextService.ClientAuth.REQUIRED.name());
    +        runner.setProperty(ListenTCP.CLIENT_AUTH, SslContextFactory.ClientAuth.REQUIRED.name());
             configureProcessorSslContextService();
     
             final List<String> messages = new ArrayList<>();
    @@ -155,11 +162,7 @@ public void testTLSClientAuthRequiredAndClientCertNotProvided() throws Initializ
             messages.add("This is message 5\n");
     
             // Make an SSLContext that only has the trust store, this should not work since the processor has client auth REQUIRED
    -        final SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "TLSv1.2");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
     
             try {
                 runTCP(messages, messages.size(), clientSslContext);
    @@ -170,10 +173,9 @@ public void testTLSClientAuthRequiredAndClientCertNotProvided() throws Initializ
         }
     
         @Test
    -    public void testTLSClientAuthNoneAndClientCertNotProvided() throws InitializationException, IOException, InterruptedException,
    -            UnrecoverableKeyException, CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    +    public void testTLSClientAuthNoneAndClientCertNotProvided() throws InitializationException, IOException, InterruptedException, TlsException {
     
    -        runner.setProperty(ListenTCP.CLIENT_AUTH, SSLContextService.ClientAuth.NONE.name());
    +        runner.setProperty(ListenTCP.CLIENT_AUTH, SslContextFactory.ClientAuth.NONE.name());
             configureProcessorSslContextService();
     
             final List<String> messages = new ArrayList<>();
    @@ -184,16 +186,12 @@ public void testTLSClientAuthNoneAndClientCertNotProvided() throws Initializatio
             messages.add("This is message 5\n");
     
             // Make an SSLContext that only has the trust store, this should not work since the processor has client auth REQUIRED
    -        final SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "TLSv1.2");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
     
             runTCP(messages, messages.size(), clientSslContext);
     
             List<MockFlowFile> mockFlowFiles = runner.getFlowFilesForRelationship(ListenTCP.REL_SUCCESS);
    -        for (int i=0; i < mockFlowFiles.size(); i++) {
    +        for (int i = 0; i < mockFlowFiles.size(); i++) {
                 mockFlowFiles.get(i).assertContentEquals("This is message " + (i + 1));
             }
         }
    @@ -243,7 +241,7 @@ protected void runTCP(final List<String> messages, final int expectedTransferred
                 // call onTrigger until we processed all the frames, or a certain amount of time passes
                 int numTransferred = 0;
                 long startTime = System.currentTimeMillis();
    -            while (numTransferred < expectedTransferred  && (System.currentTimeMillis() - startTime < responseTimeout)) {
    +            while (numTransferred < expectedTransferred && (System.currentTimeMillis() - startTime < responseTimeout)) {
                     proc.onTrigger(context, processSessionFactory);
                     numTransferred = runner.getFlowFilesForRelationship(ListenTCP.REL_SUCCESS).size();
                     Thread.sleep(100);
    @@ -261,13 +259,13 @@ protected void runTCP(final List<String> messages, final int expectedTransferred
         private SSLContextService configureProcessorSslContextService() throws InitializationException {
             final SSLContextService sslContextService = new StandardRestrictedSSLContextService();
             runner.addControllerService("ssl-context", sslContextService);
    -        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, "src/test/resources/truststore.jks");
    -        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, "passwordpassword");
    -        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, "JKS");
    -        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, "src/test/resources/keystore.jks");
    -        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, "passwordpassword");
    -        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, "JKS");
    -        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, "TLSv1.2");
    +        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE, KEYSTORE);
    +        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_PASSWORD, KEYSTORE_PASSWORD);
    +        runner.setProperty(sslContextService, StandardSSLContextService.KEYSTORE_TYPE, KEYSTORE_TYPE);
    +        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE, TRUSTSTORE);
    +        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_PASSWORD, TRUSTSTORE_PASSWORD);
    +        runner.setProperty(sslContextService, StandardSSLContextService.TRUSTSTORE_TYPE, TRUSTSTORE_TYPE);
    +        runner.setProperty(sslContextService, StandardSSLContextService.SSL_ALGORITHM, TLS_PROTOCOL_VERSION);
             runner.enableControllerService(sslContextService);
     
             runner.setProperty(ListenTCP.SSL_CONTEXT_SERVICE, "ssl-context");
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestListenTCPRecord.java+42 45 modified
    @@ -16,13 +16,23 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.io.Closeable;
    +import java.io.IOException;
    +import java.net.Socket;
    +import java.nio.charset.StandardCharsets;
    +import java.util.ArrayList;
    +import java.util.Collections;
    +import java.util.List;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.json.JsonTreeReader;
     import org.apache.nifi.processor.ProcessContext;
     import org.apache.nifi.processor.ProcessSessionFactory;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.schema.access.SchemaAccessUtils;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.serialization.RecordReaderFactory;
     import org.apache.nifi.serialization.RecordSetWriterFactory;
     import org.apache.nifi.serialization.record.MockRecordWriter;
    @@ -38,24 +48,24 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.net.ssl.SSLContext;
    -import java.io.Closeable;
    -import java.io.IOException;
    -import java.net.Socket;
    -import java.nio.charset.StandardCharsets;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.ArrayList;
    -import java.util.Collections;
    -import java.util.List;
    -
     public class TestListenTCPRecord {
    -
         static final Logger LOGGER = LoggerFactory.getLogger(TestListenTCPRecord.class);
     
    +    private static final String KEYSTORE = "src/test/resources/keystore.jks";
    +    private static final String KEYSTORE_PASSWORD = "passwordpassword";
    +    private static final String KEYSTORE_TYPE = "JKS";
    +    private static final String TRUSTSTORE = "src/test/resources/truststore.jks";
    +    private static final String TRUSTSTORE_PASSWORD = "passwordpassword";
    +    private static final String TRUSTSTORE_TYPE = "JKS";
    +    private static final String CLIENT_KEYSTORE = "src/test/resources/client-keystore.p12";
    +    private static final String CLIENT_KEYSTORE_TYPE = "PKCS12";
    +
    +    // TODO: The NiFi SSL classes don't yet support TLSv1.3, so set the CS version explicitly
    +    private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
    +
    +    private static TlsConfiguration clientTlsConfiguration;
    +    private static TlsConfiguration trustOnlyTlsConfiguration;
    +
         static final String SCHEMA_TEXT = "{\n" +
                 "  \"name\": \"syslogRecord\",\n" +
                 "  \"namespace\": \"nifi\",\n" +
    @@ -68,6 +78,7 @@ public class TestListenTCPRecord {
                 "}";
     
         static final List<String> DATA;
    +
         static {
             final List<String> data = new ArrayList<>();
             data.add("[");
    @@ -101,6 +112,11 @@ public void setup() throws InitializationException {
     
             runner.setProperty(ListenTCPRecord.RECORD_READER, readerId);
             runner.setProperty(ListenTCPRecord.RECORD_WRITER, writerId);
    +
    +        clientTlsConfiguration = new TlsConfiguration(CLIENT_KEYSTORE, KEYSTORE_PASSWORD, null, CLIENT_KEYSTORE_TYPE,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, TLS_PROTOCOL_VERSION);
    +        trustOnlyTlsConfiguration = new TlsConfiguration(null, null, null, null,
    +                TRUSTSTORE, TRUSTSTORE_PASSWORD, TRUSTSTORE_TYPE, TLS_PROTOCOL_VERSION);
         }
     
         @Test
    @@ -123,7 +139,7 @@ public void testOneRecordPerFlowFile() throws IOException, InterruptedException
             runTCP(DATA, 3, null);
     
             List<MockFlowFile> mockFlowFiles = runner.getFlowFilesForRelationship(ListenTCPRecord.REL_SUCCESS);
    -        for (int i=0; i < mockFlowFiles.size(); i++) {
    +        for (int i = 0; i < mockFlowFiles.size(); i++) {
                 final MockFlowFile flowFile = mockFlowFiles.get(i);
                 flowFile.assertAttributeEquals("record.count", "1");
     
    @@ -153,22 +169,13 @@ public void testMultipleRecordsPerFlowFileLessThanBatchSize() throws IOException
         }
     
         @Test
    -    public void testTLSClientAuthRequiredAndClientCertProvided() throws InitializationException, IOException, InterruptedException, UnrecoverableKeyException,
    -            CertificateException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
    +    public void testTLSClientAuthRequiredAndClientCertProvided() throws InitializationException, IOException, InterruptedException, TlsException {
     
    -        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SSLContextService.ClientAuth.REQUIRED.name());
    +        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SslContextFactory.ClientAuth.REQUIRED.name());
             configureProcessorSslContextService();
     
             // Make an SSLContext with a key and trust store to send the test messages
    -        final SSLContext clientSslContext = SslContextFactory.createSslContext(
    -                "src/test/resources/keystore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                org.apache.nifi.security.util.SslContextFactory.ClientAuth.valueOf("NONE"),
    -                "TLSv1.2");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(clientTlsConfiguration);
     
             runTCP(DATA, 1, clientSslContext);
     
    @@ -183,36 +190,26 @@ public void testTLSClientAuthRequiredAndClientCertProvided() throws Initializati
         }
     
         @Test
    -    public void testTLSClientAuthRequiredAndClientCertNotProvided() throws InitializationException, CertificateException, UnrecoverableKeyException,
    -            NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
    +    public void testTLSClientAuthRequiredAndClientCertNotProvided() throws InitializationException, IOException, InterruptedException, TlsException {
     
    -        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SSLContextService.ClientAuth.REQUIRED.name());
    +        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SslContextFactory.ClientAuth.REQUIRED.name());
             runner.setProperty(ListenTCPRecord.READ_TIMEOUT, "5 seconds");
             configureProcessorSslContextService();
     
             // Make an SSLContext that only has the trust store, this should not work since the processor has client auth REQUIRED
    -        final SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "TLS");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
     
             runTCP(DATA, 0, clientSslContext);
         }
     
         @Test
    -    public void testTLSClientAuthNoneAndClientCertNotProvided() throws InitializationException, CertificateException, UnrecoverableKeyException,
    -            NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException, InterruptedException {
    +    public void testTLSClientAuthNoneAndClientCertNotProvided() throws InitializationException, IOException, InterruptedException, TlsException {
     
    -        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SSLContextService.ClientAuth.NONE.name());
    +        runner.setProperty(ListenTCPRecord.CLIENT_AUTH, SslContextFactory.ClientAuth.NONE.name());
             configureProcessorSslContextService();
     
             // Make an SSLContext that only has the trust store, this should work since the processor has client auth NONE
    -        final SSLContext clientSslContext = SslContextFactory.createTrustSslContext(
    -                "src/test/resources/truststore.jks",
    -                "passwordpassword".toCharArray(),
    -                "jks",
    -                "TLSv1.2");
    +        final SSLContext clientSslContext = SslContextFactory.createSslContext(trustOnlyTlsConfiguration);
     
             runTCP(DATA, 1, clientSslContext);
     
    @@ -248,7 +245,7 @@ protected void runTCP(final List<String> messages, final int expectedTransferred
                 // call onTrigger until we processed all the records, or a certain amount of time passes
                 int numTransferred = 0;
                 long startTime = System.currentTimeMillis();
    -            while (numTransferred < expectedTransferred  && (System.currentTimeMillis() - startTime < timeout)) {
    +            while (numTransferred < expectedTransferred && (System.currentTimeMillis() - startTime < timeout)) {
                     proc.onTrigger(context, processSessionFactory);
                     numTransferred = runner.getFlowFilesForRelationship(ListenTCPRecord.REL_SUCCESS).size();
                     Thread.sleep(100);
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/TestPutTcpSSL.java+6 4 modified
    @@ -16,16 +16,18 @@
      */
     package org.apache.nifi.processors.standard;
     
    +import java.util.HashMap;
    +import java.util.Map;
     import org.apache.nifi.processors.standard.util.TestPutTCPCommon;
     import org.apache.nifi.reporting.InitializationException;
     import org.apache.nifi.ssl.StandardSSLContextService;
     
    -import java.util.HashMap;
    -import java.util.Map;
    -
     public class TestPutTcpSSL extends TestPutTCPCommon {
         private static Map<String, String> sslProperties;
     
    +    // TODO: The NiFi SSL classes don't yet support TLSv1.3, so set the CS version explicitly
    +    private static final String TLS_PROTOCOL_VERSION = "TLSv1.2";
    +
         public TestPutTcpSSL() {
             super();
             ssl = true;
    @@ -63,7 +65,7 @@ private static Map<String, String> createSslProperties() {
             map.put(StandardSSLContextService.TRUSTSTORE.getName(), "src/test/resources/truststore.jks");
             map.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), "passwordpassword");
             map.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), "JKS");
    -        map.put(StandardSSLContextService.SSL_ALGORITHM.getName(), "TLSv1.2");
    +        map.put(StandardSSLContextService.SSL_ALGORITHM.getName(), TLS_PROTOCOL_VERSION);
             return map;
         }
     }
    
  • nifi-nar-bundles/nifi-standard-bundle/nifi-standard-processors/src/test/java/org/apache/nifi/processors/standard/util/TCPTestServer.java+8 6 modified
    @@ -16,10 +16,6 @@
      */
     package org.apache.nifi.processors.standard.util;
     
    -import org.apache.nifi.security.util.SslContextFactory;
    -
    -import javax.net.ServerSocketFactory;
    -import javax.net.ssl.SSLContext;
     import java.io.IOException;
     import java.io.InputStream;
     import java.net.InetAddress;
    @@ -28,6 +24,11 @@
     import java.util.ArrayList;
     import java.util.List;
     import java.util.concurrent.ArrayBlockingQueue;
    +import javax.net.ServerSocketFactory;
    +import javax.net.ssl.SSLContext;
    +import org.apache.nifi.security.util.CertificateUtils;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     
     public class TCPTestServer implements Runnable {
     
    @@ -53,8 +54,9 @@ public TCPTestServer(final InetAddress ipAddress, final ArrayBlockingQueue<List<
         public synchronized void startServer(boolean ssl) throws Exception {
             if (!isServerRunning()) {
                 if(ssl){
    -                final SSLContext sslCtx = SslContextFactory.createSslContext("src/test/resources/keystore.jks","passwordpassword".toCharArray(), "JKS", "src/test/resources/truststore.jks",
    -                        "passwordpassword".toCharArray(), "JKS", SslContextFactory.ClientAuth.REQUIRED, "TLS");
    +                TlsConfiguration tlsConfiguration = new TlsConfiguration("src/test/resources/keystore.jks","passwordpassword", null, "JKS", "src/test/resources/truststore.jks",
    +                        "passwordpassword", "JKS", CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
    +                final SSLContext sslCtx = SslContextFactory.createSslContext(tlsConfiguration, SslContextFactory.ClientAuth.REQUIRED);
     
                     ServerSocketFactory sslSocketFactory = sslCtx.getServerSocketFactory();
                     serverSocket = sslSocketFactory.createServerSocket(0, 0, ipAddress);
    
  • nifi-nar-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-client-service/src/main/java/org/apache/nifi/distributed/cache/client/DistributedMapCacheClientService.java+2 3 modified
    @@ -29,7 +29,6 @@
     import java.util.concurrent.BlockingQueue;
     import java.util.concurrent.LinkedBlockingQueue;
     import java.util.concurrent.TimeUnit;
    -
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.SeeAlso;
    @@ -44,8 +43,8 @@
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.remote.StandardVersionNegotiator;
     import org.apache.nifi.remote.VersionNegotiator;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    @@ -503,7 +502,7 @@ private <T> T withCommsSession(final CommsAction<T> action) throws IOException {
             }
         }
     
    -    private static interface CommsAction<T> {
    +    private interface CommsAction<T> {
     
             T execute(CommsSession commsSession) throws IOException;
         }
    
  • nifi-nar-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-client-service/src/main/java/org/apache/nifi/distributed/cache/client/DistributedSetCacheClientService.java+1 2 modified
    @@ -25,7 +25,6 @@
     import java.util.concurrent.BlockingQueue;
     import java.util.concurrent.LinkedBlockingQueue;
     import java.util.concurrent.TimeUnit;
    -
     import org.apache.commons.io.IOUtils;
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.SeeAlso;
    @@ -40,8 +39,8 @@
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.remote.StandardVersionNegotiator;
     import org.apache.nifi.remote.VersionNegotiator;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    
  • nifi-nar-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/DistributedSetCacheServer.java+1 3 modified
    @@ -17,14 +17,12 @@
     package org.apache.nifi.distributed.cache.server;
     
     import java.io.File;
    -
     import javax.net.ssl.SSLContext;
    -
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
     import org.apache.nifi.controller.ConfigurationContext;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     
     @Tags({"distributed", "set", "distinct", "cache", "server"})
     @CapabilityDescription("Provides a set (collection of unique values) cache that can be accessed over a socket. "
    
  • nifi-nar-bundles/nifi-standard-services/nifi-distributed-cache-services-bundle/nifi-distributed-cache-server/src/main/java/org/apache/nifi/distributed/cache/server/map/DistributedMapCacheServer.java+1 3 modified
    @@ -18,18 +18,16 @@
     
     import java.io.File;
     import java.io.IOException;
    -
     import javax.net.ssl.SSLContext;
    -
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.SeeAlso;
     import org.apache.nifi.annotation.documentation.Tags;
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.distributed.cache.server.CacheServer;
     import org.apache.nifi.distributed.cache.server.DistributedCacheServer;
     import org.apache.nifi.distributed.cache.server.EvictionPolicy;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.ssl.SSLContextService;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
     
     @Tags({"distributed", "cluster", "map", "cache", "server", "key/value"})
     @CapabilityDescription("Provides a map (key/value) cache that can be accessed over a socket. Interaction with this service"
    
  • nifi-nar-bundles/nifi-standard-services/nifi-lookup-services-bundle/nifi-lookup-services/pom.xml+6 0 modified
    @@ -169,6 +169,12 @@
                 <version>${nifi.groovy.version}</version>
                 <scope>test</scope>
             </dependency>
    +        <dependency>
    +            <groupId>org.apache.nifi</groupId>
    +            <artifactId>nifi-web-utils</artifactId>
    +            <version>1.12.0-SNAPSHOT</version>
    +            <scope>compile</scope>
    +        </dependency>
         </dependencies>
         <build>
             <plugins>
    
  • nifi-nar-bundles/nifi-standard-services/nifi-lookup-services-bundle/nifi-lookup-services/src/main/java/org/apache/nifi/lookup/RestLookupService.java+22 55 modified
    @@ -17,10 +17,27 @@
     
     package org.apache.nifi.lookup;
     
    +import static org.apache.commons.lang3.StringUtils.trimToEmpty;
    +
     import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
     import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
     import com.burgstaller.okhttp.digest.CachingAuthenticator;
     import com.burgstaller.okhttp.digest.DigestAuthenticator;
    +import java.io.BufferedInputStream;
    +import java.io.IOException;
    +import java.io.InputStream;
    +import java.net.Proxy;
    +import java.util.Arrays;
    +import java.util.Collections;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +import java.util.Optional;
    +import java.util.Set;
    +import java.util.concurrent.ConcurrentHashMap;
    +import java.util.concurrent.TimeUnit;
    +import java.util.regex.Pattern;
    +import java.util.stream.Collectors;
     import okhttp3.Credentials;
     import okhttp3.MediaType;
     import okhttp3.OkHttpClient;
    @@ -40,7 +57,6 @@
     import org.apache.nifi.controller.AbstractControllerService;
     import org.apache.nifi.controller.ConfigurationContext;
     import org.apache.nifi.expression.ExpressionLanguageScope;
    -import org.apache.nifi.processor.exception.ProcessException;
     import org.apache.nifi.processor.util.StandardValidators;
     import org.apache.nifi.proxy.ProxyConfiguration;
     import org.apache.nifi.proxy.ProxyConfigurationService;
    @@ -49,7 +65,8 @@
     import org.apache.nifi.record.path.RecordPath;
     import org.apache.nifi.record.path.validation.RecordPathValidator;
     import org.apache.nifi.schema.access.SchemaNotFoundException;
    -import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.OkHttpClientUtils;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.serialization.MalformedRecordException;
     import org.apache.nifi.serialization.RecordReader;
     import org.apache.nifi.serialization.RecordReaderFactory;
    @@ -59,33 +76,6 @@
     import org.apache.nifi.serialization.record.RecordSchema;
     import org.apache.nifi.ssl.SSLContextService;
     import org.apache.nifi.util.StringUtils;
    -import org.apache.nifi.util.Tuple;
    -
    -import javax.net.ssl.SSLContext;
    -import javax.net.ssl.TrustManager;
    -import javax.net.ssl.X509TrustManager;
    -import java.io.BufferedInputStream;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.net.Proxy;
    -import java.security.KeyManagementException;
    -import java.security.KeyStoreException;
    -import java.security.NoSuchAlgorithmException;
    -import java.security.UnrecoverableKeyException;
    -import java.security.cert.CertificateException;
    -import java.util.Arrays;
    -import java.util.Collections;
    -import java.util.HashMap;
    -import java.util.List;
    -import java.util.Map;
    -import java.util.Optional;
    -import java.util.Set;
    -import java.util.concurrent.ConcurrentHashMap;
    -import java.util.concurrent.TimeUnit;
    -import java.util.regex.Pattern;
    -import java.util.stream.Collectors;
    -
    -import static org.apache.commons.lang3.StringUtils.trimToEmpty;
     
     @Tags({ "rest", "lookup", "json", "xml", "http" })
     @CapabilityDescription("Use a REST service to look up values.")
    @@ -240,34 +230,11 @@ public void onEnabled(final ConfigurationContext context) {
                 setProxy(builder);
             }
     
    +        // Apply the TLS configuration if present
             final SSLContextService sslService = context.getProperty(SSL_CONTEXT_SERVICE).asControllerService(SSLContextService.class);
             if (sslService != null) {
    -            Tuple<SSLContext, TrustManager[]> sslContextTuple = null;
    -            try {
    -                sslContextTuple = SslContextFactory.createTrustSslContextWithTrustManagers(
    -                        sslService.getKeyStoreFile(),
    -                        sslService.getKeyStorePassword() != null ? sslService.getKeyStorePassword().toCharArray() : null,
    -                        sslService.getKeyPassword() != null ? sslService.getKeyPassword().toCharArray() : null,
    -                        sslService.getKeyStoreType(),
    -                        sslService.getTrustStoreFile(),
    -                        sslService.getTrustStorePassword() != null ? sslService.getTrustStorePassword().toCharArray() : null,
    -                        sslService.getTrustStoreType(),
    -                        SslContextFactory.ClientAuth.WANT,
    -                        sslService.getSslAlgorithm()
    -                );
    -            } catch (CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | KeyStoreException | KeyManagementException | IOException e) {
    -                throw new ProcessException(e);
    -            }
    -            List<X509TrustManager> x509TrustManagers = Arrays.stream(sslContextTuple.getValue())
    -                    .filter(trustManager -> trustManager instanceof X509TrustManager)
    -                    .map(trustManager -> (X509TrustManager) trustManager).collect(Collectors.toList());
    -            builder.sslSocketFactory(sslContextTuple.getKey().getSocketFactory(), x509TrustManagers.get(0));
    -
    -            if (sslContextTuple.getValue().length > 0) {
    -                builder.sslSocketFactory(sslContextTuple.getKey().getSocketFactory(), (X509TrustManager) sslContextTuple.getValue()[0]);
    -            } else {
    -                throw new ProcessException("Failed to create SSL socket factory with trust manager.");
    -            }
    +            final TlsConfiguration tlsConfiguration = sslService.createTlsConfiguration();
    +            OkHttpClientUtils.applyTlsToOkHttpClientBuilder(tlsConfiguration, builder);
             }
     
             client = builder.build();
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardRestrictedSSLContextService.java+2 1 modified
    @@ -43,7 +43,8 @@ public class StandardRestrictedSSLContextService extends StandardSSLContextServi
                 .defaultValue("TLS")
                 .required(false)
                 .allowableValues(RestrictedSSLContextService.buildAlgorithmAllowableValues())
    -            .description("The algorithm to use for this SSL context. By default, this will choose the highest supported TLS protocol version.")
    +            .description(StandardSSLContextService.COMMON_TLS_PROTOCOL_DESCRIPTION +
    +                    "On Java 11, for example, TLSv1.3 will be the default, but if a client does not support it, TLSv1.2 will be offered as a fallback. TLSv1.0 and TLSv1.1 are not supported at all. ")
                 .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
                 .sensitive(false)
                 .build();
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/main/java/org/apache/nifi/ssl/StandardSSLContextService.java+30 41 modified
    @@ -40,6 +40,8 @@
     import org.apache.nifi.security.util.KeyStoreUtils;
     import org.apache.nifi.security.util.KeystoreType;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
    +import org.apache.nifi.security.util.TlsException;
     import org.apache.nifi.util.StringUtils;
     
     @Tags({"ssl", "secure", "certificate", "keystore", "truststore", "jks", "p12", "pkcs12", "pkcs", "tls"})
    @@ -53,6 +55,14 @@ public class StandardSSLContextService extends AbstractControllerService impleme
         public static final String STORE_TYPE_JKS = "JKS";
         public static final String STORE_TYPE_PKCS12 = "PKCS12";
     
    +    // Shared description for other SSL context services
    +    public static final String COMMON_TLS_PROTOCOL_DESCRIPTION = "The algorithm to use for this TLS/SSL context. \"TLS\" will instruct NiFi to allow all supported protocol versions " +
    +            "and choose the highest available protocol for each connection. " +
    +            "Java 8 enabled TLSv1.2, which is now the lowest version supported for incoming connections. " +
    +            "Java 11 enabled TLSv1.3. Depending on the version of Java NiFi is running on, different protocol versions will be available. " +
    +            "With \"TLS\" selected, as new protocol versions are made available, NiFi will automatically select them. " +
    +            "It is recommended unless a specific protocol version is needed. ";
    +
         public static final PropertyDescriptor TRUSTSTORE = new PropertyDescriptor.Builder()
                 .name("Truststore Filename")
                 .description("The fully-qualified filename of the Truststore")
    @@ -111,7 +121,8 @@ public class StandardSSLContextService extends AbstractControllerService impleme
                 .defaultValue("TLS")
                 .required(false)
                 .allowableValues(SSLContextService.buildAlgorithmAllowableValues())
    -            .description("The algorithm to use for this TLS/SSL context")
    +            .description(COMMON_TLS_PROTOCOL_DESCRIPTION +
    +                    "For outgoing connections, legacy protocol versions like \"TLSv1.0\" are supported, but discouraged unless necessary. ")
                 .addValidator(StandardValidators.NON_EMPTY_VALIDATOR)
                 .sensitive(false)
                 .build();
    @@ -210,48 +221,26 @@ protected int getValidationCacheExpiration() {
             return VALIDATION_CACHE_EXPIRATION;
         }
     
    +    /**
    +     * Returns a {@link TlsConfiguration} configured with the current properties of the controller
    +     * service. This is useful for transferring the TLS configuration values between services.
    +     *
    +     * @return the populated TlsConfiguration
    +     */
         @Override
    -    public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException {
    -        final String protocol = getSslAlgorithm();
    -        try {
    -            final PropertyValue keyPasswdProp = configContext.getProperty(KEY_PASSWORD);
    -            final PropertyValue truststorePasswordProp = configContext.getProperty(TRUSTSTORE_PASSWORD);
    -            final char[] keyPassword = keyPasswdProp.isSet() ? keyPasswdProp.getValue().toCharArray() : null;
    -            final char[] truststorePassword = truststorePasswordProp.isSet() ? truststorePasswordProp.getValue().toCharArray() : null;
    -
    -            final String keystoreFile = configContext.getProperty(KEYSTORE).getValue();
    -            if (keystoreFile == null) {
    -                // If keystore not specified, create SSL Context based only on trust store.
    -                return SslContextFactory.createTrustSslContext(
    -                        configContext.getProperty(TRUSTSTORE).getValue(),
    -                        truststorePassword,
    -                        configContext.getProperty(TRUSTSTORE_TYPE).getValue(),
    -                        protocol);
    -            }
    -
    -            final String truststoreFile = configContext.getProperty(TRUSTSTORE).getValue();
    -            if (truststoreFile == null) {
    -                // If truststore not specified, create SSL Context based only on key store.
    -                return SslContextFactory.createSslContext(
    -                        configContext.getProperty(KEYSTORE).getValue(),
    -                        configContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(),
    -                        keyPassword,
    -                        configContext.getProperty(KEYSTORE_TYPE).getValue(),
    -                        protocol);
    -            }
    +    public TlsConfiguration createTlsConfiguration() {
    +        return new TlsConfiguration(getKeyStoreFile(), getKeyStorePassword(),
    +                getKeyPassword(), getKeyStoreType(), getTrustStoreFile(),
    +                getTrustStorePassword(), getTrustStoreType(), getSslAlgorithm());
    +    }
     
    -            return SslContextFactory.createSslContext(
    -                    configContext.getProperty(KEYSTORE).getValue(),
    -                    configContext.getProperty(KEYSTORE_PASSWORD).getValue().toCharArray(),
    -                    keyPassword,
    -                    configContext.getProperty(KEYSTORE_TYPE).getValue(),
    -                    configContext.getProperty(TRUSTSTORE).getValue(),
    -                    truststorePassword,
    -                    configContext.getProperty(TRUSTSTORE_TYPE).getValue(),
    -                    org.apache.nifi.security.util.SslContextFactory.ClientAuth.valueOf(clientAuth.name()),
    -                    protocol);
    -        } catch (final Exception e) {
    -            throw new ProcessException(e);
    +    @Override
    +    public SSLContext createSSLContext(final SslContextFactory.ClientAuth clientAuth) throws ProcessException {
    +        try {
    +            return SslContextFactory.createSslContext(createTlsConfiguration(), clientAuth);
    +        } catch (TlsException e) {
    +            getLogger().error("Encountered an error creating the SSL context from the SSL context service: {}", new String[]{e.getLocalizedMessage()});
    +            throw new ProcessException("Error creating SSL context", e);
             }
         }
     
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/groovy/org/apache/nifi/ssl/StandardSSLContextServiceTest.groovy+3 2 modified
    @@ -19,6 +19,7 @@ package org.apache.nifi.ssl
     import org.apache.nifi.components.ValidationContext
     import org.apache.nifi.components.ValidationResult
     import org.apache.nifi.components.Validator
    +import org.apache.nifi.security.util.SslContextFactory
     import org.apache.nifi.state.MockStateManager
     import org.apache.nifi.util.MockProcessContext
     import org.apache.nifi.util.MockValidationContext
    @@ -175,7 +176,7 @@ class StandardSSLContextServiceTest {
             runner.assertValid(sslContextService)
     
             // Act
    -        SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE)
    +        SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE)
     
             // Assert
             assert sslContext
    @@ -197,7 +198,7 @@ class StandardSSLContextServiceTest {
             runner.assertValid(sslContextService)
     
             // Act
    -        SSLContext sslContext = sslContextService.createSSLContext(SSLContextService.ClientAuth.NONE)
    +        SSLContext sslContext = sslContextService.createSSLContext(SslContextFactory.ClientAuth.NONE)
     
             // Assert
             assert sslContext
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/RestrictedSSLContextServiceTest.java+3 1 modified
    @@ -21,9 +21,11 @@
     import static org.hamcrest.core.IsNull.notNullValue;
     import static org.junit.Assert.assertTrue;
     
    +import java.util.Arrays;
     import java.util.HashSet;
     import java.util.Set;
     import org.apache.nifi.components.AllowableValue;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.junit.Test;
     
     public class RestrictedSSLContextServiceTest {
    @@ -32,7 +34,7 @@ public class RestrictedSSLContextServiceTest {
         public void testTLSAlgorithms() {
             final Set<String> expected = new HashSet<>();
             expected.add("TLS");
    -        expected.add("TLSv1.2");
    +        expected.addAll(Arrays.asList(CertificateUtils.getCurrentSupportedTlsProtocolVersions()));
     
             final AllowableValue[] allowableValues = RestrictedSSLContextService.buildAlgorithmAllowableValues();
             assertThat(allowableValues, notNullValue());
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-bundle/nifi-ssl-context-service/src/test/java/org/apache/nifi/ssl/SSLContextServiceTest.java+8 1 modified
    @@ -38,12 +38,13 @@
     import org.apache.nifi.components.ValidationContext;
     import org.apache.nifi.components.ValidationResult;
     import org.apache.nifi.reporting.InitializationException;
    -import org.apache.nifi.ssl.SSLContextService.ClientAuth;
    +import org.apache.nifi.security.util.SslContextFactory.ClientAuth;
     import org.apache.nifi.util.MockProcessContext;
     import org.apache.nifi.util.MockValidationContext;
     import org.apache.nifi.util.TestRunner;
     import org.apache.nifi.util.TestRunners;
     import org.junit.Assert;
    +import org.junit.Ignore;
     import org.junit.Rule;
     import org.junit.Test;
     import org.junit.rules.TemporaryFolder;
    @@ -262,7 +263,10 @@ public void testGoodTrustOnly() {
             }
         }
     
    +    // TODO: Remove test
    +    @Ignore("This test is no longer valid as a truststore must be present if the keystore is")
         @Test
    +    @Deprecated
         public void testGoodKeyOnly() {
             try {
                 TestRunner runner = TestRunners.newTestRunner(TestProcessor.class);
    @@ -301,6 +305,9 @@ public void testDifferentKeyPassword() {
                 properties.put(StandardSSLContextService.KEYSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
                 properties.put(StandardSSLContextService.KEY_PASSWORD.getName(), "keypassword");
                 properties.put(StandardSSLContextService.KEYSTORE_TYPE.getName(), JKS_TYPE);
    +            properties.put(StandardSSLContextService.TRUSTSTORE.getName(), TRUSTSTORE_PATH);
    +            properties.put(StandardSSLContextService.TRUSTSTORE_PASSWORD.getName(), KEYSTORE_AND_TRUSTSTORE_PASSWORD);
    +            properties.put(StandardSSLContextService.TRUSTSTORE_TYPE.getName(), JKS_TYPE);
                 runner.addControllerService("test-diff-keys", service, properties);
                 runner.enableControllerService(service);
     
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/pom.xml+6 0 modified
    @@ -26,5 +26,11 @@
                 <groupId>org.apache.nifi</groupId>
                 <artifactId>nifi-api</artifactId>
             </dependency>
    +        <dependency>
    +            <groupId>org.apache.nifi</groupId>
    +            <artifactId>nifi-security-utils</artifactId>
    +            <version>1.12.0-SNAPSHOT</version>
    +            <scope>compile</scope>
    +        </dependency>
         </dependencies>
     </project>
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/RestrictedSSLContextService.java+3 2 modified
    @@ -17,12 +17,13 @@
     package org.apache.nifi.ssl;
     
     import java.util.ArrayList;
    +import java.util.Arrays;
     import java.util.Collections;
     import java.util.HashSet;
     import java.util.List;
     import java.util.Set;
    -
     import org.apache.nifi.components.AllowableValue;
    +import org.apache.nifi.security.util.CertificateUtils;
     
     /**
      * Simple extension of the regular {@link SSLContextService} to allow for restricted implementations
    @@ -47,7 +48,7 @@ static AllowableValue[] buildAlgorithmAllowableValues() {
             /*
              * Add specifically supported TLS versions
              */
    -        supportedProtocols.add("TLSv1.2");
    +        supportedProtocols.addAll(Arrays.asList(CertificateUtils.getCurrentSupportedTlsProtocolVersions()));
     
             final int numProtocols = supportedProtocols.size();
     
    
  • nifi-nar-bundles/nifi-standard-services/nifi-ssl-context-service-api/src/main/java/org/apache/nifi/ssl/SSLContextService.java+16 18 modified
    @@ -23,14 +23,14 @@
     import java.util.HashSet;
     import java.util.List;
     import java.util.Set;
    -
     import javax.net.ssl.SSLContext;
    -
     import org.apache.nifi.annotation.documentation.CapabilityDescription;
     import org.apache.nifi.annotation.documentation.Tags;
     import org.apache.nifi.components.AllowableValue;
     import org.apache.nifi.controller.ControllerService;
     import org.apache.nifi.processor.exception.ProcessException;
    +import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     
     /**
      * Definition for SSLContextService.
    @@ -41,32 +41,28 @@
             + "that configuration throughout the application")
     public interface SSLContextService extends ControllerService {
     
    -    public static enum ClientAuth {
    +    // May need to back out if NAR-specific API can't be modified in minor release
    +    TlsConfiguration createTlsConfiguration();
     
    -        WANT,
    -        REQUIRED,
    -        NONE
    -    }
    +    SSLContext createSSLContext(final SslContextFactory.ClientAuth clientAuth) throws ProcessException;
     
    -    public SSLContext createSSLContext(final ClientAuth clientAuth) throws ProcessException;
    +    String getTrustStoreFile();
     
    -    public String getTrustStoreFile();
    +    String getTrustStoreType();
     
    -    public String getTrustStoreType();
    +    String getTrustStorePassword();
     
    -    public String getTrustStorePassword();
    +    boolean isTrustStoreConfigured();
     
    -    public boolean isTrustStoreConfigured();
    +    String getKeyStoreFile();
     
    -    public String getKeyStoreFile();
    +    String getKeyStoreType();
     
    -    public String getKeyStoreType();
    +    String getKeyStorePassword();
     
    -    public String getKeyStorePassword();
    +    String getKeyPassword();
     
    -    public String getKeyPassword();
    -
    -    public boolean isKeyStoreConfigured();
    +    boolean isKeyStoreConfigured();
     
         String getSslAlgorithm();
     
    @@ -83,6 +79,8 @@ static AllowableValue[] buildAlgorithmAllowableValues() {
              * see: http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
              */
             supportedProtocols.add("TLS");
    +
    +        // This is still available for outgoing connections to legacy services, but can be disabled with jdk.tls.disabledAlgorithms
             supportedProtocols.add("SSL");
     
             // Determine those provided by the JVM on the system
    
  • nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/NiFiClientConfig.java+9 9 modified
    @@ -16,27 +16,27 @@
      */
     package org.apache.nifi.toolkit.cli.impl.client.nifi;
     
    -import org.apache.nifi.registry.security.util.KeyStoreUtils;
    -import org.apache.nifi.registry.security.util.KeystoreType;
    -
    +import java.io.File;
    +import java.io.FileInputStream;
    +import java.io.InputStream;
    +import java.security.KeyStore;
    +import java.security.SecureRandom;
     import javax.net.ssl.HostnameVerifier;
     import javax.net.ssl.KeyManager;
     import javax.net.ssl.KeyManagerFactory;
     import javax.net.ssl.SSLContext;
     import javax.net.ssl.TrustManager;
     import javax.net.ssl.TrustManagerFactory;
    -import java.io.File;
    -import java.io.FileInputStream;
    -import java.io.InputStream;
    -import java.security.KeyStore;
    -import java.security.SecureRandom;
    +import org.apache.nifi.registry.security.util.KeyStoreUtils;
    +import org.apache.nifi.registry.security.util.KeystoreType;
    +import org.apache.nifi.security.util.CertificateUtils;
     
     /**
      * Configuration for a NiFiClient.
      */
     public class NiFiClientConfig {
     
    -    public static final String DEFAULT_PROTOCOL = "TLSv1.2";
    +    public static final String DEFAULT_PROTOCOL = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion();
     
         private final String baseUrl;
         private final SSLContext sslContext;
    
  • nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/client/TlsCertificateSigningRequestPerformer.java+1 1 modified
    @@ -99,7 +99,7 @@ public X509Certificate[] perform(KeyPair keyPair) throws IOException {
     
                 HttpClientBuilder httpClientBuilder = httpClientBuilderSupplier.get();
                 SSLContextBuilder sslContextBuilder = SSLContextBuilder.create();
    -            sslContextBuilder.useProtocol("TLSv1.2");
    +            sslContextBuilder.useProtocol(CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
     
                 // We will be validating that we are talking to the correct host once we get the response's hmac of the token and public key of the ca
                 sslContextBuilder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
    
  • nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/service/server/TlsCertificateAuthorityService.java+9 9 modified
    @@ -18,6 +18,14 @@
     package org.apache.nifi.toolkit.tls.service.server;
     
     import com.fasterxml.jackson.databind.ObjectMapper;
    +import java.io.File;
    +import java.io.FileOutputStream;
    +import java.io.IOException;
    +import java.security.KeyPair;
    +import java.security.KeyStore;
    +import java.security.cert.Certificate;
    +import java.security.cert.X509Certificate;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.toolkit.tls.configuration.TlsConfig;
     import org.apache.nifi.toolkit.tls.manager.TlsCertificateAuthorityManager;
     import org.apache.nifi.toolkit.tls.manager.writer.JsonConfigurationWriter;
    @@ -35,14 +43,6 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import java.io.File;
    -import java.io.FileOutputStream;
    -import java.io.IOException;
    -import java.security.KeyPair;
    -import java.security.KeyStore;
    -import java.security.cert.Certificate;
    -import java.security.cert.X509Certificate;
    -
     /**
      * Starts a Jetty server that will either load an existing CA or create one and use it to sign CSRs
      */
    @@ -63,7 +63,7 @@ private static Server createServer(Handler handler, int port, KeyStore keyStore,
             Server server = new Server();
     
             SslContextFactory sslContextFactory = new SslContextFactory();
    -        sslContextFactory.setIncludeProtocols("TLSv1.2");
    +        sslContextFactory.setIncludeProtocols(CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion());
             sslContextFactory.setKeyStore(keyStore);
             sslContextFactory.setKeyManagerPassword(keyPassword);
     
    
  • nifi-toolkit/nifi-toolkit-tls/src/main/java/org/apache/nifi/toolkit/tls/status/TlsToolkitGetStatusCommandLine.java+17 30 modified
    @@ -16,8 +16,13 @@
      */
     package org.apache.nifi.toolkit.tls.status;
     
    +import java.net.URI;
    +import java.net.URISyntaxException;
    +import javax.net.ssl.SSLContext;
     import org.apache.commons.cli.CommandLine;
    +import org.apache.nifi.security.util.CertificateUtils;
     import org.apache.nifi.security.util.SslContextFactory;
    +import org.apache.nifi.security.util.TlsConfiguration;
     import org.apache.nifi.toolkit.tls.commandLine.BaseCommandLine;
     import org.apache.nifi.toolkit.tls.commandLine.CommandLineParseException;
     import org.apache.nifi.toolkit.tls.commandLine.ExitCode;
    @@ -26,10 +31,6 @@
     import org.slf4j.Logger;
     import org.slf4j.LoggerFactory;
     
    -import javax.net.ssl.SSLContext;
    -import java.net.URI;
    -import java.net.URISyntaxException;
    -
     public class TlsToolkitGetStatusCommandLine extends BaseCommandLine {
     
         private final Logger logger = LoggerFactory.getLogger(TlsToolkitGetStatusCommandLine.class);
    @@ -44,7 +45,7 @@ public class TlsToolkitGetStatusCommandLine extends BaseCommandLine {
         public static final String TRUSTSTORE_PASSWORD_ARG = "trustStorePassword";
         public static final String PROTOCOL_ARG = "protocol";
     
    -    public static final String DEFAULT_PROTOCOL = "TLS";
    +    public static final String DEFAULT_PROTOCOL = CertificateUtils.getHighestCurrentSupportedTlsProtocolVersion();
         public static final String DEFAULT_KEYSTORE_TYPE = "JKS";
     
         public static final String DESCRIPTION = "Checks the status of an HTTPS endpoint by making a GET request using a supplied keystore and truststore.";
    @@ -101,8 +102,9 @@ protected void postParse(CommandLine commandLine) throws CommandLineParseExcepti
                 printUsageAndThrow("Invalid Url", ExitCode.INVALID_ARGS);
             }
     
    +        // TODO: Refactor this whole thing
             final String keystoreFilename = commandLine.getOptionValue(KEYSTORE_ARG);
    -        final String keystoreTypeStr = commandLine.getOptionValue(KEYSTORE_TYPE_ARG, DEFAULT_KEYSTORE_TYPE);
    +        String keystoreTypeStr = commandLine.getOptionValue(KEYSTORE_TYPE_ARG, DEFAULT_KEYSTORE_TYPE);
             final String keystorePassword = commandLine.getOptionValue(KEYSTORE_PASSWORD_ARG);
             final String keyPassword = commandLine.getOptionValue(KEY_PASSWORD_ARG);
     
    @@ -112,34 +114,19 @@ protected void postParse(CommandLine commandLine) throws CommandLineParseExcepti
     
             final String protocol = commandLine.getOptionValue(PROTOCOL_ARG, DEFAULT_PROTOCOL);
     
    -        final boolean keystoreProvided = !StringUtils.isBlank(keystoreFilename);
    -        final boolean truststoreProvided = !StringUtils.isBlank(truststoreFilename);
    +        // This use case specifically allows truststore configuration without keystore configuration, but attempts to default the keystore type value
    +        if (StringUtils.isBlank(keystoreFilename)) {
    +            keystoreTypeStr = null;
    +        }
     
             try {
    -            final char[] keystorePass = keystorePassword == null ? null : keystorePassword.toCharArray();
    -            final char[] keyPass = keyPassword == null ? null : keyPassword.toCharArray();
    -            final char[] trustPass = truststorePassword == null ? null : truststorePassword.toCharArray();
    -
    -            if (keystoreProvided && truststoreProvided) {
    -                this.sslContext = SslContextFactory.createSslContext(
    -                        keystoreFilename,
    -                        keystorePass,
    -                        keyPass,
    -                        keystoreTypeStr,
    -                        truststoreFilename,
    -                        trustPass,
    -                        truststoreTypeStr,
    -                        SslContextFactory.ClientAuth.NONE, protocol);
    -
    -            } else if (truststoreProvided) {
    -                this.sslContext = SslContextFactory.createTrustSslContext(
    -                        truststoreFilename,
    -                        trustPass,
    -                        truststoreTypeStr,
    -                        protocol);
    +            TlsConfiguration tlsConfiguration = new TlsConfiguration(keystoreFilename, keystorePassword, keyPassword, keystoreTypeStr,
    +                    truststoreFilename, truststorePassword, truststoreTypeStr, protocol);
     
    +            if (tlsConfiguration.isAnyTruststorePopulated()) {
    +                this.sslContext = SslContextFactory.createSslContext(tlsConfiguration);
                 } else {
    -                printUsageAndThrow("No keystore or truststore was provided", ExitCode.INVALID_ARGS);
    +                printUsageAndThrow("No truststore was provided", ExitCode.INVALID_ARGS);
                 }
             } catch (Exception e) {
                 logger.error(e.getMessage(), e);
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

8

News mentions

0

No linked articles in our index yet.