Skip to content

Commit 1fe486f

Browse files
authored
Add ROOT CAs to the trust store and allow force provisioning of certs hosts & systemVMs via ssh (#12911)
1 parent a470914 commit 1fe486f

19 files changed

Lines changed: 989 additions & 152 deletions

File tree

api/src/main/java/org/apache/cloudstack/api/command/admin/ca/ProvisionCertificateCmd.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ public class ProvisionCertificateCmd extends BaseAsyncCmd {
6363
description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
6464
private String provider;
6565

66+
@Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN,
67+
description = "When true, uses SSH to re-provision the agent's certificate, bypassing the NIO agent connection. " +
68+
"Use this when agents are disconnected due to a CA change. Supported for KVM hosts and SystemVMs. Default is false",
69+
since = "4.23.0")
70+
private Boolean forced;
71+
6672
/////////////////////////////////////////////////////
6773
/////////////////// Accessors ///////////////////////
6874
/////////////////////////////////////////////////////
@@ -79,6 +85,10 @@ public String getProvider() {
7985
return provider;
8086
}
8187

88+
public boolean isForced() {
89+
return forced != null && forced;
90+
}
91+
8292
/////////////////////////////////////////////////////
8393
/////////////// API Implementation///////////////////
8494
/////////////////////////////////////////////////////
@@ -90,7 +100,7 @@ public void execute() {
90100
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId());
91101
}
92102

93-
boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider());
103+
boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider(), isForced());
94104
SuccessResponse response = new SuccessResponse(getCommandName());
95105
response.setSuccess(result);
96106
setResponseObject(response);

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.List;
2424
import java.util.Map;
2525

26+
import com.trilead.ssh2.Connection;
27+
2628
import org.apache.cloudstack.framework.ca.CAProvider;
2729
import org.apache.cloudstack.framework.ca.CAService;
2830
import org.apache.cloudstack.framework.ca.Certificate;
@@ -39,7 +41,10 @@ public interface CAManager extends CAService, Configurable, PluggableService {
3941
ConfigKey<String> CAProviderPlugin = new ConfigKey<>("Advanced", String.class,
4042
"ca.framework.provider.plugin",
4143
"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);
44+
"The CA provider plugin used for CloudStack internal certificate management (MS-agent encryption and authentication). " +
45+
"The default 'root' provider auto-generates a CA on first startup, but also supports user-provided custom CA material " +
46+
"via the ca.plugin.root.private.key, ca.plugin.root.public.key, and ca.plugin.root.ca.certificate settings. " +
47+
"Restart management server(s) when changed.", false);
4348

4449
ConfigKey<Integer> CertKeySize = new ConfigKey<>("Advanced", Integer.class,
4550
"ca.framework.cert.keysize",
@@ -85,6 +90,12 @@ public interface CAManager extends CAService, Configurable, PluggableService {
8590
"The actual implementation will depend on the configured CA provider.",
8691
false);
8792

93+
ConfigKey<Boolean> CaInjectDefaultTruststore = new ConfigKey<>("Advanced", Boolean.class,
94+
"ca.framework.inject.default.truststore", "true",
95+
"When true, injects the CA provider's certificate into the JVM default truststore on management server startup. " +
96+
"This allows outgoing HTTPS connections from the management server to trust servers with certificates signed by the configured CA. " +
97+
"Restart management server(s) when changed.", false);
98+
8899
/**
89100
* Returns a list of available CA provider plugins
90101
* @return returns list of CAProvider
@@ -130,12 +141,26 @@ public interface CAManager extends CAService, Configurable, PluggableService {
130141
boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String provider);
131142

132143
/**
133-
* Provisions certificate for given active and connected agent host
144+
* Provisions certificate for given agent host.
145+
* When forced=true, uses SSH to re-provision bypassing the NIO agent connection (for disconnected agents).
134146
* @param host
147+
* @param reconnect
135148
* @param provider
149+
* @param forced when true, provisions via SSH instead of NIO; supports KVM hosts and SystemVMs
136150
* @return returns success/failure as boolean
137151
*/
138-
boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider);
152+
boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider, final boolean forced);
153+
154+
/**
155+
* Provisions certificate for a KVM host using an existing SSH connection.
156+
* Runs keystore-setup to generate a CSR, issues a certificate, then runs keystore-cert-import.
157+
* Used during host discovery and for forced re-provisioning when the NIO agent is unreachable.
158+
* @param sshConnection active SSH connection to the KVM host
159+
* @param agentIp IP address of the KVM host agent
160+
* @param agentHostname hostname of the KVM host agent
161+
* @param caProvider optional CA provider plugin name (null uses default)
162+
*/
163+
void provisionCertificateViaSsh(Connection sshConnection, String agentIp, String agentHostname, String caProvider);
139164

140165
/**
141166
* Setups up a new keystore and generates CSR for a host

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,17 @@ public final class RootCACustomTrustManager implements X509TrustManager {
4040
private boolean authStrictness = true;
4141
private boolean allowExpiredCertificate = true;
4242
private CrlDao crlDao;
43-
private X509Certificate caCertificate;
43+
private List<X509Certificate> caCertificates;
4444
private Map<String, X509Certificate> activeCertMap;
4545

46-
public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) {
46+
public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final List<X509Certificate> caCertificates, final CrlDao crlDao) {
4747
if (StringUtils.isNotEmpty(clientAddress)) {
4848
this.clientAddress = clientAddress.replace("/", "").split(":")[0];
4949
}
5050
this.authStrictness = authStrictness;
5151
this.allowExpiredCertificate = allowExpiredCertificate;
5252
this.activeCertMap = activeCertMap;
53-
this.caCertificate = caCertificate;
53+
this.caCertificates = caCertificates;
5454
this.crlDao = crlDao;
5555
}
5656

@@ -151,6 +151,6 @@ public void checkServerTrusted(X509Certificate[] x509Certificates, String s) thr
151151

152152
@Override
153153
public X509Certificate[] getAcceptedIssuers() {
154-
return new X509Certificate[]{caCertificate};
154+
return caCertificates.toArray(new X509Certificate[0]);
155155
}
156156
}

0 commit comments

Comments
 (0)