/* * 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()) ); } } }