From 6f0d898cb253d05eaa71642f041fd9a53a1bbb69 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 1 Nov 2018 15:48:43 -0300 Subject: [PATCH 1/6] Improvements on upload direct download certificates --- agent/src/com/cloud/agent/Agent.java | 24 ++++++- .../download/DirectDownloadManagerImpl.java | 71 +++++++++++++++++-- .../DirectDownloadManagerImplTest.java | 48 +++++++++++++ 3 files changed, 134 insertions(+), 9 deletions(-) diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java index 500724dd5a36..98bf5405dcd0 100644 --- a/agent/src/com/cloud/agent/Agent.java +++ b/agent/src/com/cloud/agent/Agent.java @@ -695,16 +695,34 @@ private Answer setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; - String cerFile = agentFile.getParent() + "/" + certificateName + ".cer"; - Script.runSimpleBashScript(String.format("echo '%s' > %s", certificate, cerFile)); + String cerFile = agentFile.getParent() + "/" + "CSCERTIFICATE-" + certificateName; + s_logger.debug("Creating temporary certificate file into: " + cerFile); + int result = Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s", certificate, cerFile)); + if (result != 0) { + return new Answer(cmd, false, "Could not create the certificate file on path: " + cerFile); + } + + s_logger.debug("Retrieving keystore password"); String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); String privatePassword = Script.runSimpleBashScript(privatePasswordCmd); + if (org.apache.commons.lang.StringUtils.isBlank(privatePassword)) { + return new Answer(cmd, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME); + } + s_logger.debug("Importing certificate from temporary file to keystore"); String importCommandFormat = "keytool -importcert -file %s -keystore %s -alias '%s' -storepass '%s' -noprompt"; String importCmd = String.format(importCommandFormat, cerFile, keyStoreFile, certificateName, privatePassword); - Script.runSimpleBashScript(importCmd); + result = Script.runSimpleBashScriptForExitValue(importCmd); + + s_logger.debug("Cleaning up temporary certificate file"); + Script.runSimpleBashScript("rm -f " + cerFile); + + if (result != 0) { + return new Answer(cmd, false, "Could not import certificate to keystore: " + KeyStoreUtils.KS_FILENAME); + } + return new Answer(cmd, true, "Certificate " + certificateName + " imported"); } diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index c1ffc5ef4737..29f4b2c3b583 100755 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -41,6 +41,10 @@ import java.net.URI; import java.net.URISyntaxException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -50,6 +54,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +import com.cloud.utils.security.CertificateHelper; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.DirectDownloadAnswer; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol; @@ -69,6 +74,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.log4j.Logger; +import sun.security.x509.X509CertImpl; public class DirectDownloadManagerImpl extends ManagerBase implements DirectDownloadManager { @@ -313,14 +319,64 @@ private List getRunningHostsToUploadCertificate(HypervisorType hyperviso .collect(Collectors.toList()); } + /** + * Return pretified PEM certificate + */ + protected String getPretifiedCertificate(String certificateCer) { + String cert = certificateCer.replaceAll("(.{64})", "$1\n"); + if (!cert.startsWith(BEGIN_CERT) && !cert.endsWith(END_CERT)) { + cert = BEGIN_CERT + LINE_SEPARATOR + cert + LINE_SEPARATOR + END_CERT; + } + return cert; + } + + /** + * Generate and return certificate from the string + * @throws CloudRuntimeException if the certificate is not well formed + */ + private Certificate getCertificateFromString(String certificatePem) { + try { + return CertificateHelper.buildCertificate(certificatePem); + } catch (CertificateException e) { + e.printStackTrace(); + throw new CloudRuntimeException("Cannot parse the certificate provided, please provide a PEM certificate. Error: " + e.getMessage()); + } + } + + /** + * Perform sanity of string parsed certificate + */ + protected void certificateSanity(String certificatePem) { + Certificate certificate = getCertificateFromString(certificatePem); + + if (certificate instanceof X509CertImpl) { + X509CertImpl x509Cert = (X509CertImpl) certificate; + try { + x509Cert.checkValidity(); + } catch (CertificateExpiredException | CertificateNotYetValidException e) { + e.printStackTrace(); + throw new CloudRuntimeException("Certificate is invalid. Please provide a valid certificate. Error: " + e.getMessage()); + } + if (x509Cert.getSubjectDN() != null) { + s_logger.info("Valid certificate for domain name: " + x509Cert.getSubjectDN().getName()); + } + } + } + @Override - public boolean uploadCertificateToHosts(String certificateCer, String certificateName, String hypervisor) { + public boolean uploadCertificateToHosts(String certificateCer, String alias, String hypervisor) { HypervisorType hypervisorType = HypervisorType.getType(hypervisor); List hosts = getRunningHostsToUploadCertificate(hypervisorType); + + String certificatePem = getPretifiedCertificate(certificateCer); + certificateSanity(certificatePem); + + s_logger.info("Attempting to upload certificate: " + alias + " to " + hosts.size() + " hosts"); if (CollectionUtils.isNotEmpty(hosts)) { for (HostVO host : hosts) { - if (!uploadCertificate(certificateCer, certificateName, host.getId())) { - throw new CloudRuntimeException("Uploading certificate " + certificateName + " failed on host: " + host.getId()); + if (!uploadCertificate(certificatePem, alias, host.getId())) { + s_logger.error("Could not upload certificate on host: " + host.getName() + " (" + host.getUuid() + ")"); + throw new CloudRuntimeException("Uploading certificate " + alias + " failed on host: " + host.getUuid()); } } } @@ -331,11 +387,14 @@ public boolean uploadCertificateToHosts(String certificateCer, String certificat * Upload and import certificate to hostId on keystore */ protected boolean uploadCertificate(String certificate, String certificateName, long hostId) { - String cert = certificate.replaceAll("(.{64})", "$1\n"); - final String prettified_cert = BEGIN_CERT + LINE_SEPARATOR + cert + LINE_SEPARATOR + END_CERT; - SetupDirectDownloadCertificate cmd = new SetupDirectDownloadCertificate(prettified_cert, certificateName); + SetupDirectDownloadCertificate cmd = new SetupDirectDownloadCertificate(certificate, certificateName); Answer answer = agentManager.easySend(hostId, cmd); if (answer == null || !answer.getResult()) { + String msg = "Certificate " + certificateName + " could not be added to host " + hostId; + if (answer != null) { + msg += " due to: " + answer.getDetails(); + } + s_logger.info(msg); return false; } s_logger.info("Certificate " + certificateName + " successfully uploaded to host: " + hostId); diff --git a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java index 59405998f9fe..7498c22f131e 100644 --- a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java +++ b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java @@ -20,6 +20,7 @@ import com.cloud.agent.AgentManager; import com.cloud.host.dao.HostDao; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand.DownloadProtocol; import org.junit.Assert; import org.junit.Before; @@ -50,6 +51,41 @@ public class DirectDownloadManagerImplTest { private static final String HTTP_HEADER_2 = "Accept-Encoding"; private static final String HTTP_VALUE_2 = "gzip"; + private static final String VALID_CERTIFICATE = + "MIIGIDCCBQigAwIBAgISBBNfK9DprG2M0UhfMJI4EqQKMA0GCSqGSIb3DQEBCwUA" + + "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD" + + "ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODEwMTEwNTU3MDVaFw0x" + + "OTAxMDkwNTU3MDVaMBsxGTAXBgNVBAMTEGNsb3VkLmNlbnRvcy5vcmcwggEiMA0G" + + "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPPr8MWWyfIF7lXZ1tkazvDUBKK6Vs" + + "znDCndeuMqWAOc/vf7RN32bH5v+To9eHB/MPC0cCAMt2K38jC+c4ITgQlja75lDV" + + "G3sHzORQlD2GNmwiIL5TK6pc0NpmSrNBlMkhSlkaGeRPhNKDj7pIIDwRMbDxiaNE" + + "lZD/A/K/ugv/FkZT6M1gU17G9oo+ztdKrv6Ps8zCBIh/TlREXcridGdUR1CY5+Pl" + + "ernPEzDmWf8jKrWgw20n5uf6tbBcR50yX3PM9KjNjZRppUEtIsFfCMYlenrkR5x8" + + "LAcFd9oPcuJ9PRnhMUKRs9kSUErI7OjEn/KUU0glK6hlUt23UxJ0MhhrAgMBAAGj" + + "ggMtMIIDKTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG" + + "AQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOBxEtfv1fdgy0C27Ir6Ftsq" + + "wSz9MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEB" + + "BGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0" + + "Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0" + + "Lm9yZy8wMQYDVR0RBCowKIIUYnVpbGRsb2dzLmNlbnRvcy5vcmeCEGNsb3VkLmNl" + + "bnRvcy5vcmcwgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEB" + + "MIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYI" + + "KwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGll" + + "ZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNl" + + "IHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xl" + + "dHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzCCAQMGCisGAQQB1nkCBAIEgfQEgfEA" + + "7wB1AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABZmHqH74AAAQD" + + "AEYwRAIgB0Dx4lsNHUz3gB2j4qCT2NSvo0AZSE485HiX/S5eKRcCIGZE+R+dRlLG" + + "Q83tXVx3DawCTTEu1T0+cVy6oZ0dcbfUAHYAY/Lbzeg7zCzPC3KEJ1drM6SNYXeP" + + "vXWmOLHHaFRL2I0AAAFmYeofbAAABAMARzBFAiEAg0zxDNDTHhWgK8Hp3TQwUC2q" + + "eeFQuDdsQSokgBdbfIwCIC1cC9nq2/HOnxPXgv9J8GlVkfxunzpVnxSNftay9sVf" + + "MA0GCSqGSIb3DQEBCwUAA4IBAQBGfnEP6dEN9t9NprhGL8A/8IdjBDonFLW0Z1Se" + + "jsT73GqNuLY1ixqZhswNKO4oKIsFBfOFB5Qt4BYLKkbIqdm/ktAAlpPIjndBC2Tg" + + "zn0SGXsceQOxkEmzMVYm6SpQ12JJUmmItRI7W3A1wmBmYi4pcpgNsHCkfmdzdZf6" + + "olpzTsc3AjZGue+LAJA0B/UOwh33uNZrRlQQxig/Do1WDLz5awvhTj/4KlJucFTD" + + "S2jt4OR6GZGSXYLwEiyG/aJv2CJVtQBCbqqeSNrwj9PkKJmAT3GswnCV+PDteiy5" + + "Oid9eucJAY9VhVQPoQXEKi7XdhC2PtNvQlLT6alx82mneywq"; + @Before public void setUp() { } @@ -103,4 +139,16 @@ public void testGetHeadersFromDetailsNonHttpHeaders() { Map headers = manager.getHeadersFromDetails(details); Assert.assertTrue(headers.isEmpty()); } + + @Test + public void testCertificateSanityValidCertificate() { + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE); + manager.certificateSanity(pretifiedCertificate); + } + + @Test(expected = CloudRuntimeException.class) + public void testCertificateSanityInvalidCertificate() { + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE + "xxx"); + manager.certificateSanity(pretifiedCertificate); + } } From 547b0085c669c42fc71390026215b7fef1cdb020 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Thu, 28 Mar 2019 01:02:37 -0300 Subject: [PATCH 2/6] Move upload direct download certificate logic to KVM plugin --- agent/src/com/cloud/agent/Agent.java | 46 ------- ...TemplateDirectDownloadCertificateCmd.java} | 6 +- ...etupDirectDownloadCertificateCommand.java} | 4 +- ...rectDownloadCertificateCommandWrapper.java | 130 ++++++++++++++++++ .../cloud/server/ManagementServerImpl.java | 4 +- .../download/DirectDownloadManagerImpl.java | 4 +- 6 files changed, 139 insertions(+), 55 deletions(-) rename api/src/org/apache/cloudstack/api/command/admin/direct/download/{UploadTemplateDirectDownloadCertificate.java => UploadTemplateDirectDownloadCertificateCmd.java} (93%) rename core/src/org/apache/cloudstack/agent/directdownload/{SetupDirectDownloadCertificate.java => SetupDirectDownloadCertificateCommand.java} (89%) create mode 100644 plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java index 98bf5405dcd0..e5fbdd7a2ede 100644 --- a/agent/src/com/cloud/agent/Agent.java +++ b/agent/src/com/cloud/agent/Agent.java @@ -39,7 +39,6 @@ import javax.naming.ConfigurationException; -import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate; import org.apache.cloudstack.agent.lb.SetupMSListAnswer; import org.apache.cloudstack.agent.lb.SetupMSListCommand; import org.apache.cloudstack.ca.PostCertificateRenewalCommand; @@ -630,8 +629,6 @@ protected void processRequest(final Request request, final Link link) { if (Host.Type.Routing.equals(_resource.getType())) { scheduleServicesRestartTask(); } - } else if (cmd instanceof SetupDirectDownloadCertificate) { - answer = setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd); } else if (cmd instanceof SetupMSListCommand) { answer = setupManagementServerList((SetupMSListCommand) cmd); } else { @@ -683,49 +680,6 @@ protected void processRequest(final Request request, final Link link) { } } - private Answer setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd) { - String certificate = cmd.getCertificate(); - String certificateName = cmd.getCertificateName(); - s_logger.info("Importing certificate " + certificateName + " into keystore"); - - final File agentFile = PropertiesUtil.findConfigFile("agent.properties"); - if (agentFile == null) { - return new Answer(cmd, false, "Failed to find agent.properties file"); - } - - final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; - - String cerFile = agentFile.getParent() + "/" + "CSCERTIFICATE-" + certificateName; - - s_logger.debug("Creating temporary certificate file into: " + cerFile); - int result = Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s", certificate, cerFile)); - if (result != 0) { - return new Answer(cmd, false, "Could not create the certificate file on path: " + cerFile); - } - - s_logger.debug("Retrieving keystore password"); - String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; - String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); - String privatePassword = Script.runSimpleBashScript(privatePasswordCmd); - if (org.apache.commons.lang.StringUtils.isBlank(privatePassword)) { - return new Answer(cmd, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME); - } - - s_logger.debug("Importing certificate from temporary file to keystore"); - String importCommandFormat = "keytool -importcert -file %s -keystore %s -alias '%s' -storepass '%s' -noprompt"; - String importCmd = String.format(importCommandFormat, cerFile, keyStoreFile, certificateName, privatePassword); - result = Script.runSimpleBashScriptForExitValue(importCmd); - - s_logger.debug("Cleaning up temporary certificate file"); - Script.runSimpleBashScript("rm -f " + cerFile); - - if (result != 0) { - return new Answer(cmd, false, "Could not import certificate to keystore: " + KeyStoreUtils.KS_FILENAME); - } - - return new Answer(cmd, true, "Certificate " + certificateName + " imported"); - } - public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) { final String keyStorePassword = cmd.getKeystorePassword(); final long validityDays = cmd.getValidityDays(); diff --git a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java similarity index 93% rename from api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java rename to api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java index 89c0c25c9ca0..416d26452e72 100755 --- a/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificate.java +++ b/api/src/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java @@ -35,19 +35,19 @@ import javax.inject.Inject; -@APICommand(name = UploadTemplateDirectDownloadCertificate.APINAME, +@APICommand(name = UploadTemplateDirectDownloadCertificateCmd.APINAME, description = "Upload a certificate for HTTPS direct template download on KVM hosts", responseObject = SuccessResponse.class, requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.11.0", authorized = {RoleType.Admin}) -public class UploadTemplateDirectDownloadCertificate extends BaseCmd { +public class UploadTemplateDirectDownloadCertificateCmd extends BaseCmd { @Inject DirectDownloadManager directDownloadManager; - private static final Logger LOG = Logger.getLogger(UploadTemplateDirectDownloadCertificate.class); + private static final Logger LOG = Logger.getLogger(UploadTemplateDirectDownloadCertificateCmd.class); public static final String APINAME = "uploadTemplateDirectDownloadCertificate"; @Parameter(name = ApiConstants.CERTIFICATE, type = BaseCmd.CommandType.STRING, required = true, length = 65535, diff --git a/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java b/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java similarity index 89% rename from core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java rename to core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java index 836b321f2278..641c535f11b8 100644 --- a/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificate.java +++ b/core/src/org/apache/cloudstack/agent/directdownload/SetupDirectDownloadCertificateCommand.java @@ -20,12 +20,12 @@ import com.cloud.agent.api.Command; -public class SetupDirectDownloadCertificate extends Command { +public class SetupDirectDownloadCertificateCommand extends Command { private String certificate; private String certificateName; - public SetupDirectDownloadCertificate(String certificate, String name) { + public SetupDirectDownloadCertificateCommand(String certificate, String name) { this.certificate = certificate; this.certificateName = name; } diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java new file mode 100644 index 000000000000..7a1e57f17b15 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java @@ -0,0 +1,130 @@ +// +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import org.apache.log4j.Logger; + +import java.io.File; +import java.io.FileNotFoundException; + +import static org.apache.commons.lang.StringUtils.isBlank; + +@ResourceWrapper(handles = SetupDirectDownloadCertificateCommand.class) +public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends CommandWrapper { + + private static final String temporaryCertFilePrefix = "CSCERTIFICATE"; + + private static final Logger s_logger = Logger.getLogger(LibvirtSetupDirectDownloadCertificateCommandWrapper.class); + + /** + * Retrieve agent.properties file + */ + private File getAgentPropertiesFile() throws FileNotFoundException { + final File agentFile = PropertiesUtil.findConfigFile("agent.properties"); + if (agentFile == null) { + throw new FileNotFoundException("Failed to find agent.properties file"); + } + return agentFile; + } + + /** + * Get the property 'keystore.passphrase' value from agent.properties file + */ + private String getKeystorePassword(File agentFile) { + s_logger.debug("Retrieving keystore password from agent.properties file"); + String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; + String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); + return Script.runSimpleBashScript(privatePasswordCmd); + } + + /** + * Get keystore path + */ + private String getKeyStoreFilePath(File agentFile) { + return agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; + } + + /** + * Import certificate from temporary file into keystore + */ + private void importCertificate(String tempCerFilePath, String keyStoreFile, String certificateName, String privatePassword) { + s_logger.debug("Importing certificate from temporary file to keystore"); + String importCommandFormat = "keytool -importcert -file %s -keystore %s -alias '%s' -storepass '%s' -noprompt"; + String importCmd = String.format(importCommandFormat, tempCerFilePath, keyStoreFile, certificateName, privatePassword); + int result = Script.runSimpleBashScriptForExitValue(importCmd); + if (result != 0) { + s_logger.debug("Certificate " + certificateName + " not imported as it already exist on keystore"); + } + } + + /** + * Create temporary file and return its path + */ + private String createTemporaryFile(File agentFile, String certificateName, String certificate) { + String tempCerFilePath = String.format("%s/%s-%s", + agentFile.getParent(), temporaryCertFilePrefix, certificateName); + s_logger.debug("Creating temporary certificate file into: " + tempCerFilePath); + int result = Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s", certificate, tempCerFilePath)); + if (result != 0) { + throw new CloudRuntimeException("Could not create the certificate file on path: " + tempCerFilePath); + } + return tempCerFilePath; + } + + /** + * Remove temporary file + */ + private void cleanupTemporaryFile(String temporaryFile) { + s_logger.debug("Cleaning up temporary certificate file"); + Script.runSimpleBashScript("rm -f " + temporaryFile); + } + + @Override + public Answer execute(SetupDirectDownloadCertificateCommand cmd, LibvirtComputingResource serverResource) { + String certificate = cmd.getCertificate(); + String certificateName = cmd.getCertificateName(); + + try { + File agentFile = getAgentPropertiesFile(); + String privatePassword = getKeystorePassword(agentFile); + if (isBlank(privatePassword)) { + return new Answer(cmd, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME); + } + + final String keyStoreFile = getKeyStoreFilePath(agentFile); + String temporaryFile = createTemporaryFile(agentFile, certificateName, certificate); + importCertificate(temporaryFile, keyStoreFile, certificateName, privatePassword); + cleanupTemporaryFile(temporaryFile); + } catch (FileNotFoundException | CloudRuntimeException e) { + s_logger.error("Error while setting up certificate " + certificateName, e); + return new Answer(cmd, false, e.getMessage()); + } + + return new Answer(cmd, true, "Certificate " + certificateName + " imported"); + } +} diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 38a84bb2dad3..872f9fd72968 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -65,7 +65,7 @@ import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd; 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.UploadTemplateDirectDownloadCertificate; +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.DeleteDomainCmd; import org.apache.cloudstack.api.command.admin.domain.ListDomainChildrenCmd; @@ -3051,7 +3051,7 @@ public List> getCommands() { cmdList.add(ReleasePodIpCmdByAdmin.class); cmdList.add(CreateManagementNetworkIpRangeCmd.class); cmdList.add(DeleteManagementNetworkIpRangeCmd.class); - cmdList.add(UploadTemplateDirectDownloadCertificate.class); + cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class); // Out-of-band management APIs for admins cmdList.add(EnableOutOfBandManagementForHostCmd.class); diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index 29f4b2c3b583..0d23ef48fd68 100755 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -62,7 +62,7 @@ import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand; -import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate; +import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; @@ -387,7 +387,7 @@ public boolean uploadCertificateToHosts(String certificateCer, String alias, Str * Upload and import certificate to hostId on keystore */ protected boolean uploadCertificate(String certificate, String certificateName, long hostId) { - SetupDirectDownloadCertificate cmd = new SetupDirectDownloadCertificate(certificate, certificateName); + SetupDirectDownloadCertificateCommand cmd = new SetupDirectDownloadCertificateCommand(certificate, certificateName); Answer answer = agentManager.easySend(hostId, cmd); if (answer == null || !answer.getResult()) { String msg = "Certificate " + certificateName + " could not be added to host " + hostId; From fc9e153f4b84ad8568404d437228354e53e04a21 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Wed, 3 Apr 2019 10:49:13 -0300 Subject: [PATCH 3/6] Extend unit test certificate expiration days --- .../DirectDownloadManagerImplTest.java | 57 +++++++------------ 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java index 7498c22f131e..5df5a29920eb 100644 --- a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java +++ b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java @@ -51,40 +51,25 @@ public class DirectDownloadManagerImplTest { private static final String HTTP_HEADER_2 = "Accept-Encoding"; private static final String HTTP_VALUE_2 = "gzip"; - private static final String VALID_CERTIFICATE = - "MIIGIDCCBQigAwIBAgISBBNfK9DprG2M0UhfMJI4EqQKMA0GCSqGSIb3DQEBCwUA" + - "MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD" + - "ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODEwMTEwNTU3MDVaFw0x" + - "OTAxMDkwNTU3MDVaMBsxGTAXBgNVBAMTEGNsb3VkLmNlbnRvcy5vcmcwggEiMA0G" + - "CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPPr8MWWyfIF7lXZ1tkazvDUBKK6Vs" + - "znDCndeuMqWAOc/vf7RN32bH5v+To9eHB/MPC0cCAMt2K38jC+c4ITgQlja75lDV" + - "G3sHzORQlD2GNmwiIL5TK6pc0NpmSrNBlMkhSlkaGeRPhNKDj7pIIDwRMbDxiaNE" + - "lZD/A/K/ugv/FkZT6M1gU17G9oo+ztdKrv6Ps8zCBIh/TlREXcridGdUR1CY5+Pl" + - "ernPEzDmWf8jKrWgw20n5uf6tbBcR50yX3PM9KjNjZRppUEtIsFfCMYlenrkR5x8" + - "LAcFd9oPcuJ9PRnhMUKRs9kSUErI7OjEn/KUU0glK6hlUt23UxJ0MhhrAgMBAAGj" + - "ggMtMIIDKTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsG" + - "AQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOBxEtfv1fdgy0C27Ir6Ftsq" + - "wSz9MB8GA1UdIwQYMBaAFKhKamMEfd265tE5t6ZFZe/zqOyhMG8GCCsGAQUFBwEB" + - "BGMwYTAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuaW50LXgzLmxldHNlbmNyeXB0" + - "Lm9yZzAvBggrBgEFBQcwAoYjaHR0cDovL2NlcnQuaW50LXgzLmxldHNlbmNyeXB0" + - "Lm9yZy8wMQYDVR0RBCowKIIUYnVpbGRsb2dzLmNlbnRvcy5vcmeCEGNsb3VkLmNl" + - "bnRvcy5vcmcwgf4GA1UdIASB9jCB8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEB" + - "MIHWMCYGCCsGAQUFBwIBFhpodHRwOi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYI" + - "KwYBBQUHAgIwgZ4MgZtUaGlzIENlcnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGll" + - "ZCB1cG9uIGJ5IFJlbHlpbmcgUGFydGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNl" + - "IHdpdGggdGhlIENlcnRpZmljYXRlIFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xl" + - "dHNlbmNyeXB0Lm9yZy9yZXBvc2l0b3J5LzCCAQMGCisGAQQB1nkCBAIEgfQEgfEA" + - "7wB1AG9Tdqwx8DEZ2JkApFEV/3cVHBHZAsEAKQaNsgiaN9kTAAABZmHqH74AAAQD" + - "AEYwRAIgB0Dx4lsNHUz3gB2j4qCT2NSvo0AZSE485HiX/S5eKRcCIGZE+R+dRlLG" + - "Q83tXVx3DawCTTEu1T0+cVy6oZ0dcbfUAHYAY/Lbzeg7zCzPC3KEJ1drM6SNYXeP" + - "vXWmOLHHaFRL2I0AAAFmYeofbAAABAMARzBFAiEAg0zxDNDTHhWgK8Hp3TQwUC2q" + - "eeFQuDdsQSokgBdbfIwCIC1cC9nq2/HOnxPXgv9J8GlVkfxunzpVnxSNftay9sVf" + - "MA0GCSqGSIb3DQEBCwUAA4IBAQBGfnEP6dEN9t9NprhGL8A/8IdjBDonFLW0Z1Se" + - "jsT73GqNuLY1ixqZhswNKO4oKIsFBfOFB5Qt4BYLKkbIqdm/ktAAlpPIjndBC2Tg" + - "zn0SGXsceQOxkEmzMVYm6SpQ12JJUmmItRI7W3A1wmBmYi4pcpgNsHCkfmdzdZf6" + - "olpzTsc3AjZGue+LAJA0B/UOwh33uNZrRlQQxig/Do1WDLz5awvhTj/4KlJucFTD" + - "S2jt4OR6GZGSXYLwEiyG/aJv2CJVtQBCbqqeSNrwj9PkKJmAT3GswnCV+PDteiy5" + - "Oid9eucJAY9VhVQPoQXEKi7XdhC2PtNvQlLT6alx82mneywq"; + private static final String VALID_HUNDRED_YEARS_CERTIFICATE = + "MIIDSzCCAjMCFExHvjXI7ffTZqcH4urbc6bqazFaMA0GCSqGSIb3DQEBCwUAMGEx" + + "CzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoM" + + "AkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNT" + + "MCAXDTE5MDQwMzEzMzgyOFoYDzIxMTkwMzEwMTMzODI4WjBhMQswCQYDVQQGEwJD" + + "UzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UE" + + "CwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZI" + + "hvcNAQEBBQADggEPADCCAQoCggEBANEuKuCOjnRJZtqBeKwZD4XNVDTKmxGLNJ3j" + + "6q71qlLa8quu115di6IxHkeerB9XnQMHHmqCv1qgpoWDuxA8uAra8T/teCvQjGRl" + + "lWDlBBajFZZ4Crsj0MxGIbuoHTQ4Ossyv3vJztbm+RZ79nTEA35xzQj7HxeFVyk+" + + "zqC6e4mMCzhI+UTKd3sOZBt8/y34egCv2UK9Lso9950dHnmlXYREd1j85Kestqjh" + + "tKntw3DLo5i8RLQ/11iW4Z+xOlL11ubhvJ0S8UAF5BU8pcLMNv+WztaoAAc3N+Yc" + + "WSTIXjQUtMT9TlHec8+NKlF9e62o4XMNHiaGEOf1idXC2URqqy8CAwEAATANBgkq" + + "hkiG9w0BAQsFAAOCAQEAFSAjv8dw0Lo8U7CsjWNlW/LBZdP9D54vx0kXOLyWjeYH" + + "7u4DTikoigjunm1lB5QPL2k5jSQEpTclcN313hMGCEMW9GZEtcSoUxqkiTEtKlyw" + + "cC/PO/NHHgDrp1Fg9yhUOLKXJyBp9bfjKtm2YqPNyrpTB5cLfjRp69Hx5G5KuKCm" + + "fAxcVdrfUluu6l+d4Y4FnuvS3rb9rDy/ES36SXczXNQFAzvI8ZuTlgxTRIvM184N" + + "GA0utaoFPJAzZ01HYlRSYmipHx6NZE7roTAC5wmT3R1jkFlfkw8LSBynsR6U6Vkw" + + "90kMmEH4NoYTV+mF4A0iY+NkEsuvnSsqheDknO/8OA=="; @Before public void setUp() { @@ -142,13 +127,13 @@ public void testGetHeadersFromDetailsNonHttpHeaders() { @Test public void testCertificateSanityValidCertificate() { - String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE); + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_HUNDRED_YEARS_CERTIFICATE); manager.certificateSanity(pretifiedCertificate); } @Test(expected = CloudRuntimeException.class) public void testCertificateSanityInvalidCertificate() { - String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE + "xxx"); + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_HUNDRED_YEARS_CERTIFICATE + "xxx"); manager.certificateSanity(pretifiedCertificate); } } From efe35d35cf0f5cbe2edee48e10670ea7db44c8c2 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Wed, 24 Apr 2019 20:14:00 -0300 Subject: [PATCH 4/6] Add marvin tests and command to revoke certificates --- ...eTemplateDirectDownloadCertificateCmd.java | 86 +++++++ .../download/DirectDownloadManager.java | 4 + ...evokeDirectDownloadCertificateCommand.java | 39 +++ ...evokeDirectDownloadCertificateWrapper.java | 100 ++++++++ .../cloud/server/ManagementServerImpl.java | 2 + .../download/DirectDownloadManagerImpl.java | 30 +++ .../DirectDownloadManagerImplTest.java | 42 ++-- .../integration/smoke/test_direct_download.py | 237 ++++++++++++++++++ 8 files changed, 519 insertions(+), 21 deletions(-) create mode 100644 api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java create mode 100644 core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java create mode 100644 plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java create mode 100644 test/integration/smoke/test_direct_download.py diff --git a/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java new file mode 100644 index 000000000000..ea33a1a3103e --- /dev/null +++ b/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java @@ -0,0 +1,86 @@ +// 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.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.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.direct.download.DirectDownloadManager; +import org.apache.log4j.Logger; + +import javax.inject.Inject; + +@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME, + description = "Revoke a certificate alias from a KVM host", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = true, + responseHasSensitiveInfo = true, + since = "4.11.3", + authorized = {RoleType.Admin}) +public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd { + + @Inject + DirectDownloadManager directDownloadManager; + + 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") + private String certificateAlias; + + @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true, + description = "Hypervisor type") + private String hypervisor; + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + if (!hypervisor.equalsIgnoreCase("kvm")) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only"); + } + SuccessResponse response = new SuccessResponse(getCommandName()); + try { + LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts"); + boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor); + response.setSuccess(result); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public String getCommandName() { + return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java index b3f0841a6e89..86f299aaf25d 100644 --- a/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java +++ b/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java @@ -22,4 +22,8 @@ public interface DirectDownloadManager extends DirectDownloadService, PluggableService { + /** + * Revoke direct download certificate with alias 'alias' from hosts of hypervisor type 'hypervisor' + */ + boolean revokeCertificateAlias(String certificateAlias, String hypervisor); } diff --git a/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java new file mode 100644 index 000000000000..5f5ed673eebd --- /dev/null +++ b/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java @@ -0,0 +1,39 @@ +// +// 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.agent.directdownload; + +import com.cloud.agent.api.Command; + +public class RevokeDirectDownloadCertificateCommand extends Command { + + private String certificateAlias; + + public RevokeDirectDownloadCertificateCommand(final String alias) { + this.certificateAlias = alias; + } + + public String getCertificateAlias() { + return certificateAlias; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java new file mode 100644 index 000000000000..d8c072e3d239 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java @@ -0,0 +1,100 @@ +// +// 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 com.cloud.hypervisor.kvm.resource.wrapper; + +import com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand; +import org.apache.cloudstack.utils.security.KeyStoreUtils; +import org.apache.log4j.Logger; + +import java.io.File; +import java.io.FileNotFoundException; + +import static org.apache.commons.lang.StringUtils.isBlank; + +@ResourceWrapper(handles = RevokeDirectDownloadCertificateCommand.class) +public class LibvirtRevokeDirectDownloadCertificateWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtRevokeDirectDownloadCertificateWrapper.class); + + /** + * Retrieve agent.properties file + */ + private File getAgentPropertiesFile() throws FileNotFoundException { + final File agentFile = PropertiesUtil.findConfigFile("agent.properties"); + if (agentFile == null) { + throw new FileNotFoundException("Failed to find agent.properties file"); + } + return agentFile; + } + + /** + * Get the property 'keystore.passphrase' value from agent.properties file + */ + private String getKeystorePassword(File agentFile) { + s_logger.debug("Retrieving keystore password from agent.properties file"); + String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; + String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); + return Script.runSimpleBashScript(privatePasswordCmd); + } + + /** + * Get keystore path + */ + private String getKeyStoreFilePath(File agentFile) { + return agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; + } + + @Override + public Answer execute(RevokeDirectDownloadCertificateCommand command, LibvirtComputingResource serverResource) { + String certificateAlias = command.getCertificateAlias(); + try { + File agentFile = getAgentPropertiesFile(); + String privatePassword = getKeystorePassword(agentFile); + if (isBlank(privatePassword)) { + return new Answer(command, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME); + } + + final String keyStoreFile = getKeyStoreFilePath(agentFile); + + String checkCmd = String.format("keytool -list -alias %s -keystore %s -storepass %s", + certificateAlias, keyStoreFile, privatePassword); + int existsCmdResult = Script.runSimpleBashScriptForExitValue(checkCmd); + if (existsCmdResult == 1) { + s_logger.debug("Certificate alias " + certificateAlias + " does not exist, no need to revoke it"); + } else { + String revokeCmd = String.format("keytool -delete -alias %s -keystore %s -storepass %s", + certificateAlias, keyStoreFile, privatePassword); + s_logger.debug("Revoking certificate alias " + certificateAlias + " from keystore " + keyStoreFile); + Script.runSimpleBashScriptForExitValue(revokeCmd); + } + } catch (FileNotFoundException | CloudRuntimeException e) { + s_logger.error("Error while setting up certificate " + certificateAlias, e); + return new Answer(command, false, e.getMessage()); + } + return new Answer(command); + } +} diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 872f9fd72968..a491ae51316c 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -65,6 +65,7 @@ import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd; 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.RevokeTemplateDirectDownloadCertificateCmd; 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.DeleteDomainCmd; @@ -3052,6 +3053,7 @@ public List> getCommands() { cmdList.add(CreateManagementNetworkIpRangeCmd.class); cmdList.add(DeleteManagementNetworkIpRangeCmd.class); cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class); + cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class); // Out-of-band management APIs for admins cmdList.add(EnableOutOfBandManagementForHostCmd.class); diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index 0d23ef48fd68..c21ea732d092 100755 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -25,6 +25,8 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.EventVO; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -62,6 +64,7 @@ import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand; +import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand; import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -400,4 +403,31 @@ protected boolean uploadCertificate(String certificate, String certificateName, s_logger.info("Certificate " + certificateName + " successfully uploaded to host: " + hostId); return true; } + + @Override + public boolean revokeCertificateAlias(String certificateAlias, String hypervisor) { + HypervisorType hypervisorType = HypervisorType.getType(hypervisor); + List hosts = getRunningHostsToUploadCertificate(hypervisorType); + s_logger.info("Attempting to revoke certificate alias: " + certificateAlias + " from " + hosts.size() + " hosts"); + if (CollectionUtils.isNotEmpty(hosts)) { + for (HostVO host : hosts) { + if (!revokeCertificateAliasFromHost(certificateAlias, host.getId())) { + s_logger.error("Could not revoke certificate from host: " + host.getName() + " (" + host.getUuid() + ")"); + throw new CloudRuntimeException("Revoking certificate " + certificateAlias + " failed from host: " + host.getUuid()); + } + } + } + return true; + } + + protected boolean revokeCertificateAliasFromHost(String alias, Long hostId) { + RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias); + try { + Answer answer = agentManager.send(hostId, cmd); + return answer != null && answer.getResult(); + } catch (AgentUnavailableException | OperationTimedoutException e) { + s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e); + } + return false; + } } diff --git a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java index 5df5a29920eb..5082500a9ae8 100644 --- a/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java +++ b/server/test/org/apache/cloudstack/direct/download/DirectDownloadManagerImplTest.java @@ -51,25 +51,25 @@ public class DirectDownloadManagerImplTest { private static final String HTTP_HEADER_2 = "Accept-Encoding"; private static final String HTTP_VALUE_2 = "gzip"; - private static final String VALID_HUNDRED_YEARS_CERTIFICATE = - "MIIDSzCCAjMCFExHvjXI7ffTZqcH4urbc6bqazFaMA0GCSqGSIb3DQEBCwUAMGEx" + - "CzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoM" + - "AkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNT" + - "MCAXDTE5MDQwMzEzMzgyOFoYDzIxMTkwMzEwMTMzODI4WjBhMQswCQYDVQQGEwJD" + - "UzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UE" + - "CwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZI" + - "hvcNAQEBBQADggEPADCCAQoCggEBANEuKuCOjnRJZtqBeKwZD4XNVDTKmxGLNJ3j" + - "6q71qlLa8quu115di6IxHkeerB9XnQMHHmqCv1qgpoWDuxA8uAra8T/teCvQjGRl" + - "lWDlBBajFZZ4Crsj0MxGIbuoHTQ4Ossyv3vJztbm+RZ79nTEA35xzQj7HxeFVyk+" + - "zqC6e4mMCzhI+UTKd3sOZBt8/y34egCv2UK9Lso9950dHnmlXYREd1j85Kestqjh" + - "tKntw3DLo5i8RLQ/11iW4Z+xOlL11ubhvJ0S8UAF5BU8pcLMNv+WztaoAAc3N+Yc" + - "WSTIXjQUtMT9TlHec8+NKlF9e62o4XMNHiaGEOf1idXC2URqqy8CAwEAATANBgkq" + - "hkiG9w0BAQsFAAOCAQEAFSAjv8dw0Lo8U7CsjWNlW/LBZdP9D54vx0kXOLyWjeYH" + - "7u4DTikoigjunm1lB5QPL2k5jSQEpTclcN313hMGCEMW9GZEtcSoUxqkiTEtKlyw" + - "cC/PO/NHHgDrp1Fg9yhUOLKXJyBp9bfjKtm2YqPNyrpTB5cLfjRp69Hx5G5KuKCm" + - "fAxcVdrfUluu6l+d4Y4FnuvS3rb9rDy/ES36SXczXNQFAzvI8ZuTlgxTRIvM184N" + - "GA0utaoFPJAzZ01HYlRSYmipHx6NZE7roTAC5wmT3R1jkFlfkw8LSBynsR6U6Vkw" + - "90kMmEH4NoYTV+mF4A0iY+NkEsuvnSsqheDknO/8OA=="; + private static final String VALID_CERTIFICATE = + "MIIDSzCCAjMCFDa0LoW+1O8/cEwCI0nIqfl8c1TLMA0GCSqGSIb3DQEBCwUAMGEx\n" + + "CzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoM\n" + + "AkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNT\n" + + "MCAXDTE5MDQyNDE1NTIzNVoYDzIwOTgwOTE1MTU1MjM1WjBhMQswCQYDVQQGEwJD\n" + + "UzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UE\n" + + "CwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZI\n" + + "hvcNAQEBBQADggEPADCCAQoCggEBAKstLRcMGCo6+2hojRMjEuuimnWp27yfYhDU\n" + + "w/Cj03MJe/KCOhwsDqX82QNIr/bNtLdFf2ZJEUQd08sLLlHeUy9y5aOcxt9SGx2j\n" + + "xolqO4MBL7BW3dklO0IvjaEfBeFP6udz8ajeVur/iPPZb2Edd0zlXuHvDozfQisv\n" + + "bpuJImnTUVx0ReCXP075PBGvlqQXW2uEht+E/w3H8/2rra3JFV6J5xc77KyQSq2t\n" + + "1+2ZU7PJiy/rppXf5rjTvNm6ydfag8/av7lcgs2ntdkK4koAmkmROhAwNonlL7cD\n" + + "xIC83cKOqOFiQXSwr1IgoLf7zBNafKoTlSb/ev6Zt18BXEMLGpkCAwEAATANBgkq\n" + + "hkiG9w0BAQsFAAOCAQEAVS5uWZRz2m3yx7EUQm47RTMW5WMXU4pI8D+N5WZ9xubY\n" + + "OqtU3r2OAYpfL/QO8iT7jcqNYGoDqe8ZjEaNvfxiTG8cOI6TSXhKBG6hjSaSFQSH\n" + + "OZ5mfstM36y/3ENFh6JCJ2ao1rgWSbfDRyAaHuvt6aCkaV6zRq2OMEgoJqZSgwxL\n" + + "QO230xa2hYgKXOePMVZyHFA2oKJtSOc3jCke9Y8zDUwm0McGdMRBD8tVB0rcaOqQ\n" + + "0PlDLjB9sQuhhLu8vjdgbznmPbUmMG7JN0yhT1eJbIX5ImXyh0DoTwiaGcYwW6Sq\n" + + "YodjXACsC37xaQXAPYBiaAs4iI80TJSx1DVFO1LV0g=="; @Before public void setUp() { @@ -127,13 +127,13 @@ public void testGetHeadersFromDetailsNonHttpHeaders() { @Test public void testCertificateSanityValidCertificate() { - String pretifiedCertificate = manager.getPretifiedCertificate(VALID_HUNDRED_YEARS_CERTIFICATE); + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE); manager.certificateSanity(pretifiedCertificate); } @Test(expected = CloudRuntimeException.class) public void testCertificateSanityInvalidCertificate() { - String pretifiedCertificate = manager.getPretifiedCertificate(VALID_HUNDRED_YEARS_CERTIFICATE + "xxx"); + String pretifiedCertificate = manager.getPretifiedCertificate(VALID_CERTIFICATE + "xxx"); manager.certificateSanity(pretifiedCertificate); } } diff --git a/test/integration/smoke/test_direct_download.py b/test/integration/smoke/test_direct_download.py new file mode 100644 index 000000000000..c3b8080b33fe --- /dev/null +++ b/test/integration/smoke/test_direct_download.py @@ -0,0 +1,237 @@ +# 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. +""" Test for Direct Downloads of Templates and ISOs +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import (cleanup_resources) +from marvin.lib.base import (ServiceOffering, + NetworkOffering, + Network, + Template, + VirtualMachine) +from marvin.lib.common import (get_pod, + get_zone) +from nose.plugins.attrib import attr +from marvin.cloudstackAPI import (uploadTemplateDirectDownloadCertificate, revokeTemplateDirectDownloadCertificate) +from marvin.lib.decoratorGenerators import skipTestIf + + +class TestUploadDirectDownloadCertificates(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestUploadDirectDownloadCertificates, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.dbclient = cls.testClient.getDbConnection() + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.pod = get_pod(cls.apiclient, cls.zone.id) + cls.services = cls.testClient.getParsedTestDataConfig() + + cls._cleanup = [] + cls.hypervisorNotSupported = False + if cls.hypervisor.lower() not in ['kvm', 'lxc']: + cls.hypervisorNotSupported = True + + if not cls.hypervisorNotSupported: + cls.certificates = { + "expired": "MIIDSTCCAjECFDi8s70TWFhwVN9cj67RJoAF99c8MA0GCSqGSIb3DQEBCwUAMGExCzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoMAkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNTMB4XDTE5MDQyNDE1NTQxM1oXDTE5MDQyMjE1NTQxM1owYTELMAkGA1UEBhMCQ1MxCzAJBgNVBAgMAkNTMQswCQYDVQQHDAJDUzELMAkGA1UECgwCQ1MxCzAJBgNVBAsMAkNTMQswCQYDVQQDDAJDUzERMA8GCSqGSIb3DQEJARYCQ1MwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrLS0XDBgqOvtoaI0TIxLropp1qdu8n2IQ1MPwo9NzCXvygjocLA6l/NkDSK/2zbS3RX9mSRFEHdPLCy5R3lMvcuWjnMbfUhsdo8aJajuDAS+wVt3ZJTtCL42hHwXhT+rnc/Go3lbq/4jz2W9hHXdM5V7h7w6M30IrL26biSJp01FcdEXglz9O+TwRr5akF1trhIbfhP8Nx/P9q62tyRVeiecXO+yskEqtrdftmVOzyYsv66aV3+a407zZusnX2oPP2r+5XILNp7XZCuJKAJpJkToQMDaJ5S+3A8SAvN3CjqjhYkF0sK9SIKC3+8wTWnyqE5Um/3r+mbdfAVxDCxqZAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAG/R9sJ2pFbu35MliIJIhWkwP7FeP/7gYCNvOXFt6vVGXmcOwuw9WGBxsmsGESQRB4+NnJFjyGQ1Ck+ps5XRRMizyvq6bCQxVuC5M+vYS4J0q8YoL0RJ20pN9iwTsosZjSEKmfUlVgsufqCG2nyusV71LSaQU6f/bylJcJkKwGUhThExh+PVLZ66H5cF4/SzuK6WzWnj5p6+YX8TP+qPUkXN1mapgVKfVMo6mqLsH+eLKH+zqdy5ZZ5znNSbJFgHufYbEFlutTaxHEvKNMEgMCFkFGiyPwRuD6oaPnZFquJLh/mBZOLogpxVD5v20AcUTANtbXSlPaqOnEQFcbiVCb8=", + "invalid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXX", + "valid": "MIIDSzCCAjMCFDa0LoW+1O8/cEwCI0nIqfl8c1TLMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNVBAYTAkNTMQswCQYDVQQIDAJDUzELMAkGA1UEBwwCQ1MxCzAJBgNVBAoMAkNTMQswCQYDVQQLDAJDUzELMAkGA1UEAwwCQ1MxETAPBgkqhkiG9w0BCQEWAkNTMCAXDTE5MDQyNDE1NTIzNVoYDzIwOTgwOTE1MTU1MjM1WjBhMQswCQYDVQQGEwJDUzELMAkGA1UECAwCQ1MxCzAJBgNVBAcMAkNTMQswCQYDVQQKDAJDUzELMAkGA1UECwwCQ1MxCzAJBgNVBAMMAkNTMREwDwYJKoZIhvcNAQkBFgJDUzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKstLRcMGCo6+2hojRMjEuuimnWp27yfYhDUw/Cj03MJe/KCOhwsDqX82QNIr/bNtLdFf2ZJEUQd08sLLlHeUy9y5aOcxt9SGx2jxolqO4MBL7BW3dklO0IvjaEfBeFP6udz8ajeVur/iPPZb2Edd0zlXuHvDozfQisvbpuJImnTUVx0ReCXP075PBGvlqQXW2uEht+E/w3H8/2rra3JFV6J5xc77KyQSq2t1+2ZU7PJiy/rppXf5rjTvNm6ydfag8/av7lcgs2ntdkK4koAmkmROhAwNonlL7cDxIC83cKOqOFiQXSwr1IgoLf7zBNafKoTlSb/ev6Zt18BXEMLGpkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAVS5uWZRz2m3yx7EUQm47RTMW5WMXU4pI8D+N5WZ9xubYOqtU3r2OAYpfL/QO8iT7jcqNYGoDqe8ZjEaNvfxiTG8cOI6TSXhKBG6hjSaSFQSHOZ5mfstM36y/3ENFh6JCJ2ao1rgWSbfDRyAaHuvt6aCkaV6zRq2OMEgoJqZSgwxLQO230xa2hYgKXOePMVZyHFA2oKJtSOc3jCke9Y8zDUwm0McGdMRBD8tVB0rcaOqQ0PlDLjB9sQuhhLu8vjdgbznmPbUmMG7JN0yhT1eJbIX5ImXyh0DoTwiaGcYwW6SqYodjXACsC37xaQXAPYBiaAs4iI80TJSx1DVFO1LV0g==" + } + + return + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @skipTestIf("hypervisorNotSupported") + @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], required_hardware="false") + def test_01_sanity_check_on_certificates(self): + """Test Verify certificates before uploading to KVM hosts + """ + + # Validate the following + # 1. Invalid certificates cannot be uploaded to hosts for direct downloads + # 2. Expired certificates cannot be uploaded to hosts for direct downloads + + cmd = uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd() + cmd.hypervisor = self.hypervisor + cmd.name = "marvin-test-verify-certs" + cmd.certificate = self.certificates["invalid"] + + invalid_cert_uploadFails = False + expired_cert_upload_fails = False + try: + self.apiclient.uploadTemplateDirectDownloadCertificate(cmd) + self.fail("Invalid certificate must not be uploaded") + except Exception as e: + invalid_cert_uploadFails = True + + cmd.certificate = self.certificates["expired"] + try: + self.apiclient.uploadTemplateDirectDownloadCertificate(cmd) + self.fail("Expired certificate must not be uploaded") + except Exception as e: + expired_cert_upload_fails = True + + self.assertTrue(invalid_cert_uploadFails and expired_cert_upload_fails, + "Invalid or expired certificates must not be uploaded") + return + + @skipTestIf("hypervisorNotSupported") + @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], required_hardware="false") + def test_02_upload_direct_download_certificates(self): + """Test Upload certificates to KVM hosts for direct download + """ + + # Validate the following + # 1. Valid certificates are uploaded to hosts + # 2. Revoke uploaded certificate from hosts + + cmd = uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd() + cmd.hypervisor = self.hypervisor + cmd.name = "marvin-test-verify-certs" + cmd.certificate = self.certificates["valid"] + + try: + self.apiclient.uploadTemplateDirectDownloadCertificate(cmd) + except Exception as e: + self.fail("Valid certificate must be uploaded") + + revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd() + revokecmd.hypervisor = self.hypervisor + revokecmd.name = cmd.name + + try: + self.apiclient.revokeTemplateDirectDownloadCertificate(cmd) + except Exception as e: + self.fail("Uploaded certificates should be revoked when needed") + + return + + +class TestDirectDownloadTemplates(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestDirectDownloadTemplates, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.dbclient = cls.testClient.getDbConnection() + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.pod = get_pod(cls.apiclient, cls.zone.id) + cls.services = cls.testClient.getParsedTestDataConfig() + + cls._cleanup = [] + cls.hypervisorNotSupported = False + if cls.hypervisor.lower() not in ['kvm', 'lxc']: + cls.hypervisorNotSupported = True + + if not cls.hypervisorNotSupported: + cls.services["test_templates"]["kvm"]["directdownload"] = "true" + cls.template = Template.register(cls.apiclient, cls.services["test_templates"]["kvm"], + zoneid=cls.zone.id, hypervisor=cls.hypervisor) + cls._cleanup.append(cls.template) + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + cls.services["virtual_machine"]["hypervisor"] = cls.hypervisor + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls._cleanup.append(cls.service_offering) + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["l2-network_offering"], + ) + cls.network_offering.update(cls.apiclient, state='Enabled') + cls.services["network"]["networkoffering"] = cls.network_offering.id + cls.l2_network = Network.create( + cls.apiclient, + cls.services["l2-network"], + zoneid=cls.zone.id, + networkofferingid=cls.network_offering.id + ) + cls._cleanup.append(cls.l2_network) + cls._cleanup.append(cls.network_offering) + return + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @skipTestIf("hypervisorNotSupported") + @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], required_hardware="false") + def test_01_deploy_vm_from_direct_download_template(self): + """Test Deploy VM from direct download template + """ + + # Validate the following + # 1. Register direct download template + # 2. Deploy VM from direct download template + + vm = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + serviceofferingid=self.service_offering.id, + networkids=self.l2_network.id + ) + self.assertEqual( + vm.state, + "Running", + "Check VM deployed from direct download template is running" + ) + self.cleanup.append(vm) + return From f953268d796e7e4cc423635f3ca47ea36ef36727 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Sun, 2 Jun 2019 21:04:01 -0300 Subject: [PATCH 5/6] Review comments --- ...evokeDirectDownloadCertificateWrapper.java | 16 +++++++++++----- ...rectDownloadCertificateCommandWrapper.java | 14 ++++++++++---- .../download/DirectDownloadManagerImpl.java | 19 +++++++++++-------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java index d8c072e3d239..e942dcbad00e 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java @@ -32,6 +32,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import static org.apache.commons.lang.StringUtils.isBlank; @@ -55,10 +56,15 @@ private File getAgentPropertiesFile() throws FileNotFoundException { * Get the property 'keystore.passphrase' value from agent.properties file */ private String getKeystorePassword(File agentFile) { - s_logger.debug("Retrieving keystore password from agent.properties file"); - String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; - String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); - return Script.runSimpleBashScript(privatePasswordCmd); + String pass = null; + if (agentFile != null) { + try { + pass = PropertiesUtil.loadFromFile(agentFile).getProperty(KeyStoreUtils.KS_PASSPHRASE_PROPERTY); + } catch (IOException e) { + s_logger.error("Could not get 'keystore.passphrase' property value due to: " + e.getMessage()); + } + } + return pass; } /** @@ -84,7 +90,7 @@ public Answer execute(RevokeDirectDownloadCertificateCommand command, LibvirtCom certificateAlias, keyStoreFile, privatePassword); int existsCmdResult = Script.runSimpleBashScriptForExitValue(checkCmd); if (existsCmdResult == 1) { - s_logger.debug("Certificate alias " + certificateAlias + " does not exist, no need to revoke it"); + s_logger.error("Certificate alias " + certificateAlias + " does not exist, no need to revoke it"); } else { String revokeCmd = String.format("keytool -delete -alias %s -keystore %s -storepass %s", certificateAlias, keyStoreFile, privatePassword); diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java index 7a1e57f17b15..97035eedd221 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java @@ -31,6 +31,7 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.IOException; import static org.apache.commons.lang.StringUtils.isBlank; @@ -56,10 +57,15 @@ private File getAgentPropertiesFile() throws FileNotFoundException { * Get the property 'keystore.passphrase' value from agent.properties file */ private String getKeystorePassword(File agentFile) { - s_logger.debug("Retrieving keystore password from agent.properties file"); - String privatePasswordFormat = "sed -n '/keystore.passphrase/p' '%s' 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null"; - String privatePasswordCmd = String.format(privatePasswordFormat, agentFile.getAbsolutePath()); - return Script.runSimpleBashScript(privatePasswordCmd); + String pass = null; + if (agentFile != null) { + try { + pass = PropertiesUtil.loadFromFile(agentFile).getProperty(KeyStoreUtils.KS_PASSPHRASE_PROPERTY); + } catch (IOException e) { + s_logger.error("Could not get 'keystore.passphrase' property value due to: " + e.getMessage()); + } + } + return pass; } /** diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index c21ea732d092..cca1c0f0f95e 100755 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -357,11 +357,12 @@ protected void certificateSanity(String certificatePem) { try { x509Cert.checkValidity(); } catch (CertificateExpiredException | CertificateNotYetValidException e) { - e.printStackTrace(); - throw new CloudRuntimeException("Certificate is invalid. Please provide a valid certificate. Error: " + e.getMessage()); + String msg = "Certificate is invalid. Please provide a valid certificate. Error: " + e.getMessage(); + s_logger.error(msg); + throw new CloudRuntimeException(msg); } if (x509Cert.getSubjectDN() != null) { - s_logger.info("Valid certificate for domain name: " + x509Cert.getSubjectDN().getName()); + s_logger.debug("Valid certificate for domain name: " + x509Cert.getSubjectDN().getName()); } } } @@ -378,8 +379,9 @@ public boolean uploadCertificateToHosts(String certificateCer, String alias, Str if (CollectionUtils.isNotEmpty(hosts)) { for (HostVO host : hosts) { if (!uploadCertificate(certificatePem, alias, host.getId())) { - s_logger.error("Could not upload certificate on host: " + host.getName() + " (" + host.getUuid() + ")"); - throw new CloudRuntimeException("Uploading certificate " + alias + " failed on host: " + host.getUuid()); + String msg = "Could not upload certificate " + alias + " on host: " + host.getName() + " (" + host.getUuid() + ")"; + s_logger.error(msg); + throw new CloudRuntimeException(msg); } } } @@ -397,7 +399,7 @@ protected boolean uploadCertificate(String certificate, String certificateName, if (answer != null) { msg += " due to: " + answer.getDetails(); } - s_logger.info(msg); + s_logger.error(msg); return false; } s_logger.info("Certificate " + certificateName + " successfully uploaded to host: " + hostId); @@ -412,8 +414,9 @@ public boolean revokeCertificateAlias(String certificateAlias, String hypervisor if (CollectionUtils.isNotEmpty(hosts)) { for (HostVO host : hosts) { if (!revokeCertificateAliasFromHost(certificateAlias, host.getId())) { - s_logger.error("Could not revoke certificate from host: " + host.getName() + " (" + host.getUuid() + ")"); - throw new CloudRuntimeException("Revoking certificate " + certificateAlias + " failed from host: " + host.getUuid()); + String msg = "Could not revoke certificate from host: " + host.getName() + " (" + host.getUuid() + ")"; + s_logger.error(msg); + throw new CloudRuntimeException(msg); } } } From 49b24ee2f2e6acc185df4ea22ec71b95f739ab89 Mon Sep 17 00:00:00 2001 From: nvazquez Date: Mon, 3 Jun 2019 18:07:30 -0300 Subject: [PATCH 6/6] Do not include revoke certificates API --- ...eTemplateDirectDownloadCertificateCmd.java | 86 -------------- .../download/DirectDownloadManager.java | 4 - ...evokeDirectDownloadCertificateCommand.java | 39 ------- ...evokeDirectDownloadCertificateWrapper.java | 106 ------------------ .../cloud/server/ManagementServerImpl.java | 2 - .../download/DirectDownloadManagerImpl.java | 30 ----- .../integration/smoke/test_direct_download.py | 12 +- 7 files changed, 1 insertion(+), 278 deletions(-) delete mode 100644 api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java delete mode 100644 core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java delete mode 100644 plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java diff --git a/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java b/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java deleted file mode 100644 index ea33a1a3103e..000000000000 --- a/api/src/org/apache/cloudstack/api/command/admin/direct/download/RevokeTemplateDirectDownloadCertificateCmd.java +++ /dev/null @@ -1,86 +0,0 @@ -// 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.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.SuccessResponse; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.direct.download.DirectDownloadManager; -import org.apache.log4j.Logger; - -import javax.inject.Inject; - -@APICommand(name = RevokeTemplateDirectDownloadCertificateCmd.APINAME, - description = "Revoke a certificate alias from a KVM host", - responseObject = SuccessResponse.class, - requestHasSensitiveInfo = true, - responseHasSensitiveInfo = true, - since = "4.11.3", - authorized = {RoleType.Admin}) -public class RevokeTemplateDirectDownloadCertificateCmd extends BaseCmd { - - @Inject - DirectDownloadManager directDownloadManager; - - 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") - private String certificateAlias; - - @Parameter(name = ApiConstants.HYPERVISOR, type = BaseCmd.CommandType.STRING, required = true, - description = "Hypervisor type") - private String hypervisor; - - @Override - public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { - if (!hypervisor.equalsIgnoreCase("kvm")) { - throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Currently supporting KVM hosts only"); - } - SuccessResponse response = new SuccessResponse(getCommandName()); - try { - LOG.debug("Revoking certificate " + certificateAlias + " from " + hypervisor + " hosts"); - boolean result = directDownloadManager.revokeCertificateAlias(certificateAlias, hypervisor); - response.setSuccess(result); - setResponseObject(response); - } catch (Exception e) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); - } - } - - @Override - public String getCommandName() { - return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX; - } - - @Override - public long getEntityOwnerId() { - return CallContext.current().getCallingAccount().getId(); - } -} diff --git a/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java b/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java index 86f299aaf25d..b3f0841a6e89 100644 --- a/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java +++ b/api/src/org/apache/cloudstack/direct/download/DirectDownloadManager.java @@ -22,8 +22,4 @@ public interface DirectDownloadManager extends DirectDownloadService, PluggableService { - /** - * Revoke direct download certificate with alias 'alias' from hosts of hypervisor type 'hypervisor' - */ - boolean revokeCertificateAlias(String certificateAlias, String hypervisor); } diff --git a/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java b/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java deleted file mode 100644 index 5f5ed673eebd..000000000000 --- a/core/src/org/apache/cloudstack/agent/directdownload/RevokeDirectDownloadCertificateCommand.java +++ /dev/null @@ -1,39 +0,0 @@ -// -// 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.agent.directdownload; - -import com.cloud.agent.api.Command; - -public class RevokeDirectDownloadCertificateCommand extends Command { - - private String certificateAlias; - - public RevokeDirectDownloadCertificateCommand(final String alias) { - this.certificateAlias = alias; - } - - public String getCertificateAlias() { - return certificateAlias; - } - - @Override - public boolean executeInSequence() { - return false; - } -} diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java deleted file mode 100644 index e942dcbad00e..000000000000 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java +++ /dev/null @@ -1,106 +0,0 @@ -// -// 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 com.cloud.hypervisor.kvm.resource.wrapper; - -import com.cloud.agent.api.Answer; -import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; -import com.cloud.resource.CommandWrapper; -import com.cloud.resource.ResourceWrapper; -import com.cloud.utils.PropertiesUtil; -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.script.Script; -import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand; -import org.apache.cloudstack.utils.security.KeyStoreUtils; -import org.apache.log4j.Logger; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; - -import static org.apache.commons.lang.StringUtils.isBlank; - -@ResourceWrapper(handles = RevokeDirectDownloadCertificateCommand.class) -public class LibvirtRevokeDirectDownloadCertificateWrapper extends CommandWrapper { - - private static final Logger s_logger = Logger.getLogger(LibvirtRevokeDirectDownloadCertificateWrapper.class); - - /** - * Retrieve agent.properties file - */ - private File getAgentPropertiesFile() throws FileNotFoundException { - final File agentFile = PropertiesUtil.findConfigFile("agent.properties"); - if (agentFile == null) { - throw new FileNotFoundException("Failed to find agent.properties file"); - } - return agentFile; - } - - /** - * Get the property 'keystore.passphrase' value from agent.properties file - */ - private String getKeystorePassword(File agentFile) { - String pass = null; - if (agentFile != null) { - try { - pass = PropertiesUtil.loadFromFile(agentFile).getProperty(KeyStoreUtils.KS_PASSPHRASE_PROPERTY); - } catch (IOException e) { - s_logger.error("Could not get 'keystore.passphrase' property value due to: " + e.getMessage()); - } - } - return pass; - } - - /** - * Get keystore path - */ - private String getKeyStoreFilePath(File agentFile) { - return agentFile.getParent() + "/" + KeyStoreUtils.KS_FILENAME; - } - - @Override - public Answer execute(RevokeDirectDownloadCertificateCommand command, LibvirtComputingResource serverResource) { - String certificateAlias = command.getCertificateAlias(); - try { - File agentFile = getAgentPropertiesFile(); - String privatePassword = getKeystorePassword(agentFile); - if (isBlank(privatePassword)) { - return new Answer(command, false, "No password found for keystore: " + KeyStoreUtils.KS_FILENAME); - } - - final String keyStoreFile = getKeyStoreFilePath(agentFile); - - String checkCmd = String.format("keytool -list -alias %s -keystore %s -storepass %s", - certificateAlias, keyStoreFile, privatePassword); - int existsCmdResult = Script.runSimpleBashScriptForExitValue(checkCmd); - if (existsCmdResult == 1) { - s_logger.error("Certificate alias " + certificateAlias + " does not exist, no need to revoke it"); - } else { - String revokeCmd = String.format("keytool -delete -alias %s -keystore %s -storepass %s", - certificateAlias, keyStoreFile, privatePassword); - s_logger.debug("Revoking certificate alias " + certificateAlias + " from keystore " + keyStoreFile); - Script.runSimpleBashScriptForExitValue(revokeCmd); - } - } catch (FileNotFoundException | CloudRuntimeException e) { - s_logger.error("Error while setting up certificate " + certificateAlias, e); - return new Answer(command, false, e.getMessage()); - } - return new Answer(command); - } -} diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index a491ae51316c..872f9fd72968 100644 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -65,7 +65,6 @@ import org.apache.cloudstack.api.command.admin.config.ListHypervisorCapabilitiesCmd; 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.RevokeTemplateDirectDownloadCertificateCmd; 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.DeleteDomainCmd; @@ -3053,7 +3052,6 @@ public List> getCommands() { cmdList.add(CreateManagementNetworkIpRangeCmd.class); cmdList.add(DeleteManagementNetworkIpRangeCmd.class); cmdList.add(UploadTemplateDirectDownloadCertificateCmd.class); - cmdList.add(RevokeTemplateDirectDownloadCertificateCmd.class); // Out-of-band management APIs for admins cmdList.add(EnableOutOfBandManagementForHostCmd.class); diff --git a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java index cca1c0f0f95e..d2aa67540f32 100755 --- a/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java +++ b/server/src/org/apache/cloudstack/direct/download/DirectDownloadManagerImpl.java @@ -25,8 +25,6 @@ import com.cloud.event.ActionEventUtils; import com.cloud.event.EventTypes; import com.cloud.event.EventVO; -import com.cloud.exception.AgentUnavailableException; -import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -64,7 +62,6 @@ import org.apache.cloudstack.agent.directdownload.MetalinkDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.NfsDirectDownloadCommand; import org.apache.cloudstack.agent.directdownload.HttpsDirectDownloadCommand; -import org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand; import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -406,31 +403,4 @@ protected boolean uploadCertificate(String certificate, String certificateName, return true; } - @Override - public boolean revokeCertificateAlias(String certificateAlias, String hypervisor) { - HypervisorType hypervisorType = HypervisorType.getType(hypervisor); - List hosts = getRunningHostsToUploadCertificate(hypervisorType); - s_logger.info("Attempting to revoke certificate alias: " + certificateAlias + " from " + hosts.size() + " hosts"); - if (CollectionUtils.isNotEmpty(hosts)) { - for (HostVO host : hosts) { - if (!revokeCertificateAliasFromHost(certificateAlias, host.getId())) { - String msg = "Could not revoke certificate from host: " + host.getName() + " (" + host.getUuid() + ")"; - s_logger.error(msg); - throw new CloudRuntimeException(msg); - } - } - } - return true; - } - - protected boolean revokeCertificateAliasFromHost(String alias, Long hostId) { - RevokeDirectDownloadCertificateCommand cmd = new RevokeDirectDownloadCertificateCommand(alias); - try { - Answer answer = agentManager.send(hostId, cmd); - return answer != null && answer.getResult(); - } catch (AgentUnavailableException | OperationTimedoutException e) { - s_logger.error("Error revoking certificate " + alias + " from host " + hostId, e); - } - return false; - } } diff --git a/test/integration/smoke/test_direct_download.py b/test/integration/smoke/test_direct_download.py index c3b8080b33fe..65117f97f8c0 100644 --- a/test/integration/smoke/test_direct_download.py +++ b/test/integration/smoke/test_direct_download.py @@ -27,7 +27,7 @@ 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 from marvin.lib.decoratorGenerators import skipTestIf @@ -120,7 +120,6 @@ def test_02_upload_direct_download_certificates(self): # Validate the following # 1. Valid certificates are uploaded to hosts - # 2. Revoke uploaded certificate from hosts cmd = uploadTemplateDirectDownloadCertificate.uploadTemplateDirectDownloadCertificateCmd() cmd.hypervisor = self.hypervisor @@ -132,15 +131,6 @@ def test_02_upload_direct_download_certificates(self): except Exception as e: self.fail("Valid certificate must be uploaded") - revokecmd = revokeTemplateDirectDownloadCertificate.revokeTemplateDirectDownloadCertificateCmd() - revokecmd.hypervisor = self.hypervisor - revokecmd.name = cmd.name - - try: - self.apiclient.revokeTemplateDirectDownloadCertificate(cmd) - except Exception as e: - self.fail("Uploaded certificates should be revoked when needed") - return