NIFI-3580 Add configurable TLS Cipher Suite properties

This closes #5018

Signed-off-by: David Handermann <exceptionfactory@apache.org>
This commit is contained in:
Paul Grey 2021-04-19 15:28:53 -04:00 committed by exceptionfactory
parent aedacdf86f
commit 17fa0cf3c1
No known key found for this signature in database
GPG Key ID: 29B6A52D2AAE8DBA
5 changed files with 92 additions and 0 deletions

View File

@ -207,6 +207,8 @@ public abstract class NiFiProperties {
public static final String WEB_HTTPS_PORT = "nifi.web.https.port";
public static final String WEB_HTTPS_PORT_FORWARDING = "nifi.web.https.port.forwarding";
public static final String WEB_HTTPS_HOST = "nifi.web.https.host";
public static final String WEB_HTTPS_CIPHERSUITES_INCLUDE = "nifi.web.https.ciphersuites.include";
public static final String WEB_HTTPS_CIPHERSUITES_EXCLUDE = "nifi.web.https.ciphersuites.exclude";
public static final String WEB_HTTPS_NETWORK_INTERFACE_PREFIX = "nifi.web.https.network.interface.";
public static final String WEB_WORKING_DIR = "nifi.web.jetty.working.directory";
public static final String WEB_THREADS = "nifi.web.jetty.threads";

View File

@ -209,6 +209,28 @@ In order to facilitate the secure setup of NiFi, you can use the `tls-toolkit` c
* <<toolkit-guide.adoc#tls_intermediate_ca,Using An Existing Intermediate Certificate Authority>>
* <<toolkit-guide.adoc#additional_certificate_commands,Additional Certificate Commands>>
[[tls_cipher_suites]]
=== TLS Cipher Suites
The Java Runtime Environment provides the ability to specify custom TLS cipher suites to be used by servers when accepting client connections. See
link:https://java.com/en/configure_crypto.html[here^] for more information. To use this feature for the NiFi web service, the following NiFi properties
may be set:
[options="header,footer"]
|==================================================================================================================================================
| Property Name | Description
|`nifi.web.https.ciphersuites.include` | Set of ciphers that are available to be used by incoming client connections. Replaces system defaults if set.
|`nifi.web.https.ciphersuites.exclude` | Set of ciphers that must not be used by incoming client connections. Filters available ciphers if set.
|==================================================================================================================================================
Each property should take the form of a comma-separated list of common cipher names as specified
link:https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#ciphersuites[here^]. Regular expressions
(for example `^.*GCM_SHA256$`) may also be specified.
The semantics match the use of the following Jetty APIs:
* link:https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/util/ssl/SslContextFactory.html#setIncludeCipherSuites(java.lang.String\...)[SslContextFactory.setIncludeCipherSuites()]
* link:https://www.eclipse.org/jetty/javadoc/jetty-9/org/eclipse/jetty/util/ssl/SslContextFactory.html#setExcludeCipherSuites(java.lang.String\...)[SslContextFactory.setExcludeCipherSuites()]
[[user_authentication]]
== User Authentication
@ -3494,6 +3516,14 @@ Providing three total network interfaces, including `nifi.web.http.network.inte
|`nifi.web.https.host`|The HTTPS host. It is blank by default.
|`nifi.web.https.port`|The HTTPS port. It is blank by default. When configuring NiFi to run securely, this port should be configured.
|`nifi.web.https.port.forwarding`|Same as `nifi.web.http.port.forwarding`, but with HTTPS for secure communication. It is blank by default.
|`nifi.web.https.ciphersuites.include`|Cipher suites used to initialize the SSLContext of the Jetty HTTPS port. If unspecified, the runtime SSLContext defaults are used.
|`nifi.web.https.ciphersuites.exclude`|Cipher suites that may not be used by an SSL client to establish a connection to Jetty. If unspecified, the runtime SSLContext defaults are used.
In Chrome, the SSL cipher negotiated with Jetty may be examined in the 'Developer Tools' plugin, in the 'Security' tab.
In Firefox, the SSL cipher negotiated with Jetty may be examined in the 'Secure Connection' widget found to the left of the URL in the browser address bar.
|`nifi.web.https.network.interface`*|The name of the network interface to which NiFi should bind for HTTPS requests. It is blank by default. +
+
*NOTE*: Multiple network interfaces can be specified by using the `nifi.web.https.network.interface.` prefix with unique suffixes and separate network interface names as values. +

View File

@ -167,6 +167,10 @@ nifi.web.request.timeout=${nifi.web.request.timeout}
nifi.web.request.ip.whitelist=${nifi.web.request.ip.whitelist}
nifi.web.should.send.server.version=${nifi.web.should.send.server.version}
# Include or Exclude TLS Cipher Suites for HTTPS
nifi.web.https.ciphersuites.include=
nifi.web.https.ciphersuites.exclude=
# security properties #
nifi.sensitive.props.key=
nifi.sensitive.props.key.protected=${nifi.sensitive.props.key.protected}

View File

@ -138,6 +138,10 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
return nameToTest.endsWith(".war") && pathname.isFile();
};
// property parsing util
private static final String REGEX_SPLIT_PROPERTY = ",\\s*";
protected static final String JOIN_ARRAY = ", ";
private Server server;
private NiFiProperties props;
@ -1012,6 +1016,22 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
contextFactory.setIncludeProtocols(TlsConfiguration.getCurrentSupportedTlsProtocolVersions());
contextFactory.setExcludeProtocols("TLS", "TLSv1", "TLSv1.1", "SSL", "SSLv2", "SSLv2Hello", "SSLv3");
// on configuration, replace default application cipher suites with those configured
final String includeCipherSuitesProps = props.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE);
if (StringUtils.isNotEmpty(includeCipherSuitesProps)) {
final String[] includeCipherSuites = includeCipherSuitesProps.split(REGEX_SPLIT_PROPERTY);
logger.info("Setting include cipher suites from configuration; parsed property = [{}].",
StringUtils.join(includeCipherSuites, JOIN_ARRAY));
contextFactory.setIncludeCipherSuites(includeCipherSuites);
}
final String excludeCipherSuitesProps = props.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE);
if (StringUtils.isNotEmpty(excludeCipherSuitesProps)) {
final String[] excludeCipherSuites = excludeCipherSuitesProps.split(REGEX_SPLIT_PROPERTY);
logger.info("Setting exclude cipher suites from configuration; parsed property = [{}].",
StringUtils.join(excludeCipherSuites, JOIN_ARRAY));
contextFactory.setExcludeCipherSuites(excludeCipherSuites);
}
// require client auth when not supporting login, Kerberos service, or anonymous access
if (props.isClientAuthRequiredForRestApi()) {
contextFactory.setNeedClientAuth(true);

View File

@ -19,13 +19,17 @@ package org.apache.nifi.web.server;
import static org.apache.nifi.security.util.KeyStoreUtils.SUN_PROVIDER_NAME;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.security.util.KeystoreType;
import org.apache.nifi.util.NiFiProperties;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
@ -142,4 +146,36 @@ public class JettyServerTest {
verify(mockSCF).setTrustStoreType(trustStoreType);
verify(mockSCF).setTrustStoreProvider(BouncyCastleProvider.PROVIDER_NAME);
}
/**
* Verify correct processing of cipher suites with multiple elements. Verify call to override runtime ciphers.
*/
@Test
public void testConfigureSslIncludeExcludeCiphers() {
final String[] includeCipherSuites = {"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256"};
final String includeCipherSuitesProp = StringUtils.join(includeCipherSuites, JettyServer.JOIN_ARRAY);
final String[] excludeCipherSuites = {".*DHE.*", ".*ECDH.*"};
final String excludeCipherSuitesProp = StringUtils.join(excludeCipherSuites, JettyServer.JOIN_ARRAY);
final Map<String, String> addProps = new HashMap<>();
addProps.put(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, includeCipherSuitesProp);
addProps.put(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, excludeCipherSuitesProp);
final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
final SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
verify(mockSCF, times(1)).setIncludeCipherSuites(includeCipherSuites);
verify(mockSCF, times(1)).setExcludeCipherSuites(excludeCipherSuites);
}
/**
* Verify skip cipher configuration when NiFiProperties are not specified.
*/
@Test
public void testDoNotConfigureSslIncludeExcludeCiphers() {
final NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null);
final SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
verify(mockSCF, times(0)).setIncludeCipherSuites(any());
verify(mockSCF, times(0)).setExcludeCipherSuites(any());
}
}