blob: c36d21f590bb33a8345476d0c52936941f126c4c [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ambari.server.security.encryption;
import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
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 com.google.inject.Inject;
import com.google.inject.Singleton;
@Singleton
public class CredentialStoreServiceImpl implements CredentialStoreService {
private static final Logger LOG = LoggerFactory.getLogger(CredentialStoreServiceImpl.class);
private SecurePasswordHelper securePasswordHelper;
private FileBasedCredentialStore persistedCredentialStore = null;
private InMemoryCredentialStore temporaryCredentialStore = 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 = null;
if(masterKeyLocation.exists()) {
masterKeyService = new MasterKeyServiceImpl(masterKeyLocation);
} else {
masterKeyService = new MasterKeyServiceImpl();
}
if (masterKeyService.isMasterKeyInitialized()) {
try {
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);
}
}
}
}
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 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 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
*/
@Override
public void setCredential(String clusterName, String alias, Credential credential, CredentialStoreType credentialStoreType) throws AmbariException {
validateInitialized(credentialStoreType);
// 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);
}
@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;
}
@Override
public Credential getCredential(String clusterName, String alias, CredentialStoreType credentialStoreType) throws AmbariException {
return (isInitialized(credentialStoreType))
? getCredentialStore(credentialStoreType).getCredential(canonicalizeAlias(clusterName, alias))
: null;
}
@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<>();
if (persistedAliases != null) {
for (String alias : persistedAliases) {
if (isAliasRequested(clusterName, alias)) {
map.put(decanonicalizeAlias(clusterName, alias), CredentialStoreType.PERSISTED);
}
}
}
if (temporaryAliases != null) {
for (String alias : temporaryAliases) {
if (isAliasRequested(clusterName, alias)) {
map.put(decanonicalizeAlias(clusterName, alias), CredentialStoreType.TEMPORARY);
}
}
}
return map;
}
@Override
public synchronized boolean isInitialized() {
return isInitialized(CredentialStoreType.PERSISTED) || isInitialized(CredentialStoreType.TEMPORARY);
}
@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");
}
}
/**
* 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/>
* 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 clusterName the name of the cluster
* @param alias a string declaring the alias (or name) of the credential
* @return a ccanonicalized alias name
*/
public static String canonicalizeAlias(String clusterName, String alias) {
String canonicaizedAlias;
if ((clusterName == null) || clusterName.isEmpty() || (alias == null) || alias.isEmpty()) {
canonicaizedAlias = alias;
} else {
String prefix = createAliasPrefix(clusterName);
if (alias.toLowerCase().startsWith(prefix)) {
canonicaizedAlias = alias;
} else {
canonicaizedAlias = prefix + alias;
}
}
return (canonicaizedAlias == null)
? null
: canonicaizedAlias.toLowerCase();
}
/**
* Removes the prefix (if exists) from the front of a canonicalized alias
*
* @param clusterName the name the name of the cluster
* @param canonicaizedAlias the canonicalized alias to process
* @return an alias name
*/
public static String decanonicalizeAlias(String clusterName, String canonicaizedAlias) {
if ((clusterName == null) || clusterName.isEmpty() || (canonicaizedAlias == null) || canonicaizedAlias.isEmpty()) {
return canonicaizedAlias;
} else {
String prefix = createAliasPrefix(clusterName);
if (canonicaizedAlias.startsWith(prefix)) {
return canonicaizedAlias.substring(prefix.length());
} else {
return canonicaizedAlias;
}
}
}
/**
* Creates the prefix that is to be set in a canonicalized alias name.
*
* @param clusterName the name of the cluster
* @return the prefix value
*/
private static String createAliasPrefix(String clusterName) {
return ("cluster." + clusterName + ".").toLowerCase();
}
/**
* Tests the canonicalized alias name to see if it should be returned within the set of credentials.
* <p/>
* This filters out all credentials not tagged for a specific cluster.
*
* @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
*/
private boolean isAliasRequested(String clusterName, String canonicalizedAlias) {
return (clusterName == null) || canonicalizedAlias.toLowerCase().startsWith(createAliasPrefix(clusterName));
}
/**
* 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");
}
}
/**
* 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())
);
}
}
}