Direct download certificates additions and improvements (#6104)

* Add direct download certificates listing

* Restore class to original project

* Small refactor

* Register API

* Apply suggestions from code review

Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>

* Refactor after review

* Fix checkstyle

* Add hosts mapping to API response

* Improvements on revoke certificate

* Refactor revoke certificate API

* Fix condition

* Filter only certificates not revoked for revokeCertificate API

* Improve upload certificate and add provision certificate API

* Improve certificate response output

* Address review comments

* Refactor revoke cert test

* Fix marvin test

* Address review comments

* Fix issues

* Improvements

* Refactor upload template API response

* Fix response

Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
This commit is contained in:
Nicolas Vazquez 2022-04-11 22:57:23 -03:00 committed by GitHub
parent 177f04839c
commit 5435b0abfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 959 additions and 115 deletions

View File

@ -26,6 +26,7 @@ public class ApiConstants {
public static final String ADAPTER_TYPE = "adaptertype";
public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm";
public static final String ALIAS = "alias";
public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey";
@ -58,6 +59,10 @@ public class ApiConstants {
public static final String CERTIFICATE_CHAIN = "certchain";
public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
public static final String CERTIFICATE_ID = "certid";
public static final String CERTIFICATE_ISSUER = "issuer";
public static final String CERTIFICATE_SERIALNUM = "serialnum";
public static final String CERTIFICATE_SUBJECT = "subject";
public static final String CERTIFICATE_VALIDITY = "validity";
public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck";
public static final String CONTROLLER = "controller";
public static final String CONTROLLER_UNIT = "controllerunit";
@ -188,6 +193,7 @@ public class ApiConstants {
public static final String HOST_ID = "hostid";
public static final String HOST_IDS = "hostids";
public static final String HOST_NAME = "hostname";
public static final String HOSTS_MAP = "hostsmap";
public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline";
public static final String INSTANCE = "instance";
@ -237,6 +243,7 @@ public class ApiConstants {
public static final String LEVEL = "level";
public static final String LENGTH = "length";
public static final String LIMIT_CPU_USE = "limitcpuuse";
public static final String LIST_HOSTS = "listhosts";
public static final String LOCK = "lock";
public static final String LUN = "lun";
public static final String LBID = "lbruleid";
@ -322,6 +329,7 @@ public class ApiConstants {
public static final String RESOURCE_TYPE_NAME = "resourcetypename";
public static final String RESPONSE = "response";
public static final String REVERTABLE = "revertable";
public static final String REVOKED = "revoked";
public static final String REGISTERED = "registered";
public static final String QUALIFIERS = "qualifiers";
public static final String QUERY_FILTER = "queryfilter";

View File

@ -23,10 +23,16 @@ import java.util.Map;
import java.util.Set;
import com.cloud.server.ResourceIcon;
import com.cloud.utils.Pair;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import com.cloud.resource.RollingMaintenanceManager;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.management.ManagementServerHost;
import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
@ -491,4 +497,11 @@ public interface ResponseGenerator {
ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon);
DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate);
List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(List<DirectDownloadCertificateHostMap> hostMappings);
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status);
DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result);
}

View File

@ -0,0 +1,110 @@
// 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.cloudstack.api.command.admin.direct.download;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.BaseListCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = ListTemplateDirectDownloadCertificatesCmd.APINAME,
description = "List the uploaded certificates for direct download templates",
responseObject = DirectDownloadCertificateResponse.class,
since = "4.17.0",
authorized = {RoleType.Admin})
public class ListTemplateDirectDownloadCertificatesCmd extends BaseListCmd {
@Inject
DirectDownloadManager directDownloadManager;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DirectDownloadCertificateResponse.class,
description = "list direct download certificate by ID")
private Long id;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
description = "the zone where certificates are uploaded")
private Long zoneId;
@Parameter(name = ApiConstants.LIST_HOSTS, type = CommandType.BOOLEAN,
description = "if set to true: include the hosts where the certificate is uploaded to")
private Boolean listHosts;
private static final Logger LOG = Logger.getLogger(ListTemplateDirectDownloadCertificatesCmd.class);
public static final String APINAME = "listTemplateDirectDownloadCertificates";
public boolean isListHosts() {
return listHosts != null && listHosts;
}
private void createResponse(final List<DirectDownloadCertificate> certificates) {
final ListResponse<DirectDownloadCertificateResponse> response = new ListResponse<>();
final List<DirectDownloadCertificateResponse> responses = new ArrayList<>();
for (final DirectDownloadCertificate certificate : certificates) {
if (certificate == null) {
continue;
}
DirectDownloadCertificateResponse certificateResponse = _responseGenerator.createDirectDownloadCertificateResponse(certificate);
if (isListHosts()) {
List<DirectDownloadCertificateHostMap> hostMappings = directDownloadManager.getCertificateHostsMapping(certificate.getId());
List<DirectDownloadCertificateHostStatusResponse> hostMapResponses = _responseGenerator.createDirectDownloadCertificateHostMapResponse(hostMappings);
certificateResponse.setHostsMap(hostMapResponses);
}
responses.add(certificateResponse);
}
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException,
ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
List<DirectDownloadCertificate> certificates = directDownloadManager.listDirectDownloadCertificates(id, zoneId);
createResponse(certificates);
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -0,0 +1,78 @@
//
// 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.cloudstack.api.command.admin.direct.download;
import com.cloud.exception.ConcurrentOperationException;
import com.cloud.exception.InsufficientCapacityException;
import com.cloud.exception.NetworkRuleConflictException;
import com.cloud.exception.ResourceAllocationException;
import com.cloud.exception.ResourceUnavailableException;
import com.cloud.utils.Pair;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import javax.inject.Inject;
@APICommand(name = ProvisionTemplateDirectDownloadCertificateCmd.APINAME,
description = "Provisions a host with a direct download certificate",
responseObject = DirectDownloadCertificateHostStatusResponse.class,
since = "4.17.0",
authorized = {RoleType.Admin})
public class ProvisionTemplateDirectDownloadCertificateCmd extends BaseCmd {
public static final String APINAME = "provisionTemplateDirectDownloadCertificate";
@Inject
DirectDownloadManager directDownloadManager;
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DirectDownloadCertificateResponse.class,
description = "the id of the direct download certificate to provision", required = true)
private Long id;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
description = "the host to provision the certificate", required = true)
private Long hostId;
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
Pair<Boolean, String> result = directDownloadManager.provisionCertificate(id, hostId);
DirectDownloadCertificateHostStatusResponse response = _responseGenerator.createDirectDownloadCertificateProvisionResponse(id, hostId, result);
response.setResponseName(getCommandName());
setResponseObject(response);
}
@Override
public String getCommandName() {
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
}
@Override
public long getEntityOwnerId() {
return CallContext.current().getCallingAccount().getId();
}
}

View File

@ -30,20 +30,26 @@ import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME,
description = "Revoke a certificate alias from a KVM host",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = true,
responseHasSensitiveInfo = true,
description = "Revoke a direct download certificate from hosts in a zone",
responseObject = DirectDownloadCertificateHostStatusResponse.class,
since = "4.13",
authorized = {RoleType.Admin})
public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
@ -54,35 +60,63 @@ public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
private static final Logger LOG = Logger.getLogger(RevokeTemplateDirectDownloadCertificateCmd.class);
public static final String APINAME = "revokeTemplateDirectDownloadCertificate";
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true,
description = "alias of the SSL certificate")
@Parameter(name = ApiConstants.ID, type = CommandType.UUID,
entityType = DirectDownloadCertificateResponse.class,
description = "id of the certificate")
private Long certificateId;
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING,
description = "(optional) alias of the SSL certificate")
private String certificateAlias;
@Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true,
description = "hypervisor type")
@Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING,
description = "(optional) hypervisor type")
private String hypervisor;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class,
description = "zone to revoke certificate", required = true)
description = "(optional) zone to revoke certificate", required = true)
private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
description = "(optional) the host ID to revoke certificate")
private Long hostId;
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
if (!hypervisor.equalsIgnoreCase("kvm")) {
private void createResponse(final List<HostCertificateStatus> hostsRevokeStatusList) {
final ListResponse<DirectDownloadCertificateHostStatusResponse> response = new ListResponse<>();
final List<DirectDownloadCertificateHostStatusResponse> responses = new ArrayList<>();
for (final HostCertificateStatus status : hostsRevokeStatusList) {
if (status == null) {
continue;
}
DirectDownloadCertificateHostStatusResponse revokeResponse =
_responseGenerator.createDirectDownloadCertificateHostStatusResponse(status);
responses.add(revokeResponse);
}
response.setResponses(responses);
response.setResponseName(getCommandName());
setResponseObject(response);
}
private void validateParameters() {
if (ObjectUtils.allNull(certificateId, certificateAlias, hypervisor) ||
certificateId == null && !ObjectUtils.allNotNull(certificateAlias, hypervisor)) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please specify the hypervisor and the" +
"certificate name to revoke or the certificate ID");
}
if (StringUtils.isNotBlank(hypervisor) && !hypervisor.equalsIgnoreCase("kvm")) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only");
}
SuccessResponse response = new SuccessResponse(getCommandName());
}
@Override
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException {
validateParameters();
try {
LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts");
boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor, zoneId, hostId);
response.setSuccess(result);
setResponseObject(response);
DirectDownloadCertificate certificate = directDownloadManager.findDirectDownloadCertificateByIdOrHypervisorAndAlias(certificateId, certificateAlias, hypervisor, zoneId);
List<HostCertificateStatus> hostsResult = directDownloadManager.revokeCertificate(certificate, zoneId, hostId);
createResponse(hostsResult);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed revoking certificate: " + e.getMessage());
}
}

View File

@ -16,6 +16,8 @@
// under the License.
package org.apache.cloudstack.api.command.admin.direct.download;
import com.cloud.utils.Pair;
import com.cloud.utils.exception.CloudRuntimeException;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants;
@ -23,20 +25,23 @@ import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.HostResponse;
import org.apache.cloudstack.api.response.SuccessResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus;
import org.apache.log4j.Logger;
import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME,
description = "Upload a certificate for HTTPS direct template download on KVM hosts",
responseObject = SuccessResponse.class,
requestHasSensitiveInfo = true,
responseHasSensitiveInfo = true,
responseObject = DirectDownloadCertificateResponse.class,
since = "4.11.0",
authorized = {RoleType.Admin})
public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
@ -63,9 +68,29 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class,
description = "(optional) the host ID to revoke certificate")
description = "(optional) the host ID to upload certificate")
private Long hostId;
private void createResponse(DirectDownloadCertificate certificate, final List<HostCertificateStatus> hostStatusList) {
final List<DirectDownloadCertificateHostStatusResponse> hostMapsResponse = new ArrayList<>();
if (certificate == null) {
throw new CloudRuntimeException("Unable to upload certificate");
}
DirectDownloadCertificateResponse response = _responseGenerator.createDirectDownloadCertificateResponse(certificate);
for (final HostCertificateStatus status : hostStatusList) {
if (status == null) {
continue;
}
DirectDownloadCertificateHostStatusResponse uploadResponse =
_responseGenerator.createDirectDownloadCertificateHostStatusResponse(status);
hostMapsResponse.add(uploadResponse);
}
response.setHostsMap(hostMapsResponse);
response.setResponseName(getCommandName());
response.setObjectName("uploadtemplatedirectdownloadcertificate");
setResponseObject(response);
}
@Override
public void execute() {
if (!hypervisor.equalsIgnoreCase("kvm")) {
@ -74,10 +99,11 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
try {
LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
SuccessResponse response = new SuccessResponse(getCommandName());
response.setSuccess(result);
setResponseObject(response);
Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadStatus =
directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
DirectDownloadCertificate certificate = uploadStatus.first();
List<HostCertificateStatus> hostStatus = uploadStatus.second();
createResponse(certificate, hostStatus);
} catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage());
}

View File

@ -0,0 +1,73 @@
// 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.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
public class DirectDownloadCertificateHostStatusResponse extends BaseResponse {
@SerializedName(ApiConstants.HOST_ID)
@Param(description = "the ID of the host")
private String hostId;
@SerializedName(ApiConstants.HOST_NAME)
@Param(description = "the name of the host")
private String hostName;
@SerializedName(ApiConstants.STATUS)
@Param(description = "indicates if the certificate has been revoked from the host, failed or skipped")
private String status;
@SerializedName(ApiConstants.DETAILS)
@Param(description = "indicates the details in case of failure or host skipped")
private String details;
public String getHostId() {
return hostId;
}
public void setHostId(String hostId) {
this.hostId = hostId;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}

View File

@ -0,0 +1,162 @@
// 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.cloudstack.api.response;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.BaseResponse;
import org.apache.cloudstack.api.EntityReference;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import java.util.List;
@EntityReference(value = DirectDownloadCertificate.class)
public class DirectDownloadCertificateResponse extends BaseResponse {
@SerializedName(ApiConstants.ID)
@Param(description = "the direct download certificate id")
private String id;
@SerializedName(ApiConstants.ALIAS)
@Param(description = "the direct download certificate alias")
private String alias;
@SerializedName(ApiConstants.ZONE_ID)
@Param(description = "the zone id where the certificate is uploaded")
private String zoneId;
@SerializedName(ApiConstants.ZONE_NAME)
@Param(description = "the zone name where the certificate is uploaded")
private String zoneName;
@SerializedName(ApiConstants.VERSION)
@Param(description = "the direct download certificate version")
private String version;
@SerializedName(ApiConstants.CERTIFICATE_SUBJECT)
@Param(description = "the direct download certificate subject")
private String subject;
@SerializedName(ApiConstants.CERTIFICATE_ISSUER)
@Param(description = "the direct download certificate issuer")
private String issuer;
@SerializedName(ApiConstants.CERTIFICATE_VALIDITY)
@Param(description = "the direct download certificate issuer")
private String validity;
@SerializedName(ApiConstants.CERTIFICATE_SERIALNUM)
@Param(description = "the direct download certificate serial num")
private String serialNum;
@SerializedName(ApiConstants.HYPERVISOR)
@Param(description = "the hypervisor of the hosts where the certificate is uploaded")
private String hypervisor;
@SerializedName(ApiConstants.HOSTS_MAP)
@Param(description = "the hosts where the certificate is uploaded to", responseObject = HostResponse.class)
private List<DirectDownloadCertificateHostStatusResponse> hostsMap;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAlias() {
return alias;
}
public void setAlias(String alias) {
this.alias = alias;
}
public String getZoneId() {
return zoneId;
}
public void setZoneId(String zoneId) {
this.zoneId = zoneId;
}
public String getHypervisor() {
return hypervisor;
}
public void setHypervisor(String hypervisor) {
this.hypervisor = hypervisor;
}
public String getZoneName() {
return zoneName;
}
public void setZoneName(String zoneName) {
this.zoneName = zoneName;
}
public List<DirectDownloadCertificateHostStatusResponse> getHostsMap() {
return hostsMap;
}
public void setHostsMap(List<DirectDownloadCertificateHostStatusResponse> hosts) {
this.hostsMap = hosts;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public String getValidity() {
return validity;
}
public void setValidity(String validity) {
this.validity = validity;
}
public String getSerialNum() {
return serialNum;
}
public void setSerialNum(String serialNum) {
this.serialNum = serialNum;
}
}

View File

@ -25,5 +25,6 @@ public interface DirectDownloadCertificate extends InternalIdentity, Identity {
String getCertificate();
String getAlias();
Hypervisor.HypervisorType getHypervisorType();
Long getZoneId();
}

View File

@ -0,0 +1,26 @@
// 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.cloudstack.direct.download;
import org.apache.cloudstack.api.InternalIdentity;
public interface DirectDownloadCertificateHostMap extends InternalIdentity {
long getCertificateId();
long getHostId();
boolean isRevoked();
}

View File

@ -17,17 +17,21 @@
package org.apache.cloudstack.direct.download;
import com.cloud.host.Host;
import com.cloud.utils.Pair;
import org.apache.cloudstack.framework.agent.direct.download.DirectDownloadService;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.utils.component.PluggableService;
import java.util.List;
public interface DirectDownloadManager extends DirectDownloadService, PluggableService, Configurable {
static final int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000;
static final int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000;
static final int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000;
int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000;
int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000;
int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000;
ConfigKey<Long> DirectDownloadCertificateUploadInterval = new ConfigKey<>("Advanced", Long.class,
"direct.download.certificate.background.task.interval",
@ -37,26 +41,66 @@ public interface DirectDownloadManager extends DirectDownloadService, PluggableS
"Only certificates which have not been revoked from hosts are uploaded",
false);
static final ConfigKey<Integer> DirectDownloadConnectTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
ConfigKey<Integer> DirectDownloadConnectTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
"direct.download.connect.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT),
"Connection establishment timeout in milliseconds for direct download",
true);
static final ConfigKey<Integer> DirectDownloadSocketTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
ConfigKey<Integer> DirectDownloadSocketTimeout = new ConfigKey<Integer>("Advanced", Integer.class,
"direct.download.socket.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT),
"Socket timeout (SO_TIMEOUT) in milliseconds for direct download",
true);
static final ConfigKey<Integer> DirectDownloadConnectionRequestTimeout = new ConfigKey<Integer>("Hidden", Integer.class,
ConfigKey<Integer> DirectDownloadConnectionRequestTimeout = new ConfigKey<Integer>("Hidden", Integer.class,
"direct.download.connection.request.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT),
"Requesting a connection from connection manager timeout in milliseconds for direct download",
true);
class HostCertificateStatus {
public enum CertificateStatus {
REVOKED, FAILED, SKIPPED, UPLOADED
}
HostCertificateStatus(CertificateStatus status, Host host, String details) {
this.status = status;
this.host = host;
this.details = details;
}
private CertificateStatus status;
private Host host;
private String details;
public CertificateStatus getStatus() {
return status;
}
public Host getHost() {
return host;
}
public String getDetails() {
return details;
}
}
DirectDownloadCertificate findDirectDownloadCertificateByIdOrHypervisorAndAlias(Long id, String alias, String hypervisor, Long zoneId);
/**
* Revoke direct download certificate with alias 'alias' from hosts of hypervisor type 'hypervisor'
* Revoke direct download certificate from the hosts in the zone or a specific host
*/
boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId);
List<HostCertificateStatus> revokeCertificate(DirectDownloadCertificate certificate, Long zoneId, Long hostId);
List<DirectDownloadCertificate> listDirectDownloadCertificates(Long certificateId, Long zoneId);
List<DirectDownloadCertificateHostMap> getCertificateHostsMapping(Long certificateId);
/**
* Upload client certificate to each running host
* @return
*/
Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor, Long zoneId, Long hostId);
}

View File

@ -23,4 +23,5 @@ import java.util.List;
public interface DirectDownloadCertificateHostMapDao extends GenericDao<DirectDownloadCertificateHostMapVO, Long> {
DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId);
List<DirectDownloadCertificateHostMapVO> listByCertificateId(long certificateId);
List<DirectDownloadCertificateHostMapVO> listByCertificateIdAndRevoked(long certificateId, boolean revoked);
}

View File

@ -29,6 +29,7 @@ public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<Dire
mapSearchBuilder = createSearchBuilder();
mapSearchBuilder.and("certificate_id", mapSearchBuilder.entity().getCertificateId(), SearchCriteria.Op.EQ);
mapSearchBuilder.and("host_id", mapSearchBuilder.entity().getHostId(), SearchCriteria.Op.EQ);
mapSearchBuilder.and("revoked", mapSearchBuilder.entity().isRevoked(), SearchCriteria.Op.EQ);
mapSearchBuilder.done();
}
@Override
@ -45,4 +46,12 @@ public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<Dire
sc.setParameters("certificate_id", certificateId);
return listBy(sc);
}
@Override
public List<DirectDownloadCertificateHostMapVO> listByCertificateIdAndRevoked(long certificateId, boolean revoked) {
SearchCriteria<DirectDownloadCertificateHostMapVO> sc = mapSearchBuilder.create();
sc.setParameters("certificate_id", certificateId);
sc.setParameters("revoked", revoked);
return listBy(sc);
}
}

View File

@ -25,12 +25,12 @@ import javax.persistence.Table;
@Entity
@Table(name = "direct_download_certificate_host_map")
public class DirectDownloadCertificateHostMapVO {
public class DirectDownloadCertificateHostMapVO implements DirectDownloadCertificateHostMap {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
private long id;
@Column(name = "host_id")
private Long hostId;
@ -50,15 +50,15 @@ public class DirectDownloadCertificateHostMapVO {
this.revoked = false;
}
public Long getId() {
public long getId() {
return id;
}
public void setId(Long id) {
public void setId(long id) {
this.id = id;
}
public Long getHostId() {
public long getHostId() {
return hostId;
}
@ -66,7 +66,7 @@ public class DirectDownloadCertificateHostMapVO {
this.hostId = hostId;
}
public Long getCertificateId() {
public long getCertificateId() {
return certificateId;
}
@ -74,8 +74,8 @@ public class DirectDownloadCertificateHostMapVO {
this.certificateId = certificateId;
}
public Boolean isRevoked() {
return revoked;
public boolean isRevoked() {
return revoked != null && revoked;
}
public void setRevoked(Boolean revoked) {

View File

@ -21,6 +21,14 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-framework-direct-download</artifactId>
<name>Apache CloudStack Framework - Direct Download to Primary Storage</name>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-utils</artifactId>
<version>4.17.0.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<parent>
<artifactId>cloudstack-framework</artifactId>
<groupId>org.apache.cloudstack</groupId>

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.framework.agent.direct.download;
import com.cloud.utils.Pair;
public interface DirectDownloadService {
/**
@ -24,15 +26,10 @@ public interface DirectDownloadService {
*/
void downloadTemplate(long templateId, long poolId, long hostId);
/**
* Upload client certificate to each running host
*/
boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor, Long zoneId, Long hostId);
/**
* Upload a stored certificate on database with id 'certificateId' to host with id 'hostId'
*/
boolean uploadCertificate(long certificateId, long hostId);
Pair<Boolean, String> provisionCertificate(long certificateId, long hostId);
/**
* Sync the stored certificates to host with id 'hostId'

View File

@ -18,6 +18,8 @@ package com.cloud.api;
import static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
@ -34,6 +36,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.utils.security.CertificateHelper;
import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup;
@ -67,6 +70,7 @@ import org.apache.cloudstack.api.response.ControlledViewEntityResponse;
import org.apache.cloudstack.api.response.CounterResponse;
import org.apache.cloudstack.api.response.CreateCmdResponse;
import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse;
@ -118,6 +122,7 @@ import org.apache.cloudstack.api.response.ResourceCountResponse;
import org.apache.cloudstack.api.response.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceLimitResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostSkippedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse;
@ -160,11 +165,15 @@ import org.apache.cloudstack.backup.BackupSchedule;
import org.apache.cloudstack.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap;
import org.apache.cloudstack.direct.download.DirectDownloadManager;
import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.direct.download.DirectDownloadCertificate;
import org.apache.cloudstack.framework.jobs.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.management.ManagementServerHost;
@ -363,6 +372,7 @@ import com.cloud.vm.snapshot.VMSnapshot;
import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.commons.lang3.StringUtils;
import sun.security.x509.X509CertImpl;
public class ApiResponseHelper implements ResponseGenerator {
@ -4547,4 +4557,82 @@ public class ApiResponseHelper implements ResponseGenerator {
public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) {
return ApiDBUtils.newResourceIconResponse(resourceIcon);
}
protected void handleCertificateResponse(String certStr, DirectDownloadCertificateResponse response) {
try {
Certificate cert = CertificateHelper.buildCertificate(certStr);
if (cert instanceof X509CertImpl) {
X509CertImpl certificate = (X509CertImpl) cert;
response.setVersion(String.valueOf(certificate.getVersion()));
response.setSubject(certificate.getSubjectDN().toString());
response.setIssuer(certificate.getIssuerDN().toString());
response.setSerialNum(certificate.getSerialNumberObject().toString());
response.setValidity(String.format("From: [%s] - To: [%s]", certificate.getNotBefore(), certificate.getNotAfter()));
}
} catch (CertificateException e) {
s_logger.error("Error parsing direct download certificate: " + certStr, e);
}
}
@Override
public DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate) {
DirectDownloadCertificateResponse response = new DirectDownloadCertificateResponse();
DataCenterVO datacenter = ApiDBUtils.findZoneById(certificate.getZoneId());
if (datacenter != null) {
response.setZoneId(datacenter.getUuid());
response.setZoneName(datacenter.getName());
}
response.setId(certificate.getUuid());
response.setAlias(certificate.getAlias());
handleCertificateResponse(certificate.getCertificate(), response);
response.setHypervisor(certificate.getHypervisorType().name());
response.setObjectName("directdownloadcertificate");
return response;
}
@Override
public List<DirectDownloadCertificateHostStatusResponse> createDirectDownloadCertificateHostMapResponse(List<DirectDownloadCertificateHostMap> hostMappings) {
if (CollectionUtils.isEmpty(hostMappings)) {
return new ArrayList<>();
}
List<DirectDownloadCertificateHostStatusResponse> responses = new ArrayList<>(hostMappings.size());
for (DirectDownloadCertificateHostMap map : hostMappings) {
DirectDownloadCertificateHostStatusResponse response = new DirectDownloadCertificateHostStatusResponse();
HostVO host = ApiDBUtils.findHostById(map.getHostId());
if (host != null) {
response.setHostId(host.getUuid());
response.setHostName(host.getName());
}
response.setStatus(map.isRevoked() ? CertificateStatus.REVOKED.name() : CertificateStatus.UPLOADED.name());
response.setObjectName("directdownloadcertificatehoststatus");
responses.add(response);
}
return responses;
}
private DirectDownloadCertificateHostStatusResponse getDirectDownloadHostStatusResponseInternal(Host host, CertificateStatus status, String details) {
DirectDownloadCertificateHostStatusResponse response = new DirectDownloadCertificateHostStatusResponse();
if (host != null) {
response.setHostId(host.getUuid());
response.setHostName(host.getName());
}
response.setStatus(status.name());
response.setDetails(details);
response.setObjectName("directdownloadcertificatehoststatus");
return response;
}
@Override
public DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus hostStatus) {
Host host = hostStatus.getHost();
CertificateStatus status = hostStatus.getStatus();
return getDirectDownloadHostStatusResponseInternal(host, status, hostStatus.getDetails());
}
@Override
public DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair<Boolean, String> result) {
HostVO host = ApiDBUtils.findHostById(hostId);
CertificateStatus status = result != null && result.first() ? CertificateStatus.UPLOADED : CertificateStatus.FAILED;
return getDirectDownloadHostStatusResponseInternal(host, status, result != null ? result.second() : "provision certificate failure");
}
}

View File

@ -74,6 +74,8 @@ import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilities
import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd;
import org.apache.cloudstack.api.command.admin.direct.download.ListTemplateDirectDownloadCertificatesCmd;
import org.apache.cloudstack.api.command.admin.direct.download.ProvisionTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.RevokeTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.domain.CreateDomainCmd;
@ -3547,6 +3549,8 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
cmdList.add(DeleteManagementNetworkIpRangeCmd.class);
cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class);
cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListTemplateDirectDownloadCertificatesCmd.class);
cmdList.add(ProvisionTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListMgmtsCmd.class);
cmdList.add(GetUploadParamsForIsoCmd.class);
cmdList.add(GetRouterHealthCheckResultsCmd.class);

View File

@ -19,6 +19,7 @@
package org.apache.cloudstack.direct.download;
import static com.cloud.storage.Storage.ImageFormat;
import static org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus;
import java.net.URI;
import java.net.URISyntaxException;
@ -29,6 +30,7 @@ import java.security.cert.CertificateNotYetValidException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
@ -39,6 +41,8 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.naming.ConfigurationException;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.Pair;
import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol;
@ -66,6 +70,7 @@ import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
import org.apache.cloudstack.storage.to.TemplateObjectTO;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
@ -448,7 +453,8 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
}
@Override
public boolean uploadCertificateToHosts(String certificateCer, String alias, String hypervisor, Long zoneId, Long hostId) {
public Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadCertificateToHosts(
String certificateCer, String alias, String hypervisor, Long zoneId, Long hostId) {
if (alias != null && (alias.equalsIgnoreCase("cloud") || alias.startsWith("cloudca"))) {
throw new CloudRuntimeException("Please provide a different alias name for the certificate");
}
@ -457,6 +463,10 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateVO certificateVO;
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
if (hypervisorType != HypervisorType.KVM) {
throw new CloudRuntimeException("Direct download certificates only supported on KVM");
}
if (hostId == null) {
hosts = getRunningHostsToUploadCertificate(zoneId, hypervisorType);
@ -475,48 +485,55 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId);
if (certificateVO == null) {
s_logger.info("Certificate must be uploaded on zone " + zoneId);
return false;
return new Pair<>(certificateVO, new ArrayList<>());
}
}
s_logger.info("Attempting to upload certificate: " + alias + " to " + hosts.size() + " hosts on zone " + zoneId);
int hostCount = 0;
int success = 0;
int failed = 0;
List<HostCertificateStatus> results = new ArrayList<>();
if (CollectionUtils.isNotEmpty(hosts)) {
for (HostVO host : hosts) {
if (!uploadCertificate(certificateVO.getId(), host.getId())) {
String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + ")";
s_logger.error(msg);
throw new CloudRuntimeException(msg);
if (host == null) {
continue;
}
hostCount++;
HostCertificateStatus hostStatus;
Pair<Boolean, String> result = provisionCertificate(certificateVO.getId(), host.getId());
if (!result.first()) {
String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + "): " + result.second();
s_logger.error(msg);
failed++;
hostStatus = new HostCertificateStatus(CertificateStatus.FAILED, host, result.second());
} else {
success++;
hostStatus = new HostCertificateStatus(CertificateStatus.UPLOADED, host, "");
}
results.add(hostStatus);
}
}
s_logger.info("Certificate was successfully uploaded to " + hostCount + " hosts");
return true;
s_logger.info("Certificate was successfully uploaded to " + success + " hosts, " + failed + " failed");
return new Pair<>(certificateVO, results);
}
/**
* Upload and import certificate to hostId on keystore
*/
public boolean uploadCertificate(long certificateId, long hostId) {
DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findById(certificateId);
if (certificateVO == null) {
throw new CloudRuntimeException("Could not find certificate with id " + certificateId + " to upload to host: " + hostId);
}
private Pair<Boolean, String> setupCertificateOnHost(DirectDownloadCertificate certificate, long hostId) {
String certificateStr = certificate.getCertificate();
String alias = certificate.getAlias();
long certificateId = certificate.getId();
String certificate = certificateVO.getCertificate();
String alias = certificateVO.getAlias();
s_logger.debug("Uploading certificate: " + certificateVO.getAlias() + " to host " + hostId);
SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, alias);
s_logger.debug("Uploading certificate: " + alias + " to host " + hostId);
SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificateStr, alias);
Answer answer = agentManager.easySend(hostId, cmd);
Pair<Boolean, String> result;
if (answer == null || !answer.getResult()) {
String msg = "Certificate " + alias + " could not be added to host " + hostId;
if (answer != null) {
msg += " due to: " + answer.getDetails();
}
s_logger.error(msg);
return false;
result = new Pair<>(false, msg);
} else {
result = new Pair<>(true, "OK");
}
s_logger.info("Certificate " + alias + " successfully uploaded to host: " + hostId);
@ -528,8 +545,25 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateHostMapVO mapVO = new DirectDownloadCertificateHostMapVO(certificateId, hostId);
directDownloadCertificateHostMapDao.persist(mapVO);
}
return result;
}
/**
* Upload and import certificate to hostId on keystore
*/
public Pair<Boolean, String> provisionCertificate(long certificateId, long hostId) {
DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findById(certificateId);
if (certificateVO == null) {
throw new CloudRuntimeException("Could not find certificate with id " + certificateId + " to upload to host: " + hostId);
}
HostVO host = hostDao.findById(hostId);
if (host == null) {
throw new CloudRuntimeException("Cannot find a host with ID " + hostId);
}
if (host.getHypervisorType() != HypervisorType.KVM) {
throw new CloudRuntimeException("Cannot provision certificate to host " + host.getName() + " since it is not KVM");
}
return true;
return setupCertificateOnHost(certificateVO, hostId);
}
@Override
@ -549,8 +583,9 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateHostMapVO mapping = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId);
if (mapping == null) {
s_logger.debug("Syncing certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", uploading it");
if (!uploadCertificate(certificateVO.getId(), hostId)) {
String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed";
Pair<Boolean, String> result = provisionCertificate(certificateVO.getId(), hostId);
if (!result.first()) {
String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed: " + result.second();
s_logger.error(msg);
syncCertificatesResult = false;
} else {
@ -565,52 +600,127 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
return syncCertificatesResult;
}
@Override
public boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId) {
HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findByAlias(certificateAlias, hypervisorType, zoneId);
if (certificateVO == null) {
throw new CloudRuntimeException("Certificate alias " + certificateAlias + " does not exist");
}
List<DirectDownloadCertificateHostMapVO> maps = null;
private List<DirectDownloadCertificateHostMapVO> getCertificateHostMappings(DirectDownloadCertificate certificate, Long hostId) {
List<DirectDownloadCertificateHostMapVO> maps;
if (hostId == null) {
maps = directDownloadCertificateHostMapDao.listByCertificateId(certificateVO.getId());
maps = directDownloadCertificateHostMapDao.listByCertificateIdAndRevoked(certificate.getId(), false);
} else {
DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId);
DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificate.getId(), hostId);
if (hostMap == null) {
s_logger.info("Certificate " + certificateAlias + " cannot be revoked from host " + hostId + " as it is not available on the host");
return false;
String msg = "Certificate " + certificate.getAlias() + " cannot be revoked from host " + hostId + " as it is not available on the host";
s_logger.error(msg);
throw new CloudRuntimeException(msg);
} else if (hostMap.isRevoked()) {
s_logger.debug("Certificate " + certificate.getAlias() + " was already revoked from host " + hostId + " skipping it");
return new LinkedList<>();
}
maps = Collections.singletonList(hostMap);
}
s_logger.info("Attempting to revoke certificate alias: " + certificateAlias + " from " + maps.size() + " hosts");
if (CollectionUtils.isNotEmpty(maps)) {
for (DirectDownloadCertificateHostMapVO map : maps) {
Long mappingHostId = map.getHostId();
if (!revokeCertificateAliasFromHost(certificateAlias, mappingHostId)) {
String msg = "Could not revoke certificate from host: " + mappingHostId;
s_logger.error(msg);
throw new CloudRuntimeException(msg);
}
s_logger.info("Certificate " + certificateAlias + " revoked from host " + mappingHostId);
map.setRevoked(true);
directDownloadCertificateHostMapDao.update(map.getId(), map);
}
}
return true;
return maps;
}
protected boolean revokeCertificateAliasFromHost(String alias, Long hostId) {
@Override
public DirectDownloadCertificate findDirectDownloadCertificateByIdOrHypervisorAndAlias(Long id, String alias, String hypervisor, Long zoneId) {
DirectDownloadCertificateVO certificateVO;
if (id != null) {
certificateVO = directDownloadCertificateDao.findById(id);
} else if (StringUtils.isNotBlank(alias) && StringUtils.isNotBlank(hypervisor)) {
certificateVO = directDownloadCertificateDao.findByAlias(alias, HypervisorType.getType(hypervisor), zoneId);
} else {
throw new CloudRuntimeException("Please provide a hypervisor and certificate alias or certificate ID");
}
if (certificateVO == null) {
throw new CloudRuntimeException("Could not find certificate " +
(id != null ? "with ID " + id : "with alias " + alias + " and hypervisor " + hypervisor));
}
return certificateVO;
}
@Override
public List<HostCertificateStatus> revokeCertificate(DirectDownloadCertificate certificate, Long zoneId, Long hostId) {
String certificateAlias = certificate.getAlias();
if (!certificate.getZoneId().equals(zoneId)) {
throw new CloudRuntimeException("The certificate with alias " + certificateAlias + " was uploaded " +
" to the zone with ID=" + certificate.getZoneId() + " instead of the zone with ID=" + zoneId);
}
List<HostCertificateStatus> hostsList = new ArrayList<>();
List<DirectDownloadCertificateHostMapVO> maps = getCertificateHostMappings(certificate, hostId);
if (CollectionUtils.isEmpty(maps)) {
return hostsList;
}
int success = 0;
int failed = 0;
int skipped = 0;
s_logger.info("Attempting to revoke certificate alias: " + certificateAlias + " from " + maps.size() + " hosts");
for (DirectDownloadCertificateHostMapVO map : maps) {
Long mappingHostId = map.getHostId();
HostVO host = hostDao.findById(mappingHostId);
HostCertificateStatus hostStatus;
if (host == null || host.getDataCenterId() != zoneId || host.getHypervisorType() != HypervisorType.KVM) {
if (host != null) {
String reason = host.getDataCenterId() != zoneId ? "Host is not in the zone " + zoneId : "Host hypervisor is not KVM";
s_logger.debug("Skipping host " + host.getName() + ": " + reason);
hostStatus = new HostCertificateStatus(CertificateStatus.SKIPPED, host, reason);
hostsList.add(hostStatus);
}
skipped++;
continue;
}
Pair<Boolean, String> result = revokeCertificateAliasFromHost(certificateAlias, mappingHostId);
if (!result.first()) {
String msg = "Could not revoke certificate from host: " + mappingHostId + ": " + result.second();
s_logger.error(msg);
hostStatus = new HostCertificateStatus(CertificateStatus.FAILED, host, result.second());
failed++;
} else {
s_logger.info("Certificate " + certificateAlias + " revoked from host " + mappingHostId);
map.setRevoked(true);
hostStatus = new HostCertificateStatus(CertificateStatus.REVOKED, host, null);
success++;
directDownloadCertificateHostMapDao.update(map.getId(), map);
}
hostsList.add(hostStatus);
}
s_logger.info(String.format("Certificate alias %s revoked from: %d hosts, %d failed, %d skipped",
certificateAlias, success, failed, skipped));
return hostsList;
}
@Override
public List<DirectDownloadCertificate> listDirectDownloadCertificates(Long certificateId, Long zoneId) {
if (zoneId != null && dataCenterDao.findById(zoneId) == null) {
throw new InvalidParameterValueException("Cannot find a zone with ID = " + zoneId);
}
List<DirectDownloadCertificate> certificates = new LinkedList<>();
if (certificateId != null) {
certificates.add(directDownloadCertificateDao.findById(certificateId));
} else if (zoneId != null) {
certificates.addAll(directDownloadCertificateDao.listByZone(zoneId));
} else {
certificates.addAll(directDownloadCertificateDao.listAll());
}
return certificates;
}
@Override
public List<DirectDownloadCertificateHostMap> getCertificateHostsMapping(Long certificateId) {
if (certificateId == null) {
throw new InvalidParameterValueException("Please specify a certificate ID");
}
return new LinkedList<>(directDownloadCertificateHostMapDao.listByCertificateId(certificateId));
}
protected Pair<Boolean, String> revokeCertificateAliasFromHost(String alias, Long hostId) {
RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias);
try {
Answer answer = agentManager.send(hostId, cmd);
return answer != null && answer.getResult();
return new Pair<>(answer != null && answer.getResult(), answer != null ? answer.getDetails() : "");
} catch (AgentUnavailableException | OperationTimedoutException e) {
s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e);
return new Pair<>(false, e.getMessage());
}
return false;
}
@Override
@ -692,10 +802,13 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
s_logger.debug("Certificate " + certificateVO.getId() +
" (" + certificateVO.getAlias() + ") was not uploaded to host: " + hostVO.getId() +
" uploading it");
boolean result = directDownloadManager.uploadCertificate(certificateVO.getId(), hostVO.getId());
Pair<Boolean, String> result = directDownloadManager.provisionCertificate(certificateVO.getId(), hostVO.getId());
s_logger.debug("Certificate " + certificateVO.getAlias() + " " +
(result ? "uploaded" : "could not be uploaded") +
(result.first() ? "uploaded" : "could not be uploaded") +
" to host " + hostVO.getId());
if (!result.first()) {
s_logger.error("Certificate " + certificateVO.getAlias() + " failed: " + result.second());
}
}
}
}

View File

@ -27,6 +27,7 @@ import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.usage.UsageService;
@ -141,4 +142,46 @@ public class ApiResponseHelperTest {
assertTrue(response.getIpAddr().equals("ipv6"));
}
@Test
public void testHandleCertificateResponse() {
String certStr = "-----BEGIN CERTIFICATE-----\n" +
"MIIGLTCCBRWgAwIBAgIQOHZRhOAYLowYNcopBvxCdjANBgkqhkiG9w0BAQsFADCB\n" +
"jzELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n" +
"A1UEBxMHU2FsZm9yZDEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMTcwNQYDVQQD\n" +
"Ey5TZWN0aWdvIFJTQSBEb21haW4gVmFsaWRhdGlvbiBTZWN1cmUgU2VydmVyIENB\n" +
"MB4XDTIxMDYxNTAwMDAwMFoXDTIyMDcxNjIzNTk1OVowFzEVMBMGA1UEAwwMKi5h\n" +
"cGFjaGUub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4UoHCmK5\n" +
"XdbyZ++d2BGuX35zZcESvr4K1Hw7ZTbyzMC+uokBKJcng1Hf5ctjUFKCoz7AlWRq\n" +
"JH5U3vU0y515C0aEE+j0lUHlxMGQD2ut+sJ6BZqcTBl5d8ns1TSckEH31DBDN3Fw\n" +
"uMLqEWBOjwt1MMT3Z+kR7ekuheJYbYHbJ2VtnKQd4jHmLly+/p+UqaQ6dIvQxq82\n" +
"ggZIUNWjGKwXS2vKl6O9EDu/QaAX9e059pf3UxAxGtJjeKXWJvt1e96T53+2+kXp\n" +
"j0/PuyT6F0o+grY08tCJnw7kTB4sE2qfALdwSblvyjBDOYtS4Xj5nycMpd+4Qse4\n" +
"2+irNBdZ63pqqQIDAQABo4IC+jCCAvYwHwYDVR0jBBgwFoAUjYxexFStiuF36Zv5\n" +
"mwXhuAGNYeEwHQYDVR0OBBYEFH+9CNXAwWW4+jyizee51r8x4ofHMA4GA1UdDwEB\n" +
"/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF\n" +
"BQcDAjBJBgNVHSAEQjBAMDQGCysGAQQBsjEBAgIHMCUwIwYIKwYBBQUHAgEWF2h0\n" +
"dHBzOi8vc2VjdGlnby5jb20vQ1BTMAgGBmeBDAECATCBhAYIKwYBBQUHAQEEeDB2\n" +
"ME8GCCsGAQUFBzAChkNodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29SU0FE\n" +
"b21haW5WYWxpZGF0aW9uU2VjdXJlU2VydmVyQ0EuY3J0MCMGCCsGAQUFBzABhhdo\n" +
"dHRwOi8vb2NzcC5zZWN0aWdvLmNvbTAjBgNVHREEHDAaggwqLmFwYWNoZS5vcmeC\n" +
"CmFwYWNoZS5vcmcwggF+BgorBgEEAdZ5AgQCBIIBbgSCAWoBaAB2AEalVet1+pEg\n" +
"MLWiiWn0830RLEF0vv1JuIWr8vxw/m1HAAABehHLqfgAAAQDAEcwRQIgINH3CquJ\n" +
"zTAprwjdo2cEWkMzpaNoP1SOI4xGl68PF2oCIQC77eD7K6Smx4Fv/z/sTKk21Psb\n" +
"ZhmVq5YoqhwRKuMgVAB2AEHIyrHfIkZKEMahOglCh15OMYsbA+vrS8do8JBilgb2\n" +
"AAABehHLqcEAAAQDAEcwRQIhANh++zJa9AE4U0DsHIFq6bW40b1OfGfH8uUdmjEZ\n" +
"s1jzAiBIRtJeFVmobSnbFKlOr8BGfD2L/hg1rkAgJlKY5oFShgB2ACl5vvCeOTkh\n" +
"8FZzn2Old+W+V32cYAr4+U1dJlwlXceEAAABehHLqZ4AAAQDAEcwRQIhAOZDfvU8\n" +
"Hz80I6Iyj2rv8+yWBVq1XVixI8bMykdCO6ADAiAWj8cJ9g1zxko4dJu8ouJf+Pwl\n" +
"0bbhhuJHhy/f5kiaszANBgkqhkiG9w0BAQsFAAOCAQEAlkdB7FZtVQz39TDNKR4u\n" +
"I8VQsTH5n4Kg+zVc0pptI7HGUWtp5PjBAEsvJ/G/NQXsjVflQaNPRRd7KNZycZL1\n" +
"jls6GdVoWVno6O5aLS7cCnb0tTlb8srhb9vdLZkSoCVCZLVjik5s2TLfpLsBKrTP\n" +
"leVY3n9TBZH+vyKLHt4WHR23Z+74xDsuXunoPGXQVV8ymqTtfohaoM19jP99vjY7\n" +
"DL/289XjMSfyPFqlpU4JDM7lY/kJSKB/C4eQglT8Sgm0h/kj5hdT2uMJBIQZIJVv\n" +
"241fAVUPgrYAESOMm2TVA9r1OzeoUNlKw+e3+vjTR6sfDDp/iRKcEVQX4u9+CxZp\n" +
"9g==\n-----END CERTIFICATE-----";
DirectDownloadCertificateResponse response = new DirectDownloadCertificateResponse();
helper.handleCertificateResponse(certStr, response);
assertEquals("3", response.getVersion());
assertEquals("CN=*.apache.org", response.getSubject());
}
}

View File

@ -18,7 +18,7 @@
"""
# Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase
from marvin.lib.utils import (cleanup_resources)
from marvin.lib.utils import (cleanup_resources, validateList)
from marvin.lib.base import (ServiceOffering,
NetworkOffering,
Network,
@ -28,7 +28,8 @@ from marvin.lib.base import (ServiceOffering,
from marvin.lib.common import (get_pod,
get_zone)
from nose.plugins.attrib import attr
from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate)
from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate,
listTemplateDirectDownloadCertificates)
from marvin.lib.decoratorGenerators import skipTestIf
import uuid
@ -136,9 +137,13 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
except Exception as e:
self.fail("Valid certificate must be uploaded")
cmd = listTemplateDirectDownloadCertificates.listTemplateDirectDownloadCertificatesCmd()
certs = self.apiclient.listTemplateDirectDownloadCertificates(cmd)
validateList(certs)
cert = certs[0]
revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd()
revokecmd.hypervisor = self.hypervisor
revokecmd.name = cmd.name
revokecmd.id = cert.id
revokecmd.zoneid = self.zone.id
try:
@ -149,6 +154,7 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
return
class TestDirectDownloadTemplates(cloudstackTestCase):
@classmethod