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);

Reply via email to