You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by jg...@apache.org on 2022/06/03 15:54:34 UTC
[nifi] branch main updated: NIFI-9804 Added HTTP/2 support to Application Server
This is an automated email from the ASF dual-hosted git repository.
jgresock pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 4b655ecb68 NIFI-9804 Added HTTP/2 support to Application Server
4b655ecb68 is described below
commit 4b655ecb688685016f67fba7a9386683f8429df6
Author: exceptionfactory <ex...@apache.org>
AuthorDate: Wed Jun 1 20:46:40 2022 -0500
NIFI-9804 Added HTTP/2 support to Application Server
- Added nifi.web.https.application.protocols property
- Set default protocol to HTTP/1.1 and provided documentation for enabling HTTP/2
- Changed StandardALPNProcessor handshakeFailed log to debug
Signed-off-by: Joe Gresock <jg...@gmail.com>
This closes #6093.
---
.../connector/StandardServerConnectorFactory.java | 8 +-
.../connector/alpn/StandardALPNProcessor.java | 2 +-
.../java/org/apache/nifi/util/NiFiProperties.java | 12 +
.../src/main/asciidoc/administration-guide.adoc | 7 +
.../nifi-framework/nifi-resources/pom.xml | 1 +
.../src/main/resources/conf/nifi.properties | 1 +
.../nifi-framework/nifi-web/nifi-jetty/pom.xml | 13 +
.../org/apache/nifi/web/server/JettyServer.java | 362 ++++-----------------
.../connector/FrameworkServerConnectorFactory.java | 199 +++++++++++
.../apache/nifi/web/server/JettyServerTest.java | 181 -----------
.../FrameworkServerConnectorFactoryTest.java | 195 +++++++++++
11 files changed, 490 insertions(+), 491 deletions(-)
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
index e670ac71ea..22679ee81a 100644
--- a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/StandardServerConnectorFactory.java
@@ -162,7 +162,11 @@ public class StandardServerConnectorFactory implements ServerConnectorFactory {
this.applicationLayerProtocols = applicationLayerProtocols;
}
- private HttpConfiguration getHttpConfiguration() {
+ protected Server getServer() {
+ return server;
+ }
+
+ protected HttpConfiguration getHttpConfiguration() {
final HttpConfiguration httpConfiguration = new HttpConfiguration();
if (sslContext != null) {
@@ -177,7 +181,7 @@ public class StandardServerConnectorFactory implements ServerConnectorFactory {
return httpConfiguration;
}
- private SslContextFactory.Server getSslContextFactory() {
+ protected SslContextFactory.Server getSslContextFactory() {
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setSslContext(sslContext);
sslContextFactory.setNeedClientAuth(needClientAuth);
diff --git a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
index 0c8825226d..2e813f98ff 100644
--- a/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
+++ b/nifi-commons/nifi-jetty-configuration/src/main/java/org/apache/nifi/jetty/configuration/connector/alpn/StandardALPNProcessor.java
@@ -117,7 +117,7 @@ public class StandardALPNProcessor implements ALPNProcessor.Server, SslHandshake
*/
@Override
public void handshakeFailed(final Event event, final Throwable failure) {
- logger.warn("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
+ logger.debug("Connection Remote Address [{}] Handshake Failed", serverConnection.getEndPoint().getRemoteAddress(), failure);
}
}
}
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index 78b9ae5af9..995c720271 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -225,6 +225,7 @@ public class NiFiProperties extends ApplicationProperties {
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_APPLICATION_PROTOCOLS = "nifi.web.https.application.protocols";
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.";
@@ -334,6 +335,7 @@ public class NiFiProperties extends ApplicationProperties {
public static final String DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE = "conf/login-identity-providers.xml";
public static final Integer DEFAULT_REMOTE_INPUT_PORT = null;
public static final Path DEFAULT_TEMPLATE_DIRECTORY = Paths.get("conf", "templates");
+ private static final String DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS = "http/1.1";
public static final int DEFAULT_WEB_THREADS = 200;
public static final String DEFAULT_WEB_MAX_HEADER_SIZE = "16 KB";
public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
@@ -705,6 +707,16 @@ public class NiFiProperties extends ApplicationProperties {
}
}
+ /**
+ * Get Web HTTPS Application Protocols defaults to HTTP/1.1
+ *
+ * @return Set of configured HTTPS Application Protocols
+ */
+ public Set<String> getWebHttpsApplicationProtocols() {
+ final String protocols = getProperty(WEB_HTTPS_APPLICATION_PROTOCOLS, DEFAULT_WEB_HTTPS_APPLICATION_PROTOCOLS);
+ return Arrays.stream(protocols.split("\\s+")).collect(Collectors.toSet());
+ }
+
public String getWebMaxHeaderSize() {
return getProperty(WEB_MAX_HEADER_SIZE, DEFAULT_WEB_MAX_HEADER_SIZE);
}
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index d61b2134ef..11df488013 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -3884,6 +3884,13 @@ For example, to provide two additional network interfaces, a user could also spe
`nifi.web.https.network.interface.eth1=eth1` +
+
Providing three total network interfaces, including `nifi.web.https.network.interface.default`.
+|`nifi.web.https.application.protocols`|The space-separated list of application protocols supported when running with HTTPS enabled.
+
+The default value is `http/1.1`.
+
+The value can be set to `h2 http/1.1` to support Application Layer Protocol Negotiation (ALPN) for HTTP/2 or HTTP/1.1 based on client capabilities.
+
+The value can be set to `h2` to require HTTP/2 and disable HTTP/1.1.
|`nifi.web.jetty.working.directory`|The location of the Jetty working directory. The default value is `./work/jetty`.
|`nifi.web.jetty.threads`|The number of Jetty threads. The default value is `200`.
|`nifi.web.max.header.size`|The maximum size allowed for request and response headers. The default value is `16 KB`.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
index 0827a19fdb..8bd65c08c4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml
@@ -126,6 +126,7 @@
<nifi.web.https.host>127.0.0.1</nifi.web.https.host>
<nifi.web.https.port>8443</nifi.web.https.port>
<nifi.web.https.network.interface.default />
+ <nifi.web.https.application.protocols>http/1.1</nifi.web.https.application.protocols>
<nifi.jetty.work.dir>./work/jetty</nifi.jetty.work.dir>
<nifi.web.jetty.threads>200</nifi.web.jetty.threads>
<nifi.web.max.header.size>16 KB</nifi.web.max.header.size>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index 1426b27280..c0a593d23a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -151,6 +151,7 @@ nifi.web.http.network.interface.default=${nifi.web.http.network.interface.defaul
nifi.web.https.host=${nifi.web.https.host}
nifi.web.https.port=${nifi.web.https.port}
nifi.web.https.network.interface.default=${nifi.web.https.network.interface.default}
+nifi.web.https.application.protocols=${nifi.web.https.application.protocols}
nifi.web.jetty.working.directory=${nifi.jetty.work.dir}
nifi.web.jetty.threads=${nifi.web.jetty.threads}
nifi.web.max.header.size=${nifi.web.max.header.size}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
index 07b052e923..5483945edd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml
@@ -152,6 +152,19 @@
<artifactId>nifi-ui-extension</artifactId>
<scope>compile</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-jetty-configuration</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty.http2</groupId>
+ <artifactId>http2-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-alpn-server</artifactId>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-web-security</artifactId>
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
index f4d794ec8a..199b10e890 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java
@@ -51,37 +51,29 @@ import org.apache.nifi.nar.NarProvider;
import org.apache.nifi.nar.NarThreadContextClassLoader;
import org.apache.nifi.nar.StandardExtensionDiscoveringManager;
import org.apache.nifi.nar.StandardNarLoader;
-import org.apache.nifi.processor.DataUnit;
-import org.apache.nifi.security.util.KeyStoreUtils;
-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.UiExtension;
import org.apache.nifi.ui.extension.UiExtensionMapping;
-import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.web.ContentAccess;
import org.apache.nifi.web.NiFiWebConfigurationContext;
import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.server.connector.FrameworkServerConnectorFactory;
import org.apache.nifi.web.server.filter.FilterParameter;
import org.apache.nifi.web.server.filter.RequestFilterProvider;
import org.apache.nifi.web.server.filter.RestApiRequestFilterProvider;
import org.apache.nifi.web.server.filter.StandardRequestFilterProvider;
import org.apache.nifi.web.server.log.RequestLogProvider;
import org.apache.nifi.web.server.log.StandardRequestLogProvider;
-import org.apache.nifi.web.server.util.TrustStoreScanner;
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.deploy.App;
import org.eclipse.jetty.deploy.DeploymentManager;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.HttpConfiguration;
-import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.RequestLog;
-import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
-import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.HandlerList;
@@ -89,8 +81,6 @@ import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletHolder;
-import org.eclipse.jetty.util.ssl.KeyStoreScanner;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.JettyWebXmlConfiguration;
@@ -111,8 +101,8 @@ 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.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
@@ -130,7 +120,6 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
@@ -164,17 +153,11 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private static final String DEFAULT_NAR_PROVIDER_POLL_INTERVAL = "5 min";
private static final String DEFAULT_NAR_PROVIDER_CONFLICT_RESOLUTION = "IGNORE";
- private static final int DOS_FILTER_REJECT_REQUEST = -1;
-
private static final FileFilter WAR_FILTER = pathname -> {
final String nameToTest = pathname.getName().toLowerCase();
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;
@@ -184,7 +167,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private NarAutoLoader narAutoLoader;
private ExternalResourceProviderService narProviderService;
private DiagnosticsFactory diagnosticsFactory;
- private SslContextFactory.Server sslContextFactory;
private DecommissionTask decommissionTask;
private StatusHistoryDumpFactory statusHistoryDumpFactory;
@@ -342,7 +324,6 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
return gzip(webAppContextHandlers);
}
-
@Override
public void loadExtensionUis(final Set<Bundle> bundles) {
// Find and load any WARs contained within the set of bundles...
@@ -661,7 +642,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private void addDocsServlets(WebAppContext docsContext) {
try {
// Load the nifi/docs directory
- final File docsDir = getDocsDir("docs");
+ final File docsDir = getDocsDir();
// load the component documentation working directory
final File componentDocsDirPath = props.getComponentDocumentationWorkingDirectory();
@@ -705,10 +686,10 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
* is that the documentation links under the 'General' portion of the help
* page will not be accessible, but at least the process will be running.
*
- * @param docsDirectory Name of documentation directory in installation directory.
* @return A File object to the documentation directory; else startUpFailure called.
*/
- private File getDocsDir(final String docsDirectory) {
+ private File getDocsDir() {
+ final String docsDirectory = "docs";
File docsDir;
try {
docsDir = Paths.get(docsDirectory).toRealPath().toFile();
@@ -748,268 +729,43 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
return webApiDocsDir;
}
- private void configureConnectors(final Server server) throws ServerConfigurationException {
- // create the http configuration
- final HttpConfiguration httpConfiguration = new HttpConfiguration();
- final int headerSize = DataUnit.parseDataSize(props.getWebMaxHeaderSize(), DataUnit.B).intValue();
- httpConfiguration.setRequestHeaderSize(headerSize);
- httpConfiguration.setResponseHeaderSize(headerSize);
- httpConfiguration.setSendServerVersion(props.shouldSendServerVersion());
-
- // Check if both HTTP and HTTPS connectors are configured and fail if both are configured
- if (bothHttpAndHttpsConnectorsConfigured(props)) {
- logger.error("NiFi only supports one mode of HTTP or HTTPS operation, not both simultaneously. " +
- "Check the nifi.properties file and ensure that either the HTTP hostname and port or the HTTPS hostname and port are empty");
- startUpFailure(new IllegalStateException("Only one of the HTTP and HTTPS connectors can be configured at one time"));
- }
-
- if (props.getSslPort() != null) {
- configureHttpsConnector(server, httpConfiguration);
- } else if (props.getPort() != null) {
- configureHttpConnector(server, httpConfiguration);
- } else {
- logger.error("Neither the HTTP nor HTTPS connector was configured in nifi.properties");
- startUpFailure(new IllegalStateException("Must configure HTTP or HTTPS connector"));
- }
- }
-
- /**
- * Configures an HTTPS connector and adds it to the server.
- *
- * @param server the Jetty server instance
- * @param httpConfiguration the configuration object for the HTTPS protocol settings
- */
- private void configureHttpsConnector(Server server, HttpConfiguration httpConfiguration) {
- String hostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
- final Integer port = props.getSslPort();
- String connectorLabel = "HTTPS";
- final Map<String, String> httpsNetworkInterfaces = props.getHttpsNetworkInterfaces();
- ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> createUnconfiguredSslServerConnector(s, c, port);
-
- configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpsNetworkInterfaces, scc);
-
- if (props.isSecurityAutoReloadEnabled()) {
- configureSslContextFactoryReloading(server);
- }
- }
-
- /**
- * Configures a KeyStoreScanner and TrustStoreScanner at the configured reload intervals. This will
- * reload the SSLContextFactory if any changes are detected to the keystore or truststore.
- *
- * @param server The Jetty server
- */
- private void configureSslContextFactoryReloading(Server server) {
- final int scanIntervalSeconds = Double.valueOf(FormatUtils.getPreciseTimeDuration(
- props.getSecurityAutoReloadInterval(), TimeUnit.SECONDS))
- .intValue();
-
- final KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory);
- keyStoreScanner.setScanInterval(scanIntervalSeconds);
- server.addBean(keyStoreScanner);
-
- final TrustStoreScanner trustStoreScanner = new TrustStoreScanner(sslContextFactory);
- trustStoreScanner.setScanInterval(scanIntervalSeconds);
- server.addBean(trustStoreScanner);
- }
-
- /**
- * Configures an HTTP connector and adds it to the server.
- *
- * @param server the Jetty server instance
- * @param httpConfiguration the configuration object for the HTTP protocol settings
- */
- private void configureHttpConnector(Server server, HttpConfiguration httpConfiguration) {
- String hostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
- final Integer port = props.getPort();
- String connectorLabel = "HTTP";
- final Map<String, String> httpNetworkInterfaces = props.getHttpNetworkInterfaces();
- ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> scc = (s, c) -> new ServerConnector(s, new HttpConnectionFactory(c));
-
- configureGenericConnector(server, httpConfiguration, hostname, port, connectorLabel, httpNetworkInterfaces, scc);
- }
-
- /**
- * Configures an HTTP(S) connector for the server given the provided parameters. The functionality between HTTP and HTTPS connectors is largely similar.
- * Here the common behavior has been extracted into a shared method and the respective calling methods obtain the right values and a lambda function for the differing behavior.
- *
- * @param server the Jetty server instance
- * @param configuration the HTTP/HTTPS configuration instance
- * @param hostname the hostname from the nifi.properties file
- * @param port the port to expose
- * @param connectorLabel used for log output (e.g. "HTTP" or "HTTPS")
- * @param networkInterfaces the map of network interfaces from nifi.properties
- * @param serverConnectorCreator a function which accepts a {@code Server} and {@code HttpConnection} instance and returns a {@code ServerConnector}
- */
- private void configureGenericConnector(Server server, HttpConfiguration configuration, String hostname, Integer port, String connectorLabel, Map<String, String> networkInterfaces,
- ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> serverConnectorCreator) {
- if (port < 0 || (int) Math.pow(2, 16) <= port) {
- throw new ServerConfigurationException("Invalid " + connectorLabel + " port: " + port);
- }
-
- logger.info("Configuring Jetty for " + connectorLabel + " on port: " + port);
-
- final List<Connector> serverConnectors = new ArrayList<>();
-
- // Calculate Idle Timeout as twice the auto-refresh interval. This ensures that even with some variance in timing,
- // we are able to avoid closing connections from users' browsers most of the time. This can make a significant difference
- // in HTTPS connections, as each HTTPS connection that is established must perform the SSL handshake.
- final String autoRefreshInterval = props.getAutoRefreshInterval();
- final long autoRefreshMillis = autoRefreshInterval == null ? 30000L : FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.MILLISECONDS);
- final long idleTimeout = autoRefreshMillis * 2;
-
- // If the interfaces collection is empty or each element is empty
- if (networkInterfaces.isEmpty() || networkInterfaces.values().stream().filter(value -> StringUtils.isNotBlank(value)).collect(Collectors.toList()).isEmpty()) {
- final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
-
- // Set host and port
- if (StringUtils.isNotBlank(hostname)) {
- serverConnector.setHost(hostname);
- }
- serverConnector.setPort(port);
- serverConnector.setIdleTimeout(idleTimeout);
- serverConnectors.add(serverConnector);
- } else {
- // Add connectors for all IPs from network interfaces
- serverConnectors.addAll(new ArrayList<>(networkInterfaces.values().stream().map(ifaceName -> {
- NetworkInterface iface = null;
- try {
- iface = NetworkInterface.getByName(ifaceName);
- } catch (SocketException e) {
- logger.error("Unable to get network interface by name {}", ifaceName, e);
- }
- if (iface == null) {
- logger.warn("Unable to find network interface named {}", ifaceName);
+ private void configureConnectors(final Server server) {
+ try {
+ final FrameworkServerConnectorFactory serverConnectorFactory = new FrameworkServerConnectorFactory(server, props);
+ final Map<String, String> interfaces = props.isHTTPSConfigured() ? props.getHttpsNetworkInterfaces() : props.getHttpNetworkInterfaces();
+ final Set<String> interfaceNames = interfaces.values().stream().filter(StringUtils::isNotBlank).collect(Collectors.toSet());
+ // Add Server Connectors based on configured Network Interface Names
+ if (interfaceNames.isEmpty()) {
+ final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
+ final String host = props.isHTTPSConfigured() ? props.getProperty(NiFiProperties.WEB_HTTPS_HOST) : props.getProperty(NiFiProperties.WEB_HTTP_HOST);
+ if (StringUtils.isNotBlank(host)) {
+ serverConnector.setHost(host);
}
- return iface;
- }).filter(Objects::nonNull).flatMap(iface -> Collections.list(iface.getInetAddresses()).stream())
- .map(inetAddress -> {
- final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration);
-
- // Set host and port
- serverConnector.setHost(inetAddress.getHostAddress());
- serverConnector.setPort(port);
- serverConnector.setIdleTimeout(idleTimeout);
-
- return serverConnector;
- }).collect(Collectors.toList())));
- }
- // Add all connectors
- serverConnectors.forEach(server::addConnector);
- }
-
- /**
- * Returns true if there are configured properties for both HTTP and HTTPS connectors (specifically port because the hostname can be left blank in the HTTP connector).
- * Prints a warning log message with the relevant properties.
- *
- * @param props the NiFiProperties
- * @return true if both ports are present
- */
- static boolean bothHttpAndHttpsConnectorsConfigured(NiFiProperties props) {
- Integer httpPort = props.getPort();
- String httpHostname = props.getProperty(NiFiProperties.WEB_HTTP_HOST);
-
- Integer httpsPort = props.getSslPort();
- String httpsHostname = props.getProperty(NiFiProperties.WEB_HTTPS_HOST);
-
- if (httpPort != null && httpsPort != null) {
- logger.warn("Both the HTTP and HTTPS connectors are configured in nifi.properties. Only one of these connectors should be configured. See the NiFi Admin Guide for more details");
- logger.warn("HTTP connector: http://" + httpHostname + ":" + httpPort);
- logger.warn("HTTPS connector: https://" + httpsHostname + ":" + httpsPort);
- return true;
- }
-
- return false;
- }
-
- private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration, int port) {
- // add some secure config
- final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
- httpsConfiguration.setSecureScheme("https");
- httpsConfiguration.setSecurePort(port);
- httpsConfiguration.setSendServerVersion(props.shouldSendServerVersion());
- httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
-
- // build the connector
- return new ServerConnector(server,
- new SslConnectionFactory(createSslContextFactory(), "http/1.1"),
- new HttpConnectionFactory(httpsConfiguration));
- }
-
- private SslContextFactory createSslContextFactory() {
- final SslContextFactory.Server serverContextFactory = new SslContextFactory.Server();
- configureSslContextFactory(serverContextFactory, props);
- this.sslContextFactory = serverContextFactory;
- return serverContextFactory;
- }
-
- protected static void configureSslContextFactory(SslContextFactory.Server contextFactory, NiFiProperties props) {
- // Explicitly exclude legacy TLS protocol versions
- 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);
- } else {
- contextFactory.setWantClientAuth(true);
- }
-
- /* below code sets JSSE system properties when values are provided */
- // keystore properties
- if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_KEYSTORE))) {
- contextFactory.setKeyStorePath(props.getProperty(NiFiProperties.SECURITY_KEYSTORE));
- }
- String keyStoreType = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE);
- if (StringUtils.isNotBlank(keyStoreType)) {
- contextFactory.setKeyStoreType(keyStoreType);
- String keyStoreProvider = KeyStoreUtils.getKeyStoreProvider(keyStoreType);
- if (StringUtils.isNoneEmpty(keyStoreProvider)) {
- contextFactory.setKeyStoreProvider(keyStoreProvider);
- }
- }
- final String keystorePassword = props.getProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD);
- final String keyPassword = props.getProperty(NiFiProperties.SECURITY_KEY_PASSWD);
- if (StringUtils.isNotBlank(keystorePassword)) {
- // if no key password was provided, then assume the keystore password is the same as the key password.
- final String defaultKeyPassword = (StringUtils.isBlank(keyPassword)) ? keystorePassword : keyPassword;
- contextFactory.setKeyStorePassword(keystorePassword);
- contextFactory.setKeyManagerPassword(defaultKeyPassword);
- } else if (StringUtils.isNotBlank(keyPassword)) {
- // since no keystore password was provided, there will be no keystore integrity check
- contextFactory.setKeyManagerPassword(keyPassword);
- }
-
- // truststore properties
- if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE))) {
- contextFactory.setTrustStorePath(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE));
- }
- String trustStoreType = props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE);
- if (StringUtils.isNotBlank(trustStoreType)) {
- contextFactory.setTrustStoreType(trustStoreType);
- String trustStoreProvider = KeyStoreUtils.getKeyStoreProvider(trustStoreType);
- if (StringUtils.isNoneEmpty(trustStoreProvider)) {
- contextFactory.setTrustStoreProvider(trustStoreProvider);
+ server.addConnector(serverConnector);
+ } else {
+ interfaceNames.stream()
+ // Map interface name properties to Network Interfaces
+ .map(interfaceName -> {
+ try {
+ return NetworkInterface.getByName(interfaceName);
+ } catch (final SocketException e) {
+ throw new UncheckedIOException(String.format("Network Interface [%s] not found", interfaceName), e);
+ }
+ })
+ // Map Network Interfaces to host addresses
+ .filter(Objects::nonNull)
+ .flatMap(networkInterface -> Collections.list(networkInterface.getInetAddresses()).stream())
+ .map(InetAddress::getHostAddress)
+ // Map host addresses to Server Connectors
+ .map(host -> {
+ final ServerConnector serverConnector = serverConnectorFactory.getServerConnector();
+ serverConnector.setHost(host);
+ return serverConnector;
+ })
+ .forEach(server::addConnector);
}
- }
- if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD))) {
- contextFactory.setTrustStorePassword(props.getProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD));
+ } catch (final Throwable e) {
+ startUpFailure(e);
}
}
@@ -1123,7 +879,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
logger.info("Loading Flow...");
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(webApiContext.getServletContext());
- flowService = ctx.getBean("flowService", FlowService.class);
+ flowService = Objects.requireNonNull(ctx).getBean("flowService", FlowService.class);
// start and load the flow
flowService.start();
@@ -1157,7 +913,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
final Set<String> externalSourceNames = props.getDirectSubsequentTokens(providerPropertyPrefix);
for(final String externalSourceName : externalSourceNames) {
- logger.info("External resource provider \'{}\' found in configuration", externalSourceName);
+ logger.info("External resource provider '{}' found in configuration", externalSourceName);
final String providerClass = props.getProperty(providerPropertyPrefix + externalSourceName + "." + NAR_PROVIDER_IMPLEMENTATION_PROPERTY);
final String providerId = UUID.randomUUID().toString();
@@ -1174,6 +930,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
* In case the provider class is not an implementation of {@code ExternalResourceProvider} the method tries to instantiate it as a {@code NarProvider}. {@code NarProvider} instances
* are wrapped into an adapter in order to envelope the support.
*/
+ @SuppressWarnings("deprecation")
private ExternalResourceProvider createProviderInstance(
final ExtensionManager extensionManager,
final String providerClass,
@@ -1185,7 +942,7 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
try {
provider = NarThreadContextClassLoader.createInstance(extensionManager, providerClass, ExternalResourceProvider.class, props, providerId);
} catch (final ClassCastException e) {
- logger.warn("Class {} does not implement \"ExternalResourceProvider\" falling back to \"NarProvider\"");
+ logger.warn("Class {} does not implement ExternalResourceProvider falling back to NarProvider", providerClass);
provider = new NarProviderAdapter(NarThreadContextClassLoader.createInstance(extensionManager, providerClass, NarProvider.class, props, providerId));
}
@@ -1259,11 +1016,9 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
hosts.add(serverConnector.getHost());
} else {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
- if (networkInterfaces != null) {
- for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
- for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
- hosts.add(inetAddress.getHostAddress());
- }
+ for (NetworkInterface networkInterface : Collections.list(networkInterfaces)) {
+ for (InetAddress inetAddress : Collections.list(networkInterface.getInetAddresses())) {
+ hosts.add(inetAddress.getHostAddress());
}
}
}
@@ -1384,24 +1139,17 @@ public class JettyServer implements NiFiServer, ExtensionUiLoader {
private static class ThreadDumpDiagnosticsFactory implements DiagnosticsFactory {
@Override
public DiagnosticsDump create(final boolean verbose) {
- return new DiagnosticsDump() {
- @Override
- public void writeTo(final OutputStream out) throws IOException {
- final DiagnosticsDumpElement threadDumpElement = new ThreadDumpTask().captureDump(verbose);
- final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
- for (final String detail : threadDumpElement.getDetails()) {
- writer.write(detail);
- writer.write("\n");
- }
-
- writer.flush();
+ return out -> {
+ final DiagnosticsDumpElement threadDumpElement = new ThreadDumpTask().captureDump(verbose);
+ final BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
+ for (final String detail : threadDumpElement.getDetails()) {
+ writer.write(detail);
+ writer.write("\n");
}
+
+ writer.flush();
};
}
}
}
-@FunctionalInterface
-interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> {
- ServerConnector create(Server server, HttpConfiguration httpConfiguration);
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java
new file mode 100644
index 0000000000..3fe9153824
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactory.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.server.connector;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.nifi.jetty.configuration.connector.ApplicationLayerProtocol;
+import org.apache.nifi.jetty.configuration.connector.StandardServerConnectorFactory;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.security.util.StandardTlsConfiguration;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.security.util.TlsException;
+import org.apache.nifi.security.util.TlsPlatform;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.util.TrustStoreScanner;
+import org.eclipse.jetty.server.HttpConfiguration;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import javax.net.ssl.SSLContext;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+import static org.apache.nifi.security.util.SslContextFactory.createSslContext;
+
+/**
+ * Framework extension of Server Connector Factory configures additional settings based on application properties
+ */
+public class FrameworkServerConnectorFactory extends StandardServerConnectorFactory {
+ private static final String DEFAULT_AUTO_REFRESH_INTERVAL = "30 s";
+
+ private static final int IDLE_TIMEOUT_MULTIPLIER = 2;
+
+ private static final String CIPHER_SUITE_SEPARATOR_PATTERN = ",\\s*";
+
+ private final int headerSize;
+
+ private final int idleTimeout;
+
+ private final Integer storeScanInterval;
+
+ private final String includeCipherSuites;
+
+ private final String excludeCipherSuites;
+
+ private TlsConfiguration tlsConfiguration;
+
+ private SslContextFactory.Server sslContextFactory;
+
+ /**
+ * Framework Server Connector Factory Constructor with required properties
+ *
+ * @param server Jetty Server
+ * @param properties NiFi Properties
+ */
+ public FrameworkServerConnectorFactory(final Server server, final NiFiProperties properties) {
+ super(server, getPort(properties));
+
+ includeCipherSuites = properties.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE);
+ excludeCipherSuites = properties.getProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE);
+ headerSize = DataUnit.parseDataSize(properties.getWebMaxHeaderSize(), DataUnit.B).intValue();
+ idleTimeout = getIdleTimeout(properties);
+
+ if (properties.isHTTPSConfigured()) {
+ tlsConfiguration = StandardTlsConfiguration.fromNiFiProperties(properties);
+ try {
+ final SSLContext sslContext = createSslContext(tlsConfiguration);
+ setSslContext(sslContext);
+ } catch (final TlsException e) {
+ throw new IllegalStateException("Invalid nifi.web.https configuration in nifi.properties", e);
+ }
+
+ if (properties.isClientAuthRequiredForRestApi()) {
+ setNeedClientAuth(true);
+ } else {
+ setWantClientAuth(true);
+ }
+
+ if (properties.isSecurityAutoReloadEnabled()) {
+ final String securityAutoReloadInterval = properties.getSecurityAutoReloadInterval();
+ final double reloadIntervalSeconds = FormatUtils.getPreciseTimeDuration(securityAutoReloadInterval, TimeUnit.SECONDS);
+ storeScanInterval = (int) reloadIntervalSeconds;
+ } else {
+ storeScanInterval = null;
+ }
+
+ setApplicationLayerProtocols(properties);
+
+ // Set Transport Layer Security Protocols based on platform configuration
+ setIncludeSecurityProtocols(TlsPlatform.getPreferredProtocols().toArray(new String[0]));
+ } else {
+ storeScanInterval = null;
+ }
+ }
+
+ /**
+ * Get HTTP Configuration with additional settings based on application properties
+ *
+ * @return HTTP Configuration
+ */
+ @Override
+ protected HttpConfiguration getHttpConfiguration() {
+ final HttpConfiguration httpConfiguration = super.getHttpConfiguration();
+
+ httpConfiguration.setRequestHeaderSize(headerSize);
+ httpConfiguration.setResponseHeaderSize(headerSize);
+ httpConfiguration.setIdleTimeout(idleTimeout);
+
+ return httpConfiguration;
+ }
+
+ /**
+ * Get Jetty Server SSL Context Factory and reuse the same instance for multiple invocations
+ *
+ * @return Jetty Server SSL Context Factory
+ */
+ @Override
+ protected SslContextFactory.Server getSslContextFactory() {
+ if (sslContextFactory == null) {
+ sslContextFactory = super.getSslContextFactory();
+
+ if (StringUtils.isNotBlank(includeCipherSuites)) {
+ final String[] cipherSuites = getCipherSuites(includeCipherSuites);
+ sslContextFactory.setIncludeCipherSuites(cipherSuites);
+ }
+ if (StringUtils.isNotBlank(excludeCipherSuites)) {
+ final String[] cipherSuites = getCipherSuites(excludeCipherSuites);
+ sslContextFactory.setExcludeCipherSuites(cipherSuites);
+ }
+
+ if (storeScanInterval != null) {
+ sslContextFactory.setKeyStorePath(tlsConfiguration.getKeystorePath());
+ final KeyStoreScanner keyStoreScanner = new KeyStoreScanner(sslContextFactory);
+ keyStoreScanner.setScanInterval(storeScanInterval);
+ getServer().addBean(keyStoreScanner);
+
+ sslContextFactory.setTrustStorePath(tlsConfiguration.getTruststorePath());
+ final TrustStoreScanner trustStoreScanner = new TrustStoreScanner(sslContextFactory);
+ trustStoreScanner.setScanInterval(storeScanInterval);
+ getServer().addBean(trustStoreScanner);
+ }
+ }
+
+ return sslContextFactory;
+ }
+
+ private void setApplicationLayerProtocols(final NiFiProperties properties) {
+ final Set<String> protocols = properties.getWebHttpsApplicationProtocols();
+
+ final Set<ApplicationLayerProtocol> applicationLayerProtocols = Arrays.stream(ApplicationLayerProtocol.values())
+ .filter(
+ applicationLayerProtocol -> protocols.contains(applicationLayerProtocol.getProtocol())
+ )
+ .collect(Collectors.toSet());
+ setApplicationLayerProtocols(applicationLayerProtocols);
+ }
+
+ private int getIdleTimeout(final NiFiProperties properties) {
+ final String autoRefreshInterval = StringUtils.defaultIfBlank(properties.getAutoRefreshInterval(), DEFAULT_AUTO_REFRESH_INTERVAL);
+ final double autoRefreshMilliseconds = FormatUtils.getPreciseTimeDuration(autoRefreshInterval, TimeUnit.MILLISECONDS);
+ return Math.multiplyExact((int) autoRefreshMilliseconds, IDLE_TIMEOUT_MULTIPLIER);
+ }
+
+ private String[] getCipherSuites(final String cipherSuitesProperty) {
+ return cipherSuitesProperty.split(CIPHER_SUITE_SEPARATOR_PATTERN);
+ }
+
+ private static int getPort(final NiFiProperties properties) {
+ final Integer httpsPort = properties.getSslPort();
+ final Integer httpPort = properties.getPort();
+
+ if (ObjectUtils.allNull(httpsPort, httpPort)) {
+ throw new IllegalStateException("Invalid port configuration in nifi.properties: Neither nifi.web.https.port nor nifi.web.http.port specified");
+ } else if (ObjectUtils.allNotNull(httpsPort, httpPort)) {
+ throw new IllegalStateException("Invalid port configuration in nifi.properties: Both nifi.web.https.port and nifi.web.http.port specified");
+ }
+
+ return ObjectUtils.defaultIfNull(httpsPort, httpPort);
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java
deleted file mode 100644
index 842826d908..0000000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/JettyServerTest.java
+++ /dev/null
@@ -1,181 +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.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;
-import org.eclipse.jetty.util.ssl.SslContextFactory;
-import org.junit.Test;
-
-public class JettyServerTest {
- @Test
- public void testConfigureSslContextFactoryWithKeystorePasswordAndKeyPassword() {
- // Expect that if we set both passwords, KeyStore password is used for KeyStore, Key password is used for Key Manager
- String testKeystorePassword = "testKeystorePassword";
- String testKeyPassword = "testKeyPassword";
-
- final Map<String, String> addProps = new HashMap<>();
- addProps.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, testKeystorePassword);
- addProps.put(NiFiProperties.SECURITY_KEY_PASSWD, testKeyPassword);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setKeyStorePassword(testKeystorePassword);
- verify(mockSCF).setKeyManagerPassword(testKeyPassword);
- }
-
- @Test
- public void testConfigureSslContextFactoryWithKeyPassword() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
- // Expect that with no KeyStore password, we will only need to set Key Manager Password
- String testKeyPassword = "testKeyPassword";
-
- final Map<String, String> addProps = new HashMap<>();
- addProps.put(NiFiProperties.SECURITY_KEY_PASSWD, testKeyPassword);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setKeyManagerPassword(testKeyPassword);
- verify(mockSCF, never()).setKeyStorePassword(anyString());
- }
-
- @Test
- public void testConfigureSslContextFactoryWithKeystorePassword() throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {
- // Expect that with no KeyPassword, we use the same one from the KeyStore
- String testKeystorePassword = "testKeystorePassword";
-
- final Map<String, String> addProps = new HashMap<>();
- addProps.put(NiFiProperties.SECURITY_KEYSTORE_PASSWD, testKeystorePassword);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setKeyStorePassword(testKeystorePassword);
- verify(mockSCF).setKeyManagerPassword(testKeystorePassword);
- }
-
- @Test
- public void testConfigureSslContextFactoryWithJksKeyStore() {
- // Expect that we will not set provider for jks keystore
- final Map<String, String> addProps = new HashMap<>();
- String keyStoreType = KeystoreType.JKS.toString();
- addProps.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, keyStoreType);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setKeyStoreType(keyStoreType);
- verify(mockSCF).setKeyStoreProvider(SUN_PROVIDER_NAME);
- }
-
- @Test
- public void testConfigureSslContextFactoryWithPkcsKeyStore() {
- // Expect that we will set Bouncy Castle provider for pkcs12 keystore
- final Map<String, String> addProps = new HashMap<>();
- String keyStoreType = KeystoreType.PKCS12.toString();
- addProps.put(NiFiProperties.SECURITY_KEYSTORE_TYPE, keyStoreType);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setKeyStoreType(keyStoreType);
- verify(mockSCF).setKeyStoreProvider(BouncyCastleProvider.PROVIDER_NAME);
- }
-
- @Test
- public void testConfigureSslContextFactoryWithJksTrustStore() {
- // Expect that we will not set provider for jks truststore
- final Map<String, String> addProps = new HashMap<>();
- String trustStoreType = KeystoreType.JKS.toString();
- addProps.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, trustStoreType);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- verify(mockSCF).setTrustStoreType(trustStoreType);
- verify(mockSCF).setTrustStoreProvider(SUN_PROVIDER_NAME);
- }
-
- @Test
- public void testConfigureSslContextFactoryWithPkcsTrustStore() {
- // Expect that we will set Bouncy Castle provider for pkcs12 truststore
- final Map<String, String> addProps = new HashMap<>();
- String trustStoreType = KeystoreType.PKCS12.toString();
- addProps.put(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, trustStoreType);
- NiFiProperties nifiProperties = NiFiProperties.createBasicNiFiProperties(null, addProps);
- SslContextFactory.Server mockSCF = mock(SslContextFactory.Server.class);
-
- JettyServer.configureSslContextFactory(mockSCF, nifiProperties);
-
- 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());
- }
-}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java
new file mode 100644
index 0000000000..e997f25ac6
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/java/org/apache/nifi/web/server/connector/FrameworkServerConnectorFactoryTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.server.connector;
+
+import org.apache.nifi.jetty.configuration.connector.alpn.ALPNServerConnectionFactory;
+import org.apache.nifi.security.util.TemporaryKeyStoreBuilder;
+import org.apache.nifi.security.util.TlsConfiguration;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.server.util.TrustStoreScanner;
+import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
+import org.eclipse.jetty.server.HttpConnectionFactory;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.ServerConnector;
+import org.eclipse.jetty.server.SslConnectionFactory;
+import org.eclipse.jetty.util.ssl.KeyStoreScanner;
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class FrameworkServerConnectorFactoryTest {
+ private static final String PROPERTIES_FILE_PATH = null;
+
+ private static final int HTTP_PORT = 8080;
+
+ private static final int HTTPS_PORT = 8443;
+
+ private static final String H2_HTTP_1_1_PROTOCOLS = "h2 http/1.1";
+
+ private static final String EXCLUDED_CIPHER_SUITE = "TLS_PSK_WITH_NULL_SHA";
+
+ private static final String INCLUDED_CIPHER_SUITE_PATTERN = ".*AES_256_GCM.*";
+
+ private static TlsConfiguration tlsConfiguration;
+
+ @BeforeAll
+ static void setTlsConfiguration() {
+ final TemporaryKeyStoreBuilder builder = new TemporaryKeyStoreBuilder();
+ tlsConfiguration = builder.build();
+ }
+
+ @Test
+ void testHttpPortAndHttpsPortNotConfiguredException() {
+ final Properties serverProperties = new Properties();
+ final NiFiProperties properties = getProperties(serverProperties);
+
+ final Server server = new Server();
+ final IllegalStateException e = assertThrows(IllegalStateException.class, () -> new FrameworkServerConnectorFactory(server, properties));
+ assertTrue(e.getMessage().contains(NiFiProperties.WEB_HTTP_PORT));
+ }
+
+ @Test
+ void testHttpPortAndHttpsPortException() {
+ final Properties serverProperties = new Properties();
+ serverProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, Integer.toString(HTTP_PORT));
+ serverProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, Integer.toString(HTTPS_PORT));
+ final NiFiProperties properties = getProperties(serverProperties);
+
+ final Server server = new Server();
+ final IllegalStateException e = assertThrows(IllegalStateException.class, () -> new FrameworkServerConnectorFactory(server, properties));
+ assertTrue(e.getMessage().contains(NiFiProperties.WEB_HTTP_PORT));
+ }
+
+ @Test
+ void testGetServerConnector() {
+ final Properties serverProperties = new Properties();
+ serverProperties.setProperty(NiFiProperties.WEB_HTTP_PORT, Integer.toString(HTTP_PORT));
+ final NiFiProperties properties = getProperties(serverProperties);
+
+ final Server server = new Server();
+ final FrameworkServerConnectorFactory factory = new FrameworkServerConnectorFactory(server, properties);
+
+ final ServerConnector serverConnector = factory.getServerConnector();
+
+ assertHttpConnectionFactoryFound(serverConnector);
+ }
+
+ @Test
+ void testGetServerConnectorHttps() {
+ final Properties serverProperties = getHttpsProperties();
+ serverProperties.setProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_EXCLUDE, EXCLUDED_CIPHER_SUITE);
+ serverProperties.setProperty(NiFiProperties.WEB_HTTPS_CIPHERSUITES_INCLUDE, INCLUDED_CIPHER_SUITE_PATTERN);
+ serverProperties.setProperty(NiFiProperties.SECURITY_AUTO_RELOAD_ENABLED, Boolean.TRUE.toString());
+ final FrameworkServerConnectorFactory factory = getHttpsConnectorFactory(serverProperties);
+
+ final ServerConnector serverConnector = factory.getServerConnector();
+
+ assertHttpConnectionFactoryFound(serverConnector);
+ final SslConnectionFactory sslConnectionFactory = assertSslConnectionFactoryFound(serverConnector);
+
+ final SslContextFactory.Server sslContextFactory = (SslContextFactory.Server) sslConnectionFactory.getSslContextFactory();
+ assertTrue(sslContextFactory.getNeedClientAuth());
+ assertFalse(sslContextFactory.getWantClientAuth());
+
+ assertCipherSuitesConfigured(sslContextFactory);
+ assertAutoReloadEnabled(serverConnector);
+
+ final HTTP2ServerConnectionFactory http2ServerConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+ assertNull(http2ServerConnectionFactory);
+ }
+
+ @Test
+ void testGetServerConnectorHttpsHttp2AndHttp11() {
+ final Properties serverProperties = getHttpsProperties();
+ serverProperties.setProperty(NiFiProperties.WEB_HTTPS_APPLICATION_PROTOCOLS, H2_HTTP_1_1_PROTOCOLS);
+ final FrameworkServerConnectorFactory factory = getHttpsConnectorFactory(serverProperties);
+
+ final ServerConnector serverConnector = factory.getServerConnector();
+
+ assertHttpConnectionFactoryFound(serverConnector);
+ assertSslConnectionFactoryFound(serverConnector);
+
+ final HTTP2ServerConnectionFactory http2ServerConnectionFactory = serverConnector.getConnectionFactory(HTTP2ServerConnectionFactory.class);
+ assertNotNull(http2ServerConnectionFactory);
+
+ final ALPNServerConnectionFactory alpnServerConnectionFactory = serverConnector.getConnectionFactory(ALPNServerConnectionFactory.class);
+ assertNotNull(alpnServerConnectionFactory);
+ }
+
+ private Properties getHttpsProperties() {
+ final Properties serverProperties = new Properties();
+ serverProperties.setProperty(NiFiProperties.WEB_HTTPS_PORT, Integer.toString(HTTPS_PORT));
+ serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE, tlsConfiguration.getKeystorePath());
+ serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_TYPE, tlsConfiguration.getKeystoreType().getType());
+ serverProperties.setProperty(NiFiProperties.SECURITY_KEYSTORE_PASSWD, tlsConfiguration.getKeystorePassword());
+ serverProperties.setProperty(NiFiProperties.SECURITY_KEY_PASSWD, tlsConfiguration.getKeyPassword());
+ serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE, tlsConfiguration.getTruststorePath());
+ serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_TYPE, tlsConfiguration.getTruststoreType().getType());
+ serverProperties.setProperty(NiFiProperties.SECURITY_TRUSTSTORE_PASSWD, tlsConfiguration.getTruststorePassword());
+ return serverProperties;
+ }
+
+ private FrameworkServerConnectorFactory getHttpsConnectorFactory(final Properties serverProperties) {
+ final NiFiProperties properties = getProperties(serverProperties);
+ final Server server = new Server();
+ return new FrameworkServerConnectorFactory(server, properties);
+ }
+
+ private SslConnectionFactory assertSslConnectionFactoryFound(final ServerConnector serverConnector) {
+ final SslConnectionFactory sslConnectionFactory = serverConnector.getConnectionFactory(SslConnectionFactory.class);
+ assertNotNull(sslConnectionFactory);
+ return sslConnectionFactory;
+ }
+
+ private void assertHttpConnectionFactoryFound(final ServerConnector serverConnector) {
+ assertNotNull(serverConnector);
+ final HttpConnectionFactory connectionFactory = serverConnector.getConnectionFactory(HttpConnectionFactory.class);
+ assertNotNull(connectionFactory);
+ }
+
+ private void assertCipherSuitesConfigured(final SslContextFactory sslContextFactory) {
+ final String[] excludedCipherSuites = sslContextFactory.getExcludeCipherSuites();
+ assertEquals(1, excludedCipherSuites.length);
+ assertEquals(EXCLUDED_CIPHER_SUITE, excludedCipherSuites[0]);
+
+ final String[] includedCipherSuites = sslContextFactory.getIncludeCipherSuites();
+ assertEquals(1, includedCipherSuites.length);
+ assertEquals(INCLUDED_CIPHER_SUITE_PATTERN, includedCipherSuites[0]);
+ }
+
+ private void assertAutoReloadEnabled(final ServerConnector serverConnector) {
+ final Server server = serverConnector.getServer();
+ final KeyStoreScanner keyStoreScanner = server.getBean(KeyStoreScanner.class);
+ assertNotNull(keyStoreScanner);
+
+ final TrustStoreScanner trustStoreScanner = server.getBean(TrustStoreScanner.class);
+ assertNotNull(trustStoreScanner);
+ }
+
+ private NiFiProperties getProperties(final Properties serverProperties) {
+ return NiFiProperties.createBasicNiFiProperties(PROPERTIES_FILE_PATH, serverProperties);
+ }
+}