diff --git a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java index ef8c37ebda7..6f63a857591 100644 --- a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java +++ b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java @@ -158,8 +158,9 @@ public interface CAManager extends CAService, Configurable, PluggableService { * @param sshConnection active SSH connection to the KVM host * @param agentIp IP address of the KVM host agent * @param agentHostname hostname of the KVM host agent + * @param caProvider optional CA provider plugin name (null uses default) */ - void provisionCertificateViaSsh(Connection sshConnection, String agentIp, String agentHostname); + void provisionCertificateViaSsh(Connection sshConnection, String agentIp, String agentHostname, String caProvider); /** * Setups up a new keystore and generates CSR for a host diff --git a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java index 15f7f46d1b1..995ba4de51f 100644 --- a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java +++ b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java @@ -106,20 +106,20 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con private static ConfigKey rootCAPrivateKey = new ConfigKey<>("Hidden", String.class, "ca.plugin.root.private.key", null, - "The ROOT CA private key in PEM format (PKCS#8: must start with '-----BEGIN PRIVATE KEY-----'). " + + "The ROOT CA private key in PEM format (PKCS#8: must start with 'BEGIN PRIVATE KEY'). " + "When set along with the public key and certificate, CloudStack uses this custom CA instead of auto-generating one. " + "All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed.", true); private static ConfigKey rootCAPublicKey = new ConfigKey<>("Hidden", String.class, "ca.plugin.root.public.key", null, - "The ROOT CA public key in PEM format (X.509/SPKI: must start with '-----BEGIN PUBLIC KEY-----'). " + + "The ROOT CA public key in PEM format (X.509/SPKI: must start with 'BEGIN PUBLIC KEY'). " + "Required when providing a custom CA. Restart management server(s) when changed.", true); private static ConfigKey rootCACertificate = new ConfigKey<>("Hidden", String.class, "ca.plugin.root.ca.certificate", null, - "The ROOT CA X.509 certificate in PEM format (must start with '-----BEGIN CERTIFICATE-----'). " + + "The ROOT CA X.509 certificate in PEM format (must start with 'BEGIN CERTIFICATE'). " + "Required when providing a custom CA. Restart management server(s) when changed.", true); private static ConfigKey rootCAIssuerDN = new ConfigKey<>("Advanced", String.class, @@ -431,7 +431,7 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con logger.error("Failed to load user-provided CA keys from configuration. " + "Check that ca.plugin.root.private.key, ca.plugin.root.public.key, and " + "ca.plugin.root.ca.certificate are all set and in the correct PEM format " + - "(private key must be PKCS#8: '-----BEGIN PRIVATE KEY-----'). " + + "(private key must be PKCS#8: 'BEGIN PRIVATE KEY'). " + "Overwriting with auto-generated keys."); } if (!saveNewRootCAKeypair()) { diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import index ed28f874b16..cf355e09845 100755 --- a/scripts/util/keystore-cert-import +++ b/scripts/util/keystore-cert-import @@ -70,7 +70,7 @@ elif [ ! -f "$CACERT_FILE" ]; then fi # Import cacerts into the keystore -awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++}{print > "cloudca." n }' "$CACERT_FILE" +awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" for caChain in $(ls cloudca.* 2>/dev/null); do keytool -delete -noprompt -alias "$caChain" -keystore "$KS_FILE" -storepass "$KS_PASS" > /dev/null 2>&1 || true keytool -import -noprompt -storepass "$KS_PASS" -trustcacerts -alias "$caChain" -file "$caChain" -keystore "$KS_FILE" > /dev/null 2>&1 @@ -140,10 +140,10 @@ if [ -f "$SYSTEM_FILE" ]; then # Import CA cert(s) into realhostip.keystore so the SSVM JVM # (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh) # can trust servers signed by the CloudStack CA - REALHOSTIP_KS_FILE="$(dirname $(dirname $PROPS_FILE))/certs/realhostip.keystore" + REALHOSTIP_KS_FILE="$(dirname "$(dirname "$PROPS_FILE")")/certs/realhostip.keystore" REALHOSTIP_PASS="vmops.com" if [ -f "$REALHOSTIP_KS_FILE" ]; then - awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++}{print > "cloudca." n }' "$CACERT_FILE" + awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" for caChain in $(ls cloudca.* 2>/dev/null); do keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \ -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java index 77bf9f8cda9..b9fa3f0ebae 100644 --- a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java +++ b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java @@ -169,7 +169,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements throw new CloudRuntimeException("Cannot secure agent communication because SSH connection is invalid for host IP=" + agentIp); } - caManager.provisionCertificateViaSsh(sshConnection, agentIp, agentHostname); + caManager.provisionCertificateViaSsh(sshConnection, agentIp, agentHostname, null); if (logger.isDebugEnabled()) { logger.debug("Succeeded to import certificate in the keystore for agent on the KVM host: " + agentIp + ". Agent secured and trusted."); diff --git a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java index 1c3d99020d9..270a24dad37 100644 --- a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java +++ b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java @@ -230,15 +230,15 @@ public class CAManagerImpl extends ManagerBase implements CAManager { private boolean provisionCertificateForced(Host host, Boolean reconnect, String caProvider) { if (host.getType() == Host.Type.Routing && host.getHypervisorType() == com.cloud.hypervisor.Hypervisor.HypervisorType.KVM) { - return provisionKvmHostViaSsh(host); + return provisionKvmHostViaSsh(host, caProvider); } else if (host.getType() == Host.Type.ConsoleProxy || host.getType() == Host.Type.SecondaryStorageVM) { - return provisionSystemVmViaSsh(host, reconnect); + return provisionSystemVmViaSsh(host, reconnect, caProvider); } throw new CloudRuntimeException("Forced certificate provisioning is only supported for KVM hosts and SystemVMs."); } @Override - public void provisionCertificateViaSsh(final Connection sshConnection, final String agentIp, final String agentHostname) { + public void provisionCertificateViaSsh(final Connection sshConnection, final String agentIp, final String agentHostname, final String caProvider) { Integer validityPeriod = CAManager.CertValidityPeriod.value(); if (validityPeriod < 1) { validityPeriod = 1; @@ -266,7 +266,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager { // 2. Issue Certificate based on returned CSR final String csr = keystoreSetupResult.getStdOut(); final Certificate certificate = issueCertificate(csr, Arrays.asList(agentHostname, agentIp), - Collections.singletonList(agentIp), null, null); + Collections.singletonList(agentIp), null, caProvider); if (certificate == null || certificate.getClientCertificate() == null) { throw new CloudRuntimeException("Failed to issue certificates for host: " + agentIp); @@ -297,7 +297,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager { } } - private boolean provisionKvmHostViaSsh(Host host) { + private boolean provisionKvmHostViaSsh(Host host, String caProvider) { final HostVO hostVO = (HostVO) host; hostDao.loadDetails(hostVO); String username = hostVO.getDetail(ApiConstants.USERNAME); @@ -321,7 +321,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager { } } - provisionCertificateViaSsh(sshConnection, hostIp, host.getName()); + provisionCertificateViaSsh(sshConnection, hostIp, host.getName(), caProvider); SSHCmdHelper.sshExecuteCmd(sshConnection, "sudo service cloudstack-agent restart"); return true; @@ -335,7 +335,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager { } } - private boolean provisionSystemVmViaSsh(Host host, Boolean reconnect) { + private boolean provisionSystemVmViaSsh(Host host, Boolean reconnect, String caProvider) { VMInstanceVO vm = vmInstanceDao.findVMByInstanceName(host.getName()); if (vm == null) { throw new CloudRuntimeException("Cannot find underlying VM for host: " + host.getName()); @@ -352,7 +352,7 @@ public class CAManagerImpl extends ManagerBase implements CAManager { } final Certificate certificate = issueCertificate(null, Arrays.asList(vm.getHostName(), vm.getInstanceName()), - new ArrayList<>(ipAddressDetails.values()), CertValidityPeriod.value(), null); + new ArrayList<>(ipAddressDetails.values()), CertValidityPeriod.value(), caProvider); return deployCertificate(hypervisorHost, certificate, reconnect, sshAccessDetails); } catch (Exception e) { logger.error("Failed to provision system VM " + host.getName() + " via hypervisor SSH proxy. Ensure the hypervisor host is connected.", e); diff --git a/systemvm/patch-sysvms.sh b/systemvm/patch-sysvms.sh index e5dcbe14a10..8d96de9ba3b 100755 --- a/systemvm/patch-sysvms.sh +++ b/systemvm/patch-sysvms.sh @@ -139,7 +139,7 @@ patch_systemvm() { CACERT_FILE="/usr/local/share/ca-certificates/cloudstack/ca.crt" if [ -f "$CACERT_FILE" ] && [ -f "$REALHOSTIP_KS_FILE" ]; then - awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++}{print > "cloudca." n }' "$CACERT_FILE" + awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++} n>0{print > "cloudca." n }' "$CACERT_FILE" for caChain in $(ls cloudca.* 2>/dev/null); do keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \ -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true