This is an automated email from the ASF dual-hosted git repository.
krisden pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 399bb65 KNOX-1820 - Cleanup KeystoreService implementations and add
unit tests (#72)
399bb65 is described below
commit 399bb6584c05a7c1307a900e33b324bbd20373a8
Author: Robert Levas <[email protected]>
AuthorDate: Thu Mar 14 18:36:00 2019 -0400
KNOX-1820 - Cleanup KeystoreService implementations and add unit tests (#72)
---
.../org/apache/knox/gateway/GatewayMessages.java | 5 +
.../security/impl/DefaultKeystoreService.java | 195 ++++++--
.../security/impl/DefaultKeystoreServiceTest.java | 489 ++++++++++++++++++++-
.../knox/gateway/i18n/GatewaySpiMessages.java | 15 -
.../security/impl/BaseKeystoreService.java | 178 --------
.../services/security/impl/CMFKeystoreService.java | 149 -------
.../security/impl/CMFKeystoreServiceTest.java | 153 -------
7 files changed, 622 insertions(+), 562 deletions(-)
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
index 1aef1c3..1362455 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/GatewayMessages.java
@@ -635,4 +635,9 @@ public interface GatewayMessages {
@Message( level = MessageLevel.ERROR, text = "The path to the keystore is
not accessible: {0}" )
void keystoreFileIsNotAccessible(String path);
+ @Message( level = MessageLevel.ERROR, text = "Failed to add credential: {1}"
)
+ void failedToAddCredential( @StackTrace( level = MessageLevel.DEBUG )
Exception e );
+
+ @Message(level = MessageLevel.ERROR, text = "Failed to remove credential:
{1}")
+ void failedToRemoveCredential(@StackTrace(level = MessageLevel.DEBUG)
Exception e);
}
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
index a681bed..97889fc 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreService.java
@@ -17,6 +17,8 @@
*/
package org.apache.knox.gateway.services.security.impl;
+import static
org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
+
import org.apache.commons.lang.StringUtils;
import org.apache.knox.gateway.GatewayMessages;
import org.apache.knox.gateway.GatewayResources;
@@ -27,12 +29,15 @@ import org.apache.knox.gateway.services.Service;
import org.apache.knox.gateway.services.ServiceLifecycleException;
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 java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
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.GeneralSecurityException;
@@ -55,13 +60,13 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
-import static
org.apache.knox.gateway.services.security.AliasService.NO_CLUSTER_NAME;
+import javax.crypto.spec.SecretKeySpec;
-public class DefaultKeystoreService extends BaseKeystoreService implements
- KeystoreService, Service {
+public class DefaultKeystoreService implements KeystoreService, Service {
- private static final String dnTemplate =
"CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
+ private static final String DN_TEMPLATE =
"CN={0},OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
private static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
+ private static final String CREDENTIALS_STORE_TYPE = "JCEKS";
private static final String CERT_GEN_MODE = "hadoop.gateway.cert.gen.mode";
private static final String CERT_GEN_MODE_LOCALHOST = "localhost";
private static final String CERT_GEN_MODE_HOSTNAME = "hostname";
@@ -73,6 +78,13 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
private Lock readLock;
private Lock writeLock;
+ private MasterService masterService;
+ private Path keyStoreDirPath;
+
+ public void setMasterService(MasterService ms) {
+ this.masterService = ms;
+ }
+
@Override
public void init(GatewayConfig config, Map<String, String> options)
throws ServiceLifecycleException {
@@ -82,11 +94,14 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
this.config = config;
- this.keyStoreDir = config.getGatewayKeystoreDir();
- File ksd = new File(this.keyStoreDir);
- if (!ksd.exists()) {
- if( !ksd.mkdirs() ) {
- throw new ServiceLifecycleException(
RES.failedToCreateKeyStoreDirectory( ksd.getAbsolutePath() ) );
+ this.keyStoreDirPath = Paths.get(config.getGatewayKeystoreDir());
+ if (Files.notExists(keyStoreDirPath)) {
+ try {
+ // This will attempt to create all missing directories. No failures
will occur if the
+ // directories already exist.
+ Files.createDirectories(keyStoreDirPath);
+ } catch (IOException e) {
+ throw new
ServiceLifecycleException(RES.failedToCreateKeyStoreDirectory(keyStoreDirPath.toString()));
}
}
}
@@ -103,8 +118,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
public void createKeystoreForGateway() throws KeystoreServiceException {
writeLock.lock();
try {
- String filename = getKeystorePath();
- createKeystore(filename, config.getIdentityKeystoreType(),
getKeystorePassword(config.getIdentityKeystorePasswordAlias()));
+ createKeyStore(Paths.get(config.getIdentityKeystorePath()),
config.getIdentityKeystoreType(),
getKeyStorePassword(config.getIdentityKeystorePasswordAlias()));
} finally {
writeLock.unlock();
}
@@ -137,7 +151,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
String keyStoreType;
String passwordAlias;
if(keystoreName != null) {
- keyStoreFile = Paths.get(keyStoreDir, keystoreName + ".jks");
+ keyStoreFile = keyStoreDirPath.resolve(keystoreName + ".jks");
keyStoreType = "jks";
passwordAlias = null;
} else {
@@ -187,8 +201,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
passphrase,
new java.security.cert.Certificate[]{cert});
- writeKeystoreToFile(privateKS, new File(
config.getIdentityKeystorePath() ),
getKeystorePassword(config.getIdentityKeystorePasswordAlias()));
- //writeCertificateToFile( cert, new File( keyStoreDir + alias + ".pem"
) );
+ writeKeyStoreToFile(privateKS,
Paths.get(config.getIdentityKeystorePath()),
getKeyStorePassword(config.getIdentityKeystorePasswordAlias()));
} catch (GeneralSecurityException | IOException e) {
LOG.failedToAddSeflSignedCertForGateway( alias, e );
throw new KeystoreServiceException(e);
@@ -200,7 +213,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
}
private String buildDistinguishedName(String hostname) {
- MessageFormat headerFormatter = new MessageFormat(dnTemplate, Locale.ROOT);
+ MessageFormat headerFormatter = new MessageFormat(DN_TEMPLATE,
Locale.ROOT);
String[] paramArray = new String[1];
paramArray[0] = hostname;
return headerFormatter.format(paramArray);
@@ -208,10 +221,10 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
@Override
public void createCredentialStoreForCluster(String clusterName) throws
KeystoreServiceException {
- String filename = Paths.get(keyStoreDir, clusterName +
CREDENTIALS_SUFFIX).toString();
+ Path keystoreFilePath = keyStoreDirPath.resolve(clusterName +
CREDENTIALS_SUFFIX);
writeLock.lock();
try {
- createKeystore(filename, "JCEKS", masterService.getMasterSecret());
+ createKeyStore(keystoreFilePath, CREDENTIALS_STORE_TYPE,
masterService.getMasterSecret());
}
finally {
writeLock.unlock();
@@ -221,11 +234,11 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
@Override
public boolean isCredentialStoreForClusterAvailable(String clusterName)
throws KeystoreServiceException {
boolean rc;
- final File keyStoreFile = new File( keyStoreDir, clusterName +
CREDENTIALS_SUFFIX );
+ final Path keyStoreFilePath = keyStoreDirPath.resolve(clusterName +
CREDENTIALS_SUFFIX);
readLock.lock();
try {
try {
- rc = isKeystoreAvailable(keyStoreFile, "JCEKS",
masterService.getMasterSecret());
+ rc = isKeyStoreAvailable(keyStoreFilePath, CREDENTIALS_STORE_TYPE,
masterService.getMasterSecret());
} catch (KeyStoreException | IOException e) {
throw new KeystoreServiceException(e);
}
@@ -238,18 +251,13 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
@Override
public boolean isKeystoreForGatewayAvailable() throws
KeystoreServiceException {
- boolean rc;
- final File keyStoreFile = new File( config.getIdentityKeystorePath() );
+ final Path keyStoreFilePath = Paths.get(config.getIdentityKeystorePath());
readLock.lock();
try {
- try {
- rc = isKeystoreAvailable(keyStoreFile,
config.getIdentityKeystoreType(),
getKeystorePassword(config.getIdentityKeystorePasswordAlias()));
- } catch (KeyStoreException | IOException e) {
- throw new KeystoreServiceException(e);
- }
- return rc;
- }
- finally {
+ return isKeyStoreAvailable(keyStoreFilePath,
config.getIdentityKeystoreType(),
getKeyStorePassword(config.getIdentityKeystorePasswordAlias()));
+ } catch (KeyStoreException | IOException e) {
+ throw new KeystoreServiceException(e);
+ } finally {
readLock.unlock();
}
}
@@ -323,7 +331,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
throws KeystoreServiceException {
// Do not fail getting the credential store if the keystore file does not
exist. The returned
// KeyStore will be empty. This seems like a potential bug, but is the
behavior before KNOX-1812
- return getKeystore(Paths.get(keyStoreDir, clusterName +
CREDENTIALS_SUFFIX), "JCEKS", null, false);
+ return getKeystore(keyStoreDirPath.resolve(clusterName +
CREDENTIALS_SUFFIX), CREDENTIALS_STORE_TYPE, null, false);
}
@Override
@@ -334,9 +342,9 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
removeFromCache(clusterName, alias);
KeyStore ks = getCredentialStoreForCluster(clusterName);
addCredential(alias, value, ks);
- final File keyStoreFile = new File( keyStoreDir, clusterName +
CREDENTIALS_SUFFIX );
+ final Path keyStoreFilePath = keyStoreDirPath.resolve(clusterName +
CREDENTIALS_SUFFIX);
try {
- writeKeystoreToFile(ks, keyStoreFile, masterService.getMasterSecret());
+ writeKeyStoreToFile(ks, keyStoreFilePath,
masterService.getMasterSecret());
} catch (KeyStoreException | IOException | CertificateException |
NoSuchAlgorithmException e) {
LOG.failedToAddCredentialForCluster( clusterName, e );
}
@@ -379,19 +387,18 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
@Override
public void removeCredentialForCluster(String clusterName, String alias)
throws KeystoreServiceException {
- final File keyStoreFile = new File( keyStoreDir, clusterName +
CREDENTIALS_SUFFIX );
+ final Path keyStoreFilePath = keyStoreDirPath.resolve(clusterName +
CREDENTIALS_SUFFIX);
writeLock.lock();
try {
removeFromCache(clusterName, alias);
KeyStore ks = getCredentialStoreForCluster(clusterName);
removeCredential(alias, ks);
try {
- writeKeystoreToFile(ks, keyStoreFile, masterService.getMasterSecret());
+ writeKeyStoreToFile(ks, keyStoreFilePath,
masterService.getMasterSecret());
} catch (KeyStoreException | IOException | CertificateException |
NoSuchAlgorithmException e) {
LOG.failedToRemoveCredentialForCluster(clusterName, e);
}
- }
- finally {
+ } finally {
writeLock.unlock();
}
}
@@ -442,7 +449,7 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
* <p>
* if <code>failIfNotAccessible</code> is <code>true</code>, then the path
to the keystore file
* (keystorePath) is validated such that it exists, is a file and can be
read by the process. If
- * any of these checks fail, a {@link KeystoreServiceException} is thrown in
dicatating the exact
+ * any of these checks fail, a {@link KeystoreServiceException} is thrown in
dictating the exact
* reason.
* <p>
* Before the keystore file is loaded, the service's read lock is locked to
prevent concurrent
@@ -450,36 +457,128 @@ public class DefaultKeystoreService extends
BaseKeystoreService implements
*
* @param keystorePath the path to the keystore file
* @param keystoreType the type of keystore file
- * @param alias the alias for the password to the keystore
file (see {@link #getKeystorePassword(String)})
+ * @param alias the alias for the password to the keystore
file (see {@link #getKeyStorePassword(String)})
* @param failIfNotAccessible <code>true</code> to ensure the keystore file
exists and is readable; <code>false</code> to not check
* @return a {@link KeyStore}, or <code>null</code> if the requested
keystore cannot be created
* @throws KeystoreServiceException if an error occurs loading the keystore
file
*/
private KeyStore getKeystore(Path keystorePath, String keystoreType, String
alias, boolean failIfNotAccessible) throws KeystoreServiceException {
- File keystoreFile = keystorePath.toFile();
if (failIfNotAccessible) {
- if (!keystoreFile.exists()) {
+ if (Files.notExists(keystorePath)) {
LOG.keystoreFileDoesNotExist(keystorePath.toString());
- throw new KeystoreServiceException("The keystore file does not exist:
" + keystoreFile.getAbsolutePath());
- } else if (!keystoreFile.isFile()) {
+ throw new KeystoreServiceException("The keystore file does not exist:
" + keystorePath.toString());
+ } else if (!Files.isRegularFile(keystorePath)) {
LOG.keystoreFileIsNotAFile(keystorePath.toString());
- throw new KeystoreServiceException("The keystore file is not a file: "
+ keystoreFile.getAbsolutePath());
- } else if (!keystoreFile.canRead()) {
+ throw new KeystoreServiceException("The keystore file is not a file: "
+ keystorePath.toString());
+ } else if (!Files.isReadable(keystorePath)) {
LOG.keystoreFileIsNotAccessible(keystorePath.toString());
- throw new KeystoreServiceException("The keystore file cannot be read:
" + keystoreFile.getAbsolutePath());
+ throw new KeystoreServiceException("The keystore file cannot be read:
" + keystorePath.toString());
}
}
readLock.lock();
try {
- return getKeystore(keystoreFile, keystoreType,
getKeystorePassword(alias));
+ return loadKeyStore(keystorePath, keystoreType,
getKeyStorePassword(alias));
} finally {
readLock.unlock();
}
}
- private char[] getKeystorePassword(String alias) throws
KeystoreServiceException {
+ private boolean isKeyStoreAvailable(final Path keyStoreFilePath, String
storeType, char[] password) throws KeyStoreException, IOException {
+ if (Files.exists(keyStoreFilePath)) {
+ try (InputStream input = Files.newInputStream(keyStoreFilePath)) {
+ final KeyStore keyStore = KeyStore.getInstance(storeType);
+ keyStore.load(input, password);
+ return true;
+ } catch (NoSuchAlgorithmException | CertificateException e) {
+ LOG.failedToLoadKeystore(keyStoreFilePath.toString(), storeType, e);
+ } catch (IOException | KeyStoreException e) {
+ LOG.failedToLoadKeystore(keyStoreFilePath.toString(), storeType, e);
+ throw e;
+ }
+ }
+ return false;
+ }
+
+ // Package private for unit test access
+ KeyStore createKeyStore(Path keystoreFilePath, String keystoreType, char[]
password) throws KeystoreServiceException {
+ if (Files.notExists(keystoreFilePath)) {
+ // Ensure the parent directory exists...
+ try {
+ // This will attempt to create all missing directories. No failures
will occur if the
+ // directories already exist.
+ Files.createDirectories(keystoreFilePath.getParent());
+ } catch (IOException e) {
+ LOG.failedToCreateKeystore(keystoreFilePath.toString(), keystoreType,
e);
+ throw new KeystoreServiceException(e);
+ }
+ }
+
+ try (OutputStream out = Files.newOutputStream(keystoreFilePath)) {
+ KeyStore ks = KeyStore.getInstance(keystoreType);
+ ks.load(null, null);
+ ks.store(out, password);
+ return ks;
+ } catch (NoSuchAlgorithmException | CertificateException |
KeyStoreException | IOException e) {
+ LOG.failedToCreateKeystore(keystoreFilePath.toString(), keystoreType, e);
+ throw new KeystoreServiceException(e);
+ }
+ }
+
+ private void addCredential(String alias, String value, KeyStore ks) {
+ if (ks != null) {
+ try {
+ final Key key = new
SecretKeySpec(value.getBytes(StandardCharsets.UTF_8), "AES");
+ ks.setKeyEntry(alias, key, masterService.getMasterSecret(), null);
+ } catch (KeyStoreException e) {
+ LOG.failedToAddCredential(e);
+ }
+ }
+ }
+
+ private void removeCredential(String alias, KeyStore ks) {
+ if (ks != null) {
+ try {
+ if (ks.containsAlias(alias)) {
+ ks.deleteEntry(alias);
+ }
+ } catch (KeyStoreException e) {
+ LOG.failedToRemoveCredential(e);
+ }
+ }
+ }
+
+ // Package private for unit test access
+ KeyStore loadKeyStore(final Path keyStoreFilePath, final String storeType,
final char[] password) throws KeystoreServiceException {
+ try {
+ final KeyStore keyStore = KeyStore.getInstance(storeType);
+
+ // If the file does not exist, create an empty keystore
+ if (Files.exists(keyStoreFilePath)) {
+ try (InputStream input = Files.newInputStream(keyStoreFilePath)) {
+ keyStore.load(input, password);
+ }
+ } else {
+ keyStore.load(null, password);
+ }
+ return keyStore;
+ } catch (CertificateException | NoSuchAlgorithmException |
KeyStoreException | IOException e) {
+ LOG.failedToLoadKeystore(keyStoreFilePath.toString(), storeType, e);
+ throw new KeystoreServiceException(e);
+ }
+ }
+
+ // Package private for unit test access
+ void writeKeyStoreToFile(final KeyStore keyStore, final Path path, char[]
password)
+ throws KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException {
+ // TODO: backup the keystore on disk before attempting a write and restore
on failure
+ try (OutputStream out = Files.newOutputStream(path)) {
+ keyStore.store(out, password);
+ }
+ }
+
+ private char[] getKeyStorePassword(String alias) throws
KeystoreServiceException {
char[] password = null;
if (StringUtils.isNotEmpty(alias)) {
password = getCredentialForCluster(NO_CLUSTER_NAME, alias);
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreServiceTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreServiceTest.java
index 52efc40..ebfdc57 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreServiceTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/security/impl/DefaultKeystoreServiceTest.java
@@ -20,6 +20,13 @@
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;
@@ -28,20 +35,42 @@ import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
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.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import java.io.File;
+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.Locale;
public class DefaultKeystoreServiceTest {
@Rule
@@ -49,10 +78,10 @@ public class DefaultKeystoreServiceTest {
@Test
public void testGetTruststoreForHttpClientDefaults() throws Exception {
- final File dataDir = testFolder.newFolder();
+ final Path dataDir = testFolder.newFolder().toPath();
GatewayConfigImpl config = new GatewayConfigImpl();
- config.set("gateway.data.dir", dataDir.getAbsolutePath());
+ config.set("gateway.data.dir", dataDir.toString());
KeyStore keystore = createNiceMock(KeyStore.class);
@@ -72,25 +101,25 @@ public class DefaultKeystoreServiceTest {
@Test
public void testGetTruststoreForHttpClientCustomTrustStore() throws
Exception {
- final File dataDir = testFolder.newFolder();
- final File truststoreFile = testFolder.newFile();
+ 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.getAbsolutePath());
- config.set("gateway.httpclient.truststore.path",
truststoreFile.getAbsolutePath());
+ 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("getKeystore", File.class, String.class, char[].class)
+ .addMockedMethod("loadKeyStore", Path.class, String.class,
char[].class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
- expect(keystoreService.getKeystore(eq(truststoreFile), eq(truststoreType),
eq(truststorePassword)))
+ expect(keystoreService.loadKeyStore(eq(truststoreFile),
eq(truststoreType), eq(truststorePassword)))
.andReturn(keystore)
.once();
expect(keystoreService.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME),
eq(truststorePasswordAlias)))
@@ -108,19 +137,18 @@ public class DefaultKeystoreServiceTest {
@Test(expected = KeystoreServiceException.class)
public void testGetTruststoreForHttpClientMissingCustomTrustStore() throws
Exception {
- final File dataDir = testFolder.newFolder();
+ 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.getAbsolutePath());
- config.set("gateway.httpclient.truststore.path",
Paths.get(dataDir.getAbsolutePath(), "missing_file.jks").toString());
+ 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("getKeystore", File.class, String.class, char[].class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.createMock();
expect(keystoreService.getCredentialForCluster(eq(AliasService.NO_CLUSTER_NAME),
eq(truststorePasswordAlias)))
@@ -138,24 +166,24 @@ public class DefaultKeystoreServiceTest {
@Test
public void
testGetTruststoreForHttpClientCustomTrustStoreMissingPasswordAlias() throws
Exception {
- final File dataDir = testFolder.newFolder();
- final File truststoreFile = testFolder.newFile();
+ 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.getAbsolutePath());
- config.set("gateway.httpclient.truststore.path",
truststoreFile.getAbsolutePath());
+ 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("getKeystore", File.class, String.class, char[].class)
+ .addMockedMethod("loadKeyStore", Path.class, String.class,
char[].class)
.addMockedMethod("getCredentialForCluster", String.class, String.class)
.withConstructor()
.createMock();
- expect(keystoreService.getKeystore(eq(truststoreFile), eq(truststoreType),
eq(masterSecret)))
+ 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)))
@@ -175,4 +203,427 @@ public class DefaultKeystoreServiceTest {
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();
+
+ replay(keystoreServiceAlt, 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);
+
+ // 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));
+
+ verify(keystoreServiceAlt, 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 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);
+ }
+
+ 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);
+ }
}
\ No newline at end of file
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
index 589e24d..2a5389b 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/i18n/GatewaySpiMessages.java
@@ -37,21 +37,6 @@ public interface GatewaySpiMessages {
@Message( level = MessageLevel.ERROR, text = "Failed to generate secret key
from password: {0}" )
void failedToGenerateKeyFromPassword( @StackTrace( level =
MessageLevel.DEBUG ) Exception e );
- @Message( level = MessageLevel.ERROR, text = "Failed to create keystore
[filename={0}, type={1}]: {2}" )
- void failedToCreateKeystore( String fileName, String keyStoreType,
@StackTrace( level = MessageLevel.DEBUG ) Exception e );
-
- @Message( level = MessageLevel.ERROR, text = "Failed to load keystore
[filename={0}, type={1}]: {2}" )
- void failedToLoadKeystore( String fileName, String keyStoreType,
@StackTrace( level = MessageLevel.DEBUG ) Exception e );
-
- @Message( level = MessageLevel.ERROR, text = "Failed to add credential: {1}"
)
- void failedToAddCredential( @StackTrace( level = MessageLevel.DEBUG )
Exception e );
-
- @Message(level = MessageLevel.ERROR, text = "Failed to remove credential:
{1}")
- void failedToRemoveCredential(@StackTrace(level = MessageLevel.DEBUG)
Exception e);
-
- @Message( level = MessageLevel.ERROR, text = "Failed to get credential: {1}"
)
- void failedToGetCredential(@StackTrace( level = MessageLevel.DEBUG )
Exception e);
-
@Message( level = MessageLevel.ERROR, text = "Failed to persist master
secret: {0}" )
void failedToPersistMasterSecret( @StackTrace( level = MessageLevel.DEBUG )
Exception e );
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/BaseKeystoreService.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/BaseKeystoreService.java
deleted file mode 100644
index ffa5759..0000000
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/BaseKeystoreService.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * 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 org.apache.commons.codec.binary.Base64;
-import org.apache.knox.gateway.i18n.GatewaySpiMessages;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
-import org.apache.knox.gateway.services.security.MasterService;
-
-import javax.crypto.spec.SecretKeySpec;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateException;
-
-public class BaseKeystoreService {
- private static GatewaySpiMessages LOG = MessagesFactory.get(
GatewaySpiMessages.class );
-
- protected MasterService masterService;
- protected String keyStoreDir;
-
- private static KeyStore loadKeyStore(final File keyStoreFile, final char[]
masterPassword, String storeType)
- throws CertificateException, IOException, KeyStoreException,
NoSuchAlgorithmException {
- final KeyStore keyStore = KeyStore.getInstance(storeType);
- if ( keyStoreFile.exists() ) {
- try (InputStream input =
Files.newInputStream(keyStoreFile.toPath())) {
- keyStore.load( input, masterPassword );
- }
- } else {
- keyStore.load( null, masterPassword );
- }
-
- return keyStore;
- }
-
- private static OutputStream createKeyStoreFile(String fileName ) throws
IOException {
- File file = new File( fileName );
- if( file.exists() ) {
- if( file.isDirectory() ) {
- throw new IOException( file.getAbsolutePath() );
- } else if( !file.canWrite() ) {
- throw new IOException( file.getAbsolutePath() );
- }
- } else {
- File dir = file.getParentFile();
- if( !dir.exists() ) {
- if( !dir.mkdirs() ) {
- throw new IOException( file.getAbsolutePath() );
- }
- }
- }
- return Files.newOutputStream( file.toPath() );
- }
-
- protected void createKeystore(String filename, String keystoreType, char[]
password) throws KeystoreServiceException {
- try (OutputStream out = createKeyStoreFile( filename )) {
- KeyStore ks = KeyStore.getInstance(keystoreType);
- ks.load( null, null );
- ks.store( out, password );
- } catch (NoSuchAlgorithmException | CertificateException |
KeyStoreException | IOException e) {
- LOG.failedToCreateKeystore( filename, keystoreType, e );
- throw new KeystoreServiceException(e);
- }
- }
-
- protected boolean isKeystoreAvailable(final File keyStoreFile, String
storeType, char[] password) throws KeyStoreException, IOException {
- if ( keyStoreFile.exists() ) {
- try (InputStream input = Files.newInputStream(keyStoreFile.toPath())){
- final KeyStore keyStore = KeyStore.getInstance(storeType);
- keyStore.load( input, password );
- return true;
- } catch (NoSuchAlgorithmException | CertificateException e) {
- LOG.failedToLoadKeystore( keyStoreFile.getName(), storeType, e );
- } catch (IOException | KeyStoreException e) {
- LOG.failedToLoadKeystore( keyStoreFile.getName(), storeType, e );
- throw e;
- }
- }
- return false;
- }
-
- protected KeyStore getKeystore(final File keyStoreFile, String storeType,
char[] password) throws KeystoreServiceException {
- KeyStore credStore;
- try {
- credStore = loadKeyStore( keyStoreFile, password, storeType);
- } catch (CertificateException | IOException | NoSuchAlgorithmException |
KeyStoreException e) {
- LOG.failedToLoadKeystore( keyStoreFile.getName(), storeType, e );
- throw new KeystoreServiceException(e);
- }
- return credStore;
- }
-
- public BaseKeystoreService() {
- super();
- }
-
- protected void addCredential(String alias, String value, KeyStore ks) {
- if (ks != null) {
- try {
- final Key key = new
SecretKeySpec(value.getBytes(StandardCharsets.UTF_8), "AES");
- ks.setKeyEntry( alias, key, masterService.getMasterSecret(), null);
- } catch (KeyStoreException e) {
- LOG.failedToAddCredential(e);
- }
- }
- }
-
- public void removeCredential(String alias, KeyStore ks) {
- if (ks != null) {
- try {
- if (ks.containsAlias(alias)) {
- ks.deleteEntry(alias);
- }
- } catch (KeyStoreException e) {
- LOG.failedToRemoveCredential(e);
- }
- }
- }
-
- protected char[] getCredential(String alias, char[] credential, KeyStore ks)
{
- if (ks != null) {
- try {
- credential = new String(ks.getKey(alias,
masterService.getMasterSecret()).getEncoded(),
StandardCharsets.UTF_8).toCharArray();
- } catch (UnrecoverableKeyException | NoSuchAlgorithmException |
KeyStoreException e) {
- LOG.failedToGetCredential(e);
- }
- }
- return credential;
- }
-
- protected void writeCertificateToFile( Certificate cert, final File file )
throws CertificateEncodingException, IOException {
- byte[] bytes = cert.getEncoded();
- Base64 encoder = new Base64( 76, "\n".getBytes( StandardCharsets.US_ASCII
) );
- try( OutputStream out = Files.newOutputStream( file.toPath() ) ) {
- out.write( "-----BEGIN CERTIFICATE-----\n".getBytes(
StandardCharsets.US_ASCII ) );
- out.write( encoder.encodeToString( bytes ).getBytes(
StandardCharsets.US_ASCII ) );
- out.write( "-----END CERTIFICATE-----\n".getBytes(
StandardCharsets.US_ASCII ) );
- }
- }
-
- protected void writeKeystoreToFile(final KeyStore keyStore, final File file,
char[] password)
- throws KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException {
- // TODO: backup the keystore on disk before attempting a write and
restore on failure
- try( OutputStream out = Files.newOutputStream(file.toPath()) ) {
- keyStore.store( out, password );
- }
- }
-
- public void setMasterService(MasterService ms) {
- this.masterService = ms;
- }
-}
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreService.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreService.java
deleted file mode 100644
index 205db12..0000000
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreService.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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 java.io.File;
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-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.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.security.cert.X509Certificate;
-
-import org.apache.knox.gateway.i18n.GatewaySpiMessages;
-import org.apache.knox.gateway.i18n.messages.MessagesFactory;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
-import org.apache.knox.gateway.util.X509CertificateUtil;
-
-public class CMFKeystoreService extends BaseKeystoreService {
- private static GatewaySpiMessages LOG = MessagesFactory.get(
GatewaySpiMessages.class );
-
- private static final String TEST_CERT_DN =
"CN=hadoop,OU=Test,O=Hadoop,L=Test,ST=Test,C=US";
- private static final String CREDENTIALS_SUFFIX = "-credentials.jceks";
-
- private String serviceName;
-
- public CMFKeystoreService(String keystoreDir, String serviceName)
- throws ServiceLifecycleException {
- this.serviceName = serviceName;
- this.keyStoreDir = keystoreDir + File.separator;
- File ksd = new File(this.keyStoreDir);
- if (!ksd.exists() && !ksd.mkdirs()) {
- throw new ServiceLifecycleException("Cannot create the keystore
directory");
- }
- }
-
- public void createKeystore() throws KeystoreServiceException {
- String filename = keyStoreDir + serviceName + ".jks";
- createKeystore(filename, "JKS", masterService.getMasterSecret());
- }
-
- public KeyStore getKeystore() throws KeystoreServiceException {
- final File keyStoreFile = new File( keyStoreDir + serviceName );
- return getKeystore(keyStoreFile, "JKS", masterService.getMasterSecret());
- }
-
- public void addSelfSignedCert(String alias, char[] passphrase)
- throws KeystoreServiceException {
- KeyPairGenerator keyPairGenerator;
- try {
- keyPairGenerator = KeyPairGenerator.getInstance("RSA");
- keyPairGenerator.initialize(2048);
- KeyPair KPair = keyPairGenerator.generateKeyPair();
- X509Certificate cert =
X509CertificateUtil.generateCertificate(TEST_CERT_DN, KPair, 365,
"SHA1withRSA");
-
- KeyStore privateKS = getKeystore();
- if (privateKS != null) {
- privateKS.setKeyEntry(alias, KPair.getPrivate(),
- passphrase,
- new java.security.cert.Certificate[]{cert});
- writeKeystoreToFile(privateKS, new File( keyStoreDir + serviceName ),
masterService.getMasterSecret());
- } else {
- throw new IOException("Unable to open gateway keystore.");
- }
- } catch (IOException | GeneralSecurityException e) {
- LOG.failedToAddSeflSignedCertForGateway(alias, e);
- }
- }
-
- public void createCredentialStore() throws KeystoreServiceException {
- String filename = keyStoreDir + serviceName + CREDENTIALS_SUFFIX;
- createKeystore(filename, "JCEKS", masterService.getMasterSecret());
- }
-
- public boolean isCredentialStoreAvailable() throws KeystoreServiceException {
- final File keyStoreFile = new File( keyStoreDir + serviceName +
CREDENTIALS_SUFFIX );
- try {
- return isKeystoreAvailable(keyStoreFile, "JCEKS",
masterService.getMasterSecret());
- } catch (KeyStoreException | IOException e) {
- throw new KeystoreServiceException(e);
- }
- }
-
- public boolean isKeystoreAvailable() throws KeystoreServiceException {
- final File keyStoreFile = new File( keyStoreDir + serviceName + ".jks" );
- try {
- return isKeystoreAvailable(keyStoreFile, "JKS",
masterService.getMasterSecret());
- } catch (KeyStoreException | IOException e) {
- throw new KeystoreServiceException(e);
- }
- }
-
- public Key getKey(String alias, char[] passphrase) throws
KeystoreServiceException {
- Key key = null;
- KeyStore ks = getKeystore();
- if (ks != null) {
- try {
- key = ks.getKey(alias, passphrase);
- } catch (UnrecoverableKeyException | KeyStoreException |
NoSuchAlgorithmException e) {
- LOG.failedToGetKey(alias, e);
- }
- }
- return key;
- }
-
- public KeyStore getCredentialStore() throws KeystoreServiceException {
- final File keyStoreFile = new File( keyStoreDir + serviceName +
CREDENTIALS_SUFFIX );
- return getKeystore(keyStoreFile, "JCEKS", masterService.getMasterSecret());
- }
-
- public void addCredential(String alias, String value) throws
KeystoreServiceException {
- KeyStore ks = getCredentialStore();
- addCredential(alias, value, ks);
- final File keyStoreFile = new File( keyStoreDir + serviceName +
CREDENTIALS_SUFFIX );
- try {
- writeKeystoreToFile(ks, keyStoreFile, masterService.getMasterSecret());
- } catch (KeyStoreException | NoSuchAlgorithmException |
CertificateException | IOException e) {
- LOG.failedToAddCredential(e);
- }
- }
-
- public char[] getCredential(String alias) throws KeystoreServiceException {
- char[] credential = null;
- KeyStore ks = getCredentialStore();
- credential = getCredential(alias, credential, ks);
- return credential;
- }
-
-}
diff --git
a/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreServiceTest.java
b/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreServiceTest.java
deleted file mode 100644
index 8a21782..0000000
---
a/gateway-spi/src/test/java/org/apache/knox/gateway/services/security/impl/CMFKeystoreServiceTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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 java.io.File;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.util.Map;
-
-import org.apache.knox.gateway.config.GatewayConfig;
-import org.apache.knox.gateway.services.ServiceLifecycleException;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
-import org.apache.knox.gateway.services.security.MasterService;
-import org.apache.knox.test.category.FastTests;
-import org.apache.knox.test.category.UnitTests;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
-*
-*/
-@Category( { UnitTests.class, FastTests.class } )
-public class CMFKeystoreServiceTest {
- CMFKeystoreService ks;
- String aliasName = "TestAliasName";
- String secretValue = "AliasSecretValue";
- char[] password = { 'P', 'A', 'S', 'S' };
- File credentialsStoreFile = new File("ambari-credentials.jceks");
- File keyStoreFile = new File("ambari.jks");
- File certificateFile = new File("ambari");
-
- @Before
- public void setUp() {
- try {
- ks = new CMFKeystoreService(".", "ambari");
- ks.setMasterService(new MasterService() {
-
- public void init(GatewayConfig config, Map<String, String> options)
- throws ServiceLifecycleException {
- }
-
- public void start() throws ServiceLifecycleException {}
-
- public void stop() throws ServiceLifecycleException {}
-
- @Override
- public char[] getMasterSecret() {
- return "testmaster".toCharArray();
- }
-
- });
- } catch (ServiceLifecycleException e) {
- e.printStackTrace();
- }
- }
-
- @Test
- public void testCreationOfStoreForCredential() throws
KeystoreServiceException {
- try {
- ks.createCredentialStore();
- assertTrue("Credential Store file is not created",
ks.isCredentialStoreAvailable()
- && credentialsStoreFile.exists());
- KeyStore credentialStore = ks.getCredentialStore();
- assertTrue("Credential Store file is not created with proper file type",
- ("JCEKS").equalsIgnoreCase(credentialStore.getType()));
- } finally {
- credentialsStoreFile.deleteOnExit();
- }
- }
-
- @Test
- public void testCreationOfKeyStore() throws KeystoreServiceException {
- try {
- ks.createKeystore();
- assertTrue("Key Store file is not created", ks.isKeystoreAvailable() &&
keyStoreFile.exists());
- KeyStore keystore = ks.getKeystore();
- assertTrue("Key Store file is not created with proper file type",
- ("JKS").equalsIgnoreCase(keystore.getType()));
- ks.createCredentialStore();
- ks.addCredential(aliasName, "secretValue");
- } finally {
- keyStoreFile.deleteOnExit();
- credentialsStoreFile.deleteOnExit();
- }
- }
-
- @Test
- public void testAdditionOfCredentialsToKeyStore() throws
KeystoreServiceException {
- try {
- ks.createKeystore();
- ks.createCredentialStore();
- ks.addCredential(aliasName, "secretValue");
- char[] secret = ks.getCredential(aliasName);
- assertEquals("Addition of Credentials failed", "secretValue", new
String(secret));
- } finally {
- credentialsStoreFile.deleteOnExit();
- keyStoreFile.deleteOnExit();
- }
- }
-
- @Test
- public void testAdditionOfAliasWithSelfSignedCertificate() throws
KeystoreServiceException,
- KeyStoreException {
- try {
- ks.createKeystore();
- ks.createCredentialStore();
- ks.addCredential(aliasName, "secretValue");
- ks.addSelfSignedCert(aliasName, password);
- KeyStore keystore = ks.getKeystore();
- assertTrue("Addition of Alias with Self Signed Certificate failed",
- !keystore.getCertificate(aliasName).toString().isEmpty() &&
certificateFile.exists());
- } finally {
- credentialsStoreFile.deleteOnExit();
- keyStoreFile.deleteOnExit();
- certificateFile.deleteOnExit();
- }
- }
-
- @Test
- public void testFetchOfAliasKey() throws KeystoreServiceException {
- try {
- ks.createKeystore();
- ks.createCredentialStore();
- ks.addCredential(aliasName, "secretValue");
- ks.addSelfSignedCert(aliasName, password);
- assertTrue("Fetch of AliasKey failed", !ks.getKey(aliasName,
password).toString().isEmpty()
- && certificateFile.exists());
- } finally {
- credentialsStoreFile.deleteOnExit();
- keyStoreFile.deleteOnExit();
- certificateFile.deleteOnExit();
- }
- }
-}