/*
* Copyright (c) 2008-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.DbObjectMapper.toNamedRelatedResource;
import static com.emc.storageos.api.mapper.VirtualDataCenterMapper.map;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.Principal;
import java.security.cert.*;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;
import javax.crypto.SecretKey;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.emc.storageos.coordinator.client.model.SiteState;
import com.emc.storageos.db.client.model.*;
import com.emc.storageos.security.helpers.SecurityUtil;
import com.emc.vipr.model.sys.ClusterInfo;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.emc.storageos.api.mapper.TaskMapper;
import com.emc.storageos.api.service.authorization.PermissionsHelper;
import com.emc.storageos.coordinator.client.model.Site;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.client.service.DrUtil;
import com.emc.storageos.coordinator.common.Service;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.model.VirtualDataCenter.ConnectionStatus;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.TaskResourceRep;
import com.emc.storageos.model.auth.PrincipalsToValidate;
import com.emc.storageos.model.auth.RoleAssignmentChanges;
import com.emc.storageos.model.auth.RoleAssignmentEntry;
import com.emc.storageos.model.auth.RoleAssignments;
import com.emc.storageos.model.vdc.VirtualDataCenterAddParam;
import com.emc.storageos.model.vdc.VirtualDataCenterList;
import com.emc.storageos.model.vdc.VirtualDataCenterModifyParam;
import com.emc.storageos.model.vdc.VirtualDataCenterRestRep;
import com.emc.storageos.model.vdc.VirtualDataCenterSecretKeyRestRep;
import com.emc.storageos.security.audit.AuditLogManager;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator;
import com.emc.storageos.security.authentication.InternalApiSignatureKeyGenerator.SignatureKeyType;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.CheckPermission;
import com.emc.storageos.security.authorization.DefaultPermissions;
import com.emc.storageos.security.authorization.PermissionsKey;
import com.emc.storageos.security.authorization.Role;
import com.emc.storageos.security.exceptions.SecurityException;
import com.emc.storageos.security.geo.GeoServiceHelper;
import com.emc.storageos.security.geo.GeoServiceJob;
import com.emc.storageos.security.geo.GeoServiceJob.JobType;
import com.emc.storageos.security.keystore.impl.CertificateVersionHelper;
import com.emc.storageos.security.keystore.impl.CoordinatorConfigStoringHelper;
import com.emc.storageos.security.keystore.impl.KeyCertificateAlgorithmValuesHolder;
import com.emc.storageos.security.keystore.impl.KeyCertificateEntry;
import com.emc.storageos.security.keystore.impl.KeyCertificatePairGenerator;
import com.emc.storageos.security.keystore.impl.KeyStoreUtil;
import com.emc.storageos.security.keystore.impl.KeystoreEngine;
import com.emc.storageos.security.validator.Validator;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.model.ServiceCoded;
import com.emc.storageos.svcs.errorhandling.model.ServiceError;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.InternalServerErrorException;
import com.emc.storageos.svcs.errorhandling.resources.ServiceCode;
import com.emc.vipr.model.keystore.CertificateChain;
import com.emc.vipr.model.keystore.KeyAndCertificateChain;
import com.emc.vipr.model.keystore.RotateKeyAndCertParam;
/**
* Resource for VirtualDataCenter manipulation
*/
@Path("/vdc")
@DefaultPermissions(readRoles = { Role.SECURITY_ADMIN, Role.SYSTEM_ADMIN, Role.SYSTEM_MONITOR },
writeRoles = { Role.SECURITY_ADMIN })
public class VirtualDataCenterService extends TaskResourceService {
@Autowired
@Qualifier("keyGenerator")
InternalApiSignatureKeyGenerator apiSignatureGenerator;
@Autowired
private Service service;
@Autowired
private DrUtil drUtil;
private Map<String, StorageOSUser> _localUsers;
public void setLocalUsers(Map<String, StorageOSUser> localUsers) {
_localUsers = localUsers;
}
private static final String EVENT_SERVICE_TYPE = "vdc";
private static final String ROOT = "root";
private static final Logger _log = LoggerFactory.getLogger(VirtualDataCenterService.class);
// for retrieving subject alternative names from certificate
private static final int SUBALTNAME_DNSNAME = 2;
private static final int SUBALTNAME_IPADDRESS = 7;
private static final String VERSION_PART_SEPERATOR = "\\.";
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
@Override
protected URI getTenantOwner(URI id) {
// vdc is not a tenant resource
return null;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.VDC;
}
private CoordinatorClient coordinator;
@Override
public void setCoordinator(CoordinatorClient coordinator) {
this.coordinator = coordinator;
}
private CoordinatorConfigStoringHelper coordConfigStoringHelper;
public void setCoordConfigStoringHelper(CoordinatorConfigStoringHelper coordConfigStoringHelper) {
this.coordConfigStoringHelper = coordConfigStoringHelper;
}
protected GeoServiceHelper _geoHelper;
public void setGeoServiceHelper(GeoServiceHelper geohelper) {
_geoHelper = geohelper;
}
private KeyCertificatePairGenerator generator;
private KeyStore viprKeyStore;
private KeyStore getKeyStore() {
if (viprKeyStore == null) {
try {
viprKeyStore = KeyStoreUtil.getViPRKeystore(coordinator);
} catch (Exception e) {
_log.error("Failed to load the VIPR keystore", e);
throw new IllegalStateException(e);
}
}
return viprKeyStore;
}
protected CertificateVersionHelper certificateVersionHelper;
public void setCertificateVersionHelper(
CertificateVersionHelper certificateVersionHelper) {
this.certificateVersionHelper = certificateVersionHelper;
}
@Override
protected VirtualDataCenter queryResource(URI id) {
ArgValidator.checkUri(id);
VirtualDataCenter vdc = _dbClient.queryObject(VirtualDataCenter.class, id);
ArgValidator.checkEntityNotNull(vdc, id, isIdEmbeddedInURL(id));
return vdc;
}
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN }, blockProxies = true)
public TaskResourceRep addVirtualDataCenter(VirtualDataCenterAddParam param) {
blockRoot();
ArgValidator.checkFieldNotEmpty(param.getApiEndpoint(), "api_endpoint");
ArgValidator.checkFieldNotEmpty(param.getSecretKey(), "secret_key");
ArgValidator.checkFieldNotEmpty(param.getName(), "name");
checkForDuplicateName(param.getName(), VirtualDataCenter.class);
if (service.getId().endsWith("standalone")) {
throw new IllegalStateException("standalone VDCs cannot be connected into a geo system");
}
List<Site> drSitesInCurrentVdc = drUtil.listSites();
if (drSitesInCurrentVdc.size() > 1) {
throw APIException.badRequests.notAllowedToAddVdcInDRConfig();
}
ArgValidator.checkFieldNotEmpty(param.getCertificateChain(), "certificate_chain");
verifyVdcCert(param.getCertificateChain(), param.getApiEndpoint(), true);
// TODO: We need a way to reject this if another "add" is already
// in progress so that we only have one new VDC being synced at a time
VirtualDataCenter localVDC = VdcUtil.getLocalVdc();
Properties vdcInfo = prepareVdcOpParam(localVDC.getId(), param);
auditOp(OperationTypeEnum.ADD_VDC, true, null, param.getApiEndpoint(), param.getApiEndpoint());
return enqueueJob(localVDC, JobType.VDC_CONNECT_JOB, Arrays.asList(new Object[] { vdcInfo }));
}
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public VirtualDataCenterList getVirtualDataCenters() {
VirtualDataCenterList vdcList = new VirtualDataCenterList();
List<URI> ids = _dbClient.queryByType(VirtualDataCenter.class, true);
Iterator<VirtualDataCenter> iter = _dbClient.queryIterativeObjects(VirtualDataCenter.class, ids);
while (iter.hasNext()) {
vdcList.getVirtualDataCenters().add(toNamedRelatedResource(iter.next()));
}
return vdcList;
}
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
public VirtualDataCenterRestRep getVirtualDataCenter(@PathParam("id") URI id) {
ArgValidator.checkFieldUriType(id, VirtualDataCenter.class, "id");
VirtualDataCenter vdc = queryResource(id);
VirtualDataCenterRestRep restRep = map(vdc);
return restRep;
}
@PUT
@Path("/{id}")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN, Role.RESTRICTED_SECURITY_ADMIN }, blockProxies = true)
public TaskResourceRep updateVirtualDataCenter(@PathParam("id") URI id, VirtualDataCenterModifyParam param) {
ArgValidator.checkFieldUriType(id, VirtualDataCenter.class, "id");
String localVdcId = VdcUtil.getLocalVdc().getId().toString();
if (!id.toString().equalsIgnoreCase(localVdcId)) {
blockRoot();
}
VirtualDataCenter vdc = queryResource(id);
if (StringUtils.isNotEmpty(param.getName()) && !param.getName().equals(vdc.getLabel())) {
checkForDuplicateName(param.getName(), VirtualDataCenter.class);
}
/*
* If vdc is in failed state:
* CONNECT_FAILED
* REMOVE_FAILED
*
* Stop this request. (CTRL-3883)
*/
// CTRL-3883 update should fail if previous op failed
ConnectionStatus status = vdc.getConnectionStatus();
_log.info("Updating VDC {}, connection status {}", vdc.getShortId(), status);
if (status.equals(VirtualDataCenter.ConnectionStatus.CONNECT_FAILED) ||
status.equals(VirtualDataCenter.ConnectionStatus.REMOVE_FAILED)) {
_log.error("Cannot update VDC {} if it's in a failed state. Mannual VDC recovery required", vdc.getShortId());
throw APIException.methodNotAllowed.notSupported();
}
List<Object> params = new ArrayList<>();
params.add(modifyVirtualDataCenterInfo(VdcUtil.getLocalVdc(), vdc, param, null));
auditOp(OperationTypeEnum.UPDATE_VDC, true, null, id.toString());
return enqueueJob(vdc, JobType.VDC_UPDATE_JOB, params);
}
@DELETE
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}")
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN }, blockProxies = true)
public TaskResourceRep removeVirtualDataCenter(@PathParam("id") URI id) {
blockRoot();
ArgValidator.checkFieldUriType(id, VirtualDataCenter.class, "id");
VirtualDataCenter vdc = queryResource(id);
ConnectionStatus status = vdc.getConnectionStatus();
if (BooleanUtils.isTrue(vdc.getLocal())) {
throw APIException.badRequests.cantChangeConnectionStatusOfLocalVDC();
}
if (status.equals(ConnectionStatus.CONNECT_FAILED)) {
_log.error("Cannot delete {}. Mannual VDC recovery required", vdc.getShortId());
throw APIException.methodNotAllowed.notSupported();
}
// TODO Need to check that VDC to be removed is not "local VDC".
// We can not remove local VDC.
// TODO: Are there more pre-checks we want to do synchronously?
auditOp(OperationTypeEnum.REMOVE_VDC, true, null, vdc.getLabel(), vdc.getApiEndpoint());
return enqueueJob(vdc, JobType.VDC_REMOVE_JOB);
}
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/disconnect")
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN }, blockProxies = true)
public TaskResourceRep disconnectVirtualDataCenter(@PathParam("id") URI id) {
blockRoot();
ArgValidator.checkFieldUriType(id, VirtualDataCenter.class, "id");
VirtualDataCenter vdc = queryResource(id);
if (BooleanUtils.isTrue(vdc.getLocal())) {
throw APIException.badRequests.cantChangeConnectionStatusOfLocalVDC();
}
// TODO: Are there more pre-checks we want to do synchronously?
auditOp(OperationTypeEnum.DISCONNECT_VDC, true, null, id.toString());
return enqueueJob(vdc, JobType.VDC_DISCONNECT_JOB);
}
@POST
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Path("/{id}/reconnect")
@CheckPermission(roles = { Role.SYSTEM_ADMIN, Role.SECURITY_ADMIN }, blockProxies = true)
public TaskResourceRep reconnectVirtualDataCenter(@PathParam("id") URI id) {
blockRoot();
ArgValidator.checkFieldUriType(id, VirtualDataCenter.class, "id");
VirtualDataCenter vdc = queryResource(id);
if (BooleanUtils.isTrue(vdc.getLocal())) {
throw APIException.badRequests.cantChangeConnectionStatusOfLocalVDC();
}
// TODO: Are there more pre-checks we want to do synchronously?
auditOp(OperationTypeEnum.RECONNECT_VDC, true, null, id.toString());
return enqueueJob(vdc, JobType.VDC_RECONNECT_JOB);
}
/**
* Get vdc role assignments
*
* @return Role assignment details
* @brief List vdc role assignments
*/
@Path("/role-assignments")
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.RESTRICTED_SECURITY_ADMIN })
public RoleAssignments getRoleAssignments() {
VirtualDataCenter localVdc = VdcUtil.getLocalVdc();
return getRoleAssignmentsResponse(localVdc);
}
/**
* Retrieves the vdc's secret key. If it doesn't exist,
* it gets generated
*
* @return VDC secret key
* @brief Retrieves the vdc's secret key
*/
@Path("/secret-key")
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.RESTRICTED_SECURITY_ADMIN })
public VirtualDataCenterSecretKeyRestRep getVDCSecretKey() {
SecretKey key = apiSignatureGenerator.getSignatureKey(SignatureKeyType.INTERVDC_API);
VirtualDataCenterSecretKeyRestRep resp = new VirtualDataCenterSecretKeyRestRep();
resp.setSecretKey(new String(Base64.encodeBase64(key.getEncoded()), Charset.forName("UTF-8")));
return resp;
}
/**
* Add or remove individual role assignments. Request body must include at least one add or remove operation.
*
* @param changes Role assignment changes
* @return No data returned in response body
* @brief Add or remove role assignments
*/
@Path("/role-assignments")
@PUT
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.RESTRICTED_SECURITY_ADMIN }, blockProxies = true)
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public RoleAssignments updateRoleAssignments(RoleAssignmentChanges changes) {
VirtualDataCenter localVdc = VdcUtil.getLocalVdc();
TenantOrg rootTenant = _permissionsHelper.getRootTenant();
_permissionsHelper.updateRoleAssignments(localVdc, changes,
new ZoneRoleInputFilter(rootTenant));
validateVdcRoleAssignmentChange(localVdc);
_dbClient.updateAndReindexObject(localVdc);
_auditMgr.recordAuditLog(localVdc.getId(),
URI.create(getUserFromContext().getName()),
EVENT_SERVICE_TYPE, OperationTypeEnum.MODIFY_ZONE_ROLES,
System.currentTimeMillis(), AuditLogManager.AUDITLOG_SUCCESS,
null, localVdc.getId().toString(), localVdc.getLabel(), changes);
return getRoleAssignmentsResponse(localVdc);
}
/**
* restrict SecurityAdmin from dropping his own SECURITY_ADMIN role.
*
* @param vdc vdc to be persisted with the new role change
*/
private void validateVdcRoleAssignmentChange(VirtualDataCenter vdc) {
StorageOSUser user = (StorageOSUser) sc.getUserPrincipal();
// return if user is a local user
if (_localUsers.keySet().contains(user.getName())) {
return;
}
if (!user.getRoles().contains(Role.SECURITY_ADMIN.name())) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
// populate vdc roles to the cloned user after vdc role-assignment change.
// then do the check
StorageOSUser tempUser = user.clone();
tempUser.setRoles(new StringSet());
_permissionsHelper.populateZoneRoles(tempUser, vdc);
_log.info(tempUser.toString());
if (!tempUser.getRoles().contains(Role.SECURITY_ADMIN.name())) {
throw APIException.forbidden.securityAdminCantDropHisOwnSecurityAdminRole(user.getName());
}
}
/**
* prepare the vdc to fulfill the requirement of being able to add other vdc in this one.
* tasks are:
* 1. remove root's roles from all tenants
* 2. remove root's ownership from all projects
*
* @return http response
* @brief prepare vdc by removing root's tenant roles and project ownerships
*/
@Path("/prepare-vdc")
@POST
@CheckPermission(roles = { Role.SECURITY_ADMIN }, blockProxies = true)
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response prepareLocalVdc() {
// remove root's role from all tenants
try {
_permissionsHelper.removeRootRoleAssignmentOnTenantAndProject();
} catch (DatabaseException dbe) {
throw InternalServerErrorException.internalServerErrors.
genericApisvcError("Fail to remove root's roles and project ownerships.", dbe);
}
return Response.ok().build();
}
/**
* Get the certificate chain being used by ViPR
*
* @brief Get the certificate chain being used by ViPR
* @prereq none
*/
@Path("/keystore")
@GET
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public CertificateChain getCertificateChain() {
CertificateChain chain = new CertificateChain();
try {
Certificate[] certChain = null;
certChain =
getKeyStore().getCertificateChain(
KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS);
chain.setChain(KeyCertificatePairGenerator
.getCertificateChainAsString(certChain));
return chain;
} catch (KeyStoreException e) {
_log.error(e.getMessage(), e);
throw new IllegalStateException(e);
} catch (CertificateEncodingException e) {
throw SecurityException.fatals.couldNotParseCertificateToString(e);
}
}
/**
* Rotate the VIPR key and certificate chain.
*
* @param rotateKeyAndCertParam
* @return the new certificate chain being used by ViPR
* @brief Rotate the VIPR key and certificate chain to a new system self-signed or a specified input.
*/
@Path("/keystore")
@PUT
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN, Role.RESTRICTED_SECURITY_ADMIN }, blockProxies = true)
public CertificateChain setKeyCertificatePair(RotateKeyAndCertParam rotateKeyAndCertParam) {
if (!coordinator.isClusterUpgradable()) {
throw SecurityException.retryables.updatingKeystoreWhileClusterIsUnstable();
}
if (!drUtil.isActiveSite()) {
SiteState state = drUtil.getLocalSite().getState();
if (state == SiteState.STANDBY_PAUSING ||
state == SiteState.STANDBY_PAUSED || state == SiteState.STANDBY_RESUMING) {
throw SecurityException.retryables.failToUpdateKeyStoreDueToStandbyPause();
}
}
Boolean selfsigned = rotateKeyAndCertParam.getSystemSelfSigned();
byte[] key = null;
Certificate[] chain = null;
RSAPrivateKey rsaPrivateKey = null;
try {
if (selfsigned != null && selfsigned) {
KeyCertificateEntry pair = getGenerator().generateKeyCertificatePair();
// key is needed to clear
key = pair.getKey();
chain = pair.getCertificateChain();
} else {
KeyAndCertificateChain newKey = rotateKeyAndCertParam.getKeyCertChain();
if (newKey == null || StringUtils.isBlank(newKey.getCertificateChain())
|| StringUtils.isBlank(newKey.getPrivateKey())) {
throw APIException.badRequests.requiredParameterMissingOrEmpty("key_and_certificate");
}
try {
chain =
KeyCertificatePairGenerator.getCertificateChainFromString(newKey
.getCertificateChain());
if (ArrayUtils.isEmpty(chain)) {
throw APIException.badRequests.failedToLoadCertificateFromString(newKey
.getCertificateChain());
}
X509Certificate cert = (X509Certificate) chain[0];
cert.checkValidity();
key = SecurityUtil.loadPrivateKeyFromPEMString(newKey.getPrivateKey());
rsaPrivateKey = (RSAPrivateKey) KeyCertificatePairGenerator.loadPrivateKeyFromBytes(key);
int keyLength = rsaPrivateKey.getModulus().bitLength();
if (keyLength < KeyCertificateAlgorithmValuesHolder.FIPS_MINIMAL_KEY_SIZE) {
throw APIException.badRequests
.invalidParameterBelowMinimum(
"private_key",
keyLength,
KeyCertificateAlgorithmValuesHolder.FIPS_MINIMAL_KEY_SIZE,
"bits");
}
KeyCertificatePairGenerator.validateKeyAndCertPairing(rsaPrivateKey, chain);
Certificate prevCert = null;
try {
prevCert =
getKeyStore().getCertificate(
KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS);
if (cert.equals(prevCert)) {
throw APIException.badRequests.newCertificateMustBeSpecified();
}
} catch (KeyStoreException e) {
_log.error("failed to get previous certificate", e);
}
selfsigned = Boolean.FALSE;
} catch (CertificateExpiredException | CertificateNotYetValidException e) {
throw APIException.badRequests.invalidField("key_and_certificate",
chain[0].toString());
} catch (CertificateException e) {
throw APIException.badRequests.failedToLoadCertificateFromString(
newKey.getCertificateChain(), e);
}
}
Boolean selfSignedPrevious =
KeyStoreUtil.isSelfGeneratedCertificate(coordConfigStoringHelper);
// This has to be done before the set keys entry call
KeyStoreUtil.setSelfGeneratedCertificate(coordConfigStoringHelper, selfsigned);
try {
getKeyStore().setKeyEntry(KeystoreEngine.ViPR_KEY_AND_CERTIFICATE_ALIAS, key, chain);
} catch (KeyStoreException e) {
_log.error("failed to rotate key and certificate chain.");
KeyStoreUtil.setSelfGeneratedCertificate(coordConfigStoringHelper,
selfSignedPrevious);
throw SecurityException.fatals.failedToUpdateKeyCertificateEntry(e);
}
if (!certificateVersionHelper.updateCertificateVersion()) {
_log.error("failed to update version for new key and certificate chain.");
throw SecurityException.fatals.failedToUpdateKeyCertificateEntry();
}
return getCertificateChain();
} finally {
if (key != null) {
// SensitiveData.clear(key);
SecurityUtil.clearSensitiveData(key);
}
if (rsaPrivateKey != null) {
// SensitiveData.clear(rsaPrivateKey);
SecurityUtil.clearSensitiveData(rsaPrivateKey);
}
}
}
/***
* Checks if the all the VDCs in the federation are in the
* same expected version or not.
*
* @usage \vdc\admin\check-compatibility?expect_verion=2.3
* @param expectVersion
* @return true if all the VDCs in the federation are in
* the expect_version otherwise false.
*/
@GET
@Path("check-compatibility")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public Response checkGeoVersionCompatibility(@QueryParam("expect_version") String expectVersion) {
if (!isValidateVersion(expectVersion)) {
_log.warn("invalid Geo version {} : only support major and minor ", expectVersion);
throw APIException.badRequests.invalidParameter("invalid Geo version {} : only support major and minor ", expectVersion);
}
Boolean versionSupported = this._dbClient.checkGeoCompatible(expectVersion);
return Response.ok(versionSupported.toString(), MediaType.APPLICATION_OCTET_STREAM).build();
}
/**
* Check if the setup is geo-distributed multi-VDC
*
* @usage \vdc\admin\check-geo-distributed
* @return true if the setup is geo-distributed VDC
*/
@GET
@Path("check-geo-distributed")
public Response checkGeoSetup() {
Boolean isGeo = false;
List<URI> ids = _dbClient.queryByType(VirtualDataCenter.class, true);
Iterator<VirtualDataCenter> iter = _dbClient.queryIterativeObjects(VirtualDataCenter.class, ids);
while (iter.hasNext()) {
VirtualDataCenter vdc = iter.next();
if (!vdc.getLocal()) {
if ((vdc.getConnectionStatus() == VirtualDataCenter.ConnectionStatus.ISOLATED)
|| vdc.getRepStatus() == VirtualDataCenter.GeoReplicationStatus.REP_NONE) {
continue;
}
isGeo = true;
}
}
return Response.ok(isGeo.toString(), MediaType.APPLICATION_OCTET_STREAM).build();
}
private static boolean isValidateVersion(String expectVersion) {
if (StringUtils.isBlank(expectVersion)) {
return false;
}
if (expectVersion.split(VERSION_PART_SEPERATOR).length != 2) {
return false;
}
return true;
}
private Properties prepareVdcOpParam(URI localVdcId,
VirtualDataCenterAddParam param) throws DatabaseException {
Properties taskProperties = new Properties();
taskProperties.setProperty(GeoServiceJob.LOCAL_VDC_ID, localVdcId.toString());
taskProperties.setProperty(GeoServiceJob.VDC_NAME, param.getName());
taskProperties.setProperty(GeoServiceJob.VDC_API_ENDPOINT, param.getApiEndpoint());
taskProperties.setProperty(GeoServiceJob.VDC_SECRETE_KEY, param.getSecretKey());
String description = param.getDescription();
if (description != null) {
taskProperties.setProperty(GeoServiceJob.VDC_DESCRIPTION, param.getDescription());
}
String geoCommandEndpoint = param.getGeoCommandEndpoint();
if (geoCommandEndpoint != null) {
taskProperties.setProperty(GeoServiceJob.VDC_GEOCOMMAND_ENDPOINT, param.getGeoCommandEndpoint());
}
String geoDataEndpoint = param.getGeoDataEndpoint();
if (geoDataEndpoint != null) {
taskProperties.setProperty(GeoServiceJob.VDC_GEODATA_ENDPOINT, param.getGeoDataEndpoint());
}
taskProperties.setProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN, param.getCertificateChain());
String vdcShortId = _geoHelper.createMonoVdcId();
taskProperties.setProperty(GeoServiceJob.VDC_SHORT_ID, vdcShortId);
taskProperties.setProperty(GeoServiceJob.OPERATED_VDC_ID, URIUtil.createVirtualDataCenterId(vdcShortId).toString());
return taskProperties;
}
private Properties modifyVirtualDataCenterInfo(VirtualDataCenter localVdc, VirtualDataCenter vdc,
VirtualDataCenterModifyParam param, Certificate[] certchain) throws DatabaseException {
Properties taskProperties = new Properties();
taskProperties.setProperty(GeoServiceJob.LOCAL_VDC_ID, localVdc.getId().toString());
taskProperties.setProperty(GeoServiceJob.OPERATED_VDC_ID, vdc.getId().toString());
if (StringUtils.isNotEmpty(param.getName())) {
taskProperties.setProperty(GeoServiceJob.VDC_NAME, param.getName());
/*
* TODO: it is part of change vip flow
* if (StringUtils.isNotEmpty(param.getApiEndpoint()))
* taskProperties.setProperty(GeoServiceJob.VDC_API_ENDPOINT,param.getApiEndpoint);
* vdc.setLabel(param.getApiEndpoint());
*/
/*
* TODO: wait for security to support key rotation
* if (StringUtils.isNotEmpty(param.getSecretKey()))
* taskProperties.setProperty(GeoServiceJob.VDC_SECRETE_KEY,param.getSecretKey());
*/
}
if (StringUtils.isNotEmpty(param.getDescription())) {
taskProperties.setProperty(GeoServiceJob.VDC_DESCRIPTION, param.getDescription());
}
if (StringUtils.isNotEmpty(param.getGeoCommandEndpoint())) {
taskProperties.setProperty(GeoServiceJob.VDC_GEOCOMMAND_ENDPOINT, param.getGeoCommandEndpoint());
}
if (StringUtils.isNotEmpty(param.getGeoDataEndpoint())) {
taskProperties.setProperty(GeoServiceJob.VDC_GEODATA_ENDPOINT, param.getGeoDataEndpoint());
}
if (certchain != null) {
try {
String certChain = KeyCertificatePairGenerator.getCertificateChainAsString(certchain);
taskProperties.setProperty(GeoServiceJob.VDC_CERTIFICATE_CHAIN, certChain);
} catch (CertificateEncodingException e) {
throw APIException.badRequests.failedToLoadKeyFromString(e);
}
}
return taskProperties;
}
/**
* Add a job to the async queue and return a rest representation for the task
*
* @param vdc the vdc to operate on
* @param jobType the operation to perform
* @return the task representation
*/
private TaskResourceRep enqueueJob(VirtualDataCenter vdc, JobType jobType) {
return enqueueJob(vdc, jobType, null);
}
private TaskResourceRep enqueueJob(VirtualDataCenter vdc, JobType jobType, List<Object> params) {
String taskId = UUID.randomUUID().toString();
Operation op = _dbClient.createTaskOpStatus(VirtualDataCenter.class, vdc.getId(),
taskId, jobType.toResourceOperationType());
// add to the job queue
try {
GeoServiceJob job = new GeoServiceJob(vdc, taskId, jobType, params);
_geoHelper.enqueueJob(job);
} catch (Exception ex) {
_log.error("Exception occurred while enqueue job on due to:", ex);
ServiceCoded coded = ServiceError.buildServiceError(
ServiceCode.COORDINATOR_UNABLE_TO_QUEUE_JOB, ex.getMessage());
op.error(coded);
}
return TaskMapper.toTask(vdc, taskId, op);
}
private class ZoneRoleInputFilter extends PermissionsHelper.RoleInputFilter {
public ZoneRoleInputFilter(TenantOrg tenant) {
super(tenant);
}
@Override
public boolean isValidRole(String ace) {
return _permissionsHelper.isExternalRoleZoneLevel(ace);
}
@Override
public StringSetMap convertFromRolesRemove(List<RoleAssignmentEntry> remove) {
return convertFromRolesNoLocalUsers(remove);
}
@Override
protected void validatePrincipals() {
StringBuilder error = new StringBuilder();
PrincipalsToValidate principalsToValidate = new PrincipalsToValidate();
principalsToValidate.setTenantId(_tenant.getId().toString());
principalsToValidate.setGroups(_groups);
principalsToValidate.setUsers(_users);
if (!Validator.validatePrincipals(principalsToValidate, error)) {
throw APIException.badRequests.invalidRoleAssignments(error.toString());
}
}
@Override
protected void addPrincipalToList(PermissionsKey key,
RoleAssignmentEntry roleAssignment) {
switch (key.getType()) {
case GROUP:
_groups.add(key.getValue());
break;
case SID:
_users.add(key.getValue());
break;
case TENANT:
default:
break;
}
}
@Override
public StringSetMap convertFromRolesAdd(List<RoleAssignmentEntry> add,
boolean validate) {
StringSetMap returnedStringSetMap = convertFromRolesNoLocalUsers(add);
if (validate) {
validatePrincipals();
}
return returnedStringSetMap;
}
}
private RoleAssignments getRoleAssignmentsResponse(VirtualDataCenter vdc) {
RoleAssignments roleAssignmentsResponse = new RoleAssignments();
roleAssignmentsResponse.setAssignments(
_permissionsHelper.convertToRoleAssignments(vdc.getRoleAssignments(), true));
roleAssignmentsResponse.setSelfLink(getCurrentSelfLink());
return roleAssignmentsResponse;
}
private KeyCertificatePairGenerator getGenerator() {
if (generator == null) {
generator = new KeyCertificatePairGenerator();
generator.setKeyCertificateAlgorithmValuesHolder(new KeyCertificateAlgorithmValuesHolder(coordinator));
}
return generator;
}
private List<Object> prepareKeyCert(KeyAndCertificateChain newKey) {
Boolean selfsigned = null;
byte[] key = null;
Certificate[] chain = null;
if (newKey != null) {
try {
chain =
KeyCertificatePairGenerator.getCertificateChainFromString(newKey
.getCertificateChain());
if (ArrayUtils.isEmpty(chain)) {
throw APIException.badRequests.failedToLoadCertificateFromString(newKey
.getCertificateChain());
}
key = SecurityUtil.loadPrivateKeyFromPEMString(newKey.getPrivateKey());
selfsigned = Boolean.FALSE;
} catch (CertificateException e) {
throw APIException.badRequests.failedToLoadCertificateFromString(
newKey.getCertificateChain(), e);
} catch (Exception e) {
throw APIException.badRequests.failedToLoadKeyFromString(e);
}
} else {
KeyCertificateEntry pair = getGenerator().generateKeyCertificatePair();
selfsigned = Boolean.TRUE;
key = pair.getKey();
chain = pair.getCertificateChain();
}
List<Object> list = new ArrayList<Object>();
list.add(selfsigned);
list.add(key);
list.add(chain);
return list;
}
public static Certificate verifyVdcCert(String certstr, String apiEndpoint, Boolean certchain) {
// verify vdc cert
_log.info("Verifying certificate ...");
Certificate cert = null;
try {
if (certchain) {
Certificate[] chain = null;
chain = KeyCertificatePairGenerator.getCertificateChainFromString(certstr);
if (!ArrayUtils.isEmpty(chain)) {
cert = chain[0];
}
} else {
cert = KeyCertificatePairGenerator.getCertificateFromString(certstr);
}
if (cert == null) {
throw APIException.badRequests.failedToLoadCertificateFromString(certstr);
}
validateEndpoint((X509Certificate) cert, apiEndpoint);
} catch (CertificateException e) {
_log.error(e.getMessage(), e);
}
return cert;
}
private void blockRoot() {
Principal principal = sc.getUserPrincipal();
if (!(principal instanceof StorageOSUser)) {
throw APIException.forbidden.invalidSecurityContext();
}
StorageOSUser user = (StorageOSUser) principal;
if (user.getName().equalsIgnoreCase(ROOT)) {
throw APIException.forbidden.insufficientPermissionsForUser(ROOT);
}
}
/**
* check if given endpoint is valid by comparing ips in endpoint against subject names in certificate
*
* @param x509Certificate
* @param endpoint
*/
private static void validateEndpoint(X509Certificate x509Certificate, String endpoint) {
Set<String> ipsOfEndpoint = new HashSet<String>();
Set<String> subjectIpsInCert = new HashSet<String>();
// retrieves IPs from endpoint, endpoint could be provided as hostname or IP
try {
for (InetAddress addr : InetAddress.getAllByName(endpoint)) {
ipsOfEndpoint.add(addr.getHostAddress());
}
} catch (UnknownHostException uhe) {
_log.error(uhe.getMessage());
}
// retrieves IPs from certificate's subject alternative names
Collection altNames = null;
try {
altNames = x509Certificate.getSubjectAlternativeNames();
} catch (CertificateParsingException cpe) {
_log.error(cpe.getMessage());
}
if (altNames != null) {
Iterator itAltNames = altNames.iterator();
while (itAltNames.hasNext()) {
List extensionEntry = (List) itAltNames.next();
Integer nameType = (Integer) extensionEntry.get(0);
if (nameType == SUBALTNAME_DNSNAME || nameType == SUBALTNAME_IPADDRESS) {
String name = (String) extensionEntry.get(1);
try {
for (InetAddress addr : InetAddress.getAllByName(name)) {
subjectIpsInCert.add(addr.getHostAddress());
}
} catch (UnknownHostException uhe) {
_log.error(uhe.getMessage());
}
}
}
}
// check at least one IP in both subjectIpsInCert and ipsOfEndpoint
if (ipsOfEndpoint.isEmpty() || subjectIpsInCert.isEmpty()) {
throw APIException.badRequests.apiEndpointNotMatchCertificate(endpoint);
}
boolean bFound = false;
for (String ip : ipsOfEndpoint) {
if (subjectIpsInCert.contains(ip)) {
bFound = true;
break;
}
}
if (!bFound) {
throw APIException.badRequests.apiEndpointNotMatchCertificate(endpoint);
}
}
}