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