/*
* Copyright (c) 2013-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.security.keystore.impl;
import java.io.IOException;
import java.security.KeyStore.LoadStoreParameter;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.time.DateUtils;
import org.apache.log4j.lf5.LogLevel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.coordinator.client.model.Constants;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.security.exceptions.SecurityException;
import com.emc.storageos.security.keystore.DistributedKeyStore;
import com.emc.storageos.services.util.AlertsLogger;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
/**
* Implementation for ZooKeeper based keystore
*/
public class DistributedKeyStoreImpl implements DistributedKeyStore {
static final String KEY_CERTIFICATE_PAIR_LOCK = "keyCertificatePairLock";
static final String KEY_CERTIFICATE_PAIR_CONFIG_KIND = Constants.KEY_CERTIFICATE_PAIR_CONFIG_KIND;
static final String KEY_CERTIFICATE_PAIR_ID = "keyCertificatePairId";
static final String KEY_CERTIFICATE_PAIR_KEY = "keyCertificatePairEntry";
static final String IS_SELF_GENERATED_KEY = "isSelfGeneratedKeyCertificatePairEntry";
public static final String TRUSTED_CERTIFICATES_LOCK = "trustedCertificatesLock";
public static final String TRUSTED_CERTIFICATES_CONFIG_KIND = "trustedCertificatesConfig";
private static final String LAST_CERTIFICATE_ALERT_ID = "lastCertificateAlertId";
private static final String LAST_CERTIFICATE_ALERT_KEY = "keyCertificatePairEntry";
private static final String TRUSTED_CERTIFICATES_CONFIG_KEY = "trustedCertificatesKey";
public static final String CA_CERTIFICATES_CONFIG_KIND = "caCerts";
static final String CA_CERTIFICATES_CONFIG_ID = "caCertsConfig";
static final String CA_CERTIFICATES_CONFIG_KEY_VERSION = "caCertsVersion";
public static final String UPDATE_LOG = "updateLog";
public static final String UPDATE_TIME = "updateTime";
private static Logger log = LoggerFactory.getLogger(DistributedKeyStoreImpl.class);
private static AlertsLogger alertsLog = AlertsLogger.getAlertsLogger();
private CoordinatorConfigStoringHelper coordConfigStoringHelper;
private KeyCertificatePairGenerator generator;
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#init(java.security.KeyStore.LoadStoreParameter)
*/
@Override
public void init(LoadStoreParameter param) throws SecurityException {
if (param instanceof DistributedLoadKeyStoreParam) {
DistributedLoadKeyStoreParam zkConnectionInfo =
(DistributedLoadKeyStoreParam) param;
CoordinatorClient coordinator = zkConnectionInfo.getCoordinator();
coordConfigStoringHelper = new CoordinatorConfigStoringHelper(coordinator);
generator = new KeyCertificatePairGenerator();
generator
.setKeyCertificateAlgorithmValuesHolder(new KeyCertificateAlgorithmValuesHolder(
coordinator));
} else {
throw SecurityException.fatals
.failedToInitializedKeystoreNeedDistKeystoreParams();
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#getTrustedCertificates()
*/
@Override
public Map<String, TrustedCertificateEntry> getTrustedCertificates()
throws SecurityException {
try {
return coordConfigStoringHelper.readAllConfigs(TRUSTED_CERTIFICATES_CONFIG_KIND,
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#setTrustedCertificates(java.util.Map)
*/
@Override
public synchronized void setTrustedCertificates(
Map<String, TrustedCertificateEntry> trustedCerts)
throws SecurityException {
try {
coordConfigStoringHelper.removeAllConfigOfKInd(TRUSTED_CERTIFICATES_LOCK,
TRUSTED_CERTIFICATES_CONFIG_KIND);
} catch (Exception e) {
// it doesn't really matter if this failed
log.warn(e.getMessage(), e);
}
if (!CollectionUtils.isEmpty(trustedCerts)) {
for (Entry<String, TrustedCertificateEntry> entry : trustedCerts.entrySet()) {
try {
log.info("adding the following as trusted certificate:"
+ entry.getValue().getCertificate().toString());
coordConfigStoringHelper.createOrUpdateConfig(entry.getValue(),
TRUSTED_CERTIFICATES_LOCK,
TRUSTED_CERTIFICATES_CONFIG_KIND, entry.getKey(),
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateTrustedCertificates(e);
}
}
}
}
@Override
public Map<String, TrustedCertificateEntry> getCACertificates() {
try {
return coordConfigStoringHelper.readAllConfigs(CA_CERTIFICATES_CONFIG_KIND,
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
@Override
public void setCACertificates(Map<String, TrustedCertificateEntry> trustedCerts) {
// clean all before setting
try {
coordConfigStoringHelper.removeAllConfigOfKInd(TRUSTED_CERTIFICATES_LOCK,
CA_CERTIFICATES_CONFIG_KIND);
} catch (Exception e) {
// it doesn't really matter if this failed
log.warn(e.getMessage(), e);
}
if (CollectionUtils.isEmpty(trustedCerts)) {
return;
}
// set
for (Entry<String, TrustedCertificateEntry> entry : trustedCerts.entrySet()) {
try {
log.info("adding the following as trusted certificate:"
+ entry.getValue().getCertificate().toString());
coordConfigStoringHelper.createOrUpdateConfig(entry.getValue(),
TRUSTED_CERTIFICATES_LOCK,
CA_CERTIFICATES_CONFIG_KIND, entry.getKey(),
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateTrustedCertificates(e);
}
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#removeTrustedCertificate(java.lang.String)
*/
@Override
public synchronized void removeCACertificate(String alias)
throws SecurityException {
try {
log.info("removing the trusted CA certificate whose alias is " + alias);
coordConfigStoringHelper.removeConfig(TRUSTED_CERTIFICATES_LOCK,
CA_CERTIFICATES_CONFIG_KIND,
alias);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateTrustedCertificates(e);
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#addTrustedCertificate(java.lang.String,
* com.emc.storageos.security.keystore.TrustedCertificateEntry)
*/
@Override
public synchronized void addTrustedCertificate(String alias,
TrustedCertificateEntry cert)
throws SecurityException {
try {
log.info("adding the following trusted certificate under alias " + alias
+ ": " + cert.getCertificate().toString());
coordConfigStoringHelper.createOrUpdateConfig(cert, TRUSTED_CERTIFICATES_LOCK,
TRUSTED_CERTIFICATES_CONFIG_KIND, alias,
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateTrustedCertificates(e);
}
}
/*
* (non-Javadoc)
*
* @see com.emc.storageos.security.keystore.DistributedKeyStore#removeTrustedCertificate(java.lang.String)
*/
@Override
public synchronized void removeTrustedCertificate(String alias)
throws SecurityException {
try {
log.info("removing the trusted certificate whose alias is " + alias);
coordConfigStoringHelper.removeConfig(TRUSTED_CERTIFICATES_LOCK,
TRUSTED_CERTIFICATES_CONFIG_KIND,
alias);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateTrustedCertificates(e);
}
}
/**
* @see com.emc.storageos.security.keystore.DistributedKeyStore#getKeyCertificatePair()
*/
@Override
public KeyCertificateEntry getKeyCertificatePair() throws SecurityException {
log.info("Retrieving ViPR certificate");
KeyCertificateEntry entryToReturn;
try {
entryToReturn = readKeyCertificateEntry();
if (entryToReturn == null) {
entryToReturn = setupKeyCertificatePair();
} else {
entryToReturn = checkKeyCertificatePair(entryToReturn);
}
log.info("Retrieved ViPR certificate successfully");
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadKeyCertificateEntry(e);
}
return entryToReturn;
}
private InterProcessLock acquireKeyCertificatePairLock() {
InterProcessLock lock;
try {
lock = coordConfigStoringHelper.acquireLock(KEY_CERTIFICATE_PAIR_LOCK);
} catch (Exception e) {
throw SecurityException.fatals.failedToGetKeyCertificate();
}
if (lock == null) {
throw SecurityException.fatals.failedToGetKeyCertificate();
}
return lock;
}
private KeyCertificateEntry setupKeyCertificatePair() throws IOException, ClassNotFoundException{
InterProcessLock lock = null;
try {
lock = acquireKeyCertificatePairLock();
// re-read the key/cert pair after lock acquired. avoid the case another concurrent thread may have done that
KeyCertificateEntry entryToReturn = readKeyCertificateEntry();
if (entryToReturn == null) {
log.info("ViPR certificate not found");
entryToReturn = generator.tryGetV1Cert();
if (entryToReturn != null) {
KeyStoreUtil.setSelfGeneratedCertificate(coordConfigStoringHelper,
Boolean.FALSE);
entryToReturn.setCreationDate(new Date());
setKeyCertificatePair(entryToReturn);
} else {
log.info("Generating new certificate");
entryToReturn = generateNewKeyCertificatePair();
}
}
return entryToReturn;
} finally {
coordConfigStoringHelper.releaseLock(lock);
}
}
private KeyCertificateEntry checkKeyCertificatePair(KeyCertificateEntry entry) throws IOException, ClassNotFoundException {
InterProcessLock lock = null;
X509Certificate cert = (X509Certificate) entry.getCertificateChain()[0];
if (KeyStoreUtil.isSelfGeneratedCertificate(coordConfigStoringHelper)
&& !generator.isCertificateIPsCorrect(cert)) {
try {
lock = acquireKeyCertificatePairLock();
// re-read the key/cert pair after lock acquired. avoid the case another concurrent thread may have done that
entry = readKeyCertificateEntry();
if (!generator.isCertificateIPsCorrect(cert)) {
log.info("ViPR certificate is self generated and has illegal IPs. Generating a new one...");
entry = generateNewKeyCertificatePair();
}
} finally {
coordConfigStoringHelper.releaseLock(lock);
}
}
checkCertificateDateValidity(cert);
return entry;
}
private KeyCertificateEntry readKeyCertificateEntry() throws IOException, ClassNotFoundException{
KeyCertificateEntry entryToReturn =
coordConfigStoringHelper.readConfig(coordConfigStoringHelper.getSiteId(), KEY_CERTIFICATE_PAIR_CONFIG_KIND,
KEY_CERTIFICATE_PAIR_ID,
KEY_CERTIFICATE_PAIR_KEY);
if (entryToReturn == null) {
log.info("Certificate not found from site specific area. Try global area");
entryToReturn =
coordConfigStoringHelper.readConfig(KEY_CERTIFICATE_PAIR_CONFIG_KIND,
KEY_CERTIFICATE_PAIR_ID,
KEY_CERTIFICATE_PAIR_KEY);
if (entryToReturn != null) {
InterProcessLock lock = null;
try {
lock = acquireKeyCertificatePairLock();
// re-read from global area after acquiring the lock
entryToReturn =
coordConfigStoringHelper.readConfig(KEY_CERTIFICATE_PAIR_CONFIG_KIND,
KEY_CERTIFICATE_PAIR_ID,
KEY_CERTIFICATE_PAIR_KEY);
if (entryToReturn != null) {
String siteId = coordConfigStoringHelper.getSiteId();
log.info("Found certificate from global area. Moving to site specific area");
coordConfigStoringHelper.createOrUpdateConfig(entryToReturn, KEY_CERTIFICATE_PAIR_LOCK,
siteId, KEY_CERTIFICATE_PAIR_CONFIG_KIND, KEY_CERTIFICATE_PAIR_ID,
KEY_CERTIFICATE_PAIR_KEY);
Boolean isSelfSigned = coordConfigStoringHelper.readConfig(
DistributedKeyStoreImpl.KEY_CERTIFICATE_PAIR_CONFIG_KIND,
DistributedKeyStoreImpl.KEY_CERTIFICATE_PAIR_ID,
DistributedKeyStoreImpl.IS_SELF_GENERATED_KEY);
KeyStoreUtil.setSelfGeneratedCertificate(coordConfigStoringHelper, isSelfSigned);
coordConfigStoringHelper.removeConfig(KEY_CERTIFICATE_PAIR_LOCK, KEY_CERTIFICATE_PAIR_CONFIG_KIND, KEY_CERTIFICATE_PAIR_ID);
}
} catch (Exception ex) {
log.error("Failed to move key certificate pair to site specific area", ex);
} finally {
coordConfigStoringHelper.releaseLock(lock);
}
}
}
return entryToReturn;
}
/**
* Generates a new key certificate pair
*
* @return
*/
private KeyCertificateEntry generateNewKeyCertificatePair() {
KeyCertificateEntry entryToReturn;
entryToReturn = generator.generateKeyCertificatePair();
entryToReturn.setCreationDate(new Date());
setKeyCertificatePair(entryToReturn);
KeyStoreUtil.setSelfGeneratedCertificate(coordConfigStoringHelper, Boolean.TRUE);
return entryToReturn;
}
/**
* checks if the certificate is about to expire, and alerts in case it is
*
* @param certificate
* the certificate to check
*/
private void checkCertificateDateValidity(X509Certificate certificate) {
final String CERTIFICATE_EXPIRED_MESSAGE =
"ViPR's certificate has expired. Please set a new one, or post a regenerate request";
final String CERTIFICATE_WILL_EXPIRE_MESSAGE_FORMAT =
"ViPR's certificate will expire within %d %s. Please make arrangements to set a new certificate or to post a regenerate request";
Date lastCertificateAlert = null;
try {
lastCertificateAlert =
coordConfigStoringHelper.readConfig(coordConfigStoringHelper.getSiteId(), KEY_CERTIFICATE_PAIR_CONFIG_KIND,
LAST_CERTIFICATE_ALERT_ID, LAST_CERTIFICATE_ALERT_KEY);
} catch (Exception e) {
// don't really care about the exception here
log.warn(e.getMessage(), e);
}
Date notAfter = DateUtils.truncate(certificate.getNotAfter(), Calendar.DATE);
Date today = DateUtils.truncate(new Date(), Calendar.DATE);
Date nextWeek = DateUtils.addWeeks(today, 1);
Date nextMonth = DateUtils.addMonths(today, 1);
Date next3Months = DateUtils.addMonths(today, 3);
Date next6Months = DateUtils.addMonths(today, 6);
if (lastCertificateAlert == null || DateUtils.truncatedCompareTo(lastCertificateAlert, today, Calendar.DATE) < 0) {
boolean logAlert = false;
String messageToLog = CERTIFICATE_WILL_EXPIRE_MESSAGE_FORMAT;
int timeAmount = 0;
String timeType = null;
LogLevel logLevel = LogLevel.WARN;
if (notAfter.before(today)) {
logLevel = LogLevel.FATAL;
messageToLog = CERTIFICATE_EXPIRED_MESSAGE;
logAlert = true;
} else if (DateUtils.isSameDay(notAfter, today)) {
timeType = "days";
logLevel = LogLevel.ERROR;
logAlert = true;
} else if (notAfter.before(nextWeek)) {
timeAmount = 1;
timeType = "week";
logAlert = true;
} else if (notAfter.before(nextMonth)) {
timeAmount = 1;
timeType = "month";
logAlert = true;
} else if (notAfter.before(next3Months)) {
timeAmount = 3;
timeType = "months";
logAlert = true;
} else if (notAfter.before(next6Months)) {
timeAmount = 6;
timeType = "months";
logAlert = true;
}
if (logAlert) {
logAlert(messageToLog, timeAmount, timeType, logLevel);
try {
coordConfigStoringHelper.createOrUpdateConfig(today, KEY_CERTIFICATE_PAIR_LOCK,
coordConfigStoringHelper.getSiteId(), KEY_CERTIFICATE_PAIR_CONFIG_KIND, LAST_CERTIFICATE_ALERT_ID,
LAST_CERTIFICATE_ALERT_KEY);
} catch (Exception e) {
log.error(
"Could not set the time of last alert about certificate expiry",
e);
}
}
}
}
/**
*
* @param messageFormatToLog
* @param timeAmount
* @param timeType
* @param logLevel
*/
private void logAlert(String messageFormatToLog, int timeAmount, String timeType,
LogLevel logLevel) {
String messageToLog = String.format(messageFormatToLog, timeAmount, timeType);
if (logLevel == LogLevel.FATAL) {
alertsLog.fatal(messageToLog);
log.error(messageToLog);
} else if (logLevel == LogLevel.ERROR) {
alertsLog.error(messageToLog);
log.error(messageToLog);
} else if (logLevel == LogLevel.WARN) {
alertsLog.warn(messageToLog);
log.warn(messageToLog);
}
}
/*
* (non-Javadoc)
*
* @see
* com.emc.storageos.security.keystore.DistributedKeyStore#addTrustedCertificate(com
* .emc.storageos.security.keystore.impl.KeyCertificateEntry)
*/
@Override
public void setKeyCertificatePair(KeyCertificateEntry entry)
throws SecurityException {
try {
if (entry != null) {
log.info("Setting ViPR's key and certificate chain. New certificate is: "
+ entry.getCertificateChain()[0]);
}
coordConfigStoringHelper.createOrUpdateConfig(entry, KEY_CERTIFICATE_PAIR_LOCK,
coordConfigStoringHelper.getSiteId(), KEY_CERTIFICATE_PAIR_CONFIG_KIND, KEY_CERTIFICATE_PAIR_ID,
KEY_CERTIFICATE_PAIR_KEY);
} catch (Exception e) {
throw SecurityException.fatals.failedToUpdateKeyCertificateEntry(e);
}
try {
coordConfigStoringHelper.removeConfig(KEY_CERTIFICATE_PAIR_LOCK,
KEY_CERTIFICATE_PAIR_CONFIG_KIND, LAST_CERTIFICATE_ALERT_ID);
} catch (Exception e) {
// don't really care if this fails
log.error("Could not set the time of last alert about certificate expiry", e);
}
}
@Override
public TrustedCertificateEntry getUserAddedCert(String alias) {
try {
return coordConfigStoringHelper.readConfig(TRUSTED_CERTIFICATES_CONFIG_KIND, alias,
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
@Override
public TrustedCertificateEntry getViprAddedCert(String alias) {
try {
return coordConfigStoringHelper.readConfig(CA_CERTIFICATES_CONFIG_KIND, alias,
TRUSTED_CERTIFICATES_CONFIG_KEY);
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
@Override
public boolean containsUserAddedCerts(String alias) {
try {
if (null == coordConfigStoringHelper.readConfig(TRUSTED_CERTIFICATES_CONFIG_KIND, alias,
TRUSTED_CERTIFICATES_CONFIG_KEY)) {
return false;
} else {
return true;
}
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
@Override
public boolean containsViprSuppliedCerts(String alias) {
try {
if (null == coordConfigStoringHelper.readConfig(CA_CERTIFICATES_CONFIG_KIND, alias,
TRUSTED_CERTIFICATES_CONFIG_KEY)) {
return false;
} else {
return true;
}
} catch (IOException | ClassNotFoundException e) {
throw SecurityException.fatals.failedToReadTrustedCertificates(e);
}
}
}