blob: 85841c34141623c8f561570bcdfa30aefe3da968 [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
*
* 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.
*/
package org.apache.knox.gateway.services.security.impl;
import static org.apache.knox.gateway.config.GatewayConfig.IDENTITY_KEYSTORE_PASSWORD_ALIAS;
import static org.apache.knox.gateway.config.GatewayConfig.IDENTITY_KEYSTORE_PATH;
import static org.apache.knox.gateway.config.GatewayConfig.IDENTITY_KEYSTORE_TYPE;
import static org.apache.knox.gateway.config.GatewayConfig.IDENTITY_KEY_ALIAS;
import static org.apache.knox.gateway.config.GatewayConfig.IDENTITY_KEY_PASSPHRASE_ALIAS;
import static org.apache.knox.gateway.config.GatewayConfig.SIGNING_KEYSTORE_NAME;
import static org.apache.knox.gateway.config.GatewayConfig.SIGNING_KEY_ALIAS;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createMockBuilder;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
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;
import static org.junit.Assert.fail;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.config.impl.GatewayConfigImpl;
import org.apache.knox.gateway.services.ServiceLifecycleException;
import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.KeystoreService;
import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.gateway.services.security.MasterService;
import org.apache.knox.gateway.util.X509CertificateUtil;
import org.easymock.IAnswer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class DefaultKeystoreServiceTest {
@Rule
public final TemporaryFolder testFolder = new TemporaryFolder();
@Test
public void testGetTruststoreForHttpClientDefaults() throws Exception {
final Path dataDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = new GatewayConfigImpl();
config.set("gateway.data.dir", dataDir.toString());
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.init(config, Collections.emptyMap());
assertNull(keystoreService.getTruststoreForHttpClient());
}
@Test
public void testGetTruststoreForHttpClientCustomTrustStore() throws Exception {
final Path dataDir = testFolder.newFolder().toPath();
final Path truststoreFile = testFolder.newFile().toPath();
final String truststoreType = "jks";
final String truststorePasswordAlias = "password-alias";
final char[] truststorePassword = "truststore_password".toCharArray();
GatewayConfigImpl config = new GatewayConfigImpl();
config.set("gateway.data.dir", dataDir.toString());
config.set("gateway.httpclient.truststore.path", truststoreFile.toString());
config.set("gateway.httpclient.truststore.type", truststoreType);
config.set("gateway.httpclient.truststore.password.alias", truststorePasswordAlias);
KeyStore keystore = createNiceMock(KeyStore.class);
DefaultKeystoreService keystoreService = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("loadKeyStore", Path.class, String.class, char[].class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreService.loadKeyStore(eq(truststoreFile), eq(truststoreType), eq(truststorePassword)))
.andReturn(keystore)
.once();
expect(keystoreService.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq(truststorePasswordAlias)))
.andReturn(truststorePassword)
.once();
replay(keystore, keystoreService);
keystoreService.init(config, Collections.emptyMap());
assertEquals(keystore, keystoreService.getTruststoreForHttpClient());
verify(keystore, keystoreService);
}
@Test(expected = KeystoreServiceException.class)
public void testGetTruststoreForHttpClientMissingCustomTrustStore() throws Exception {
final Path dataDir = testFolder.newFolder().toPath();
final String truststoreType = "jks";
final String truststorePasswordAlias = "password-alias";
final char[] truststorePassword = "truststore_password".toCharArray();
GatewayConfigImpl config = new GatewayConfigImpl();
config.set("gateway.data.dir", dataDir.toString());
config.set("gateway.httpclient.truststore.path", Paths.get(dataDir.toString(), "missing_file.jks").toString());
config.set("gateway.httpclient.truststore.type", truststoreType);
config.set("gateway.httpclient.truststore.password.alias", truststorePasswordAlias);
DefaultKeystoreService keystoreService = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreService.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq(truststorePasswordAlias)))
.andReturn(truststorePassword)
.once();
replay(keystoreService);
keystoreService.init(config, Collections.emptyMap());
keystoreService.getTruststoreForHttpClient();
verify(keystoreService);
}
@Test
public void testGetTruststoreForHttpClientCustomTrustStoreMissingPasswordAlias() throws Exception {
final Path dataDir = testFolder.newFolder().toPath();
final Path truststoreFile = testFolder.newFile().toPath();
final String truststoreType = "jks";
final char[] masterSecret = "master_secret".toCharArray();
GatewayConfigImpl config = new GatewayConfigImpl();
config.set("gateway.data.dir", dataDir.toString());
config.set("gateway.httpclient.truststore.path", truststoreFile.toString());
config.set("gateway.httpclient.truststore.type", truststoreType);
KeyStore keystore = createNiceMock(KeyStore.class);
DefaultKeystoreService keystoreService = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("loadKeyStore", Path.class, String.class, char[].class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.withConstructor()
.createMock();
expect(keystoreService.loadKeyStore(eq(truststoreFile), eq(truststoreType), eq(masterSecret)))
.andReturn(keystore)
.once();
expect(keystoreService.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq(GatewayConfig.DEFAULT_HTTP_CLIENT_TRUSTSTORE_PASSWORD_ALIAS)))
.andReturn(null)
.once();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterSecret);
replay(keystore, keystoreService, masterService);
keystoreService.init(config, Collections.emptyMap());
keystoreService.setMasterService(masterService);
assertEquals(keystore, keystoreService.getTruststoreForHttpClient());
verify(keystore, keystoreService, masterService);
}
@Test
public void testKeystoreForGateway() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
char[] keyPassword = "key_password".toCharArray();
char[] customKeyPassword = "custom-key-passphrase".toCharArray();
char[] customKeystorePassword = "custom-keystore-password".toCharArray();
DefaultKeystoreService keystoreServiceAlt = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreServiceAlt.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq("custom_key_passphrase_alias")))
.andReturn(customKeyPassword)
.anyTimes();
expect(keystoreServiceAlt.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq("custom_keystore_password_alias")))
.andReturn(customKeystorePassword)
.anyTimes();
replay(keystoreServiceAlt, masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
/* *******************
* Test Defaults
*/
Path defaultKeystoreFile = baseDir.resolve("security").resolve("keystores").resolve("gateway.jks");
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
testCreateGetAndCheckKeystoreForGateway(keystoreService,
defaultKeystoreFile,
GatewayConfigImpl.DEFAULT_IDENTITY_KEY_ALIAS,
keyPassword, config);
/* *******************
* Test Custom Values
*/
Path customKeystoreFile = baseDir.resolve("test").resolve("keystore").resolve("custom_keystore.p12");
String customKeystoreType = "pkcs12";
String customAlias = "custom_alias";
config.set(IDENTITY_KEYSTORE_PATH, customKeystoreFile.toAbsolutePath().toString());
config.set(IDENTITY_KEYSTORE_TYPE, customKeystoreType);
config.set(IDENTITY_KEYSTORE_PASSWORD_ALIAS, "custom_keystore_password_alias");
config.set(IDENTITY_KEY_ALIAS, customAlias);
config.set(IDENTITY_KEY_PASSPHRASE_ALIAS, "custom_key_passphrase_alias");
keystoreServiceAlt.setMasterService(masterService);
keystoreServiceAlt.init(config, Collections.emptyMap());
keystoreServiceAlt.init(config, Collections.emptyMap());
testCreateGetAndCheckKeystoreForGateway(keystoreServiceAlt, customKeystoreFile, customAlias, customKeyPassword, config);
// Verify the keystore passwords are set properly...
assertTrue(Files.exists(defaultKeystoreFile));
assertNotNull(keystoreService.loadKeyStore(defaultKeystoreFile, GatewayConfigImpl.DEFAULT_IDENTITY_KEYSTORE_TYPE, masterPassword));
assertTrue(Files.exists(customKeystoreFile));
assertNotNull(keystoreService.loadKeyStore(customKeystoreFile, customKeystoreType, customKeystorePassword));
verify(keystoreServiceAlt, masterService);
}
@Test
public void testSigningKeystore() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
DefaultKeystoreService keystoreServiceAlt = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreServiceAlt.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS)))
.andReturn(null)
.atLeastOnce();
DefaultKeystoreService keystoreServiceSymlink = createMockBuilder(DefaultKeystoreService.class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreServiceSymlink.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME), eq(GatewayConfig.DEFAULT_SIGNING_KEYSTORE_PASSWORD_ALIAS)))
.andReturn(null)
.atLeastOnce();
replay(keystoreServiceAlt, keystoreServiceSymlink, masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
/* *******************
* Test Defaults
*/
Path defaultFile = baseDir.resolve("security").resolve("keystores").resolve("gateway.jks");
String defaultAlias = "gateway-identity";
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
try {
keystoreService.init(config, Collections.emptyMap());
} catch (ServiceLifecycleException e) {
fail("Not expecting ServiceLifecycleException due to missing signing keystore file since a custom one is not specified");
}
createKeystore(keystoreService, defaultFile, defaultAlias, masterPassword);
keystoreService.init(config, Collections.emptyMap());
testSigningKeystore(keystoreService, defaultFile, defaultAlias, masterPassword);
/* *******************
* Test Custom Values
*/
String customFileName = "custom_signing.jks";
Path customFile = baseDir.resolve("security").resolve("keystores").resolve(customFileName);
String customKeyAlias = "custom_alias";
config.set(SIGNING_KEYSTORE_NAME, customFileName);
config.set(SIGNING_KEY_ALIAS, customKeyAlias);
keystoreServiceAlt.setMasterService(masterService);
// Ensure the signing keystore exists before init-ing the keystore service
createKeystore(keystoreService, customFile, customKeyAlias, masterPassword);
keystoreServiceAlt.init(config, Collections.emptyMap());
testSigningKeystore(keystoreServiceAlt, customFile, customKeyAlias, masterPassword);
/* *******************
* Test Symlink Parent
*/
String symlinkFileName = "symlink_signing.jks";
Path symlinkSecurityDir = baseDir.resolve("security").resolve("symlink");
Path symlinkTarget = baseDir.resolve("symlinkTarget");
Path symlinkParentLink = symlinkSecurityDir.resolve("keystores");
try {
//Creating the real path to symlinkParentLink
Files.createDirectories(symlinkTarget.resolve("keystores"));
} catch (IOException e) {
fail("Unable to create symlink target directory.");
}
try {
Files.createSymbolicLink(symlinkSecurityDir, symlinkTarget);
} catch (IOException e) {
fail("Unable to create symlink while instantiating keystores.");
}
Path symlinkFile = symlinkParentLink.resolve(symlinkFileName);
String symlinkKeyAlias = "symlink_alias";
config.set(SIGNING_KEYSTORE_NAME, symlinkFileName);
config.set(SIGNING_KEY_ALIAS, symlinkKeyAlias);
config.set(GatewayConfigImpl.SECURITY_DIR, symlinkSecurityDir.toString());
keystoreServiceSymlink.setMasterService(masterService);
// Ensure the signing keystore exists before init-ing the keystore service
createKeystore(keystoreService, symlinkFile, symlinkKeyAlias, masterPassword);
keystoreServiceSymlink.init(config, Collections.emptyMap());
testSigningKeystore(keystoreServiceSymlink, symlinkFile, symlinkKeyAlias, masterPassword);
// Verify the keystore passwords are set properly...
assertTrue(Files.exists(defaultFile));
assertNotNull(keystoreService.loadKeyStore(defaultFile, "JKS", masterPassword));
assertTrue(Files.exists(customFile));
assertNotNull(keystoreService.loadKeyStore(customFile, "JKS", masterPassword));
assertTrue(Files.exists(symlinkFile));
assertNotNull(keystoreService.loadKeyStore(symlinkFile, "JKS", masterPassword));
verify(keystoreServiceAlt, keystoreServiceSymlink, masterService);
}
@Test
public void testCredentialsForCluster() throws Exception {
String clusterName = "my_cluster";
String credentialAlias = "my_alias";
String credentialValue = "my_value";
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
Path baseFolder = testFolder.newFolder().toPath();
GatewayConfig config = createGatewayConfig(baseFolder);
Path expectedFile = Paths.get(config.getGatewayKeystoreDir(), clusterName + "-credentials.jceks");
replay(masterService);
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
assertFalse(Files.exists(expectedFile));
assertFalse(keystoreService.isCredentialStoreForClusterAvailable(clusterName));
// This should be an empty keystore...
KeyStore emptyKeystore = keystoreService.getCredentialStoreForCluster(clusterName);
assertNotNull(emptyKeystore);
assertEquals(0, emptyKeystore.size());
keystoreService.createCredentialStoreForCluster(clusterName);
assertTrue(Files.exists(expectedFile));
assertTrue(keystoreService.isCredentialStoreForClusterAvailable(clusterName));
KeyStore credentialStore = keystoreService.getCredentialStoreForCluster(clusterName);
assertNotNull(credentialStore);
assertEquals(0, credentialStore.size());
keystoreService.addCredentialForCluster(clusterName, credentialAlias, credentialValue);
// The previous version of the credential store was not updated
assertEquals(0, credentialStore.size());
// Get the updated credential store
credentialStore = keystoreService.getCredentialStoreForCluster(clusterName);
assertNotNull(credentialStore);
assertEquals(1, credentialStore.size());
// Make sure the expected alias and value exists in the credential store
Key key = credentialStore.getKey(credentialAlias, masterPassword);
assertNotNull(key);
assertEquals(credentialValue, new String(key.getEncoded(), StandardCharsets.UTF_8));
// A request for a existing alias should return the expected value
char[] keyValue = keystoreService.getCredentialForCluster(clusterName, credentialAlias);
assertNotNull(keyValue);
assertEquals(credentialValue, new String(keyValue));
// A request for an alias that does not exists, should return NULL
assertNull(keystoreService.getCredentialForCluster(clusterName, "not!my_alias"));
// Remove that credential
keystoreService.removeCredentialForCluster(clusterName, credentialAlias);
// Get the updated credential store
credentialStore = keystoreService.getCredentialStoreForCluster(clusterName);
assertNotNull(credentialStore);
assertEquals(0, credentialStore.size());
// Make sure the expected alias does not exist in the credential store
key = credentialStore.getKey(credentialAlias, masterPassword);
assertNull(key);
// A request for a existing alias should return null
assertNull(keystoreService.getCredentialForCluster(clusterName, credentialAlias));
verify(masterService);
}
@Test
public void testCredentialCache() throws Exception {
final String clusterName = "my_cluster";
final String credentialAlias = "my_alias";
final String credentialValue = "my_value";
final MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn("master_password".toCharArray()).anyTimes();
replay(masterService);
final GatewayConfigImpl gatewayConfig = createGatewayConfig(testFolder.newFolder().toPath());
final DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(gatewayConfig, null);
keystoreService.addCredentialForCluster(clusterName, credentialAlias, credentialValue);
// changing the security folder so that we "invalidate" the previously created
// keystore and rely on the cache when fetching a credential for an alias
gatewayConfig.set(GatewayConfigImpl.SECURITY_DIR, gatewayConfig.getGatewaySecurityDir() + "_other");
keystoreService.init(gatewayConfig, null);
// A request for a existing alias should return the expected value from cache.
// Since the service now has a new security folder set it should return 'null'
// if the cache did not contain the previously added value
final char[] keyValue = keystoreService.getCredentialForCluster(clusterName, credentialAlias);
assertNotNull(keyValue);
assertEquals(credentialValue, new String(keyValue));
verify(masterService);
}
@Test
public void testAddSelfSignedCertForGatewayLocalhost() throws Exception {
testAddSelfSignedCertForGateway(null);
}
@Test
public void testAddSelfSignedCertForGatewayExplicitHostname() throws Exception {
testAddSelfSignedCertForGateway("knox.example.com");
}
@Test
public void testAddSelfSignedCertForGatewayCalculateHostname() throws Exception {
testAddSelfSignedCertForGateway("hostname");
}
@Test
public void testGetKeyAndCertificateForGateway() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
replay(masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
createKeystore(keystoreService, Paths.get(config.getIdentityKeystorePath()), config.getIdentityKeyAlias(), masterPassword);
assertNull(keystoreService.getKeyForGateway("wrongpassword".toCharArray()));
assertNotNull(keystoreService.getKeyForGateway(masterPassword));
assertNotNull(keystoreService.getKeyForGateway(null)); // implicitly should use master secret
assertNotNull(keystoreService.getCertificateForGateway());
verify(masterService);
}
@Test
public void testAddRemoveCredentialForCluster() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
replay(masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
String notClusterName = "cluster_not";
String notAlias= "alias_not";
String clusterName = "cluster";
String alias = "alias1";
String value = "value1";
keystoreService.createCredentialStoreForCluster(clusterName);
assertNull(keystoreService.getCredentialForCluster(clusterName, alias));
assertNull(keystoreService.getCredentialForCluster(notClusterName, alias));
keystoreService.addCredentialForCluster(clusterName, alias, value);
assertEquals(value, String.valueOf(keystoreService.getCredentialForCluster(clusterName, alias)));
assertNull(keystoreService.getCredentialForCluster(notClusterName, alias));
assertNull(keystoreService.getCredentialForCluster(clusterName, notAlias));
assertNull(keystoreService.getCredentialForCluster(notClusterName, notAlias));
keystoreService.removeCredentialForCluster(clusterName, alias);
assertNull(keystoreService.getCredentialForCluster(clusterName, alias));
verify(masterService);
}
@Test
public void testAddRemoveCredentialForClusterSynchronization() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
IAnswer<? extends char[]> iAnswer = (IAnswer<char[]>) () -> {
// Wait .5-1 seconds to return
// Thread.sleep(ThreadLocalRandom.current().nextInt(500, 1000));
return masterPassword;
};
expect(masterService.getMasterSecret()).andAnswer(iAnswer).anyTimes();
replay(masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
String clusterName = "cluster";
keystoreService.createCredentialStoreForCluster(clusterName);
int numberTotalRequests = 10;
Set<Callable<Void>> addCallables = new HashSet<>(numberTotalRequests);
for (int i = 0; i < numberTotalRequests; i++) {
String alias = "alias" + i;
String value = "value" + i;
addCallables.add(() -> {
keystoreService.addCredentialForCluster(clusterName, alias, value);
return null;
});
}
ExecutorService insertExecutor = Executors.newFixedThreadPool(numberTotalRequests);
insertExecutor.invokeAll(addCallables);
insertExecutor.shutdown();
insertExecutor.awaitTermination(5, TimeUnit.SECONDS);
assertThat(insertExecutor.isTerminated(), is(true));
// Ensure not to use cache
keystoreService.cache.invalidateAll();
for (int i = 0; i < numberTotalRequests; i++) {
String alias = "alias" + i;
String value = "value" + i;
assertEquals(value, String.valueOf(keystoreService.getCredentialForCluster(clusterName, alias)));
}
Set<Callable<Void>> removeCallables = new HashSet<>(numberTotalRequests);
for (int i = 0; i < numberTotalRequests; i++) {
String alias = "alias" + i;
removeCallables.add(() -> {
keystoreService.removeCredentialForCluster(clusterName, alias);
return null;
});
}
ExecutorService removeExecutor = Executors.newFixedThreadPool(numberTotalRequests);
removeExecutor.invokeAll(removeCallables);
removeExecutor.shutdown();
removeExecutor.awaitTermination(5, TimeUnit.SECONDS);
assertThat(removeExecutor.isTerminated(), is(true));
// Ensure not to use cache
keystoreService.cache.invalidateAll();
for (int i = 0; i < numberTotalRequests; i++) {
String alias = "alias" + i;
assertNull(keystoreService.getCredentialForCluster(clusterName, alias));
}
verify(masterService);
}
/*
* Test the bulk key removal method, which should only load the keystore file once, and subsequently write the
* keystore file only once, rather than once each per key.
*/
@Test
public void testRemoveCredentialsForCluster() throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
replay(masterService);
Path baseDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = createGatewayConfig(baseDir);
CountingDefaultKeystoreService keystoreService = new CountingDefaultKeystoreService();
keystoreService.setMasterService(masterService);
keystoreService.init(config, Collections.emptyMap());
String clusterName = "cluster";
Map<String, String> testAliases = new HashMap<>();
testAliases.put("alias1", "value1");
testAliases.put("alias2", "value2");
testAliases.put("alias3", "value3");
Set<String> aliases = testAliases.keySet();
keystoreService.createCredentialStoreForCluster(clusterName);
for (String alias : aliases) {
keystoreService.addCredentialForCluster(clusterName, alias, testAliases.get(alias));
assertEquals(testAliases.get(alias), String.valueOf(keystoreService.getCredentialForCluster(clusterName, alias)));
}
// Clear the counts recorded from adding the credentials
keystoreService.clearCounts();
// Invoke the bulk removal method
keystoreService.removeCredentialsForCluster(clusterName, aliases);
// Validate the number of loads/writes of the keystore file
assertEquals("Expected only a single load of the keystore file.", 1, keystoreService.loadCount);
assertEquals("Expected only a single write to the keystore file.", 1, keystoreService.storeCount);
for (String alias : aliases) {
assertNull(keystoreService.getCredentialForCluster(clusterName, alias));
}
verify(masterService);
}
private void testAddSelfSignedCertForGateway(String hostname) throws Exception {
char[] masterPassword = "master_password".toCharArray();
MasterService masterService = createMock(MasterService.class);
expect(masterService.getMasterSecret()).andReturn(masterPassword).anyTimes();
replay(masterService);
Path baseFolder = testFolder.newFolder().toPath();
GatewayConfig config = createGatewayConfig(baseFolder);
Path defaultFile = baseFolder.resolve("security").resolve("keystores").resolve("gateway.jks");
DefaultKeystoreService keystoreService = new DefaultKeystoreService();
keystoreService.init(config, Collections.emptyMap());
keystoreService.setMasterService(masterService);
keystoreService.createKeyStore(defaultFile, "JKS", masterPassword);
String alias;
char[] password;
String expectedSubjectName;
if (hostname == null) {
alias = "test_localhost";
password = "test_localhost".toCharArray();
expectedSubjectName = "CN=localhost, OU=Test, O=Hadoop, L=Test, ST=Test, C=US";
keystoreService.addSelfSignedCertForGateway(alias, password);
} else {
alias = "test_" + hostname;
password = ("test_" + hostname).toCharArray();
if ("hostname".equals(hostname)) {
expectedSubjectName = "CN=" + InetAddress.getLocalHost().getHostName() + ", OU=Test, O=Hadoop, L=Test, ST=Test, C=US";
} else {
expectedSubjectName = "CN=" + hostname + ", OU=Test, O=Hadoop, L=Test, ST=Test, C=US";
}
keystoreService.addSelfSignedCertForGateway(alias, password, hostname);
}
assertNotNull(keystoreService.getKeyForGateway(alias, password));
KeyStore keystore = keystoreService.getKeystoreForGateway();
assertNotNull(keystore);
assertNotNull(keystore.getKey(alias, password));
Certificate certificate = keystore.getCertificate(alias);
assertTrue(certificate instanceof X509Certificate);
Principal subject = ((X509Certificate) certificate).getSubjectDN();
assertEquals(expectedSubjectName, subject.getName());
verify(masterService);
}
private void testCreateGetAndCheckKeystoreForGateway(KeystoreService keystoreService,
Path expectedKeystoreFilePath,
String expectedAlias,
char[] keyPassword,
GatewayConfig config) throws Exception {
assertEquals(expectedKeystoreFilePath.toAbsolutePath().toString(), keystoreService.getKeystorePath());
assertFalse(Files.exists(expectedKeystoreFilePath));
assertFalse(keystoreService.isKeystoreForGatewayAvailable());
keystoreService.createKeystoreForGateway();
// The keystore file has now been created
assertTrue(Files.exists(expectedKeystoreFilePath));
KeyStore postCreateKeystore = keystoreService.getKeystoreForGateway();
assertNotNull(postCreateKeystore);
assertEquals(0, postCreateKeystore.size());
keystoreService.addSelfSignedCertForGateway(config.getIdentityKeyAlias(), keyPassword, "localhost");
assertNotNull(keystoreService.getKeyForGateway(expectedAlias, keyPassword));
assertEquals(0, postCreateKeystore.size());
// reread the keystore
postCreateKeystore = keystoreService.getKeystoreForGateway();
assertEquals(1, postCreateKeystore.size());
assertNotNull(postCreateKeystore.getKey(expectedAlias, keyPassword));
Certificate certificate = postCreateKeystore.getCertificate(expectedAlias);
assertNotNull(certificate);
}
private void testSigningKeystore(KeystoreService keystoreService,
Path expectedKeystoreFilePath,
String keyAlias,
char[] masterPassword) throws Exception {
assertTrue(Files.exists(expectedKeystoreFilePath));
assertNotNull(keystoreService.getSigningKeystore());
assertNotNull(keystoreService.getSigningKey(keyAlias, masterPassword));
}
private GatewayConfigImpl createGatewayConfig(Path baseDir) {
GatewayConfigImpl config = new GatewayConfigImpl();
config.set("gateway.data.dir", baseDir.toAbsolutePath().toString());
config.set("gateway.security.dir", baseDir.resolve("security").toAbsolutePath().toString());
return config;
}
private void createKeystore(DefaultKeystoreService keystoreService, Path keystoreFilePath, String alias, char[] password)
throws KeystoreServiceException, KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
KeyStore keystore = keystoreService.createKeyStore(keystoreFilePath, "JKS", password);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
X509Certificate cert = X509CertificateUtil.generateCertificate(
String.format(Locale.ROOT, "CN=%s,OU=Test,O=Hadoop,L=Test,ST=Test,C=US", this.getClass().getName()),
keyPair,
365,
"SHA1withRSA");
keystore.setKeyEntry(alias, keyPair.getPrivate(),
password,
new java.security.cert.Certificate[]{cert});
keystoreService.writeKeyStoreToFile(keystore, keystoreFilePath, password);
}
private static class CountingDefaultKeystoreService extends DefaultKeystoreService {
int loadCount;
int storeCount;
void clearCounts() {
loadCount = 0;
storeCount = 0;
}
@Override
synchronized KeyStore loadKeyStore(Path keyStoreFilePath, String storeType, char[] password) throws KeystoreServiceException {
loadCount++;
return super.loadKeyStore(keyStoreFilePath, storeType, password);
}
@Override
synchronized void writeKeyStoreToFile(KeyStore keyStore, Path path, char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
storeCount++;
super.writeKeyStoreToFile(keyStore, path, password);
}
}
}