Skip to content

Commit c901d51

Browse files
committed
Add root CA to the truststore for System VMs
1 parent 1bff543 commit c901d51

File tree

6 files changed

+79
-14
lines changed

6 files changed

+79
-14
lines changed

api/src/main/java/org/apache/cloudstack/ca/CAManager.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,10 @@ public interface CAManager extends CAService, Configurable, PluggableService {
3939
ConfigKey<String> CAProviderPlugin = new ConfigKey<>("Advanced", String.class,
4040
"ca.framework.provider.plugin",
4141
"root",
42-
"The CA provider plugin that is used for secure CloudStack management server-agent communication for encryption and authentication. Restart management server(s) when changed.", true);
42+
"The CA provider plugin used for CloudStack internal certificate management (MS-agent encryption and authentication). " +
43+
"The default 'root' provider auto-generates a CA on first startup, but also supports user-provided custom CA material " +
44+
"via the ca.plugin.root.private.key, ca.plugin.root.public.key, and ca.plugin.root.ca.certificate settings. " +
45+
"Restart management server(s) when changed.", true);
4346

4447
ConfigKey<Integer> CertKeySize = new ConfigKey<>("Advanced", Integer.class,
4548
"ca.framework.cert.keysize",

plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,17 +106,21 @@ public final class RootCAProvider extends AdapterBase implements CAProvider, Con
106106
private static ConfigKey<String> rootCAPrivateKey = new ConfigKey<>("Hidden", String.class,
107107
"ca.plugin.root.private.key",
108108
null,
109-
"The ROOT CA private key.", true);
109+
"The ROOT CA private key in PEM format (PKCS#8: must start with '-----BEGIN PRIVATE KEY-----'). " +
110+
"When set along with the public key and certificate, CloudStack uses this custom CA instead of auto-generating one. " +
111+
"All three ca.plugin.root.* keys must be set together. Restart management server(s) when changed.", true);
110112

111113
private static ConfigKey<String> rootCAPublicKey = new ConfigKey<>("Hidden", String.class,
112114
"ca.plugin.root.public.key",
113115
null,
114-
"The ROOT CA public key.", true);
116+
"The ROOT CA public key in PEM format (X.509/SPKI: must start with '-----BEGIN PUBLIC KEY-----'). " +
117+
"Required when providing a custom CA. Restart management server(s) when changed.", true);
115118

116119
private static ConfigKey<String> rootCACertificate = new ConfigKey<>("Hidden", String.class,
117120
"ca.plugin.root.ca.certificate",
118121
null,
119-
"The ROOT CA certificate.", true);
122+
"The ROOT CA X.509 certificate in PEM format (must start with '-----BEGIN CERTIFICATE-----'). " +
123+
"Required when providing a custom CA. Restart management server(s) when changed.", true);
120124

121125
private static ConfigKey<String> rootCAIssuerDN = new ConfigKey<>("Advanced", String.class,
122126
"ca.plugin.root.issuer.dn",
@@ -422,13 +426,29 @@ protected void addConfiguredManagementIp(List<String> ipList) {
422426

423427

424428
private boolean setupCA() {
425-
if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) {
426-
logger.error("Failed to save and load root CA keypair");
427-
return false;
429+
if (!loadRootCAKeyPair()) {
430+
if (hasUserProvidedCAKeys()) {
431+
logger.error("Failed to load user-provided CA keys from configuration. " +
432+
"Check that ca.plugin.root.private.key, ca.plugin.root.public.key, and " +
433+
"ca.plugin.root.ca.certificate are all set and in the correct PEM format " +
434+
"(private key must be PKCS#8: '-----BEGIN PRIVATE KEY-----'). " +
435+
"Overwriting with auto-generated keys.");
436+
}
437+
if (!saveNewRootCAKeypair()) {
438+
logger.error("Failed to save and load root CA keypair");
439+
return false;
440+
}
428441
}
429-
if (!loadRootCACertificate() && !saveNewRootCACertificate()) {
430-
logger.error("Failed to save and load root CA certificate");
431-
return false;
442+
if (!loadRootCACertificate()) {
443+
if (hasUserProvidedCAKeys()) {
444+
logger.error("Failed to load user-provided CA certificate. " +
445+
"Check that ca.plugin.root.ca.certificate is set and in PEM format. " +
446+
"Overwriting with auto-generated certificate.");
447+
}
448+
if (!saveNewRootCACertificate()) {
449+
logger.error("Failed to save and load root CA certificate");
450+
return false;
451+
}
432452
}
433453
if (!loadManagementKeyStore()) {
434454
logger.error("Failed to check and configure management server keystore");
@@ -437,10 +457,16 @@ private boolean setupCA() {
437457
return true;
438458
}
439459

460+
private boolean hasUserProvidedCAKeys() {
461+
return StringUtils.isNotEmpty(rootCAPublicKey.value())
462+
|| StringUtils.isNotEmpty(rootCAPrivateKey.value())
463+
|| StringUtils.isNotEmpty(rootCACertificate.value());
464+
}
465+
440466
@Override
441467
public boolean start() {
442468
managementCertificateCustomSAN = CAManager.CertManagementCustomSubjectAlternativeName.value();
443-
return loadRootCAKeyPair() && loadRootCAKeyPair() && loadManagementKeyStore();
469+
return loadRootCAKeyPair() && loadRootCACertificate() && loadManagementKeyStore();
444470
}
445471

446472
@Override

scripts/util/keystore-cert-import

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,22 @@ if [ -f "$SYSTEM_FILE" ]; then
137137
chmod 644 /usr/local/share/ca-certificates/cloudstack/ca.crt
138138
update-ca-certificates > /dev/null 2>&1 || true
139139

140+
# Import CA cert(s) into realhostip.keystore so the SSVM JVM
141+
# (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh)
142+
# can trust servers signed by the CloudStack CA
143+
REALHOSTIP_KS_FILE="$(dirname $(dirname $PROPS_FILE))/certs/realhostip.keystore"
144+
REALHOSTIP_PASS="vmops.com"
145+
if [ -f "$REALHOSTIP_KS_FILE" ]; then
146+
awk '/-----BEGIN CERTIFICATE-----/{n++}{print > "cloudca." n }' "$CACERT_FILE"
147+
for caChain in $(ls cloudca.* 2>/dev/null); do
148+
keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \
149+
-storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true
150+
keytool -import -noprompt -trustcacerts -alias "$caChain" -file "$caChain" \
151+
-keystore "$REALHOSTIP_KS_FILE" -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1
152+
done
153+
rm -f cloudca.*
154+
fi
155+
140156
# Ensure cloud service is running in systemvm
141157
if [ "$MODE" == "ssh" ]; then
142158
systemctl start cloud > /dev/null 2>&1

systemvm/patch-sysvms.sh

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,28 @@ patch_systemvm() {
126126

127127
if [ "$TYPE" = "consoleproxy" ] || [ "$TYPE" = "secstorage" ]; then
128128
# Import global cacerts into 'cloud' service's keystore
129-
keytool -importkeystore -srckeystore /etc/ssl/certs/java/cacerts -destkeystore /usr/local/cloud/systemvm/certs/realhostip.keystore -srcstorepass changeit -deststorepass vmops.com -noprompt 2>/dev/null || true
129+
REALHOSTIP_KS_FILE="/usr/local/cloud/systemvm/certs/realhostip.keystore"
130+
REALHOSTIP_PASS="vmops.com"
131+
132+
keytool -importkeystore -srckeystore /etc/ssl/certs/java/cacerts \
133+
-destkeystore "$REALHOSTIP_KS_FILE" -srcstorepass changeit -deststorepass \
134+
"$REALHOSTIP_PASS" -noprompt 2>/dev/null || true
135+
136+
# Import CA cert(s) into realhostip.keystore so the SSVM JVM
137+
# (which overrides the truststore via -Djavax.net.ssl.trustStore in _run.sh)
138+
# can trust servers signed by the CloudStack CA
139+
CACERT_FILE="/usr/local/share/ca-certificates/cloudstack/ca.crt"
140+
141+
if [ -f "$CACERT_FILE" ] && [ -f "$REALHOSTIP_KS_FILE" ]; then
142+
awk '/-----BEGIN CERTIFICATE-----/{n++}{print > "cloudca." n }' "$CACERT_FILE"
143+
for caChain in $(ls cloudca.* 2>/dev/null); do
144+
keytool -delete -noprompt -alias "$caChain" -keystore "$REALHOSTIP_KS_FILE" \
145+
-storepass "$REALHOSTIP_PASS" > /dev/null 2>&1 || true
146+
keytool -import -noprompt -trustcacerts -alias "$caChain" -file "$caChain" \
147+
-keystore "$REALHOSTIP_KS_FILE" -storepass "$REALHOSTIP_PASS" > /dev/null 2>&1
148+
done
149+
rm -f cloudca.*
150+
fi
130151
fi
131152

132153
update_checksum $newpath/cloud-scripts.tgz

utils/src/main/java/com/cloud/utils/nio/Link.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,7 @@ private static HandshakeHolder doHandshakeWrap(final SocketChannel socketChannel
552552
LOGGER.error(String.format("SSL error caught during wrap data: %s, for local address=%s, remote address=%s.",
553553
sslException.getMessage(), socketChannel.getLocalAddress(), socketChannel.getRemoteAddress()));
554554
sslEngine.closeOutbound();
555-
return new HandshakeHolder(myAppData, myNetData, true);
555+
return new HandshakeHolder(myAppData, myNetData, false);
556556
}
557557
if (result == null) {
558558
return new HandshakeHolder(myAppData, myNetData, false);

utils/src/main/java/org/apache/cloudstack/utils/security/KeyStoreUtils.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
public class KeyStoreUtils {
2727
public static final String KS_SETUP_SCRIPT = "keystore-setup";
2828
public static final String KS_IMPORT_SCRIPT = "keystore-cert-import";
29-
public static final String KS_SYSTEMVM_IMPORT_SCRIPT = "keystore-cert-import-sysvm";
3029

3130
public static final String AGENT_PROPSFILE = "agent.properties";
3231
public static final String KS_PASSPHRASE_PROPERTY = "keystore.passphrase";

0 commit comments

Comments
 (0)