blob: 7b190daf30f0ddaaa6ff7bce2824775cdc6576e8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.services.security.impl;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.test.InstanceSpec;
import org.apache.curator.test.TestingCluster;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.ACL;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.easymock.EasyMock.capture;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Test the listener/monitor service for {@link ZookeeperRemoteAliasService}.
*/
public class ZookeeperRemoteAliasMonitorTest {
private static final String configMonitorName = "remoteConfigMonitorClient";
private static final String expectedClusterName = "sandbox";
private static final String expectedAlias = "knox.test.alias";
private static final String expectedPassword = "dummyPassword";
private static final String expectedClusterNameDev = "development";
private static final String expectedAliasDev = "knox.test.alias.dev";
private static final String expectedPasswordDev = "otherDummyPassword";
private static final String preferRemoteAlias = "prefer.remote.alias";
private static final String preferRemoteAliasEncryptedPassword = "QmgrK2JBRlE1MUU9OjpIYzZlVUttKzdaWkFOSjlYZVVyVzNRPT06Om5kdTQ3WTJ1by9vSHprZUZHcjBqVG5TaGxsMFVUdUNyN0EvUlZDV1ZHQUU9";
private static final String preferRemoteAliasClearPassword = "ApacheKnoxPassword123";
private static TestingCluster zkNodes;
private static CuratorFramework client;
@BeforeClass
public static void setupSuite() throws Exception {
// Configure security for the ZK cluster instances
Map<String, Object> customInstanceSpecProps = new HashMap<>();
customInstanceSpecProps.put("authProvider.1",
"org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
customInstanceSpecProps.put("requireClientAuthScheme", "sasl");
customInstanceSpecProps.put("admin.enableServer", false);
// Define the test cluster
List<InstanceSpec> instanceSpecs = new ArrayList<>();
for (int i = 0; i < 1; i++) {
InstanceSpec is = new InstanceSpec(null, -1, -1, -1, false, (i + 1), -1,
-1, customInstanceSpecProps);
instanceSpecs.add(is);
}
zkNodes = new TestingCluster(instanceSpecs);
zkNodes.start();
// Create the client for the test cluster
client = CuratorFrameworkFactory.builder()
.connectString(zkNodes.getConnectString())
.retryPolicy(new ExponentialBackoffRetry(100, 3)).build();
assertNotNull(client);
client.start();
assertTrue(client.blockUntilConnected(10, TimeUnit.SECONDS));
// 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(
ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterName);
assertNotNull("Failed to create node:"
+ ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+ ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterName, client.checkExists().forPath(
ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterName));
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT)
.withACL(acls).forPath(
ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterNameDev);
assertNotNull("Failed to create node:"
+ ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+ ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterNameDev, client.checkExists().forPath(
ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY + ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterNameDev));
/* Start Zookeeper with an existing alias */
client.create().withMode(CreateMode.PERSISTENT).
forPath(ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+ ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterName
+ ZookeeperRemoteAliasService.PATH_SEPARATOR + preferRemoteAlias,
preferRemoteAliasEncryptedPassword.getBytes(StandardCharsets.UTF_8));
}
@AfterClass
public static void tearDownSuite() throws Exception {
// Clean up the ZK nodes, and close the client
if (client != null) {
client.delete().deletingChildrenIfNeeded()
.forPath(ZookeeperRemoteAliasService.PATH_KNOX_SECURITY);
client.close();
}
// Shutdown the ZK cluster
zkNodes.close();
}
@Test
public void testListener() throws Exception {
// Setup the base GatewayConfig mock
GatewayConfig gc = EasyMock.createNiceMock(GatewayConfig.class);
EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
.andReturn(Collections.singletonList(configMonitorName)).anyTimes();
final String registryConfig =
GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "="
+ ZooKeeperClientService.TYPE + ";"
+ GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkNodes
.getConnectString();
EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
.andReturn(registryConfig).anyTimes();
EasyMock.expect(gc.getRemoteConfigurationMonitorClientName())
.andReturn(configMonitorName).anyTimes();
EasyMock.expect(gc.isRemoteAliasServiceEnabled())
.andReturn(true).anyTimes();
EasyMock.replay(gc);
// Mock Alias Service
final DefaultAliasService defaultAlias = EasyMock
.createNiceMock(DefaultAliasService.class);
// Captures for validating the alias creation for a generated topology
final Capture<String> capturedCluster = EasyMock.newCapture();
final Capture<String> capturedAlias = EasyMock.newCapture();
final Capture<String> capturedPwd = EasyMock.newCapture();
defaultAlias
.addAliasForCluster(capture(capturedCluster), capture(capturedAlias),
capture(capturedPwd));
EasyMock.expectLastCall().anyTimes();
/* defaultAlias.getAliasesForCluster() never returns null */
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterName))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterNameDev))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.expect(defaultAlias.getPasswordFromAliasForCluster(expectedClusterName, preferRemoteAlias))
.andReturn("thisiswrong".toCharArray()).anyTimes();
EasyMock.replay(defaultAlias);
final DefaultMasterService ms = EasyMock
.createNiceMock(DefaultMasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
.anyTimes();
EasyMock.replay(ms);
final RemoteConfigurationRegistryClientService clientService = (new ZooKeeperClientServiceProvider())
.newInstance();
clientService.setAliasService(defaultAlias);
clientService.init(gc, Collections.emptyMap());
final ZookeeperRemoteAliasService zkAlias = new ZookeeperRemoteAliasService(defaultAlias,
ms, clientService);
zkAlias.init(gc, Collections.emptyMap());
zkAlias.start();
/* GET Aliases */
List<String> aliases = zkAlias.getAliasesForCluster(expectedClusterName);
List<String> aliasesDev = zkAlias
.getAliasesForCluster(expectedClusterNameDev);
/* no alias added so ist should be empty, except the one in ZK */
Assert.assertEquals(aliases.size(), 1);
Assert.assertEquals(aliasesDev.size(), 0);
/* Create an alias in Zookeeper externally */
client.create().withMode(CreateMode.PERSISTENT).
forPath(ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+ ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterName
+ ZookeeperRemoteAliasService.PATH_SEPARATOR + expectedAlias,
zkAlias.encrypt(expectedPassword).getBytes(StandardCharsets.UTF_8));
/* Create an alias in Zookeeper externally */
client.create().withMode(CreateMode.PERSISTENT).
forPath(ZookeeperRemoteAliasService.PATH_KNOX_ALIAS_STORE_TOPOLOGY
+ ZookeeperRemoteAliasService.
PATH_SEPARATOR + expectedClusterNameDev
+ ZookeeperRemoteAliasService.PATH_SEPARATOR + expectedAliasDev,
zkAlias.encrypt(expectedPasswordDev).getBytes(StandardCharsets.UTF_8));
/* Try */
aliases = zkAlias.getAliasesForCluster(expectedClusterName);
aliasesDev = zkAlias.getAliasesForCluster(expectedClusterNameDev);
Assert.assertTrue("Expected alias 'knox.test.alias' not found ",
aliases.contains(expectedAlias));
Assert.assertTrue("Expected alias 'knox.test.alias.dev' not found ",
aliasesDev.contains(expectedAliasDev));
final char[] result = zkAlias
.getPasswordFromAliasForCluster(expectedClusterName, expectedAlias);
final char[] result1 = zkAlias
.getPasswordFromAliasForCluster(expectedClusterNameDev,
expectedAliasDev);
/* make sure the externally added passwords match */
Assert.assertEquals(expectedPassword, new String(result));
Assert.assertEquals(expectedPasswordDev, new String(result1));
/* test that remote alias service prefers remote over local */
final char[] prefAliasResult = zkAlias
.getPasswordFromAliasForCluster(expectedClusterName, preferRemoteAlias);
Assert.assertEquals(preferRemoteAliasClearPassword, new String(prefAliasResult));
zkAlias.stop();
}
}