You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2015/10/02 23:12:19 UTC
[15/32] ambari git commit: AMBARI-13214. Create a credentials
resource used to securely set, update,
and remove credentials used by Ambari (rlevas)
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/credential/PrincipalKeyCredential.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/credential/PrincipalKeyCredential.java b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/PrincipalKeyCredential.java
new file mode 100644
index 0000000..55bf076
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/credential/PrincipalKeyCredential.java
@@ -0,0 +1,154 @@
+/*
+ * 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.ambari.server.security.credential;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+
+import java.util.Arrays;
+
+/**
+ * PrincipalKeyCredential encapsulates a credential consisting of a principal (or username) and
+ * a (secret) key.
+ */
+public class PrincipalKeyCredential implements Credential {
+
+ private static final String VALUE_PREFIX = "PrincipalKeyCredential";
+ /**
+ * This principal value
+ */
+ private String principal;
+
+ /**
+ * The plaintext key value
+ */
+ private char[] key;
+
+ /**
+ * Creates an empty PrincipalKeyCredential
+ */
+ public PrincipalKeyCredential() {
+ this(null, (char[]) null);
+ }
+
+ /**
+ * Creates a new PrincipalKeyCredential
+ *
+ * @param principal a String containing the principal name for this credential
+ * @param key a String containing the secret key for this credential
+ */
+ public PrincipalKeyCredential(String principal, String key) {
+ this(principal, (key == null) ? null : key.toCharArray());
+ }
+
+ /**
+ * Creates a new PrincipalKeyCredential
+ *
+ * @param principal a String containing the principal name for this credential
+ * @param key a char array containing the secret key for this credential
+ */
+ public PrincipalKeyCredential(String principal, char[] key) {
+ this.principal = principal;
+ this.key = key;
+ }
+
+ /**
+ * @return a String containing the principal name for this credential
+ */
+ public String getPrincipal() {
+ return principal;
+ }
+
+ /**
+ * @param principal a String containing the principal name for this credential
+ */
+ public void setPrincipal(String principal) {
+ this.principal = principal;
+ }
+
+ /**
+ * @return a char array containing the secret key for this credential
+ */
+ public char[] getKey() {
+ return key;
+ }
+
+ /**
+ * @param key a char array containing the secret key for this credential
+ */
+ public void setKey(char[] key) {
+ this.key = key;
+ }
+
+
+ /**
+ * Returns a value representation of this PrincipalKeyCredential
+ *
+ * @return a String containing the value representation of this PrincipalKeyCredential
+ */
+ public char[] toValue() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("PrincipalKeyCredential");
+ builder.append(new Gson().toJson(this));
+ return builder.toString().toCharArray();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ } else if (this == obj) {
+ return true;
+ } else if (obj.getClass() == PrincipalKeyCredential.class) {
+ PrincipalKeyCredential other = (PrincipalKeyCredential) obj;
+ return ((this.principal == null) ? (other.principal == null) : this.principal.equals(other.principal)) &&
+ ((this.key == null) ? (other.key == null) : Arrays.equals(this.key, other.key));
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return ((principal == null) ? 0 : principal.hashCode()) +
+ ((key == null) ? 0 : Arrays.hashCode(key));
+ }
+
+ /**
+ * Renders a new PrincipalKeyCredential from its value representation
+ *
+ * @param value a String containing the value representation of this PrincipalKeyCredential
+ * @return a new PrincipalKeyCredential or null if a new PrincipalKeyCredential cannot be created
+ */
+ public static PrincipalKeyCredential fromValue(String value) throws InvalidCredentialValueException {
+ if (isValidValue(value)) {
+ value = value.substring(VALUE_PREFIX.length());
+ try {
+ return (value.isEmpty()) ? null : new Gson().fromJson(value, PrincipalKeyCredential.class);
+ } catch (JsonSyntaxException e) {
+ throw new InvalidCredentialValueException("The value does not represent a PrincipalKeyCredential", e);
+ }
+ } else {
+ throw new InvalidCredentialValueException("The value does not represent a PrincipalKeyCredential");
+ }
+ }
+
+ public static boolean isValidValue(String value) {
+ return ((value != null) && value.startsWith(VALUE_PREFIX));
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AbstractCredentialStore.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AbstractCredentialStore.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AbstractCredentialStore.java
new file mode 100644
index 0000000..96faa4a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/AbstractCredentialStore.java
@@ -0,0 +1,415 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.security.encryption;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.security.credential.Credential;
+import org.apache.ambari.server.security.credential.CredentialFactory;
+
+import javax.crypto.spec.SecretKeySpec;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * AbstractCredentialStore is an abstract implementation of CredentialStore that loads and
+ * stores @{link KeyStore} data. Implementations of this class, provide the input and output streams
+ * used to read and write the data.
+ */
+public abstract class AbstractCredentialStore implements CredentialStore {
+ protected static final String DEFAULT_STORE_TYPE = "JCEKS";
+
+ /**
+ * A lock object to lock access to the credential store, allowing only one thread to access
+ * the credential store at a time.
+ */
+ private final Lock lock = new ReentrantLock();
+
+ /**
+ * The MasterKeyService containing the key used to encrypt the KeyStore data
+ */
+ private MasterKeyService masterKeyService;
+
+ /**
+ * Adds a new credential to this CredentialStore
+ * <p/>
+ * The supplied key will be converted into UTF-8 bytes before being stored.
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential to store
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ @Override
+ public void addCredential(String alias, Credential credential) throws AmbariException {
+ if ((alias == null) || alias.isEmpty()) {
+ throw new IllegalArgumentException("Alias cannot be null or empty.");
+ }
+
+ lock.lock();
+ try {
+ KeyStore ks = loadCredentialStore();
+ addCredential(ks, alias, credential);
+ persistCredentialStore(ks);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Retrieves the specified credential from this CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return a Credential or null of not found
+ * @throws AmbariException if an error occurs while retrieving the new credential
+ */
+ @Override
+ public Credential getCredential(String alias) throws AmbariException {
+ if (alias == null) {
+ return null;
+ } else {
+ lock.lock();
+ try {
+ return getCredential(loadCredentialStore(), alias);
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * Removes the specified credential from this CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @throws AmbariException if an error occurs while removing the new credential
+ */
+ @Override
+ public void removeCredential(String alias) throws AmbariException {
+ if ((alias != null) && !alias.isEmpty()) {
+ lock.lock();
+ try {
+ KeyStore ks = loadCredentialStore();
+ if (ks != null) {
+ try {
+ ks.deleteEntry(alias);
+ persistCredentialStore(ks);
+ } catch (KeyStoreException e) {
+ throw new AmbariException("Failed to delete the KeyStore entry - the key store may not have been initialized", e);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * Returns a list of the alias names for the credentials stored in the CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @return a Set of Strings representing alias names for the credentials stored in the CredentialStore
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ @Override
+ public Set<String> listCredentials() throws AmbariException {
+ Set<String> credentials = null;
+
+ lock.lock();
+ try {
+ KeyStore ks = loadCredentialStore();
+ if (ks != null) {
+ try {
+ Enumeration<String> aliases = ks.aliases();
+
+ if (aliases != null) {
+ credentials = new HashSet<String>();
+ while (aliases.hasMoreElements()) {
+ credentials.add(aliases.nextElement());
+ }
+ }
+ } catch (KeyStoreException e) {
+ throw new AmbariException("Failed to read KeyStore - the key store may not have been initialized", e);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ return credentials;
+ }
+
+ /**
+ * Tests this CredentialStore for the existence of a credential with the specified alias
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return true if the alias exists; otherwise false
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ @Override
+ public boolean containsCredential(String alias) throws AmbariException {
+ boolean exists = false;
+
+ if ((alias != null) && !alias.isEmpty()) {
+ lock.lock();
+ try {
+ KeyStore ks = loadCredentialStore();
+ if (ks != null) {
+ try {
+ exists = ks.containsAlias(alias);
+ } catch (KeyStoreException e) {
+ throw new AmbariException("Failed to search the KeyStore for the requested entry - the key store may not have been initialized", e);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ return exists;
+ }
+
+ @Override
+ public void setMasterKeyService(MasterKeyService masterKeyService) {
+ this.masterKeyService = masterKeyService;
+ }
+
+ /**
+ * Adds a new credential to the supplied KeyStore
+ * <p/>
+ * The supplied key will be converted into UTF-8 bytes before being stored.
+ *
+ * @param keyStore the KeyStore
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential to store
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ protected void addCredential(KeyStore keyStore, String alias, Credential credential) throws AmbariException {
+ if (keyStore != null) {
+ try {
+ Key key;
+ char[] value = (credential == null) ? null : credential.toValue();
+
+ if ((value == null) || (value.length == 0)) {
+ key = null;
+ } else {
+ key = new SecretKeySpec(toBytes(value), "AES");
+ }
+
+ keyStore.setKeyEntry(alias, key, masterKeyService.getMasterSecret(), null);
+ } catch (KeyStoreException e) {
+ throw new AmbariException("The key store has not been initialized", e);
+ }
+ }
+ }
+
+ /**
+ * Retrieves the specified credential from a KeyStore
+ *
+ * @param keyStore the KeyStore
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return an array of chars containing the credential
+ * @throws AmbariException if an error occurs while retrieving the new credential
+ */
+ protected Credential getCredential(KeyStore keyStore, String alias) throws AmbariException {
+ char[] value = null;
+
+ if (keyStore != null) {
+ try {
+ Key key = keyStore.getKey(alias, masterKeyService.getMasterSecret());
+ if (key != null) {
+ value = toChars(key.getEncoded());
+ }
+ } catch (UnrecoverableKeyException e) {
+ throw new AmbariException("The key cannot be recovered (e.g., the given password is wrong)", e);
+ } catch (KeyStoreException e) {
+ throw new AmbariException("The key store has not been initialized", e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AmbariException(" if the algorithm for recovering the key cannot be found", e);
+ }
+ }
+
+ return CredentialFactory.createCredential(value);
+ }
+
+ /**
+ * Calls the implementation-specific facility to persist the KeyStore
+ *
+ * @param keyStore the KeyStore to persist
+ * @throws AmbariException if an error occurs while persisting the key store data
+ */
+ protected abstract void persistCredentialStore(KeyStore keyStore) throws AmbariException;
+
+ /**
+ * Calls the implementation-specific facility to load the KeyStore
+ *
+ * @throws AmbariException if an error occurs while loading the key store data
+ */
+ protected abstract KeyStore loadCredentialStore() throws AmbariException;
+
+
+ /**
+ * Gets the lock object used to protect access to the credential store
+ *
+ * @return a Lock object
+ */
+ protected Lock getLock() {
+ return lock;
+ }
+
+ /**
+ * Loads a KeyStore from an InputStream
+ * <p/>
+ * Implementations are expected to call this to load the relevant KeyStore data from the
+ * InputStream of some storage facility.
+ *
+ * @param inputStream the InputStream to read the data from
+ * @param keyStoreType the type of key store data expected
+ * @return a new KeyStore instance with the loaded data
+ * @throws AmbariException if an error occurs while loading the key store data from the InputStream
+ */
+ protected KeyStore loadKeyStore(InputStream inputStream, String keyStoreType) throws AmbariException {
+ if (masterKeyService == null) {
+ throw new AmbariException("Master Key Service is not set for this Credential store.");
+ }
+
+ KeyStore keyStore;
+ try {
+ keyStore = KeyStore.getInstance(keyStoreType);
+ } catch (KeyStoreException e) {
+ throw new AmbariException(String.format("No provider supports a key store implementation for the specified type: %s", keyStoreType), e);
+ }
+
+ try {
+ keyStore.load(inputStream, masterKeyService.getMasterSecret());
+ } catch (CertificateException e) {
+ throw new AmbariException(String.format("One or more credentials from the key store could not be loaded: %s", e.getLocalizedMessage()), e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AmbariException(String.format("The algorithm used to check the integrity of the key store cannot be found: %s", e.getLocalizedMessage()), e);
+ } catch (IOException e) {
+ if (e.getCause() instanceof UnrecoverableKeyException) {
+ throw new AmbariException(String.format("The password used to decrypt the key store is incorrect: %s", e.getLocalizedMessage()), e);
+ } else {
+ throw new AmbariException(String.format("Failed to read the key store: %s", e.getLocalizedMessage()), e);
+ }
+ }
+
+ return keyStore;
+ }
+
+ /**
+ * Writes a KeyStore to an OutputStream
+ * <p/>
+ * Implementations are expected to call this to write the relevant KeyStore data to the
+ * OutputStream of some storage facility.
+ *
+ * @param keyStore the KeyStore to write
+ * @param outputStream the OutputStream to write the data into
+ * @throws AmbariException if an error occurs while writing the key store data
+ */
+ protected void writeKeyStore(KeyStore keyStore, OutputStream outputStream) throws AmbariException {
+ if (masterKeyService == null) {
+ throw new AmbariException("Master Key Service is not set for this Credential store.");
+ }
+
+ try {
+ keyStore.store(outputStream, masterKeyService.getMasterSecret());
+ } catch (CertificateException e) {
+ throw new AmbariException(String.format("A credential within in the key store data could not be stored: %s", e.getLocalizedMessage()), e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AmbariException(String.format("The appropriate data integrity algorithm could not be found: %s", e.getLocalizedMessage()), e);
+ } catch (KeyStoreException e) {
+ throw new AmbariException(String.format("The key store has not been initialized: %s", e.getLocalizedMessage()), e);
+ } catch (IOException e) {
+ throw new AmbariException(String.format("Failed to write the key store: %s", e.getLocalizedMessage()), e);
+ }
+ }
+
+ /**
+ * Converts an array of characters to an array of bytes by encoding each character into UTF-8 bytes.
+ * <p/>
+ * An attempt is made to clear out sensitive data by filling any buffers with 0's
+ *
+ * @param chars the array of chars to convert
+ * @return an array of bytes, or null if the original array was null
+ */
+ protected byte[] toBytes(char[] chars) {
+ if (chars == null) {
+ return null;
+ } else {
+ CharBuffer charBuffer = CharBuffer.wrap(chars);
+ ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+
+ byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
+
+ // Clear sensitive data
+ Arrays.fill(charBuffer.array(), '\u0000');
+ Arrays.fill(byteBuffer.array(), (byte) 0);
+
+ return bytes;
+ }
+ }
+
+ /**
+ * Converts an array of bytes to an array of character by decoding the bytes using the UTF-8
+ * character set.
+ * <p/>
+ * An attempt is made to clear out sensitive data by filling any buffers with 0's
+ *
+ * @param bytes the array of bytes to convert
+ * @return an array of chars, or null if the original array was null
+ */
+ protected char[] toChars(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ } else {
+ ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
+
+ char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
+
+ // Clear sensitive data
+ Arrays.fill(charBuffer.array(), '\u0000');
+ Arrays.fill(byteBuffer.array(), (byte) 0);
+
+ return chars;
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialProvider.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialProvider.java
index b812337..0c420e0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialProvider.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialProvider.java
@@ -19,6 +19,8 @@ package org.apache.ambari.server.security.encryption;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.security.credential.Credential;
+import org.apache.ambari.server.security.credential.GenericKeyCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -39,20 +41,20 @@ public class CredentialProvider {
'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'2', '3', '4', '5', '6', '7', '8', '9'};
- private CredentialStoreService keystoreService;
+ private CredentialStore keystoreService;
static final Logger LOG = LoggerFactory.getLogger(CredentialProvider.class);
- public CredentialProvider(String masterKey, String masterKeyLocation,
- boolean isMasterKeyPersisted) throws AmbariException {
+ public CredentialProvider(String masterKey, File masterKeyLocation,
+ boolean isMasterKeyPersisted, File masterKeyStoreLocation) throws AmbariException {
MasterKeyService masterKeyService;
if (masterKey != null) {
masterKeyService = new MasterKeyServiceImpl(masterKey);
} else {
if (isMasterKeyPersisted) {
- if ((masterKeyLocation == null) || masterKeyLocation.isEmpty()) {
- throw new IllegalArgumentException("The master key file location may not be null or empty if the master key is persisted");
+ if (masterKeyLocation == null) {
+ throw new IllegalArgumentException("The master key file location must be specified if the master key is persisted");
}
- masterKeyService = new MasterKeyServiceImpl(new File(masterKeyLocation));
+ masterKeyService = new MasterKeyServiceImpl(masterKeyLocation);
} else {
masterKeyService = new MasterKeyServiceImpl();
}
@@ -60,17 +62,18 @@ public class CredentialProvider {
if (!masterKeyService.isMasterKeyInitialized()) {
throw new AmbariException("Master key initialization failed.");
}
- String storeDir = masterKeyLocation.substring(0,
- masterKeyLocation.indexOf(Configuration.MASTER_KEY_FILENAME_DEFAULT));
- this.keystoreService = new FileBasedCredentialStoreService(storeDir);
+ this.keystoreService = new FileBasedCredentialStore(masterKeyStoreLocation);
this.keystoreService.setMasterKeyService(masterKeyService);
}
public char[] getPasswordForAlias(String alias) throws AmbariException {
- if (isAliasString(alias)) {
- return keystoreService.getCredential(getAliasFromString(alias));
- }
- return keystoreService.getCredential(alias);
+ Credential credential = (isAliasString(alias))
+ ? keystoreService.getCredential(getAliasFromString(alias))
+ : keystoreService.getCredential(alias);
+
+ return (credential instanceof GenericKeyCredential)
+ ? ((GenericKeyCredential) credential).getKey()
+ : null;
}
public void generateAliasWithPassword(String alias) throws AmbariException {
@@ -86,7 +89,7 @@ public class CredentialProvider {
if (passwordString == null || passwordString.isEmpty()) {
throw new IllegalArgumentException("Empty or null password not allowed.");
}
- keystoreService.addCredential(alias, passwordString.toCharArray());
+ keystoreService.addCredential(alias, new GenericKeyCredential(passwordString.toCharArray()));
}
private String generatePassword(int length) {
@@ -110,7 +113,7 @@ public class CredentialProvider {
return strPasswd.substring(strPasswd.indexOf("=") + 1, strPasswd.length() - 1);
}
- protected CredentialStoreService getKeystoreService() {
+ protected CredentialStore getKeystoreService() {
return keystoreService;
}
@@ -144,7 +147,8 @@ public class CredentialProvider {
try {
credentialProvider = new CredentialProvider(masterKey,
configuration.getMasterKeyLocation(),
- configuration.isMasterKeyPersisted());
+ configuration.isMasterKeyPersisted(),
+ configuration.getMasterKeyStoreLocation());
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStore.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStore.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStore.java
new file mode 100644
index 0000000..efe5e23
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStore.java
@@ -0,0 +1,78 @@
+/*
+ * 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.ambari.server.security.encryption;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.security.credential.Credential;
+
+import java.util.Set;
+
+public interface CredentialStore {
+ /**
+ * Adds a new credential to this CredentialStore
+ * <p/>
+ * The supplied key will be converted into UTF-8 bytes before being stored.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential to store
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ void addCredential(String alias, Credential credential) throws AmbariException;
+
+ /**
+ * Retrieves the specified credential from this CredentialStore
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return a Credential or null of not found
+ * @throws AmbariException if an error occurs while retrieving the new credential
+ */
+ Credential getCredential(String alias) throws AmbariException;
+
+ /**
+ * Removes the specified credential from this CredentialStore
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @throws AmbariException if an error occurs while removing the new credential
+ */
+ void removeCredential(String alias) throws AmbariException;
+
+ /**
+ * Returns a list of the alias names for the credentials stored in the CredentialStore
+ *
+ * @return a Set of Strings representing alias names for the credentials stored in the CredentialStore
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ Set<String> listCredentials() throws AmbariException;
+
+ /**
+ * Tests this CredentialStore for the existence of a credential with the specified alias
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return true if the alias exists; otherwise false
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ boolean containsCredential(String alias) throws AmbariException;
+
+ /**
+ * Sets the MasterKeyService for this CredentialStore
+ *
+ * @param masterKeyService the MasterKeyService
+ */
+ void setMasterKeyService(MasterKeyService masterKeyService);
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreService.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreService.java
index 4aa3b0a..c21154e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreService.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -19,40 +19,117 @@
package org.apache.ambari.server.security.encryption;
import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.security.credential.Credential;
+
+import java.util.Map;
public interface CredentialStoreService {
/**
- * Adds a new credential to this CredentialStoreService
+ * Adds a new credential to ether the persistent or the temporary CredentialStore
* <p/>
* The supplied key will be converted into UTF-8 bytes before being stored.
*
- * @param alias a string declaring the alias (or name) of the credential
- * @param key an array of chars containing the credential
+ * @param clusterName the name of the cluster the credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential value to store
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
* @throws AmbariException if an error occurs while storing the new credential
*/
- void addCredential(String alias, char[] key) throws AmbariException;
+ void setCredential(String clusterName, String alias, Credential credential, CredentialStoreType credentialStoreType) throws AmbariException;
+
+ /**
+ * Retrieves the specified credential looking in the temporary and then the persistent CredentialStore
+ *
+ * @param clusterName the name of the cluster the credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return the requested Credential
+ * @throws AmbariException if an error occurs while retrieving the credential
+ */
+ Credential getCredential(String clusterName, String alias) throws AmbariException;
+
+ /**
+ * Retrieves the specified credential looking in ether the persistent or the temporary CredentialStore
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @return the requested Credential
+ * @throws AmbariException if an error occurs while retrieving the credential
+ */
+ Credential getCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException;
+
+ /**
+ * Removes the specified credential from all CredentialStores
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @throws AmbariException if an error occurs while removing the credential
+ */
+ void removeCredential(String clusterName, String alias) throws AmbariException;
+
+ /**
+ * Removes the specified credential from ether the persistent or the temporary CredentialStore
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @throws AmbariException if an error occurs while removing the credential
+ */
+ void removeCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException;
+
+ /**
+ * Tests to see if the requested alias exists in any CredentialStore
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return true if it exists; otherwise false
+ * @throws AmbariException if an error occurs while searching for the credential
+ */
+ boolean containsCredential(String clusterName, String alias) throws AmbariException;
+
+ /**
+ * Tests to see if the requested alias exists in ether the persistent or the temporary CredentialStore
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @return true if it exists; otherwise false
+ * @throws AmbariException if an error occurs while searching for the credential
+ */
+ boolean containsCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException;
+
+ /**
+ * Gets the type of the credential store used to store the requested credential
+ *
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return a CredentialStoreType
+ * @throws AmbariException if an error occurs while searching for the credential
+ */
+ CredentialStoreType getCredentialStoreType(String clusterName, String alias) throws AmbariException;
/**
- * Retrieves the specified credential from this CredentialStoreService
+ * Maps the existing alias names to their relevant credential store types.
*
- * @param alias a string declaring the alias (or name) of the credential
- * @return an array of chars containing the credential
- * @throws AmbariException if an error occurs while retrieving the new credential
+ * @param clusterName the name of the cluster this credential is related to
+ * @return a map of alias names to CredentialStoreTypes
+ * @throws AmbariException if an error occurs while searching for the credentials
*/
- char[] getCredential(String alias) throws AmbariException;
+ Map<String, CredentialStoreType> listCredentials(String clusterName) throws AmbariException;
/**
- * Removes the specified credential from this CredentialStoreService
+ * Tests this CredentialStoreService to check if it has been properly initialized
*
- * @param alias a string declaring the alias (or name) of the credential
- * @throws AmbariException if an error occurs while removing the new credential
+ * @return true if initialized; otherwise false
*/
- void removeCredential(String alias) throws AmbariException;
+ boolean isInitialized();
/**
- * Sets the MasterKeyService for this CredentialStoreService
+ * Tests this CredentialStoreService to check if ether the persistent or the temporary CredentialStore
+ * has been properly initialized
*
- * @param masterKeyService the MasterKeyService
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @return true if initialized; otherwise false
*/
- void setMasterKeyService(MasterKeyService masterKeyService);
+ boolean isInitialized(CredentialStoreType credentialStoreType);
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImpl.java
index 968e96a..fe14004 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreServiceImpl.java
@@ -1,4 +1,4 @@
-/**
+/*
* 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
@@ -15,266 +15,328 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.ambari.server.security.encryption;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.security.SecurePasswordHelper;
+import org.apache.ambari.server.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-import javax.crypto.spec.SecretKeySpec;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-import java.security.Key;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableKeyException;
-import java.security.cert.CertificateException;
-import java.util.Arrays;
-
-/**
- * CredentialStoreServiceImpl is an abstract implementation of CredentialStoreService that loads and
- * stores @{link KeyStore} data. Implementations of this class, provide the input and output streams
- * used to read and write the data.
- */
-public abstract class CredentialStoreServiceImpl implements CredentialStoreService {
- protected static final String DEFAULT_STORE_TYPE = "JCEKS";
+import java.io.File;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
- /**
- * The MasterKeyService containing the key used to encrypt the KeyStore data
- */
- private MasterKeyService masterKeyService;
+@Singleton
+public class CredentialStoreServiceImpl implements CredentialStoreService {
- @Override
- public void addCredential(String alias, char[] value) throws AmbariException {
- if ((alias == null) || alias.isEmpty()) {
- throw new IllegalArgumentException("Alias cannot be null or empty.");
- }
+ private static final Logger LOG = LoggerFactory.getLogger(CredentialStoreServiceImpl.class);
- KeyStore ks = loadCredentialStore();
- addCredential(ks, alias, value);
- persistCredentialStore(ks);
- }
+ private SecurePasswordHelper securePasswordHelper;
- @Override
- public char[] getCredential(String alias) throws AmbariException {
- if (alias == null) {
- return null;
- } else {
- return getCredential(loadCredentialStore(), alias);
- }
- }
+ private FileBasedCredentialStore persistedCredentialStore = null;
+ private InMemoryCredentialStore temporaryCredentialStore = null;
- @Override
- public void removeCredential(String alias) throws AmbariException {
- if ((alias != null) && !alias.isEmpty()) {
- KeyStore ks = loadCredentialStore();
- if (ks != null) {
+
+ @Inject
+ public CredentialStoreServiceImpl(Configuration configuration, SecurePasswordHelper securePasswordHelper) {
+
+ this.securePasswordHelper = securePasswordHelper;
+
+ if (configuration != null) {
+ File masterKeyLocation = configuration.getMasterKeyLocation();
+
+ try {
+ initializeTemporaryCredentialStore(configuration.getTemporaryKeyStoreRetentionMinutes(),
+ TimeUnit.MINUTES,
+ configuration.isActivelyPurgeTemporaryKeyStore());
+ LOG.info("Initialized the temporary credential store. KeyStore entries will be retained for {} minutes and {} be actively purged",
+ configuration.getTemporaryKeyStoreRetentionMinutes(), (configuration.isActivelyPurgeTemporaryKeyStore()) ? "will" : "will not");
+ } catch (AmbariException e) {
+ LOG.error("Failed to initialize the temporary credential store. Storage of temporary credentials will fail.", e);
+ }
+
+
+ // If the MasterKeyService is initialized, assume that we should be initializing the persistent
+ // CredentialStore; else do not initialize it.
+ MasterKeyService masterKeyService = new MasterKeyServiceImpl(masterKeyLocation);
+ if (masterKeyService.isMasterKeyInitialized()) {
try {
- ks.deleteEntry(alias);
- persistCredentialStore(ks);
- } catch (KeyStoreException e) {
- throw new AmbariException("Failed to delete the KeyStore entry - the key store may not have been initialized", e);
+ initializePersistedCredentialStore(configuration.getMasterKeyStoreLocation(), masterKeyService);
+ LOG.info("Initialized the persistent credential store. Using KeyStore file at {}", persistedCredentialStore.getKeyStorePath().getAbsolutePath());
+ } catch (AmbariException e) {
+ LOG.error("Failed to initialize the persistent credential store. Storage of persisted credentials will fail.", e);
}
}
}
}
- @Override
- public void setMasterKeyService(MasterKeyService masterKeyService) {
- this.masterKeyService = masterKeyService;
+ public synchronized void initializeTemporaryCredentialStore(long retentionDuration, TimeUnit units, boolean activelyPurge) throws AmbariException {
+ if (isInitialized(CredentialStoreType.TEMPORARY)) {
+ throw new AmbariException("This temporary CredentialStore has already been initialized");
+ }
+
+ temporaryCredentialStore = new InMemoryCredentialStore(retentionDuration, units, activelyPurge);
+ temporaryCredentialStore.setMasterKeyService(new MasterKeyServiceImpl(securePasswordHelper.createSecurePassword()));
+ }
+
+ public synchronized void initializePersistedCredentialStore(File credentialStoreLocation, MasterKeyService masterKeyService) throws AmbariException {
+ if (isInitialized(CredentialStoreType.PERSISTED)) {
+ throw new AmbariException("This persisted CredentialStore has already been initialized");
+ }
+
+ persistedCredentialStore = new FileBasedCredentialStore(credentialStoreLocation);
+ persistedCredentialStore.setMasterKeyService(masterKeyService);
}
/**
- * Adds a new credential to the supplied KeyStore
+ * Adds a new credential to either the persistent or the temporary CredentialStore
* <p/>
* The supplied key will be converted into UTF-8 bytes before being stored.
+ * <p/>
+ * The alias name will be canonicalized as follows:
+ * <ul>
+ * <li>if a cluster name is supplied, then "cluster.name." will be prepended to it</li>
+ * <li>the characters will converted to all lowercase</li>
+ * </ul>
+ * Only a single instance of the named credential will be stored, therefore if a credential with
+ * alias ALIAS1 is stored in the persisted CredentialStore and a call is made to store a credentials
+ * with alias ALIAS1 into the temporary CredentialStore, the instance in the persisted CredentialStore
+ * will be removed.
*
- * @param keyStore the KeyStore
- * @param alias a string declaring the alias (or name) of the credential
- * @param value an array of chars containing the credential
- * @throws AmbariException if an error occurs while storing the new credential
+ * @param clusterName the name of the cluster this credential is related to
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential value to store
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @throws AmbariException
*/
- protected void addCredential(KeyStore keyStore, String alias, char[] value) throws AmbariException {
- if (keyStore != null) {
- try {
- Key key;
+ @Override
+ public void setCredential(String clusterName, String alias, Credential credential, CredentialStoreType credentialStoreType) throws AmbariException {
+ validateInitialized(credentialStoreType);
- if ((value == null) || (value.length == 0)) {
- key = null;
- } else {
- key = new SecretKeySpec(toBytes(value), "AES");
- }
+ // Ensure only one copy of this alias exists.. either in the persisted or the temporary CertificateStore
+ removeCredential(clusterName, alias);
+ getCredentialStore(credentialStoreType).addCredential(canonicalizeAlias(clusterName, alias), credential);
+ }
- keyStore.setKeyEntry(alias, key, masterKeyService.getMasterSecret(), null);
- } catch (KeyStoreException e) {
- throw new AmbariException("The key store has not been initialized", e);
- }
+ @Override
+ public Credential getCredential(String clusterName, String alias) throws AmbariException {
+ // First check the temporary CredentialStore
+ Credential credential = getCredential(clusterName, alias, CredentialStoreType.TEMPORARY);
+
+ if (credential == null) {
+ // If needed, check the persisted CredentialStore
+ credential = getCredential(clusterName, alias, CredentialStoreType.PERSISTED);
}
+
+ return credential;
}
- /**
- * Retrieves the specified credential from a KeyStore
- *
- * @param keyStore the KeyStore
- * @param alias a string declaring the alias (or name) of the credential
- * @return an array of chars containing the credential
- * @throws AmbariException if an error occurs while retrieving the new credential
- */
- protected char[] getCredential(KeyStore keyStore, String alias) throws AmbariException {
- char[] credential = null;
+ @Override
+ public Credential getCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException {
+ return (isInitialized(credentialStoreType))
+ ? getCredentialStore(credentialStoreType).getCredential(canonicalizeAlias(clusterName, alias))
+ : null;
+ }
- if (keyStore != null) {
- try {
- Key key = keyStore.getKey(alias, masterKeyService.getMasterSecret());
- if (key != null) {
- credential = toChars(key.getEncoded());
+ @Override
+ public void removeCredential(String clusterName, String alias) throws AmbariException {
+ removeCredential(clusterName, alias, CredentialStoreType.PERSISTED);
+ removeCredential(clusterName, alias, CredentialStoreType.TEMPORARY);
+ }
+
+ @Override
+ public void removeCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException {
+ if (isInitialized(credentialStoreType)) {
+ getCredentialStore(credentialStoreType).removeCredential(canonicalizeAlias(clusterName, alias));
+ }
+ }
+
+ @Override
+ public boolean containsCredential(String clusterName, String alias) throws AmbariException {
+ return containsCredential(clusterName, alias, CredentialStoreType.TEMPORARY) ||
+ containsCredential(clusterName, alias, CredentialStoreType.PERSISTED);
+ }
+
+ @Override
+ public boolean containsCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException {
+ return isInitialized(credentialStoreType) &&
+ getCredentialStore(credentialStoreType).containsCredential(canonicalizeAlias(clusterName, alias));
+ }
+
+ @Override
+ public CredentialStoreType getCredentialStoreType(String clusterName, String alias) throws AmbariException {
+ if (containsCredential(clusterName, alias, CredentialStoreType.TEMPORARY)) {
+ return CredentialStoreType.TEMPORARY;
+ } else if (containsCredential(clusterName, alias, CredentialStoreType.PERSISTED)) {
+ return CredentialStoreType.PERSISTED;
+ } else {
+ throw new AmbariException("The alias was not found in either the persisted or temporary credential stores");
+ }
+ }
+
+ @Override
+ public Map<String, CredentialStoreType> listCredentials(String clusterName) throws AmbariException {
+ if (!isInitialized()) {
+ throw new AmbariException("This CredentialStoreService has not yet been initialized");
+ }
+
+ Collection<String> persistedAliases = isInitialized(CredentialStoreType.PERSISTED)
+ ? persistedCredentialStore.listCredentials()
+ : null;
+
+ Collection<String> temporaryAliases = isInitialized(CredentialStoreType.TEMPORARY)
+ ? temporaryCredentialStore.listCredentials()
+ : null;
+
+ Map<String, CredentialStoreType> map = new HashMap<String, CredentialStoreType>();
+
+ if (persistedAliases != null) {
+ for (String alias : persistedAliases) {
+ if (isAliasRequested(clusterName, alias)) {
+ map.put(decanonicalizeAlias(clusterName, alias), CredentialStoreType.PERSISTED);
}
- } catch (UnrecoverableKeyException e) {
- throw new AmbariException("The key cannot be recovered (e.g., the given password is wrong)", e);
- } catch (KeyStoreException e) {
- throw new AmbariException("The key store has not been initialized", e);
- } catch (NoSuchAlgorithmException e) {
- throw new AmbariException(" if the algorithm for recovering the key cannot be found", e);
}
}
- return credential;
+ if (temporaryAliases != null) {
+ for (String alias : temporaryAliases) {
+ if (isAliasRequested(clusterName, alias)) {
+ map.put(decanonicalizeAlias(clusterName, alias), CredentialStoreType.TEMPORARY);
+ }
+ }
+ }
+
+ return map;
}
- /**
- * Calls the implementation-specific facility to persist the KeyStore
- *
- * @param keyStore the KeyStore to persist
- * @throws AmbariException if an error occurs while persisting the key store data
- */
- protected abstract void persistCredentialStore(KeyStore keyStore) throws AmbariException;
+ @Override
+ public synchronized boolean isInitialized() {
+ return isInitialized(CredentialStoreType.PERSISTED) || isInitialized(CredentialStoreType.TEMPORARY);
+ }
- /**
- * Calls the implementation-specific facility to load the KeyStore
- *
- * @throws AmbariException if an error occurs while loading the key store data
- */
- protected abstract KeyStore loadCredentialStore() throws AmbariException;
+ @Override
+ public synchronized boolean isInitialized(CredentialStoreType credentialStoreType) {
+ if (CredentialStoreType.PERSISTED == credentialStoreType) {
+ return persistedCredentialStore != null;
+ } else if (CredentialStoreType.TEMPORARY == credentialStoreType) {
+ return temporaryCredentialStore != null;
+ } else {
+ throw new IllegalArgumentException("Invalid or unexpected credential store type specified");
+ }
+ }
/**
- * Loads a KeyStore from an InputStream
+ * Canonicalizes an alias name by making sure that is contains the prefix indicating what cluster it belongs to.
+ * This helps to reduce collisions of alias between clusters pointing to the same keystore files.
* <p/>
- * Implementations are expected to call this to load the relevant KeyStore data from the
- * InputStream of some storage facility.
+ * Each alias is expected to have a prefix of <code>cluster.:clusterName.</code>, and the
+ * combination is to be converted to have all lowercase characters. For example if the alias was
+ * "external.DB" and the cluster name is "c1", then the canonicalized alias name would be
+ * "cluster.c1.external.db".
*
- * @param inputStream the InputStream to read the data from
- * @param keyStoreType the type of key store data expected
- * @return a new KeyStore instance with the loaded data
- * @throws AmbariException if an error occurs while loading the key store data from the InputStream
+ * @param clusterName the name of the cluster
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return a ccanonicalized alias name
*/
- protected KeyStore loadKeyStore(InputStream inputStream, String keyStoreType) throws AmbariException {
- if (masterKeyService == null) {
- throw new AmbariException("Master Key Service is not set for this Credential store.");
- }
+ public static String canonicalizeAlias(String clusterName, String alias) {
+ String canonicaizedAlias;
- KeyStore keyStore;
- try {
- keyStore = KeyStore.getInstance(keyStoreType);
- } catch (KeyStoreException e) {
- throw new AmbariException(String.format("No provider supports a key store implementation for the specified type: %s", keyStoreType), e);
- }
+ if ((clusterName == null) || clusterName.isEmpty() || (alias == null) || alias.isEmpty()) {
+ canonicaizedAlias = alias;
+ } else {
+ String prefix = createAliasPrefix(clusterName);
- try {
- keyStore.load(inputStream, masterKeyService.getMasterSecret());
- } catch (CertificateException e) {
- throw new AmbariException(String.format("One or more credentials from the key store could not be loaded: %s", e.getLocalizedMessage()), e);
- } catch (NoSuchAlgorithmException e) {
- throw new AmbariException(String.format("The algorithm used to check the integrity of the key store cannot be found: %s", e.getLocalizedMessage()), e);
- } catch (IOException e) {
- if (e.getCause() instanceof UnrecoverableKeyException) {
- throw new AmbariException(String.format("The password used to decrypt the key store is incorrect: %s", e.getLocalizedMessage()), e);
+ if (alias.toLowerCase().startsWith(prefix)) {
+ canonicaizedAlias = alias;
} else {
- throw new AmbariException(String.format("Failed to read the key store: %s", e.getLocalizedMessage()), e);
+ canonicaizedAlias = prefix + alias;
}
}
- return keyStore;
+ return (canonicaizedAlias == null)
+ ? null
+ : canonicaizedAlias.toLowerCase();
}
/**
- * Writes a KeyStore to an OutputStream
- * <p/>
- * Implementations are expected to call this to write the relevant KeyStore data to the
- * OutputStream of some storage facility.
+ * Removes the prefix (if exists) from the front of a canonicalized alias
*
- * @param keyStore the KeyStore to write
- * @param outputStream the OutputStream to write the data into
- * @throws AmbariException if an error occurs while writing the key store data
+ * @param clusterName the name the name of the cluster
+ * @param canonicaizedAlias the canonicalized alias to process
+ * @return an alias name
*/
- protected void writeKeyStore(KeyStore keyStore, OutputStream outputStream) throws AmbariException {
- if (masterKeyService == null) {
- throw new AmbariException("Master Key Service is not set for this Credential store.");
- }
+ public static String decanonicalizeAlias(String clusterName, String canonicaizedAlias) {
+ if ((clusterName == null) || clusterName.isEmpty() || (canonicaizedAlias == null) || canonicaizedAlias.isEmpty()) {
+ return canonicaizedAlias;
+ } else {
+ String prefix = createAliasPrefix(clusterName);
- try {
- keyStore.store(outputStream, masterKeyService.getMasterSecret());
- } catch (CertificateException e) {
- throw new AmbariException(String.format("A credential within in the key store data could not be stored: %s", e.getLocalizedMessage()), e);
- } catch (NoSuchAlgorithmException e) {
- throw new AmbariException(String.format("The appropriate data integrity algorithm could not be found: %s", e.getLocalizedMessage()), e);
- } catch (KeyStoreException e) {
- throw new AmbariException(String.format("The key store has not been initialized: %s", e.getLocalizedMessage()), e);
- } catch (IOException e) {
- throw new AmbariException(String.format("Failed to write the key store: %s", e.getLocalizedMessage()), e);
+ if (canonicaizedAlias.startsWith(prefix)) {
+ return canonicaizedAlias.substring(prefix.length());
+ } else {
+ return canonicaizedAlias;
+ }
}
}
/**
- * Converts an array of characters to an array of bytes by encoding each character into UTF-8 bytes.
- * <p/>
- * An attempt is made to clear out sensitive data by filling any buffers with 0's
+ * Creates the prefix that is to be set in a canonicalized alias name.
*
- * @param chars the array of chars to convert
- * @return an array of bytes, or null if the original array was null
+ * @param clusterName the name of the cluster
+ * @return the prefix value
*/
- protected byte[] toBytes(char[] chars) {
- if (chars == null) {
- return null;
- } else {
- CharBuffer charBuffer = CharBuffer.wrap(chars);
- ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
-
- byte[] bytes = Arrays.copyOfRange(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit());
-
- // Clear sensitive data
- Arrays.fill(charBuffer.array(), '\u0000');
- Arrays.fill(byteBuffer.array(), (byte) 0);
-
- return bytes;
- }
+ private static String createAliasPrefix(String clusterName) {
+ return ("cluster." + clusterName + ".").toLowerCase();
}
/**
- * Converts an array of bytes to an array of character by decoding the bytes using the UTF-8
- * character set.
+ * Tests the canonicalized alias name to see if it should be returned within the set of credentials.
* <p/>
- * An attempt is made to clear out sensitive data by filling any buffers with 0's
+ * This filters out all credentials not tagged for a specific cluster.
*
- * @param bytes the array of bytes to convert
- * @return an array of chars, or null if the original array was null
+ * @param clusterName the name of the cluster
+ * @param canonicalizedAlias the canonicalized alias
+ * @return true if the alias is tagged for the requested cluster; otherwise false
*/
- protected char[] toChars(byte[] bytes) {
- if (bytes == null) {
- return null;
- } else {
- ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
- CharBuffer charBuffer = Charset.forName("UTF-8").decode(byteBuffer);
+ private boolean isAliasRequested(String clusterName, String canonicalizedAlias) {
+ return (clusterName == null) || canonicalizedAlias.toLowerCase().startsWith(createAliasPrefix(clusterName));
+ }
- char[] chars = Arrays.copyOfRange(charBuffer.array(), charBuffer.position(), charBuffer.limit());
- // Clear sensitive data
- Arrays.fill(charBuffer.array(), '\u0000');
- Arrays.fill(byteBuffer.array(), (byte) 0);
+ /**
+ * Gets either the persisted or temporary CredentialStore as requested
+ *
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @return a CredentialStore implementation
+ */
+ private CredentialStore getCredentialStore(CredentialStoreType credentialStoreType) {
+ if (CredentialStoreType.PERSISTED == credentialStoreType) {
+ return persistedCredentialStore;
+ } else if (CredentialStoreType.TEMPORARY == credentialStoreType) {
+ return temporaryCredentialStore;
+ } else {
+ throw new IllegalArgumentException("Invalid or unexpected credential store type specified");
+ }
+ }
- return chars;
+ /**
+ * Validate the relevant storage facility is initialized.
+ *
+ * @param credentialStoreType a CredentialStoreType indicating which credential store facility to use
+ * @throws AmbariException if the requested store has not been initialized
+ */
+ private void validateInitialized(CredentialStoreType credentialStoreType) throws AmbariException {
+ if (!isInitialized(credentialStoreType)) {
+ throw new AmbariException(String.format("The %s CredentialStore for this CredentialStoreService has not yet been initialized",
+ credentialStoreType.name().toLowerCase())
+ );
}
}
-}
\ No newline at end of file
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreType.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreType.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreType.java
new file mode 100644
index 0000000..a15fc84
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/CredentialStoreType.java
@@ -0,0 +1,27 @@
+/*
+ * 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.ambari.server.security.encryption;
+
+/**
+ * Defines available credential store types
+ */
+public enum CredentialStoreType {
+ PERSISTED,
+ TEMPORARY
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStore.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStore.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStore.java
new file mode 100644
index 0000000..0a6746e
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStore.java
@@ -0,0 +1,162 @@
+/*
+ * 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.ambari.server.security.encryption;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.configuration.Configuration;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.security.KeyStore;
+
+/**
+ * FileBasedCredentialStore is a CredentialStore implementation that creates and manages
+ * a JCEKS (Java Cryptography Extension KeyStore) file on disk. The key store and its contents are
+ * encrypted using the key from the supplied {@link MasterKeyService}.
+ * <p/>
+ * Most of the work for this implementation is handled by the {@link AbstractCredentialStore}.
+ * This class handles the details of the storage location and associated input and output streams.
+ */
+public class FileBasedCredentialStore extends AbstractCredentialStore {
+ private static final Logger LOG = LoggerFactory.getLogger(FileBasedCredentialStore.class);
+
+ /**
+ * The directory to use for storing the key store file
+ */
+ private File keyStoreFile;
+
+ /**
+ * Constructs a new FileBasedCredentialStore using the specified key store directory
+ *
+ * @param keyStoreLocation a File pointing to the directory in which to store the key store file; or the file itself
+ */
+ public FileBasedCredentialStore(File keyStoreLocation) {
+ if (keyStoreLocation == null) {
+ // If the keyStoreLocation is not set, create the file (using the default filename) in the
+ // current working directory.
+ LOG.warn("Writing key store to the current working directory of the running process");
+ keyStoreLocation = new File(Configuration.MASTER_KEYSTORE_FILENAME_DEFAULT);
+ } else if (keyStoreLocation.isDirectory()) {
+ // If the keyStoreLocation is a directory, create the file (using the default filename) in
+ // that directory.
+ keyStoreLocation = new File(keyStoreLocation, Configuration.MASTER_KEYSTORE_FILENAME_DEFAULT);
+ }
+
+ if (keyStoreLocation.exists()) {
+ if (!keyStoreLocation.canWrite()) {
+ LOG.warn("The destination file is not writable. Failures may occur when writing the key store to disk: {}", keyStoreLocation.getAbsolutePath());
+ }
+ } else {
+ File directory = keyStoreLocation.getParentFile();
+ if ((directory != null) && !directory.canWrite()) {
+ LOG.warn("The destination directory is not writable. Failures may occur when writing the key store to disk: {}", keyStoreLocation.getAbsolutePath());
+ }
+ }
+
+ this.keyStoreFile = keyStoreLocation;
+ }
+
+ /**
+ * Gets the path to this FileBasedCredentialStore's KeyStore file
+ *
+ * @return a file indicating the path to this FileBasedCredentialStore's KeyStore file
+ */
+ public File getKeyStorePath() {
+ return keyStoreFile;
+ }
+
+
+ @Override
+ protected void persistCredentialStore(KeyStore keyStore) throws AmbariException {
+ putKeyStore(keyStore, this.keyStoreFile);
+ }
+
+
+ @Override
+ protected KeyStore loadCredentialStore() throws AmbariException {
+ return getKeyStore(this.keyStoreFile, DEFAULT_STORE_TYPE);
+ }
+
+ /**
+ * Reads the key store data from the specified file. If the file does not exist, a new KeyStore
+ * will be created.
+ *
+ * @param keyStoreFile a File pointing to the key store file
+ * @param keyStoreType the type of key store data to read (or create)
+ * @return the loaded KeyStore
+ * @throws AmbariException if the Master Key Service is not set
+ * @see AbstractCredentialStore#loadCredentialStore()
+ */
+ private KeyStore getKeyStore(final File keyStoreFile, String keyStoreType) throws AmbariException {
+ KeyStore keyStore;
+ FileInputStream inputStream;
+
+ if (keyStoreFile.exists()) {
+ if (keyStoreFile.length() > 0) {
+ LOG.debug("Reading key store from {}", keyStoreFile.getAbsolutePath());
+ try {
+ inputStream = new FileInputStream(keyStoreFile);
+ } catch (FileNotFoundException e) {
+ throw new AmbariException(String.format("Failed to open the key store file: %s", e.getLocalizedMessage()), e);
+ }
+ } else {
+ LOG.debug("The key store file found in {} is empty. Returning new (non-persisted) KeyStore", keyStoreFile.getAbsolutePath());
+ inputStream = null;
+ }
+ } else {
+ LOG.debug("Key store file not found in {}. Returning new (non-persisted) KeyStore", keyStoreFile.getAbsolutePath());
+ inputStream = null;
+ }
+
+ try {
+ keyStore = loadKeyStore(inputStream, keyStoreType);
+ } finally {
+ IOUtils.closeQuietly(inputStream);
+ }
+
+ return keyStore;
+ }
+
+ /**
+ * Writes the specified KeyStore to a file.
+ *
+ * @param keyStore the KeyStore to write to a file
+ * @param keyStoreFile the File in which to store the KeyStore data
+ * @throws AmbariException if an error occurs while writing the KeyStore data
+ */
+ private void putKeyStore(KeyStore keyStore, File keyStoreFile) throws AmbariException {
+ LOG.debug("Writing key store to {}", keyStoreFile.getAbsolutePath());
+
+ FileOutputStream outputStream = null;
+
+ try {
+ outputStream = new FileOutputStream(this.keyStoreFile);
+ writeKeyStore(keyStore, outputStream);
+ } catch (FileNotFoundException e) {
+ throw new AmbariException(String.format("Failed to open the key store file: %s", e.getLocalizedMessage()), e);
+ } finally {
+ IOUtils.closeQuietly(outputStream);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStoreService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStoreService.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStoreService.java
deleted file mode 100644
index 41ff71b..0000000
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStoreService.java
+++ /dev/null
@@ -1,151 +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.ambari.server.security.encryption;
-
-import org.apache.ambari.server.AmbariException;
-import org.apache.commons.io.IOUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.security.KeyStore;
-
-/**
- * FileBasedCredentialStoreService is a CredentialStoreService implementation that creates and manages
- * a JCEKS (Java Cryptography Extension KeyStore) file on disk. The key store and its contents are
- * encrypted using the key from the supplied {@link MasterKeyService}.
- * <p/>
- * Most of the work for this implementation is handled by the {@link CredentialStoreServiceImpl}.
- * This class handles the details of the storage location and associated input and output streams.
- */
-public class FileBasedCredentialStoreService extends CredentialStoreServiceImpl {
- private static final String KEYSTORE_FILENAME = "credentials.jceks";
- private static final Logger LOG = LoggerFactory.getLogger(FileBasedCredentialStoreService.class);
-
- /**
- * The directory to use for storing the key store file
- */
- private File keyStoreDir;
-
- /**
- * Constructs a new FileBasedCredentialStoreService using the specified key store directory
- *
- * @param keyStoreDir a String containing the absolute path to the directory in which to store the key store file
- */
- public FileBasedCredentialStoreService(String keyStoreDir) {
- this(new File(keyStoreDir));
- }
-
- /**
- * Constructs a new FileBasedCredentialStoreService using the specified key store directory
- *
- * @param keyStoreDir a File pointing to the directory in which to store the key store file
- */
- public FileBasedCredentialStoreService(File keyStoreDir) {
- if (keyStoreDir == null) {
- LOG.warn("Writing key store to the current working directory of the running process");
- } else if (!keyStoreDir.exists()) {
- LOG.warn("The destination directory does not exist. Failures may occur when writing the key store to disk: {}", keyStoreDir.getAbsolutePath());
- } else if (!keyStoreDir.isDirectory()) {
- LOG.warn("The destination does not point to directory. Failures may occur when writing the key store to disk: {}", keyStoreDir.getAbsolutePath());
- }
-
- this.keyStoreDir = keyStoreDir;
- }
-
- @Override
- protected void persistCredentialStore(KeyStore keyStore) throws AmbariException {
- putKeyStore(keyStore, getKeyStoreFile());
- }
-
-
- @Override
- protected KeyStore loadCredentialStore() throws AmbariException {
- return getKeyStore(getKeyStoreFile(), DEFAULT_STORE_TYPE);
- }
-
- /**
- * Reads the key store data from the specified file. If the file does not exist, a new KeyStore
- * will be created.
- *
- * @param keyStoreFile a File pointing to the key store file
- * @param keyStoreType the type of key store data to read (or create)
- * @return the loaded KeyStore
- * @throws AmbariException if the Master Key Service is not set
- * @see CredentialStoreServiceImpl#loadCredentialStore()
- */
- private KeyStore getKeyStore(final File keyStoreFile, String keyStoreType) throws AmbariException{
- KeyStore keyStore;
- FileInputStream inputStream;
-
- if (keyStoreFile.exists()) {
- LOG.debug("Reading key store from {}", keyStoreFile.getAbsolutePath());
- try {
- inputStream = new FileInputStream(keyStoreFile);
- } catch (FileNotFoundException e) {
- throw new AmbariException(String.format("Failed to open the key store file: %s", e.getLocalizedMessage()), e);
- }
- } else {
- LOG.debug("Key store file not found in {}. Returning new (non-persisted) KeyStore", keyStoreFile.getAbsolutePath());
- inputStream = null;
- }
-
- try {
- keyStore = loadKeyStore(inputStream, keyStoreType);
- } finally {
- IOUtils.closeQuietly(inputStream);
- }
-
- return keyStore;
- }
-
- /**
- * Writes the specified KeyStore to a file.
- *
- * @param keyStore the KeyStore to write to a file
- * @param keyStoreFile the File in which to store the KeyStore data
- * @throws AmbariException if an error occurs while writing the KeyStore data
- */
- private void putKeyStore(KeyStore keyStore, File keyStoreFile) throws AmbariException {
- LOG.debug("Writing key store to {}", keyStoreFile.getAbsolutePath());
-
- FileOutputStream outputStream = null;
-
- try {
- outputStream = new FileOutputStream(new File(keyStoreDir, KEYSTORE_FILENAME));
- writeKeyStore(keyStore, outputStream);
- } catch (FileNotFoundException e) {
- throw new AmbariException(String.format("Failed to open the key store file: %s", e.getLocalizedMessage()), e);
- } finally {
- IOUtils.closeQuietly(outputStream);
- }
- }
-
- /**
- * Calculates the absolute path to the key store file
- *
- * @return a File pointing to the absolute path of the key store file
- */
- private File getKeyStoreFile() {
- return new File(keyStoreDir, KEYSTORE_FILENAME);
- }
-}
http://git-wip-us.apache.org/repos/asf/ambari/blob/3b411744/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStore.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStore.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStore.java
new file mode 100644
index 0000000..a7b9ba6
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStore.java
@@ -0,0 +1,233 @@
+/*
+ * 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.ambari.server.security.encryption;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.KeyStore;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+
+/**
+ * InMemoryCredentialStore is a CredentialStore implementation that creates and manages
+ * a JCEKS (Java Cryptography Extension KeyStore) in memory. The key store and its contents are
+ * encrypted using the key from the supplied {@link MasterKeyService}.
+ * <p/>
+ * This class handles the details of the in-memory storage buffer and associated input and output
+ * streams. Each credential is stored in its own KeyStore that may be be purged upon some
+ * retention timeout - if specified.
+ */
+public class InMemoryCredentialStore extends AbstractCredentialStore {
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryCredentialStore.class);
+
+ /**
+ * A cache containing the KeyStore data
+ */
+ private final Cache<String, KeyStore> cache;
+
+ /**
+ * Constructs a new InMemoryCredentialStore where credentials have no retention timeout
+ */
+ public InMemoryCredentialStore() {
+ this(0, TimeUnit.MINUTES, false);
+ }
+
+ /**
+ * Constructs a new InMemoryCredentialStore with a specified credential timeout
+ *
+ * @param retentionDuration the time in some units to keep stored credentials, from the time they are added
+ * @param units the units for the retention duration (minutes, seconds, etc...)
+ * @param activelyPurge true to actively purge credentials after the retention time has expired;
+ * otherwise false, to passively purge credentials after the retention time has expired
+ */
+ public InMemoryCredentialStore(final long retentionDuration, final TimeUnit units, boolean activelyPurge) {
+ CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();
+
+ // If the retentionDuration is less the 1, then no retention policy is to be enforced
+ if (retentionDuration > 0) {
+ // If actively purging expired credentials, set up a timer to periodically clean the cache
+ if (activelyPurge) {
+ ThreadFactory threadFactory = new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread t = Executors.defaultThreadFactory().newThread(runnable);
+ if (t != null) {
+ t.setName(String.format("%s active cleanup timer", InMemoryCredentialStore.class.getSimpleName()));
+ t.setDaemon(true);
+ }
+ return t;
+ }
+ };
+ Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Cleaning up cache due to retention timeout of {} milliseconds",
+ units.toMillis(retentionDuration));
+ }
+ cache.cleanUp();
+ }
+ };
+
+ Executors.newSingleThreadScheduledExecutor(threadFactory).schedule(runnable, 1, TimeUnit.MINUTES);
+ }
+
+ builder.expireAfterWrite(retentionDuration, units);
+ }
+
+ cache = builder.build();
+ }
+
+ /**
+ * Adds a new credential to this CredentialStore
+ * <p/>
+ * The supplied key will be converted into UTF-8 bytes before being stored.
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @param credential the credential to store
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ @Override
+ public void addCredential(String alias, Credential credential) throws AmbariException {
+ if ((alias == null) || alias.isEmpty()) {
+ throw new IllegalArgumentException("Alias cannot be null or empty.");
+ }
+
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ KeyStore keyStore = loadKeyStore(null, DEFAULT_STORE_TYPE);
+ addCredential(keyStore, alias, credential);
+ cache.put(alias, keyStore);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Retrieves the specified credential from this CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return a Credential or null of not found
+ * @throws AmbariException if an error occurs while retrieving the new credential
+ */
+ @Override
+ public Credential getCredential(String alias) throws AmbariException {
+ Credential credential = null;
+
+ if ((alias != null) && !alias.isEmpty()) {
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ KeyStore keyStore = cache.getIfPresent(alias);
+ if (keyStore != null) {
+ credential = getCredential(keyStore, alias);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ return credential;
+ }
+
+ /**
+ * Removes the specified credential from this CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @throws AmbariException if an error occurs while removing the new credential
+ */
+ @Override
+ public void removeCredential(String alias) throws AmbariException {
+ if (alias != null) {
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ cache.invalidate(alias);
+ } finally {
+ lock.unlock();
+ }
+ }
+ }
+
+ /**
+ * Returns a list of the alias names for the credentials stored in the CredentialStore
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @return a Set of Strings representing alias names for the credentials stored in the CredentialStore
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ @Override
+ public Set<String> listCredentials() throws AmbariException {
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ return new HashSet<String>(cache.asMap().keySet());
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
+ * Tests this CredentialStore for the existence of a credential with the specified alias
+ * <p/>
+ * This implementation is thread-safe, allowing one thread at a time to access the credential store.
+ *
+ * @param alias a string declaring the alias (or name) of the credential
+ * @return true if the alias exists; otherwise false
+ * @throws AmbariException if an error occurs while searching forthe credential
+ */
+ @Override
+ public boolean containsCredential(String alias) throws AmbariException {
+ Lock lock = getLock();
+ lock.lock();
+ try {
+ return (alias != null) && !alias.isEmpty() && (cache.getIfPresent(alias) != null);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+
+ @Override
+ protected void persistCredentialStore(KeyStore keyStore) throws AmbariException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected KeyStore loadCredentialStore() throws AmbariException {
+ throw new UnsupportedOperationException();
+ }
+}