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.
This commit is contained in:
Nitin Mehta 2014-04-24 17:20:41 -07:00
parent 42d48fe9ab
commit 1d45b75298
9 changed files with 98 additions and 33 deletions

View File

@ -28,4 +28,6 @@ public interface KeystoreDao extends GenericDao<KeystoreVO, Long> {
void save(String alias, String certificate, Integer index, String domainSuffix); void save(String alias, String certificate, Integer index, String domainSuffix);
List<KeystoreVO> findCertChain(); List<KeystoreVO> findCertChain();
List<KeystoreVO> findCertChain(String domainSuffix);
} }

View File

@ -38,6 +38,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements KeystoreDao { public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements KeystoreDao {
protected final SearchBuilder<KeystoreVO> FindByNameSearch; protected final SearchBuilder<KeystoreVO> FindByNameSearch;
protected final SearchBuilder<KeystoreVO> CertChainSearch; protected final SearchBuilder<KeystoreVO> CertChainSearch;
protected final SearchBuilder<KeystoreVO> CertChainSearchForDomainSuffix;
public KeystoreDaoImpl() { public KeystoreDaoImpl() {
FindByNameSearch = createSearchBuilder(); FindByNameSearch = createSearchBuilder();
@ -47,6 +48,11 @@ public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements
CertChainSearch = createSearchBuilder(); CertChainSearch = createSearchBuilder();
CertChainSearch.and("key", CertChainSearch.entity().getKey(), Op.NULL); CertChainSearch.and("key", CertChainSearch.entity().getKey(), Op.NULL);
CertChainSearch.done(); CertChainSearch.done();
CertChainSearchForDomainSuffix = createSearchBuilder();
CertChainSearchForDomainSuffix.and("key", CertChainSearchForDomainSuffix.entity().getKey(), Op.NULL);
CertChainSearchForDomainSuffix.and("domainSuffix", CertChainSearchForDomainSuffix.entity().getDomainSuffix(), Op.EQ);
CertChainSearchForDomainSuffix.done();
} }
@Override @Override
@ -64,6 +70,19 @@ public class KeystoreDaoImpl extends GenericDaoBase<KeystoreVO, Long> implements
return ks; return ks;
} }
@Override
public List<KeystoreVO> findCertChain(String domainSuffix) {
SearchCriteria<KeystoreVO> sc = CertChainSearchForDomainSuffix.create();
sc.setParameters("domainSuffix", domainSuffix);
List<KeystoreVO> 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 @Override
public KeystoreVO findByName(String name) { public KeystoreVO findByName(String name) {
assert (name != null); assert (name != null);

View File

@ -28,15 +28,18 @@ public interface KeystoreManager extends Manager {
private String privCert; private String privCert;
@LogLevel(Log4jLevel.Off) @LogLevel(Log4jLevel.Off)
private String certChain; private String certChain;
@LogLevel(Log4jLevel.Off)
private String rootCACert;
public Certificates() { public Certificates() {
} }
public Certificates(String prvKey, String privCert, String certChain) { public Certificates(String prvKey, String privCert, String certChain, String rootCACert) {
privKey = prvKey; this.privKey = prvKey;
this.privCert = privCert; this.privCert = privCert;
this.certChain = certChain; this.certChain = certChain;
this.rootCACert = rootCACert;
} }
public String getPrivKey() { public String getPrivKey() {
@ -50,6 +53,10 @@ public interface KeystoreManager extends Manager {
public String getCertChain() { public String getCertChain() {
return certChain; return certChain;
} }
public String getRootCACert() {
return rootCACert;
}
} }
boolean validateCertificate(String certificate, String key, String domainSuffix); boolean validateCertificate(String certificate, String key, String domainSuffix);

View File

@ -23,6 +23,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException; import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -30,6 +31,7 @@ import java.util.regex.Pattern;
import javax.ejb.Local; import javax.ejb.Local;
import javax.inject.Inject; import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -129,17 +131,22 @@ public class KeystoreManagerImpl extends ManagerBase implements KeystoreManager
} }
String prvKey = ksVo.getKey(); String prvKey = ksVo.getKey();
String prvCert = ksVo.getCertificate(); String prvCert = ksVo.getCertificate();
String domainSuffix = ksVo.getDomainSuffix();
String certChain = null; String certChain = null;
List<KeystoreVO> certchains = _ksDao.findCertChain(); String rootCert = null;
List<KeystoreVO> certchains = _ksDao.findCertChain(domainSuffix);
if (certchains.size() > 0) { if (certchains.size() > 0) {
StringBuilder chains = new StringBuilder(); ArrayList<String> chains = new ArrayList<String>();
for (KeystoreVO cert : certchains) { for (KeystoreVO cert : certchains) {
chains.append(cert.getCertificate()); if (chains.size() == 0) {// For the first time it will be length 0
chains.append("\n"); rootCert = cert.getCertificate();
} }
certChain = chains.toString(); chains.add(cert.getCertificate());
} }
Certificates certs = new Certificates(prvKey, prvCert, certChain); Collections.reverse(chains);
certChain = StringUtils.join(chains, "\n");
}
Certificates certs = new Certificates(prvKey, prvCert, certChain, rootCert);
return certs; return certs;
} }

View File

@ -16,9 +16,7 @@
// under the License. // under the License.
package com.cloud.server; package com.cloud.server;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Calendar; import java.util.Calendar;
@ -3447,16 +3445,6 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
String certificate = cmd.getCertificate(); String certificate = cmd.getCertificate();
String key = cmd.getPrivateKey(); 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())) { if (cmd.getPrivateKey() != null && !_ksMgr.validateCertificate(certificate, key, cmd.getDomainSuffix())) {
throw new InvalidParameterValueException("Failed to pass certificate validation check"); throw new InvalidParameterValueException("Failed to pass certificate validation check");
@ -3464,16 +3452,20 @@ public class ManagementServerImpl extends ManagerBase implements ManagementServe
if (cmd.getPrivateKey() != null) { if (cmd.getPrivateKey() != null) {
_ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, certificate, key, cmd.getDomainSuffix()); _ksMgr.saveCertificate(ConsoleProxyManager.CERTIFICATE_NAME, certificate, key, cmd.getDomainSuffix());
// Reboot ssvm here since private key is present - meaning server cert being passed
List<SecondaryStorageVmVO> alreadyRunning = _secStorageVmDao.getSecStorageVmListInStates(null, State.Running, State.Migrating, State.Starting);
for (SecondaryStorageVmVO ssVmVm : alreadyRunning) {
_secStorageVmMgr.rebootSecStorageVm(ssVmVm.getId());
}
} else { } else {
_ksMgr.saveCertificate(cmd.getAlias(), certificate, cmd.getCertIndex(), cmd.getDomainSuffix()); _ksMgr.saveCertificate(cmd.getAlias(), certificate, cmd.getCertIndex(), cmd.getDomainSuffix());
} }
_consoleProxyMgr.setManagementState(ConsoleProxyManagementState.ResetSuspending); _consoleProxyMgr.setManagementState(ConsoleProxyManagementState.ResetSuspending);
List<SecondaryStorageVmVO> alreadyRunning = _secStorageVmDao.getSecStorageVmListInStates(null, State.Running, State.Migrating, State.Starting); return "Certificate has been successfully updated, if its the server certificate we would reboot all " +
for (SecondaryStorageVmVO ssVmVm : alreadyRunning) { "running console proxy VMs and secondary storage VMs to propagate the new certificate, " +
_secStorageVmMgr.rebootSecStorageVm(ssVmVm.getId()); "please give a few minutes for console access and storage services service to be up and working again";
}
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";
} }
@Override @Override

View File

@ -1188,6 +1188,8 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
} else { } else {
String prvKey = certs.getPrivKey(); String prvKey = certs.getPrivKey();
String pubCert = certs.getPrivCert(); String pubCert = certs.getPrivCert();
String certChain = certs.getCertChain();
String rootCACert = certs.getRootCACert();
try { try {
File prvKeyFile = File.createTempFile("prvkey", null); File prvKeyFile = File.createTempFile("prvkey", null);
@ -1203,10 +1205,34 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
out.write(pubCert); out.write(pubCert);
out.close(); 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(); prvKeyFile.delete();
pubCertFile.delete(); pubCertFile.delete();
if(certChainFile != null){
certChainFile.delete();
}
if(rootCACertFile != null){
rootCACertFile.delete();
}
} catch (IOException e) { } catch (IOException e) {
s_logger.debug("Failed to config ssl: " + e.toString()); 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) { if (!_inSystemVM) {
return; return;
} }
@ -2076,6 +2102,9 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
if (certChainPath != null) { if (certChainPath != null) {
command.add("-t", certChainPath); command.add("-t", certChainPath);
} }
if (rootCACert != null) {
command.add("-u", rootCACert);
}
String result = command.execute(); String result = command.execute();
if (result != null) { if (result != null) {
s_logger.warn("Unable to configure httpd to use ssl"); s_logger.warn("Unable to configure httpd to use ssl");

View File

@ -68,4 +68,4 @@ if [ "$(uname -m | grep '64')" == "" ]; then
fi fi
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 $@

View File

@ -24,6 +24,7 @@ help() {
printf " -k path of private key\n" printf " -k path of private key\n"
printf " -p path of certificate of public key\n" printf " -p path of certificate of public key\n"
printf " -t path of certificate chain\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 -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.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 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() { copy_certs() {
@ -88,12 +93,13 @@ cccflag=
customPrivKey=$(dirname $0)/certs/realhostip.key customPrivKey=$(dirname $0)/certs/realhostip.key
customPrivCert=$(dirname $0)/certs/realhostip.crt customPrivCert=$(dirname $0)/certs/realhostip.crt
customCertChain= customCertChain=
customCACert=
publicIp= publicIp=
hostName= hostName=
keyStore=$(dirname $0)/certs/realhostip.keystore keyStore=$(dirname $0)/certs/realhostip.keystore
aliasName="CPVMCertificate" aliasName="CPVMCertificate"
storepass="vmops.com" storepass="vmops.com"
while getopts 'i:h:k:p:t:c' OPTION while getopts 'i:h:k:p:t:u:c' OPTION
do do
case $OPTION in case $OPTION in
c) cflag=1 c) cflag=1
@ -107,6 +113,9 @@ do
t) cccflag=1 t) cccflag=1
customCertChain="$OPTARG" customCertChain="$OPTARG"
;; ;;
u) ccacflag=1
customCACert="$OPTARG"
;;
i) publicIp="$OPTARG" i) publicIp="$OPTARG"
;; ;;
h) hostName="$OPTARG" h) hostName="$OPTARG"
@ -165,10 +174,10 @@ then
exit 2 exit 2
fi fi
if [ -f "$customPrivCert" ] if [ -f "$customCACert" ]
then then
keytool -delete -alias $aliasName -keystore $keyStore -storepass $storepass -noprompt 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 fi
if [ -d /etc/apache2 ] if [ -d /etc/apache2 ]

View File

@ -117,8 +117,8 @@
type: "POST", type: "POST",
url: createURL('uploadCustomCertificate'), url: createURL('uploadCustomCertificate'),
data: { data: {
certificate: encodeURIComponent(args.data.certificate), certificate: args.data.certificate,
privatekey: encodeURIComponent(args.data.privatekey), privatekey: args.data.privatekey,
domainsuffix: args.data.domainsuffix domainsuffix: args.data.domainsuffix
}, },
dataType: 'json', dataType: 'json',