blob: bee945aff54cf5aa7d8bf5ca8e09e2d84e837469 [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.test.InstanceSpec;
import org.apache.curator.test.TestingCluster;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientService;
import org.apache.knox.gateway.service.config.remote.zk.ZooKeeperClientServiceProvider;
import org.apache.knox.gateway.services.config.client.RemoteConfigurationRegistryClientService;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.MasterService;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import static org.easymock.EasyMock.capture;
/**
* Test for {@link ZookeeperRemoteAliasService} backed by Zookeeper.
*/
public class ZookeeperRemoteAliasServiceTest {
@ClassRule
public static final TemporaryFolder testFolder = new TemporaryFolder();
private static TestingCluster zkNodes;
private static GatewayConfig gc;
@BeforeClass
public static void setupSuite() throws Exception {
String configMonitorName = "remoteConfigMonitorClient";
configureAndStartZKCluster();
// Setup the base GatewayConfig mock
gc = EasyMock.createNiceMock(GatewayConfig.class);
EasyMock.expect(gc.getRemoteRegistryConfigurationNames())
.andReturn(Collections.singletonList(configMonitorName)).anyTimes();
final String registryConfig =
GatewayConfig.REMOTE_CONFIG_REGISTRY_TYPE + "="
+ ZooKeeperClientService.TYPE + ";"
+ GatewayConfig.REMOTE_CONFIG_REGISTRY_ADDRESS + "=" + zkNodes
.getConnectString();
EasyMock.expect(gc.getRemoteRegistryConfiguration(configMonitorName))
.andReturn(registryConfig).anyTimes();
EasyMock.expect(gc.getRemoteConfigurationMonitorClientName())
.andReturn(configMonitorName).anyTimes();
EasyMock.expect(gc.getAlgorithm()).andReturn("AES").anyTimes();
EasyMock.expect(gc.isRemoteAliasServiceEnabled())
.andReturn(true).anyTimes();
final Path baseFolder = Paths.get(testFolder.newFolder().getAbsolutePath());
EasyMock.expect(gc.getGatewayDataDir()).andReturn(Paths.get(baseFolder.toString(), "data").toString()).anyTimes();
EasyMock.expect(gc.getGatewayKeystoreDir()).andReturn(Paths.get(baseFolder.toString(), "data", "keystores").toString()).anyTimes();
EasyMock.replay(gc);
}
private static void configureAndStartZKCluster() throws Exception {
// Configure security for the ZK cluster instances
Map<String, Object> customInstanceSpecProps = new HashMap<>();
customInstanceSpecProps.put("authProvider.1",
"org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
customInstanceSpecProps.put("requireClientAuthScheme", "sasl");
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);
// Start the cluster
zkNodes.start();
}
@AfterClass
public static void tearDownSuite() throws Exception {
// Shutdown the ZK cluster
zkNodes.close();
}
@Test
public void testAliasForCluster() throws Exception {
final String expectedClusterName = "sandbox";
final String expectedAlias = "knox.test.Alias";
final String expectedPassword = "dummyPassword";
final String expectedClusterNameDev = "development";
final String expectedAliasDev = "knox.test.alias.dev";
final String expectedPasswordDev = "otherDummyPassword";
// Mock Alias Service
final DefaultAliasService defaultAlias = EasyMock
.createNiceMock(DefaultAliasService.class);
// Captures for validating the alias creation for a generated topology
final Capture<String> capturedCluster = EasyMock.newCapture();
final Capture<String> capturedAlias = EasyMock.newCapture();
final Capture<String> capturedPwd = EasyMock.newCapture();
defaultAlias
.addAliasForCluster(capture(capturedCluster), capture(capturedAlias),
capture(capturedPwd));
EasyMock.expectLastCall().anyTimes();
/* defaultAlias.getAliasesForCluster() never returns null */
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterName))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterNameDev))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.replay(defaultAlias);
final DefaultMasterService ms = EasyMock
.createNiceMock(DefaultMasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
.anyTimes();
EasyMock.replay(ms);
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();
/* Put */
zkAlias.addAliasForCluster(expectedClusterName, expectedAlias,
expectedPassword);
zkAlias.addAliasForCluster(expectedClusterNameDev, expectedAliasDev,
expectedPasswordDev);
/* GET all Aliases */
List<String> aliases = zkAlias.getAliasesForCluster(expectedClusterName);
List<String> aliasesDev = zkAlias
.getAliasesForCluster(expectedClusterNameDev);
Assert.assertEquals(aliases.size(), 1);
Assert.assertEquals(aliasesDev.size(), 1);
Assert.assertTrue("Expected alias '" + expectedAlias + "' not found ",
aliases.contains(expectedAlias.toLowerCase(Locale.ROOT)));
Assert.assertTrue("Expected alias '" + expectedAliasDev + "' not found ",
aliasesDev.contains(expectedAliasDev));
final char[] result = zkAlias
.getPasswordFromAliasForCluster(expectedClusterName, expectedAlias);
Assert.assertNotNull("Failed lookup for " + expectedAlias, result);
final char[] result1 = zkAlias
.getPasswordFromAliasForCluster(expectedClusterNameDev,
expectedAliasDev);
Assert.assertNotNull("Failed lookup for " + expectedAliasDev, result1);
Assert.assertEquals(expectedPassword, new String(result));
Assert.assertEquals(expectedPasswordDev, new String(result1));
/* Remove */
zkAlias.removeAliasForCluster(expectedClusterNameDev, expectedAliasDev);
/* Make sure expectedAliasDev is removed*/
aliases = zkAlias.getAliasesForCluster(expectedClusterName);
aliasesDev = zkAlias.getAliasesForCluster(expectedClusterNameDev);
Assert.assertEquals(aliasesDev.size(), 0);
Assert.assertFalse("Expected alias '" + expectedAliasDev + "' to be removed but found.",
aliasesDev.contains(expectedAliasDev));
Assert.assertEquals(aliases.size(), 1);
Assert.assertTrue("Expected alias '" + expectedAlias + "' not found ",
aliases.contains(expectedAlias.toLowerCase(Locale.ROOT)));
/* Test auto-generate password for alias */
final String testAutoGeneratedpasswordAlias = "knox.test.alias.auto";
final char[] autoGeneratedPassword = zkAlias
.getPasswordFromAliasForCluster(expectedClusterName,
testAutoGeneratedpasswordAlias, true);
aliases = zkAlias.getAliasesForCluster(expectedClusterName);
Assert.assertNotNull(autoGeneratedPassword);
Assert.assertEquals(aliases.size(), 2);
Assert.assertTrue("Expected alias 'knox.test.alias' not found ",
aliases.contains(testAutoGeneratedpasswordAlias));
}
@Test
public void testEncryptDecrypt() throws Exception {
final String testPassword = "ApacheKnoxPassword123";
final AliasService defaultAlias = EasyMock
.createNiceMock(AliasService.class);
EasyMock.replay(defaultAlias);
final DefaultMasterService ms = EasyMock
.createNiceMock(DefaultMasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray())
.anyTimes();
EasyMock.replay(ms);
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());
final String encrypted = zkAlias.encrypt(testPassword);
Assert.assertNotNull(encrypted);
final String clear = zkAlias.decrypt(encrypted);
Assert.assertEquals(testPassword, clear);
try {
// Match default data that is put into a newly created znode
final byte[] badData = new byte[0];
zkAlias.decrypt(new String(badData, StandardCharsets.UTF_8));
Assert.fail("Should have failed to decrypt.");
} catch (IllegalArgumentException e) {
Assert.assertEquals("Data should have 3 parts split by ::", e.getMessage());
}
}
@Test
public void testRemoveAliasesForCluster() throws Exception {
final String expectedClusterName = "sandbox";
final String expectedAlias = "knox.test.alias";
final String expectedPassword = "dummyPassword";
final int aliasCount = 5;
final Set<String> expectedAliases = new HashSet<>();
for (int i = 0; i < aliasCount ; i++) {
expectedAliases.add(expectedAlias + i);
}
final String expectedClusterNameDev = "development";
final String expectedAliasDev = "knox.test.alias.dev";
final String expectedPasswordDev = "otherDummyPassword";
final int devAliasCount = 3;
final Set<String> expectedDevAliases = new HashSet<>();
for (int i = 0; i < 3 ; i++) {
expectedDevAliases.add(expectedAliasDev + i);
}
// Mock Alias Service
final DefaultAliasService defaultAlias = EasyMock.createNiceMock(DefaultAliasService.class);
// Captures for validating the alias creation for a generated topology
final Capture<String> capturedCluster = EasyMock.newCapture();
final Capture<String> capturedAlias = EasyMock.newCapture();
final Capture<String> capturedPwd = EasyMock.newCapture();
defaultAlias.addAliasForCluster(capture(capturedCluster), capture(capturedAlias), capture(capturedPwd));
EasyMock.expectLastCall().anyTimes();
// defaultAlias.getAliasesForCluster() never returns null
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterName))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.expect(defaultAlias.getAliasesForCluster(expectedClusterNameDev))
.andReturn(new ArrayList<>()).anyTimes();
EasyMock.replay(defaultAlias);
final DefaultMasterService ms = EasyMock.createNiceMock(DefaultMasterService.class);
EasyMock.expect(ms.getMasterSecret()).andReturn("knox".toCharArray()).anyTimes();
EasyMock.replay(ms);
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();
int originalSize = zkAlias.getAliasesForCluster(expectedClusterName).size();
// Put
for (String alias : expectedAliases) {
zkAlias.addAliasForCluster(expectedClusterName, alias, expectedPassword);
}
for (String alias : expectedDevAliases) {
zkAlias.addAliasForCluster(expectedClusterNameDev, alias, expectedPasswordDev);
}
Assert.assertEquals(originalSize + aliasCount, zkAlias.getAliasesForCluster(expectedClusterName).size());
Assert.assertEquals(devAliasCount, zkAlias.getAliasesForCluster(expectedClusterNameDev).size());
// Invoke the bulk removal method for the dev cluster
zkAlias.removeAliasesForCluster(expectedClusterNameDev, expectedDevAliases);
List<String> aliasesDev = zkAlias.getAliasesForCluster(expectedClusterNameDev);
Assert.assertEquals("Expected 'knox.test.alias.dev' aliases to have been removed.", 0, aliasesDev.size());
// Invoke the bulk removal method for the sandbox cluster
zkAlias.removeAliasesForCluster(expectedClusterName, expectedAliases);
List<String> aliases = zkAlias.getAliasesForCluster(expectedClusterName);
Assert.assertEquals("Expected 'knox.test.alias' aliases to have been removed.", originalSize, aliases.size());
}
@Test
@Ignore("should be executed manually in case you'd like to measure how much time alias addition/fetch takes")
public void testPerformance() throws Exception {
final MasterService masterService = EasyMock.createNiceMock(MasterService.class);
EasyMock.expect(masterService.getMasterSecret()).andReturn("ThisIsMyM4sterP4sW0r!d".toCharArray()).anyTimes();
EasyMock.replay(masterService);
final DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.init(gc, null);
keystoreService.setMasterService(masterService);
final int rounds = 11;
final int numOfAliases = 200;
final String cluster = "myTestCluster";
for (int round = 0; round < rounds; round++) {
//re-creating the alias service every time so that its cache is empty too
final DefaultAliasService aliasService = new DefaultAliasService();
aliasService.init(gc, null);
aliasService.setMasterService(masterService);
aliasService.setKeystoreService(keystoreService);
RemoteConfigurationRegistryClientService clientService = (new ZooKeeperClientServiceProvider()).newInstance();
clientService.setAliasService(aliasService);
clientService.init(gc, Collections.emptyMap());
final ZookeeperRemoteAliasService zkAlias = new ZookeeperRemoteAliasService(aliasService, masterService, clientService);
zkAlias.init(gc, Collections.emptyMap());
zkAlias.start();
final long start = System.currentTimeMillis();
for (int i = 0; i < numOfAliases; i++) {
zkAlias.addAliasForCluster(cluster, "alias" + i, "password" + i);
Assert.assertEquals("password" + i, new String(zkAlias.getPasswordFromAliasForCluster(cluster, "alias" + i)));
}
System.out.println(System.currentTimeMillis() - start);
}
}
}