mirror of https://github.com/apache/nifi.git
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 <kdoran@apache.org>
This commit is contained in:
parent
92b4a3208f
commit
7a4990e7fe
|
@ -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*`
|
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.
|
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
|
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
|
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:
|
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.
|
* 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.
|
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
|
* 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
|
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.
|
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.
|
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
|
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
|
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:
|
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
|
** 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
|
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.
|
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
|
** 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
|
||||||
|
|
|
@ -22,7 +22,27 @@
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>nifi-jetty</artifactId>
|
<artifactId>nifi-jetty</artifactId>
|
||||||
<packaging>jar</packaging>
|
<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>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-log4j12</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.nifi</groupId>
|
<groupId>org.apache.nifi</groupId>
|
||||||
<artifactId>nifi-api</artifactId>
|
<artifactId>nifi-api</artifactId>
|
||||||
|
@ -168,6 +188,12 @@
|
||||||
<artifactId>nifi-framework-cluster</artifactId>
|
<artifactId>nifi-framework-cluster</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.stefanbirkner</groupId>
|
||||||
|
<artifactId>system-rules</artifactId>
|
||||||
|
<version>1.16.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,14 @@ public class JettyServer implements NiFiServer {
|
||||||
server.setHandler(allHandlers);
|
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) {
|
private Handler loadWars(final Set<Bundle> bundles) {
|
||||||
|
|
||||||
// load WARs
|
// load WARs
|
||||||
|
@ -601,106 +609,143 @@ public class JettyServer implements NiFiServer {
|
||||||
httpConfiguration.setRequestHeaderSize(headerSize);
|
httpConfiguration.setRequestHeaderSize(headerSize);
|
||||||
httpConfiguration.setResponseHeaderSize(headerSize);
|
httpConfiguration.setResponseHeaderSize(headerSize);
|
||||||
|
|
||||||
if (props.getPort() != null) {
|
// Check if both HTTP and HTTPS connectors are configured and fail if both are configured
|
||||||
final Integer port = props.getPort();
|
if (bothHttpAndHttpsConnectorsConfigured(props)) {
|
||||||
if (port < 0 || (int) Math.pow(2, 16) <= port) {
|
logger.error("NiFi only supports one mode of HTTP or HTTPS operation, not both simultaneously. " +
|
||||||
throw new ServerConfigurationException("Invalid HTTP port: " + port);
|
"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);
|
|
||||||
|
|
||||||
final List<Connector> serverConnectors = Lists.newArrayList();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.getSslPort() != null) {
|
if (props.getSslPort() != null) {
|
||||||
final Integer port = props.getSslPort();
|
configureHttpsConnector(server, httpConfiguration);
|
||||||
if (port < 0 || (int) Math.pow(2, 16) <= port) {
|
} else if (props.getPort() != null) {
|
||||||
throw new ServerConfigurationException("Invalid HTTPs port: " + port);
|
configureHttpConnector(server, httpConfiguration);
|
||||||
}
|
} else {
|
||||||
|
logger.error("Neither the HTTP nor HTTPS connector was configured in nifi.properties");
|
||||||
logger.info("Configuring Jetty for HTTPs on port: " + port);
|
startUpFailure(new IllegalStateException("Must configure HTTP or HTTPS connector"));
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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())));
|
|
||||||
}
|
|
||||||
// add all connectors
|
|
||||||
serverConnectors.forEach(server::addConnector);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration) {
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = Lists.newArrayList();
|
||||||
|
|
||||||
|
// 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(hostname)) {
|
||||||
|
serverConnector.setHost(hostname);
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
// add some secure config
|
||||||
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
|
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
|
||||||
httpsConfiguration.setSecureScheme("https");
|
httpsConfiguration.setSecureScheme("https");
|
||||||
httpsConfiguration.setSecurePort(props.getSslPort());
|
httpsConfiguration.setSecurePort(port);
|
||||||
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
|
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
|
||||||
// build the connector
|
// build the connector
|
||||||
|
@ -990,3 +1035,8 @@ public class JettyServer implements NiFiServer {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ServerConnectorCreator<Server, HttpConfiguration, ServerConnector> {
|
||||||
|
ServerConnector create(Server server, HttpConfiguration httpConfiguration);
|
||||||
|
}
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
Loading…
Reference in New Issue