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>
