You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by rl...@apache.org on 2015/09/04 16:56:27 UTC
[2/2] ambari git commit: AMBARI-12772. Adding host via blueprint
fails on secure cluster (rlevas)
AMBARI-12772. Adding host via blueprint fails on secure cluster (rlevas)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e681f2bf
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e681f2bf
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e681f2bf
Branch: refs/heads/trunk
Commit: e681f2bf4d2f902d9d46c195baf5c405ab9ebc8e
Parents: 6c98f47
Author: Robert Levas <rl...@hortonworks.com>
Authored: Fri Sep 4 10:55:36 2015 -0400
Committer: Robert Levas <rl...@hortonworks.com>
Committed: Fri Sep 4 10:55:49 2015 -0400
----------------------------------------------------------------------
.../AmbariManagementControllerImpl.java | 46 ++-
.../server/controller/KerberosHelper.java | 29 ++
.../server/controller/KerberosHelperImpl.java | 167 ++++----
.../security/encryption/CredentialProvider.java | 66 ++--
.../encryption/CredentialStoreService.java | 39 +-
.../encryption/CredentialStoreServiceImpl.java | 326 ++++++++++------
.../FileBasedCredentialStoreService.java | 151 ++++++++
.../InMemoryCredentialStoreService.java | 142 +++++++
.../encryption/MasterKeyServiceImpl.java | 381 ++++++++++---------
.../kerberos/KerberosCredential.java | 54 ++-
.../kerberos/KerberosOperationHandler.java | 14 +-
.../kerberos/KerberosServerAction.java | 30 +-
.../kerberos/MITKerberosOperationHandler.java | 4 +-
.../server/configuration/ConfigurationTest.java | 2 +-
.../server/controller/KerberosHelperTest.java | 104 ++---
.../encryption/CredentialProviderTest.java | 74 ++--
.../encryption/CredentialStoreServiceTest.java | 244 +++++++++---
.../encryption/MasterKeyServiceTest.java | 69 ++--
.../ADKerberosOperationHandlerTest.java | 18 +-
.../kerberos/KerberosCredentialTest.java | 6 +-
.../kerberos/KerberosOperationHandlerTest.java | 8 +-
.../kerberos/KerberosServerActionTest.java | 32 +-
.../MITKerberosOperationHandlerTest.java | 24 +-
23 files changed, 1338 insertions(+), 692 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index 859b361..a90cb31 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
@@ -118,6 +118,7 @@ import org.apache.ambari.server.security.authorization.Users;
import org.apache.ambari.server.security.ldap.AmbariLdapDataPopulator;
import org.apache.ambari.server.security.ldap.LdapBatchDto;
import org.apache.ambari.server.security.ldap.LdapSyncDto;
+import org.apache.ambari.server.serveraction.kerberos.KerberosCredential;
import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
import org.apache.ambari.server.serveraction.kerberos.KerberosOperationException;
import org.apache.ambari.server.stageplanner.RoleGraph;
@@ -1258,7 +1259,50 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
throw new AmbariException("The cluster may not be null");
}
- cluster.addSessionAttributes(request.getSessionAttributes());
+ Map<String, Object> sessionAttributes = request.getSessionAttributes();
+
+ // TODO: Create REST API entry point to securely set credentials in the CredentialProvider then
+ // TODO: remove this block to _clean_ the session attributes and store any KDC administrator
+ // TODO: credentials in the secure credential provider facility.
+ // For now, to keep things backwards compatible, get and remove the KDC administrator credentials
+ // from the session attributes and store them in the CredentialsProvider. The KDC administrator
+ // credentials are prefixed with kdc_admin/. The following attributes are expected, if setting
+ // the KDC administrator credentials:
+ // kerberos_admin/principal
+ // kerberos_admin/password
+ if((sessionAttributes != null) && !sessionAttributes.isEmpty()) {
+ Map<String, Object> cleanedSessionAttributes = new HashMap<String, Object>();
+ String principal = null;
+ char[] password = null;
+
+ for(Map.Entry<String,Object> entry: sessionAttributes.entrySet()) {
+ String name = entry.getKey();
+ Object value = entry.getValue();
+
+ if ("kerberos_admin/principal".equals(name)) {
+ if(value instanceof String) {
+ principal = (String)value;
+ }
+ }
+ else if ("kerberos_admin/password".equals(name)) {
+ if(value instanceof String) {
+ password = ((String) value).toCharArray();
+ }
+ } else {
+ cleanedSessionAttributes.put(name, value);
+ }
+ }
+
+ if(principal != null) {
+ // The KDC admin principal exists... set the credentials in the credentials store
+ kerberosHelper.setKDCCredentials(new KerberosCredential(principal, password, null));
+ }
+
+ sessionAttributes = cleanedSessionAttributes;
+ }
+ // TODO: END
+
+ cluster.addSessionAttributes(sessionAttributes);
//
// ***************************************************
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java
index cb9e6ca..c39485b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelper.java
@@ -21,6 +21,7 @@ package org.apache.ambari.server.controller;
import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.controller.internal.RequestStageContainer;
import org.apache.ambari.server.serveraction.kerberos.KerberosAdminAuthenticationException;
+import org.apache.ambari.server.serveraction.kerberos.KerberosCredential;
import org.apache.ambari.server.serveraction.kerberos.KerberosIdentityDataFileWriter;
import org.apache.ambari.server.serveraction.kerberos.KerberosInvalidConfigurationException;
import org.apache.ambari.server.serveraction.kerberos.KerberosMissingAdminCredentialsException;
@@ -415,6 +416,34 @@ public interface KerberosHelper {
boolean replaceHostNames)
throws AmbariException;
+ /**
+ * Sets the KDC administrator credentials.
+ * <p/>
+ * It is up to the implementation to determine how to store
+ * these credentials and for how long.
+ *
+ * @param credentials the KDC administrator credentials
+ * @throws AmbariException if an error occurs while storing the credentials
+ */
+ void setKDCCredentials(KerberosCredential credentials) throws AmbariException;
+
+ /**
+ * Removes the previously set KDC administrator credentials.
+ *
+ * @throws AmbariException if an error occurs while removing the credentials
+ * @see KerberosHelper#setKDCCredentials(KerberosCredential)
+ */
+ void removeKDCCredentials() throws AmbariException;
+
+ /**
+ * Gets the previously stored KDC administrator credentials.
+ *
+ * @return a KerberosCredential or null, if the KDC administrator credentials have not be set or
+ * have been removed
+ * @throws AmbariException if an error occurs while retrieving the credentials
+ * @see KerberosHelper#setKDCCredentials(KerberosCredential)
+ */
+ KerberosCredential getKDCCredentials() throws AmbariException;
/**
* Command to invoke against the Ambari backend.
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
index 708d267..6f407c9 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/KerberosHelperImpl.java
@@ -30,8 +30,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -60,6 +60,9 @@ import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
import org.apache.ambari.server.controller.utilities.PredicateBuilder;
import org.apache.ambari.server.metadata.RoleCommandOrder;
+import org.apache.ambari.server.security.SecurePasswordHelper;
+import org.apache.ambari.server.security.encryption.InMemoryCredentialStoreService;
+import org.apache.ambari.server.security.encryption.MasterKeyServiceImpl;
import org.apache.ambari.server.serveraction.ServerAction;
import org.apache.ambari.server.serveraction.kerberos.CleanupServerAction;
import org.apache.ambari.server.serveraction.kerberos.CreateKeytabFilesServerAction;
@@ -126,10 +129,15 @@ public class KerberosHelperImpl implements KerberosHelper {
private static final Logger LOG = LoggerFactory.getLogger(KerberosHelperImpl.class);
/**
- * name of the property used to hold the service check identifier value, used when creating and
- * destroying the (unique) service check identity.
+ * The alias to assign to the KDC administrator credential Keystore item
*/
- private static final String SERVICE_CHECK_IDENTIFIER = "_kerberos_internal_service_check_identifier";
+ public static final String KDC_ADMINISTRATOR_CREDENTIAL_ALIAS = "kdc_admin";
+
+ /**
+ * The default length of time (in minutes) to store the KDC administrator credentials before
+ * automatically removing them.
+ */
+ private static final long DEFAULT_KDC_ADMINISTRATOR_CREDENTIALS_RETENTION_MINUTES = 90;
/**
* Regular expression pattern used to parse auth_to_local property specifications into the following
@@ -184,12 +192,29 @@ public class KerberosHelperImpl implements KerberosHelper {
@Inject
private KerberosIdentityDataFileWriterFactory kerberosIdentityDataFileWriterFactory;
+ @Inject
+ private SecurePasswordHelper securePasswordHelper;
+
/**
* Used to get kerberos descriptors associated with the cluster or stack.
* Currently not available via injection.
*/
private static ClusterController clusterController = null;
+ /**
+ * The secure storage facility to use to store KDC administrator credentials. This implementation
+ * is uses an InMemoryCredentialStoreService to keep the credentials in memory rather than
+ * storing them on disk.
+ */
+ private final InMemoryCredentialStoreService kdcCredentialStoreService;
+
+ /**
+ * Default KerberosHelperImpl constructor
+ */
+ public KerberosHelperImpl() {
+ kdcCredentialStoreService = new InMemoryCredentialStoreService(DEFAULT_KDC_ADMINISTRATOR_CREDENTIALS_RETENTION_MINUTES, TimeUnit.MINUTES, true);
+ }
+
@Override
public RequestStageContainer toggleKerberos(Cluster cluster, SecurityType securityType,
RequestStageContainer requestStageContainer,
@@ -344,10 +369,6 @@ public class KerberosHelperImpl implements KerberosHelper {
RequestStageContainer requestStageContainer)
throws KerberosOperationException, AmbariException {
requestStageContainer = handleTestIdentity(cluster, getKerberosDetails(cluster, null), commandParamsStage, requestStageContainer, new DeletePrincipalsAndKeytabsHandler());
-
- // Clear the Kerberos service check identifier
- setKerberosServiceCheckIdentifier(cluster, null);
-
return requestStageContainer;
}
@@ -764,7 +785,7 @@ public class KerberosHelperImpl implements KerberosHelper {
// add clusterHostInfo config
Map<String, Set<String>> clusterHostInfo = StageUtils.getClusterHostInfo(cluster);
- if(clusterHostInfo != null) {
+ if (clusterHostInfo != null) {
Map<String, String> componentHosts = new HashMap<String, String>();
clusterHostInfo = StageUtils.substituteHostIndexes(clusterHostInfo);
@@ -890,6 +911,67 @@ public class KerberosHelperImpl implements KerberosHelper {
}
/**
+ * Sets the KDC administrator credentials.
+ * <p/>
+ * This implementation stores the credentials in a secure CredentialStoreService implementation and
+ * sets a timer to remove the stored credentials after the retention period expires.
+ * <p/>
+ * If existing credentials are stored when setting new credentials, the previously stored data will
+ * be cleared out. Each time credentials are set, a new master key is generated and used to encrypt
+ * the data.
+ *
+ * @param credentials the KDC administrator credentials
+ * @throws AmbariException if an error occurs while storing the credentials
+ */
+ @Override
+ public void setKDCCredentials(KerberosCredential credentials) throws AmbariException {
+ kdcCredentialStoreService.removeCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
+
+ if (credentials != null) {
+ String jsonValue = credentials.toJSON();
+
+ if (jsonValue != null) {
+ kdcCredentialStoreService.setMasterKeyService(new MasterKeyServiceImpl(securePasswordHelper.createSecurePassword()));
+ kdcCredentialStoreService.addCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS, jsonValue.toCharArray());
+ }
+ }
+ }
+
+ /**
+ * Removes the previously set KDC administrator credentials.
+ * <p/>
+ * This implementation clears the secure CredentialsStoreService instance, removing the previously
+ * generated master key and credentials data. The configured timer to enforce retention time is
+ * cleared and set to null.
+ *
+ * @throws AmbariException if an error occurs while removing the credentials
+ * @see KerberosHelper#setKDCCredentials(KerberosCredential)
+ */
+ @Override
+ public void removeKDCCredentials() throws AmbariException {
+ kdcCredentialStoreService.removeCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
+ }
+
+ /**
+ * Gets the previously stored KDC administrator credentials.
+ * <p/>
+ * This implementation accesses the secure CredentialStoreService instance to get the data.
+ *
+ * @return a KerberosCredential or null, if the KDC administrator credentials have not be set or
+ * have been removed
+ * @throws AmbariException if an error occurs while retrieving the credentials
+ * @see KerberosHelper#setKDCCredentials(KerberosCredential)
+ */
+ @Override
+ public KerberosCredential getKDCCredentials() throws AmbariException {
+ char[] credentials = kdcCredentialStoreService.getCredential(KDC_ADMINISTRATOR_CREDENTIAL_ALIAS);
+
+ return (credentials == null)
+ ? null
+ : KerberosCredential.fromJSON(new String(credentials));
+ }
+
+ /**
* Validate the KDC admin credentials.
*
* @param kerberosDetails the KerberosDetails containing information about the Kerberos configuration
@@ -908,8 +990,8 @@ public class KerberosHelperImpl implements KerberosHelper {
}
if (kerberosDetails.manageIdentities()) {
- String credentials = getEncryptedAdministratorCredentials(cluster);
- if (credentials == null) {
+ KerberosCredential kerberosCredentials = getKDCCredentials();
+ if (kerberosCredentials == null) {
throw new KerberosMissingAdminCredentialsException(
"Missing KDC administrator credentials.\n" +
"The KDC administrator credentials must be set in session by updating the relevant Cluster resource." +
@@ -926,9 +1008,6 @@ public class KerberosHelperImpl implements KerberosHelper {
if (operationHandler == null) {
throw new AmbariException("Failed to get an appropriate Kerberos operation handler.");
} else {
- byte[] key = Integer.toHexString(cluster.hashCode()).getBytes();
- KerberosCredential kerberosCredentials = KerberosCredential.decrypt(credentials, key);
-
boolean missingCredentials = false;
try {
operationHandler.open(kerberosCredentials, kerberosDetails.getDefaultRealm(), kerberosDetails.getKerberosEnvProperties());
@@ -1243,9 +1322,6 @@ public class KerberosHelperImpl implements KerberosHelper {
}
});
- // Get or create the unique service check identifier
- String serviceCheckId = getKerberosServiceCheckIdentifier(cluster, true);
-
try {
// Iterate over the hosts in the cluster to find the components installed in each. For each
// component (aka service component host - sch) determine the configuration updates and
@@ -1261,9 +1337,6 @@ public class KerberosHelperImpl implements KerberosHelper {
// variables within the Kerberos descriptor data
Map<String, Map<String, String>> configurations = calculateConfigurations(cluster, hostname, kerberosDescriptorProperties);
- // Set the unique service check identifier
- configurations.get("").put("service_check_id", serviceCheckId);
-
// Add a short date value
configurations.get("").put("short_date", new SimpleDateFormat("MMddyy").format(new Date()));
@@ -1706,56 +1779,6 @@ public class KerberosHelperImpl implements KerberosHelper {
}
/**
- * Using the session data from the relevant Cluster object, gets the previously stored
- * Kerberos service check identifier value or creates a new one if indicated to do so.
- * <p/>
- * This value is used intended to be used by the KerberosHelper to manage uniquely crated
- * principals for use in service checks.
- *
- * @param cluster the relevant Cluster
- * @return the previously stored Kerberos service check identifier value, or null if
- * not previously stored
- */
- private String getKerberosServiceCheckIdentifier(Cluster cluster, boolean createIfNull) {
- Map<String, Object> sessionAttributes = cluster.getSessionAttributes();
- Object value = (sessionAttributes == null) ? null : sessionAttributes.get(SERVICE_CHECK_IDENTIFIER);
- String serviceCheckIdentifier = (value instanceof String) ? (String) value : null;
-
- if ((serviceCheckIdentifier == null) && createIfNull) {
- // Create a new (ideally) unique(ish) identifier
- Random random = new Random(System.currentTimeMillis());
- char[] chars = new char[8];
-
- for (int i = 0; i < 8; i++) {
- chars[i] = (char) ((int) 'a' + random.nextInt(26));
- }
-
- serviceCheckIdentifier = String.valueOf(chars);
- setKerberosServiceCheckIdentifier(cluster, serviceCheckIdentifier);
- }
-
- return serviceCheckIdentifier;
- }
-
- /**
- * Stores the Kerberos service check identifier value into the session data from the
- * relevant Cluster object.
- * <p/>
- * This value is used intended to be used by the KerberosHelper to manage uniquely crated
- * principals for use in service checks.
- *
- * @param cluster the relevant Cluster
- * @param value the Kerberos service check identifier to store or null to clear any previously set value
- */
- private void setKerberosServiceCheckIdentifier(Cluster cluster, String value) {
- if (value == null) {
- cluster.removeSessionAttribute(SERVICE_CHECK_IDENTIFIER);
- } else {
- cluster.setSessionAttribute(SERVICE_CHECK_IDENTIFIER, value);
- }
- }
-
- /**
* Given a Collection of ServiceComponentHosts generates a unique list of hosts.
*
* @param serviceComponentHosts a Collection of ServiceComponentHosts from which to to retrieve host names
@@ -2432,7 +2455,6 @@ public class KerberosHelperImpl implements KerberosHelper {
if (kerberosDetails.manageIdentities()) {
commandParameters.put(KerberosServerAction.KDC_TYPE, kerberosDetails.getKdcType().name());
- commandParameters.put(KerberosServerAction.ADMINISTRATOR_CREDENTIAL, getEncryptedAdministratorCredentials(cluster));
// *****************************************************************
// Create stage to create principals
@@ -2544,7 +2566,6 @@ public class KerberosHelperImpl implements KerberosHelper {
if (kerberosDetails.manageIdentities()) {
commandParameters.put(KerberosServerAction.KDC_TYPE, kerberosDetails.getKdcType().name());
- commandParameters.put(KerberosServerAction.ADMINISTRATOR_CREDENTIAL, getEncryptedAdministratorCredentials(cluster));
// *****************************************************************
// Create stage to remove principals
@@ -2681,7 +2702,6 @@ public class KerberosHelperImpl implements KerberosHelper {
if (kerberosDetails.manageIdentities()) {
commandParameters.put(KerberosServerAction.KDC_TYPE, kerberosDetails.getKdcType().name());
- commandParameters.put(KerberosServerAction.ADMINISTRATOR_CREDENTIAL, getEncryptedAdministratorCredentials(cluster));
// *****************************************************************
// Create stage to create principals
@@ -2781,7 +2801,6 @@ public class KerberosHelperImpl implements KerberosHelper {
}
commandParameters.put(KerberosServerAction.KDC_TYPE, kerberosDetails.getKdcType().name());
- commandParameters.put(KerberosServerAction.ADMINISTRATOR_CREDENTIAL, getEncryptedAdministratorCredentials(cluster));
// *****************************************************************
// Create stage to create principals
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/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 8351a99..b812337 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
@@ -22,48 +22,54 @@ import org.apache.ambari.server.configuration.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CredentialProvider {
- public static final Pattern PASSWORD_ALIAS_PATTERN =
- Pattern.compile("\\$\\{alias=[\\w\\.]+\\}");
+ public static final Pattern PASSWORD_ALIAS_PATTERN = Pattern.compile("\\$\\{alias=[\\w\\.]+\\}");
- protected char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
- 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
- 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
- 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- '2', '3', '4', '5', '6', '7', '8', '9'};
+ protected char[] chars = {'a', 'b', 'c', 'd', 'e', 'f', 'g',
+ 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
+ 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
+ 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ '2', '3', '4', '5', '6', '7', '8', '9'};
private CredentialStoreService keystoreService;
static final Logger LOG = LoggerFactory.getLogger(CredentialProvider.class);
public CredentialProvider(String masterKey, String masterKeyLocation,
- boolean isMasterKeyPersisted) throws AmbariException {
+ boolean isMasterKeyPersisted) throws AmbariException {
MasterKeyService masterKeyService;
if (masterKey != null) {
masterKeyService = new MasterKeyServiceImpl(masterKey);
} else {
- masterKeyService = new MasterKeyServiceImpl(masterKeyLocation,
- isMasterKeyPersisted);
+ 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");
+ }
+ masterKeyService = new MasterKeyServiceImpl(new File(masterKeyLocation));
+ } else {
+ masterKeyService = new MasterKeyServiceImpl();
+ }
}
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 CredentialStoreServiceImpl(storeDir);
+ masterKeyLocation.indexOf(Configuration.MASTER_KEY_FILENAME_DEFAULT));
+ this.keystoreService = new FileBasedCredentialStoreService(storeDir);
this.keystoreService.setMasterKeyService(masterKeyService);
}
public char[] getPasswordForAlias(String alias) throws AmbariException {
- if (isAliasString(alias))
+ if (isAliasString(alias)) {
return keystoreService.getCredential(getAliasFromString(alias));
+ }
return keystoreService.getCredential(alias);
}
@@ -73,13 +79,14 @@ public class CredentialProvider {
}
public void addAliasToCredentialStore(String alias, String passwordString)
- throws AmbariException {
- if (alias == null || alias.isEmpty())
+ throws AmbariException {
+ if (alias == null || alias.isEmpty()) {
throw new IllegalArgumentException("Alias cannot be null or empty.");
- if (passwordString == null || passwordString.isEmpty())
- throw new IllegalArgumentException("Empty or null password not allowed" +
- ".");
- keystoreService.addCredential(alias, passwordString);
+ }
+ if (passwordString == null || passwordString.isEmpty()) {
+ throw new IllegalArgumentException("Empty or null password not allowed.");
+ }
+ keystoreService.addCredential(alias, passwordString.toCharArray());
}
private String generatePassword(int length) {
@@ -92,15 +99,15 @@ public class CredentialProvider {
}
public static boolean isAliasString(String aliasStr) {
- if (aliasStr == null || aliasStr.isEmpty())
+ if (aliasStr == null || aliasStr.isEmpty()) {
return false;
+ }
Matcher matcher = PASSWORD_ALIAS_PATTERN.matcher(aliasStr);
return matcher.matches();
}
private String getAliasFromString(String strPasswd) {
- return strPasswd.substring(strPasswd.indexOf("=") + 1,
- strPasswd.length() - 1);
+ return strPasswd.substring(strPasswd.indexOf("=") + 1, strPasswd.length() - 1);
}
protected CredentialStoreService getKeystoreService() {
@@ -113,8 +120,8 @@ public class CredentialProvider {
* args[1] => Alias
* args[2] => Payload (FilePath for GET/Password for PUT)
* args[3] => Master Key (Empty)
- * @param args
*
+ * @param args
*/
public static void main(String args[]) {
if (args != null && args.length > 0) {
@@ -130,15 +137,14 @@ public class CredentialProvider {
System.exit(1);
}
// None - To avoid incorrectly assuming redirection as argument
- if (args.length > 3 && !args[3].isEmpty() && !args[3].equalsIgnoreCase
- ("None")) {
+ if (args.length > 3 && !args[3].isEmpty() && !args[3].equalsIgnoreCase("None")) {
masterKey = args[3];
LOG.debug("Master key provided as an argument.");
}
try {
credentialProvider = new CredentialProvider(masterKey,
- configuration.getMasterKeyLocation(),
- configuration.isMasterKeyPersisted());
+ configuration.getMasterKeyLocation(),
+ configuration.isMasterKeyPersisted());
} catch (Exception ex) {
ex.printStackTrace();
System.exit(1);
@@ -150,7 +156,7 @@ public class CredentialProvider {
password = args[2];
}
if (alias != null && !alias.isEmpty()
- && password != null && !password.isEmpty()) {
+ && password != null && !password.isEmpty()) {
try {
credentialProvider.addAliasToCredentialStore(alias, password);
} catch (AmbariException e) {
@@ -166,7 +172,7 @@ public class CredentialProvider {
writeFilePath = args[2];
}
if (alias != null && !alias.isEmpty() && writeFilePath != null &&
- !writeFilePath.isEmpty()) {
+ !writeFilePath.isEmpty()) {
String passwd = "";
try {
char[] retPasswd = credentialProvider.getPasswordForAlias(alias);
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/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 8ea7ca2..4aa3b0a 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
@@ -20,10 +20,39 @@ package org.apache.ambari.server.security.encryption;
import org.apache.ambari.server.AmbariException;
-import java.security.KeyStore;
-
public interface CredentialStoreService {
- public void addCredential(String alias, String key) throws AmbariException;
- public char[] getCredential(String alias) throws AmbariException;
- public void setMasterKeyService(MasterKeyService masterService);
+ /**
+ * Adds a new credential to this CredentialStoreService
+ * <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
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ void addCredential(String alias, char[] key) throws AmbariException;
+
+ /**
+ * Retrieves the specified credential from this CredentialStoreService
+ *
+ * @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
+ */
+ char[] getCredential(String alias) throws AmbariException;
+
+ /**
+ * Removes the specified credential from this CredentialStoreService
+ *
+ * @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;
+
+ /**
+ * Sets the MasterKeyService for this CredentialStoreService
+ *
+ * @param masterKeyService the MasterKeyService
+ */
+ void setMasterKeyService(MasterKeyService masterKeyService);
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/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 d93faec..968e96a 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
@@ -18,183 +18,263 @@
package org.apache.ambari.server.security.encryption;
import org.apache.ambari.server.AmbariException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import javax.crypto.spec.SecretKeySpec;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
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;
-public class CredentialStoreServiceImpl implements CredentialStoreService {
- private static final String CREDENTIALS_SUFFIX = "credentials.jceks";
- private static final String DEFAULT_STORE_TYPE = "JCEKS";
- private MasterKeyService masterService;
- private String keyStoreDir;
- private static volatile boolean isCredentialStoreCreated = false;
- static final Logger LOG = LoggerFactory.getLogger
- (CredentialStoreServiceImpl.class);
-
- public CredentialStoreServiceImpl(String keyStoreDir) {
- this.keyStoreDir = keyStoreDir;
- final File keyStoreFile = new File(keyStoreDir + File.separator +
- CREDENTIALS_SUFFIX);
- if (keyStoreFile.exists()) {
- isCredentialStoreCreated = true;
+/**
+ * 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";
+
+ /**
+ * The MasterKeyService containing the key used to encrypt the KeyStore data
+ */
+ private MasterKeyService masterKeyService;
+
+ @Override
+ public void addCredential(String alias, char[] value) throws AmbariException {
+ if ((alias == null) || alias.isEmpty()) {
+ throw new IllegalArgumentException("Alias cannot be null or empty.");
}
+
+ KeyStore ks = loadCredentialStore();
+ addCredential(ks, alias, value);
+ persistCredentialStore(ks);
}
- protected KeyStore loadCredentialStore() throws AmbariException {
- if (masterService == null)
- throw new AmbariException("Master Key Service is not set for this " +
- "Credential store.");
+ @Override
+ public char[] getCredential(String alias) throws AmbariException {
+ if (alias == null) {
+ return null;
+ } else {
+ return getCredential(loadCredentialStore(), alias);
+ }
+ }
- final File keyStoreFile = new File(keyStoreDir + File.separator +
- CREDENTIALS_SUFFIX);
- LOG.debug("keystoreFile => " + keyStoreFile.getAbsolutePath());
- if (!isCredentialStoreCreated) {
- createCredentialStore();
+ @Override
+ public void removeCredential(String alias) throws AmbariException {
+ if ((alias != null) && !alias.isEmpty()) {
+ 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);
+ }
+ }
}
- return getKeystore(keyStoreFile, DEFAULT_STORE_TYPE);
}
@Override
- public void addCredential(String alias, String value) throws
- AmbariException {
- KeyStore ks = loadCredentialStore();
- if (ks != null) {
+ 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 value an array of chars containing the credential
+ * @throws AmbariException if an error occurs while storing the new credential
+ */
+ protected void addCredential(KeyStore keyStore, String alias, char[] value) throws AmbariException {
+ if (keyStore != null) {
try {
- final Key key = new SecretKeySpec(value.getBytes("UTF8"), "AES");
- ks.setKeyEntry( alias, key, masterService.getMasterSecret(), null);
- final File keyStoreFile = new File(keyStoreDir + File.separator +
- CREDENTIALS_SUFFIX);
- writeKeystoreToFile(ks, keyStoreFile);
+ Key key;
+
+ 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) {
- e.printStackTrace();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- } catch (CertificateException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
+ throw new AmbariException("The key store has not been initialized", e);
}
}
}
- @Override
- public char[] getCredential(String alias) throws AmbariException {
+ /**
+ * 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;
- KeyStore ks = loadCredentialStore();
- if (ks != null && alias != null && !alias.isEmpty()) {
+
+ if (keyStore != null) {
try {
- LOG.debug("keystore = " + ks.aliases());
- Key key = ks.getKey(alias, masterService.getMasterSecret());
- if (key == null) {
- throw new AmbariException("Credential not found for alias: " +
- alias);
+ Key key = keyStore.getKey(alias, masterKeyService.getMasterSecret());
+ if (key != null) {
+ credential = toChars(key.getEncoded());
}
- credential = new String(key.getEncoded()).toCharArray();
} catch (UnrecoverableKeyException e) {
- e.printStackTrace();
+ throw new AmbariException("The key cannot be recovered (e.g., the given password is wrong)", e);
} catch (KeyStoreException e) {
- e.printStackTrace();
+ throw new AmbariException("The key store has not been initialized", e);
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
+ throw new AmbariException(" if the algorithm for recovering the key cannot be found", e);
}
}
+
return credential;
}
- public void writeKeystoreToFile(final KeyStore keyStore, final File file)
- throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
- final FileOutputStream out = new FileOutputStream(file);
- try {
- keyStore.store(out, masterService.getMasterSecret());
- }
- finally {
- out.close();
- }
- }
+ /**
+ * 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;
- private synchronized void createCredentialStore() {
- String filename = keyStoreDir + File.separator + CREDENTIALS_SUFFIX;
- createKeystore(filename, DEFAULT_STORE_TYPE);
- isCredentialStoreCreated = true;
- }
+ /**
+ * 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;
+
+ /**
+ * 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.");
+ }
- private void createKeystore(String filename, String keystoreType) {
- FileOutputStream out = null;
+ KeyStore keyStore;
try {
- out = new FileOutputStream(filename);
- KeyStore ks = KeyStore.getInstance(keystoreType);
- ks.load(null, null);
- ks.store(out, masterService.getMasterSecret());
+ keyStore = KeyStore.getInstance(keyStoreType);
} catch (KeyStoreException e) {
- e.printStackTrace();
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
+ 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) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
+ 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) {
- e.printStackTrace();
- } finally {
- try {
- out.close();
- } catch (IOException e) {
- e.printStackTrace();
+ 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;
}
- private KeyStore getKeystore(final File keyStoreFile, String storeType) {
- KeyStore credStore = null;
+ /**
+ * 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 {
- credStore = loadKeyStore(keyStoreFile, masterService.getMasterSecret(), storeType);
+ keyStore.store(outputStream, masterKeyService.getMasterSecret());
} catch (CertificateException e) {
- e.printStackTrace();
- } catch (KeyStoreException e) {
- e.printStackTrace();
+ throw new AmbariException(String.format("A credential within in the key store data could not be stored: %s", e.getLocalizedMessage()), e);
} catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
+ 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) {
- e.printStackTrace();
+ throw new AmbariException(String.format("Failed to write the key store: %s", e.getLocalizedMessage()), e);
}
- return credStore;
}
- 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()) {
- final FileInputStream input = new FileInputStream(keyStoreFile);
- try {
- keyStore.load(input, masterPassword);
- }
- finally {
- input.close();
- }
- }
- else {
- keyStore.load(null, masterPassword);
- }
+ /**
+ * 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);
- return keyStore;
+ 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;
+ }
}
- @Override
- public void setMasterKeyService(MasterKeyService masterService) {
- this.masterService = masterService;
+ /**
+ * 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/e681f2bf/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
new file mode 100644
index 0000000..41ff71b
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/FileBasedCredentialStoreService.java
@@ -0,0 +1,151 @@
+/*
+ * 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/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStoreService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStoreService.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStoreService.java
new file mode 100644
index 0000000..3766cbb
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/InMemoryCredentialStoreService.java
@@ -0,0 +1,142 @@
+/*
+ * 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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.KeyStore;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * InMemoryCredentialStoreService is a CredentialStoreService 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 InMemoryCredentialStoreService extends CredentialStoreServiceImpl {
+ private static final Logger LOG = LoggerFactory.getLogger(InMemoryCredentialStoreService.class);
+
+ /**
+ * A cache containing the KeyStore data
+ */
+ private final Cache<String, KeyStore> cache;
+
+ /**
+ * Constructs a new InMemoryCredentialStoreService where credentials have no retention timeout
+ */
+ public InMemoryCredentialStoreService() {
+ this(0, TimeUnit.MINUTES, false);
+ }
+
+ /**
+ * Constructs a new InMemoryCredentialStoreService 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 InMemoryCredentialStoreService(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", InMemoryCredentialStoreService.class.getSimpleName()));
+ }
+ 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();
+ }
+
+ @Override
+ public void addCredential(String alias, char[] value) throws AmbariException {
+ if ((alias == null) || alias.isEmpty()) {
+ throw new IllegalArgumentException("Alias cannot be null or empty.");
+ }
+
+ KeyStore keyStore = loadKeyStore(null, DEFAULT_STORE_TYPE);
+ addCredential(keyStore, alias, value);
+ cache.put(alias, keyStore);
+ }
+
+ @Override
+ public char[] getCredential(String alias) throws AmbariException {
+ char[] credential = null;
+
+ if ((alias != null) && !alias.isEmpty()) {
+ KeyStore keyStore = cache.getIfPresent(alias);
+ if (keyStore != null) {
+ credential = getCredential(keyStore, alias);
+ }
+ }
+
+ return credential;
+ }
+
+ @Override
+ public void removeCredential(String alias) throws AmbariException {
+ if (alias != null) {
+ cache.invalidate(alias);
+ }
+ }
+
+ @Override
+ protected void persistCredentialStore(KeyStore keyStore) throws AmbariException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected KeyStore loadCredentialStore() throws AmbariException {
+ throw new UnsupportedOperationException();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
index 219c14b..759fd8e 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/security/encryption/MasterKeyServiceImpl.java
@@ -18,103 +18,62 @@
package org.apache.ambari.server.security.encryption;
+import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ntp.TimeStamp;
+
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
import java.io.IOException;
-import java.io.PrintWriter;
+import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
-import java.util.Random;
public class MasterKeyServiceImpl implements MasterKeyService {
- private static Log LOG = LogFactory.getLog(MasterKeyServiceImpl.class);
- private static final String MASTER_PERSISTENCE_TAG = "#1.0# " +
- TimeStamp.getCurrentTime().toDateString();
- private char[] master = null;
- private String MASTER_PASSPHRASE = "masterpassphrase";
- private AESEncryptor aes = new AESEncryptor(MASTER_PASSPHRASE);
+ private static final Log LOG = LogFactory.getLog(MasterKeyServiceImpl.class);
+ private static final String MASTER_PASSPHRASE = "masterpassphrase";
+ private static final String MASTER_PERSISTENCE_TAG_PREFIX = "#1.0# ";
+ private static final AESEncryptor aes = new AESEncryptor(MASTER_PASSPHRASE);
- // TODO: Create static factory methods vs constructors
+ private char[] master = null;
/**
- * Test/console friendly construction
- * @param masterKey
- * @param masterFileLocation
- * @param persistMaster
+ * Constructs a new MasterKeyServiceImpl using a master key read from a file.
+ *
+ * @param masterKeyFile the location of the master key file
*/
- public MasterKeyServiceImpl(String masterKey, String masterFileLocation,
- boolean persistMaster) {
- this.master = masterKey.toCharArray();
- if (masterFileLocation != null) {
- if (persistMaster) {
- LOG.debug("Persisting master key file.");
- File masterFile = new File(masterFileLocation);
- if (masterFile.exists()) {
- LOG.info("Resetting master key before persist.");
- try {
- PrintWriter pw = new PrintWriter(masterFile);
- pw.print("");
- pw.close();
- } catch (FileNotFoundException e) {
- LOG.error("Cannot reset master key file located at: " +
- masterFileLocation);
- e.printStackTrace();
- }
- }
- persistMaster(masterFile);
- }
- } else {
- if (persistMaster) {
- LOG.error("Cannot persist master key without specifying master key " +
- "location.");
- }
+ public MasterKeyServiceImpl(File masterKeyFile) {
+ if (masterKeyFile == null) {
+ throw new IllegalArgumentException("Master Key location not provided.");
}
- }
- /**
- * Construction - post creation of the key
- * @param masterFileLocation
- * @param isPersisted
- */
- public MasterKeyServiceImpl(String masterFileLocation, boolean isPersisted) {
- if (masterFileLocation == null || masterFileLocation.isEmpty())
- throw new IllegalArgumentException("Master Key location not provided.");
- if (isPersisted) {
- File masterFile = new File(masterFileLocation);
- if (masterFile.exists()) {
+ if (masterKeyFile.exists()) {
+ if (isMasterKeyFile(masterKeyFile)) {
try {
- initializeFromFile(masterFile);
- } catch (Exception ex) {
- LOG.error("Cannot intitialize master key from file: " +
- masterFileLocation + "\n" + ex);
+ initializeFromFile(masterKeyFile);
+ } catch (Exception e) {
+ LOG.error(String.format("Cannot initialize master key from %s: %s", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
}
} else {
- LOG.error("Cannot find master key at specified location " +
- masterFileLocation);
+ LOG.error(String.format("The file at %s is not a master ket file", masterKeyFile.getAbsolutePath()));
}
} else {
- // Master key is not persisted, read from environment.
- String key = readMasterKey();
- if (key != null) {
- this.master = key.toCharArray();
- } else {
- LOG.debug("Master key is not provided as a System property or an " +
- "environment varialble.");
- }
+ LOG.error(String.format("Cannot open master key file, %s", masterKeyFile.getAbsolutePath()));
}
}
/**
- * Construction for Non-persisted master key
- * @param masterKey
+ * Constructs a new MasterKeyServiceImpl using the specified master key.
+ *
+ * @param masterKey the master key
*/
public MasterKeyServiceImpl(String masterKey) {
if (masterKey != null) {
@@ -125,16 +84,190 @@ public class MasterKeyServiceImpl implements MasterKeyService {
}
/**
- * Construction for Non-persisted master key from environment
- *
+ * Constructs a new MasterKeyServiceImpl using the master key found in the environment.
*/
public MasterKeyServiceImpl() {
String key = readMasterKey();
if (key == null) {
- throw new IllegalStateException("Cannot read master key from " +
- "environment.");
- } else
+ throw new IllegalStateException("Cannot read master key from environment.");
+ } else {
this.master = key.toCharArray();
+ }
+ }
+
+ public boolean isMasterKeyInitialized() {
+ return this.master != null;
+ }
+
+ @Override
+ public char[] getMasterSecret() {
+ return this.master;
+ }
+
+ public static void main(String args[]) {
+ String masterKey = "ThisissomeSecretPassPhrasse";
+ String masterKeyLocation = "/var/lib/ambari-server/keys/master";
+ boolean persistMasterKey = false;
+ if (args != null && args.length > 0) {
+ masterKey = args[0];
+ if (args.length > 1) {
+ masterKeyLocation = args[1];
+ }
+ if (args.length > 2 && !args[2].isEmpty()) {
+ persistMasterKey = args[2].toLowerCase().equals("true");
+ }
+ }
+
+ if (persistMasterKey && !MasterKeyServiceImpl.initializeMasterKeyFile(new File(masterKeyLocation), masterKey)) {
+ System.exit(1);
+ } else {
+ System.exit(0);
+ }
+ }
+
+ /**
+ * Initializes the master key file.
+ * <p/>
+ * If the specified file already exists, it it tested to see if it is a master key file. If so, it
+ * will be truncated and the new master key will be stored in the new file. If the file appears
+ * to not be a master key file,no changes will be made. The user must manually remove the file if
+ * deemed appropriate.
+ *
+ * @param masterKeyFile the file to write the master key to
+ * @param masterKey the master key
+ * @return true if the master key was written to the specified file; otherwise false
+ */
+ public static boolean initializeMasterKeyFile(File masterKeyFile, String masterKey) {
+ LOG.debug(String.format("Persisting master key into %s", masterKeyFile.getAbsolutePath()));
+
+ EncryptionResult atom = null;
+
+ if (masterKey != null) {
+ try {
+ atom = aes.encrypt(masterKey);
+ } catch (Exception e) {
+ LOG.error(String.format("Failed to encrypt master key, no changes have been made: %s", e.getLocalizedMessage()), e);
+ return false;
+ }
+ }
+
+ if (masterKeyFile.exists()) {
+ if ((masterKeyFile.length() == 0) || isMasterKeyFile(masterKeyFile)) {
+ LOG.info(String.format("Master key file exists at %s, resetting.", masterKeyFile.getAbsolutePath()));
+ FileChannel fileChannel = null;
+ try {
+ fileChannel = new FileOutputStream(masterKeyFile).getChannel();
+ fileChannel.truncate(0);
+ } catch (FileNotFoundException e) {
+ LOG.error(String.format("Failed to open key file at %s: %s", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
+ } catch (IOException e) {
+ LOG.error(String.format("Failed to reset key file at %s: %s", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
+ } finally {
+ if (fileChannel != null) {
+ try {
+ fileChannel.close();
+ } catch (IOException e) {
+ // Ignore...
+ }
+ }
+ }
+ } else {
+ LOG.info(String.format("File exists at %s, but may not be a master key file. " +
+ "It must be manually removed before this file location can be used", masterKeyFile.getAbsolutePath()));
+ return false;
+ }
+ }
+
+ if (atom != null) {
+ try {
+ ArrayList<String> lines = new ArrayList<String>();
+ lines.add(MASTER_PERSISTENCE_TAG_PREFIX + TimeStamp.getCurrentTime().toDateString());
+
+ String line = Base64.encodeBase64String((
+ Base64.encodeBase64String(atom.salt) + "::" +
+ Base64.encodeBase64String(atom.iv) + "::" +
+ Base64.encodeBase64String(atom.cipher)).getBytes("UTF8"));
+ lines.add(line);
+ FileUtils.writeLines(masterKeyFile, "UTF8", lines);
+
+ // restrict os permissions to only the user running this process
+ protectAccess(masterKeyFile);
+ } catch (IOException e) {
+ LOG.error(String.format("Failed to persist master key to %s: %s ", masterKeyFile.getAbsolutePath(), e.getLocalizedMessage()), e);
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines if the specified file is a "master key" file by checking the file header to see if it
+ * matches an expected value.
+ * <p/>
+ * The "master key" file is expected to have a header (or first line) that starts with "#1.0#". If it,
+ * it is assumed to be a "master key" file, otherwise it is assumed to not be.
+ *
+ * @param file the file to test
+ * @return true if the file is identitified as "master key" file; otherwise false
+ */
+ private static boolean isMasterKeyFile(File file) {
+ FileReader reader = null;
+
+ try {
+ reader = new FileReader(file);
+ char[] buffer = new char[MASTER_PERSISTENCE_TAG_PREFIX.length()];
+ return (reader.read(buffer) == buffer.length) && Arrays.equals(buffer, MASTER_PERSISTENCE_TAG_PREFIX.toCharArray());
+ } catch (Exception e) {
+ // Ignore, assume the file is not a master key file...
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // Ignore...
+ }
+ }
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Ensures that the owner of this process is the only local user account able to read and write to
+ * the specified file or read, write to, and execute the specified directory.
+ *
+ * @param file the file or directory for which to modify access
+ */
+ private static void protectAccess(File file) throws AmbariException {
+ if (file.exists()) {
+ if (!file.setReadable(false, false) || !file.setReadable(true, true)) {
+ String message = String.format("Failed to set %s readable only by current user", file.getAbsolutePath());
+ LOG.warn(message);
+ throw new AmbariException(message);
+ }
+
+ if (!file.setWritable(false, false) || !file.setWritable(true, true)) {
+ String message = String.format("Failed to set %s writable only by current user", file.getAbsolutePath());
+ LOG.warn(message);
+ throw new AmbariException(message);
+ }
+
+ if (file.isDirectory()) {
+ if (!file.setExecutable(false, false) || !file.setExecutable(true, true)) {
+ String message = String.format("Failed to set %s executable by current user", file.getAbsolutePath());
+ LOG.warn(message);
+ throw new AmbariException(message);
+ }
+ } else {
+ if (!file.setExecutable(false, false)) {
+ String message = String.format("Failed to set %s not executable", file.getAbsolutePath());
+ LOG.warn(message);
+ throw new AmbariException(message);
+ }
+ }
+ }
}
private String readMasterKey() {
@@ -149,8 +282,9 @@ public class MasterKeyServiceImpl implements MasterKeyService {
if (keyFile.exists()) {
try {
initializeFromFile(keyFile);
- if (this.master != null)
+ if (this.master != null) {
key = new String(this.master);
+ }
FileUtils.deleteQuietly(keyFile);
} catch (IOException e) {
LOG.error("Cannot read master key from file: " + keyPath);
@@ -166,42 +300,6 @@ public class MasterKeyServiceImpl implements MasterKeyService {
return key;
}
- public boolean isMasterKeyInitialized() {
- return this.master != null;
- }
-
- private EncryptionResult encryptMaster(char[] master) {
- try {
- return aes.encrypt(new String(master));
- } catch (Exception e) {
- // TODO log failed encryption attempt
- // need to ensure that we don't persist now
- e.printStackTrace();
- }
- return null;
- }
-
- private void persistMaster(File masterFile) {
- EncryptionResult atom = encryptMaster(master);
- try {
- ArrayList<String> lines = new ArrayList<String>();
- lines.add(MASTER_PERSISTENCE_TAG);
-
- String line = Base64.encodeBase64String((
- Base64.encodeBase64String(atom.salt) + "::" +
- Base64.encodeBase64String(atom.iv) + "::" +
- Base64.encodeBase64String(atom.cipher)).getBytes("UTF8"));
- lines.add(line);
- FileUtils.writeLines(masterFile, "UTF8", lines);
-
- // restrict os permissions to only the user running this process
- chmod("600", masterFile);
- } catch (IOException e) {
- LOG.error("Failed to persist master. " + e.getLocalizedMessage());
- e.printStackTrace();
- }
- }
-
private void initializeFromFile(File masterFile) throws Exception {
try {
List<String> lines = FileUtils.readLines(masterFile, "UTF8");
@@ -210,8 +308,8 @@ public class MasterKeyServiceImpl implements MasterKeyService {
String line = new String(Base64.decodeBase64(lines.get(1)));
String[] parts = line.split("::");
this.master = new String(aes.decrypt(Base64.decodeBase64(parts[0]),
- Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])),
- "UTF8").toCharArray();
+ Base64.decodeBase64(parts[1]), Base64.decodeBase64(parts[2])),
+ "UTF8").toCharArray();
} catch (IOException e) {
e.printStackTrace();
throw e;
@@ -220,65 +318,4 @@ public class MasterKeyServiceImpl implements MasterKeyService {
throw e;
}
}
-
- @Override
- public char[] getMasterSecret() {
- return this.master;
- }
-
- private void chmod(String args, File file) throws IOException {
- if (isUnixEnv()) {
- //args and file should never be null.
- if (args == null || file == null)
- throw new IOException("nullArg");
- if (!file.exists())
- throw new IOException("fileNotFound");
-
- // " +" regular expression for 1 or more spaces
- final String[] argsString = args.split(" +");
- List<String> cmdList = new ArrayList<String>();
- cmdList.add("/bin/chmod");
- cmdList.addAll(Arrays.asList(argsString));
- cmdList.add(file.getAbsolutePath());
- new ProcessBuilder(cmdList).start();
- }
- }
-
- private boolean isUnixEnv() {
- return (File.separatorChar == '/');
- }
-
- private String generateMasterKey() {
- char[] chars = { 'a', 'b', 'c', 'd', 'e', 'f', 'g',
- 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
- 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
- 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
- '2', '3', '4', '5', '6', '7', '8', '9'};
-
- StringBuffer sb = new StringBuffer();
- Random r = new Random();
- for (int i = 0; i < chars.length; i++) {
- sb.append(chars[r.nextInt(chars.length)]);
- }
- return sb.toString();
- }
-
- public static void main(String args[]) {
- String masterKey = "ThisissomeSecretPassPhrasse";
- String masterKeyLocation = "/var/lib/ambari-server/keys/master";
- boolean persistMasterKey = false;
- if (args != null && args.length > 0) {
- masterKey = args[0];
- if (args.length > 1)
- masterKeyLocation = args[1];
- if (args.length > 2 && !args[2].isEmpty())
- persistMasterKey = args[2].toLowerCase().equals("true");
- }
- MasterKeyService masterKeyService = new MasterKeyServiceImpl
- (masterKey, masterKeyLocation, persistMasterKey);
- if (!masterKeyService.isMasterKeyInitialized()) {
- System.exit(1);
- }
- System.exit(0);
- }
}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
index 19997e7..a173e08 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosCredential.java
@@ -61,7 +61,7 @@ public class KerberosCredential {
/**
* The plaintext password value
*/
- private String password = null;
+ private char[] password = null;
/**
* A base64-encoded keytab
@@ -94,7 +94,7 @@ public class KerberosCredential {
if (map != null) {
Object attribute;
String principal;
- String password;
+ char[] password;
String keytab;
if (prefix == null) {
@@ -105,13 +105,19 @@ public class KerberosCredential {
principal = (attribute == null) ? null : attribute.toString();
attribute = map.get(prefix + KEY_NAME_PASSWORD);
- password = (attribute == null) ? null : attribute.toString();
+ if (attribute instanceof char[]) {
+ password = (char[]) attribute;
+ } else if (attribute instanceof String) {
+ password = ((String) attribute).toCharArray();
+ } else {
+ password = null;
+ }
attribute = map.get(prefix + KEY_NAME_KEYTAB);
keytab = (attribute == null) ? null : attribute.toString();
if (((principal != null) && !principal.isEmpty()) ||
- ((password != null) && !password.isEmpty()) ||
+ ((password != null) && (password.length > 0)) ||
((keytab != null) && !keytab.isEmpty())) {
credential = new KerberosCredential(principal, password, keytab);
}
@@ -141,7 +147,7 @@ public class KerberosCredential {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] plaintext = cipher.doFinal(Base64.decodeBase64(cipherText));
- return new Gson().fromJson(new String(plaintext), KerberosCredential.class);
+ return fromJSON(new String(plaintext));
} catch (NoSuchAlgorithmException e) {
throw new AmbariException("Failed to decrypt cipher text due to invalid encryption algorithm", e);
} catch (NoSuchPaddingException e) {
@@ -184,7 +190,7 @@ public class KerberosCredential {
} catch (NoSuchPaddingException e) {
throw new AmbariException("Failed to encrypt plaintext due to invalid padding scheme algorithm", e);
} catch (IllegalBlockSizeException e) {
- throw new AmbariException("Failed to encrypt plaintext due to invalid key", e);
+ throw new AmbariException("The encryption algorithm is unable to process the input data provided.", e);
} catch (BadPaddingException e) {
throw new AmbariException("Failed to encrypt plaintext due to unexpected reasons", e);
} catch (InvalidKeyException e) {
@@ -206,10 +212,10 @@ public class KerberosCredential {
* Creates a new KerberosCredential
*
* @param principal a String containing the principal name for this Kerberos credential
- * @param password a String containing the password for this Kerberos credential
+ * @param password a char array containing the password for this Kerberos credential
* @param keytab a String containing the base64 encoded keytab for this Kerberos credential
*/
- public KerberosCredential(String principal, String password, String keytab) {
+ public KerberosCredential(String principal, char[] password, String keytab) {
this.principal = principal;
this.password = password;
this.keytab = keytab;
@@ -230,16 +236,16 @@ public class KerberosCredential {
}
/**
- * @return a String containing the password for this Kerberos credential
+ * @return a char array containing the password for this Kerberos credential
*/
- public String getPassword() {
+ public char[] getPassword() {
return password;
}
/**
- * @param password a String containing the password for this Kerberos credential
+ * @param password a char array containing the password for this Kerberos credential
*/
- public void setPassword(String password) {
+ public void setPassword(char[] password) {
this.password = password;
}
@@ -270,4 +276,28 @@ public class KerberosCredential {
public String encrypt(byte[] key) throws AmbariException {
return encrypt(this, key);
}
+
+ /**
+ * Returns a JSON representation of this KerberosCredential
+ *
+ * @return a String containing the JSON representation of this KerberosCredential
+ */
+ public String toJSON() {
+ return new Gson().toJson(this);
+ }
+
+ /**
+ * Renders a new KerberosCredential from its JSON representation
+ *
+ * @param json a string containing a JSON representation of a KerberosCredential
+ * @return a new KerberosCredential or null if a new KerberosCredential cannot be created
+ */
+ public static KerberosCredential fromJSON(String json) {
+ try {
+ return ((json == null) || json.isEmpty()) ? null : new Gson().fromJson(json, KerberosCredential.class);
+ }
+ catch(JsonSyntaxException e) {
+ return null;
+ }
+ }
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
index 425aa06..c8b8ca6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosOperationHandler.java
@@ -503,20 +503,18 @@ public abstract class KerberosOperationHandler {
throw new KerberosAdminAuthenticationException("The administrator credential must not be null");
}
- String value;
-
// Ensure the principal is not null or empty
- value = administratorCredentials.getPrincipal();
- if ((value == null) || value.isEmpty()) {
+ String principal = administratorCredentials.getPrincipal();
+ if ((principal == null) || principal.isEmpty()) {
throw new KerberosAdminAuthenticationException("Must specify a principal but it is null or empty");
}
// Ensure either the password or the keytab value is not null or empty
- value = administratorCredentials.getPassword();
- if ((value == null) || value.isEmpty()) {
- value = administratorCredentials.getKeytab();
+ char[] password = administratorCredentials.getPassword();
+ if ((password == null) || (password.length == 0)) {
+ String keytab = administratorCredentials.getKeytab();
- if ((value == null) || value.isEmpty()) {
+ if ((keytab == null) || keytab.isEmpty()) {
throw new KerberosAdminAuthenticationException("Must specify either a password or a keytab but both are null or empty");
}
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/e681f2bf/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
index 389f1b8..c861b45 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/serveraction/kerberos/KerberosServerAction.java
@@ -23,6 +23,7 @@ import org.apache.ambari.server.AmbariException;
import org.apache.ambari.server.actionmanager.HostRoleStatus;
import org.apache.ambari.server.agent.CommandReport;
import org.apache.ambari.server.agent.ExecutionCommand;
+import org.apache.ambari.server.controller.KerberosHelper;
import org.apache.ambari.server.serveraction.AbstractServerAction;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Clusters;
@@ -59,12 +60,6 @@ public abstract class KerberosServerAction extends AbstractServerAction {
public static final String DATA_DIRECTORY = "data_directory";
/**
- * A (command parameter) property name used to hold encrypted data representing the KDC
- * administrator credentials
- */
- public static final String ADMINISTRATOR_CREDENTIAL = "kerberos_admin_credential";
-
- /**
* A (command parameter) property name used to hold the default Kerberos realm value.
*/
public static final String DEFAULT_REALM = "default_realm";
@@ -146,6 +141,12 @@ public abstract class KerberosServerAction extends AbstractServerAction {
private KerberosIdentityDataFileReaderFactory kerberosIdentityDataFileReaderFactory;
/**
+ * KerberosHelper
+ */
+ @Inject
+ private KerberosHelper kerberosHelper;
+
+ /**
* Given a (command parameter) Map and a property name, attempts to safely retrieve the requested
* data.
*
@@ -305,21 +306,6 @@ public abstract class KerberosServerAction extends AbstractServerAction {
return clusters;
}
-
- /**
- * Given a (command parameter) Map, attempts to safely retrieve the "data_directory" property.
- *
- * @param commandParameters a Map containing the dictionary of data to interrogate
- * @return a String indicating the data directory or null (if not found or set)
- */
- protected KerberosCredential getAdministratorCredential(Map<String, String> commandParameters) throws AmbariException {
- Cluster cluster = getCluster();
-
- // Create the key like we did when we encrypted the data, based on the Cluster objects hashcode.
- byte[] key = Integer.toHexString(cluster.hashCode()).getBytes();
- return KerberosCredential.decrypt(getCommandParameterValue(commandParameters, ADMINISTRATOR_CREDENTIAL), key);
- }
-
/**
* Attempts to safely retrieve the "data_directory" property from the this action's relevant
* command parameters Map.
@@ -355,7 +341,7 @@ public abstract class KerberosServerAction extends AbstractServerAction {
if (commandParameters != null) {
// Grab the relevant data from this action's command parameters map
- KerberosCredential administratorCredential = getAdministratorCredential(commandParameters);
+ KerberosCredential administratorCredential = kerberosHelper.getKDCCredentials();
String defaultRealm = getDefaultRealm(commandParameters);
KDCType kdcType = getKDCType(commandParameters);
String dataDirectoryPath = getDataDirectoryPath(commandParameters);