NIFI-3355 Allows NiFi to bind to specific network interfaces, with separate interface lists for HTTP and HTTPS.

This closes #1508.

Signed-off-by: Bryan Rosander <brosander@apache.org>
This commit is contained in:
Jeff Storck 2017-01-17 16:24:38 -05:00 committed by Bryan Rosander
parent 4a68dacc43
commit 8b90343715
No known key found for this signature in database
GPG Key ID: 2065F38F3FF65D23
5 changed files with 159 additions and 27 deletions

View File

@ -152,9 +152,11 @@ public abstract class NiFiProperties {
public static final String WEB_HTTP_PORT = "nifi.web.http.port";
public static final String WEB_HTTP_PORT_FORWARDING = "nifi.web.http.port.forwarding";
public static final String WEB_HTTP_HOST = "nifi.web.http.host";
public static final String WEB_HTTP_NETWORK_INTERFACE_PREFIX = "nifi.web.http.network.interface.";
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_NETWORK_INTERFACE_PREFIX = "nifi.web.https.network.interface.";
public static final String WEB_WORKING_DIR = "nifi.web.jetty.working.directory";
public static final String WEB_THREADS = "nifi.web.jetty.threads";
@ -1016,6 +1018,50 @@ public abstract class NiFiProperties {
}
}
/**
* Returns the network interface list to use for HTTP. This method returns a mapping of
* network interface property names to network interface names.
*
* @return the property name and network interface name of all HTTP network interfaces
*/
public Map<String, String> getHttpNetworkInterfaces() {
final Map<String, String> networkInterfaces = new HashMap<>();
// go through each property
for (String propertyName : getPropertyKeys()) {
// determine if the property is a network interface name
if (StringUtils.startsWith(propertyName, WEB_HTTP_NETWORK_INTERFACE_PREFIX)) {
// get the network interface property key
final String key = StringUtils.substringAfter(propertyName,
WEB_HTTP_NETWORK_INTERFACE_PREFIX);
networkInterfaces.put(key, getProperty(propertyName));
}
}
return networkInterfaces;
}
/**
* Returns the network interface list to use for HTTPS. This method returns a mapping of
* network interface property names to network interface names.
*
* @return the property name and network interface name of all HTTPS network interfaces
*/
public Map<String, String> getHttpsNetworkInterfaces() {
final Map<String, String> networkInterfaces = new HashMap<>();
// go through each property
for (String propertyName : getPropertyKeys()) {
// determine if the property is a network interface name
if (StringUtils.startsWith(propertyName, WEB_HTTPS_NETWORK_INTERFACE_PREFIX)) {
// get the network interface property key
final String key = StringUtils.substringAfter(propertyName,
WEB_HTTPS_NETWORK_INTERFACE_PREFIX);
networkInterfaces.put(key, getProperty(propertyName));
}
}
return networkInterfaces;
}
public int size() {
return getPropertyKeys().size();
}

View File

@ -152,8 +152,9 @@ NiFi provides several different configuration options for security purposes. The
Once the above properties have been configured, we can enable the User Interface to be accessed over HTTPS instead of HTTP. This is accomplished
by setting the `nifi.web.https.host` and `nifi.web.https.port` properties. The `nifi.web.https.host` property indicates which hostname the server
should run on. This allows admins to configure the application to run only on specific network interfaces. If it is desired that the HTTPS interface
be accessible from all network interfaces, a value of `0.0.0.0` should be used.
should run on. If it is desired that the HTTPS interface be accessible from all network interfaces, a value of `0.0.0.0` should be used. To allow
admins to configure the application to run only on specific network interfaces, `nifi.web.http.network.interface*` or `nifi.web.http.network.interface*`
properties can be specified.
NOTE: It is important when enabling HTTPS that the `nifi.web.http.port` property be unset.
@ -2162,9 +2163,29 @@ These properties pertain to the web-based User Interface.
|nifi.web.http.host|The HTTP host. It is blank by default.
|nifi.web.http.port|The HTTP port. The default value is 8080.
|nifi.web.http.port.forwarding|The port which forwards incoming HTTP requests to nifi.web.http.host. This property is designed to be used with 'port forwarding', when NiFi has to be started by a non-root user for better security, yet it needs to be accessed via low port to go through a firewall. For example, to expose NiFi via HTTP protocol on port 80, but actually listening on port 8080, you need to configure OS level port forwarding such as `iptables` (Linux/Unix) or `pfctl` (OS X) that redirects requests from 80 to 8080. Then set `nifi.web.http.port` as 8080, and `nifi.web.http.port.forwarding` as 80. It is blank by default.
|nifi.web.http.network.interface*|The name of the network interface to which NiFi should bind for HTTP requests. It is blank by default. +
+
*NOTE*: Multiple network interfaces can be specified by using the *_nifi.web.http.network.interface._* prefix with unique suffixes and separate network interface names as values. +
+
For example, to provide two additional network interfaces, a user could also specify additional properties with keys of: +
+
nifi.web.http.network.interface.eth0=eth0 +
nifi.web.http.network.interface.eth1=eth1 +
+
Providing three total network interfaces, including _nifi.web.http.network.interface.default_.
|nifi.web.https.host|The HTTPS host. It is blank by default.
|nifi.web.https.port|The HTTPS port. It is blank by default. When configuring NiFi to run securely, this port should be configured.
|nifi.web.https.port.forwarding|Same as `nifi.web.http.port.forwarding`, but with HTTPS for secure communication. It is blank by default.
|nifi.web.https.network.interface*|The name of the network interface to which NiFi should bind for HTTPS requests. It is blank by default. +
+
*NOTE*: Multiple network interfaces can be specified by using the *_nifi.web.https.network.interface._* prefix with unique suffixes and separate network interface names as values. +
+
For example, to provide two additional network interfaces, a user could also specify additional properties with keys of: +
+
nifi.web.https.network.interface.eth0=eth0 +
nifi.web.https.network.interface.eth1=eth1 +
+
Providing three total network interfaces, including _nifi.web.https.network.interface.default_.
|nif.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.
|====

View File

@ -119,8 +119,10 @@
<nifi.web.war.directory>./lib</nifi.web.war.directory>
<nifi.web.http.host />
<nifi.web.http.port>8080</nifi.web.http.port>
<nifi.web.http.network.interface.default />
<nifi.web.https.host />
<nifi.web.https.port />
<nifi.web.https.network.interface.default />
<nifi.jetty.work.dir>./work/jetty</nifi.jetty.work.dir>
<nifi.web.jetty.threads>200</nifi.web.jetty.threads>

View File

@ -124,8 +124,10 @@ nifi.remote.input.http.transaction.ttl=30 sec
nifi.web.war.directory=${nifi.web.war.directory}
nifi.web.http.host=${nifi.web.http.host}
nifi.web.http.port=${nifi.web.http.port}
nifi.web.http.network.interface.default=${nifi.web.http.network.interface.default}
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.jetty.working.directory=${nifi.jetty.work.dir}
nifi.web.jetty.threads=${nifi.web.jetty.threads}

View File

@ -16,6 +16,8 @@
*/
package org.apache.nifi.web.server;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
@ -85,9 +87,11 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
/**
* Encapsulates the Jetty instance.
@ -569,17 +573,43 @@ public class JettyServer implements NiFiServer {
logger.info("Configuring Jetty for HTTP on port: " + port);
// create the connector
final ServerConnector http = new ServerConnector(server, new HttpConnectionFactory(httpConfiguration));
final List<Connector> serverConnectors = Lists.newArrayList();
// set host and port
if (StringUtils.isNotBlank(props.getProperty(NiFiProperties.WEB_HTTP_HOST))) {
http.setHost(props.getProperty(NiFiProperties.WEB_HTTP_HOST));
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())));
}
http.setPort(port);
// add this connector
server.addConnector(http);
// add all connectors
serverConnectors.forEach(server::addConnector);
}
if (props.getSslPort() != null) {
@ -590,28 +620,59 @@ public class JettyServer implements NiFiServer {
logger.info("Configuring Jetty for HTTPs on port: " + port);
// add some secure config
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
httpsConfiguration.setSecureScheme("https");
httpsConfiguration.setSecurePort(props.getSslPort());
httpsConfiguration.addCustomizer(new SecureRequestCustomizer());
final List<Connector> serverConnectors = Lists.newArrayList();
// build the connector
final ServerConnector https = new ServerConnector(server,
new SslConnectionFactory(createSslContextFactory(), "http/1.1"),
new HttpConnectionFactory(httpsConfiguration));
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));
// 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())));
}
https.setPort(port);
// add this connector
server.addConnector(https);
// add all connectors
serverConnectors.forEach(server::addConnector);
}
}
private ServerConnector createUnconfiguredSslServerConnector(Server server, HttpConfiguration httpConfiguration) {
// add some secure config
final HttpConfiguration httpsConfiguration = new HttpConfiguration(httpConfiguration);
httpsConfiguration.setSecureScheme("https");
httpsConfiguration.setSecurePort(props.getSslPort());
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 contextFactory = new SslContextFactory();
configureSslContextFactory(contextFactory, props);