[NO ISSUE][*DB] Enable ability to configure RMI bind address

Change-Id: Ib0b759cbcbf6dc89e98ed378b3e44968356aaa90
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17659
Reviewed-by: Murtadha Al Hubail <mhubail@apache.org>
Tested-by: Michael Blow <mblow@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/nc/NCAppRuntimeContext.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/nc/NCAppRuntimeContext.java
index 1a89168..d783f99 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/nc/NCAppRuntimeContext.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/nc/NCAppRuntimeContext.java
@@ -462,18 +462,10 @@
             final INetworkSecurityManager networkSecurityManager =
                     ncServiceContext.getControllerService().getNetworkSecurityManager();
 
-            // clients need to have the client factory on their classpath- to enable older clients, only use
-            // our client socket factory when SSL is enabled
-            if (networkSecurityManager.getConfiguration().isSslEnabled()) {
-                final RMIServerFactory serverSocketFactory = new RMIServerFactory(networkSecurityManager);
-                final RMIClientFactory clientSocketFactory =
-                        new RMIClientFactory(networkSecurityManager.getConfiguration());
-                metadataNodeStub = (IMetadataNode) UnicastRemoteObject.exportObject(MetadataNode.INSTANCE,
-                        getMetadataProperties().getMetadataPort(), clientSocketFactory, serverSocketFactory);
-            } else {
-                metadataNodeStub = (IMetadataNode) UnicastRemoteObject.exportObject(MetadataNode.INSTANCE,
-                        getMetadataProperties().getMetadataPort());
-            }
+            metadataNodeStub = (IMetadataNode) UnicastRemoteObject.exportObject(MetadataNode.INSTANCE,
+                    getMetadataProperties().getMetadataPort(),
+                    RMIClientFactory.getSocketFactory(networkSecurityManager),
+                    RMIServerFactory.getSocketFactory(networkSecurityManager));
         }
     }
 
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIClientFactory.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIClientFactory.java
index 515e763..ce459e2 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIClientFactory.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIClientFactory.java
@@ -29,6 +29,7 @@
 import javax.net.ssl.SSLSocketFactory;
 
 import org.apache.hyracks.api.network.INetworkSecurityConfig;
+import org.apache.hyracks.api.network.INetworkSecurityManager;
 import org.apache.hyracks.ipc.security.NetworkSecurityManager;
 
 public class RMIClientFactory implements RMIClientSocketFactory, Serializable {
@@ -37,11 +38,21 @@
     private final INetworkSecurityConfig config;
     private transient SocketFactory socketFactory;
 
-    public RMIClientFactory(INetworkSecurityConfig config) {
+    private RMIClientFactory(INetworkSecurityConfig config) {
         this.config = config;
 
     }
 
+    public static RMIClientSocketFactory getSocketFactory(INetworkSecurityManager securityManager) {
+        // clients need to have the client factory on their classpath- to enable older clients, only use
+        // our client socket factory when SSL is enabled
+        INetworkSecurityConfig config = securityManager.getConfiguration();
+        if (config.isSslEnabled()) {
+            return new RMIClientFactory(config);
+        }
+        return null;
+    }
+
     public Socket createSocket(String host, int port) throws IOException {
         synchronized (this) {
             if (socketFactory == null) {
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIServerFactory.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIServerFactory.java
index 9506c5a..0128a87 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIServerFactory.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/RMIServerFactory.java
@@ -19,8 +19,10 @@
 package org.apache.asterix.metadata;
 
 import java.io.IOException;
+import java.net.InetAddress;
 import java.net.ServerSocket;
 import java.rmi.server.RMIServerSocketFactory;
+import java.util.Optional;
 
 import javax.net.ServerSocketFactory;
 
@@ -28,17 +30,34 @@
 
 public class RMIServerFactory implements RMIServerSocketFactory {
 
+    // default backlog used by the JDK (e.g. sun.security.ssl.SSLServerSocketFactoryImpl)
+    private static final int DEFAULT_BACKLOG = 50;
     private final INetworkSecurityManager securityManager;
 
-    public RMIServerFactory(INetworkSecurityManager securityManager) {
+    private RMIServerFactory(INetworkSecurityManager securityManager) {
         this.securityManager = securityManager;
     }
 
+    public static RMIServerSocketFactory getSocketFactory(INetworkSecurityManager securityManager) {
+        if (securityManager.getConfiguration().isSslEnabled()) {
+            return new RMIServerFactory(securityManager);
+        }
+        return null;
+    }
+
     @Override
     public ServerSocket createServerSocket(int port) throws IOException {
+        ServerSocketFactory socketFactory;
         if (securityManager.getConfiguration().isSslEnabled()) {
-            return securityManager.newSSLContext().getServerSocketFactory().createServerSocket(port);
+            socketFactory = securityManager.newSSLContext().getServerSocketFactory();
+        } else {
+            socketFactory = ServerSocketFactory.getDefault();
         }
-        return ServerSocketFactory.getDefault().createServerSocket(port);
+        Optional<InetAddress> rmiBindAddress = securityManager.getConfiguration().getRMIBindAddress();
+        if (rmiBindAddress.isPresent()) {
+            return socketFactory.createServerSocket(port, DEFAULT_BACKLOG, rmiBindAddress.get());
+        } else {
+            return socketFactory.createServerSocket(port);
+        }
     }
 }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/AsterixStateProxy.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/AsterixStateProxy.java
index 2104fdf..cedcccf 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/AsterixStateProxy.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/AsterixStateProxy.java
@@ -43,18 +43,9 @@
 
     public static IAsterixStateProxy registerRemoteObject(INetworkSecurityManager networkSecurityManager,
             int metadataCallbackPort) throws RemoteException {
-        IAsterixStateProxy stub;
-        // clients need to have the client factory on their classpath- to enable older clients, only use
-        // our client socket factory when SSL is enabled
-        if (networkSecurityManager.getConfiguration().isSslEnabled()) {
-            final RMIServerFactory serverSocketFactory = new RMIServerFactory(networkSecurityManager);
-            final RMIClientFactory clientSocketFactory =
-                    new RMIClientFactory(networkSecurityManager.getConfiguration());
-            stub = (IAsterixStateProxy) UnicastRemoteObject.exportObject(cc, metadataCallbackPort, clientSocketFactory,
-                    serverSocketFactory);
-        } else {
-            stub = (IAsterixStateProxy) UnicastRemoteObject.exportObject(cc, metadataCallbackPort);
-        }
+        IAsterixStateProxy stub = (IAsterixStateProxy) UnicastRemoteObject.exportObject(cc, metadataCallbackPort,
+                RMIClientFactory.getSocketFactory(networkSecurityManager),
+                RMIServerFactory.getSocketFactory(networkSecurityManager));
         LOGGER.info("Asterix Distributed State Proxy Bound");
         return stub;
     }
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
index b483158..7fc0335 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/main/java/org/apache/hyracks/api/network/INetworkSecurityConfig.java
@@ -20,7 +20,9 @@
 
 import java.io.File;
 import java.io.Serializable;
+import java.net.InetAddress;
 import java.security.KeyStore;
+import java.util.Optional;
 
 public interface INetworkSecurityConfig extends Serializable {
 
@@ -65,4 +67,11 @@
      * @return the trust store file
      */
     File getTrustStoreFile();
+
+    /**
+     * The optional address to bind for RMI server sockets; or absent to bind to all addresses / interfaces.
+     *
+     * @return the optional bind address
+     */
+    Optional<InetAddress> getRMIBindAddress();
 }
diff --git a/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityConfig.java b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityConfig.java
index 2170c15..bfcd623 100644
--- a/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityConfig.java
+++ b/hyracks-fullstack/hyracks/hyracks-ipc/src/main/java/org/apache/hyracks/ipc/security/NetworkSecurityConfig.java
@@ -22,10 +22,12 @@
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
+import java.net.InetAddress;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
 import java.security.cert.CertificateException;
+import java.util.Optional;
 
 import org.apache.hyracks.api.network.INetworkSecurityConfig;
 
@@ -90,6 +92,11 @@
         return trustStoreFile;
     }
 
+    @Override
+    public Optional<InetAddress> getRMIBindAddress() {
+        return Optional.empty();
+    }
+
     private void writeObject(ObjectOutputStream out) throws IOException {
         out.defaultWriteObject();
         writeStore(keyStore, out);