Repository: nifi Updated Branches: refs/heads/master 92b4a3208 -> 7a4990e7f
NIFI-5146 Only support HTTP or HTTPS operation for NiFi API/UI - Added logic to check for simultaneous configuration of HTTP and HTTPS connectors in JettyServer. - Added test logging resources. Added unit tests. - Refactored shared functionality to generic method which accepts lambdas. Fixed unit test with logging side effects. - Added note about exclusive HTTP/HTTPS behavior to Admin Guide. Fixed typos. This closes #2683. Signed-off-by: Kevin Doran <kdo...@apache.org> Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/7a4990e7 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/7a4990e7 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/7a4990e7 Branch: refs/heads/master Commit: 7a4990e7fe7c38c95b4ee1436a822428ff1f5f98 Parents: 92b4a32 Author: Andy LoPresto <alopre...@apache.org> Authored: Thu May 3 16:02:19 2018 -0700 Committer: Kevin Doran <kdo...@apache.org> Committed: Wed May 9 12:14:11 2018 -0400 ---------------------------------------------------------------------- .../src/main/asciidoc/administration-guide.adoc | 10 +- .../nifi-framework/nifi-web/nifi-jetty/pom.xml | 26 ++ .../org/apache/nifi/web/server/JettyServer.java | 220 ++++++++++------ .../web/server/JettyServerGroovyTest.groovy | 258 +++++++++++++++++++ .../src/test/resources/log4j.properties | 27 ++ 5 files changed, 451 insertions(+), 90 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-docs/src/main/asciidoc/administration-guide.adoc ---------------------------------------------------------------------- diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 64facc7..b9857d6 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -148,7 +148,7 @@ should run on. If it is desired that the HTTPS interface be accessible from all admins to configure the application to run only on specific network interfaces, `nifi.web.http.network.interface*` or `nifi.web.https.network.interface*` properties can be specified. -NOTE: It is important when enabling HTTPS that the `nifi.web.http.port` property be unset. +NOTE: It is important when enabling HTTPS that the `nifi.web.http.port` property be unset. NiFi only supports running on HTTP *or* HTTPS, not both simultaneously. Similar to `nifi.security.needClientAuth`, the web server can be configured to require certificate based client authentication for users accessing the User Interface. In order to do this it must be configured to not support username/password authentication using <<ldap_login_identity_provider>> or <<kerberos_login_identity_provider>>. Either of these options @@ -1967,8 +1967,8 @@ they must be set the same on every instance in the cluster. For each Node, the minimum properties to configure are as follows: -* Under the _Web Properties_ section, set either the http or https port that you want the Node to run on. - Also, consider whether you need to set the http or https host property. +* Under the _Web Properties_ section, set either the HTTP or HTTPS port that you want the Node to run on. + Also, consider whether you need to set the HTTP or HTTPS host property. All nodes in the cluster should use the same protocol setting. * Under the _State Management section_, set the `nifi.state.management.provider.cluster` property to the identifier of the Cluster State Provider. Ensure that the Cluster State Provider has been configured in the _state-management.xml_ file. See <<state_providers>> for more information. @@ -2164,7 +2164,7 @@ In order to secure the communications, we need to ensure that both the client an NiFi ZooKeeper client and embedded ZooKeeper server to use Kerberos are provided below. If Kerberos is not already setup in your environment, you can find information on installing and setting up a Kerberos Server at -link:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html[https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html^]. This guide assumes that Kerberos already has been installed in the environment in which NiFi is running. +https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Managing_Smart_Cards/Configuring_a_Kerberos_5_Server.html[Red Hat Customer Portal: Configuring a Kerberos 5 Server]. This guide assumes that Kerberos already has been installed in the environment in which NiFi is running. Note, the following procedures for kerberizing an Embedded ZooKeeper server in your NiFi Node and kerberizing a ZooKeeper NiFi client will require that Kerberos client libraries be installed. This is accomplished in Fedora-based Linux distributions via: @@ -2652,7 +2652,7 @@ documentation of the proxy for guidance for your deployment environment and use ** By default, if NiFi is running securely it will only accept HTTP requests with a Host header matching the host[:port] that it is bound to. If NiFi is to accept requests directed to a different host[:port] the expected values need to be configured. This may be required when running behind a proxy or in a containerized environment. This is configured in a comma -separated list in _nifi.properties_ using the `nifi.web.proxy.host` property (e.g. localhost:18443, proxyhost:443). IPv6 addressed are accepted. Please refer to +separated list in _nifi.properties_ using the `nifi.web.proxy.host` property (e.g. localhost:18443, proxyhost:443). IPv6 addresses are accepted. Please refer to RFC 5952 Sections link:https://tools.ietf.org/html/rfc5952#section-4[4] and link:https://tools.ietf.org/html/rfc5952#section-6[6] for additional details. ** NiFi will only accept HTTP requests with a X-ProxyContextPath or X-Forwarded-Context header if the value is whitelisted in the `nifi.web.proxy.context.path` property in http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/pom.xml ---------------------------------------------------------------------- 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 80a2a9c..3d1c6ba 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 @@ -22,8 +22,28 @@ </parent> <artifactId>nifi-jetty</artifactId> <packaging>jar</packaging> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.codehaus.groovy</groupId> + <artifactId>groovy-all</artifactId> + <version>2.4.13</version> + <scope>compile</scope> + <exclusions> + <exclusion> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </exclusion> + </exclusions> + </dependency> + </dependencies> + </dependencyManagement> <dependencies> <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + </dependency> + <dependency> <groupId>org.apache.nifi</groupId> <artifactId>nifi-api</artifactId> <scope>compile</scope> @@ -168,6 +188,12 @@ <artifactId>nifi-framework-cluster</artifactId> <scope>compile</scope> </dependency> + <dependency> + <groupId>com.github.stefanbirkner</groupId> + <artifactId>system-rules</artifactId> + <version>1.16.0</version> + <scope>test</scope> + </dependency> </dependencies> </project> http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/main/java/org/apache/nifi/web/server/JettyServer.java ---------------------------------------------------------------------- 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 de53a24..83b489f 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 @@ -173,6 +173,14 @@ public class JettyServer implements NiFiServer { server.setHandler(allHandlers); } + /** + * Instantiates this object but does not perform any configuration. Used for unit testing. + */ + JettyServer(Server server, NiFiProperties properties) { + this.server = server; + this.props = properties; + } + private Handler loadWars(final Set<Bundle> bundles) { // load WARs @@ -601,106 +609,143 @@ public class JettyServer implements NiFiServer { httpConfiguration.setRequestHeaderSize(headerSize); httpConfiguration.setResponseHeaderSize(headerSize); - if (props.getPort() != null) { - final Integer port = props.getPort(); - if (port < 0 || (int) Math.pow(2, 16) <= port) { - throw new ServerConfigurationException("Invalid HTTP port: " + port); - } + // 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")); + } - logger.info("Configuring Jetty for HTTP on port: " + port); + 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")); + } + } - final List<Connector> serverConnectors = Lists.newArrayList(); + /** + * 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); + } - final Map<String, String> httpNetworkInterfaces = props.getHttpNetworkInterfaces(); - if (httpNetworkInterfaces.isEmpty() || httpNetworkInterfaces.values().stream().filter(value -> !Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) { - // create the connector - final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration)); - // set host and port - if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.WEB_HTTP_HOST))) { - http.setHost(props.getProperty(NiFiProperties.WEB_HTTP_HOST)); - } - http.setPort(port); - serverConnectors.add(http); - } else { - // add connectors for all IPs from http network interfaces - serverConnectors.addAll(Lists.newArrayList(httpNetworkInterfaces.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); - } - return iface; - }).filter(Objects::nonNull).flatMap(iface -> Collections.list(iface.getInetAddresses()).stream()) - .map(inetAddress -> { - // create the connector - final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration)); - // set host and port - http.setHost(inetAddress.getHostAddress()); - http.setPort(port); - return http; - }).collect(Collectors.toList()))); - } - // add all connectors - serverConnectors.forEach(server::addConnector); - } + /** + * 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); + } - if (props.getSslPort() != null) { - final Integer port = props.getSslPort(); - if (port < 0 || (int) Math.pow(2, 16) <= port) { - throw new ServerConfigurationException("Invalid HTTPs port: " + port); - } + /** + * 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 HTTPs on port: " + port); + logger.info("Configuring Jetty for " + connectorLabel + " on port: " + port); - final List<Connector> serverConnectors = Lists.newArrayList(); + final List<Connector> serverConnectors = Lists.newArrayList(); - final Map<String, String> httpsNetworkInterfaces = props.getHttpsNetworkInterfaces(); - if (httpsNetworkInterfaces.isEmpty() || httpsNetworkInterfaces.values().stream().filter(value -> !Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) { - final ServerConnector https = createUnconfiguredSslServerConnector(server, httpConfiguration); + // If the interfaces collection is empty or each element is empty + if (networkInterfaces.isEmpty() || networkInterfaces.values().stream().filter(value -> !Strings.isNullOrEmpty(value)).collect(Collectors.toList()).isEmpty()) { + final ServerConnector serverConnector = serverConnectorCreator.create(server, configuration); - // set host and port - if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.WEB_HTTPS_HOST))) { - https.setHost(props.getProperty(NiFiProperties.WEB_HTTPS_HOST)); - } - https.setPort(port); - serverConnectors.add(https); - } else { - // add connectors for all IPs from https network interfaces - serverConnectors.addAll(Lists.newArrayList(httpsNetworkInterfaces.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); - } - return iface; - }).filter(Objects::nonNull).flatMap(iface -> Collections.list(iface.getInetAddresses()).stream()) - .map(inetAddress -> { - final ServerConnector https = createUnconfiguredSslServerConnector(server, httpConfiguration); - - // set host and port - https.setHost(inetAddress.getHostAddress()); - https.setPort(port); - return https; - }).collect(Collectors.toList()))); + // Set host and port + if (StringUtils.isNotBlank(hostname)) { + serverConnector.setHost(hostname); } - // add all connectors - serverConnectors.forEach(server::addConnector); + serverConnector.setPort(port); + serverConnectors.add(serverConnector); + } else { + // Add connectors for all IPs from network interfaces + serverConnectors.addAll(Lists.newArrayList(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); + } + 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); + return serverConnector; + }).collect(Collectors.toList()))); } + // Add all connectors + serverConnectors.forEach(server::addConnector); } - private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration) { + /** + * 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(props.getSslPort()); + httpsConfiguration.setSecurePort(port); httpsConfiguration.addCustomizer(new SecureRequestCustomizer()); // build the connector @@ -990,3 +1035,8 @@ public class JettyServer implements NiFiServer { } }; } + +@FunctionalInterface +interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> { + ServerConnector create(Server server, HttpConfiguration httpConfiguration); +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy new file mode 100644 index 0000000..174efc0 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/groovy/org/apache/nifi/web/server/JettyServerGroovyTest.groovy @@ -0,0 +1,258 @@ +/* + * 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 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.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.junit.After +import org.junit.AfterClass +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Rule +import org.junit.Test +import org.junit.contrib.java.lang.system.Assertion +import org.junit.contrib.java.lang.system.ExpectedSystemExit +import org.junit.contrib.java.lang.system.SystemErrRule +import org.junit.contrib.java.lang.system.SystemOutRule +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 JettyServerGroovyTest extends GroovyTestCase { + private static final Logger logger = LoggerFactory.getLogger(JettyServerGroovyTest.class) + + @Rule + public final ExpectedSystemExit exit = ExpectedSystemExit.none() + + @Rule + public final SystemOutRule systemOutRule = new SystemOutRule().enableLog() + + @Rule + public final SystemErrRule systemErrRule = new SystemErrRule().enableLog() + + @BeforeClass + static void setUpOnce() throws Exception { + Security.addProvider(new BouncyCastleProvider()) + + logger.metaClass.methodMissing = { String name, args -> + logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}") + } + + TestAppender.reset() + } + + @AfterClass + static void tearDownOnce() throws Exception { + + } + + @Before + void setUp() throws Exception { + + } + + @After + void tearDown() throws Exception { + TestAppender.reset() + } + + @Test + void testShouldDetectHttpAndHttpsConfigurationsBothPresent() { + // Arrange + Map badProps = [ + (NiFiProperties.WEB_HTTP_HOST) : "localhost", + (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com", + (NiFiProperties.WEB_THREADS) : NiFiProperties.DEFAULT_WEB_THREADS + ] + NiFiProperties mockProps = [ + getPort : { -> 8080 }, + getSslPort : { -> 8443 }, + getProperty: { String prop -> + String value = badProps[prop] ?: "no_value" + logger.mock("getProperty(${prop}) -> ${value}") + value + }, + ] as StandardNiFiProperties + + // Act + boolean bothConfigsPresent = JettyServer.bothHttpAndHttpsConnectorsConfigured(mockProps) + logger.info("Both configs present: ${bothConfigsPresent}") + def log = TestAppender.getLogLines() + + // Assert + assert bothConfigsPresent + assert !log.isEmpty() + assert log.first() =~ "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" + } + + @Test + void testDetectHttpAndHttpsConfigurationsShouldAllowEither() { + // Arrange + Map httpMap = [ + (NiFiProperties.WEB_HTTP_HOST) : "localhost", + (NiFiProperties.WEB_HTTPS_HOST): null, + ] + NiFiProperties httpProps = [ + getPort : { -> 8080 }, + getSslPort : { -> null }, + getProperty: { String prop -> + String value = httpMap[prop] ?: "no_value" + logger.mock("getProperty(${prop}) -> ${value}") + value + }, + ] as StandardNiFiProperties + + Map httpsMap = [ + (NiFiProperties.WEB_HTTP_HOST) : null, + (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com", + ] + NiFiProperties httpsProps = [ + getPort : { -> null }, + getSslPort : { -> 8443 }, + getProperty: { String prop -> + String value = httpsMap[prop] ?: "no_value" + logger.mock("getProperty(${prop}) -> ${value}") + value + }, + ] as StandardNiFiProperties + + // Act + boolean bothConfigsPresentForHttp = JettyServer.bothHttpAndHttpsConnectorsConfigured(httpProps) + logger.info("Both configs present for HTTP properties: ${bothConfigsPresentForHttp}") + + boolean bothConfigsPresentForHttps = JettyServer.bothHttpAndHttpsConnectorsConfigured(httpsProps) + logger.info("Both configs present for HTTPS properties: ${bothConfigsPresentForHttps}") + def log = TestAppender.getLogLines() + + // Assert + assert !bothConfigsPresentForHttp + assert !bothConfigsPresentForHttps + + // Verifies that the warning was not logged + assert log.size() == 2 + assert log.first() == "Both configs present for HTTP properties: false" + assert log.last() == "Both configs present for HTTPS properties: false" + } + + @Test + void testShouldFailToStartWithHttpAndHttpsConfigurationsBothPresent() { + // Arrange + Map badProps = [ + (NiFiProperties.WEB_HTTP_HOST) : "localhost", + (NiFiProperties.WEB_HTTPS_HOST): "secure.host.com", + ] + NiFiProperties mockProps = [ + getPort : { -> 8080 }, + getSslPort : { -> 8443 }, + getProperty: { String prop -> + String value = badProps[prop] ?: "no_value" + logger.mock("getProperty(${prop}) -> ${value}") + value + }, + getWebThreads: { -> NiFiProperties.DEFAULT_WEB_THREADS }, + getWebMaxHeaderSize: { -> NiFiProperties.DEFAULT_WEB_MAX_HEADER_SIZE }, + isHTTPSConfigured: { -> true } + ] as StandardNiFiProperties + + // The web server should fail to start and exit Java + exit.expectSystemExitWithStatus(1) + exit.checkAssertionAfterwards(new Assertion() { + void checkAssertion() { + 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..."} + } + }) + + // Act + JettyServer jettyServer = new JettyServer(mockProps, [] as Set<Bundle>) + + // Assert + + // Assertions defined above + } + + @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", + ])) + + Server internalServer = new Server() + JettyServer jetty = new JettyServer(internalServer, httpsProps) + + // Act + jetty.configureHttpsConnector(internalServer, new HttpConfiguration()) + List<Connector> connectors = Arrays.asList(internalServer.connectors) + + // Assert + assert connectors.size() == 1 + ServerConnector connector = connectors.first() as ServerConnector + assert connector.host == "secure.host.com" + assert connector.port == 8443 + } +} + +class TestAppender extends AppenderSkeleton { + static final List<LoggingEvent> events = new ArrayList<>() + + @Override + protected void append(LoggingEvent e) { + synchronized (events) { + events.add(e) + } + } + + static void reset() { + synchronized (events) { + events.clear() + } + } + + @Override + void close() { + } + + @Override + boolean requiresLayout() { + return false + } + + static List<String> getLogLines() { + synchronized (events) { + events.collect { LoggingEvent le -> le.getRenderedMessage() } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi/blob/7a4990e7/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties new file mode 100644 index 0000000..4608f9e --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-jetty/src/test/resources/log4j.properties @@ -0,0 +1,27 @@ +# +# 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,test + +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 + +log4j.appender.test=org.apache.nifi.web.server.TestAppender +log4j.appender.test.layout=org.apache.log4j.PatternLayout +log4j.appender.test.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss} %p %c{2}: %m%n \ No newline at end of file