You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by lm...@apache.org on 2017/11/27 17:46:48 UTC

[1/3] knox git commit: KNOX-1107 - Remote Configuration Registry Client Service (Phil Zampino via lmccay)

Repository: knox
Updated Branches:
  refs/heads/master 24d51ad9e -> 5af2413c4


http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
new file mode 100644
index 0000000..6cbef9b
--- /dev/null
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfigTest.java
@@ -0,0 +1,255 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+import org.apache.hadoop.gateway.service.config.remote.zk.RemoteConfigurationRegistryJAASConfig;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class RemoteConfigurationRegistryJAASConfigTest {
+
+    @Test
+    public void testZooKeeperDigestContextEntry() throws Exception {
+        List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
+        final String ENTRY_NAME       = "my_digest_context";
+        final String DIGEST_PRINCIPAL = "myIdentity";
+        final String DIGEST_PWD_ALIAS = "myAlias";
+        final String DIGEST_PWD       = "mysecret";
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        EasyMock.expect(aliasService.getPasswordFromAliasForGateway(DIGEST_PWD_ALIAS)).andReturn(DIGEST_PWD.toCharArray()).anyTimes();
+        EasyMock.replay(aliasService);
+
+        registryConfigs.add(createDigestConfig(ENTRY_NAME, DIGEST_PRINCIPAL, DIGEST_PWD_ALIAS));
+
+        try {
+            RemoteConfigurationRegistryJAASConfig jaasConfig =
+                                    RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, aliasService);
+
+            // Make sure there are no entries for an invalid context entry name
+            assertNull(jaasConfig.getAppConfigurationEntry("invalid"));
+
+            // Validate the intended context entry
+            validateDigestContext(jaasConfig,
+                                  ENTRY_NAME,
+                                  RemoteConfigurationRegistryJAASConfig.digestLoginModules.get("ZOOKEEPER"),
+                                  DIGEST_PRINCIPAL,
+                                  DIGEST_PWD);
+        } finally {
+            Configuration.setConfiguration(null);
+        }
+    }
+
+    @Test
+    public void testKerberosContextEntry() throws Exception {
+        List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
+        final String ENTRY_NAME = "my_kerberos_context";
+        final String PRINCIPAL  = "myIdentity";
+
+        File dummyKeyTab = File.createTempFile("my_context", "keytab");
+        registryConfigs.add(createKerberosConfig(ENTRY_NAME, PRINCIPAL, dummyKeyTab.getAbsolutePath()));
+
+        try {
+            RemoteConfigurationRegistryJAASConfig jaasConfig =
+                                            RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, null);
+
+            // Make sure there are no entries for an invalid context entry name
+            assertNull(jaasConfig.getAppConfigurationEntry("invalid"));
+
+            // Validate the intended context entry
+            validateKerberosContext(jaasConfig,
+                                    ENTRY_NAME,
+                                    PRINCIPAL,
+                                    dummyKeyTab.getAbsolutePath(),
+                                    true,
+                                    false);
+
+        } finally {
+            Configuration.setConfiguration(null);
+        }
+    }
+
+    @Test
+    public void testZooKeeperMultipleContextEntries() throws Exception {
+        List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
+        final String KERBEROS_ENTRY_NAME = "my_kerberos_context";
+        final String KERBEROS_PRINCIPAL  = "myKerberosIdentity";
+        final String DIGEST_ENTRY_NAME   = "my_digest_context";
+        final String DIGEST_PRINCIPAL    = "myDigestIdentity";
+        final String DIGEST_PWD_ALIAS    = "myAlias";
+        final String DIGEST_PWD          = "mysecret";
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        EasyMock.expect(aliasService.getPasswordFromAliasForGateway(DIGEST_PWD_ALIAS)).andReturn(DIGEST_PWD.toCharArray()).anyTimes();
+        EasyMock.replay(aliasService);
+
+        File dummyKeyTab = File.createTempFile("my_context", "keytab");
+        registryConfigs.add(createKerberosConfig(KERBEROS_ENTRY_NAME, KERBEROS_PRINCIPAL, dummyKeyTab.getAbsolutePath()));
+        registryConfigs.add(createDigestConfig(DIGEST_ENTRY_NAME, DIGEST_PRINCIPAL, DIGEST_PWD_ALIAS));
+
+        try {
+            RemoteConfigurationRegistryJAASConfig jaasConfig =
+                                        RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, aliasService);
+
+            // Make sure there are no entries for an invalid context entry name
+            assertNull(jaasConfig.getAppConfigurationEntry("invalid"));
+
+            // Validate the kerberos context entry
+            validateKerberosContext(jaasConfig,
+                                    KERBEROS_ENTRY_NAME,
+                                    KERBEROS_PRINCIPAL,
+                                    dummyKeyTab.getAbsolutePath(),
+                                    true,
+                                    false);
+
+            // Validate the digest context entry
+            validateDigestContext(jaasConfig,
+                                  DIGEST_ENTRY_NAME,
+                                  RemoteConfigurationRegistryJAASConfig.digestLoginModules.get("ZOOKEEPER"),
+                                  DIGEST_PRINCIPAL,
+                                  DIGEST_PWD);
+
+        } finally {
+            Configuration.setConfiguration(null);
+        }
+    }
+
+    @Test
+    public void testZooKeeperDigestContextEntryWithoutAliasService() throws Exception {
+        List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
+        final String ENTRY_NAME       = "my_digest_context";
+        final String DIGEST_PRINCIPAL = "myIdentity";
+        final String DIGEST_PWD_ALIAS = "myAlias";
+
+        registryConfigs.add(createDigestConfig(ENTRY_NAME, DIGEST_PRINCIPAL, DIGEST_PWD_ALIAS));
+
+        try {
+            RemoteConfigurationRegistryJAASConfig jaasConfig =
+                                            RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, null);
+            fail("Expected IllegalArgumentException because the AliasService is not available.");
+        } catch (IllegalArgumentException e) {
+            // Expected
+            assertTrue(e.getMessage().contains("AliasService"));
+        } catch (Throwable e) {
+            fail("Wrong exception encountered: " + e.getClass().getName() + ", " + e.getMessage());
+        } finally {
+            Configuration.setConfiguration(null);
+        }
+    }
+
+    private static RemoteConfigurationRegistryConfig createDigestConfig(String entryName,
+                                                                        String principal,
+                                                                        String credentialAlias) {
+        return createDigestConfig(entryName, principal, credentialAlias, "ZooKeeper");
+    }
+
+    private static RemoteConfigurationRegistryConfig createDigestConfig(String entryName,
+                                                                        String principal,
+                                                                        String credentialAlias,
+                                                                        String registryType) {
+        RemoteConfigurationRegistryConfig rc = EasyMock.createNiceMock(RemoteConfigurationRegistryConfig.class);
+        EasyMock.expect(rc.getRegistryType()).andReturn(registryType).anyTimes();
+        EasyMock.expect(rc.getName()).andReturn(entryName).anyTimes();
+        EasyMock.expect(rc.isSecureRegistry()).andReturn(true).anyTimes();
+        EasyMock.expect(rc.getAuthType()).andReturn("digest").anyTimes();
+        EasyMock.expect(rc.getPrincipal()).andReturn(principal).anyTimes();
+        EasyMock.expect(rc.getCredentialAlias()).andReturn(credentialAlias).anyTimes();
+        EasyMock.replay(rc);
+        return rc;
+    }
+
+
+    private static RemoteConfigurationRegistryConfig createKerberosConfig(String entryName,
+                                                                          String principal,
+                                                                          String keyTabPath) {
+        return createKerberosConfig(entryName, principal, keyTabPath, "ZooKeeper");
+    }
+
+    private static RemoteConfigurationRegistryConfig createKerberosConfig(String entryName,
+                                                                          String principal,
+                                                                          String keyTabPath,
+                                                                          String registryType) {
+        return createKerberosConfig(entryName, principal, keyTabPath, null, null, registryType);
+    }
+
+    private static RemoteConfigurationRegistryConfig createKerberosConfig(String entryName,
+                                                                          String principal,
+                                                                          String keyTabPath,
+                                                                          Boolean useKeyTab,
+                                                                          Boolean useTicketCache,
+                                                                          String registryType) {
+        RemoteConfigurationRegistryConfig rc = EasyMock.createNiceMock(RemoteConfigurationRegistryConfig.class);
+        EasyMock.expect(rc.getRegistryType()).andReturn(registryType).anyTimes();
+        EasyMock.expect(rc.getName()).andReturn(entryName).anyTimes();
+        EasyMock.expect(rc.isSecureRegistry()).andReturn(true).anyTimes();
+        EasyMock.expect(rc.getAuthType()).andReturn("kerberos").anyTimes();
+        EasyMock.expect(rc.getPrincipal()).andReturn(principal).anyTimes();
+        EasyMock.expect(rc.getKeytab()).andReturn(keyTabPath).anyTimes();
+        EasyMock.expect(rc.isUseKeyTab()).andReturn(useKeyTab != null ? useKeyTab : true).anyTimes();
+        EasyMock.expect(rc.isUseTicketCache()).andReturn(useTicketCache != null ? useTicketCache : false).anyTimes();
+        EasyMock.replay(rc);
+        return rc;
+    }
+
+    private static void validateDigestContext(RemoteConfigurationRegistryJAASConfig config,
+                                              String                                entryName,
+                                              String                                loginModule,
+                                              String                                principal,
+                                              String                                password) throws Exception {
+        AppConfigurationEntry[] myContextEntries = config.getAppConfigurationEntry(entryName);
+        assertNotNull(myContextEntries);
+        assertEquals(1, myContextEntries.length);
+        AppConfigurationEntry entry = myContextEntries[0];
+        assertTrue(entry.getLoginModuleName().equals(loginModule));
+        Map<String, ?> entryOpts = entry.getOptions();
+        assertEquals(principal, entryOpts.get("username"));
+        assertEquals(password, entryOpts.get("password"));
+    }
+
+    private static void validateKerberosContext(RemoteConfigurationRegistryJAASConfig config,
+                                                String                                entryName,
+                                                String                                principal,
+                                                String                                keyTab,
+                                                boolean                               useKeyTab,
+                                                boolean                               useTicketCache) throws Exception {
+        AppConfigurationEntry[] myContextEntries = config.getAppConfigurationEntry(entryName);
+        assertNotNull(myContextEntries);
+        assertEquals(1, myContextEntries.length);
+        AppConfigurationEntry entry = myContextEntries[0];
+        assertTrue(entry.getLoginModuleName().endsWith(".security.auth.module.Krb5LoginModule"));
+        Map<String, ?> entryOpts = entry.getOptions();
+        assertEquals(principal, entryOpts.get("principal"));
+        assertEquals(keyTab, entryOpts.get("keyTab"));
+        assertEquals(useKeyTab, Boolean.valueOf((String)entryOpts.get("isUseKeyTab")));
+        assertEquals(useTicketCache, Boolean.valueOf((String)entryOpts.get("isUseTicketCache")));
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
index 66fb83c..5cfaf36 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/config/GatewayConfig.java
@@ -42,6 +42,16 @@ public interface GatewayConfig {
   public static final String SIGNING_KEYSTORE_NAME = "gateway.signing.keystore.name";
   public static final String SIGNING_KEY_ALIAS = "gateway.signing.key.alias";
 
+  String REMOTE_CONFIG_REGISTRY_TYPE = "type";
+  String REMOTE_CONFIG_REGISTRY_ADDRESS = "address";
+  String REMOTE_CONFIG_REGISTRY_NAMESPACE = "namespace";
+  String REMOTE_CONFIG_REGISTRY_AUTH_TYPE = "authType";
+  String REMOTE_CONFIG_REGISTRY_PRINCIPAL = "principal";
+  String REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS = "credentialAlias";
+  String REMOTE_CONFIG_REGISTRY_KEYTAB = "keytab";
+  String REMOTE_CONFIG_REGISTRY_USE_KEYTAB = "useKeytab";
+  String REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE = "useTicketCache";
+
   /**
    * The location of the gateway configuration.
    * Subdirectories will be: topologies
@@ -76,6 +86,10 @@ public interface GatewayConfig {
 
   String getGatewayPath();
 
+  String getGatewayProvidersConfigDir();
+
+  String getGatewayDescriptorsDir();
+
   String getGatewayTopologyDir();
 
   String getGatewaySecurityDir();
@@ -299,4 +313,24 @@ public interface GatewayConfig {
    * @return
    */
   boolean isGatewayServerHeaderEnabled();
+
+  /**
+   * @return The list of the names of any remote registry configurations defined herein.
+   */
+  List<String> getRemoteRegistryConfigurationNames();
+
+  /**
+   *
+   * @param name The name of the remote registry configuration
+   *
+   * @return The configuration associated with the specified name.
+   */
+  String getRemoteRegistryConfiguration(String name);
+
+  /**
+   *
+   * @return The name of a remote configuration registry client
+   */
+  String getRemoteConfigurationMonitorClientName();
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/GatewayServices.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/GatewayServices.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/GatewayServices.java
index 2e6227c..2894bbc 100644
--- a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/GatewayServices.java
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/GatewayServices.java
@@ -39,6 +39,8 @@ public interface GatewayServices extends Service, ProviderDeploymentContributor
   public static final String SERVICE_DEFINITION_REGISTRY = "ServiceDefinitionRegistry";
   public static final String METRICS_SERVICE = "MetricsService";
 
+  String REMOTE_REGISTRY_CLIENT_SERVICE = "RemoteConfigRegistryClientService";
+
   public abstract Collection<String> getServiceNames();
 
   public abstract <T> T getService( String serviceName );

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClient.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClient.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClient.java
new file mode 100644
index 0000000..6fbf410
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClient.java
@@ -0,0 +1,74 @@
+/**
+ * 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.hadoop.gateway.services.config.client;
+
+import java.util.List;
+
+public interface RemoteConfigurationRegistryClient {
+
+    String getAddress();
+
+    boolean entryExists(String path);
+
+    List<EntryACL> getACL(String path);
+
+    List<String> listChildEntries(String path);
+
+    String getEntryData(String path);
+
+    String getEntryData(String path, String encoding);
+
+    void createEntry(String path);
+
+    void createEntry(String path, String data);
+
+    void createEntry(String path, String data, String encoding);
+
+    int setEntryData(String path, String data);
+
+    int setEntryData(String path, String data, String encoding);
+
+    void deleteEntry(String path);
+
+    void addChildEntryListener(String path, ChildEntryListener listener) throws Exception;
+
+    void addEntryListener(String path, EntryListener listener) throws Exception;
+
+    void removeEntryListener(String path) throws Exception;
+
+    interface ChildEntryListener {
+
+        enum Type {
+            ADDED,
+            REMOVED,
+            UPDATED
+        }
+
+        void childEvent(RemoteConfigurationRegistryClient client, ChildEntryListener.Type type, String path);
+    }
+
+    interface EntryListener {
+        void entryChanged(RemoteConfigurationRegistryClient client, String path, byte[] data);
+    }
+
+    interface EntryACL {
+        String getId();
+        String getType();
+        Object getPermissions();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClientService.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClientService.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClientService.java
new file mode 100644
index 0000000..1467f75
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/services/config/client/RemoteConfigurationRegistryClientService.java
@@ -0,0 +1,28 @@
+/**
+ * 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.hadoop.gateway.services.config.client;
+
+import org.apache.hadoop.gateway.services.Service;
+import org.apache.hadoop.gateway.services.security.AliasService;
+
+public interface RemoteConfigurationRegistryClientService extends Service {
+
+    void setAliasService(AliasService aliasService);
+
+    RemoteConfigurationRegistryClient get(String l);
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitor.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitor.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitor.java
new file mode 100644
index 0000000..82c5809
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitor.java
@@ -0,0 +1,24 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+public interface RemoteConfigurationMonitor {
+
+    void start() throws Exception;
+
+    void stop() throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
----------------------------------------------------------------------
diff --git a/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
new file mode 100644
index 0000000..d19dace
--- /dev/null
+++ b/gateway-spi/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorProvider.java
@@ -0,0 +1,34 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+public interface RemoteConfigurationMonitorProvider {
+
+    /**
+     *
+     * @param config        The gateway configuration.
+     * @param clientService The RemoteConfigurationRegistryClientService for accessing the remote configuration.
+     *
+     * @return A RemoteConfigurationMonitor for keeping the local config in sync with the remote config
+     */
+    RemoteConfigurationMonitor newInstance(GatewayConfig config, RemoteConfigurationRegistryClientService clientService);
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java
----------------------------------------------------------------------
diff --git a/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java b/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java
index ff9a877..f7ea633 100644
--- a/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java
+++ b/gateway-test-release-utils/src/main/java/org/apache/hadoop/gateway/GatewayTestConfig.java
@@ -25,6 +25,7 @@ import java.io.File;
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -614,4 +615,29 @@ public class GatewayTestConfig extends Configuration implements GatewayConfig {
   public boolean isClientAuthWanted() {
     return false;
   }
+
+  @Override
+  public String getGatewayProvidersConfigDir() {
+    return null;
+  }
+
+  @Override
+  public String getGatewayDescriptorsDir() {
+    return null;
+  }
+
+  @Override
+  public List<String> getRemoteRegistryConfigurationNames() {
+    return Collections.emptyList();
+  }
+
+  @Override
+  public String getRemoteRegistryConfiguration(String s) {
+    return null;
+  }
+
+  @Override
+  public String getRemoteConfigurationMonitorClientName() {
+    return null;
+  }
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-test-utils/src/main/java/org/apache/hadoop/test/TestUtils.java
----------------------------------------------------------------------
diff --git a/gateway-test-utils/src/main/java/org/apache/hadoop/test/TestUtils.java b/gateway-test-utils/src/main/java/org/apache/hadoop/test/TestUtils.java
index 076c312..cf446a3 100644
--- a/gateway-test-utils/src/main/java/org/apache/hadoop/test/TestUtils.java
+++ b/gateway-test-utils/src/main/java/org/apache/hadoop/test/TestUtils.java
@@ -50,7 +50,7 @@ public class TestUtils {
   private static Logger LOG = Logger.getLogger(TestUtils.class);
 
   public static final long SHORT_TIMEOUT = 1000L;
-  public static final long MEDIUM_TIMEOUT = 20 * 1000L;
+  public static final long MEDIUM_TIMEOUT = 30 * 1000L;
   public static final long LONG_TIMEOUT = 60 * 1000L;
 
   public static String getResourceName( Class clazz, String name ) {

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-test/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-test/pom.xml b/gateway-test/pom.xml
index 3b622aa..4eb5093 100644
--- a/gateway-test/pom.xml
+++ b/gateway-test/pom.xml
@@ -132,6 +132,12 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
     </dependencies>
 
     <build>

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-test/src/test/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
----------------------------------------------------------------------
diff --git a/gateway-test/src/test/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorTest.java b/gateway-test/src/test/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
new file mode 100644
index 0000000..14d98a9
--- /dev/null
+++ b/gateway-test/src/test/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorTest.java
@@ -0,0 +1,397 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+import org.apache.commons.io.FileUtils;
+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.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientService;
+import org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.test.TestUtils;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.easymock.EasyMock;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the RemoteConfigurationMonitor functionality with SASL configured, and znode ACLs applied.
+ *
+ * The expected implementation is org.apache.hadoop.gateway.topology.monitor.zk.ZooKeeperConfigMonitor
+ *
+ * Digest-based SASL is used for this test, but since that is dictated solely by the JAAS config, Kerberos-based SASL
+ * should work in exactly the same way, simply by modifying the SASL config.
+ */
+public class RemoteConfigurationMonitorTest {
+
+    private static final String PATH_KNOX = "/knox";
+    private static final String PATH_KNOX_CONFIG = PATH_KNOX + "/config";
+    private static final String PATH_KNOX_PROVIDERS = PATH_KNOX_CONFIG + "/shared-providers";
+    private static final String PATH_KNOX_DESCRIPTORS = PATH_KNOX_CONFIG + "/descriptors";
+
+    private static final String ZK_USERNAME = "testsasluser";
+    private static final String ZK_PASSWORD = "testsaslpwd";
+
+    private static File testTmp;
+    private static File providersDir;
+    private static File descriptorsDir;
+
+    private static TestingCluster zkCluster;
+
+    private static CuratorFramework client;
+
+    @BeforeClass
+    public static void setupSuite() throws Exception {
+        testTmp = TestUtils.createTempDir(RemoteConfigurationMonitorTest.class.getName());
+        File confDir   = TestUtils.createTempDir(testTmp + "/conf");
+        providersDir   = TestUtils.createTempDir(confDir + "/shared-providers");
+        descriptorsDir = TestUtils.createTempDir(confDir + "/descriptors");
+
+        configureAndStartZKCluster();
+    }
+
+    /**
+     * Create and persist a JAAS configuration file, defining the SASL config for both the ZooKeeper cluster instances
+     * and ZooKeeper clients.
+     *
+     * @param username The digest username
+     * @param password The digest password
+     *
+     * @return The JAAS configuration file
+     */
+    private static File setupDigestSaslConfig(String username, String password) throws Exception {
+        File saslConfigFile = new File(testTmp, "server-jaas.conf");
+        FileWriter fw = new FileWriter(saslConfigFile);
+        fw.write("Server {\n" +
+                "    org.apache.zookeeper.server.auth.DigestLoginModule required\n" +
+                "    user_" + username + " =\"" + password + "\";\n" +
+                "};\n"+
+                "Client {\n" +
+                "    org.apache.zookeeper.server.auth.DigestLoginModule required\n" +
+                "    username=\"" + username + "\"\n" +
+                "    password=\"" + password + "\";\n" +
+                "};\n");
+        fw.close();
+        return saslConfigFile;
+    }
+
+    /**
+     * Configure and start the ZooKeeper test cluster, and create the znodes monitored by the RemoteConfigurationMonitor.
+     */
+    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);
+        }
+        zkCluster = new TestingCluster(instanceSpecs);
+
+        // Configure auth for the ZooKeeper servers and the clients
+        File saslConfigFile = setupDigestSaslConfig(ZK_USERNAME, ZK_PASSWORD);
+
+        // This system property is used by the ZooKeeper cluster instances, the test driver client, and the
+        // RemoteConfigurationMonitor implementation for SASL authentication/authorization
+        System.setProperty("java.security.auth.login.config", saslConfigFile.getAbsolutePath());
+
+        // Start the cluster
+        zkCluster.start();
+
+        // Create the client for the test cluster
+        client = CuratorFrameworkFactory.builder()
+                .connectString(zkCluster.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, new Id("sasl", ZK_USERNAME)));
+
+        client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX_DESCRIPTORS);
+        assertNotNull("Failed to create node:" + PATH_KNOX_DESCRIPTORS,
+                client.checkExists().forPath(PATH_KNOX_DESCRIPTORS));
+        client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX_PROVIDERS);
+        assertNotNull("Failed to create node:" + PATH_KNOX_PROVIDERS,
+                client.checkExists().forPath(PATH_KNOX_PROVIDERS));
+    }
+
+    @AfterClass
+    public static void tearDownSuite() throws Exception {
+        // Clean up the ZK nodes, and close the client
+        if (client != null) {
+            client.delete().deletingChildrenIfNeeded().forPath(PATH_KNOX);
+            client.close();
+        }
+
+        // Shutdown the ZK cluster
+        zkCluster.close();
+
+        // Delete the working dir
+        testTmp.delete();
+    }
+
+    @Test
+    public void testZooKeeperConfigMonitorSASL() throws Exception {
+        final String configMonitorName = "zkConfigClient";
+
+        // Setup the base GatewayConfig mock
+        GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class);
+        EasyMock.expect(gc.getGatewayProvidersConfigDir()).andReturn(providersDir.getAbsolutePath()).anyTimes();
+        EasyMock.expect(gc.getGatewayDescriptorsDir()).andReturn(descriptorsDir.getAbsolutePath()).anyTimes();
+        EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
+                .andReturn(Collections.singletonList(configMonitorName))
+                .anyTimes();
+        final String registryConfig =
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
+        EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
+                .andReturn(registryConfig).anyTimes();
+        EasyMock.expect(gc.getRemoteConfigurationMonitorClientName()).andReturn(configMonitorName).anyTimes();
+        EasyMock.replay(gc);
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        EasyMock.replay(aliasService);
+
+        RemoteConfigurationRegistryClientService clientService = (new ZooKeeperClientServiceProvider()).newInstance();
+        clientService.setAliasService(aliasService);
+        clientService.init(gc, Collections.emptyMap());
+        clientService.start();
+
+        RemoteConfigurationMonitorFactory.setClientService(clientService);
+
+        RemoteConfigurationMonitor cm = RemoteConfigurationMonitorFactory.get(gc);
+        assertNotNull("Failed to load RemoteConfigurationMonitor", cm);
+
+        try {
+            cm.start();
+        } catch (Exception e) {
+            fail("Failed to start monitor: " + e.getMessage());
+        }
+
+        try {
+            final String pc_one_znode = getProviderPath("providers-config1.xml");
+            final File pc_one         = new File(providersDir, "providers-config1.xml");
+            final String pc_two_znode = getProviderPath("providers-config2.xml");
+            final File pc_two         = new File(providersDir, "providers-config2.xml");
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(pc_one_znode, TEST_PROVIDERS_CONFIG_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_one.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_1, FileUtils.readFileToString(pc_one));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(getProviderPath("providers-config2.xml"), TEST_PROVIDERS_CONFIG_2.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_two.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_2, FileUtils.readFileToString(pc_two));
+
+            client.setData().forPath(pc_two_znode, TEST_PROVIDERS_CONFIG_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_two.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_1, FileUtils.readFileToString(pc_two));
+
+            client.delete().forPath(pc_two_znode);
+            Thread.sleep(100);
+            assertFalse(pc_two.exists());
+
+            client.delete().forPath(pc_one_znode);
+            Thread.sleep(100);
+            assertFalse(pc_one.exists());
+
+            final String desc_one_znode   = getDescriptorPath("test1.json");
+            final String desc_two_znode   = getDescriptorPath("test2.json");
+            final String desc_three_znode = getDescriptorPath("test3.json");
+            final File desc_one           = new File(descriptorsDir, "test1.json");
+            final File desc_two           = new File(descriptorsDir, "test2.json");
+            final File desc_three         = new File(descriptorsDir, "test3.json");
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_one_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_one.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_one));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_two_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_two.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_two));
+
+            client.setData().forPath(desc_two_znode, TEST_DESCRIPTOR_2.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_two.exists());
+            assertEquals(TEST_DESCRIPTOR_2, FileUtils.readFileToString(desc_two));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_three_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_three.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_three));
+
+            client.delete().forPath(desc_two_znode);
+            Thread.sleep(100);
+            assertFalse("Expected test2.json to have been deleted.", desc_two.exists());
+
+            client.delete().forPath(desc_three_znode);
+            Thread.sleep(100);
+            assertFalse(desc_three.exists());
+
+            client.delete().forPath(desc_one_znode);
+            Thread.sleep(100);
+            assertFalse(desc_one.exists());
+        } finally {
+            cm.stop();
+        }
+    }
+
+    private static String getDescriptorPath(String descriptorName) {
+        return PATH_KNOX_DESCRIPTORS + "/" + descriptorName;
+    }
+
+    private static String getProviderPath(String providerConfigName) {
+        return PATH_KNOX_PROVIDERS + "/" + providerConfigName;
+    }
+
+
+    private static final String TEST_PROVIDERS_CONFIG_1 =
+                    "<gateway>\n" +
+                    "    <provider>\n" +
+                    "        <role>identity-assertion</role>\n" +
+                    "        <name>Default</name>\n" +
+                    "        <enabled>true</enabled>\n" +
+                    "    </provider>\n" +
+                    "    <provider>\n" +
+                    "        <role>hostmap</role>\n" +
+                    "        <name>static</name>\n" +
+                    "        <enabled>true</enabled>\n" +
+                    "        <param><name>localhost</name><value>sandbox,sandbox.hortonworks.com</value></param>\n" +
+                    "    </provider>\n" +
+                    "</gateway>\n";
+
+    private static final String TEST_PROVIDERS_CONFIG_2 =
+                    "<gateway>\n" +
+                    "    <provider>\n" +
+                    "        <role>authentication</role>\n" +
+                    "        <name>ShiroProvider</name>\n" +
+                    "        <enabled>true</enabled>\n" +
+                    "        <param>\n" +
+                    "            <name>sessionTimeout</name>\n" +
+                    "            <value>30</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapRealm</name>\n" +
+                    "            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapContextFactory</name>\n" +
+                    "            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapRealm.contextFactory</name>\n" +
+                    "            <value>$ldapContextFactory</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapRealm.userDnTemplate</name>\n" +
+                    "            <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapRealm.contextFactory.url</name>\n" +
+                    "            <value>ldap://localhost:33389</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>main.ldapRealm.contextFactory.authenticationMechanism</name>\n" +
+                    "            <value>simple</value>\n" +
+                    "        </param>\n" +
+                    "        <param>\n" +
+                    "            <name>urls./**</name>\n" +
+                    "            <value>authcBasic</value>\n" +
+                    "        </param>\n" +
+                    "    </provider>\n" +
+                    "</gateway>\n";
+
+    private static final String TEST_DESCRIPTOR_1 =
+                    "{\n" +
+                    "  \"discovery-type\":\"AMBARI\",\n" +
+                    "  \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
+                    "  \"discovery-user\":\"maria_dev\",\n" +
+                    "  \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
+                    "  \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
+                    "  \"cluster\":\"Sandbox\",\n" +
+                    "  \"services\":[\n" +
+                    "    {\"name\":\"NODEUI\"},\n" +
+                    "    {\"name\":\"YARNUI\"},\n" +
+                    "    {\"name\":\"HDFSUI\"},\n" +
+                    "    {\"name\":\"OOZIEUI\"},\n" +
+                    "    {\"name\":\"HBASEUI\"},\n" +
+                    "    {\"name\":\"NAMENODE\"},\n" +
+                    "    {\"name\":\"JOBTRACKER\"},\n" +
+                    "    {\"name\":\"WEBHDFS\"},\n" +
+                    "    {\"name\":\"WEBHCAT\"},\n" +
+                    "    {\"name\":\"OOZIE\"},\n" +
+                    "    {\"name\":\"WEBHBASE\"},\n" +
+                    "    {\"name\":\"RESOURCEMANAGER\"},\n" +
+                    "    {\"name\":\"AMBARI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]},\n" +
+                    "    {\"name\":\"AMBARIUI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]}\n" +
+                    "  ]\n" +
+                    "}\n";
+
+    private static final String TEST_DESCRIPTOR_2 =
+                    "{\n" +
+                    "  \"discovery-type\":\"AMBARI\",\n" +
+                    "  \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
+                    "  \"discovery-user\":\"maria_dev\",\n" +
+                    "  \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
+                    "  \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
+                    "  \"cluster\":\"Sandbox\",\n" +
+                    "  \"services\":[\n" +
+                    "    {\"name\":\"NAMENODE\"},\n" +
+                    "    {\"name\":\"JOBTRACKER\"},\n" +
+                    "    {\"name\":\"WEBHDFS\"},\n" +
+                    "    {\"name\":\"WEBHCAT\"},\n" +
+                    "    {\"name\":\"OOZIE\"},\n" +
+                    "    {\"name\":\"WEBHBASE\"},\n" +
+                    "    {\"name\":\"RESOURCEMANAGER\"}\n" +
+                    "  ]\n" +
+                    "}\n";
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 51ff1a4..a55acd6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -83,6 +83,7 @@
         <module>gateway-service-tgs</module>
         <module>gateway-service-rm</module>
         <module>gateway-service-storm</module>
+        <module>gateway-service-remoteconfig</module>
         <module>gateway-service-definitions</module>
         <module>gateway-shell</module>
         <module>gateway-shell-launcher</module>
@@ -629,6 +630,11 @@
             </dependency>
             <dependency>
                 <groupId>${gateway-group}</groupId>
+                <artifactId>gateway-service-remoteconfig</artifactId>
+                <version>${gateway-version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${gateway-group}</groupId>
                 <artifactId>gateway-service-test</artifactId>
                 <version>${gateway-version}</version>
             </dependency>
@@ -1136,12 +1142,17 @@
             <dependency>
                 <groupId>org.apache.zookeeper</groupId>
                 <artifactId>zookeeper</artifactId>
-                <version>3.4.6</version>
+                <version>3.4.10</version>
             </dependency>
             <dependency>
                 <groupId>org.apache.curator</groupId>
                 <artifactId>curator-framework</artifactId>
-                <version>2.6.0</version>
+                <version>4.0.0</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-client</artifactId>
+                <version>4.0.0</version>
             </dependency>
 
             <!-- Html pull parser.  EPLv1 license -->
@@ -1333,11 +1344,10 @@
             <dependency>
                 <groupId>org.apache.curator</groupId>
                 <artifactId>curator-test</artifactId>
-                <version>2.6.0</version>
+                <version>2.12.0</version>
                 <scope>test</scope>
             </dependency>
 
-
         </dependencies>
     </dependencyManagement>
 


[3/3] knox git commit: KNOX-1107 - Remote Configuration Registry Client Service (Phil Zampino via lmccay)

Posted by lm...@apache.org.
KNOX-1107 - Remote Configuration Registry Client Service (Phil Zampino via lmccay)

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

Branch: refs/heads/master
Commit: 5af2413c4ea4593a081e4f5ad8ba6b2d3cf78e12
Parents: 24d51ad
Author: Larry McCay <lm...@hortonworks.com>
Authored: Mon Nov 27 12:46:35 2017 -0500
Committer: Larry McCay <lm...@hortonworks.com>
Committed: Mon Nov 27 12:46:35 2017 -0500

----------------------------------------------------------------------
 gateway-server/pom.xml                          |   9 +
 .../apache/hadoop/gateway/GatewayMessages.java  |  40 +-
 .../gateway/config/impl/GatewayConfigImpl.java  |  47 +-
 .../gateway/services/CLIGatewayServices.java    |  10 +
 .../services/DefaultGatewayServices.java        |  14 +-
 .../topology/impl/DefaultTopologyService.java   |  38 +-
 .../DefaultConfigurationMonitorProvider.java    |  31 ++
 .../DefaultRemoteConfigurationMonitor.java      | 163 +++++++
 .../RemoteConfigurationMonitorFactory.java      |  74 ++++
 .../simple/SimpleDescriptorHandler.java         |   1 +
 ...y.monitor.RemoteConfigurationMonitorProvider |  19 +
 .../config/impl/GatewayConfigImplTest.java      |  28 ++
 .../topology/DefaultTopologyServiceTest.java    |  10 +-
 .../ZooKeeperConfigurationMonitorTest.java      | 355 ++++++++++++++++
 .../apache/hadoop/gateway/util/KnoxCLITest.java |  26 +-
 .../hadoop/gateway/websockets/BadUrlTest.java   |  11 +
 .../gateway/websockets/WebsocketEchoTest.java   |  11 +
 .../WebsocketMultipleConnectionTest.java        |  11 +
 gateway-service-remoteconfig/pom.xml            |  89 ++++
 .../remote/RemoteConfigurationMessages.java     |  46 ++
 ...nfigurationRegistryClientServiceFactory.java |  41 ++
 ...figurationRegistryClientServiceProvider.java |  27 ++
 .../RemoteConfigurationRegistryConfig.java      |  43 ++
 .../DefaultRemoteConfigurationRegistries.java   | 104 +++++
 .../config/RemoteConfigurationRegistries.java   |  33 ++
 .../RemoteConfigurationRegistriesAccessor.java  |  60 +++
 .../RemoteConfigurationRegistriesParser.java    |  48 +++
 .../config/RemoteConfigurationRegistry.java     | 139 ++++++
 .../config/remote/zk/CuratorClientService.java  | 423 ++++++++++++++++++
 .../RemoteConfigurationRegistryJAASConfig.java  | 169 ++++++++
 .../remote/zk/ZooKeeperClientService.java       |  25 ++
 .../zk/ZooKeeperClientServiceProvider.java      |  34 ++
 ...teConfigurationRegistryClientServiceProvider |  19 +
 ...efaultRemoteConfigurationRegistriesTest.java | 184 ++++++++
 ...teConfigurationRegistryConfigParserTest.java | 108 +++++
 .../util/RemoteRegistryConfigTestUtils.java     | 117 +++++
 ...eConfigurationRegistryClientServiceTest.java | 424 +++++++++++++++++++
 ...moteConfigurationRegistryJAASConfigTest.java | 255 +++++++++++
 .../hadoop/gateway/config/GatewayConfig.java    |  34 ++
 .../gateway/services/GatewayServices.java       |   2 +
 .../RemoteConfigurationRegistryClient.java      |  74 ++++
 ...emoteConfigurationRegistryClientService.java |  28 ++
 .../monitor/RemoteConfigurationMonitor.java     |  24 ++
 .../RemoteConfigurationMonitorProvider.java     |  34 ++
 .../hadoop/gateway/GatewayTestConfig.java       |  26 ++
 .../java/org/apache/hadoop/test/TestUtils.java  |   2 +-
 gateway-test/pom.xml                            |   6 +
 .../monitor/RemoteConfigurationMonitorTest.java | 397 +++++++++++++++++
 pom.xml                                         |  18 +-
 49 files changed, 3918 insertions(+), 13 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-server/pom.xml b/gateway-server/pom.xml
index 0c05625..0a43584 100644
--- a/gateway-server/pom.xml
+++ b/gateway-server/pom.xml
@@ -188,6 +188,10 @@
             <artifactId>gateway-server-xforwarded-filter</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.knox</groupId>
+            <artifactId>gateway-service-remoteconfig</artifactId>
+        </dependency>
+        <dependency>
             <groupId>net.lingala.zip4j</groupId>
             <artifactId>zip4j</artifactId>
         </dependency>
@@ -316,6 +320,11 @@
             <artifactId>metrics-servlets</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-test</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 </project>

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
index 4cb4c40..d78ef71 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/GatewayMessages.java
@@ -514,19 +514,57 @@ public interface GatewayMessages {
   void topologyPortMappingCannotFindTopology(final String topology, final int port);
 
 
+  @Message( level = MessageLevel.WARN, text = "There is no registry client defined for remote configuration monitoring." )
+  void missingClientConfigurationForRemoteMonitoring();
+
+  @Message( level = MessageLevel.WARN, text = "Could not resolve a remote configuration registry client for {0}." )
+  void unresolvedClientConfigurationForRemoteMonitoring(final String clientName);
+
   @Message( level = MessageLevel.INFO, text = "Monitoring simple descriptors in directory: {0}" )
   void monitoringDescriptorChangesInDirectory(String descriptorsDir);
 
-
   @Message( level = MessageLevel.INFO, text = "Monitoring shared provider configurations in directory: {0}" )
   void monitoringProviderConfigChangesInDirectory(String sharedProviderDir);
 
+  @Message( level = MessageLevel.ERROR, text = "Error registering listener for remote configuration path {0} : {1}" )
+  void errorAddingRemoteConfigurationListenerForPath(final String path,
+                                                     @StackTrace( level = MessageLevel.DEBUG ) Exception e);
+
+  @Message( level = MessageLevel.ERROR, text = "Error unregistering listener for remote configuration path {0} : {1}" )
+  void errorRemovingRemoteConfigurationListenerForPath(final String path,
+                                                       @StackTrace( level = MessageLevel.DEBUG ) Exception e);
+
+  @Message( level = MessageLevel.ERROR, text = "Error downloading remote configuration {0} : {1}" )
+  void errorDownloadingRemoteConfiguration(final String path,
+                                           @StackTrace( level = MessageLevel.DEBUG ) Exception e);
+
   @Message( level = MessageLevel.INFO, text = "Prevented deletion of shared provider configuration because there are referencing descriptors: {0}" )
   void preventedDeletionOfSharedProviderConfiguration(String providerConfigurationPath);
 
   @Message( level = MessageLevel.INFO, text = "Generated topology {0} because the associated descriptor {1} changed." )
   void generatedTopologyForDescriptorChange(String topologyName, String descriptorName);
 
+  @Message( level = MessageLevel.WARN, text = "An error occurred while attempting to initialize the remote configuration monitor: {0}" )
+  void remoteConfigurationMonitorInitFailure(final String errorMessage,
+                                             @StackTrace( level = MessageLevel.DEBUG ) Exception e );
+
+  @Message( level = MessageLevel.WARN, text = "An error occurred while attempting to start the remote configuration monitor {0} : {1}" )
+  void remoteConfigurationMonitorStartFailure(final String monitorType,
+                                              final String errorMessage,
+                                              @StackTrace( level = MessageLevel.DEBUG ) Exception e );
+
+  @Message( level = MessageLevel.INFO, text = "Starting remote configuration monitor for source {0} ..." )
+  void startingRemoteConfigurationMonitor(final String address);
+
+  @Message( level = MessageLevel.INFO, text = "Monitoring remote configuration source {0}" )
+  void monitoringRemoteConfigurationSource(final String address);
+
+  @Message( level = MessageLevel.INFO, text = "Remote configuration monitor downloaded {0} configuration file {1}" )
+  void downloadedRemoteConfigFile(final String type, final String configFileName);
+
+  @Message( level = MessageLevel.INFO, text = "Remote configuration monitor deleted {0} configuration file {1} based on remote change." )
+  void deletedRemoteConfigFile(final String type, final String configFileName);
+
   @Message( level = MessageLevel.ERROR, text = "An error occurred while processing {0} : {1}" )
   void simpleDescriptorHandlingError(final String simpleDesc,
                                      @StackTrace(level = MessageLevel.DEBUG) Exception e);

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
index 4202a18..17c2552 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImpl.java
@@ -184,6 +184,8 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   public static final String DEFAULT_DEPLOYMENT_DIR = "deployments";
   public static final String DEFAULT_SECURITY_DIR = "security";
   public static final String DEFAULT_DATA_DIR = "data";
+  private static final String PROVIDERCONFIG_DIR_NAME = "shared-providers";
+  private static final String DESCRIPTORS_DIR_NAME = "descriptors";
 
   /* Websocket defaults */
   public static final boolean DEFAULT_WEBSOCKET_FEATURE_ENABLED = false;
@@ -214,6 +216,10 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   private static final String CRYPTO_KEY_LENGTH = GATEWAY_CONFIG_FILE_PREFIX + ".crypto.key.length";
   public static final String SERVER_HEADER_ENABLED = GATEWAY_CONFIG_FILE_PREFIX + ".server.header.enabled";
 
+  /* @since 0.15 Remote configuration monitoring */
+  static final String CONFIG_REGISTRY_PREFIX = GATEWAY_CONFIG_FILE_PREFIX + ".remote.config.registry";
+  static final String REMOTE_CONFIG_MONITOR_CLIENT_NAME = GATEWAY_CONFIG_FILE_PREFIX + ".remote.config.monitor.client";
+
   private static List<String> DEFAULT_GLOBAL_RULES_SERVICES;
 
 
@@ -264,7 +270,7 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
     } else {
       dataDir = get(DATA_DIR, getGatewayHomeDir() + File.separator + DEFAULT_DATA_DIR);
     }
-    return dataDir;
+    return FilenameUtils.normalize(dataDir);
   }
 
   @Override
@@ -412,6 +418,16 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   }
 
   @Override
+  public String getGatewayProvidersConfigDir() {
+    return getGatewayConfDir() + File.separator + PROVIDERCONFIG_DIR_NAME;
+  }
+
+  @Override
+  public String getGatewayDescriptorsDir() {
+    return getGatewayConfDir() + File.separator + DESCRIPTORS_DIR_NAME;
+  }
+
+  @Override
   public String getGatewayTopologyDir() {
     return getGatewayConfDir() + File.separator + "topologies";
   }
@@ -923,4 +939,33 @@ public class GatewayConfigImpl extends Configuration implements GatewayConfig {
   public boolean isGatewayServerHeaderEnabled() {
     return Boolean.parseBoolean(getVar(SERVER_HEADER_ENABLED, "true"));
   }
+
+  @Override
+  public List<String> getRemoteRegistryConfigurationNames() {
+    List<String> result = new ArrayList<>();
+
+    // Iterate over all the properties in this configuration
+    for (Map.Entry<String, String> entry : this) {
+      String propertyName = entry.getKey();
+
+      // Search for all the remote config registry properties
+      if (propertyName.startsWith(CONFIG_REGISTRY_PREFIX)) {
+        String registryName = propertyName.substring(CONFIG_REGISTRY_PREFIX.length() + 1);
+        result.add(registryName);
+      }
+    }
+
+    return result;
+  }
+
+  @Override
+  public String getRemoteRegistryConfiguration(String name) {
+    return get(CONFIG_REGISTRY_PREFIX + "." + name );
+  }
+
+  @Override
+  public String getRemoteConfigurationMonitorClientName() {
+    return get(REMOTE_CONFIG_MONITOR_CLIENT_NAME);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/services/CLIGatewayServices.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/CLIGatewayServices.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/CLIGatewayServices.java
index 114aa83..74dc4d3 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/CLIGatewayServices.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/CLIGatewayServices.java
@@ -23,6 +23,8 @@ import org.apache.hadoop.gateway.deploy.DeploymentContext;
 import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
 import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
 import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceFactory;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
 import org.apache.hadoop.gateway.services.topology.impl.DefaultTopologyService;
 import org.apache.hadoop.gateway.services.security.impl.DefaultAliasService;
 import org.apache.hadoop.gateway.services.security.impl.DefaultCryptoService;
@@ -71,6 +73,12 @@ public class CLIGatewayServices implements GatewayServices {
     DefaultTopologyService tops = new DefaultTopologyService();
     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 {
@@ -83,6 +91,8 @@ public class CLIGatewayServices implements GatewayServices {
 
     DefaultTopologyService tops = (DefaultTopologyService)services.get(TOPOLOGY_SERVICE);
     tops.start();
+
+    (services.get(REMOTE_REGISTRY_CLIENT_SERVICE)).start();
   }
 
   public void stop() throws ServiceLifecycleException {

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/services/DefaultGatewayServices.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/DefaultGatewayServices.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/DefaultGatewayServices.java
index 02ac154..9dca344 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/DefaultGatewayServices.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/DefaultGatewayServices.java
@@ -23,6 +23,8 @@ import org.apache.hadoop.gateway.deploy.DeploymentContext;
 import org.apache.hadoop.gateway.descriptor.FilterParamDescriptor;
 import org.apache.hadoop.gateway.descriptor.ResourceDescriptor;
 import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceFactory;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
 import org.apache.hadoop.gateway.services.registry.impl.DefaultServiceDefinitionRegistry;
 import org.apache.hadoop.gateway.services.metrics.impl.DefaultMetricsService;
 import org.apache.hadoop.gateway.services.topology.impl.DefaultTopologyService;
@@ -104,6 +106,12 @@ 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);
+
     DefaultTopologyService tops = new DefaultTopologyService();
     tops.setAliasService(alias);
     tops.init(  config, options  );
@@ -117,7 +125,7 @@ public class DefaultGatewayServices implements GatewayServices {
     metricsService.init( config, options );
     services.put( METRICS_SERVICE, metricsService );
   }
-  
+
   public void start() throws ServiceLifecycleException {
     ms.start();
 
@@ -132,6 +140,10 @@ public class DefaultGatewayServices implements GatewayServices {
     ServerInfoService sis = (ServerInfoService) services.get(SERVER_INFO_SERVICE);
     sis.start();
 
+    RemoteConfigurationRegistryClientService clientService =
+                            (RemoteConfigurationRegistryClientService)services.get(REMOTE_REGISTRY_CLIENT_SERVICE);
+    clientService.start();
+
     DefaultTopologyService tops = (DefaultTopologyService)services.get(TOPOLOGY_SERVICE);
     tops.start();
 

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
index 39e8029..5fc3620 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/services/topology/impl/DefaultTopologyService.java
@@ -46,6 +46,8 @@ import org.apache.hadoop.gateway.topology.TopologyListener;
 import org.apache.hadoop.gateway.topology.TopologyMonitor;
 import org.apache.hadoop.gateway.topology.TopologyProvider;
 import org.apache.hadoop.gateway.topology.builder.TopologyBuilder;
+import org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitor;
+import org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitorFactory;
 import org.apache.hadoop.gateway.topology.simple.SimpleDescriptorHandler;
 import org.apache.hadoop.gateway.topology.validation.TopologyValidator;
 import org.apache.hadoop.gateway.topology.xml.AmbariFormatXmlTopologyRules;
@@ -101,6 +103,7 @@ public class DefaultTopologyService
   private volatile Map<File, Topology> topologies;
   private AliasService aliasService;
 
+  private RemoteConfigurationMonitor remoteMonitor = null;
 
   private Topology loadTopology(File file) throws IOException, SAXException, URISyntaxException, InterruptedException {
     final long TIMEOUT = 250; //ms
@@ -214,6 +217,16 @@ public class DefaultTopologyService
     return events;
   }
 
+  private File calculateAbsoluteProvidersConfigDir(GatewayConfig config) {
+    File pcDir = new File(config.getGatewayProvidersConfigDir());
+    return pcDir.getAbsoluteFile();
+  }
+
+  private File calculateAbsoluteDescriptorsDir(GatewayConfig config) {
+    File descDir = new File(config.getGatewayDescriptorsDir());
+    return descDir.getAbsoluteFile();
+  }
+
   private File calculateAbsoluteTopologiesDir(GatewayConfig config) {
     File topoDir = new File(config.getGatewayTopologyDir());
     topoDir = topoDir.getAbsoluteFile();
@@ -221,7 +234,7 @@ public class DefaultTopologyService
   }
 
   private File calculateAbsoluteConfigDir(GatewayConfig config) {
-    File configDir = null;
+    File configDir;
 
     String path = config.getGatewayConfDir();
     configDir = (path != null) ? new File(path) : (new File(config.getGatewayTopologyDir())).getParentFile();
@@ -468,16 +481,32 @@ public class DefaultTopologyService
 
   @Override
   public void startMonitor() throws Exception {
+    // Start the local configuration monitors
     for (FileAlterationMonitor monitor : monitors) {
       monitor.start();
     }
+
+    // Start the remote configuration monitor, if it has been initialized
+    if (remoteMonitor != null) {
+      try {
+        remoteMonitor.start();
+      } catch (Exception e) {
+        log.remoteConfigurationMonitorStartFailure(remoteMonitor.getClass().getTypeName(), e.getLocalizedMessage(), e);
+      }
+    }
   }
 
   @Override
   public void stopMonitor() throws Exception {
+    // Stop the local configuration monitors
     for (FileAlterationMonitor monitor : monitors) {
       monitor.stop();
     }
+
+    // Stop the remote configuration monitor, if it has been initialized
+    if (remoteMonitor != null) {
+      remoteMonitor.stop();
+    }
   }
 
   @Override
@@ -532,7 +561,7 @@ public class DefaultTopologyService
   public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
 
     try {
-      listeners = new HashSet<>();
+      listeners  = new HashSet<>();
       topologies = new HashMap<>();
 
       topologiesDirectory = calculateAbsoluteTopologiesDir(config);
@@ -567,6 +596,9 @@ public class DefaultTopologyService
           }
       }
 
+      // Initialize the remote configuration monitor, if it has been configured
+      remoteMonitor = RemoteConfigurationMonitorFactory.get(config);
+
     } catch (IOException | SAXException io) {
       throw new ServiceLifecycleException(io.getMessage());
     }
@@ -582,7 +614,7 @@ public class DefaultTopologyService
    * @return A List of the Files on the directory.
    */
   private static List<File> listFiles(File directory) {
-    List<File> result = null;
+    List<File> result;
     File[] files = directory.listFiles();
     if (files != null) {
       result = Arrays.asList(files);

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
new file mode 100644
index 0000000..7b34e3d
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultConfigurationMonitorProvider.java
@@ -0,0 +1,31 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+
+public class DefaultConfigurationMonitorProvider implements RemoteConfigurationMonitorProvider {
+
+    @Override
+    public RemoteConfigurationMonitor newInstance(final GatewayConfig                            config,
+                                                  final RemoteConfigurationRegistryClientService clientService) {
+        return new DefaultRemoteConfigurationMonitor(config, clientService);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
new file mode 100644
index 0000000..1dd71ac
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/DefaultRemoteConfigurationMonitor.java
@@ -0,0 +1,163 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.gateway.GatewayMessages;
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.EntryListener;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+
+class DefaultRemoteConfigurationMonitor implements RemoteConfigurationMonitor {
+
+    private static final String NODE_KNOX = "/knox";
+    private static final String NODE_KNOX_CONFIG = NODE_KNOX + "/config";
+    private static final String NODE_KNOX_PROVIDERS = NODE_KNOX_CONFIG + "/shared-providers";
+    private static final String NODE_KNOX_DESCRIPTORS = NODE_KNOX_CONFIG + "/descriptors";
+
+    private static GatewayMessages log = MessagesFactory.get(GatewayMessages.class);
+
+    private RemoteConfigurationRegistryClient client = null;
+
+    private File providersDir;
+    private File descriptorsDir;
+
+    /**
+     * @param config                The gateway configuration
+     * @param registryClientService The service from which the remote registry client should be acquired.
+     */
+    DefaultRemoteConfigurationMonitor(GatewayConfig                            config,
+                                      RemoteConfigurationRegistryClientService registryClientService) {
+        this.providersDir   = new File(config.getGatewayProvidersConfigDir());
+        this.descriptorsDir = new File(config.getGatewayDescriptorsDir());
+
+        if (registryClientService != null) {
+            String clientName = config.getRemoteConfigurationMonitorClientName();
+            if (clientName != null) {
+                this.client = registryClientService.get(clientName);
+                if (this.client == null) {
+                    log.unresolvedClientConfigurationForRemoteMonitoring(clientName);
+                }
+            } else {
+                log.missingClientConfigurationForRemoteMonitoring();
+            }
+        }
+    }
+
+    @Override
+    public void start() throws Exception {
+        if (client == null) {
+            throw new IllegalStateException("Failed to acquire a remote configuration registry client.");
+        }
+
+        final String monitorSource = client.getAddress();
+        log.startingRemoteConfigurationMonitor(monitorSource);
+
+        // Confirm access to the remote provider configs directory znode
+        List<String> providerConfigs = client.listChildEntries(NODE_KNOX_PROVIDERS);
+        if (providerConfigs == null) {
+            // Either the ZNode does not exist, or there is an authentication problem
+            throw new IllegalStateException("Unable to access remote path: " + NODE_KNOX_PROVIDERS);
+        }
+
+        // Confirm access to the remote descriptors directory znode
+        List<String> descriptors = client.listChildEntries(NODE_KNOX_DESCRIPTORS);
+        if (descriptors == null) {
+            // Either the ZNode does not exist, or there is an authentication problem
+            throw new IllegalStateException("Unable to access remote path: " + NODE_KNOX_DESCRIPTORS);
+        }
+
+        // Register a listener for provider config znode additions/removals
+        client.addChildEntryListener(NODE_KNOX_PROVIDERS, new ConfigDirChildEntryListener(providersDir));
+
+        // Register a listener for descriptor znode additions/removals
+        client.addChildEntryListener(NODE_KNOX_DESCRIPTORS, new ConfigDirChildEntryListener(descriptorsDir));
+
+        log.monitoringRemoteConfigurationSource(monitorSource);
+    }
+
+
+    @Override
+    public void stop() throws Exception {
+    }
+
+
+    private static class ConfigDirChildEntryListener implements ChildEntryListener {
+        File localDir;
+
+        ConfigDirChildEntryListener(File localDir) {
+            this.localDir = localDir;
+        }
+
+        @Override
+        public void childEvent(RemoteConfigurationRegistryClient client, Type type, String path) {
+            File localFile = new File(localDir, path.substring(path.lastIndexOf("/") + 1));
+
+            switch (type) {
+                case REMOVED:
+                    FileUtils.deleteQuietly(localFile);
+                    log.deletedRemoteConfigFile(localDir.getName(), localFile.getName());
+                    try {
+                        client.removeEntryListener(path);
+                    } catch (Exception e) {
+                        log.errorRemovingRemoteConfigurationListenerForPath(path, e);
+                    }
+                    break;
+                case ADDED:
+                    try {
+                        client.addEntryListener(path, new ConfigEntryListener(localDir));
+                    } catch (Exception e) {
+                        log.errorAddingRemoteConfigurationListenerForPath(path, e);
+                    }
+                    break;
+            }
+        }
+    }
+
+    private static class ConfigEntryListener implements EntryListener {
+        private File localDir;
+
+        ConfigEntryListener(File localDir) {
+            this.localDir = localDir;
+        }
+
+        @Override
+        public void entryChanged(RemoteConfigurationRegistryClient client, String path, byte[] data) {
+            File localFile = new File(localDir, path.substring(path.lastIndexOf("/")));
+            if (data != null) {
+                try {
+                    FileUtils.writeByteArrayToFile(localFile, data);
+                    log.downloadedRemoteConfigFile(localDir.getName(), localFile.getName());
+                } catch (IOException e) {
+                    log.errorDownloadingRemoteConfiguration(path, e);
+                }
+            } else {
+                FileUtils.deleteQuietly(localFile);
+                log.deletedRemoteConfigFile(localDir.getName(), localFile.getName());
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
new file mode 100644
index 0000000..4d2df45
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/monitor/RemoteConfigurationMonitorFactory.java
@@ -0,0 +1,74 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+import org.apache.hadoop.gateway.GatewayMessages;
+import org.apache.hadoop.gateway.GatewayServer;
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+import java.util.ServiceLoader;
+
+public class RemoteConfigurationMonitorFactory {
+
+    private static final GatewayMessages log = MessagesFactory.get(GatewayMessages.class);
+
+    private static RemoteConfigurationRegistryClientService remoteConfigRegistryClientService = null;
+
+    public static void setClientService(RemoteConfigurationRegistryClientService clientService) {
+        remoteConfigRegistryClientService = clientService;
+    }
+
+    private static RemoteConfigurationRegistryClientService getClientService() {
+        if (remoteConfigRegistryClientService == null) {
+            GatewayServices services = GatewayServer.getGatewayServices();
+            if (services != null) {
+                remoteConfigRegistryClientService = services.getService(GatewayServices.REMOTE_REGISTRY_CLIENT_SERVICE);
+            }
+        }
+
+        return remoteConfigRegistryClientService;
+    }
+
+    /**
+     *
+     * @param config The GatewayConfig
+     *
+     * @return The first RemoteConfigurationMonitor extension that is found.
+     */
+    public static RemoteConfigurationMonitor get(GatewayConfig config) {
+        RemoteConfigurationMonitor rcm = null;
+
+        ServiceLoader<RemoteConfigurationMonitorProvider> providers =
+                                                 ServiceLoader.load(RemoteConfigurationMonitorProvider.class);
+        for (RemoteConfigurationMonitorProvider provider : providers) {
+            try {
+                rcm = provider.newInstance(config, getClientService());
+                if (rcm != null) {
+                    break;
+                }
+            } catch (Exception e) {
+                log.remoteConfigurationMonitorInitFailure(e.getLocalizedMessage(), e);
+            }
+        }
+
+        return rcm;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/simple/SimpleDescriptorHandler.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/simple/SimpleDescriptorHandler.java b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/simple/SimpleDescriptorHandler.java
index 089925d..d1dc11d 100644
--- a/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/simple/SimpleDescriptorHandler.java
+++ b/gateway-server/src/main/java/org/apache/hadoop/gateway/topology/simple/SimpleDescriptorHandler.java
@@ -185,6 +185,7 @@ public class SimpleDescriptorHandler {
 
             // Write the service declarations
             for (String serviceName : serviceNames) {
+                fw.write("\n");
                 fw.write("    <service>\n");
                 fw.write("        <role>" + serviceName + "</role>\n");
 

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/main/resources/META-INF/services/org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitorProvider
----------------------------------------------------------------------
diff --git a/gateway-server/src/main/resources/META-INF/services/org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitorProvider b/gateway-server/src/main/resources/META-INF/services/org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitorProvider
new file mode 100644
index 0000000..bd4023e
--- /dev/null
+++ b/gateway-server/src/main/resources/META-INF/services/org.apache.hadoop.gateway.topology.monitor.RemoteConfigurationMonitorProvider
@@ -0,0 +1,19 @@
+##########################################################################
+# 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
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+##########################################################################
+
+org.apache.hadoop.gateway.topology.monitor.DefaultConfigurationMonitorProvider

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImplTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImplTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImplTest.java
index a9347f4..cd56f11 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImplTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/config/impl/GatewayConfigImplTest.java
@@ -11,6 +11,10 @@ import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.nullValue;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNotNull;
 
 /**
  * Licensed to the Apache Software Foundation (ASF) under one
@@ -217,4 +221,28 @@ public class GatewayConfigImplTest {
     assertThat( serverHeaderEnabled, is(false));
   }
 
+
+  @Test
+  public void testGetRemoteConfigurationRegistryNames() {
+    GatewayConfigImpl config = new GatewayConfigImpl();
+
+    List<String> registryNames = config.getRemoteRegistryConfigurationNames();
+    assertNotNull(registryNames);
+    assertTrue(registryNames.isEmpty());
+
+    config.set(GatewayConfigImpl.CONFIG_REGISTRY_PREFIX + ".test1",
+               "type=ZooKeeper;address=host1:2181;authType=digest;principal=itsme;credentialAlias=testAlias");
+    registryNames = config.getRemoteRegistryConfigurationNames();
+    assertNotNull(registryNames);
+    assertFalse(registryNames.isEmpty());
+    assertEquals(1, registryNames.size());
+
+    config.set(GatewayConfigImpl.CONFIG_REGISTRY_PREFIX + ".test2",
+               "type=ZooKeeper;address=host2:2181,host3:2181,host4:2181");
+    registryNames = config.getRemoteRegistryConfigurationNames();
+    assertNotNull(registryNames);
+    assertFalse(registryNames.isEmpty());
+    assertEquals(registryNames.size(), 2);
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
index 2357ad6..e4a0eba 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/services/topology/DefaultTopologyServiceTest.java
@@ -96,6 +96,12 @@ public class DefaultTopologyServiceTest {
     File dir = createDir();
     File topologyDir = new File(dir, "topologies");
 
+    File descriptorsDir = new File(dir, "descriptors");
+    descriptorsDir.mkdirs();
+
+    File sharedProvidersDir = new File(dir, "shared-providers");
+    sharedProvidersDir.mkdirs();
+
     long time = topologyDir.lastModified();
     try {
       createFile(topologyDir, "one.xml", "org/apache/hadoop/gateway/topology/file/topology-one.xml", time);
@@ -108,7 +114,9 @@ public class DefaultTopologyServiceTest {
 
       GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
       EasyMock.expect(config.getGatewayTopologyDir()).andReturn(topologyDir.getAbsolutePath()).anyTimes();
-      EasyMock.expect(config.getGatewayConfDir()).andReturn(topologyDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayConfDir()).andReturn(descriptorsDir.getParentFile().getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayProvidersConfigDir()).andReturn(sharedProvidersDir.getAbsolutePath()).anyTimes();
+      EasyMock.expect(config.getGatewayDescriptorsDir()).andReturn(descriptorsDir.getAbsolutePath()).anyTimes();
       EasyMock.replay(config);
 
       provider.init(config, c);

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
new file mode 100644
index 0000000..1c4ed6e
--- /dev/null
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/topology/monitor/ZooKeeperConfigurationMonitorTest.java
@@ -0,0 +1,355 @@
+/**
+ * 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.hadoop.gateway.topology.monitor;
+
+import org.apache.commons.io.FileUtils;
+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.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientService;
+import org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.test.TestUtils;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.easymock.EasyMock;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the ZooKeeperConfigMonitor WITHOUT SASL configured or znode ACLs applied.
+ * The implementation of the monitor is the same regardless, since the ACLs are defined by the ZooKeeper znode
+ * creator, and the SASL config is purely JAAS (and external to the implementation).
+ */
+public class ZooKeeperConfigurationMonitorTest {
+
+    private static final String PATH_KNOX = "/knox";
+    private static final String PATH_KNOX_CONFIG = PATH_KNOX + "/config";
+    private static final String PATH_KNOX_PROVIDERS = PATH_KNOX_CONFIG + "/shared-providers";
+    private static final String PATH_KNOX_DESCRIPTORS = PATH_KNOX_CONFIG + "/descriptors";
+
+    private static File testTmp;
+    private static File providersDir;
+    private static File descriptorsDir;
+
+    private static TestingCluster zkCluster;
+
+    private static CuratorFramework client;
+
+    private GatewayConfig gc;
+
+
+    @BeforeClass
+    public static void setupSuite() throws Exception {
+        testTmp = TestUtils.createTempDir(ZooKeeperConfigurationMonitorTest.class.getName());
+        File confDir   = TestUtils.createTempDir(testTmp + "/conf");
+        providersDir   = TestUtils.createTempDir(confDir + "/shared-providers");
+        descriptorsDir = TestUtils.createTempDir(confDir + "/descriptors");
+
+        configureAndStartZKCluster();
+    }
+
+    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);
+        }
+        zkCluster = new TestingCluster(instanceSpecs);
+
+        // Start the cluster
+        zkCluster.start();
+
+        // Create the client for the test cluster
+        client = CuratorFrameworkFactory.builder()
+                                        .connectString(zkCluster.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(PATH_KNOX_DESCRIPTORS);
+        assertNotNull("Failed to create node:" + PATH_KNOX_DESCRIPTORS,
+                client.checkExists().forPath(PATH_KNOX_DESCRIPTORS));
+        client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(PATH_KNOX_PROVIDERS);
+        assertNotNull("Failed to create node:" + PATH_KNOX_PROVIDERS,
+                client.checkExists().forPath(PATH_KNOX_PROVIDERS));
+    }
+
+    @AfterClass
+    public static void tearDownSuite() throws Exception {
+        // Clean up the ZK nodes, and close the client
+        if (client != null) {
+            client.delete().deletingChildrenIfNeeded().forPath(PATH_KNOX);
+            client.close();
+        }
+
+        // Shutdown the ZK cluster
+        zkCluster.close();
+
+        // Delete the working dir
+        testTmp.delete();
+    }
+
+    @Test
+    public void testZooKeeperConfigMonitor() throws Exception {
+        String configMonitorName = "remoteConfigMonitorClient";
+
+        // Setup the base GatewayConfig mock
+        gc = EasyMock.createNiceMock(GatewayConfig.class);
+        EasyMock.expect(gc.getGatewayProvidersConfigDir()).andReturn(providersDir.getAbsolutePath()).anyTimes();
+        EasyMock.expect(gc.getGatewayDescriptorsDir()).andReturn(descriptorsDir.getAbsolutePath()).anyTimes();
+        EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
+                .andReturn(Collections.singletonList(configMonitorName))
+                .anyTimes();
+        final String registryConfig =
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
+        EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
+                .andReturn(registryConfig)
+                .anyTimes();
+        EasyMock.expect(gc.getRemoteConfigurationMonitorClientName()).andReturn(configMonitorName).anyTimes();
+        EasyMock.replay(gc);
+
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        EasyMock.replay(aliasService);
+
+        RemoteConfigurationRegistryClientService clientService = (new ZooKeeperClientServiceProvider()).newInstance();
+        clientService.setAliasService(aliasService);
+        clientService.init(gc, Collections.emptyMap());
+        clientService.start();
+
+        DefaultRemoteConfigurationMonitor cm = new DefaultRemoteConfigurationMonitor(gc, clientService);
+
+        try {
+            cm.start();
+        } catch (Exception e) {
+            fail("Failed to start monitor: " + e.getMessage());
+        }
+
+        try {
+            final String pc_one_znode = getProviderPath("providers-config1.xml");
+            final File pc_one         = new File(providersDir, "providers-config1.xml");
+            final String pc_two_znode = getProviderPath("providers-config2.xml");
+            final File pc_two         = new File(providersDir, "providers-config2.xml");
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(pc_one_znode, TEST_PROVIDERS_CONFIG_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_one.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_1, FileUtils.readFileToString(pc_one));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(getProviderPath("providers-config2.xml"), TEST_PROVIDERS_CONFIG_2.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_two.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_2, FileUtils.readFileToString(pc_two));
+
+            client.setData().forPath(pc_two_znode, TEST_PROVIDERS_CONFIG_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(pc_two.exists());
+            assertEquals(TEST_PROVIDERS_CONFIG_1, FileUtils.readFileToString(pc_two));
+
+            client.delete().forPath(pc_two_znode);
+            Thread.sleep(100);
+            assertFalse(pc_two.exists());
+
+            client.delete().forPath(pc_one_znode);
+            Thread.sleep(100);
+            assertFalse(pc_one.exists());
+
+            final String desc_one_znode   = getDescriptorPath("test1.json");
+            final String desc_two_znode   = getDescriptorPath("test2.json");
+            final String desc_three_znode = getDescriptorPath("test3.json");
+            final File desc_one           = new File(descriptorsDir, "test1.json");
+            final File desc_two           = new File(descriptorsDir, "test2.json");
+            final File desc_three         = new File(descriptorsDir, "test3.json");
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_one_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_one.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_one));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_two_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_two.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_two));
+
+            client.setData().forPath(desc_two_znode, TEST_DESCRIPTOR_2.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_two.exists());
+            assertEquals(TEST_DESCRIPTOR_2, FileUtils.readFileToString(desc_two));
+
+            client.create().withMode(CreateMode.PERSISTENT).forPath(desc_three_znode, TEST_DESCRIPTOR_1.getBytes());
+            Thread.sleep(100);
+            assertTrue(desc_three.exists());
+            assertEquals(TEST_DESCRIPTOR_1, FileUtils.readFileToString(desc_three));
+
+            client.delete().forPath(desc_two_znode);
+            Thread.sleep(100);
+            assertFalse("Expected test2.json to have been deleted.", desc_two.exists());
+
+            client.delete().forPath(desc_three_znode);
+            Thread.sleep(100);
+            assertFalse(desc_three.exists());
+
+            client.delete().forPath(desc_one_znode);
+            Thread.sleep(100);
+            assertFalse(desc_one.exists());
+        } finally {
+            cm.stop();
+        }
+    }
+
+    private static String getDescriptorPath(String descriptorName) {
+        return PATH_KNOX_DESCRIPTORS + "/" + descriptorName;
+    }
+
+    private static String getProviderPath(String providerConfigName) {
+        return PATH_KNOX_PROVIDERS + "/" + providerConfigName;
+    }
+
+
+    private static final String TEST_PROVIDERS_CONFIG_1 =
+            "<gateway>\n" +
+            "    <provider>\n" +
+            "        <role>identity-assertion</role>\n" +
+            "        <name>Default</name>\n" +
+            "        <enabled>true</enabled>\n" +
+            "    </provider>\n" +
+            "    <provider>\n" +
+            "        <role>hostmap</role>\n" +
+            "        <name>static</name>\n" +
+            "        <enabled>true</enabled>\n" +
+            "        <param><name>localhost</name><value>sandbox,sandbox.hortonworks.com</value></param>\n" +
+            "    </provider>\n" +
+            "</gateway>\n";
+
+    private static final String TEST_PROVIDERS_CONFIG_2 =
+            "<gateway>\n" +
+            "    <provider>\n" +
+            "        <role>authentication</role>\n" +
+            "        <name>ShiroProvider</name>\n" +
+            "        <enabled>true</enabled>\n" +
+            "        <param>\n" +
+            "            <name>sessionTimeout</name>\n" +
+            "            <value>30</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapRealm</name>\n" +
+            "            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapRealm</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapContextFactory</name>\n" +
+            "            <value>org.apache.hadoop.gateway.shirorealm.KnoxLdapContextFactory</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapRealm.contextFactory</name>\n" +
+            "            <value>$ldapContextFactory</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapRealm.userDnTemplate</name>\n" +
+            "            <value>uid={0},ou=people,dc=hadoop,dc=apache,dc=org</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapRealm.contextFactory.url</name>\n" +
+            "            <value>ldap://localhost:33389</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>main.ldapRealm.contextFactory.authenticationMechanism</name>\n" +
+            "            <value>simple</value>\n" +
+            "        </param>\n" +
+            "        <param>\n" +
+            "            <name>urls./**</name>\n" +
+            "            <value>authcBasic</value>\n" +
+            "        </param>\n" +
+            "    </provider>\n" +
+            "</gateway>\n";
+
+    private static final String TEST_DESCRIPTOR_1 =
+            "{\n" +
+            "  \"discovery-type\":\"AMBARI\",\n" +
+            "  \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
+            "  \"discovery-user\":\"maria_dev\",\n" +
+            "  \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
+            "  \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
+            "  \"cluster\":\"Sandbox\",\n" +
+            "  \"services\":[\n" +
+            "    {\"name\":\"NODEUI\"},\n" +
+            "    {\"name\":\"YARNUI\"},\n" +
+            "    {\"name\":\"HDFSUI\"},\n" +
+            "    {\"name\":\"OOZIEUI\"},\n" +
+            "    {\"name\":\"HBASEUI\"},\n" +
+            "    {\"name\":\"NAMENODE\"},\n" +
+            "    {\"name\":\"JOBTRACKER\"},\n" +
+            "    {\"name\":\"WEBHDFS\"},\n" +
+            "    {\"name\":\"WEBHCAT\"},\n" +
+            "    {\"name\":\"OOZIE\"},\n" +
+            "    {\"name\":\"WEBHBASE\"},\n" +
+            "    {\"name\":\"RESOURCEMANAGER\"},\n" +
+            "    {\"name\":\"AMBARI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]},\n" +
+            "    {\"name\":\"AMBARIUI\", \"urls\":[\"http://c6401.ambari.apache.org:8080\"]}\n" +
+            "  ]\n" +
+            "}\n";
+
+    private static final String TEST_DESCRIPTOR_2 =
+            "{\n" +
+            "  \"discovery-type\":\"AMBARI\",\n" +
+            "  \"discovery-address\":\"http://sandbox.hortonworks.com:8080\",\n" +
+            "  \"discovery-user\":\"maria_dev\",\n" +
+            "  \"discovery-pwd-alias\":\"sandbox.ambari.discovery.password\",\n" +
+            "  \"provider-config-ref\":\"sandbox-providers.xml\",\n" +
+            "  \"cluster\":\"Sandbox\",\n" +
+            "  \"services\":[\n" +
+            "    {\"name\":\"NAMENODE\"},\n" +
+            "    {\"name\":\"JOBTRACKER\"},\n" +
+            "    {\"name\":\"WEBHDFS\"},\n" +
+            "    {\"name\":\"WEBHCAT\"},\n" +
+            "    {\"name\":\"OOZIE\"},\n" +
+            "    {\"name\":\"WEBHBASE\"},\n" +
+            "    {\"name\":\"RESOURCEMANAGER\"}\n" +
+            "  ]\n" +
+            "}\n";
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/util/KnoxCLITest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/util/KnoxCLITest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/util/KnoxCLITest.java
index 0352fa3..838f114 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/util/KnoxCLITest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/util/KnoxCLITest.java
@@ -20,8 +20,11 @@ package org.apache.hadoop.gateway.util;
 import com.mycila.xmltool.XMLDoc;
 import com.mycila.xmltool.XMLTag;
 import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.gateway.config.impl.GatewayConfigImpl;
 import org.apache.hadoop.gateway.services.GatewayServices;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
 import org.apache.hadoop.gateway.services.security.AliasService;
 import org.apache.hadoop.gateway.services.security.MasterService;
 import org.junit.Before;
@@ -42,6 +45,7 @@ import static org.hamcrest.CoreMatchers.notNullValue;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -60,7 +64,27 @@ public class KnoxCLITest {
   }
 
   @Test
-  public void testSuccessfulAlaisLifecycle() throws Exception {
+  public void testRemoteConfigurationRegistryClientService() throws Exception {
+    outContent.reset();
+    KnoxCLI cli = new KnoxCLI();
+    Configuration config = new GatewayConfigImpl();
+    config.set("gateway.remote.config.registry.test_client", "type=ZooKeeper;address=localhost:2181");
+    cli.setConf(config);
+
+    // This is only to get the gateway services initialized
+    cli.run(new String[]{"version"});
+
+    RemoteConfigurationRegistryClientService service =
+                                   cli.getGatewayServices().getService(GatewayServices.REMOTE_REGISTRY_CLIENT_SERVICE);
+    assertNotNull(service);
+    RemoteConfigurationRegistryClient client = service.get("test_client");
+    assertNotNull(client);
+
+    assertNull(service.get("bogus"));
+  }
+
+  @Test
+  public void testSuccessfulAliasLifecycle() throws Exception {
     outContent.reset();
     String[] args1 = {"create-alias", "alias1", "--value", "testvalue1", "--master", "master"};
     int rc = 0;

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/BadUrlTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/BadUrlTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/BadUrlTest.java
index 559b2a1..c465585 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/BadUrlTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/BadUrlTest.java
@@ -23,6 +23,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -205,6 +206,12 @@ public class BadUrlTest {
     EasyMock.expect(gatewayConfig.getGatewayTopologyDir())
         .andReturn(topoDir.toString()).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getGatewayProvidersConfigDir())
+            .andReturn(topoDir.getAbsolutePath() + "/shared-providers").anyTimes();
+
+    EasyMock.expect(gatewayConfig.getGatewayDescriptorsDir())
+            .andReturn(topoDir.getAbsolutePath() + "/descriptors").anyTimes();
+
     EasyMock.expect(gatewayConfig.getGatewayServicesDir())
         .andReturn(serviceUrl.getFile()).anyTimes();
 
@@ -247,6 +254,10 @@ public class BadUrlTest {
     EasyMock.expect(gatewayConfig.getWebsocketIdleTimeout())
         .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_IDLE_TIMEOUT).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getRemoteRegistryConfigurationNames())
+            .andReturn(Collections.emptyList())
+            .anyTimes();
+
     EasyMock.replay(gatewayConfig);
 
     try {

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketEchoTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketEchoTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketEchoTest.java
index 4b0fe08..5d5f280 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketEchoTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketEchoTest.java
@@ -26,6 +26,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -290,6 +291,12 @@ public class WebsocketEchoTest {
     EasyMock.expect(gatewayConfig.getGatewayTopologyDir())
         .andReturn(topoDir.toString()).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getGatewayProvidersConfigDir())
+            .andReturn(topoDir.getAbsolutePath() + "/shared-providers").anyTimes();
+
+    EasyMock.expect(gatewayConfig.getGatewayDescriptorsDir())
+            .andReturn(topoDir.getAbsolutePath() + "/descriptors").anyTimes();
+
     EasyMock.expect(gatewayConfig.getGatewayServicesDir())
         .andReturn(serviceUrl.getFile()).anyTimes();
 
@@ -332,6 +339,10 @@ public class WebsocketEchoTest {
     EasyMock.expect(gatewayConfig.getWebsocketIdleTimeout())
         .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_IDLE_TIMEOUT).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getRemoteRegistryConfigurationNames())
+            .andReturn(Collections.emptyList())
+            .anyTimes();
+
     EasyMock.replay(gatewayConfig);
 
     try {

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketMultipleConnectionTest.java
----------------------------------------------------------------------
diff --git a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketMultipleConnectionTest.java b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketMultipleConnectionTest.java
index 676c98c..7ddada2 100644
--- a/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketMultipleConnectionTest.java
+++ b/gateway-server/src/test/java/org/apache/hadoop/gateway/websockets/WebsocketMultipleConnectionTest.java
@@ -25,6 +25,7 @@ import java.lang.management.MemoryMXBean;
 import java.net.URI;
 import java.net.URL;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
 import java.util.List;
@@ -283,6 +284,12 @@ public class WebsocketMultipleConnectionTest {
     EasyMock.expect(gatewayConfig.getGatewayTopologyDir())
         .andReturn(topoDir.toString()).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getGatewayProvidersConfigDir())
+            .andReturn(topoDir.getAbsolutePath() + "/shared-providers").anyTimes();
+
+    EasyMock.expect(gatewayConfig.getGatewayDescriptorsDir())
+            .andReturn(topoDir.getAbsolutePath() + "/descriptors").anyTimes();
+
     EasyMock.expect(gatewayConfig.getGatewayServicesDir())
         .andReturn(serviceUrl.getFile()).anyTimes();
 
@@ -325,6 +332,10 @@ public class WebsocketMultipleConnectionTest {
     EasyMock.expect(gatewayConfig.getWebsocketIdleTimeout())
         .andReturn(GatewayConfigImpl.DEFAULT_WEBSOCKET_IDLE_TIMEOUT).anyTimes();
 
+    EasyMock.expect(gatewayConfig.getRemoteRegistryConfigurationNames())
+            .andReturn(Collections.emptyList())
+            .anyTimes();
+
     EasyMock.replay(gatewayConfig);
 
     try {

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/pom.xml
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/pom.xml b/gateway-service-remoteconfig/pom.xml
new file mode 100644
index 0000000..8d06360
--- /dev/null
+++ b/gateway-service-remoteconfig/pom.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.knox</groupId>
+        <artifactId>gateway</artifactId>
+        <version>1.0.0-SNAPSHOT</version>
+    </parent>
+    <artifactId>gateway-service-remoteconfig</artifactId>
+
+    <name>gateway-service-remoteconfig</name>
+    <description>The gateway service for interacting with remote configuration registries.</description>
+
+    <licenses>
+        <license>
+            <name>The Apache Software License, Version 2.0</name>
+            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <dependencies>
+        <dependency>
+            <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-spi</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.zookeeper</groupId>
+            <artifactId>zookeeper</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-framework</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>${gateway-group}</groupId>
+            <artifactId>gateway-test-utils</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+
+</project>

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationMessages.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationMessages.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationMessages.java
new file mode 100644
index 0000000..22e622d
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationMessages.java
@@ -0,0 +1,46 @@
+/**
+ * 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.hadoop.gateway.service.config.remote;
+
+import org.apache.hadoop.gateway.i18n.messages.Message;
+import org.apache.hadoop.gateway.i18n.messages.MessageLevel;
+import org.apache.hadoop.gateway.i18n.messages.Messages;
+import org.apache.hadoop.gateway.i18n.messages.StackTrace;
+
+
+/**
+ *
+ */
+@Messages(logger="org.apache.hadoop.gateway.service.config.remote")
+public interface RemoteConfigurationMessages {
+
+    @Message(level = MessageLevel.WARN,
+             text = "Multiple remote configuration registries are not currently supported if any of them requires authentication.")
+    void multipleRemoteRegistryConfigurations();
+
+    @Message(level = MessageLevel.ERROR, text = "Failed to resolve the credential alias {0}")
+    void unresolvedCredentialAlias(final String alias);
+
+    @Message(level = MessageLevel.ERROR, text = "An error occurred interacting with the remote configuration registry : {0}")
+    void errorInteractingWithRemoteConfigRegistry(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+    @Message(level = MessageLevel.ERROR, text = "An error occurred handling the ACL for remote configuration {0} : {1}")
+    void errorHandlingRemoteConfigACL(final String path,
+                                      @StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceFactory.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceFactory.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceFactory.java
new file mode 100644
index 0000000..cd58e22
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceFactory.java
@@ -0,0 +1,41 @@
+/**
+ * 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.hadoop.gateway.service.config.remote;
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+import java.util.ServiceLoader;
+
+public class RemoteConfigurationRegistryClientServiceFactory {
+
+    public static RemoteConfigurationRegistryClientService newInstance(GatewayConfig config) {
+        RemoteConfigurationRegistryClientService rcs = null;
+
+        ServiceLoader<RemoteConfigurationRegistryClientServiceProvider> providers =
+                                             ServiceLoader.load(RemoteConfigurationRegistryClientServiceProvider.class);
+        for (RemoteConfigurationRegistryClientServiceProvider provider : providers) {
+            rcs = provider.newInstance();
+            if (rcs != null) {
+                break;
+            }
+        }
+
+        return rcs;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceProvider.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceProvider.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceProvider.java
new file mode 100644
index 0000000..ddfc392
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryClientServiceProvider.java
@@ -0,0 +1,27 @@
+/**
+ * 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.hadoop.gateway.service.config.remote;
+
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+public interface RemoteConfigurationRegistryClientServiceProvider {
+
+    String getType();
+
+    RemoteConfigurationRegistryClientService newInstance();
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryConfig.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryConfig.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryConfig.java
new file mode 100644
index 0000000..6409250
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/RemoteConfigurationRegistryConfig.java
@@ -0,0 +1,43 @@
+/**
+ * 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.hadoop.gateway.service.config.remote;
+
+public interface RemoteConfigurationRegistryConfig {
+
+    String getName();
+
+    String getRegistryType();
+
+    String getConnectionString();
+
+    String getNamespace();
+
+    boolean isSecureRegistry();
+
+    String getAuthType(); // digest, kerberos, etc...
+
+    String getPrincipal();
+
+    String getCredentialAlias();
+
+    String getKeytab();
+
+    boolean isUseTicketCache();
+
+    boolean isUseKeyTab();
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistries.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistries.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistries.java
new file mode 100644
index 0000000..ebcae1b
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistries.java
@@ -0,0 +1,104 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A set of RemoteConfigurationRegistry configurations based on a set of property name-value pairs.
+ */
+class DefaultRemoteConfigurationRegistries extends RemoteConfigurationRegistries {
+
+    private static final String PROPERTY_DELIM       = ";";
+    private static final String PROPERTY_VALUE_DELIM = "=";
+
+    private List<RemoteConfigurationRegistry> configuredRegistries = new ArrayList<>();
+
+    /**
+     * Derive the remote registry configurations from the specified GatewayConfig.
+     *
+     * @param gc The source GatewayConfig
+     */
+    DefaultRemoteConfigurationRegistries(GatewayConfig gc) {
+        List<String> configRegistryNames = gc.getRemoteRegistryConfigurationNames();
+        for (String configRegistryName : configRegistryNames) {
+            configuredRegistries.add(extractConfigForRegistry(gc, configRegistryName));
+        }
+    }
+
+    /**
+     * Extract the configuration for the specified registry configuration name.
+     *
+     * @param gc           The GatewayConfig from which to extract the registry config.
+     * @param registryName The name of the registry config.
+     *
+     * @return The resulting RemoteConfigurationRegistry object, or null.
+     */
+    private static RemoteConfigurationRegistry extractConfigForRegistry(GatewayConfig gc, String registryName) {
+        RemoteConfigurationRegistry result = new RemoteConfigurationRegistry();
+
+        result.setName(registryName);
+
+        Map<String, String> properties = parsePropertyValue(gc.getRemoteRegistryConfiguration(registryName));
+
+        result.setRegistryType(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE));
+        result.setConnectionString(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS));
+        result.setNamespace(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_NAMESPACE));
+        result.setAuthType(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE));
+        result.setPrincipal(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL));
+        result.setCredentialAlias(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS));
+        result.setKeytab(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB));
+        result.setUseKeytab(Boolean.valueOf(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB)));
+        result.setUseTicketCache(Boolean.valueOf(properties.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE)));
+
+        return result;
+    }
+
+    /**
+     * Parse the specified registry config properties String.
+     *
+     * @param value The property value content from GatewayConfig.
+     *
+     * @return A Map of the parsed properties and their respective values.
+     */
+    private static Map<String, String> parsePropertyValue(final String value) {
+        Map<String, String> result = new HashMap<>();
+
+        if (value != null) {
+            String[] props = value.split(PROPERTY_DELIM);
+            for (String prop : props) {
+                String[] split = prop.split(PROPERTY_VALUE_DELIM);
+                String propName  = split[0];
+                String propValue = (split.length > 1) ? split[1] : null;
+                result.put(propName, propValue);
+            }
+        }
+
+        return result;
+    }
+
+    @Override
+    List<RemoteConfigurationRegistry> getRegistryConfigurations() {
+        return configuredRegistries;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistries.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistries.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistries.java
new file mode 100644
index 0000000..fa045c0
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistries.java
@@ -0,0 +1,33 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.ArrayList;
+import java.util.List;
+
+@XmlRootElement(name="remote-configuration-registries")
+class RemoteConfigurationRegistries {
+
+    private List<RemoteConfigurationRegistry> registryConfigurations = new ArrayList<>();
+
+    @XmlElement(name="remote-configuration-registry")
+    List<RemoteConfigurationRegistry> getRegistryConfigurations() {
+        return registryConfigurations;
+    }
+}


[2/3] knox git commit: KNOX-1107 - Remote Configuration Registry Client Service (Phil Zampino via lmccay)

Posted by lm...@apache.org.
http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java
new file mode 100644
index 0000000..9fed589
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesAccessor.java
@@ -0,0 +1,60 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RemoteConfigurationRegistriesAccessor {
+
+    // System property for specifying a reference to an XML configuration external to the gateway config
+    private static final String XML_CONFIG_REFERENCE_SYSTEM_PROPERTY_NAME =
+                                                                "org.apache.knox.gateway.remote.registry.config.file";
+
+
+    public static List<RemoteConfigurationRegistryConfig> getRemoteRegistryConfigurations(GatewayConfig gatewayConfig) {
+        List<RemoteConfigurationRegistryConfig> result = new ArrayList<>();
+
+        boolean useReferencedFile = false;
+
+        // First check for the system property pointing to a valid XML config for the remote registries
+        String remoteConfigRegistryConfigFilename = System.getProperty(XML_CONFIG_REFERENCE_SYSTEM_PROPERTY_NAME);
+        if (remoteConfigRegistryConfigFilename != null) {
+            File remoteConfigRegistryConfigFile = new File(remoteConfigRegistryConfigFilename);
+            if (remoteConfigRegistryConfigFile.exists()) {
+                useReferencedFile = true;
+                // Parse the file, and build the registry config set
+                result.addAll(RemoteConfigurationRegistriesParser.getConfig(remoteConfigRegistryConfigFilename));
+            }
+        }
+
+        // If the system property was not set to a valid reference to another config file, then try to derive the
+        // registry configurations from the gateway config.
+        if (!useReferencedFile) {
+            RemoteConfigurationRegistries remoteConfigRegistries =
+                                                            new DefaultRemoteConfigurationRegistries(gatewayConfig);
+            result.addAll(remoteConfigRegistries.getRegistryConfigurations());
+        }
+
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java
new file mode 100644
index 0000000..3ea71ef
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistriesParser.java
@@ -0,0 +1,48 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Unmarshaller;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+class RemoteConfigurationRegistriesParser {
+
+    static List<RemoteConfigurationRegistryConfig> getConfig(String configFilename) {
+        List<RemoteConfigurationRegistryConfig> result = new ArrayList<>();
+
+        File file = new File(configFilename);
+
+        try {
+            JAXBContext jaxbContext = JAXBContext.newInstance(RemoteConfigurationRegistries.class);
+            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
+            RemoteConfigurationRegistries parsedContent = (RemoteConfigurationRegistries) jaxbUnmarshaller.unmarshal(file);
+            if (parsedContent != null) {
+                result.addAll(parsedContent.getRegistryConfigurations());
+            }
+        } catch (JAXBException e) {
+            e.printStackTrace();
+        }
+
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java
new file mode 100644
index 0000000..f3e7dbd
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistry.java
@@ -0,0 +1,139 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+
+import javax.xml.bind.annotation.XmlElement;
+
+class RemoteConfigurationRegistry implements RemoteConfigurationRegistryConfig {
+
+    private String name;
+    private String type;
+    private String connectionString;
+    private String namespace;
+    private String authType;
+    private String principal;
+    private String credentialAlias;
+    private String keyTab;
+    private boolean useKeyTab;
+    private boolean useTicketCache;
+
+    RemoteConfigurationRegistry() {
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public void setRegistryType(String type) {
+        this.type = type;
+    }
+
+    public void setConnectionString(String connectionString) {
+        this.connectionString = connectionString;
+    }
+
+    public void setNamespace(String namespace) {
+        this.namespace = namespace;
+    }
+
+    public void setAuthType(String authType) {
+        this.authType = authType;
+    }
+
+    public void setPrincipal(String principal) {
+        this.principal = principal;
+    }
+
+    public void setCredentialAlias(String alias) {
+        this.credentialAlias = alias;
+    }
+
+    public void setUseTicketCache(boolean useTicketCache) {
+        this.useTicketCache = useTicketCache;
+    }
+
+    public void setUseKeytab(boolean useKeytab) {
+        this.useKeyTab = useKeytab;
+    }
+
+    public void setKeytab(String keytab) {
+        this.keyTab = keytab;
+    }
+
+    @XmlElement(name="name")
+    public String getName() {
+        return name;
+    }
+
+    @XmlElement(name="type")
+    public String getRegistryType() {
+        return type;
+    }
+
+    @XmlElement(name="auth-type")
+    public String getAuthType() {
+        return authType;
+    }
+
+    @XmlElement(name="principal")
+    public String getPrincipal() {
+        return principal;
+    }
+
+    @XmlElement(name="credential-alias")
+    public String getCredentialAlias() {
+        return credentialAlias;
+    }
+
+    @Override
+    @XmlElement(name="address")
+    public String getConnectionString() {
+        return connectionString;
+    }
+
+    @Override
+    @XmlElement(name="namespace")
+    public String getNamespace() {
+        return namespace;
+    }
+
+    @Override
+    @XmlElement(name="use-ticket-cache")
+    public boolean isUseTicketCache() {
+        return useTicketCache;
+    }
+
+    @Override
+    @XmlElement(name="use-key-tab")
+    public boolean isUseKeyTab() {
+        return useKeyTab;
+    }
+
+    @Override
+    @XmlElement(name="keytab")
+    public String getKeytab() {
+        return keyTab;
+    }
+
+    @Override
+    public boolean isSecureRegistry() {
+        return (getAuthType() != null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java
new file mode 100644
index 0000000..0908252
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/CuratorClientService.java
@@ -0,0 +1,423 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.framework.api.ACLProvider;
+import org.apache.curator.framework.imps.DefaultACLProvider;
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.framework.recipes.cache.NodeCache;
+import org.apache.curator.framework.recipes.cache.NodeCacheListener;
+import org.apache.curator.framework.recipes.cache.PathChildrenCache;
+import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
+import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
+import org.apache.curator.retry.ExponentialBackoffRetry;
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationMessages;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.EntryListener;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+import org.apache.hadoop.gateway.service.config.remote.config.RemoteConfigurationRegistriesAccessor;
+import org.apache.hadoop.gateway.services.ServiceLifecycleException;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.client.ZooKeeperSaslClient;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.data.Stat;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * RemoteConfigurationRegistryClientService implementation that employs the Curator ZooKeeper client framework.
+ */
+class CuratorClientService implements ZooKeeperClientService {
+
+    private static final String LOGIN_CONTEXT_NAME_PROPERTY = ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY;
+
+    private static final String DEFAULT_LOGIN_CONTEXT_NAME = "Client";
+
+    private static final RemoteConfigurationMessages log =
+                                                        MessagesFactory.get(RemoteConfigurationMessages.class);
+
+    private Map<String, RemoteConfigurationRegistryClient> clients = new HashMap<>();
+
+    private AliasService aliasService = null;
+
+
+    @Override
+    public void init(GatewayConfig config, Map<String, String> options) throws ServiceLifecycleException {
+
+        List<RemoteConfigurationRegistryConfig> registryConfigs = new ArrayList<>();
+
+        // Load the remote registry configurations
+        registryConfigs.addAll(RemoteConfigurationRegistriesAccessor.getRemoteRegistryConfigurations(config));
+
+        // Configure registry authentication
+        RemoteConfigurationRegistryJAASConfig.configure(registryConfigs, aliasService);
+
+        if (registryConfigs.size() > 1) {
+            // Warn about current limit on number of supported client configurations
+            log.multipleRemoteRegistryConfigurations();
+        }
+
+        // Create the clients
+        for (RemoteConfigurationRegistryConfig registryConfig : registryConfigs) {
+            if (TYPE.equalsIgnoreCase(registryConfig.getRegistryType())) {
+                RemoteConfigurationRegistryClient registryClient = createClient(registryConfig);
+                clients.put(registryConfig.getName(), registryClient);
+            }
+        }
+    }
+
+    @Override
+    public void setAliasService(AliasService aliasService) {
+        this.aliasService = aliasService;
+    }
+
+    @Override
+    public void start() throws ServiceLifecycleException {
+    }
+
+    @Override
+    public void stop() throws ServiceLifecycleException {
+    }
+
+    @Override
+    public RemoteConfigurationRegistryClient get(String name) {
+        return clients.get(name);
+    }
+
+
+    private RemoteConfigurationRegistryClient createClient(RemoteConfigurationRegistryConfig config) {
+        ACLProvider aclProvider;
+        if (config.isSecureRegistry()) {
+            configureSasl(config);
+            aclProvider = new SASLOwnerACLProvider(config.getPrincipal());
+        } else {
+            // Clear SASL system property
+            System.clearProperty(LOGIN_CONTEXT_NAME_PROPERTY);
+            aclProvider = new DefaultACLProvider();
+        }
+
+        CuratorFramework client = CuratorFrameworkFactory.builder()
+                                                         .connectString(config.getConnectionString())
+                                                         .retryPolicy(new ExponentialBackoffRetry(1000, 3))
+                                                         .aclProvider(aclProvider)
+                                                         .build();
+        client.start();
+
+        return (new ClientAdapter(client, config));
+    }
+
+
+    private void configureSasl(RemoteConfigurationRegistryConfig config) {
+        String registryName = config.getName();
+        if (registryName == null) {
+            registryName = DEFAULT_LOGIN_CONTEXT_NAME;
+        }
+        System.setProperty(LOGIN_CONTEXT_NAME_PROPERTY, registryName);
+    }
+
+
+    private static final class ClientAdapter implements RemoteConfigurationRegistryClient {
+
+        private static final String DEFAULT_ENCODING = "UTF-8";
+
+        private CuratorFramework delegate;
+
+        private RemoteConfigurationRegistryConfig config;
+
+        private Map<String, NodeCache> entryNodeCaches = new HashMap<>();
+
+        ClientAdapter(CuratorFramework delegate, RemoteConfigurationRegistryConfig config) {
+            this.delegate = delegate;
+            this.config = config;
+        }
+
+        @Override
+        public String getAddress() {
+            return config.getConnectionString();
+        }
+
+        @Override
+        public boolean entryExists(String path) {
+            Stat s = null;
+            try {
+                s = delegate.checkExists().forPath(path);
+            } catch (Exception e) {
+                // Ignore
+            }
+            return (s != null);
+        }
+
+        @Override
+        public List<RemoteConfigurationRegistryClient.EntryACL> getACL(String path) {
+            List<RemoteConfigurationRegistryClient.EntryACL> acl = new ArrayList<>();
+            try {
+                List<ACL> zkACL = delegate.getACL().forPath(path);
+                if (zkACL != null) {
+                    for (ACL aclEntry : zkACL) {
+                        RemoteConfigurationRegistryClient.EntryACL entryACL = new ZooKeeperACLAdapter(aclEntry);
+                        acl.add(entryACL);
+                    }
+                }
+            } catch (Exception e) {
+                log.errorHandlingRemoteConfigACL(path, e);
+            }
+            return acl;
+        }
+
+        @Override
+        public List<String> listChildEntries(String path) {
+            List<String> result = null;
+            try {
+                result = delegate.getChildren().forPath(path);
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+            return result;
+        }
+
+        @Override
+        public void addChildEntryListener(String path, ChildEntryListener listener) throws Exception {
+            PathChildrenCache childCache = new PathChildrenCache(delegate, path, false);
+            childCache.getListenable().addListener(new ChildEntryListenerAdapter(this, listener));
+            childCache.start();
+        }
+
+        @Override
+        public void addEntryListener(String path, EntryListener listener) throws Exception {
+            NodeCache nodeCache = new NodeCache(delegate, path);
+            nodeCache.getListenable().addListener(new EntryListenerAdapter(this, nodeCache, listener));
+            nodeCache.start();
+            entryNodeCaches.put(path, nodeCache);
+        }
+
+        @Override
+        public void removeEntryListener(String path) throws Exception {
+            NodeCache nodeCache = entryNodeCaches.remove(path);
+            if (nodeCache != null) {
+                nodeCache.close();
+            }
+        }
+
+        @Override
+        public String getEntryData(String path) {
+            return getEntryData(path, DEFAULT_ENCODING);
+        }
+
+        @Override
+        public String getEntryData(String path, String encoding) {
+            String result = null;
+            try {
+                byte[] data = delegate.getData().forPath(path);
+                if (data != null) {
+                    result = new String(data, Charset.forName(encoding));
+                }
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+            return result;
+        }
+
+        @Override
+        public void createEntry(String path) {
+            try {
+                if (delegate.checkExists().forPath(path) == null) {
+                    delegate.create().forPath(path);
+                }
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+        }
+
+        @Override
+        public void createEntry(String path, String data) {
+            createEntry(path, data, DEFAULT_ENCODING);
+        }
+
+        @Override
+        public void createEntry(String path, String data, String encoding) {
+            try {
+                createEntry(path);
+                setEntryData(path, data, encoding);
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+        }
+
+        @Override
+        public int setEntryData(String path, String data) {
+            return setEntryData(path, data, DEFAULT_ENCODING);
+        }
+
+        @Override
+        public int setEntryData(String path, String data, String encoding) {
+            int version = 0;
+            try {
+                Stat s = delegate.setData().forPath(path, data.getBytes(Charset.forName(encoding)));
+                if (s != null) {
+                    version = s.getVersion();
+                }
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+            return version;
+        }
+
+        @Override
+        public void deleteEntry(String path) {
+            try {
+                delegate.delete().forPath(path);
+            } catch (Exception e) {
+                log.errorInteractingWithRemoteConfigRegistry(e);
+            }
+        }
+    }
+
+    /**
+     * SASL ACLProvider
+     */
+    private static class SASLOwnerACLProvider implements ACLProvider {
+
+        private final List<ACL> saslACL;
+
+        private SASLOwnerACLProvider(String principal) {
+            this.saslACL = Collections.singletonList(new ACL(ZooDefs.Perms.ALL, new Id("sasl", principal)));
+        }
+
+        @Override
+        public List<ACL> getDefaultAcl() {
+            return saslACL;
+        }
+
+        @Override
+        public List<ACL> getAclForPath(String path) {
+            return getDefaultAcl();
+        }
+    }
+
+
+    private static final class ChildEntryListenerAdapter implements PathChildrenCacheListener {
+
+        private RemoteConfigurationRegistryClient client;
+        private ChildEntryListener delegate;
+
+        ChildEntryListenerAdapter(RemoteConfigurationRegistryClient client, ChildEntryListener delegate) {
+            this.client = client;
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent)
+                throws Exception {
+            ChildData childData = pathChildrenCacheEvent.getData();
+            if (childData != null) {
+                delegate.childEvent(client,
+                                    adaptType(pathChildrenCacheEvent.getType()),
+                                    childData.getPath());
+            }
+        }
+
+        private ChildEntryListener.Type adaptType(PathChildrenCacheEvent.Type type) {
+            ChildEntryListener.Type adapted = null;
+
+            switch(type) {
+                case CHILD_ADDED:
+                    adapted = ChildEntryListener.Type.ADDED;
+                    break;
+                case CHILD_REMOVED:
+                    adapted = ChildEntryListener.Type.REMOVED;
+                    break;
+                case CHILD_UPDATED:
+                    adapted = ChildEntryListener.Type.UPDATED;
+                    break;
+            }
+
+            return adapted;
+        }
+    }
+
+    private static final class EntryListenerAdapter implements NodeCacheListener {
+
+        private RemoteConfigurationRegistryClient client;
+        private EntryListener delegate;
+        private NodeCache nodeCache;
+
+        EntryListenerAdapter(RemoteConfigurationRegistryClient client, NodeCache nodeCache, EntryListener delegate) {
+            this.client = client;
+            this.nodeCache = nodeCache;
+            this.delegate = delegate;
+        }
+
+        @Override
+        public void nodeChanged() throws Exception {
+            String path = null;
+            byte[] data = null;
+
+            ChildData cd = nodeCache.getCurrentData();
+            if (cd != null) {
+                path = cd.getPath();
+                data = cd.getData();
+            }
+
+            if (path != null) {
+                delegate.entryChanged(client, path, data);
+            }
+        }
+    }
+
+    /**
+     * ACL adapter
+     */
+    private static final class ZooKeeperACLAdapter implements RemoteConfigurationRegistryClient.EntryACL {
+        private String type;
+        private String id;
+        private Object permissions;
+
+        ZooKeeperACLAdapter(ACL acl) {
+            this.permissions = acl.getPerms();
+            this.type = acl.getId().getScheme();
+            this.id = acl.getId().getId();
+        }
+
+        @Override
+        public String getId() {
+            return id;
+        }
+
+        @Override
+        public String getType() {
+            return type;
+        }
+
+        @Override
+        public Object getPermissions() {
+            return permissions;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
new file mode 100644
index 0000000..d51d7d5
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryJAASConfig.java
@@ -0,0 +1,169 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.hadoop.gateway.i18n.messages.MessagesFactory;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationMessages;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.hadoop.gateway.services.security.AliasServiceException;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Configuration decorator that adds SASL JAAS configuration to whatever JAAS config is already applied.
+ */
+class RemoteConfigurationRegistryJAASConfig extends Configuration {
+
+    // Underlying SASL mechanisms supported
+    enum SASLMechanism {
+        Kerberos,
+        Digest
+    }
+
+    static final Map<String, String> digestLoginModules = new HashMap<>();
+    static {
+        digestLoginModules.put("ZOOKEEPER", "org.apache.zookeeper.server.auth.DigestLoginModule");
+    }
+
+    private static final RemoteConfigurationMessages log = MessagesFactory.get(RemoteConfigurationMessages.class);
+
+    // Cache the current JAAS configuration
+    private Configuration delegate = Configuration.getConfiguration();
+
+    private AliasService aliasService;
+
+    private Map<String, AppConfigurationEntry[]> contextEntries =  new HashMap<>();
+
+    static RemoteConfigurationRegistryJAASConfig configure(List<RemoteConfigurationRegistryConfig> configs, AliasService aliasService) {
+        return new RemoteConfigurationRegistryJAASConfig(configs, aliasService);
+    }
+
+    private RemoteConfigurationRegistryJAASConfig(List<RemoteConfigurationRegistryConfig> configs, AliasService aliasService) {
+        this.aliasService = aliasService;
+
+        // Populate context entries
+        List<AppConfigurationEntry> appConfigEntries = new ArrayList<>();
+        for (RemoteConfigurationRegistryConfig config : configs) {
+            if (config.isSecureRegistry()) {
+                contextEntries.put(config.getName(), createEntries(config));
+            }
+        }
+
+        // If there is at least one context entry, then set this as the client configuration
+        if (!contextEntries.isEmpty()) {
+            // TODO: PJZ: ZooKeeper 3.6.0 will have per-client JAAS Configuration support; Upgrade ASAP!!
+            // For now, set this as the static JAAS configuration
+            Configuration.setConfiguration(this);
+        }
+    }
+
+    @Override
+    public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+        AppConfigurationEntry[] result = null;
+
+        // First, try the delegate's context entries
+        result = delegate.getAppConfigurationEntry(name);
+        if (result == null || result.length < 1) {
+            // Try our additional context entries
+            result = contextEntries.get(name);
+        }
+
+        return result;
+    }
+
+    private AppConfigurationEntry[] createEntries(RemoteConfigurationRegistryConfig config) {
+        // Only supporting a single app config entry per configuration/context
+        AppConfigurationEntry[] result = new AppConfigurationEntry[1];
+        result[0] = createEntry(config);
+        return result;
+    }
+
+    private AppConfigurationEntry createEntry(RemoteConfigurationRegistryConfig config) {
+        AppConfigurationEntry entry = null;
+
+        Map<String, String> opts = new HashMap<>();
+        SASLMechanism saslMechanism = getSASLMechanism(config.getAuthType());
+        switch (saslMechanism) {
+            case Digest:
+                // Digest auth options
+                opts.put("username", config.getPrincipal());
+
+                char[] credential = null;
+                if (aliasService != null) {
+                    try {
+                        credential = aliasService.getPasswordFromAliasForGateway(config.getCredentialAlias());
+                    } catch (AliasServiceException e) {
+                        log.unresolvedCredentialAlias(config.getCredentialAlias());
+                    }
+                } else {
+                    throw new IllegalArgumentException("The AliasService is required to resolve credential aliases.");
+                }
+
+                if (credential != null) {
+                    opts.put("password", new String(credential));
+                }
+                break;
+            case Kerberos:
+                opts.put("isUseTicketCache", String.valueOf(config.isUseTicketCache()));
+                opts.put("isUseKeyTab", String.valueOf(config.isUseKeyTab()));
+                opts.put("keyTab", config.getKeytab());
+                opts.put("principal", config.getPrincipal());
+        }
+
+        entry = new AppConfigurationEntry(getLoginModuleName(config.getRegistryType(), saslMechanism),
+                                          AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                                          opts);
+
+        return entry;
+    }
+
+    private static String getLoginModuleName(String registryType, SASLMechanism saslMechanism) {
+        String loginModuleName = null;
+
+        switch (saslMechanism) {
+            case Kerberos:
+                if (System.getProperty("java.vendor").contains("IBM")) {
+                    loginModuleName = "com.ibm.security.auth.module.Krb5LoginModule";
+                } else {
+                    loginModuleName = "com.sun.security.auth.module.Krb5LoginModule";
+                }
+                break;
+            case Digest:
+                loginModuleName = digestLoginModules.get(registryType.toUpperCase());
+        }
+        return loginModuleName;
+    }
+
+    private static SASLMechanism getSASLMechanism(String authType) {
+        SASLMechanism result = null;
+        for (SASLMechanism at : SASLMechanism.values()) {
+            if (at.name().equalsIgnoreCase(authType)) {
+                result = at;
+                break;
+            }
+        }
+        return result;
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java
new file mode 100644
index 0000000..c4add4a
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientService.java
@@ -0,0 +1,25 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+
+public interface ZooKeeperClientService extends RemoteConfigurationRegistryClientService {
+
+    String TYPE = "ZooKeeper";
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java
new file mode 100644
index 0000000..f30d3da
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/java/org/apache/hadoop/gateway/service/config/remote/zk/ZooKeeperClientServiceProvider.java
@@ -0,0 +1,34 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider;
+
+
+public class ZooKeeperClientServiceProvider implements RemoteConfigurationRegistryClientServiceProvider {
+
+    @Override
+    public String getType() {
+        return ZooKeeperClientService.TYPE;
+    }
+
+    @Override
+    public ZooKeeperClientService newInstance() {
+        return new CuratorClientService();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider b/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider
new file mode 100644
index 0000000..7f2312a
--- /dev/null
+++ b/gateway-service-remoteconfig/src/main/resources/META-INF/services/org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceProvider
@@ -0,0 +1,19 @@
+##########################################################################
+# 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
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+##########################################################################
+
+org.apache.hadoop.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java
new file mode 100644
index 0000000..a33fcc2
--- /dev/null
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/DefaultRemoteConfigurationRegistriesTest.java
@@ -0,0 +1,184 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.hadoop.gateway.config.GatewayConfig;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.junit.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+
+public class DefaultRemoteConfigurationRegistriesTest {
+
+    /**
+     * Test a single registry configuration with digest auth configuration.
+     */
+    @Test
+    public void testPropertiesRemoteConfigurationRegistriesSingleDigest() throws Exception {
+        Map<String, Properties> testProperties = new HashMap<>();
+        Properties p = new Properties();
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "hostx:2181");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "zkDigestUser");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "digest");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS, "zkDigestAlias");
+        testProperties.put("testDigest", p);
+
+        doTestPropertiesRemoteConfigurationRegistries(testProperties);
+    }
+
+
+    /**
+     * Test a single registry configuration with kerberos auth configuration.
+     */
+    @Test
+    public void testPropertiesRemoteConfigurationRegistriesSingleKerberos() throws Exception {
+        Map<String, Properties> testProperties = new HashMap<>();
+        Properties p = new Properties();
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "hostx:2181");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "zkUser");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "kerberos");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB, "/home/user/remoteregistry.keytab");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB, "true");
+        p.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE, "false");
+        testProperties.put("testKerb", p);
+
+        doTestPropertiesRemoteConfigurationRegistries(testProperties);
+    }
+
+    /**
+     * Test multiple registry configuration with varying auth configurations.
+     */
+    @Test
+    public void testPropertiesRemoteConfigurationRegistriesMultipleMixed() throws Exception {
+        Map<String, Properties> testProperties = new HashMap<>();
+
+        Properties kerb = new Properties();
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host1:2181");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_NAMESPACE, "/knox/config");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "kerbPrincipal");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "kerberos");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB, "/home/user/mykrb.keytab");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB, "true");
+        kerb.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE, "false");
+        testProperties.put("testKerb1", kerb);
+
+        Properties digest = new Properties();
+        digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper");
+        digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host2:2181");
+        digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL, "digestPrincipal");
+        digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE, "digest");
+        digest.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS, "digestPwdAlias");
+        testProperties.put("testDigest1", digest);
+
+        Properties unsecured = new Properties();
+        unsecured.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE, "ZooKeeper");
+        unsecured.setProperty(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS, "host2:2181");
+        testProperties.put("testUnsecured", unsecured);
+
+        doTestPropertiesRemoteConfigurationRegistries(testProperties);
+    }
+
+
+    /**
+     * Perform the actual test.
+     *
+     * @param testProperties The test properties
+     */
+    private void doTestPropertiesRemoteConfigurationRegistries(Map<String, Properties> testProperties) throws Exception {
+        // Mock gateway config
+        GatewayConfig gc = mockGatewayConfig(testProperties);
+
+        // Create the RemoteConfigurationRegistries object to be tested from the GatewayConfig
+        RemoteConfigurationRegistries registries = new DefaultRemoteConfigurationRegistries(gc);
+
+        // Basic validation
+        assertNotNull(registries);
+        List<RemoteConfigurationRegistry> registryConfigs = registries.getRegistryConfigurations();
+        assertNotNull(registryConfigs);
+        assertEquals(testProperties.size(), registryConfigs.size());
+
+        // Validate the contents of the created object
+        for (RemoteConfigurationRegistry regConfig : registryConfigs) {
+            validateRemoteRegistryConfig(regConfig.getName(), testProperties.get(regConfig.getName()), regConfig);
+        }
+    }
+
+
+    /**
+     * Create a mock GatewayConfig based on the specified test properties.
+     *
+     * @param testProperties The test properties to set on the config
+     */
+    private GatewayConfig mockGatewayConfig(Map<String, Properties> testProperties) {
+        // Mock gateway config
+        GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class);
+        List<String> configNames = new ArrayList<>();
+        for (String registryName : testProperties.keySet()) {
+            configNames.add(registryName);
+
+            String propertyValueString = "";
+            Properties props = testProperties.get(registryName);
+            Enumeration names = props.propertyNames();
+            while (names.hasMoreElements()) {
+                String propertyName = (String) names.nextElement();
+                propertyValueString += propertyName + "=" + props.get(propertyName);
+                if (names.hasMoreElements()) {
+                    propertyValueString += ";";
+                }
+            }
+            EasyMock.expect(gc.getRemoteRegistryConfiguration(registryName))
+                    .andReturn(propertyValueString)
+                    .anyTimes();
+        }
+        EasyMock.expect(gc.getRemoteRegistryConfigurationNames()).andReturn(configNames).anyTimes();
+        EasyMock.replay(gc);
+
+        return gc;
+    }
+
+
+    /**
+     * Validate the specified RemoteConfigurationRegistry based on the expected test properties.
+     */
+    private void validateRemoteRegistryConfig(String                      configName,
+                                              Properties                  expected,
+                                              RemoteConfigurationRegistry registryConfig) throws Exception {
+        assertEquals(configName, registryConfig.getName());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE), registryConfig.getRegistryType());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS), registryConfig.getConnectionString());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_NAMESPACE), registryConfig.getNamespace());
+        assertEquals(registryConfig.isSecureRegistry(), expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE) != null);
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE), registryConfig.getAuthType());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL), registryConfig.getPrincipal());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS), registryConfig.getCredentialAlias());
+        assertEquals(expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_KEYTAB), registryConfig.getKeytab());
+        assertEquals(Boolean.valueOf((String)expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_KEYTAB)), registryConfig.isUseKeyTab());
+        assertEquals(Boolean.valueOf((String)expected.get(GatewayConfig.REMOTE_CONFIG_REGISTRY_USE_TICKET_CACHE)), registryConfig.isUseTicketCache());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java
new file mode 100644
index 0000000..386e332
--- /dev/null
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/config/RemoteConfigurationRegistryConfigParserTest.java
@@ -0,0 +1,108 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.config;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryConfig;
+import org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import static org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils.*;
+
+public class RemoteConfigurationRegistryConfigParserTest {
+
+    @Test
+    public void testExternalXMLParsing() throws Exception {
+        final String CONN_STR = "http://my.zookeeper.host:2181";
+
+        Map<String, Map<String, String>> testRegistryConfigurations = new HashMap<>();
+
+        Map<String, String> config1 = new HashMap<>();
+        config1.put(PROPERTY_TYPE, "ZooKeeper");
+        config1.put(PROPERTY_NAME, "registry1");
+        config1.put(PROPERTY_ADDRESS, CONN_STR);
+        config1.put(PROPERTY_SECURE, "true");
+        config1.put(PROPERTY_AUTH_TYPE, "Digest");
+        config1.put(PROPERTY_PRINCIPAL, "knox");
+        config1.put(PROPERTY_CRED_ALIAS, "zkCredential");
+        testRegistryConfigurations.put(config1.get("name"), config1);
+
+        Map<String, String> config2 = new HashMap<>();
+        config2.put(PROPERTY_TYPE, "ZooKeeper");
+        config2.put(PROPERTY_NAME, "MyKerberos");
+        config2.put(PROPERTY_ADDRESS, CONN_STR);
+        config2.put(PROPERTY_SECURE, "true");
+        config2.put(PROPERTY_AUTH_TYPE, "Kerberos");
+        config2.put(PROPERTY_PRINCIPAL, "knox");
+        File myKeyTab = File.createTempFile("mytest", "keytab");
+        config2.put(PROPERTY_KEYTAB, myKeyTab.getAbsolutePath());
+        config2.put(PROPERTY_USE_KEYTAB, "false");
+        config2.put(PROPERTY_USE_TICKET_CACHE, "true");
+        testRegistryConfigurations.put(config2.get("name"), config2);
+
+        Map<String, String> config3 = new HashMap<>();
+        config3.put(PROPERTY_TYPE, "ZooKeeper");
+        config3.put(PROPERTY_NAME, "anotherRegistry");
+        config3.put(PROPERTY_ADDRESS, "whatever:1281");
+        testRegistryConfigurations.put(config3.get("name"), config3);
+
+        String configXML =
+                    RemoteRegistryConfigTestUtils.createRemoteConfigRegistriesXML(testRegistryConfigurations.values());
+
+        File registryConfigFile = File.createTempFile("remote-registries", "xml");
+        try {
+            FileUtils.writeStringToFile(registryConfigFile, configXML);
+
+            List<RemoteConfigurationRegistryConfig> configs =
+                                    RemoteConfigurationRegistriesParser.getConfig(registryConfigFile.getAbsolutePath());
+            assertNotNull(configs);
+            assertEquals(testRegistryConfigurations.keySet().size(), configs.size());
+
+            for (RemoteConfigurationRegistryConfig registryConfig : configs) {
+                Map<String, String> expected = testRegistryConfigurations.get(registryConfig.getName());
+                assertNotNull(expected);
+                validateParsedRegistryConfiguration(registryConfig, expected);
+            }
+        } finally {
+            registryConfigFile.delete();
+        }
+    }
+
+    private void validateParsedRegistryConfiguration(RemoteConfigurationRegistryConfig config,
+                                                     Map<String, String> expected) throws Exception {
+        assertEquals(expected.get(PROPERTY_TYPE), config.getRegistryType());
+        assertEquals(expected.get(PROPERTY_ADDRESS), config.getConnectionString());
+        assertEquals(expected.get(PROPERTY_NAME), config.getName());
+        assertEquals(expected.get(PROPERTY_NAMESAPCE), config.getNamespace());
+        assertEquals(Boolean.valueOf(expected.get(PROPERTY_SECURE)), config.isSecureRegistry());
+        assertEquals(expected.get(PROPERTY_AUTH_TYPE), config.getAuthType());
+        assertEquals(expected.get(PROPERTY_PRINCIPAL), config.getPrincipal());
+        assertEquals(expected.get(PROPERTY_CRED_ALIAS), config.getCredentialAlias());
+        assertEquals(expected.get(PROPERTY_KEYTAB), config.getKeytab());
+        assertEquals(Boolean.valueOf(expected.get(PROPERTY_USE_KEYTAB)), config.isUseKeyTab());
+        assertEquals(Boolean.valueOf(expected.get(PROPERTY_USE_TICKET_CACHE)), config.isUseTicketCache());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java
new file mode 100644
index 0000000..35919d0
--- /dev/null
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/util/RemoteRegistryConfigTestUtils.java
@@ -0,0 +1,117 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.util;
+
+import java.util.Collection;
+import java.util.Map;
+
+public class RemoteRegistryConfigTestUtils {
+
+    public static final String PROPERTY_TYPE = "type";
+    public static final String PROPERTY_NAME = "name";
+    public static final String PROPERTY_ADDRESS = "address";
+    public static final String PROPERTY_NAMESAPCE = "namespace";
+    public static final String PROPERTY_SECURE = "secure";
+    public static final String PROPERTY_AUTH_TYPE = "authType";
+    public static final String PROPERTY_PRINCIPAL = "principal";
+    public static final String PROPERTY_CRED_ALIAS = "credentialAlias";
+    public static final String PROPERTY_KEYTAB = "keyTab";
+    public static final String PROPERTY_USE_KEYTAB = "useKeyTab";
+    public static final String PROPERTY_USE_TICKET_CACHE = "useTicketCache";
+
+    public static String createRemoteConfigRegistriesXML(Collection<Map<String, String>> configProperties) {
+        String result = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
+                        "<remote-configuration-registries>\n";
+
+        for (Map<String, String> props : configProperties) {
+            String authType = props.get(PROPERTY_AUTH_TYPE);
+            if ("Kerberos".equalsIgnoreCase(authType)) {
+                result +=
+                   createRemoteConfigRegistryXMLWithKerberosAuth(props.get(PROPERTY_TYPE),
+                                                                 props.get(PROPERTY_NAME),
+                                                                 props.get(PROPERTY_ADDRESS),
+                                                                 props.get(PROPERTY_PRINCIPAL),
+                                                                 props.get(PROPERTY_KEYTAB),
+                                                                 Boolean.valueOf(props.get(PROPERTY_USE_KEYTAB)),
+                                                                 Boolean.valueOf(props.get(PROPERTY_USE_TICKET_CACHE)));
+            } else if ("Digest".equalsIgnoreCase(authType)) {
+                result +=
+                    createRemoteConfigRegistryXMLWithDigestAuth(props.get(PROPERTY_TYPE),
+                                                                props.get(PROPERTY_NAME),
+                                                                props.get(PROPERTY_ADDRESS),
+                                                                props.get(PROPERTY_PRINCIPAL),
+                                                                props.get(PROPERTY_CRED_ALIAS));
+            } else {
+                result += createRemoteConfigRegistryXMLNoAuth(props.get(PROPERTY_TYPE),
+                                                              props.get(PROPERTY_NAME),
+                                                              props.get(PROPERTY_ADDRESS));
+            }
+        }
+
+        result += "</remote-configuration-registries>\n";
+
+        return result;
+    }
+
+    public static String createRemoteConfigRegistryXMLWithKerberosAuth(String type,
+                                                                       String name,
+                                                                       String address,
+                                                                       String principal,
+                                                                       String keyTab,
+                                                                       boolean userKeyTab,
+                                                                       boolean useTicketCache) {
+        return "  <remote-configuration-registry>\n" +
+               "    <name>" + name + "</name>\n" +
+               "    <type>" + type + "</type>\n" +
+               "    <address>" + address + "</address>\n" +
+               "    <secure>true</secure>\n" +
+               "    <auth-type>" + "Kerberos" + "</auth-type>\n" +
+               "    <principal>" + principal + "</principal>\n" +
+               "    <keytab>" + keyTab + "</keytab>\n" +
+               "    <use-keytab>" + String.valueOf(userKeyTab) + "</use-keytab>\n" +
+               "    <use-ticket-cache>" + String.valueOf(useTicketCache) + "</use-ticket-cache>\n" +
+               "  </remote-configuration-registry>\n";
+    }
+
+    public static String createRemoteConfigRegistryXMLWithDigestAuth(String type,
+                                                                     String name,
+                                                                     String address,
+                                                                     String principal,
+                                                                     String credentialAlias) {
+        return "  <remote-configuration-registry>\n" +
+               "    <name>" + name + "</name>\n" +
+               "    <type>" + type + "</type>\n" +
+               "    <address>" + address + "</address>\n" +
+               "    <secure>true</secure>\n" +
+               "    <auth-type>" + "Digest" + "</auth-type>\n" +
+               "    <principal>" + principal + "</principal>\n" +
+               "    <credential-alias>" + credentialAlias + "</credential-alias>\n" +
+               "  </remote-configuration-registry>\n";
+    }
+
+
+    public static String createRemoteConfigRegistryXMLNoAuth(String type,
+                                                             String name,
+                                                             String address) {
+        return "  <remote-configuration-registry>\n" +
+               "    <name>" + name + "</name>\n" +
+               "    <type>" + type + "</type>\n" +
+               "    <address>" + address + "</address>\n" +
+               "  </remote-configuration-registry>\n";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/knox/blob/5af2413c/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java
----------------------------------------------------------------------
diff --git a/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java
new file mode 100644
index 0000000..0292ee3
--- /dev/null
+++ b/gateway-service-remoteconfig/src/test/java/org/apache/hadoop/gateway/service/config/remote/zk/RemoteConfigurationRegistryClientServiceTest.java
@@ -0,0 +1,424 @@
+/**
+ * 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.hadoop.gateway.service.config.remote.zk;
+
+import org.apache.commons.io.FileUtils;
+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.hadoop.gateway.config.GatewayConfig;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient.ChildEntryListener;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClient;
+import org.apache.hadoop.gateway.services.config.client.RemoteConfigurationRegistryClientService;
+import org.apache.hadoop.gateway.service.config.remote.RemoteConfigurationRegistryClientServiceFactory;
+import org.apache.hadoop.gateway.service.config.remote.util.RemoteRegistryConfigTestUtils;
+import org.apache.hadoop.gateway.services.security.AliasService;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class RemoteConfigurationRegistryClientServiceTest {
+
+    /**
+     * Test a configuration for an unsecured remote registry, included in the gateway configuration.
+     */
+    @Test
+    public void testUnsecuredZooKeeperWithSimpleRegistryConfig() throws Exception {
+        final String REGISTRY_CLIENT_NAME = "unsecured-zk-registry-name";
+        final String PRINCIPAL = null;
+        final String PWD = null;
+        final String CRED_ALIAS = null;
+
+        // Configure and start a secure ZK cluster
+        TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD);
+
+        try {
+            // Create the setup client for the test cluster, and initialize the test znodes
+            CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL);
+
+            // Mock configuration
+            GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+            final String registryConfigValue =
+                        GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                        GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
+            EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME))
+                    .andReturn(registryConfigValue)
+                    .anyTimes();
+            EasyMock.expect(config.getRemoteRegistryConfigurationNames())
+                    .andReturn(Collections.singletonList(REGISTRY_CLIENT_NAME)).anyTimes();
+            EasyMock.replay(config);
+
+            doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD);
+        } finally {
+            zkCluster.stop();
+        }
+    }
+
+    /**
+     * Test multiple configurations for an unsecured remote registry.
+     */
+    @Test
+    public void testMultipleUnsecuredZooKeeperWithSimpleRegistryConfig() throws Exception {
+        final String REGISTRY_CLIENT_NAME_1 = "zkclient1";
+        final String REGISTRY_CLIENT_NAME_2 = "zkclient2";
+        final String PRINCIPAL = null;
+        final String PWD = null;
+        final String CRED_ALIAS = null;
+
+        // Configure and start a secure ZK cluster
+        TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD);
+
+        try {
+            // Create the setup client for the test cluster, and initialize the test znodes
+            CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL);
+
+            // Mock configuration
+            GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+            final String registryConfigValue1 =
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
+            EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME_1))
+                    .andReturn(registryConfigValue1).anyTimes();
+            final String registryConfigValue2 =
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                                GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString();
+            EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME_2))
+                    .andReturn(registryConfigValue2).anyTimes();
+            EasyMock.expect(config.getRemoteRegistryConfigurationNames())
+                    .andReturn(Arrays.asList(REGISTRY_CLIENT_NAME_1, REGISTRY_CLIENT_NAME_2)).anyTimes();
+            EasyMock.replay(config);
+
+            // Create the client service instance
+            RemoteConfigurationRegistryClientService clientService =
+                    RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
+            assertEquals("Wrong registry client service type.", clientService.getClass(), CuratorClientService.class);
+            clientService.setAliasService(null);
+            clientService.init(config, null);
+            clientService.start();
+
+            RemoteConfigurationRegistryClient client1 = clientService.get(REGISTRY_CLIENT_NAME_1);
+            assertNotNull(client1);
+
+            RemoteConfigurationRegistryClient client2 = clientService.get(REGISTRY_CLIENT_NAME_2);
+            assertNotNull(client2);
+
+            doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME_1, clientService, false);
+            doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME_2, clientService, false);
+        } finally {
+            zkCluster.stop();
+        }
+    }
+
+    /**
+     * Test a configuration for a secure remote registry, included in the gateway configuration.
+     */
+    @Test
+    public void testZooKeeperWithSimpleRegistryConfig() throws Exception {
+        final String AUTH_TYPE = "digest";
+        final String REGISTRY_CLIENT_NAME = "zk-registry-name";
+        final String PRINCIPAL = "knox";
+        final String PWD = "knoxtest";
+        final String CRED_ALIAS = "zkCredential";
+
+        // Configure and start a secure ZK cluster
+        TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD);
+
+        try {
+            // Create the setup client for the test cluster, and initialize the test znodes
+            CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL);
+
+            // Mock configuration
+            GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+            final String registryConfigValue =
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "=" + ZooKeeperClientService.TYPE + ";" +
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkCluster.getConnectString() + ";" +
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_AUTH_TYPE + "=" + AUTH_TYPE + ";" +
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_PRINCIPAL + "=" + PRINCIPAL + ";" +
+                            GatewayConfig.REMOTE_CONFIG_REGISTRY_CREDENTIAL_ALIAS + "=" + CRED_ALIAS;
+            EasyMock.expect(config.getRemoteRegistryConfiguration(REGISTRY_CLIENT_NAME))
+                    .andReturn(registryConfigValue)
+                    .anyTimes();
+            EasyMock.expect(config.getRemoteRegistryConfigurationNames())
+                    .andReturn(Collections.singletonList(REGISTRY_CLIENT_NAME)).anyTimes();
+            EasyMock.replay(config);
+
+            doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD);
+        } finally {
+            zkCluster.stop();
+        }
+    }
+
+    /**
+     * Test the remote registry configuration external to, and referenced from, the gateway configuration, for a secure
+     * client.
+     */
+    @Test
+    public void testZooKeeperWithSingleExternalRegistryConfig() throws Exception {
+        final String AUTH_TYPE = "digest";
+        final String REGISTRY_CLIENT_NAME = "my-zookeeper_registryNAME";
+        final String PRINCIPAL = "knox";
+        final String PWD = "knoxtest";
+        final String CRED_ALIAS = "zkCredential";
+
+        // Configure and start a secure ZK cluster
+        TestingCluster zkCluster = setupAndStartSecureTestZooKeeper(PRINCIPAL, PWD);
+
+        File tmpRegConfigFile = null;
+
+        try {
+            // Create the setup client for the test cluster, and initialize the test znodes
+            CuratorFramework setupClient = initializeTestClientAndZNodes(zkCluster, PRINCIPAL);
+
+            // Mock configuration
+            Map<String, String> registryConfigProps = new HashMap<>();
+            registryConfigProps.put("type", ZooKeeperClientService.TYPE);
+            registryConfigProps.put("name", REGISTRY_CLIENT_NAME);
+            registryConfigProps.put("address", zkCluster.getConnectString());
+            registryConfigProps.put("secure", "true");
+            registryConfigProps.put("authType", AUTH_TYPE);
+            registryConfigProps.put("principal", PRINCIPAL);
+            registryConfigProps.put("credentialAlias", CRED_ALIAS);
+            String registryConfigXML =
+                  RemoteRegistryConfigTestUtils.createRemoteConfigRegistriesXML(Collections.singleton(registryConfigProps));
+            tmpRegConfigFile = File.createTempFile("myRemoteRegistryConfig", "xml");
+            FileUtils.writeStringToFile(tmpRegConfigFile, registryConfigXML);
+
+            System.setProperty("org.apache.knox.gateway.remote.registry.config.file", tmpRegConfigFile.getAbsolutePath());
+
+            GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+            EasyMock.replay(config);
+
+            doTestZooKeeperClient(setupClient, REGISTRY_CLIENT_NAME, config, CRED_ALIAS, PWD);
+        } finally {
+            zkCluster.stop();
+            if (tmpRegConfigFile != null && tmpRegConfigFile.exists()) {
+                tmpRegConfigFile.delete();
+            }
+            System.clearProperty("org.apache.knox.gateway.remote.registry.config.file");
+        }
+    }
+
+    /**
+     * Setup and start a secure test ZooKeeper cluster.
+     */
+    private TestingCluster setupAndStartSecureTestZooKeeper(String principal, String digestPassword) throws Exception {
+        final boolean applyAuthentication = (principal != null);
+
+        // Configure security for the ZK cluster instances
+        Map<String, Object> customInstanceSpecProps = new HashMap<>();
+
+        if (applyAuthentication) {
+            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);
+        }
+        TestingCluster zkCluster = new TestingCluster(instanceSpecs);
+
+        if (applyAuthentication) {
+            // Setup ZooKeeper server SASL
+            Map<String, String> digestOptions = new HashMap<>();
+            digestOptions.put("user_" + principal, digestPassword);
+            final AppConfigurationEntry[] serverEntries =
+                    {new AppConfigurationEntry("org.apache.zookeeper.server.auth.DigestLoginModule",
+                            AppConfigurationEntry.LoginModuleControlFlag.REQUIRED,
+                            digestOptions)};
+            Configuration.setConfiguration(new Configuration() {
+                @Override
+                public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+                    return ("Server".equalsIgnoreCase(name)) ? serverEntries : null;
+                }
+            });
+        }
+
+        // Start the cluster
+        zkCluster.start();
+
+        return zkCluster;
+    }
+
+    /**
+     * Create a ZooKeeper client with SASL digest auth configured, and initialize the test znodes.
+     */
+    private CuratorFramework initializeTestClientAndZNodes(TestingCluster zkCluster, String principal) throws Exception {
+        // Create the client for the test cluster
+        CuratorFramework setupClient = CuratorFrameworkFactory.builder()
+                                                              .connectString(zkCluster.getConnectString())
+                                                              .retryPolicy(new ExponentialBackoffRetry(100, 3))
+                                                              .build();
+        assertNotNull(setupClient);
+        setupClient.start();
+
+        List<ACL> acls = new ArrayList<>();
+        if (principal != null) {
+            acls.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", principal)));
+        } else {
+            acls.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE));
+        }
+        setupClient.create().creatingParentsIfNeeded().withACL(acls).forPath("/knox/config/descriptors");
+        setupClient.create().creatingParentsIfNeeded().withACL(acls).forPath("/knox/config/shared-providers");
+
+        List<ACL> negativeACLs = new ArrayList<>();
+        if (principal != null) {
+            negativeACLs.add(new ACL(ZooDefs.Perms.ALL, new Id("sasl", "notyou")));
+        } else {
+            negativeACLs.add(new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.ANYONE_ID_UNSAFE));
+        }
+        setupClient.create().creatingParentsIfNeeded().withACL(negativeACLs).forPath("/someotherconfig");
+
+        return setupClient;
+    }
+
+    private void doTestZooKeeperClient(final CuratorFramework setupClient,
+                                       final String           testClientName,
+                                       final GatewayConfig    config,
+                                       final String           credentialAlias,
+                                       final String           digestPassword) throws Exception {
+        boolean isSecureTest = (credentialAlias != null && digestPassword != null);
+
+        // Mock alias service
+        AliasService aliasService = EasyMock.createNiceMock(AliasService.class);
+        EasyMock.expect(aliasService.getPasswordFromAliasForGateway(credentialAlias))
+                .andReturn(isSecureTest ? digestPassword.toCharArray() : null)
+                .anyTimes();
+        EasyMock.replay(aliasService);
+
+        // Create the client service instance
+        RemoteConfigurationRegistryClientService clientService =
+                RemoteConfigurationRegistryClientServiceFactory.newInstance(config);
+        assertEquals("Wrong registry client service type.", clientService.getClass(), CuratorClientService.class);
+        clientService.setAliasService(aliasService);
+        clientService.init(config, null);
+        clientService.start();
+
+        doTestZooKeeperClient(setupClient, testClientName, clientService, isSecureTest);
+    }
+
+    /**
+     * Test secure ZooKeeper client interactions.
+     *
+     * @param setupClient    The client used for interacting with ZooKeeper independent from the registry client service.
+     * @param testClientName The name of the client to use from the registry client service.
+     * @param clientService  The RemoteConfigurationRegistryClientService
+     * @param isSecureTest   Flag to indicate whether this is a secure interaction test
+     */
+    private void doTestZooKeeperClient(final CuratorFramework                         setupClient,
+                                       final String                                   testClientName,
+                                       final RemoteConfigurationRegistryClientService clientService,
+                                       boolean                                        isSecureTest) throws Exception {
+
+        RemoteConfigurationRegistryClient client = clientService.get(testClientName);
+        assertNotNull(client);
+        List<String> descriptors = client.listChildEntries("/knox/config/descriptors");
+        assertNotNull(descriptors);
+        for (String descriptor : descriptors) {
+            System.out.println("Descriptor: " + descriptor);
+        }
+
+        List<String> providerConfigs = client.listChildEntries("/knox/config/shared-providers");
+        assertNotNull(providerConfigs);
+        for (String providerConfig : providerConfigs) {
+            System.out.println("Provider config: " + providerConfig);
+        }
+
+        List<String> someotherConfig = client.listChildEntries("/someotherconfig");
+        if (isSecureTest) {
+            assertNull("Expected null because of the ACL mismatch.", someotherConfig);
+        } else {
+            assertNotNull(someotherConfig);
+        }
+
+        // Test listeners
+        final String MY_NEW_ZNODE = "/clientServiceTestNode";
+        final String MY_NEW_DATA_ZNODE = MY_NEW_ZNODE + "/mydata";
+
+        if (setupClient.checkExists().forPath(MY_NEW_ZNODE) != null) {
+            setupClient.delete().deletingChildrenIfNeeded().forPath(MY_NEW_ZNODE);
+        }
+
+        final List<String> listenerLog = new ArrayList<>();
+        client.addChildEntryListener(MY_NEW_ZNODE, (c, type, path) -> {
+            listenerLog.add("EXTERNAL: " + type.toString() + ":" + path);
+            if (ChildEntryListener.Type.ADDED.equals(type)) {
+                try {
+                    c.addEntryListener(path, (cc, p, d) -> listenerLog.add("EXTERNAL: " + p + ":" + (d != null ? new String(d) : "null")));
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        });
+
+        client.createEntry(MY_NEW_ZNODE);
+        client.createEntry(MY_NEW_DATA_ZNODE, "more test data");
+        String testData = client.getEntryData(MY_NEW_DATA_ZNODE);
+        assertNotNull(testData);
+        assertEquals("more test data", testData);
+
+        assertTrue(client.entryExists(MY_NEW_DATA_ZNODE));
+        client.setEntryData(MY_NEW_DATA_ZNODE, "still more data");
+
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            //
+        }
+
+        client.setEntryData(MY_NEW_DATA_ZNODE, "changed completely");
+
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            //
+        }
+
+        client.deleteEntry(MY_NEW_DATA_ZNODE);
+
+        try {
+            Thread.sleep(1000);
+        } catch (InterruptedException e) {
+            //
+        }
+
+        assertFalse(listenerLog.isEmpty());
+    }
+
+}