Repository: knox
Updated Branches:
  refs/heads/master fbd84791e -> 20d018ced


KNOX-1187 - Support for Distributed Alias Service

Signed-off-by: Sandeep More <m...@apache.org>


Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/20d018ce
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/20d018ce
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/20d018ce

Branch: refs/heads/master
Commit: 20d018cedf2757bf21bec8f387daee73b0a43ffb
Parents: fbd8479
Author: Sandeep More <m...@apache.org>
Authored: Thu Apr 5 12:39:02 2018 -0400
Committer: Sandeep More <m...@apache.org>
Committed: Wed Apr 11 16:33:18 2018 -0400

----------------------------------------------------------------------
 .../apache/knox/gateway/GatewayMessages.java    |  29 +
 .../gateway/services/CLIGatewayServices.java    |  33 +-
 .../services/DefaultGatewayServices.java        |  37 +-
 .../security/impl/DefaultAliasService.java      |   4 +-
 .../security/impl/RemoteAliasService.java       | 673 +++++++++++++++++++
 .../DefaultRemoteConfigurationMonitor.java      |   1 -
 .../security/impl/RemoteAliasMonitorTest.java   | 256 +++++++
 .../security/impl/RemoteAliasServiceTest.java   | 264 ++++++++
 8 files changed, 1277 insertions(+), 20 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java 
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index b53d3b8..ab21c23 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -615,4 +615,33 @@ public interface GatewayMessages {
                                      final String clusterName,
                                      @StackTrace(level = MessageLevel.DEBUG) 
Exception e);
 
+  @Message(level = MessageLevel.INFO,
+           text = "Adding alias {1} for cluster {0} locally (local keystore) ")
+  void addAliasLocally(final String cluster, final String alias);
+
+  @Message(level = MessageLevel.ERROR,
+           text = "Error adding alias {1} for cluster {0} locally (local 
keystore), cause: {2} ")
+  void errorAddingAliasLocally(final String cluster, final String alias, final 
String cause);
+
+  @Message(level = MessageLevel.INFO,
+           text = "Remove alias {1} for cluster {0} locally (local keystore) ")
+  void removeAliasLocally(final String cluster, final String alias);
+
+  @Message(level = MessageLevel.ERROR,
+           text = "Error removing alias {1} for cluster {0} locally (local 
keystore), cause: {2} ")
+  void errorRemovingAliasLocally(final String cluster, final String alias, 
final String cause);
+
+  @Message(level = MessageLevel.INFO,
+           text = "Adding remote listener for path {0} ")
+  void addRemoteListener(final String path);
+
+  @Message(level = MessageLevel.ERROR,
+           text = "Error adding remote listener for path {0}, cause: {1} ")
+  void errorAddingRemoteListener(final String path, final String cause);
+
+  @Message(level = MessageLevel.ERROR,
+           text = "Error removing remote listener for path {0}, cause: {1} ")
+  void errorRemovingRemoteListener(final String path, final String cause);
+
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/services/CLIGatewayServices.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/CLIGatewayServices.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/CLIGatewayServices.java
index f168d44..b5f8e73 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/CLIGatewayServices.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/CLIGatewayServices.java
@@ -25,6 +25,7 @@ import org.apache.knox.gateway.descriptor.ResourceDescriptor;
 import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 import 
org.apache.knox.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceFactory;
 import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.knox.gateway.services.security.impl.RemoteAliasService;
 import org.apache.knox.gateway.services.topology.impl.DefaultTopologyService;
 import org.apache.knox.gateway.services.security.impl.DefaultAliasService;
 import org.apache.knox.gateway.services.security.impl.DefaultCryptoService;
@@ -50,6 +51,16 @@ public class CLIGatewayServices implements GatewayServices {
   }
 
   public void init(GatewayConfig config, Map<String,String> options) throws 
ServiceLifecycleException {
+
+    /* create an instance so that it can be passed to other services */
+    final RemoteAliasService alias = new RemoteAliasService();
+
+    final RemoteConfigurationRegistryClientService registryClientService =
+        RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
+    registryClientService.setAliasService(alias);
+    registryClientService.init(config, options);
+    services.put(REMOTE_REGISTRY_CLIENT_SERVICE, registryClientService);
+
     ms = new CLIMasterService();
     ms.init(config, options);
     services.put("MasterService", ms);
@@ -59,9 +70,20 @@ public class CLIGatewayServices implements GatewayServices {
     ks.init(config, options);
     services.put(KEYSTORE_SERVICE, ks);
     
-    DefaultAliasService alias = new DefaultAliasService();
-    alias.setKeystoreService(ks);
+    DefaultAliasService defaultAlias = new DefaultAliasService();
+    defaultAlias.setKeystoreService(ks);
+    defaultAlias.init(config, options);
+
+    /*
+     * Setup and initialize remote Alias Service.
+     * NOTE: registryClientService.init() needs to
+     * be called before alias.start();
+     */
+    alias.setLocalAliasService(defaultAlias);
+    alias.setMasterService(ms);
+    alias.setRegistryClientService(registryClientService);
     alias.init(config, options);
+    alias.start();
     services.put(ALIAS_SERVICE, alias);
 
     DefaultCryptoService crypto = new DefaultCryptoService();
@@ -74,11 +96,8 @@ public class CLIGatewayServices implements GatewayServices {
     tops.init(  config, options  );
     services.put(TOPOLOGY_SERVICE, tops);
 
-    RemoteConfigurationRegistryClientService registryClientService =
-                                                    
RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
-    registryClientService.setAliasService(alias);
-    registryClientService.init(config, options);
-    services.put(REMOTE_REGISTRY_CLIENT_SERVICE, registryClientService);
+
+
   }
   
   public void start() throws ServiceLifecycleException {

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
index 7542d75..278c5f9 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/DefaultGatewayServices.java
@@ -27,6 +27,7 @@ import 
org.apache.knox.gateway.service.config.remote.RemoteConfigurationRegistry
 import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
 import 
org.apache.knox.gateway.services.registry.impl.DefaultServiceDefinitionRegistry;
 import org.apache.knox.gateway.services.metrics.impl.DefaultMetricsService;
+import org.apache.knox.gateway.services.security.impl.RemoteAliasService;
 import 
org.apache.knox.gateway.services.topology.impl.DefaultClusterConfigurationMonitorService;
 import org.apache.knox.gateway.services.topology.impl.DefaultTopologyService;
 import org.apache.knox.gateway.services.hostmap.impl.DefaultHostMapperService;
@@ -67,13 +68,34 @@ public class DefaultGatewayServices implements 
GatewayServices {
     ks.setMasterService(ms);
     ks.init(config, options);
     services.put(KEYSTORE_SERVICE, ks);
-    
-    DefaultAliasService alias = new DefaultAliasService();
-    alias.setKeystoreService(ks);
+
+    /* create an instance so that it can be passed to other services */
+    final RemoteAliasService alias = new RemoteAliasService();
+
+    final RemoteConfigurationRegistryClientService registryClientService =
+        RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
+    registryClientService.setAliasService(alias);
+    registryClientService.init(config, options);
+    services.put(REMOTE_REGISTRY_CLIENT_SERVICE, registryClientService);
+
+    final DefaultAliasService defaultAlias = new DefaultAliasService();
+    defaultAlias.setKeystoreService(ks);
+    defaultAlias.setMasterService(ms);
+    defaultAlias.init(config, options);
+
+    /*
+     * Setup and initialize remote Alias Service.
+     * NOTE: registryClientService.init() needs to
+     * be called before alias.start();
+     */
+    alias.setLocalAliasService(defaultAlias);
     alias.setMasterService(ms);
+    alias.setRegistryClientService(registryClientService);
     alias.init(config, options);
+    alias.start();
     services.put(ALIAS_SERVICE, alias);
 
+
     DefaultCryptoService crypto = new DefaultCryptoService();
     crypto.setKeystoreService(ks);
     crypto.setAliasService(alias);
@@ -107,11 +129,6 @@ public class DefaultGatewayServices implements 
GatewayServices {
     sis.init( config, options );
     services.put( SERVER_INFO_SERVICE, sis );
 
-    RemoteConfigurationRegistryClientService registryClientService =
-                                                    
RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
-    registryClientService.setAliasService(alias);
-    registryClientService.init(config, options);
-    services.put(REMOTE_REGISTRY_CLIENT_SERVICE, registryClientService);
 
     DefaultClusterConfigurationMonitorService ccs = new 
DefaultClusterConfigurationMonitorService();
     ccs.setAliasService(alias);
@@ -137,7 +154,7 @@ public class DefaultGatewayServices implements 
GatewayServices {
 
     ks.start();
 
-    DefaultAliasService alias = (DefaultAliasService) 
services.get(ALIAS_SERVICE);
+    Service alias = services.get(ALIAS_SERVICE);
     alias.start();
 
     SSLService ssl = (SSLService) services.get(SSL_SERVICE);
@@ -166,7 +183,7 @@ public class DefaultGatewayServices implements 
GatewayServices {
 
     (services.get(CLUSTER_CONFIGURATION_MONITOR_SERVICE)).stop();
 
-    DefaultAliasService alias = (DefaultAliasService) 
services.get(ALIAS_SERVICE);
+    Service alias = services.get(ALIAS_SERVICE);
     alias.stop();
 
     SSLService ssl = (SSLService) services.get(SSL_SERVICE);

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
index b5e62ab..053d477 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultAliasService.java
@@ -41,7 +41,7 @@ public class DefaultAliasService implements AliasService {
 
   private static final String GATEWAY_IDENTITY_PASSPHRASE = 
"gateway-identity-passphrase";
 
-  protected char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
+  protected static char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
   'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
   'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
   'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
@@ -103,7 +103,7 @@ public class DefaultAliasService implements AliasService {
     return credential;
   }
 
-  private String generatePassword(int length) {
+  protected static String generatePassword(int length) {
     StringBuilder sb = new StringBuilder();
     SecureRandom r = new SecureRandom();
     for (int i = 0; i < length; i++) {

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
new file mode 100644
index 0000000..2e287e0
--- /dev/null
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/RemoteAliasService.java
@@ -0,0 +1,673 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.services.security.impl;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.knox.gateway.GatewayMessages;
+import org.apache.knox.gateway.GatewayServer;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.GatewayServices;
+import org.apache.knox.gateway.services.ServiceLifecycleException;
+import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClient;
+import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
+import org.apache.knox.gateway.services.security.EncryptionResult;
+import org.apache.knox.gateway.services.security.MasterService;
+import org.apache.zookeeper.ZooDefs;
+
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@link AliasService} implementation based on
+ * remote service registry.
+ * <p>
+ * This class encapsulates the default AliasService implementation which uses
+ * local keystore to store the aliases. The order in which Aliases are stored 
are
+ * <ul>
+ * <li>Local Keystore</li>
+ * <li>Remote Registry</li>
+ * </ul>
+ *
+ * @since 1.1.0
+ */
+public class RemoteAliasService implements AliasService {
+
+  public static final String PATH_KNOX = "/knox";
+  public static final String PATH_KNOX_SECURITY = PATH_KNOX + "/security";
+  public static final String PATH_KNOX_ALIAS_STORE_TOPOLOGY =
+      PATH_KNOX_SECURITY + "/topology";
+  public static final String PATH_SEPARATOR = "/";
+  public static final String DEFAULT_CLUSTER_NAME = "__gateway";
+  public static final String GATEWAY_IDENTITY_PASSPHRASE = 
"gateway-identity-passphrase";
+
+  private static final GatewayMessages LOG = MessagesFactory
+      .get(GatewayMessages.class);
+  // N.B. This is ZooKeeper-specific, and should be abstracted when another 
registry is supported
+  private static final RemoteConfigurationRegistryClient.EntryACL 
AUTHENTICATED_USERS_ALL;
+
+  static {
+    AUTHENTICATED_USERS_ALL = new RemoteConfigurationRegistryClient.EntryACL() 
{
+      public String getId() {
+        return "";
+      }
+
+      public String getType() {
+        return "auth";
+      }
+
+      public Object getPermissions() {
+        return ZooDefs.Perms.ALL;
+      }
+
+      public boolean canRead() {
+        return true;
+      }
+
+      public boolean canWrite() {
+        return true;
+      }
+    };
+  }
+
+  private RemoteConfigurationRegistryClient remoteClient;
+  private ConfigurableEncryptor encryptor;
+  /**
+   * Default alias service
+   */
+  private AliasService localAliasService;
+  private RemoteConfigurationRegistryClientService registryClientService;
+  private MasterService ms;
+  private GatewayConfig config;
+  private Map<String, String> options;
+
+  /* create an instance */
+  public RemoteAliasService() {
+    super();
+  }
+
+  /**
+   * Build an entry path for the given cluster and alias
+   *
+   * @param clusterName
+   * @param alias
+   * @return
+   */
+  private static String buildAliasEntryName(final String clusterName,
+      final String alias) {
+    return buildClusterEntryName(clusterName) + PATH_SEPARATOR + alias;
+  }
+
+  /**
+   * Build an entry path for the given cluster
+   *
+   * @param clusterName
+   * @return
+   */
+  private static String buildClusterEntryName(final String clusterName) {
+    return PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + clusterName;
+  }
+
+  /**
+   * Ensure that the given entry path exists.
+   *
+   * @param path
+   * @param remoteClient
+   */
+  private static void ensureEntry(final String path,
+      final RemoteConfigurationRegistryClient remoteClient) {
+    if (!remoteClient.entryExists(path)) {
+      remoteClient.createEntry(path);
+    } else {
+      // Validate the ACL
+      List<RemoteConfigurationRegistryClient.EntryACL> entryACLs = remoteClient
+          .getACL(path);
+      for (RemoteConfigurationRegistryClient.EntryACL entryACL : entryACLs) {
+        // N.B. This is ZooKeeper-specific, and should be abstracted when 
another registry is supported
+        // For now, check for world:anyone with ANY permissions (even 
read-only)
+        if (entryACL.getType().equals("world") && entryACL.getId()
+            .equals("anyone")) {
+          LOG.suspectWritableRemoteConfigurationEntry(path);
+
+          // If the client is authenticated, but "anyone" can write the 
content, then the content may not
+          // be trustworthy.
+          if (remoteClient.isAuthenticationConfigured()) {
+            LOG.correctingSuspectWritableRemoteConfigurationEntry(path);
+
+            // Replace the existing ACL with one that permits only 
authenticated users
+            remoteClient.setACL(path,
+                Collections.singletonList(AUTHENTICATED_USERS_ALL));
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Check to make sure all the required entries are properly set up
+   *
+   * @param remoteClient
+   */
+  private static void checkPathsExist(
+      final RemoteConfigurationRegistryClient remoteClient) {
+    ensureEntry(PATH_KNOX, remoteClient);
+    ensureEntry(PATH_KNOX_SECURITY, remoteClient);
+    ensureEntry(PATH_KNOX_ALIAS_STORE_TOPOLOGY, remoteClient);
+    ensureEntry(
+        PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + DEFAULT_CLUSTER_NAME,
+        remoteClient);
+
+  }
+
+  /**
+   * Returns an empty list if the given list is null,
+   * else returns the given list.
+   *
+   * @param given
+   * @return
+   */
+  private static List<String> safe(final List given) {
+    return given == null ? Collections.EMPTY_LIST : given;
+  }
+
+  /**
+   * Set a {@link RemoteConfigurationRegistryClientService} instance
+   * used to talk to remote remote service registry.
+   *
+   * @param registryClientService
+   */
+  public void setRegistryClientService(
+      final RemoteConfigurationRegistryClientService registryClientService) {
+    this.registryClientService = registryClientService;
+  }
+
+  /**
+   * Set a {@link MasterService} instance.
+   *
+   * @param ms
+   */
+  public void setMasterService(final MasterService ms) {
+    this.ms = ms;
+  }
+
+  /**
+   * Set local alias service
+   *
+   * @param localAliasService
+   */
+  public void setLocalAliasService(AliasService localAliasService) {
+    this.localAliasService = localAliasService;
+  }
+
+  /**
+   * Get a list of all aliases for a given cluster.
+   * Remote aliases are preferred over local.
+   *
+   * @param clusterName cluster name
+   * @return List of all the aliases
+   * @throws AliasServiceException
+   */
+  @Override
+  public List<String> getAliasesForCluster(final String clusterName)
+      throws AliasServiceException {
+
+    List<String> remoteAliases = new ArrayList<>();
+
+    /* If we have remote registry configured, query it */
+    if (remoteClient != null) {
+      remoteAliases = remoteClient
+          .listChildEntries(buildClusterEntryName(clusterName));
+    }
+
+    List<String> localAliases = localAliasService
+        .getAliasesForCluster(clusterName);
+
+    /* merge */
+    for (final String alias : safe(localAliases)) {
+      if (!remoteAliases.contains(alias.toLowerCase())) {
+        remoteAliases.add(alias);
+      }
+    }
+
+    return remoteAliases;
+  }
+
+  @Override
+  public void addAliasForCluster(final String clusterName,
+      final String givenAlias, final String value)
+      throws AliasServiceException {
+
+    /* convert all alias names to lower case since JDK expects the same 
behaviour */
+    final String alias = givenAlias.toLowerCase();
+
+    /* first add the alias to the local keystore */
+    localAliasService.addAliasForCluster(clusterName, alias, value);
+
+    if (remoteClient != null) {
+
+      final String aliasEntryPath = buildAliasEntryName(clusterName, alias);
+
+      /* Ensure the entries are properly set up */
+      checkPathsExist(remoteClient);
+      ensureEntry(buildClusterEntryName(clusterName), remoteClient);
+      try {
+
+        remoteClient.createEntry(aliasEntryPath, encrypt(value));
+
+      } catch (Exception e) {
+        throw new AliasServiceException(e);
+      }
+
+      if (remoteClient.getEntryData(aliasEntryPath) == null) {
+        throw new IllegalStateException(String.format(
+            "Failed to store alias %s for cluster %s in remote registry", 
alias,
+            clusterName));
+      }
+
+    }
+  }
+
+  @Override
+  public void removeAliasForCluster(final String clusterName,
+      final String givenAlias) throws AliasServiceException {
+
+    /* convert all alias names to lower case since JDK expects the same 
behaviour */
+    final String alias = givenAlias.toLowerCase();
+
+    /* first remove it from the local keystore */
+    localAliasService.removeAliasForCluster(clusterName, alias);
+
+    /* If we have remote registry configured, query it */
+    if (remoteClient != null) {
+
+      final String aliasEntryPath = buildAliasEntryName(clusterName, alias);
+
+      remoteClient.deleteEntry(aliasEntryPath);
+
+      if (remoteClient.entryExists(aliasEntryPath)) {
+        throw new IllegalStateException(String.format(
+            "Failed to delete alias %s for cluster %s in remote registry",
+            alias, clusterName));
+      }
+
+    } else {
+      LOG.missingClientConfigurationForRemoteMonitoring();
+    }
+
+  }
+
+  @Override
+  public char[] getPasswordFromAliasForCluster(String clusterName, String 
alias)
+      throws AliasServiceException {
+    return getPasswordFromAliasForCluster(clusterName, alias, false);
+  }
+
+  @Override
+  public char[] getPasswordFromAliasForCluster(String clusterName,
+      String givenAlias, boolean generate) throws AliasServiceException {
+
+    /* convert all alias names to lower case since JDK expects the same 
behaviour */
+    final String alias = givenAlias.toLowerCase();
+
+    char[] password;
+    /* try to get it from the local keystore, ignore generate flag. */
+    password = localAliasService
+        .getPasswordFromAliasForCluster(clusterName, alias);
+    if (password != null) {
+      return password;
+    }
+
+    /* try to get it from remote registry */
+    if (remoteClient != null) {
+
+      checkPathsExist(remoteClient);
+      final String encrypted = remoteClient
+          .getEntryData(buildAliasEntryName(clusterName, alias));
+
+      /* Generate a new password */
+      if (encrypted == null) {
+
+        /* Generate a new password  */
+        if (generate) {
+          generateAliasForCluster(clusterName, alias);
+          password = getPasswordFromAliasForCluster(clusterName, alias);
+        }
+
+      } else {
+        try {
+          return decrypt(encrypted).toCharArray();
+        } catch (final Exception e) {
+          throw new AliasServiceException(e);
+        }
+      }
+
+    }
+
+    /* Case where remote registry is not configured and we need to generate 
password and save it locally */
+    else if (generate) {
+      return localAliasService
+          .getPasswordFromAliasForCluster(clusterName, alias, generate);
+    }
+
+    /* found nothing */
+    return password;
+  }
+
+  @Override
+  public void generateAliasForCluster(final String clusterName,
+      final String givenAlias) throws AliasServiceException {
+
+    /* convert all alias names to lower case since JDK expects the same 
behaviour */
+    final String alias = givenAlias.toLowerCase();
+    /* auto-generated password */
+    final String passwordString = DefaultAliasService.generatePassword(16);
+    addAliasForCluster(clusterName, alias, passwordString);
+  }
+
+  @Override
+  public char[] getPasswordFromAliasForGateway(String alias)
+      throws AliasServiceException {
+    return getPasswordFromAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+  }
+
+  @Override
+  public char[] getGatewayIdentityPassphrase() throws AliasServiceException {
+    char[] passphrase = getPasswordFromAliasForGateway(
+        GATEWAY_IDENTITY_PASSPHRASE);
+    if (passphrase == null) {
+      passphrase = ms.getMasterSecret();
+    }
+    return passphrase;
+  }
+
+  @Override
+  public void generateAliasForGateway(final String alias)
+      throws AliasServiceException {
+    generateAliasForCluster(DEFAULT_CLUSTER_NAME, alias);
+  }
+
+  @Override
+  public Certificate getCertificateForGateway(final String alias)
+      throws AliasServiceException {
+    /* We don't store certs in remote registry so we just delegate certs to 
keystore (DefaultAliasService.getCertificateForGateway) */
+    return localAliasService.getCertificateForGateway(alias);
+  }
+
+  @Override
+  public void init(final GatewayConfig config,
+      final Map<String, String> options) throws ServiceLifecycleException {
+    this.config = config;
+    this.options = options;
+
+    /* setup and initialize encryptor for encryption and decryption of 
passwords */
+    encryptor = new ConfigurableEncryptor(new String(ms.getMasterSecret()));
+    encryptor.init(config);
+
+    /* If we have remote registry configured, query it */
+    final String clientName = config.getRemoteConfigurationMonitorClientName();
+    if (clientName != null) {
+
+      if (registryClientService != null) {
+
+        remoteClient = registryClientService.get(clientName);
+
+      } else {
+        throw new ServiceLifecycleException(
+            "Remote configuration registry not initialized");
+      }
+
+    } else {
+      LOG.missingClientConfigurationForRemoteMonitoring();
+    }
+
+  }
+
+  @Override
+  public void start() throws ServiceLifecycleException {
+
+    if (remoteClient != null) {
+
+      /* ensure that nodes are properly setup */
+      ensureEntries(remoteClient);
+
+      /* Confirm access to the remote aliases directory */
+      final List<String> aliases = remoteClient
+          .listChildEntries(PATH_KNOX_ALIAS_STORE_TOPOLOGY);
+      if (aliases == null) {
+        // Either the entry does not exist, or there is an authentication 
problem
+        throw new IllegalStateException(
+            "Unable to access remote path: " + PATH_KNOX_ALIAS_STORE_TOPOLOGY);
+      }
+
+      /* Register a listener for aliases entry additions/removals */
+      try {
+        remoteClient.addChildEntryListener(PATH_KNOX_ALIAS_STORE_TOPOLOGY,
+            new RemoteAliasChildListener());
+      } catch (final Exception e) {
+        throw new IllegalStateException(
+            "Unable to add listener for path " + 
PATH_KNOX_ALIAS_STORE_TOPOLOGY,
+            e);
+      }
+
+    }
+
+  }
+
+  @Override
+  public void stop() throws ServiceLifecycleException {
+    try {
+      remoteClient.removeEntryListener(PATH_KNOX_ALIAS_STORE_TOPOLOGY);
+    } catch (final Exception e) {
+      LOG.errorRemovingRemoteListener(PATH_KNOX_ALIAS_STORE_TOPOLOGY,
+          e.toString());
+    }
+  }
+
+  /**
+   * Add the alias to the local keystore.
+   * Most likely this will be called by remote registry watch listener.
+   *
+   * @param clusterName Name of the cluster
+   * @param alias       Alias name to be added
+   * @param value       alias value to be added
+   * @throws AliasServiceException
+   */
+  public void addAliasForClusterLocally(final String clusterName,
+      final String alias, final String value) throws AliasServiceException {
+    localAliasService.addAliasForCluster(clusterName, alias, value);
+  }
+
+  /**
+   * Remove the given alias from local keystore.
+   * Most likely this will be called by remote registry watch listener.
+   *
+   * @param clusterName Name of the cluster
+   * @param alias       Alias name to be removed
+   * @throws AliasServiceException
+   */
+  public void removeAliasForClusterLocally(final String clusterName,
+      final String alias) throws AliasServiceException {
+    LOG.removeAliasLocally(clusterName, alias);
+    localAliasService.removeAliasForCluster(clusterName, alias);
+  }
+
+  /**
+   * Ensure that the nodes are properly set up.
+   *
+   * @param remoteClient
+   */
+  private void ensureEntries(
+      final RemoteConfigurationRegistryClient remoteClient) {
+    ensureEntry(PATH_KNOX, remoteClient);
+    ensureEntry(PATH_KNOX_SECURITY, remoteClient);
+    ensureEntry(PATH_KNOX_ALIAS_STORE_TOPOLOGY, remoteClient);
+    ensureEntry(
+        PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR + DEFAULT_CLUSTER_NAME,
+        remoteClient);
+  }
+
+  /**
+   * Encrypt the clear text with master password.
+   * @param clear clear text to be encrypted
+   * @return encrypted and base 64 encoded result.
+   * @throws Exception
+   */
+  public String encrypt(final String clear) throws Exception {
+
+    final EncryptionResult result = encryptor.encrypt(clear);
+
+    return Base64.encodeBase64String(
+        (Base64.encodeBase64String(result.salt) + "::" + Base64
+            .encodeBase64String(result.iv) + "::" + Base64
+            .encodeBase64String(result.cipher)).getBytes("UTF8"));
+
+  }
+
+  /**
+   * Function to decrypt the encrypted text using master secret.
+   *
+   * @param encoded encoded and encrypted string.
+   * @return decrypted password.
+   */
+  public String decrypt(final String encoded) throws Exception {
+
+    final String line = new String(Base64.decodeBase64(encoded));
+    final String[] parts = line.split("::");
+
+    return new String(encryptor
+        .decrypt(Base64.decodeBase64(parts[0]), Base64.decodeBase64(parts[1]),
+            Base64.decodeBase64(parts[2])), "UTF8");
+  }
+
+  /**
+   * A listener that listens for changes to the child nodes.
+   */
+  private static class RemoteAliasChildListener
+      implements RemoteConfigurationRegistryClient.ChildEntryListener {
+
+    @Override
+    public void childEvent(final RemoteConfigurationRegistryClient client,
+        final Type type, final String path) {
+
+      final String subPath = StringUtils.substringAfter(path,
+          PATH_KNOX_ALIAS_STORE_TOPOLOGY + PATH_SEPARATOR);
+      final String[] paths = StringUtils.split(subPath, '/');
+
+      switch (type) {
+      case REMOVED:
+        try {
+          /* remove listener */
+          client.removeEntryListener(path);
+
+          if (GatewayServer.getGatewayServices() != null) {
+            /* remove the alias from local keystore */
+            final AliasService aliasService = 
GatewayServer.getGatewayServices()
+                .getService(GatewayServices.ALIAS_SERVICE);
+            if (aliasService != null && paths.length > 1
+                && aliasService instanceof RemoteAliasService) {
+              ((RemoteAliasService) aliasService)
+                  .removeAliasForClusterLocally(paths[0], paths[1]);
+            }
+          }
+
+        } catch (final Exception e) {
+          LOG.errorRemovingAliasLocally(paths[0], paths[1], e.toString());
+        }
+        break;
+
+      case ADDED:
+        /* do not set listeners on cluster name but on respective aliases */
+        if (paths.length > 1) {
+          LOG.addAliasLocally(paths[0], paths[1]);
+          try {
+            client.addEntryListener(path,
+                new RemoteAliasEntryListener(paths[0], paths[1]));
+          } catch (final Exception e) {
+            LOG.errorRemovingAliasLocally(paths[0], paths[1], e.toString());
+          }
+
+        } else if (subPath != null) {
+          /* Add a child listener for the cluster */
+          LOG.addRemoteListener(path);
+          try {
+            client.addChildEntryListener(path, new RemoteAliasChildListener());
+          } catch (Exception e) {
+            LOG.errorAddingRemoteListener(path, e.toString());
+          }
+
+        }
+
+        break;
+      }
+
+    }
+  }
+
+  /**
+   * A listener that listens for changes to node value.
+   */
+  private static class RemoteAliasEntryListener
+      implements RemoteConfigurationRegistryClient.EntryListener {
+
+    final String cluster;
+    final String alias;
+
+    /**
+     * Create an instance
+     *
+     * @param cluster
+     * @param alias
+     */
+    public RemoteAliasEntryListener(final String cluster, final String alias) {
+      super();
+      this.cluster = cluster;
+      this.alias = alias;
+    }
+
+    @Override
+    public void entryChanged(final RemoteConfigurationRegistryClient client,
+        final String path, final byte[] data) {
+
+      if (GatewayServer.getGatewayServices() != null) {
+        final AliasService aliasService = GatewayServer.getGatewayServices()
+            .getService(GatewayServices.ALIAS_SERVICE);
+
+        if (aliasService != null
+            && aliasService instanceof RemoteAliasService) {
+          try {
+            ((RemoteAliasService) aliasService)
+                .addAliasForClusterLocally(cluster, alias, new String(data));
+          } catch (final AliasServiceException e) {
+            /* log and move on */
+            LOG.errorAddingAliasLocally(cluster, alias, e.toString());
+          }
+
+        }
+
+      }
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
index 6db3952..3c424d1 100644
--- 
a/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
+++ 
b/gateway-server/src/main/java/org/apache/knox/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
@@ -28,7 +28,6 @@ import org.apache.zookeeper.ZooDefs;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasMonitorTest.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasMonitorTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasMonitorTest.java
new file mode 100644
index 0000000..b6a4ab9
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasMonitorTest.java
@@ -0,0 +1,256 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.security.impl;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.ExponentialBackoffRetry;
+import org.apache.curator.test.InstanceSpec;
+import org.apache.curator.test.TestingCluster;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
+import 
org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
+import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.knox.gateway.services.security.impl.DefaultAliasService;
+import org.apache.knox.gateway.services.security.impl.DefaultMasterService;
+import org.apache.knox.gateway.services.security.impl.RemoteAliasService;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.testng.Assert;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.easymock.EasyMock.capture;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Test the listener/monitor service for
+ * remote alias service.
+ */
+public class RemoteAliasMonitorTest {
+
+  private static TestingCluster zkNodes;
+
+  private static CuratorFramework client;
+  private static String configMonitorName = "remoteConfigMonitorClient";
+  private static String expectedClusterName = "sandbox";
+  private static String expectedAlias = "knox.test.alias";
+  private static String expectedPassword = "dummyPassword";
+  private static String expectedClusterNameDev = "development";
+  private static String expectedAliasDev = "knox.test.alias.dev";
+  private static String expectedPasswordDev = "otherDummyPassword";
+  /* For CLI tests */
+  private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+  private final ByteArrayOutputStream errContent = new ByteArrayOutputStream();
+  private GatewayConfig gc;
+
+  public RemoteAliasMonitorTest() {
+    super();
+    System.setOut(new PrintStream(outContent));
+    System.setErr(new PrintStream(errContent));
+  }
+
+  @BeforeClass
+  public static void setupSuite() throws Exception {
+    // Configure security for the ZK cluster instances
+    Map<String, Object> customInstanceSpecProps = new HashMap<>();
+    customInstanceSpecProps.put("authProvider.1",
+        "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
+    customInstanceSpecProps.put("requireClientAuthScheme", "sasl");
+
+    // Define the test cluster
+    List<InstanceSpec> instanceSpecs = new ArrayList<>();
+    for (int i = 0; i < 3; i++) {
+      InstanceSpec is = new InstanceSpec(null, -1, -1, -1, false, (i + 1), -1,
+          -1, customInstanceSpecProps);
+      instanceSpecs.add(is);
+    }
+    zkNodes = new TestingCluster(instanceSpecs);
+
+    zkNodes.start();
+
+    // Create the client for the test cluster
+    client = CuratorFrameworkFactory.builder()
+        .connectString(zkNodes.getConnectString())
+        .retryPolicy(new ExponentialBackoffRetry(100, 3)).build();
+    assertNotNull(client);
+    client.start();
+
+    // Create the knox config paths with an ACL for the sasl user configured 
for the client
+    List<ACL> acls = new ArrayList<>();
+    acls.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE));
+
+    client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
+        .withACL(acls).forPath(
+        RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + RemoteAliasService.
+            PATH_SEPARATOR + expectedClusterName);
+
+    assertNotNull("Failed to create node:"
+        + RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+        + RemoteAliasService.
+        PATH_SEPARATOR + expectedClusterName, client.checkExists().forPath(
+        RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + RemoteAliasService.
+            PATH_SEPARATOR + expectedClusterName));
+
+    client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
+        .withACL(acls).forPath(
+        RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + RemoteAliasService.
+            PATH_SEPARATOR + expectedClusterNameDev);
+    assertNotNull("Failed to create node:"
+        + RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+        + RemoteAliasService.
+        PATH_SEPARATOR + expectedClusterNameDev, client.checkExists().forPath(
+        RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + RemoteAliasService.
+            PATH_SEPARATOR + expectedClusterNameDev));
+  }
+
+  @AfterClass
+  public static void tearDownSuite() throws Exception {
+    // Clean up the ZK nodes, and close the client
+    if (client != null) {
+      client.delete().deletingChildrenIfNeeded()
+          .forPath(RemoteAliasService.PATH_KNOX_SECURITY);
+      client.close();
+    }
+
+    // Shutdown the ZK cluster
+    zkNodes.close();
+  }
+
+  @Test
+  public void testListener() throws Exception {
+
+    // Setup the base GatewayConfig mock
+    gc = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
+        .andReturn(Collections.singletonList(configMonitorName)).anyTimes();
+
+    final String registryConfig =
+        GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "="
+            + ZooKeeperClientService.TYPE + ";"
+            + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkNodes
+            .getConnectString();
+
+    EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
+        .andReturn(registryConfig).anyTimes();
+
+    EasyMock.expect(gc.getRemoteConfigurationMonitorClientName())
+        .andReturn(configMonitorName).anyTimes();
+    EasyMock.replay(gc);
+
+    // Mock Alias Service
+    final DefaultAliasService defaultAlias = EasyMock
+        .createNiceMock(DefaultAliasService.class);
+    // Captures for validating the alias creation for a generated topology
+    final Capture<String> capturedCluster = EasyMock.newCapture();
+    final Capture<String> capturedAlias = EasyMock.newCapture();
+    final Capture<String> capturedPwd = EasyMock.newCapture();
+
+    defaultAlias
+        .addAliasForCluster(capture(capturedCluster), capture(capturedAlias),
+            capture(capturedPwd));
+    EasyMock.expectLastCall().anyTimes();
+
+    /* defaultAlias.getAliasesForCluster() never returns null */
+    EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterName))
+        .andReturn(new ArrayList<>()).anyTimes();
+    EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterNameDev))
+        .andReturn(new ArrayList<>()).anyTimes();
+
+    EasyMock.replay(defaultAlias);
+
+    final DefaultMasterService ms = EasyMock
+        .createNiceMock(DefaultMasterService.class);
+    EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
+        .anyTimes();
+    EasyMock.replay(ms);
+
+    final RemoteAliasService zkAlias = new RemoteAliasService();
+
+    final RemoteConfigurationRegistryClientService clientService = (new 
ZooKeeperClientServiceProvider())
+        .newInstance();
+    clientService.setAliasService(zkAlias);
+    clientService.init(gc, Collections.emptyMap());
+
+    /* init */
+    zkAlias.setLocalAliasService(defaultAlias);
+    zkAlias.setRegistryClientService(clientService);
+    zkAlias.setMasterService(ms);
+    zkAlias.init(gc, Collections.emptyMap());
+    zkAlias.start();
+
+    /* GET Aliases */
+    List<String> aliases = zkAlias.getAliasesForCluster(expectedClusterName);
+    List<String> aliasesDev = zkAlias
+        .getAliasesForCluster(expectedClusterNameDev);
+
+    /* no alias added so ist should be empty */
+    Assert.assertEquals(aliases.size(), 0);
+    Assert.assertEquals(aliasesDev.size(), 0);
+
+
+    /* Create an alias in Zookeeper externally */
+    client.create().withMode(CreateMode.PERSISTENT).
+        forPath(RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+                + RemoteAliasService.
+                PATH_SEPARATOR + expectedClusterName
+                + RemoteAliasService.PATH_SEPARATOR + expectedAlias,
+            zkAlias.encrypt(expectedPassword).getBytes());
+
+    /* Create an alias in Zookeeper externally */
+    client.create().withMode(CreateMode.PERSISTENT).
+        forPath(RemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+                + RemoteAliasService.
+                PATH_SEPARATOR + expectedClusterNameDev
+                + RemoteAliasService.PATH_SEPARATOR + expectedAliasDev,
+            zkAlias.encrypt(expectedPasswordDev).getBytes());
+
+    /* Try */
+    aliases = zkAlias.getAliasesForCluster(expectedClusterName);
+    aliasesDev = zkAlias.getAliasesForCluster(expectedClusterNameDev);
+
+    Assert.assertTrue(aliases.contains(expectedAlias),
+        "Expected alias 'knox.test.alias' not found ");
+    Assert.assertTrue(aliasesDev.contains(expectedAliasDev),
+        "Expected alias 'knox.test.alias.dev' not found ");
+
+    final char[] result = zkAlias
+        .getPasswordFromAliasForCluster(expectedClusterName, expectedAlias);
+    final char[] result1 = zkAlias
+        .getPasswordFromAliasForCluster(expectedClusterNameDev,
+            expectedAliasDev);
+
+    /* make sure the externally added passwords match */
+    Assert.assertEquals(expectedPassword, new String(result));
+    Assert.assertEquals(expectedPasswordDev, new String(result1));
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/20d018ce/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasServiceTest.java
----------------------------------------------------------------------
diff --git 
a/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasServiceTest.java
 
b/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasServiceTest.java
new file mode 100644
index 0000000..a3c9a81
--- /dev/null
+++ 
b/gateway-server/src/test/java/org/apache/knox/gateway/security/impl/RemoteAliasServiceTest.java
@@ -0,0 +1,264 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.knox.gateway.security.impl;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.test.InstanceSpec;
+import org.apache.curator.test.TestingCluster;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
+import 
org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
+import 
org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.impl.DefaultAliasService;
+import org.apache.knox.gateway.services.security.impl.DefaultMasterService;
+import org.apache.knox.gateway.services.security.impl.RemoteAliasService;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.testng.Assert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.easymock.EasyMock.capture;
+
+/**
+ * Test for {@link RemoteAliasService} backed by Zookeeper.
+ */
+public class RemoteAliasServiceTest {
+
+  private static TestingCluster zkNodes;
+
+  private static CuratorFramework client;
+
+  private static String configMonitorName = "remoteConfigMonitorClient";
+
+  private static GatewayConfig gc;
+
+  public RemoteAliasServiceTest() {
+    super();
+  }
+
+  @BeforeClass
+  public static void setupSuite() throws Exception {
+
+    configureAndStartZKCluster();
+
+    // Setup the base GatewayConfig mock
+    gc = EasyMock.createNiceMock(GatewayConfig.class);
+    EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
+        .andReturn(Collections.singletonList(configMonitorName)).anyTimes();
+
+    final String registryConfig =
+        GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "="
+            + ZooKeeperClientService.TYPE + ";"
+            + GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkNodes
+            .getConnectString();
+
+    EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
+        .andReturn(registryConfig).anyTimes();
+
+    EasyMock.expect(gc.getRemoteConfigurationMonitorClientName())
+        .andReturn(configMonitorName).anyTimes();
+
+    EasyMock.expect(gc.getAlgorithm()).andReturn("AES").anyTimes();
+
+    EasyMock.replay(gc);
+
+  }
+
+  private static void configureAndStartZKCluster() throws Exception {
+    // Configure security for the ZK cluster instances
+    Map<String, Object> customInstanceSpecProps = new HashMap<>();
+    customInstanceSpecProps.put("authProvider.1",
+        "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
+    customInstanceSpecProps.put("requireClientAuthScheme", "sasl");
+
+    // Define the test cluster
+    List<InstanceSpec> instanceSpecs = new ArrayList<>();
+    for (int i = 0; i < 3; i++) {
+      InstanceSpec is = new InstanceSpec(null, -1, -1, -1, false, (i + 1), -1,
+          -1, customInstanceSpecProps);
+      instanceSpecs.add(is);
+    }
+    zkNodes = new TestingCluster(instanceSpecs);
+
+    // Start the cluster
+    zkNodes.start();
+
+  }
+
+  @AfterClass
+  public static void tearDownSuite() throws Exception {
+    // Clean up the ZK nodes, and close the client
+    if (client != null) {
+      client.delete().deletingChildrenIfNeeded()
+          .forPath(RemoteAliasService.PATH_KNOX_SECURITY);
+      client.close();
+    }
+
+    // Shutdown the ZK cluster
+    zkNodes.close();
+  }
+
+  @Test
+  public void testAliasForCluster() throws Exception {
+    final String expectedClusterName = "sandbox";
+    final String expectedAlias = "knox.test.alias";
+    final String expectedPassword = "dummyPassword";
+
+    final String expectedClusterNameDev = "development";
+    final String expectedAliasDev = "knox.test.alias.dev";
+    final String expectedPasswordDev = "otherDummyPassword";
+
+    // Mock Alias Service
+    final DefaultAliasService defaultAlias = EasyMock
+        .createNiceMock(DefaultAliasService.class);
+    // Captures for validating the alias creation for a generated topology
+    final Capture<String> capturedCluster = EasyMock.newCapture();
+    final Capture<String> capturedAlias = EasyMock.newCapture();
+    final Capture<String> capturedPwd = EasyMock.newCapture();
+
+    defaultAlias
+        .addAliasForCluster(capture(capturedCluster), capture(capturedAlias),
+            capture(capturedPwd));
+    EasyMock.expectLastCall().anyTimes();
+
+    /* defaultAlias.getAliasesForCluster() never returns null */
+    EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterName))
+        .andReturn(new ArrayList<>()).anyTimes();
+    EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterNameDev))
+        .andReturn(new ArrayList<>()).anyTimes();
+
+    EasyMock.replay(defaultAlias);
+
+    final DefaultMasterService ms = EasyMock
+        .createNiceMock(DefaultMasterService.class);
+    EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
+        .anyTimes();
+    EasyMock.replay(ms);
+
+    final RemoteAliasService zkAlias = new RemoteAliasService();
+
+    RemoteConfigurationRegistryClientService clientService = (new 
ZooKeeperClientServiceProvider())
+        .newInstance();
+    clientService.setAliasService(zkAlias);
+    clientService.init(gc, Collections.emptyMap());
+
+    /* init */
+    zkAlias.setLocalAliasService(defaultAlias);
+    zkAlias.setRegistryClientService(clientService);
+    zkAlias.setMasterService(ms);
+    zkAlias.init(gc, Collections.emptyMap());
+    zkAlias.start();
+
+
+    /* Put */
+    zkAlias.addAliasForCluster(expectedClusterName, expectedAlias,
+        expectedPassword);
+    zkAlias.addAliasForCluster(expectedClusterNameDev, expectedAliasDev,
+        expectedPasswordDev);
+
+    /* GET all Aliases */
+    List<String> aliases = zkAlias.getAliasesForCluster(expectedClusterName);
+    List<String> aliasesDev = zkAlias
+        .getAliasesForCluster(expectedClusterNameDev);
+
+    Assert.assertEquals(aliases.size(), 1);
+    Assert.assertEquals(aliasesDev.size(), 1);
+
+    Assert.assertTrue(aliases.contains(expectedAlias),
+        "Expected alias 'knox.test.alias' not found ");
+    Assert.assertTrue(aliasesDev.contains(expectedAliasDev),
+        "Expected alias 'knox.test.alias.dev' not found ");
+
+    final char[] result = zkAlias
+        .getPasswordFromAliasForCluster(expectedClusterName, expectedAlias);
+    final char[] result1 = zkAlias
+        .getPasswordFromAliasForCluster(expectedClusterNameDev,
+            expectedAliasDev);
+
+    Assert.assertEquals(expectedPassword, new String(result));
+    Assert.assertEquals(expectedPasswordDev, new String(result1));
+
+    /* Remove */
+    zkAlias.removeAliasForCluster(expectedClusterNameDev, expectedAliasDev);
+
+    /* Make sure expectedAliasDev is removed*/
+    aliases = zkAlias.getAliasesForCluster(expectedClusterName);
+    aliasesDev = zkAlias.getAliasesForCluster(expectedClusterNameDev);
+
+    Assert.assertEquals(aliasesDev.size(), 0);
+    Assert.assertFalse(aliasesDev.contains(expectedAliasDev),
+        "Expected alias 'knox.test.alias.dev' to be removed but found.");
+
+    Assert.assertEquals(aliases.size(), 1);
+    Assert.assertTrue(aliases.contains(expectedAlias),
+        "Expected alias 'knox.test.alias' not found ");
+
+    /* Test auto-generate password for alias */
+    final String testAutoGeneratedpasswordAlias = "knox.test.alias.auto";
+
+    final char[] autoGeneratedPassword = zkAlias
+        .getPasswordFromAliasForCluster(expectedClusterName,
+            testAutoGeneratedpasswordAlias, true);
+    aliases = zkAlias.getAliasesForCluster(expectedClusterName);
+
+    Assert.assertNotNull(autoGeneratedPassword);
+    Assert.assertEquals(aliases.size(), 2);
+    Assert.assertTrue(aliases.contains(testAutoGeneratedpasswordAlias),
+        "Expected alias 'knox.test.alias' not found ");
+
+  }
+
+  @Test
+  public void testEncryptDecrypt() throws Exception {
+
+    final String testPassword = "ApacheKnoxPassword123";
+
+    final AliasService defaultAlias = EasyMock
+        .createNiceMock(AliasService.class);
+    EasyMock.replay(defaultAlias);
+
+    final DefaultMasterService ms = EasyMock
+        .createNiceMock(DefaultMasterService.class);
+    EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
+        .anyTimes();
+    EasyMock.replay(ms);
+
+    final RemoteAliasService zkAlias = new RemoteAliasService();
+    zkAlias.setLocalAliasService(defaultAlias);
+    zkAlias.setRegistryClientService(
+        (new ZooKeeperClientServiceProvider()).newInstance());
+    zkAlias.setMasterService(ms);
+    zkAlias.init(gc, Collections.emptyMap());
+
+    final String encrypted = zkAlias.encrypt(testPassword);
+    Assert.assertNotNull(encrypted);
+    final String clear = zkAlias.decrypt(encrypted);
+    Assert.assertEquals(testPassword, clear);
+
+  }
+
+}

Reply via email to