Repository: nifi Updated Branches: refs/heads/master 4a68dacc4 -> 8b9034371
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 <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/8b903437 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/8b903437 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/8b903437 Branch: refs/heads/master Commit: 8b90343715be8e28e465991fcdde00f64a8c6c6f Parents: 4a68dac Author: Jeff Storck <[email protected]> Authored: Tue Jan 17 16:24:38 2017 -0500 Committer: Bryan Rosander <[email protected]> Committed: Wed Feb 15 18:39:26 2017 -0500 ---------------------------------------------------------------------- .../org/apache/nifi/util/NiFiProperties.java | 46 ++++++++ .../src/main/asciidoc/administration-guide.adoc | 25 ++++- .../nifi-framework/nifi-resources/pom.xml | 2 + .../src/main/resources/conf/nifi.properties | 2 + .../org/apache/nifi/web/server/JettyServer.java | 111 ++++++++++++++----- 5 files changed, 159 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/8b903437/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java ---------------------------------------------------------------------- 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 933d62d..6a39852 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 @@ -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(); } http://git-wip-us.apache.org/repos/asf/nifi/blob/8b903437/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 25b2f11..0daa775 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -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. |==== http://git-wip-us.apache.org/repos/asf/nifi/blob/8b903437/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/pom.xml ---------------------------------------------------------------------- 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 9baa3ea..662f87e 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 @@ -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> http://git-wip-us.apache.org/repos/asf/nifi/blob/8b903437/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties ---------------------------------------------------------------------- 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 a768af4..11b2d6a 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 @@ -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} http://git-wip-us.apache.org/repos/asf/nifi/blob/8b903437/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 61b0e86..d2def9f 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 @@ -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);
