From 1d45b75298ec8458565f14ddbcc4d63f7c493ea2 Mon Sep 17 00:00:00 2001 From: Nitin Mehta Date: Thu, 24 Apr 2014 17:20:41 -0700 Subject: [PATCH] CLOUDSTACK-6499: Made changes so that uploading custom certificate works for ssvm. 1. Reboot ssvm only when private key is passed meaning the server cert is passed. This is because while uploading the server cert is the last to be uploaded. And we want to propagate the entire chain once uploading is done. 2. Change the SecStorageSetupCommand sent to ssvm so that it also carries the root cert apart from having the chain and the server cert and key. 3. Change ssvm agent code to be able to configure root cert to the java key store. 4. Change ssvm configure ssl script to insert the chain certs correctly. 5. Fix order of chain certificates for apache webserver in SSVM 6. Remove double encoding and decoding for uploadCustomCertificate API from UI and server code respectively, so that API call without UI works fine 7. Java 1.7 - disable using SNI since copyTemplate doesnt work for SSL. --- .../security/keystore/KeystoreDao.java | 2 ++ .../security/keystore/KeystoreDaoImpl.java | 19 +++++++++++ .../security/keystore/KeystoreManager.java | 11 +++++-- .../keystore/KeystoreManagerImpl.java | 19 +++++++---- .../cloud/server/ManagementServerImpl.java | 26 +++++---------- .../resource/NfsSecondaryStorageResource.java | 33 +++++++++++++++++-- systemvm/scripts/_run.sh | 2 +- systemvm/scripts/config_ssl.sh | 15 +++++++-- ui/scripts/ui-custom/physicalResources.js | 4 +-- 9 files changed, 98 insertions(+), 33 deletions(-) diff --git a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDao.java b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDao.java index e60e4b004c6..67eea928864 100644 --- a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDao.java +++ b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDao.java @@ -28,4 +28,6 @@ public interface KeystoreDao extends GenericDao { void save(String alias, String certificate, Integer index, String domainSuffix); List findCertChain(); + + List findCertChain(String domainSuffix); } diff --git a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDaoImpl.java b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDaoImpl.java index cd24611a86e..b856582ac11 100644 --- a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDaoImpl.java +++ b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreDaoImpl.java @@ -38,6 +38,7 @@ import com.cloud.utils.exception.CloudRuntimeException; public class KeystoreDaoImpl extends GenericDaoBase implements KeystoreDao { protected final SearchBuilder FindByNameSearch; protected final SearchBuilder CertChainSearch; + protected final SearchBuilder CertChainSearchForDomainSuffix; public KeystoreDaoImpl() { FindByNameSearch = createSearchBuilder(); @@ -47,6 +48,11 @@ public class KeystoreDaoImpl extends GenericDaoBase implements CertChainSearch = createSearchBuilder(); CertChainSearch.and("key", CertChainSearch.entity().getKey(), Op.NULL); CertChainSearch.done(); + + CertChainSearchForDomainSuffix = createSearchBuilder(); + CertChainSearchForDomainSuffix.and("key", CertChainSearchForDomainSuffix.entity().getKey(), Op.NULL); + CertChainSearchForDomainSuffix.and("domainSuffix", CertChainSearchForDomainSuffix.entity().getDomainSuffix(), Op.EQ); + CertChainSearchForDomainSuffix.done(); } @Override @@ -64,6 +70,19 @@ public class KeystoreDaoImpl extends GenericDaoBase implements return ks; } + @Override + public List findCertChain(String domainSuffix) { + SearchCriteria sc = CertChainSearchForDomainSuffix.create(); + sc.setParameters("domainSuffix", domainSuffix); + List ks = listBy(sc); + Collections.sort(ks, new Comparator() { public int compare(Object o1, Object o2) { + Integer seq1 = ((KeystoreVO)o1).getIndex(); + Integer seq2 = ((KeystoreVO)o2).getIndex(); + return seq1.compareTo(seq2); + }}); + return ks; + } + @Override public KeystoreVO findByName(String name) { assert (name != null); diff --git a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java index 3b99947c4b5..c44347c85ef 100644 --- a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java +++ b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManager.java @@ -28,15 +28,18 @@ public interface KeystoreManager extends Manager { private String privCert; @LogLevel(Log4jLevel.Off) private String certChain; + @LogLevel(Log4jLevel.Off) + private String rootCACert; public Certificates() { } - public Certificates(String prvKey, String privCert, String certChain) { - privKey = prvKey; + public Certificates(String prvKey, String privCert, String certChain, String rootCACert) { + this.privKey = prvKey; this.privCert = privCert; this.certChain = certChain; + this.rootCACert = rootCACert; } public String getPrivKey() { @@ -50,6 +53,10 @@ public interface KeystoreManager extends Manager { public String getCertChain() { return certChain; } + + public String getRootCACert() { + return rootCACert; + } } boolean validateCertificate(String certificate, String key, String domainSuffix); diff --git a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java index 306083492af..374c080413f 100644 --- a/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java +++ b/framework/security/src/org/apache/cloudstack/framework/security/keystore/KeystoreManagerImpl.java @@ -23,6 +23,7 @@ import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.spec.InvalidKeySpecException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,6 +31,7 @@ import java.util.regex.Pattern; import javax.ejb.Local; import javax.inject.Inject; +import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.springframework.stereotype.Component; @@ -129,17 +131,22 @@ public class KeystoreManagerImpl extends ManagerBase implements KeystoreManager } String prvKey = ksVo.getKey(); String prvCert = ksVo.getCertificate(); + String domainSuffix = ksVo.getDomainSuffix(); String certChain = null; - List certchains = _ksDao.findCertChain(); + String rootCert = null; + List certchains = _ksDao.findCertChain(domainSuffix); if (certchains.size() > 0) { - StringBuilder chains = new StringBuilder(); + ArrayList chains = new ArrayList(); for (KeystoreVO cert : certchains) { - chains.append(cert.getCertificate()); - chains.append("\n"); + if (chains.size() == 0) {// For the first time it will be length 0 + rootCert = cert.getCertificate(); + } + chains.add(cert.getCertificate()); } - certChain = chains.toString(); + Collections.reverse(chains); + certChain = StringUtils.join(chains, "\n"); } - Certificates certs = new Certificates(prvKey, prvCert, certChain); + Certificates certs = new Certificates(prvKey, prvCert, certChain, rootCert); return certs; } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index e33c7c71086..081145f3295 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -16,9 +16,7 @@ // under the License. package com.cloud.server; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; -import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; @@ -3447,16 +3445,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe String certificate = cmd.getCertificate(); String key = cmd.getPrivateKey(); - try { - if (certificate != null) { - certificate = URLDecoder.decode(certificate, "UTF-8"); - } - if (key != null) { - key = URLDecoder.decode(key, "UTF-8"); - } - } catch (UnsupportedEncodingException e) { - } finally { - } if (cmd.getPrivateKey() != null && !_ksMgr.validateCertificate(certificate, key, cmd.getDomainSuffix())) { throw new InvalidParameterValueException("Failed to pass certificate validation check"); @@ -3464,16 +3452,20 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe if (cmd.getPrivateKey() != null) { _ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, certificate, key, cmd.getDomainSuffix()); + + // Reboot ssvm here since private key is present - meaning server cert being passed + List alreadyRunning = _secStorageVmDao.getSecStorageVmListInStates(null, State.Running, State.Migrating, State.Starting); + for (SecondaryStorageVmVO ssVmVm : alreadyRunning) { + _secStorageVmMgr.rebootSecStorageVm(ssVmVm.getId()); + } } else { _ksMgr.saveCertificate(cmd.getAlias(), certificate, cmd.getCertIndex(), cmd.getDomainSuffix()); } _consoleProxyMgr.setManagementState(ConsoleProxyManagementState.ResetSuspending); - List alreadyRunning = _secStorageVmDao.getSecStorageVmListInStates(null, State.Running, State.Migrating, State.Starting); - for (SecondaryStorageVmVO ssVmVm : alreadyRunning) { - _secStorageVmMgr.rebootSecStorageVm(ssVmVm.getId()); - } - return "Certificate has been updated, we will stop all running console proxy VMs and secondary storage VMs to propagate the new certificate, please give a few minutes for console access service to be up again"; + return "Certificate has been successfully updated, if its the server certificate we would reboot all " + + "running console proxy VMs and secondary storage VMs to propagate the new certificate, " + + "please give a few minutes for console access and storage services service to be up and working again"; } @Override diff --git a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java index f35b3f544ef..6927f028cf2 100755 --- a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java +++ b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java @@ -1188,6 +1188,8 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S } else { String prvKey = certs.getPrivKey(); String pubCert = certs.getPrivCert(); + String certChain = certs.getCertChain(); + String rootCACert = certs.getRootCACert(); try { File prvKeyFile = File.createTempFile("prvkey", null); @@ -1203,10 +1205,34 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S out.write(pubCert); out.close(); - configureSSL(prvkeyPath, pubCertFilePath, null); + String certChainFilePath = null, rootCACertFilePath = null; + File certChainFile = null, rootCACertFile = null; + if(certChain != null){ + certChainFile = File.createTempFile("certchain", null); + certChainFilePath = certChainFile.getAbsolutePath(); + out = new BufferedWriter(new FileWriter(certChainFile)); + out.write(certChain); + out.close(); + } + + if(rootCACert != null){ + rootCACertFile = File.createTempFile("rootcert", null); + rootCACertFilePath = rootCACertFile.getAbsolutePath(); + out = new BufferedWriter(new FileWriter(rootCACertFile)); + out.write(rootCACert); + out.close(); + } + + configureSSL(prvkeyPath, pubCertFilePath, certChainFilePath, rootCACertFilePath); prvKeyFile.delete(); pubCertFile.delete(); + if(certChainFile != null){ + certChainFile.delete(); + } + if(rootCACertFile != null){ + rootCACertFile.delete(); + } } catch (IOException e) { s_logger.debug("Failed to config ssl: " + e.toString()); @@ -2064,7 +2090,7 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S } } - private void configureSSL(String prvkeyPath, String prvCertPath, String certChainPath) { + private void configureSSL(String prvkeyPath, String prvCertPath, String certChainPath, String rootCACert) { if (!_inSystemVM) { return; } @@ -2076,6 +2102,9 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S if (certChainPath != null) { command.add("-t", certChainPath); } + if (rootCACert != null) { + command.add("-u", rootCACert); + } String result = command.execute(); if (result != null) { s_logger.warn("Unable to configure httpd to use ssl"); diff --git a/systemvm/scripts/_run.sh b/systemvm/scripts/_run.sh index 1a37c7d372c..3792261c143 100755 --- a/systemvm/scripts/_run.sh +++ b/systemvm/scripts/_run.sh @@ -68,4 +68,4 @@ if [ "$(uname -m | grep '64')" == "" ]; then fi fi -java -Djavax.net.ssl.trustStore=./certs/realhostip.keystore -Dlog.home=$LOGHOME -mx${maxmem}m -cp $CP com.cloud.agent.AgentShell $keyvalues $@ +java -Djavax.net.ssl.trustStore=./certs/realhostip.keystore -Djsse.enableSNIExtension=false -Dlog.home=$LOGHOME -mx${maxmem}m -cp $CP com.cloud.agent.AgentShell $keyvalues $@ diff --git a/systemvm/scripts/config_ssl.sh b/systemvm/scripts/config_ssl.sh index e4747872693..69710554f1e 100755 --- a/systemvm/scripts/config_ssl.sh +++ b/systemvm/scripts/config_ssl.sh @@ -24,6 +24,7 @@ help() { printf " -k path of private key\n" printf " -p path of certificate of public key\n" printf " -t path of certificate chain\n" + printf " -u path of root ca certificate \n" } @@ -53,6 +54,10 @@ config_apache2_conf() { sed -i -e "s/NameVirtualHost .*:80/NameVirtualHost $ip:80/g" /etc/apache2/ports.conf sed -i 's/ssl-cert-snakeoil.key/cert_apache.key/' /etc/apache2/sites-available/default-ssl sed -i 's/ssl-cert-snakeoil.pem/cert_apache.crt/' /etc/apache2/sites-available/default-ssl + if [ -f /etc/ssl/certs/cert_apache_chain.crt ] + then + sed -i -e "s/#SSLCertificateChainFile.*/SSLCertificateChainFile \/etc\/ssl\/certs\/cert_apache_chain.crt/" /etc/apache2/sites-available/default-ssl + fi } copy_certs() { @@ -88,12 +93,13 @@ cccflag= customPrivKey=$(dirname $0)/certs/realhostip.key customPrivCert=$(dirname $0)/certs/realhostip.crt customCertChain= +customCACert= publicIp= hostName= keyStore=$(dirname $0)/certs/realhostip.keystore aliasName="CPVMCertificate" storepass="vmops.com" -while getopts 'i:h:k:p:t:c' OPTION +while getopts 'i:h:k:p:t:u:c' OPTION do case $OPTION in c) cflag=1 @@ -107,6 +113,9 @@ do t) cccflag=1 customCertChain="$OPTARG" ;; + u) ccacflag=1 + customCACert="$OPTARG" + ;; i) publicIp="$OPTARG" ;; h) hostName="$OPTARG" @@ -165,10 +174,10 @@ then exit 2 fi -if [ -f "$customPrivCert" ] +if [ -f "$customCACert" ] then keytool -delete -alias $aliasName -keystore $keyStore -storepass $storepass -noprompt - keytool -import -alias $aliasName -keystore $keyStore -storepass $storepass -noprompt -file $customPrivCert + keytool -import -alias $aliasName -keystore $keyStore -storepass $storepass -noprompt -file $customCACert fi if [ -d /etc/apache2 ] diff --git a/ui/scripts/ui-custom/physicalResources.js b/ui/scripts/ui-custom/physicalResources.js index fcc2f6ab5bd..06fcca71c72 100644 --- a/ui/scripts/ui-custom/physicalResources.js +++ b/ui/scripts/ui-custom/physicalResources.js @@ -117,8 +117,8 @@ type: "POST", url: createURL('uploadCustomCertificate'), data: { - certificate: encodeURIComponent(args.data.certificate), - privatekey: encodeURIComponent(args.data.privatekey), + certificate: args.data.certificate, + privatekey: args.data.privatekey, domainsuffix: args.data.domainsuffix }, dataType: 'json',