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 ADAPTER_TYPE = "adaptertype";
public static final String ADDRESS = "address"; public static final String ADDRESS = "address";
public static final String ALGORITHM = "algorithm"; public static final String ALGORITHM = "algorithm";
public static final String ALIAS = "alias";
public static final String ALLOCATED_ONLY = "allocatedonly"; public static final String ALLOCATED_ONLY = "allocatedonly";
public static final String ANNOTATION = "annotation"; public static final String ANNOTATION = "annotation";
public static final String API_KEY = "apikey"; 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_CHAIN = "certchain";
public static final String CERTIFICATE_FINGERPRINT = "fingerprint"; public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
public static final String CERTIFICATE_ID = "certid"; 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 ENABLED_REVOCATION_CHECK = "enabledrevocationcheck";
public static final String CONTROLLER = "controller"; public static final String CONTROLLER = "controller";
public static final String CONTROLLER_UNIT = "controllerunit"; 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_ID = "hostid";
public static final String HOST_IDS = "hostids"; public static final String HOST_IDS = "hostids";
public static final String HOST_NAME = "hostname"; public static final String HOST_NAME = "hostname";
public static final String HOSTS_MAP = "hostsmap";
public static final String HYPERVISOR = "hypervisor"; public static final String HYPERVISOR = "hypervisor";
public static final String INLINE = "inline"; public static final String INLINE = "inline";
public static final String INSTANCE = "instance"; public static final String INSTANCE = "instance";
@ -237,6 +243,7 @@ public class ApiConstants {
public static final String LEVEL = "level"; public static final String LEVEL = "level";
public static final String LENGTH = "length"; public static final String LENGTH = "length";
public static final String LIMIT_CPU_USE = "limitcpuuse"; 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 LOCK = "lock";
public static final String LUN = "lun"; public static final String LUN = "lun";
public static final String LBID = "lbruleid"; 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 RESOURCE_TYPE_NAME = "resourcetypename";
public static final String RESPONSE = "response"; public static final String RESPONSE = "response";
public static final String REVERTABLE = "revertable"; public static final String REVERTABLE = "revertable";
public static final String REVOKED = "revoked";
public static final String REGISTERED = "registered"; public static final String REGISTERED = "registered";
public static final String QUALIFIERS = "qualifiers"; public static final String QUALIFIERS = "qualifiers";
public static final String QUERY_FILTER = "queryfilter"; public static final String QUERY_FILTER = "queryfilter";

View File

@ -23,10 +23,16 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import com.cloud.server.ResourceIcon; 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.ResourceIconResponse;
import org.apache.cloudstack.api.response.DirectDownloadCertificateHostStatusResponse;
import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse; import org.apache.cloudstack.api.response.RouterHealthCheckResultResponse;
import com.cloud.resource.RollingMaintenanceManager; import com.cloud.resource.RollingMaintenanceManager;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse; 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.management.ManagementServerHost;
import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroup;
import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.affinity.AffinityGroupResponse;
@ -491,4 +497,11 @@ public interface ResponseGenerator {
ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon); 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.BaseCmd;
import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException; 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.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.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext; 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;
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 org.apache.log4j.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME, @APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME,
description = "Revoke a certificate alias from a KVM host", description = "Revoke a direct download certificate from hosts in a zone",
responseObject = SuccessResponse.class, responseObject = DirectDownloadCertificateHostStatusResponse.class,
requestHasSensitiveInfo = true,
responseHasSensitiveInfo = true,
since = "4.13", since = "4.13",
authorized = {RoleType.Admin}) authorized = {RoleType.Admin})
public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd { public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
@ -54,35 +60,63 @@ public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd {
private static final Logger LOG = Logger.getLogger(RevokeTemplateDirectDownloadCertificateCmd.class); private static final Logger LOG = Logger.getLogger(RevokeTemplateDirectDownloadCertificateCmd.class);
public static final String APINAME = "revokeTemplateDirectDownloadCertificate"; public static final String APINAME = "revokeTemplateDirectDownloadCertificate";
@Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true, @Parameter(name = ApiConstants.ID, type = CommandType.UUID,
description = "alias of the SSL certificate") 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; private String certificateAlias;
@Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true, @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING,
description = "hypervisor type") description = "(optional) hypervisor type")
private String hypervisor; private String hypervisor;
@Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, @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; private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, @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 revoke certificate")
private Long hostId; private Long hostId;
@Override private void createResponse(final List<HostCertificateStatus> hostsRevokeStatusList) {
public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { final ListResponse<DirectDownloadCertificateHostStatusResponse> response = new ListResponse<>();
if (!hypervisor.equalsIgnoreCase("kvm")) { 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"); 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 { try {
LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts"); DirectDownloadCertificate certificate = directDownloadManager.findDirectDownloadCertificateByIdOrHypervisorAndAlias(certificateId, certificateAlias, hypervisor, zoneId);
boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor, zoneId, hostId); List<HostCertificateStatus> hostsResult = directDownloadManager.revokeCertificate(certificate, zoneId, hostId);
response.setSuccess(result); createResponse(hostsResult);
setResponseObject(response);
} catch (Exception e) { } 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. // under the License.
package org.apache.cloudstack.api.command.admin.direct.download; 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.acl.RoleType;
import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.APICommand;
import org.apache.cloudstack.api.ApiConstants; 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.Parameter;
import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.ApiErrorCode; 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.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.api.response.ZoneResponse;
import org.apache.cloudstack.context.CallContext; 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;
import org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.ArrayList;
import java.util.List;
@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME, @APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME,
description = "Upload a certificate for HTTPS direct template download on KVM hosts", description = "Upload a certificate for HTTPS direct template download on KVM hosts",
responseObject = SuccessResponse.class, responseObject = DirectDownloadCertificateResponse.class,
requestHasSensitiveInfo = true,
responseHasSensitiveInfo = true,
since = "4.11.0", since = "4.11.0",
authorized = {RoleType.Admin}) authorized = {RoleType.Admin})
public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd { public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
@ -63,9 +68,29 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
private Long zoneId; private Long zoneId;
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, @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 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 @Override
public void execute() { public void execute() {
if (!hypervisor.equalsIgnoreCase("kvm")) { if (!hypervisor.equalsIgnoreCase("kvm")) {
@ -74,10 +99,11 @@ public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd {
try { try {
LOG.debug("Uploading certificate " + name + " to agents for Direct Download"); LOG.debug("Uploading certificate " + name + " to agents for Direct Download");
boolean result = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId); Pair<DirectDownloadCertificate, List<HostCertificateStatus>> uploadStatus =
SuccessResponse response = new SuccessResponse(getCommandName()); directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId);
response.setSuccess(result); DirectDownloadCertificate certificate = uploadStatus.first();
setResponseObject(response); List<HostCertificateStatus> hostStatus = uploadStatus.second();
createResponse(certificate, hostStatus);
} catch (Exception e) { } catch (Exception e) {
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); 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 getCertificate();
String getAlias(); String getAlias();
Hypervisor.HypervisorType getHypervisorType(); 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; 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.agent.direct.download.DirectDownloadService;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.Configurable;
import com.cloud.utils.component.PluggableService; import com.cloud.utils.component.PluggableService;
import java.util.List;
public interface DirectDownloadManager extends DirectDownloadService, PluggableService, Configurable { public interface DirectDownloadManager extends DirectDownloadService, PluggableService, Configurable {
static final int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000; int DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT = 5000;
static final int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000; int DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT = 5000;
static final int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000; int DEFAULT_DIRECT_DOWNLOAD_CONNECTION_REQUEST_TIMEOUT = 5000;
ConfigKey<Long> DirectDownloadCertificateUploadInterval = new ConfigKey<>("Advanced", Long.class, ConfigKey<Long> DirectDownloadCertificateUploadInterval = new ConfigKey<>("Advanced", Long.class,
"direct.download.certificate.background.task.interval", "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", "Only certificates which have not been revoked from hosts are uploaded",
false); 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", "direct.download.connect.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT), String.valueOf(DEFAULT_DIRECT_DOWNLOAD_CONNECT_TIMEOUT),
"Connection establishment timeout in milliseconds for direct download", "Connection establishment timeout in milliseconds for direct download",
true); 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", "direct.download.socket.timeout",
String.valueOf(DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT), String.valueOf(DEFAULT_DIRECT_DOWNLOAD_SOCKET_TIMEOUT),
"Socket timeout (SO_TIMEOUT) in milliseconds for direct download", "Socket timeout (SO_TIMEOUT) in milliseconds for direct download",
true); 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", "direct.download.connection.request.timeout",
String.valueOf(DEFAULT_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", "Requesting a connection from connection manager timeout in milliseconds for direct download",
true); 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> { public interface DirectDownloadCertificateHostMapDao extends GenericDao<DirectDownloadCertificateHostMapVO, Long> {
DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId); DirectDownloadCertificateHostMapVO findByCertificateAndHost(long certificateId, long hostId);
List<DirectDownloadCertificateHostMapVO> listByCertificateId(long certificateId); 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 = createSearchBuilder();
mapSearchBuilder.and("certificate_id", mapSearchBuilder.entity().getCertificateId(), SearchCriteria.Op.EQ); mapSearchBuilder.and("certificate_id", mapSearchBuilder.entity().getCertificateId(), SearchCriteria.Op.EQ);
mapSearchBuilder.and("host_id", mapSearchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); mapSearchBuilder.and("host_id", mapSearchBuilder.entity().getHostId(), SearchCriteria.Op.EQ);
mapSearchBuilder.and("revoked", mapSearchBuilder.entity().isRevoked(), SearchCriteria.Op.EQ);
mapSearchBuilder.done(); mapSearchBuilder.done();
} }
@Override @Override
@ -45,4 +46,12 @@ public class DirectDownloadCertificateHostMapDaoImpl extends GenericDaoBase<Dire
sc.setParameters("certificate_id", certificateId); sc.setParameters("certificate_id", certificateId);
return listBy(sc); 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 @Entity
@Table(name = "direct_download_certificate_host_map") @Table(name = "direct_download_certificate_host_map")
public class DirectDownloadCertificateHostMapVO { public class DirectDownloadCertificateHostMapVO implements DirectDownloadCertificateHostMap {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id") @Column(name = "id")
private Long id; private long id;
@Column(name = "host_id") @Column(name = "host_id")
private Long hostId; private Long hostId;
@ -50,15 +50,15 @@ public class DirectDownloadCertificateHostMapVO {
this.revoked = false; this.revoked = false;
} }
public Long getId() { public long getId() {
return id; return id;
} }
public void setId(Long id) { public void setId(long id) {
this.id = id; this.id = id;
} }
public Long getHostId() { public long getHostId() {
return hostId; return hostId;
} }
@ -66,7 +66,7 @@ public class DirectDownloadCertificateHostMapVO {
this.hostId = hostId; this.hostId = hostId;
} }
public Long getCertificateId() { public long getCertificateId() {
return certificateId; return certificateId;
} }
@ -74,8 +74,8 @@ public class DirectDownloadCertificateHostMapVO {
this.certificateId = certificateId; this.certificateId = certificateId;
} }
public Boolean isRevoked() { public boolean isRevoked() {
return revoked; return revoked != null && revoked;
} }
public void setRevoked(Boolean revoked) { public void setRevoked(Boolean revoked) {

View File

@ -21,6 +21,14 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>cloud-framework-direct-download</artifactId> <artifactId>cloud-framework-direct-download</artifactId>
<name>Apache CloudStack Framework - Direct Download to Primary Storage</name> <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> <parent>
<artifactId>cloudstack-framework</artifactId> <artifactId>cloudstack-framework</artifactId>
<groupId>org.apache.cloudstack</groupId> <groupId>org.apache.cloudstack</groupId>

View File

@ -17,6 +17,8 @@
package org.apache.cloudstack.framework.agent.direct.download; package org.apache.cloudstack.framework.agent.direct.download;
import com.cloud.utils.Pair;
public interface DirectDownloadService { public interface DirectDownloadService {
/** /**
@ -24,15 +26,10 @@ public interface DirectDownloadService {
*/ */
void downloadTemplate(long templateId, long poolId, long hostId); 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' * 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' * 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 static com.cloud.utils.NumbersUtil.toHumanReadableSize;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -34,6 +36,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import com.cloud.utils.security.CertificateHelper;
import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.acl.ControlledEntity;
import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.acl.ControlledEntity.ACLType;
import org.apache.cloudstack.affinity.AffinityGroup; 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.CounterResponse;
import org.apache.cloudstack.api.response.CreateCmdResponse; import org.apache.cloudstack.api.response.CreateCmdResponse;
import org.apache.cloudstack.api.response.CreateSSHKeyPairResponse; 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.DiskOfferingResponse;
import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.DomainResponse;
import org.apache.cloudstack.api.response.DomainRouterResponse; 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.ResourceIconResponse;
import org.apache.cloudstack.api.response.ResourceLimitResponse; import org.apache.cloudstack.api.response.ResourceLimitResponse;
import org.apache.cloudstack.api.response.ResourceTagResponse; 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.RollingMaintenanceHostSkippedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse; import org.apache.cloudstack.api.response.RollingMaintenanceHostUpdatedResponse;
import org.apache.cloudstack.api.response.RollingMaintenanceResponse; 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.backup.dao.BackupOfferingDao;
import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.Configuration;
import org.apache.cloudstack.context.CallContext; 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.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreCapabilities; 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.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo; 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.AsyncJob;
import org.apache.cloudstack.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.AsyncJobManager;
import org.apache.cloudstack.management.ManagementServerHost; 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.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import sun.security.x509.X509CertImpl;
public class ApiResponseHelper implements ResponseGenerator { public class ApiResponseHelper implements ResponseGenerator {
@ -4547,4 +4557,82 @@ public class ApiResponseHelper implements ResponseGenerator {
public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) { public ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon) {
return ApiDBUtils.newResourceIconResponse(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.ResetCfgCmd;
import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; 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.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.RevokeTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd; import org.apache.cloudstack.api.command.admin.direct.download.UploadTemplateDirectDownloadCertificateCmd;
import org.apache.cloudstack.api.command.admin.domain.CreateDomainCmd; 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(DeleteManagementNetworkIpRangeCmd.class);
cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class); cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class);
cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class); cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListTemplateDirectDownloadCertificatesCmd.class);
cmdList.add(ProvisionTemplateDirectDownloadCertificateCmd.class);
cmdList.add(ListMgmtsCmd.class); cmdList.add(ListMgmtsCmd.class);
cmdList.add(GetUploadParamsForIsoCmd.class); cmdList.add(GetUploadParamsForIsoCmd.class);
cmdList.add(GetRouterHealthCheckResultsCmd.class); cmdList.add(GetRouterHealthCheckResultsCmd.class);

View File

@ -19,6 +19,7 @@
package org.apache.cloudstack.direct.download; package org.apache.cloudstack.direct.download;
import static com.cloud.storage.Storage.ImageFormat; import static com.cloud.storage.Storage.ImageFormat;
import static org.apache.cloudstack.direct.download.DirectDownloadManager.HostCertificateStatus.CertificateStatus;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -29,6 +30,7 @@ import java.security.cert.CertificateNotYetValidException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@ -39,6 +41,8 @@ import java.util.stream.Collectors;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; 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.DirectDownloadAnswer;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand;
import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol; 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.cloudstack.storage.to.TemplateObjectTO;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils; import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone; import org.joda.time.DateTimeZone;
@ -448,7 +453,8 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
} }
@Override @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"))) { if (alias != null && (alias.equalsIgnoreCase("cloud") || alias.startsWith("cloudca"))) {
throw new CloudRuntimeException("Please provide a different alias name for the certificate"); 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; DirectDownloadCertificateVO certificateVO;
HypervisorType hypervisorType = HypervisorType.getType(hypervisor); HypervisorType hypervisorType = HypervisorType.getType(hypervisor);
if (hypervisorType != HypervisorType.KVM) {
throw new CloudRuntimeException("Direct download certificates only supported on KVM");
}
if (hostId == null) { if (hostId == null) {
hosts = getRunningHostsToUploadCertificate(zoneId, hypervisorType); hosts = getRunningHostsToUploadCertificate(zoneId, hypervisorType);
@ -475,48 +485,55 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId); certificateVO = directDownloadCertificateDao.findByAlias(alias, hypervisorType, zoneId);
if (certificateVO == null) { if (certificateVO == null) {
s_logger.info("Certificate must be uploaded on zone " + zoneId); 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); 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)) { if (CollectionUtils.isNotEmpty(hosts)) {
for (HostVO host : hosts) { for (HostVO host : hosts) {
if (!uploadCertificate(certificateVO.getId(), host.getId())) { if (host == null) {
String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + ")"; continue;
s_logger.error(msg);
throw new CloudRuntimeException(msg);
} }
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"); s_logger.info("Certificate was successfully uploaded to " + success + " hosts, " + failed + " failed");
return true; return new Pair<>(certificateVO, results);
} }
/** private Pair<Boolean, String> setupCertificateOnHost(DirectDownloadCertificate certificate, long hostId) {
* Upload and import certificate to hostId on keystore String certificateStr = certificate.getCertificate();
*/ String alias = certificate.getAlias();
public boolean uploadCertificate(long certificateId, long hostId) { long certificateId = certificate.getId();
DirectDownloadCertificateVO certificateVO = directDownloadCertificateDao.findById(certificateId);
if (certificateVO == null) {
throw new CloudRuntimeException("Could not find certificate with id " + certificateId + " to upload to host: " + hostId);
}
String certificate = certificateVO.getCertificate(); s_logger.debug("Uploading certificate: " + alias + " to host " + hostId);
String alias = certificateVO.getAlias(); SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificateStr, alias);
s_logger.debug("Uploading certificate: " + certificateVO.getAlias() + " to host " + hostId);
SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, alias);
Answer answer = agentManager.easySend(hostId, cmd); Answer answer = agentManager.easySend(hostId, cmd);
Pair<Boolean, String> result;
if (answer == null || !answer.getResult()) { if (answer == null || !answer.getResult()) {
String msg = "Certificate " + alias + " could not be added to host " + hostId; String msg = "Certificate " + alias + " could not be added to host " + hostId;
if (answer != null) { if (answer != null) {
msg += " due to: " + answer.getDetails(); msg += " due to: " + answer.getDetails();
} }
s_logger.error(msg); 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); 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); DirectDownloadCertificateHostMapVO mapVO = new DirectDownloadCertificateHostMapVO(certificateId, hostId);
directDownloadCertificateHostMapDao.persist(mapVO); 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 @Override
@ -549,8 +583,9 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
DirectDownloadCertificateHostMapVO mapping = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId); DirectDownloadCertificateHostMapVO mapping = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId);
if (mapping == null) { if (mapping == null) {
s_logger.debug("Syncing certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", uploading it"); s_logger.debug("Syncing certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", uploading it");
if (!uploadCertificate(certificateVO.getId(), hostId)) { Pair<Boolean, String> result = provisionCertificate(certificateVO.getId(), hostId);
String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed"; if (!result.first()) {
String msg = "Could not sync certificate " + certificateVO.getId() + " (" + certificateVO.getAlias() + ") on host: " + hostId + ", upload failed: " + result.second();
s_logger.error(msg); s_logger.error(msg);
syncCertificatesResult = false; syncCertificatesResult = false;
} else { } else {
@ -565,52 +600,127 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
return syncCertificatesResult; return syncCertificatesResult;
} }
@Override private List<DirectDownloadCertificateHostMapVO> getCertificateHostMappings(DirectDownloadCertificate certificate, Long hostId) {
public boolean revokeCertificateAlias(String certificateAlias, String hypervisor, Long zoneId, Long hostId) { List<DirectDownloadCertificateHostMapVO> maps;
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;
if (hostId == null) { if (hostId == null) {
maps = directDownloadCertificateHostMapDao.listByCertificateId(certificateVO.getId()); maps = directDownloadCertificateHostMapDao.listByCertificateIdAndRevoked(certificate.getId(), false);
} else { } else {
DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificateVO.getId(), hostId); DirectDownloadCertificateHostMapVO hostMap = directDownloadCertificateHostMapDao.findByCertificateAndHost(certificate.getId(), hostId);
if (hostMap == null) { if (hostMap == null) {
s_logger.info("Certificate " + certificateAlias + " cannot be revoked from host " + hostId + " as it is not available on the host"); String msg = "Certificate " + certificate.getAlias() + " cannot be revoked from host " + hostId + " as it is not available on the host";
return false; 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); maps = Collections.singletonList(hostMap);
} }
return maps;
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;
} }
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); RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias);
try { try {
Answer answer = agentManager.send(hostId, cmd); 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) { } catch (AgentUnavailableException | OperationTimedoutException e) {
s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e); s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e);
return new Pair<>(false, e.getMessage());
} }
return false;
} }
@Override @Override
@ -692,10 +802,13 @@ public class DirectDownloadManagerImpl extends ManagerBase implements DirectDown
s_logger.debug("Certificate " + certificateVO.getId() + s_logger.debug("Certificate " + certificateVO.getId() +
" (" + certificateVO.getAlias() + ") was not uploaded to host: " + hostVO.getId() + " (" + certificateVO.getAlias() + ") was not uploaded to host: " + hostVO.getId() +
" uploading it"); " 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() + " " + s_logger.debug("Certificate " + certificateVO.getAlias() + " " +
(result ? "uploaded" : "could not be uploaded") + (result.first() ? "uploaded" : "could not be uploaded") +
" to host " + hostVO.getId()); " 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.Date;
import java.util.TimeZone; import java.util.TimeZone;
import org.apache.cloudstack.api.response.DirectDownloadCertificateResponse;
import org.apache.cloudstack.api.response.NicSecondaryIpResponse; import org.apache.cloudstack.api.response.NicSecondaryIpResponse;
import org.apache.cloudstack.api.response.UsageRecordResponse; import org.apache.cloudstack.api.response.UsageRecordResponse;
import org.apache.cloudstack.usage.UsageService; import org.apache.cloudstack.usage.UsageService;
@ -141,4 +142,46 @@ public class ApiResponseHelperTest {
assertTrue(response.getIpAddr().equals("ipv6")); 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 # Import Local Modules
from marvin.cloudstackTestCase import cloudstackTestCase 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, from marvin.lib.base import (ServiceOffering,
NetworkOffering, NetworkOffering,
Network, Network,
@ -28,7 +28,8 @@ from marvin.lib.base import (ServiceOffering,
from marvin.lib.common import (get_pod, from marvin.lib.common import (get_pod,
get_zone) get_zone)
from nose.plugins.attrib import attr 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 from marvin.lib.decoratorGenerators import skipTestIf
import uuid import uuid
@ -136,9 +137,13 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
except Exception as e: except Exception as e:
self.fail("Valid certificate must be uploaded") self.fail("Valid certificate must be uploaded")
cmd = listTemplateDirectDownloadCertificates.listTemplateDirectDownloadCertificatesCmd()
certs = self.apiclient.listTemplateDirectDownloadCertificates(cmd)
validateList(certs)
cert = certs[0]
revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd() revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd()
revokecmd.hypervisor = self.hypervisor revokecmd.id = cert.id
revokecmd.name = cmd.name
revokecmd.zoneid = self.zone.id revokecmd.zoneid = self.zone.id
try: try:
@ -149,6 +154,7 @@ class TestUploadDirectDownloadCertificates(cloudstackTestCase):
return return
class TestDirectDownloadTemplates(cloudstackTestCase): class TestDirectDownloadTemplates(cloudstackTestCase):
@classmethod @classmethod