/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.systemservices.impl.ipsec;
import com.emc.storageos.coordinator.client.model.Site;
import com.emc.storageos.coordinator.client.model.SiteInfo;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.client.service.DrUtil;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.VirtualDataCenter;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.model.ipsec.IPsecStatus;
import com.emc.storageos.model.ipsec.IpsecParam;
import com.emc.storageos.security.geo.GeoClientCacheManager;
import com.emc.storageos.security.helpers.SecurityUtil;
import com.emc.storageos.security.ipsec.IPsecConfig;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.systemservices.impl.upgrade.LocalRepository;
import com.emc.storageos.security.exceptions.SecurityException;
import org.apache.commons.lang.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* This class is to handle all ipsec related requests from web app.
*/
public class IPsecManager {
private static final int KEY_LENGTH = 64;
private static final char[] charsForKey =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
private static final Logger log = LoggerFactory.getLogger(IPsecManager.class);
public static final String STATUS_ENABLED = "enabled";
public static final String STATUS_DISABLED = "disabled";
private static final String STATUS_GOOD = "good";
private static final String STATUS_DEGRADED = "degraded";
@Autowired
private IPsecConfig ipsecConfig;
private CoordinatorClient coordinator;
@Autowired
private DrUtil drUtil;
@Autowired
DbClient dbClient;
@Autowired
private GeoClientCacheManager geoClientManager;
/**
* generate a 64-byte key for IPsec
* @return
*/
public String generateKey() throws Exception {
return RandomStringUtils.random(KEY_LENGTH, 0, charsForKey.length-1,
true, true, charsForKey, SecureRandom.getInstance(SecurityUtil.getSecuredRandomAlgorithm()));
}
/**
* Checking ipsec status against the entire system.
* @return
*/
public IPsecStatus checkStatus() {
log.info("Checking ipsec status ...");
IPsecStatus status = new IPsecStatus();
String vdcConfigVersion = loadVdcConfigVersionFromZK();
status.setVersion(vdcConfigVersion);
String ipsecKeyUpdatedTime = ipsecConfig.getIpsecKeyUpdatedTime();
String ipsecStatus = ipsecConfig.getIpsecStatus();
if (ipsecStatus != null && ipsecStatus.equals(STATUS_DISABLED)) {
status.setStatus(ipsecStatus);
} else {
List<String> disconnectedNodes = checkIPsecStatus();
if (CollectionUtils.isEmpty(disconnectedNodes)) {
status.setStatus(STATUS_GOOD);
} else {
status.setStatus(STATUS_DEGRADED);
status.setDisconnectedNodes(disconnectedNodes);
}
if (ipsecKeyUpdatedTime == null ) { // No this field in last version
ipsecKeyUpdatedTime = vdcConfigVersion;
}
status.setUpdatedTime(ipsecKeyUpdatedTime);
}
return status;
}
/**
* Rotate IPsec preshared key for the entired system.
* @return
*/
public String rotateKey(boolean enableIpsec) {
try {
String psk = generateKey();
long vdcConfigVersion = DrUtil.newVdcConfigVersion();
String ipsecStatus = null;
if (enableIpsec) {
ipsecStatus = STATUS_ENABLED;
}
// send to other VDCs if has.
updateIPsecKeyToOtherVDCs(psk, vdcConfigVersion, ipsecStatus);
// finally update local vdc
if (enableIpsec) {
ipsecConfig.setIpsecStatus(ipsecStatus);
}
ipsecConfig.setPreSharedKey(psk);
updateTargetSiteInfo(vdcConfigVersion);
log.info("IPsec Key gets rotated successfully to the version {}", vdcConfigVersion);
return Long.toString(vdcConfigVersion);
} catch (Exception e) {
log.warn("Fail to rotate ipsec key.", e);
throw SecurityException.fatals.failToRotateIPsecKey(e);
}
}
/**
* Rotate preshared key for the entired system.
* @return
*/
public String rotateKey() {
return rotateKey(false);
}
private void updateIPsecKeyToOtherVDCs(String psk, long vdcConfigVersion, String ipsecStatus) {
if (! drUtil.isMultivdc()) {
log.info("This is not Geo deployment. No need to update ipsec key to other VDCs");
return;
}
List<String> vdcIds = drUtil.getOtherVdcIds();
for (String peerVdcId : vdcIds) {
IpsecParam ipsecParam = buildIpsecParam(vdcConfigVersion, psk, ipsecStatus);
geoClientManager.getGeoClient(peerVdcId).rotateIpsecKey(peerVdcId, ipsecParam);
}
log.info("Updated all the VDCs latest ipsec properties");
}
private IpsecParam buildIpsecParam(long vdcConfigVersion, String ipsecKey, String ipsecStatus) {
IpsecParam param = new IpsecParam();
param.setIpsecKey(ipsecKey);
param.setVdcConfigVersion(vdcConfigVersion);
param.setIpsecStatus(ipsecStatus);
return param;
}
/**
* enable/disable IPSec for the vdc
*
* @param status
* @return
*/
public String changeIpsecStatus(String status) {
return changeIpsecStatus(status, true);
}
public String changeIpsecStatus(String status, boolean bChangeStatusForOtherVdcs) {
if (status != null && (status.equalsIgnoreCase(STATUS_ENABLED) || status.equalsIgnoreCase(STATUS_DISABLED))) {
String oldState = ipsecConfig.getIpsecStatus();
if (status.equalsIgnoreCase(oldState)) {
log.info("ipsec already in state: " + oldState + ", skip the operation.");
return oldState;
}
log.info("changing Ipsec State from " + oldState + " to " + status);
// in GEO env, sending request to other vdcs
if (bChangeStatusForOtherVdcs && drUtil.isMultivdc()) {
List<String> vdcIds = drUtil.getOtherVdcIds();
String vdcConfigVersion = loadVdcConfigVersionFromZK();
for (String peerVdcId : vdcIds) {
log.info("changing ipsec status for: " + vdcIds);
geoClientManager.getGeoClient(peerVdcId).changeIpsecStatus(peerVdcId,
status, vdcConfigVersion);
}
}
ipsecConfig.setIpsecStatus(status);
} else {
throw APIException.badRequests.invalidIpsecStatus();
}
String version = updateTargetSiteInfo(DrUtil.newVdcConfigVersion());
log.info("ipsec state changed, and new config version is {}", version);
return status;
}
public boolean isKeyRotationDone() throws Exception {
return CollectionUtils.isEmpty(checkIPsecStatus());
}
private List<String> checkIPsecStatus() {
LocalRepository localRepository = new LocalRepository();
String[] disconnectedIPs = localRepository.checkIpsecConnection();
if (disconnectedIPs[0].isEmpty()) {
log.info("IPsec runtime status is good.");
return new ArrayList<String>(); // return empty list to avoid null pointer in java client.
} else {
log.info("Some nodes disconnected over IPsec {}", disconnectedIPs);
return Arrays.asList(disconnectedIPs);
}
}
private String loadVdcConfigVersionFromZK() {
String vdcConfigVersion = Long.toString(coordinator.getTargetInfo(SiteInfo.class).getVdcConfigVersion());
log.info("Loaded Vdc config version is {}", vdcConfigVersion);
return vdcConfigVersion;
}
private String updateTargetSiteInfo(long vdcConfigVersion) {
for (Site site : drUtil.listSites()) {
SiteInfo siteInfo;
String siteId = site.getUuid();
SiteInfo currentSiteInfo = coordinator.getTargetInfo(siteId, SiteInfo.class);
if (currentSiteInfo != null) {
siteInfo = new SiteInfo(vdcConfigVersion, SiteInfo.IPSEC_OP_ROTATE_KEY, currentSiteInfo.getTargetDataRevision(), SiteInfo.ActionScope.VDC);
} else {
siteInfo = new SiteInfo(vdcConfigVersion, SiteInfo.IPSEC_OP_ROTATE_KEY, SiteInfo.ActionScope.VDC);
}
coordinator.setTargetInfo(siteId, siteInfo);
log.info("VDC target version updated to {} for site {}", siteInfo.getVdcConfigVersion(), siteId);
}
return Long.toString(vdcConfigVersion);
}
/**
* make sure cluster is in stable status
*/
public void verifyIPsecOpAllowable() {
verifyIPsecOpAllowableOverGeo();
drUtil.verifyIPsecOpAllowableWithinDR();
}
private void verifyIPsecOpAllowableOverGeo() {
if (!drUtil.isMultivdc()) {
return;
}
// Other VDCs are stable
List<String> vdcIds = drUtil.getOtherVdcIds();
for (String peerVdcId : vdcIds) {
if (!geoClientManager.getGeoClient(peerVdcId).isVdcStable()) {
log.error(vdcIds + " is not stable");
throw APIException.serviceUnavailable.vdcNotStable(peerVdcId);
}
}
// No ongoing jobs
VdcUtil.setDbClient(dbClient);
VirtualDataCenter localVdc = VdcUtil.getLocalVdc();
VirtualDataCenter.ConnectionStatus vdcStatus = localVdc.getConnectionStatus();
if (! vdcStatus.equals(VirtualDataCenter.ConnectionStatus.CONNECTED) &&
! vdcStatus.equals(VirtualDataCenter.ConnectionStatus.ISOLATED)) {
throw APIException.serviceUnavailable.vdcOngingJob(localVdc.getShortId(), vdcStatus.name());
}
}
/**
* get the coordinator client
* @return
*/
public CoordinatorClient getCoordinator() {
return coordinator;
}
/**
* set the coordinator client.
* @param coordinator
*/
public void setCoordinator(CoordinatorClient coordinator) {
this.coordinator = coordinator;
}
/**
* Check if ipsec is enabled.
* @return
*/
public boolean isEnabled() {
return ipsecConfig.getIpsecStatus() == null ||
ipsecConfig.getIpsecStatus().equals(STATUS_ENABLED);
}
}