mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
CLOUDSTACK-9993: Securing Agents Communications (#2239)
This introduces a new certificate authority framework that allows pluggable CA provider implementations to handle certificate operations around issuance, revocation and propagation. The framework injects itself to `NioServer` to handle agent connections securely. The framework adds assumptions in `NioClient` that a keystore if available with known name `cloud.jks` will be used for SSL negotiations and handshake. This includes a default 'root' CA provider plugin which creates its own self-signed root certificate authority on first run and uses it for issuance and provisioning of certificate to CloudStack agents such as the KVM, CPVM and SSVM agents and also for the management server for peer clustering. Additional changes and notes: - Comma separate list of management server IPs can be set to the 'host' global setting. Newly provisioned agents (KVM/CPVM/SSVM etc) will get radomized comma separated list to which they will attempt connection or reconnection in provided order. This removes need of a TCP LB on port 8250 (default) of the management server(s). - All fresh deployment will enforce two-way SSL authentication where connecting agents will be required to present certificates issued by the 'root' CA plugin. - Existing environment on upgrade will continue to use one-way SSL authentication and connecting agents will not be required to present certificates. - A script `keystore-setup` is responsible for initial keystore setup and CSR generation on the agent/hosts. - A script `keystore-cert-import` is responsible for import provided certificate payload to the java keystore file. - Agent security (keystore, certificates etc) are setup initially using SSH, and later provisioning is handled via an existing agent connection using command-answers. The supported clients and agents are limited to CPVM, SSVM, and KVM agents, and clustered management server (peering). - Certificate revocation does not revoke an existing agent-mgmt server connection, however rejects a revoked certificate used during SSL handshake. - Older `cloudstackmanagement.keystore` is deprecated and will no longer be used by mgmt server(s) for SSL negotiations and handshake. New keystores will be named `cloud.jks`, any additional SSL certificates should not be imported in it for use with tomcat etc. The `cloud.jks` keystore is stricly used for agent-server communications. - Management server keystore are validated and renewed on start up only, the validity of them are same as the CA certificates. New APIs: - listCaProviders: lists all available CA provider plugins - listCaCertificate: lists the CA certificate(s) - issueCertificate: issues X509 client certificate with/without a CSR - provisionCertificate: provisions certificate to a host - revokeCertificate: revokes a client certificate using its serial Global settings for the CA framework: - ca.framework.provider.plugin: The configured CA provider plugin - ca.framework.cert.keysize: The key size for certificate generation - ca.framework.cert.signature.algorithm: The certificate signature algorithm - ca.framework.cert.validity.period: Certificate validity in days - ca.framework.cert.automatic.renewal: Certificate auto-renewal setting - ca.framework.background.task.delay: CA background task delay/interval - ca.framework.cert.expiry.alert.period: Days to check and alert expiring certificates Global settings for the default 'root' CA provider: - ca.plugin.root.private.key: (hidden/encrypted) CA private key - ca.plugin.root.public.key: (hidden/encrypted) CA public key - ca.plugin.root.ca.certificate: (hidden/encrypted) CA certificate - ca.plugin.root.issuer.dn: The CA issue distinguished name - ca.plugin.root.auth.strictness: Are clients required to present certificates - ca.plugin.root.allow.expired.cert: Are clients with expired certificates allowed UI changes: - Button to download/save the CA certificates. Misc changes: - Upgrades bountycastle version and uses newer classes - Refactors SAMLUtil to use new CertUtils Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
parent
64e56a2159
commit
7ce54bf7a8
@ -34,6 +34,7 @@ env:
|
||||
matrix:
|
||||
- TESTS="smoke/test_affinity_groups
|
||||
smoke/test_affinity_groups_projects
|
||||
smoke/test_certauthority_root
|
||||
smoke/test_deploy_vgpu_enabled_vm
|
||||
smoke/test_deploy_vm_iso
|
||||
smoke/test_deploy_vm_root_resize
|
||||
|
||||
@ -16,12 +16,14 @@
|
||||
// under the License.
|
||||
package com.cloud.agent;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.channels.ClosedChannelException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -35,7 +37,13 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.SetupCertificateAnswer;
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeystoreAnswer;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
@ -68,6 +76,7 @@ import com.cloud.utils.nio.NioConnection;
|
||||
import com.cloud.utils.nio.Task;
|
||||
import com.cloud.utils.script.OutputInterpreter;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* @config
|
||||
@ -126,6 +135,9 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
private final ThreadPoolExecutor _ugentTaskPool;
|
||||
ExecutorService _executor;
|
||||
|
||||
private String _keystoreSetupPath;
|
||||
private String _keystoreCertImportPath;
|
||||
|
||||
// for simulator use only
|
||||
public Agent(final IAgentShell shell) {
|
||||
_shell = shell;
|
||||
@ -166,7 +178,8 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
throw new ConfigurationException("Unable to configure " + _resource.getName());
|
||||
}
|
||||
|
||||
_connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
|
||||
final String host = _shell.getHost();
|
||||
_connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
|
||||
|
||||
// ((NioClient)_connection).setBindAddress(_shell.getPrivateIp());
|
||||
|
||||
@ -182,7 +195,7 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
"agentRequest-Handler"));
|
||||
|
||||
s_logger.info("Agent [id = " + (_id != null ? _id : "new") + " : type = " + getResourceName() + " : zone = " + _shell.getZone() + " : pod = " + _shell.getPod() +
|
||||
" : workers = " + _shell.getWorkers() + " : host = " + _shell.getHost() + " : port = " + _shell.getPort());
|
||||
" : workers = " + _shell.getWorkers() + " : host = " + host + " : port = " + _shell.getPort());
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
@ -224,6 +237,16 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
throw new CloudRuntimeException("Unable to start the resource: " + _resource.getName());
|
||||
}
|
||||
|
||||
_keystoreSetupPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreSetupScript);
|
||||
if (_keystoreSetupPath == null) {
|
||||
throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreSetupScript));
|
||||
}
|
||||
|
||||
_keystoreCertImportPath = Script.findScript("scripts/util/", KeyStoreUtils.keyStoreImportScript);
|
||||
if (_keystoreCertImportPath == null) {
|
||||
throw new CloudRuntimeException(String.format("Unable to find the '%s' script", KeyStoreUtils.keyStoreImportScript));
|
||||
}
|
||||
|
||||
try {
|
||||
_connection.start();
|
||||
} catch (final NioConnectionException e) {
|
||||
@ -231,8 +254,10 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...");
|
||||
}
|
||||
while (!_connection.isStartup()) {
|
||||
final String host = _shell.getHost();
|
||||
_shell.getBackoffAlgorithm().waitBeforeRetry();
|
||||
_connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
|
||||
_connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
|
||||
s_logger.info("Connecting to host:" + host);
|
||||
try {
|
||||
_connection.start();
|
||||
} catch (final NioConnectionException e) {
|
||||
@ -408,14 +433,21 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
_shell.getBackoffAlgorithm().waitBeforeRetry();
|
||||
}
|
||||
|
||||
_connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this);
|
||||
final String host = _shell.getHost();
|
||||
do {
|
||||
s_logger.info("Reconnecting...");
|
||||
_connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
|
||||
s_logger.info("Reconnecting to host:" + host);
|
||||
try {
|
||||
_connection.start();
|
||||
} catch (final NioConnectionException e) {
|
||||
s_logger.warn("NIO Connection Exception " + e);
|
||||
s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...");
|
||||
_connection.stop();
|
||||
try {
|
||||
_connection.cleanUp();
|
||||
} catch (final IOException ex) {
|
||||
s_logger.warn("Fail to clean up old connection. " + ex);
|
||||
}
|
||||
}
|
||||
_shell.getBackoffAlgorithm().waitBeforeRetry();
|
||||
} while (!_connection.isStartup());
|
||||
@ -515,7 +547,10 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
s_logger.warn("No handler found to process cmd: " + cmd.toString());
|
||||
answer = new AgentControlAnswer(cmd);
|
||||
}
|
||||
|
||||
} else if (cmd instanceof SetupKeyStoreCommand && ((SetupKeyStoreCommand) cmd).isHandleByAgent()) {
|
||||
answer = setupAgentKeystore((SetupKeyStoreCommand) cmd);
|
||||
} else if (cmd instanceof SetupCertificateCommand && ((SetupCertificateCommand) cmd).isHandleByAgent()) {
|
||||
answer = setupAgentCertificate((SetupCertificateCommand) cmd);
|
||||
} else {
|
||||
if (cmd instanceof ReadyCommand) {
|
||||
processReadyCommand(cmd);
|
||||
@ -565,6 +600,86 @@ public class Agent implements HandlerFactory, IAgentControl {
|
||||
}
|
||||
}
|
||||
|
||||
public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) {
|
||||
final String keyStorePassword = cmd.getKeystorePassword();
|
||||
final long validityDays = cmd.getValidityDays();
|
||||
|
||||
s_logger.debug("Setting up agent keystore file and generating CSR");
|
||||
|
||||
final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
|
||||
if (agentFile == null) {
|
||||
return new Answer(cmd, false, "Failed to find agent.properties file");
|
||||
}
|
||||
final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
|
||||
final String csrFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCsrFile;
|
||||
|
||||
String storedPassword = _shell.getPersistentProperty(null, KeyStoreUtils.passphrasePropertyName);
|
||||
if (Strings.isNullOrEmpty(storedPassword)) {
|
||||
storedPassword = keyStorePassword;
|
||||
_shell.setPersistentProperty(null, KeyStoreUtils.passphrasePropertyName, storedPassword);
|
||||
}
|
||||
|
||||
Script script = new Script(_keystoreSetupPath, 60000, s_logger);
|
||||
script.add(agentFile.getAbsolutePath());
|
||||
script.add(keyStoreFile);
|
||||
script.add(storedPassword);
|
||||
script.add(String.valueOf(validityDays));
|
||||
script.add(csrFile);
|
||||
String result = script.execute();
|
||||
if (result != null) {
|
||||
throw new CloudRuntimeException("Unable to setup keystore file");
|
||||
}
|
||||
|
||||
final String csrString;
|
||||
try {
|
||||
csrString = FileUtils.readFileToString(new File(csrFile), Charset.defaultCharset());
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException("Unable to read generated CSR file", e);
|
||||
}
|
||||
return new SetupKeystoreAnswer(csrString);
|
||||
}
|
||||
|
||||
private Answer setupAgentCertificate(final SetupCertificateCommand cmd) {
|
||||
final String certificate = cmd.getCertificate();
|
||||
final String privateKey = cmd.getPrivateKey();
|
||||
final String caCertificates = cmd.getCaCertificates();
|
||||
|
||||
s_logger.debug("Importing received certificate to agent's keystore");
|
||||
|
||||
final File agentFile = PropertiesUtil.findConfigFile("agent.properties");
|
||||
if (agentFile == null) {
|
||||
return new Answer(cmd, false, "Failed to find agent.properties file");
|
||||
}
|
||||
final String keyStoreFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
|
||||
final String certFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCertFile;
|
||||
final String privateKeyFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultPrivateKeyFile;
|
||||
final String caCertFile = agentFile.getParent() + "/" + KeyStoreUtils.defaultCaCertFile;
|
||||
|
||||
try {
|
||||
FileUtils.writeStringToFile(new File(certFile), certificate, Charset.defaultCharset());
|
||||
FileUtils.writeStringToFile(new File(caCertFile), caCertificates, Charset.defaultCharset());
|
||||
s_logger.debug("Saved received client certificate to: " + certFile);
|
||||
} catch (IOException e) {
|
||||
throw new CloudRuntimeException("Unable to save received agent client and ca certificates", e);
|
||||
}
|
||||
|
||||
Script script = new Script(_keystoreCertImportPath, 60000, s_logger);
|
||||
script.add(agentFile.getAbsolutePath());
|
||||
script.add(keyStoreFile);
|
||||
script.add(KeyStoreUtils.agentMode);
|
||||
script.add(certFile);
|
||||
script.add("");
|
||||
script.add(caCertFile);
|
||||
script.add("");
|
||||
script.add(privateKeyFile);
|
||||
script.add(privateKey);
|
||||
String result = script.execute();
|
||||
if (result != null) {
|
||||
throw new CloudRuntimeException("Unable to import certificate into keystore file");
|
||||
}
|
||||
return new SetupCertificateAnswer(true);
|
||||
}
|
||||
|
||||
public void processResponse(final Response response, final Link link) {
|
||||
final Answer answer = response.getAnswer();
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
|
||||
@ -67,6 +67,7 @@ public class AgentShell implements IAgentShell, Daemon {
|
||||
private int _proxyPort;
|
||||
private int _workers;
|
||||
private String _guid;
|
||||
private int _hostCounter = 0;
|
||||
private int _nextAgentId = 1;
|
||||
private volatile boolean _exit = false;
|
||||
private int _pingRetries;
|
||||
@ -107,7 +108,17 @@ public class AgentShell implements IAgentShell, Daemon {
|
||||
|
||||
@Override
|
||||
public String getHost() {
|
||||
return _host;
|
||||
final String[] hosts = _host.split(",");
|
||||
if (_hostCounter >= hosts.length) {
|
||||
_hostCounter = 0;
|
||||
}
|
||||
final String host = hosts[_hostCounter % hosts.length];
|
||||
_hostCounter++;
|
||||
return host;
|
||||
}
|
||||
|
||||
public void setHost(final String host) {
|
||||
_host = host;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -51,6 +51,9 @@ public class PropertiesStorage implements StorageComponent {
|
||||
|
||||
@Override
|
||||
public synchronized void persist(String key, String value) {
|
||||
if (!loadFromFile(_file)) {
|
||||
s_logger.error("Failed to load changes and then write to them");
|
||||
}
|
||||
_properties.setProperty(key, value);
|
||||
FileOutputStream output = null;
|
||||
try {
|
||||
@ -65,6 +68,20 @@ public class PropertiesStorage implements StorageComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized boolean loadFromFile(final File file) {
|
||||
try {
|
||||
PropertiesUtil.loadFromFile(_properties, file);
|
||||
_file = file;
|
||||
} catch (FileNotFoundException e) {
|
||||
s_logger.error("How did we get here? ", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
s_logger.error("IOException: ", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean configure(String name, Map<String, Object> params) {
|
||||
_name = name;
|
||||
@ -86,17 +103,7 @@ public class PropertiesStorage implements StorageComponent {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
PropertiesUtil.loadFromFile(_properties, file);
|
||||
_file = file;
|
||||
} catch (FileNotFoundException e) {
|
||||
s_logger.error("How did we get here? ", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
s_logger.error("IOException: ", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
return loadFromFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -32,11 +32,8 @@ import java.util.Properties;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.Agent.ExitStatus;
|
||||
import com.cloud.agent.api.AgentControlAnswer;
|
||||
@ -64,6 +61,7 @@ import com.cloud.resource.ServerResourceBase;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.utils.script.Script;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
*
|
||||
@ -240,9 +238,11 @@ public class ConsoleProxyResource extends ServerResourceBase implements ServerRe
|
||||
_proxyVmId = NumbersUtil.parseLong(value, 0);
|
||||
|
||||
if (_localgw != null) {
|
||||
String mgmtHost = (String)params.get("host");
|
||||
String mgmtHosts = (String)params.get("host");
|
||||
if (_eth1ip != null) {
|
||||
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
|
||||
for (final String mgmtHost : mgmtHosts.split(",")) {
|
||||
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
|
||||
}
|
||||
String internalDns1 = (String) params.get("internaldns1");
|
||||
if (internalDns1 == null) {
|
||||
s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
// under the License.
|
||||
package com.cloud.agent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
@ -23,6 +25,8 @@ import javax.naming.ConfigurationException;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.cloud.utils.StringUtils;
|
||||
|
||||
public class AgentShellTest {
|
||||
@Test
|
||||
public void parseCommand() throws ConfigurationException {
|
||||
@ -44,4 +48,15 @@ public class AgentShellTest {
|
||||
Assert.assertNotNull(shell.getProperties());
|
||||
Assert.assertFalse(shell.getProperties().entrySet().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetHost() {
|
||||
AgentShell shell = new AgentShell();
|
||||
List<String> hosts = Arrays.asList("10.1.1.1", "20.2.2.2", "30.3.3.3", "2001:db8::1");
|
||||
shell.setHost(StringUtils.listToCsvTags(hosts));
|
||||
for (String host : hosts) {
|
||||
Assert.assertEquals(host, shell.getHost());
|
||||
}
|
||||
Assert.assertEquals(shell.getHost(), hosts.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,11 @@
|
||||
<artifactId>cloud-framework-config</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
|
||||
@ -16,6 +16,14 @@
|
||||
// under the License.
|
||||
package com.cloud.event;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.acl.Role;
|
||||
import org.apache.cloudstack.acl.RolePermission;
|
||||
import org.apache.cloudstack.config.Configuration;
|
||||
import org.apache.cloudstack.usage.Usage;
|
||||
|
||||
import com.cloud.dc.DataCenter;
|
||||
import com.cloud.dc.Pod;
|
||||
import com.cloud.dc.StorageNetworkIpRange;
|
||||
@ -54,10 +62,10 @@ import com.cloud.offering.NetworkOffering;
|
||||
import com.cloud.offering.ServiceOffering;
|
||||
import com.cloud.projects.Project;
|
||||
import com.cloud.server.ResourceTag;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.GuestOS;
|
||||
import com.cloud.storage.GuestOSHypervisor;
|
||||
import com.cloud.storage.Snapshot;
|
||||
import com.cloud.storage.StoragePool;
|
||||
import com.cloud.storage.Volume;
|
||||
import com.cloud.storage.snapshot.SnapshotPolicy;
|
||||
import com.cloud.template.VirtualMachineTemplate;
|
||||
@ -66,13 +74,6 @@ import com.cloud.user.User;
|
||||
import com.cloud.vm.Nic;
|
||||
import com.cloud.vm.NicSecondaryIp;
|
||||
import com.cloud.vm.VirtualMachine;
|
||||
import org.apache.cloudstack.acl.Role;
|
||||
import org.apache.cloudstack.acl.RolePermission;
|
||||
import org.apache.cloudstack.config.Configuration;
|
||||
import org.apache.cloudstack.usage.Usage;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EventTypes {
|
||||
|
||||
@ -176,6 +177,11 @@ public class EventTypes {
|
||||
public static final String EVENT_ROLE_PERMISSION_UPDATE = "ROLE.PERMISSION.UPDATE";
|
||||
public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE";
|
||||
|
||||
// CA events
|
||||
public static final String EVENT_CA_CERTIFICATE_ISSUE = "CA.CERTIFICATE.ISSUE";
|
||||
public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE";
|
||||
public static final String EVENT_CA_CERTIFICATE_PROVISION = "CA.CERTIFICATE.PROVISION";
|
||||
|
||||
// Account events
|
||||
public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE";
|
||||
public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE";
|
||||
|
||||
@ -67,6 +67,7 @@ public interface AlertService {
|
||||
public static final AlertType ALERT_TYPE_SYNC = new AlertType((short)27, "ALERT.TYPE.SYNC", true);
|
||||
public static final AlertType ALERT_TYPE_UPLOAD_FAILED = new AlertType((short)28, "ALERT.UPLOAD.FAILED", true);
|
||||
public static final AlertType ALERT_TYPE_OOBM_AUTH_ERROR = new AlertType((short)29, "ALERT.OOBM.AUTHERROR", true);
|
||||
public static final AlertType ALERT_TYPE_CA_CERT = new AlertType((short)31, "ALERT.CA.CERT", true);
|
||||
|
||||
public short getType() {
|
||||
return type;
|
||||
|
||||
@ -37,10 +37,12 @@ public class ApiConstants {
|
||||
public static final String BYTES_WRITE_RATE = "byteswriterate";
|
||||
public static final String CATEGORY = "category";
|
||||
public static final String CAN_REVERT = "canrevert";
|
||||
public static final String CA_CERTIFICATES = "cacertificates";
|
||||
public static final String CERTIFICATE = "certificate";
|
||||
public static final String CERTIFICATE_CHAIN = "certchain";
|
||||
public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
|
||||
public static final String CERTIFICATE_ID = "certid";
|
||||
public static final String CSR = "csr";
|
||||
public static final String PRIVATE_KEY = "privatekey";
|
||||
public static final String DOMAIN_SUFFIX = "domainsuffix";
|
||||
public static final String DNS_SEARCH_ORDER = "dnssearchorder";
|
||||
@ -54,6 +56,7 @@ public class ApiConstants {
|
||||
public static final String CLUSTER_ID = "clusterid";
|
||||
public static final String CLUSTER_NAME = "clustername";
|
||||
public static final String CLUSTER_TYPE = "clustertype";
|
||||
public static final String CN = "cn";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String CMD_EVENT_TYPE = "cmdeventtype";
|
||||
public static final String COMPONENT = "component";
|
||||
@ -216,6 +219,7 @@ public class ApiConstants {
|
||||
public static final String PUBLIC_END_PORT = "publicendport";
|
||||
public static final String PUBLIC_ZONE = "publiczone";
|
||||
public static final String RECEIVED_BYTES = "receivedbytes";
|
||||
public static final String RECONNECT = "reconnect";
|
||||
public static final String REQUIRES_HVM = "requireshvm";
|
||||
public static final String RESOURCE_TYPE = "resourcetype";
|
||||
public static final String RESPONSE = "response";
|
||||
@ -234,6 +238,7 @@ public class ApiConstants {
|
||||
public static final String SECURITY_GROUP_ID = "securitygroupid";
|
||||
public static final String SENT = "sent";
|
||||
public static final String SENT_BYTES = "sentbytes";
|
||||
public static final String SERIAL = "serial";
|
||||
public static final String SERVICE_OFFERING_ID = "serviceofferingid";
|
||||
public static final String SESSIONKEY = "sessionkey";
|
||||
public static final String SHOW_CAPACITIES = "showcapacities";
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.command.admin.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.CertificateResponse;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
@APICommand(name = IssueCertificateCmd.APINAME,
|
||||
description = "Issues a client certificate using configured or provided CA plugin",
|
||||
responseObject = CertificateResponse.class,
|
||||
requestHasSensitiveInfo = true,
|
||||
responseHasSensitiveInfo = true,
|
||||
since = "4.11.0",
|
||||
authorized = {RoleType.Admin})
|
||||
public class IssueCertificateCmd extends BaseAsyncCmd {
|
||||
private static final Logger LOG = Logger.getLogger(IssueCertificateCmd.class);
|
||||
|
||||
public static final String APINAME = "issueCertificate";
|
||||
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.CSR, type = BaseCmd.CommandType.STRING, description = "The certificate signing request (in pem format), if CSR is not provided then configured/provided options are considered", length = 65535)
|
||||
private String csr;
|
||||
|
||||
@Parameter(name = ApiConstants.DOMAIN, type = BaseCmd.CommandType.STRING, description = "Comma separated list of domains, the certificate should be issued for. When csr is not provided, the first domain is used as a subject/CN")
|
||||
private String domains;
|
||||
|
||||
@Parameter(name = ApiConstants.IP_ADDRESS, type = BaseCmd.CommandType.STRING, description = "Comma separated list of IP addresses, the certificate should be issued for")
|
||||
private String addresses;
|
||||
|
||||
@Parameter(name = ApiConstants.DURATION, type = CommandType.INTEGER, description = "Certificate validity duration in number of days, when not provided the default configured value will be used")
|
||||
private Integer validityDuration;
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = BaseCmd.CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
|
||||
private String provider;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public String getCsr() {
|
||||
return csr;
|
||||
}
|
||||
|
||||
private List<String> processList(final String string) {
|
||||
final List<String> list = new ArrayList<>();
|
||||
if (!Strings.isNullOrEmpty(string)) {
|
||||
for (final String address: string.split(",")) {
|
||||
list.add(address.trim());
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public List<String> getAddresses() {
|
||||
return processList(addresses);
|
||||
}
|
||||
|
||||
public List<String> getDomains() {
|
||||
return processList(domains);
|
||||
}
|
||||
|
||||
public Integer getValidityDuration() {
|
||||
return validityDuration;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
if (Strings.isNullOrEmpty(getCsr()) && getDomains().isEmpty()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Please provide the domains or the CSR, none of them are provided");
|
||||
}
|
||||
final Certificate certificate = caManager.issueCertificate(getCsr(), getDomains(), getAddresses(), getValidityDuration(), getProvider());
|
||||
if (certificate == null) {
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to issue client certificate with given provider");
|
||||
}
|
||||
|
||||
final CertificateResponse certificateResponse = new CertificateResponse();
|
||||
try {
|
||||
certificateResponse.setCertificate(CertUtils.x509CertificateToPem(certificate.getClientCertificate()));
|
||||
if (certificate.getPrivateKey() != null) {
|
||||
certificateResponse.setPrivateKey(CertUtils.privateKeyToPem(certificate.getPrivateKey()));
|
||||
}
|
||||
if (certificate.getCaCertificates() != null) {
|
||||
certificateResponse.setCaCertificate(CertUtils.x509CertificatesToPem(certificate.getCaCertificates()));
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
LOG.error("Failed to generate and convert client certificate(s) to PEM due to error: ", e);
|
||||
throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to process and return client certificate");
|
||||
}
|
||||
certificateResponse.setResponseName(getCommandName());
|
||||
setResponseObject(certificateResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_CA_CERTIFICATE_ISSUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "issuing certificate for domain(s)=" + domains + ", ip(s)=" + addresses;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.command.admin.ca;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.CAProviderResponse;
|
||||
import org.apache.cloudstack.api.response.ListResponse;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
|
||||
import com.cloud.user.Account;
|
||||
|
||||
@APICommand(name = ListCAProvidersCmd.APINAME,
|
||||
description = "Lists available certificate authority providers in CloudStack",
|
||||
responseObject = CAProviderResponse.class,
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false,
|
||||
since = "4.11.0",
|
||||
authorized = {RoleType.Admin})
|
||||
public class ListCAProvidersCmd extends BaseCmd {
|
||||
public static final String APINAME = "listCAProviders";
|
||||
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List CA service provider by name")
|
||||
private String name;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.ACCOUNT_ID_SYSTEM;
|
||||
}
|
||||
|
||||
private void setupResponse(final List<CAProvider> providers) {
|
||||
final ListResponse<CAProviderResponse> response = new ListResponse<>();
|
||||
final List<CAProviderResponse> responses = new ArrayList<>();
|
||||
for (final CAProvider provider : providers) {
|
||||
if (provider == null || (getName() != null && !provider.getProviderName().equals(getName()))) {
|
||||
continue;
|
||||
}
|
||||
final CAProviderResponse caProviderResponse = new CAProviderResponse();
|
||||
caProviderResponse.setName(provider.getProviderName());
|
||||
caProviderResponse.setDescription(provider.getDescription());
|
||||
caProviderResponse.setObjectName("caprovider");
|
||||
responses.add(caProviderResponse);
|
||||
}
|
||||
response.setResponses(responses);
|
||||
response.setResponseName(getCommandName());
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
final List<CAProvider> caProviders = caManager.getCaProviders();
|
||||
setupResponse(caProviders);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,90 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.command.admin.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.response.CertificateResponse;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
|
||||
import com.cloud.user.Account;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
@APICommand(name = ListCaCertificateCmd.APINAME,
|
||||
description = "Lists the CA public certificate(s) as support by the configured/provided CA plugin",
|
||||
responseObject = CertificateResponse.class,
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false,
|
||||
since = "4.11.0",
|
||||
authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User})
|
||||
public class ListCaCertificateCmd extends BaseCmd {
|
||||
public static final String APINAME = "listCaCertificate";
|
||||
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
|
||||
private String provider;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
final String caCertificates;
|
||||
try {
|
||||
caCertificates = caManager.getCaCertificate(getProvider());
|
||||
} catch (final IOException e) {
|
||||
throw new CloudRuntimeException("Failed to get CA certificates for given CA provider");
|
||||
}
|
||||
final CertificateResponse certificateResponse = new CertificateResponse("cacertificates");
|
||||
certificateResponse.setCertificate(caCertificates);
|
||||
certificateResponse.setResponseName(getCommandName());
|
||||
setResponseObject(certificateResponse);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return Account.ACCOUNT_TYPE_NORMAL;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.command.admin.ca;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiCommandJobType;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.HostResponse;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.host.Host;
|
||||
|
||||
@APICommand(name = ProvisionCertificateCmd.APINAME,
|
||||
description = "Issues and propagates client certificate on a connected host/agent using configured CA plugin",
|
||||
responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = false,
|
||||
responseHasSensitiveInfo = false,
|
||||
since = "4.11.0",
|
||||
authorized = {RoleType.Admin})
|
||||
public class ProvisionCertificateCmd extends BaseAsyncCmd {
|
||||
public static final String APINAME = "provisionCertificate";
|
||||
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, required = true, entityType = HostResponse.class,
|
||||
description = "The host/agent uuid to which the certificate has to be provisioned (issued and propagated)")
|
||||
private Long hostId;
|
||||
|
||||
@Parameter(name = ApiConstants.RECONNECT, type = CommandType.BOOLEAN,
|
||||
description = "Whether to attempt reconnection with host/agent after successful deployment of certificate. When option is not provided, configured global setting is used")
|
||||
private Boolean reconnect;
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING,
|
||||
description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
|
||||
private String provider;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public Long getHostId() {
|
||||
return hostId;
|
||||
}
|
||||
|
||||
public Boolean getReconnect() {
|
||||
return reconnect;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
final Host host = _resourceService.getHost(getHostId());
|
||||
if (host == null) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId());
|
||||
}
|
||||
|
||||
boolean result = caManager.provisionCertificate(host, getReconnect(), getProvider());
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
response.setSuccess(result);
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_CA_CERTIFICATE_PROVISION;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "provisioning certificate for host id=" + hostId + " using provider=" + provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiCommandJobType getInstanceType() {
|
||||
return ApiCommandJobType.Host;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.command.admin.ca;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.apache.cloudstack.acl.RoleType;
|
||||
import org.apache.cloudstack.api.APICommand;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.BaseAsyncCmd;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
import org.apache.cloudstack.api.Parameter;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.response.SuccessResponse;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
@APICommand(name = RevokeCertificateCmd.APINAME,
|
||||
description = "Revokes certificate using configured CA plugin",
|
||||
responseObject = SuccessResponse.class,
|
||||
requestHasSensitiveInfo = true,
|
||||
responseHasSensitiveInfo = false,
|
||||
since = "4.11.0",
|
||||
authorized = {RoleType.Admin})
|
||||
public class RevokeCertificateCmd extends BaseAsyncCmd {
|
||||
|
||||
public static final String APINAME = "revokeCertificate";
|
||||
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
//////////////// API parameters /////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Parameter(name = ApiConstants.SERIAL, type = BaseCmd.CommandType.STRING, required = true, description = "The certificate serial number, as a hex value")
|
||||
private String serial;
|
||||
|
||||
@Parameter(name = ApiConstants.CN, type = BaseCmd.CommandType.STRING, description = "The certificate CN")
|
||||
private String cn;
|
||||
|
||||
@Parameter(name = ApiConstants.PROVIDER, type = BaseCmd.CommandType.STRING, description = "Name of the CA service provider, otherwise the default configured provider plugin will be used")
|
||||
private String provider;
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////////// Accessors ///////////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
public BigInteger getSerialBigInteger() {
|
||||
if (Strings.isNullOrEmpty(serial)) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Certificate serial cannot be empty");
|
||||
}
|
||||
return new BigInteger(serial, 16);
|
||||
}
|
||||
|
||||
public String getCn() {
|
||||
return cn;
|
||||
}
|
||||
|
||||
public String getProvider() {
|
||||
return provider;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////
|
||||
/////////////// API Implementation///////////////////
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void execute() {
|
||||
boolean result = caManager.revokeCertificate(getSerialBigInteger(), getCn(), getProvider());
|
||||
SuccessResponse response = new SuccessResponse(getCommandName());
|
||||
response.setSuccess(result);
|
||||
setResponseObject(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCommandName() {
|
||||
return APINAME.toLowerCase() + BaseCmd.RESPONSE_SUFFIX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEntityOwnerId() {
|
||||
return CallContext.current().getCallingAccount().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventType() {
|
||||
return EventTypes.EVENT_CA_CERTIFICATE_REVOKE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventDescription() {
|
||||
return "revoking certificate with serial id=" + serial + ", cn=" + cn;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.response;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
import org.apache.cloudstack.api.EntityReference;
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
|
||||
@EntityReference(value = CAProvider.class)
|
||||
public class CAProviderResponse extends BaseResponse {
|
||||
@SerializedName(ApiConstants.NAME)
|
||||
@Param(description = "the CA service provider name")
|
||||
private String name;
|
||||
|
||||
@SerializedName(ApiConstants.DESCRIPTION)
|
||||
@Param(description = "the description of the CA service provider")
|
||||
private String description;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.api.response;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.BaseResponse;
|
||||
|
||||
import com.cloud.serializer.Param;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class CertificateResponse extends BaseResponse {
|
||||
@SerializedName(ApiConstants.CERTIFICATE)
|
||||
@Param(description = "The client certificate")
|
||||
private String certificate = "";
|
||||
|
||||
@SerializedName(ApiConstants.PRIVATE_KEY)
|
||||
@Param(description = "Private key for the certificate")
|
||||
private String privateKey;
|
||||
|
||||
@SerializedName(ApiConstants.CA_CERTIFICATES)
|
||||
@Param(description = "The CA certificate(s)")
|
||||
private String caCertificate;
|
||||
|
||||
public CertificateResponse() {
|
||||
setObjectName("certificates");
|
||||
}
|
||||
|
||||
public CertificateResponse(final String objectName) {
|
||||
setObjectName(objectName);
|
||||
}
|
||||
|
||||
public void setCertificate(final String certificate) {
|
||||
this.certificate = certificate;
|
||||
}
|
||||
|
||||
public void setPrivateKey(final String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public void setCaCertificate(final String caCertificate) {
|
||||
this.caCertificate = caCertificate;
|
||||
}
|
||||
}
|
||||
163
api/src/org/apache/cloudstack/ca/CAManager.java
Normal file
163
api/src/org/apache/cloudstack/ca/CAManager.java
Normal file
@ -0,0 +1,163 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
import org.apache.cloudstack.framework.ca.CAService;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.utils.component.PluggableService;
|
||||
|
||||
public interface CAManager extends CAService, Configurable, PluggableService {
|
||||
|
||||
ConfigKey<String> CAProviderPlugin = new ConfigKey<>("Advanced", String.class,
|
||||
"ca.framework.provider.plugin",
|
||||
"root",
|
||||
"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);
|
||||
|
||||
ConfigKey<Integer> CertKeySize = new ConfigKey<>("Advanced", Integer.class,
|
||||
"ca.framework.cert.keysize",
|
||||
"2048",
|
||||
"The key size to be used for random certificate keypair generation.", true);
|
||||
|
||||
ConfigKey<String> CertSignatureAlgorithm = new ConfigKey<>("Advanced", String.class,
|
||||
"ca.framework.cert.signature.algorithm",
|
||||
"SHA256withRSA",
|
||||
"The default signature algorithm to use for certificate generation.", true);
|
||||
|
||||
|
||||
ConfigKey<Integer> CertValidityPeriod = new ConfigKey<>("Advanced", Integer.class,
|
||||
"ca.framework.cert.validity.period",
|
||||
"365",
|
||||
"The validity period of a client certificate in number of days. Set the value to be more than the expiry alert period.", true);
|
||||
|
||||
ConfigKey<Boolean> AutomaticCertRenewal = new ConfigKey<>("Advanced", Boolean.class,
|
||||
"ca.framework.cert.automatic.renewal",
|
||||
"true",
|
||||
"Enable automatic renewal and provisioning of certificate to agents as supported by the configured CA plugin.", true, ConfigKey.Scope.Cluster);
|
||||
|
||||
ConfigKey<Long> CABackgroundJobDelay = new ConfigKey<>("Advanced", Long.class,
|
||||
"ca.framework.background.task.delay",
|
||||
"3600",
|
||||
"The CA framework background task delay in seconds. Background task runs expiry checks and renews certificate if auto-renewal is enabled.", true);
|
||||
|
||||
ConfigKey<Integer> CertExpiryAlertPeriod = new ConfigKey<>("Advanced", Integer.class,
|
||||
"ca.framework.cert.expiry.alert.period",
|
||||
"15",
|
||||
"The number of days before expiry of a client certificate, the validations are checked. Admins are alerted when auto-renewal is not allowed, otherwise auto-renewal is attempted.", true, ConfigKey.Scope.Cluster);
|
||||
|
||||
/**
|
||||
* Returns a list of available CA provider plugins
|
||||
* @return returns list of CAProvider
|
||||
*/
|
||||
List<CAProvider> getCaProviders();
|
||||
|
||||
/**
|
||||
* Returns a map of active agents/hosts certificates
|
||||
* @return returns a non-null map
|
||||
*/
|
||||
Map<String, X509Certificate> getActiveCertificatesMap();
|
||||
|
||||
/**
|
||||
* Checks whether the configured CA plugin can provision/create certificates
|
||||
* @return returns certificate creation capability
|
||||
*/
|
||||
boolean canProvisionCertificates();
|
||||
|
||||
/**
|
||||
* Returns PEM-encoded chained CA certificate
|
||||
* @param caProvider
|
||||
* @return returns CA certificate chain string
|
||||
*/
|
||||
String getCaCertificate(final String caProvider) throws IOException;
|
||||
|
||||
/**
|
||||
* Issues client Certificate
|
||||
* @param csr
|
||||
* @param ipAddresses
|
||||
* @param domainNames
|
||||
* @param validityDays
|
||||
* @param provider
|
||||
* @return returns Certificate
|
||||
*/
|
||||
Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final Integer validityDays, final String provider);
|
||||
|
||||
/**
|
||||
* Revokes certificate from provided serial and CN
|
||||
* @param certSerial
|
||||
* @param certCn
|
||||
* @return returns success/failure as boolean
|
||||
*/
|
||||
boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String provider);
|
||||
|
||||
/**
|
||||
* Provisions certificate for given active and connected agent host
|
||||
* @param host
|
||||
* @param provider
|
||||
* @return returns success/failure as boolean
|
||||
*/
|
||||
boolean provisionCertificate(final Host host, final Boolean reconnect, final String provider);
|
||||
|
||||
/**
|
||||
* Setups up a new keystore and generates CSR for a host
|
||||
* @param host
|
||||
* @param sshAccessDetails when provided, VirtualRoutingResource uses router proxy to execute commands via SSH in systemvms
|
||||
* @return
|
||||
* @throws AgentUnavailableException
|
||||
* @throws OperationTimedoutException
|
||||
*/
|
||||
String generateKeyStoreAndCsr(final Host host, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException;
|
||||
|
||||
/**
|
||||
* Deploys a Certificate payload to a provided host
|
||||
* @param host
|
||||
* @param certificate
|
||||
* @param reconnect when true the host/agent is reconnected on successful deployment of the certificate
|
||||
* @param sshAccessDetails when provided, VirtualRoutingResource uses router proxy to execute commands via SSH in systemvms
|
||||
* @return
|
||||
* @throws AgentUnavailableException
|
||||
* @throws OperationTimedoutException
|
||||
*/
|
||||
boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException;
|
||||
|
||||
/**
|
||||
* Removes the host from an internal active client/certificate map
|
||||
* @param host
|
||||
*/
|
||||
void purgeHostCertificate(final Host host);
|
||||
|
||||
/**
|
||||
* Sends a CA cert event alert to admins with a subject and a message
|
||||
* @param host
|
||||
* @param subject
|
||||
* @param message
|
||||
*/
|
||||
void sendAlert(final Host host, final String subject, final String message);
|
||||
|
||||
}
|
||||
@ -18,4 +18,10 @@
|
||||
package org.apache.cloudstack.poll;
|
||||
|
||||
public interface BackgroundPollTask extends Runnable {
|
||||
/**
|
||||
* Returns delay in milliseconds between two rounds
|
||||
* When it returns null a default value is used
|
||||
* @return
|
||||
*/
|
||||
Long getDelay();
|
||||
}
|
||||
|
||||
@ -63,6 +63,11 @@
|
||||
<artifactId>cloud-plugin-acl-dynamic-role-based</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-ca-rootca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-plugin-dedicated-resources</artifactId>
|
||||
@ -274,6 +279,11 @@
|
||||
<artifactId>cloud-mom-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-ipc</artifactId>
|
||||
@ -478,6 +488,7 @@
|
||||
<webAppConfig>
|
||||
<contextPath>/client</contextPath>
|
||||
<extraClasspath>${project.build.directory}/utilities/scripts/db/;${project.build.directory}/utilities/scripts/db/db/</extraClasspath>
|
||||
<webInfIncludeJarPattern>.*/cloud.*jar$|.*/classes/.*</webInfIncludeJarPattern>
|
||||
</webAppConfig>
|
||||
|
||||
<systemProperties>
|
||||
|
||||
Binary file not shown.
@ -94,7 +94,7 @@
|
||||
maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
|
||||
clientAuth="false" sslProtocol="TLS"
|
||||
keystoreType="JKS"
|
||||
keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore"
|
||||
keystoreFile="/etc/cloudstack/management/cloud.jks"
|
||||
keystorePass="vmops.com"/>
|
||||
|
||||
<!-- Define an AJP 1.3 Connector on port 20400 -->
|
||||
|
||||
@ -94,7 +94,7 @@
|
||||
maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
|
||||
clientAuth="false" sslProtocol="TLS"
|
||||
keystoreType="JKS"
|
||||
keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore"
|
||||
keystoreFile="/etc/cloudstack/management/cloud.jks"
|
||||
keystorePass="vmops.com"/>
|
||||
|
||||
<!-- Define an AJP 1.3 Connector on port 20400 -->
|
||||
|
||||
@ -40,7 +40,7 @@ CATALINA_TMPDIR="@MSENVIRON@/temp"
|
||||
|
||||
# Use JAVA_OPTS to set java.library.path for libtcnative.so
|
||||
#JAVA_OPTS="-Djava.library.path=/usr/lib64"
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=@MSLOGDIR@ -XX:MaxPermSize=800m -XX:PermSize=512M -Djava.security.properties=/etc/cloudstack/management/java.security.ciphers"
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=@MSLOGDIR@ -XX:MaxPermSize=800m -XX:PermSize=512M -Djava.security.properties=/etc/cloudstack/management/java.security.ciphers"
|
||||
|
||||
# What user should run tomcat
|
||||
TOMCAT_USER="@MSUSER@"
|
||||
|
||||
21
core/resources/META-INF/cloudstack/ca/module.properties
Normal file
21
core/resources/META-INF/cloudstack/ca/module.properties
Normal file
@ -0,0 +1,21 @@
|
||||
#
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
#
|
||||
|
||||
name=ca
|
||||
parent=backend
|
||||
@ -0,0 +1,32 @@
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
>
|
||||
|
||||
<bean class="org.apache.cloudstack.spring.lifecycle.registry.RegistryLifecycle">
|
||||
<property name="registry" ref="caProvidersRegistry" />
|
||||
<property name="typeClass" value="org.apache.cloudstack.framework.ca.CAProvider" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@ -312,4 +312,8 @@
|
||||
<property name="orderConfigDefault" value="IPMITOOL" />
|
||||
</bean>
|
||||
|
||||
<bean id="caProvidersRegistry"
|
||||
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
package com.cloud.agent.api.routing;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.Command;
|
||||
|
||||
@ -46,6 +47,18 @@ public abstract class NetworkElementCommand extends Command {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setAccessDetail(final Map<String, String> details) {
|
||||
if (details == null) {
|
||||
return;
|
||||
}
|
||||
for (final Map.Entry<String, String> detail : details.entrySet()) {
|
||||
if (detail == null) {
|
||||
continue;
|
||||
}
|
||||
setAccessDetail(detail.getKey(), detail.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
public void setAccessDetail(final String name, final String value) {
|
||||
accessDetails.put(name, value);
|
||||
}
|
||||
|
||||
@ -35,6 +35,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.SetupCertificateAnswer;
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeystoreAnswer;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
@ -108,6 +113,14 @@ public class VirtualRoutingResource {
|
||||
return executeQueryCommand(cmd);
|
||||
}
|
||||
|
||||
if (cmd instanceof SetupKeyStoreCommand) {
|
||||
return execute((SetupKeyStoreCommand) cmd);
|
||||
}
|
||||
|
||||
if (cmd instanceof SetupCertificateCommand) {
|
||||
return execute((SetupCertificateCommand) cmd);
|
||||
}
|
||||
|
||||
if (cmd instanceof AggregationControlCommand) {
|
||||
return execute((AggregationControlCommand)cmd);
|
||||
}
|
||||
@ -139,6 +152,37 @@ public class VirtualRoutingResource {
|
||||
}
|
||||
}
|
||||
|
||||
private Answer execute(final SetupKeyStoreCommand cmd) {
|
||||
final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
|
||||
"/usr/local/cloud/systemvm/conf/%s " +
|
||||
"%s %d " +
|
||||
"/usr/local/cloud/systemvm/conf/%s",
|
||||
KeyStoreUtils.defaultKeystoreFile,
|
||||
cmd.getKeystorePassword(),
|
||||
cmd.getValidityDays(),
|
||||
KeyStoreUtils.defaultCsrFile);
|
||||
ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.keyStoreSetupScript, args);
|
||||
return new SetupKeystoreAnswer(result.getDetails());
|
||||
}
|
||||
|
||||
private Answer execute(final SetupCertificateCommand cmd) {
|
||||
final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " +
|
||||
"/usr/local/cloud/systemvm/conf/%s %s " +
|
||||
"/usr/local/cloud/systemvm/conf/%s \"%s\" " +
|
||||
"/usr/local/cloud/systemvm/conf/%s \"%s\" " +
|
||||
"/usr/local/cloud/systemvm/conf/%s \"%s\"",
|
||||
KeyStoreUtils.defaultKeystoreFile,
|
||||
KeyStoreUtils.sshMode,
|
||||
KeyStoreUtils.defaultCertFile,
|
||||
cmd.getEncodedCertificate(),
|
||||
KeyStoreUtils.defaultCaCertFile,
|
||||
cmd.getEncodedCaCertificates(),
|
||||
KeyStoreUtils.defaultPrivateKeyFile,
|
||||
cmd.getEncodedPrivateKey());
|
||||
ExecutionResult result = _vrDeployer.executeInVR(cmd.getRouterAccessIp(), KeyStoreUtils.keyStoreImportScript, args);
|
||||
return new SetupCertificateAnswer(result.isSuccess());
|
||||
}
|
||||
|
||||
private Answer executeQueryCommand(NetworkElementCommand cmd) {
|
||||
if (cmd instanceof CheckRouterCommand) {
|
||||
return execute((CheckRouterCommand)cmd);
|
||||
|
||||
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
|
||||
public class SetupCertificateAnswer extends Answer {
|
||||
public SetupCertificateAnswer(final boolean result) {
|
||||
super(null);
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,99 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.cloud.agent.api.routing.NetworkElementCommand;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
public class SetupCertificateCommand extends NetworkElementCommand {
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private String certificate;
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private String privateKey = "";
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private String caCertificates;
|
||||
|
||||
private boolean handleByAgent = true;
|
||||
|
||||
public SetupCertificateCommand(final Certificate certificate) {
|
||||
super();
|
||||
if (certificate == null) {
|
||||
throw new CloudRuntimeException("A null certificate was provided to setup");
|
||||
}
|
||||
setWait(60);
|
||||
try {
|
||||
this.certificate = CertUtils.x509CertificateToPem(certificate.getClientCertificate());
|
||||
this.caCertificates = CertUtils.x509CertificatesToPem(certificate.getCaCertificates());
|
||||
if (certificate.getPrivateKey() != null) {
|
||||
this.privateKey = CertUtils.privateKeyToPem(certificate.getPrivateKey());
|
||||
}
|
||||
} catch (final IOException e) {
|
||||
throw new CloudRuntimeException("Failed to transform X509 cert to PEM format", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessDetail(final Map<String, String> accessDetails) {
|
||||
handleByAgent = false;
|
||||
super.setAccessDetail(accessDetails);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessDetail(String name, String value) {
|
||||
handleByAgent = false;
|
||||
super.setAccessDetail(name, value);
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public String getCertificate() {
|
||||
return certificate;
|
||||
}
|
||||
|
||||
public String getCaCertificates() {
|
||||
return caCertificates;
|
||||
}
|
||||
|
||||
public String getEncodedPrivateKey() {
|
||||
return privateKey.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
|
||||
}
|
||||
|
||||
public String getEncodedCertificate() {
|
||||
return certificate.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
|
||||
}
|
||||
|
||||
public String getEncodedCaCertificates() {
|
||||
return caCertificates.replace("\n", KeyStoreUtils.certNewlineEncoder).replace(" ", KeyStoreUtils.certSpaceEncoder);
|
||||
}
|
||||
|
||||
public boolean isHandleByAgent() {
|
||||
return handleByAgent;
|
||||
}
|
||||
}
|
||||
75
core/src/org/apache/cloudstack/ca/SetupKeyStoreCommand.java
Normal file
75
core/src/org/apache/cloudstack/ca/SetupKeyStoreCommand.java
Normal file
@ -0,0 +1,75 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.cloud.agent.api.routing.NetworkElementCommand;
|
||||
import com.cloud.utils.PasswordGenerator;
|
||||
|
||||
public class SetupKeyStoreCommand extends NetworkElementCommand {
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private int validityDays;
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private String keystorePassword;
|
||||
|
||||
private boolean handleByAgent = true;
|
||||
|
||||
public SetupKeyStoreCommand(final int validityDays) {
|
||||
super();
|
||||
setWait(60);
|
||||
this.validityDays = validityDays;
|
||||
if (this.validityDays < 1) {
|
||||
this.validityDays = 1;
|
||||
}
|
||||
this.keystorePassword = PasswordGenerator.generateRandomPassword(16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAccessDetail(final Map<String, String> accessDetails) {
|
||||
handleByAgent = false;
|
||||
super.setAccessDetail(accessDetails);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setAccessDetail(String name, String value) {
|
||||
handleByAgent = false;
|
||||
super.setAccessDetail(name, value);
|
||||
}
|
||||
|
||||
public int getValidityDays() {
|
||||
return validityDays;
|
||||
}
|
||||
|
||||
public String getKeystorePassword() {
|
||||
return keystorePassword;
|
||||
}
|
||||
|
||||
public boolean isHandleByAgent() {
|
||||
return handleByAgent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeInSequence() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
37
core/src/org/apache/cloudstack/ca/SetupKeystoreAnswer.java
Normal file
37
core/src/org/apache/cloudstack/ca/SetupKeystoreAnswer.java
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import com.cloud.agent.api.LogLevel;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
public class SetupKeystoreAnswer extends SetupCertificateAnswer {
|
||||
@LogLevel(LogLevel.Log4jLevel.Off)
|
||||
private final String csr;
|
||||
|
||||
public SetupKeystoreAnswer(final String csr) {
|
||||
super(!Strings.isNullOrEmpty(csr));
|
||||
this.csr = csr;
|
||||
}
|
||||
|
||||
public String getCsr() {
|
||||
return csr;
|
||||
}
|
||||
}
|
||||
3
debian/cloudstack-management.postinst
vendored
3
debian/cloudstack-management.postinst
vendored
@ -50,9 +50,6 @@ if [ "$1" = configure ]; then
|
||||
cp -a $OLDCONFDIR/$FILE $NEWCONFDIR/$FILE
|
||||
fi
|
||||
done
|
||||
if [ -f "$OLDCONFDIR/cloud.keystore" ]; then
|
||||
cp -a $OLDCONFDIR/cloud.keystore $NEWCONFDIR/cloudmanagementserver.keystore
|
||||
fi
|
||||
fi
|
||||
|
||||
CONFDIR="/etc/cloudstack/management"
|
||||
|
||||
@ -119,6 +119,11 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
|
||||
VALUES ('Advanced', 'DEFAULT', 'RoleService',
|
||||
'dynamic.apichecker.enabled', 'true');
|
||||
|
||||
-- Enable RootCA auth strictness for fresh deployments
|
||||
INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
|
||||
VALUES ('Advanced', 'DEFAULT', 'RootCAProvider',
|
||||
'ca.plugin.root.auth.strictness', 'true');
|
||||
|
||||
-- Add developer configuration entry; allows management server to be run as a user other than "cloud"
|
||||
INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
|
||||
VALUES ('Advanced', 'DEFAULT', 'management-server',
|
||||
|
||||
@ -98,6 +98,8 @@ public interface NetworkOrchestrationService {
|
||||
|
||||
List<NicProfile> getNicProfiles(VirtualMachine vm);
|
||||
|
||||
Map<String, String> getSystemVMAccessDetails(VirtualMachine vm);
|
||||
|
||||
Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long networkId, DeployDestination dest, ReservationContext context)
|
||||
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;
|
||||
|
||||
|
||||
@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
@ -134,6 +135,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
||||
protected int _monitorId = 0;
|
||||
private final Lock _agentStatusLock = new ReentrantLock();
|
||||
|
||||
@Inject
|
||||
protected CAManager caService;
|
||||
@Inject
|
||||
protected EntityManager _entityMgr;
|
||||
|
||||
@ -223,7 +226,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
||||
// allow core threads to time out even when there are no items in the queue
|
||||
_connectExecutor.allowCoreThreadTimeOut(true);
|
||||
|
||||
_connection = new NioServer("AgentManager", Port.value(), Workers.value() + 10, this);
|
||||
_connection = new NioServer("AgentManager", Port.value(), Workers.value() + 10, this, caService);
|
||||
s_logger.info("Listening on " + Port.value() + " with " + Workers.value() + " workers");
|
||||
|
||||
// executes all agent commands other than cron and ping
|
||||
@ -813,6 +816,7 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
|
||||
s_logger.debug("The next status of agent " + hostId + "is " + nextStatus + ", current status is " + currentStatus);
|
||||
}
|
||||
}
|
||||
caService.purgeHostCertificate(host);
|
||||
}
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
|
||||
@ -495,6 +495,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
||||
}
|
||||
final String ip = ms.getServiceIP();
|
||||
InetAddress addr;
|
||||
int port = Port.value();
|
||||
try {
|
||||
addr = InetAddress.getByName(ip);
|
||||
} catch (final UnknownHostException e) {
|
||||
@ -502,21 +503,21 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
||||
}
|
||||
SocketChannel ch1 = null;
|
||||
try {
|
||||
ch1 = SocketChannel.open(new InetSocketAddress(addr, Port.value()));
|
||||
ch1 = SocketChannel.open(new InetSocketAddress(addr, port));
|
||||
ch1.configureBlocking(false);
|
||||
ch1.socket().setKeepAlive(true);
|
||||
ch1.socket().setSoTimeout(60 * 1000);
|
||||
try {
|
||||
final SSLContext sslContext = Link.initSSLContext(true);
|
||||
sslEngine = sslContext.createSSLEngine(ip, Port.value());
|
||||
SSLContext sslContext = Link.initClientSSLContext();
|
||||
sslEngine = sslContext.createSSLEngine(ip, port);
|
||||
sslEngine.setUseClientMode(true);
|
||||
sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols()));
|
||||
sslEngine.beginHandshake();
|
||||
if (!Link.doHandshake(ch1, sslEngine, true)) {
|
||||
ch1.close();
|
||||
throw new IOException("SSL handshake failed!");
|
||||
throw new IOException(String.format("SSL: Handshake failed with peer management server '%s' on %s:%d ", peerName, ip, port));
|
||||
}
|
||||
s_logger.info("SSL: Handshake done");
|
||||
s_logger.info(String.format("SSL: Handshake done with peer management server '%s' on %s:%d ", peerName, ip, port));
|
||||
} catch (final Exception e) {
|
||||
ch1.close();
|
||||
throw new IOException("SSL: Fail to init SSL! " + e);
|
||||
@ -528,10 +529,12 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
|
||||
_sslEngines.put(peerName, sslEngine);
|
||||
return ch1;
|
||||
} catch (final IOException e) {
|
||||
try {
|
||||
ch1.close();
|
||||
} catch (final IOException ex) {
|
||||
s_logger.error("failed to close failed peer socket: " + ex);
|
||||
if (ch1 != null) {
|
||||
try {
|
||||
ch1.close();
|
||||
} catch (final IOException ex) {
|
||||
s_logger.error("failed to close failed peer socket: " + ex);
|
||||
}
|
||||
}
|
||||
s_logger.warn("Unable to connect to peer management server: " + peerName + ", ip: " + ip + " due to " + e.getMessage(), e);
|
||||
return null;
|
||||
|
||||
@ -22,6 +22,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
@ -39,12 +40,14 @@ import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.agent.api.AttachOrDettachConfigDriveCommand;
|
||||
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
|
||||
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
@ -97,6 +100,7 @@ import com.cloud.agent.api.StopCommand;
|
||||
import com.cloud.agent.api.UnPlugNicAnswer;
|
||||
import com.cloud.agent.api.UnPlugNicCommand;
|
||||
import com.cloud.agent.api.UnregisterVMCommand;
|
||||
import com.cloud.agent.api.routing.NetworkElementCommand;
|
||||
import com.cloud.agent.api.to.DiskTO;
|
||||
import com.cloud.agent.api.to.GPUDeviceTO;
|
||||
import com.cloud.agent.api.to.NicTO;
|
||||
@ -205,6 +209,7 @@ import com.cloud.vm.dao.VMInstanceDao;
|
||||
import com.cloud.vm.snapshot.VMSnapshotManager;
|
||||
import com.cloud.vm.snapshot.VMSnapshotVO;
|
||||
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
|
||||
private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class);
|
||||
@ -284,7 +289,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
@Inject
|
||||
protected UserVmDetailsDao _vmDetailsDao;
|
||||
@Inject
|
||||
ServiceOfferingDao _serviceOfferingDao = null;
|
||||
protected ServiceOfferingDao _serviceOfferingDao = null;
|
||||
@Inject
|
||||
protected CAManager caManager;
|
||||
|
||||
@Inject
|
||||
ConfigDepot _configDepot;
|
||||
@ -1023,7 +1030,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
|
||||
cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())));
|
||||
|
||||
|
||||
vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx);
|
||||
|
||||
work = _workDao.findById(work.getId());
|
||||
@ -1073,6 +1079,23 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Start completed for VM " + vm);
|
||||
}
|
||||
final Host vmHost = _hostDao.findById(destHostId);
|
||||
if (vmHost != null && (VirtualMachine.Type.ConsoleProxy.equals(vm.getType()) ||
|
||||
VirtualMachine.Type.SecondaryStorageVm.equals(vm.getType())) && caManager.canProvisionCertificates()) {
|
||||
final Map<String, String> sshAccessDetails = _networkMgr.getSystemVMAccessDetails(vm);
|
||||
final String csr = caManager.generateKeyStoreAndCsr(vmHost, sshAccessDetails);
|
||||
if (!Strings.isNullOrEmpty(csr)) {
|
||||
final Map<String, String> ipAddressDetails = new HashMap<>(sshAccessDetails);
|
||||
ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME);
|
||||
final Certificate certificate = caManager.issueCertificate(csr, Arrays.asList(vm.getHostName(), vm.getInstanceName()), new ArrayList<>(ipAddressDetails.values()), CAManager.CertValidityPeriod.value(), null);
|
||||
final boolean result = caManager.deployCertificate(vmHost, certificate, false, sshAccessDetails);
|
||||
if (!result) {
|
||||
s_logger.error("Failed to setup certificate for system vm: " + vm.getInstanceName());
|
||||
}
|
||||
} else {
|
||||
s_logger.error("Failed to setup keystore and generate CSR for system vm: " + vm.getInstanceName());
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
|
||||
@ -68,6 +68,7 @@ import com.cloud.agent.api.CheckNetworkCommand;
|
||||
import com.cloud.agent.api.Command;
|
||||
import com.cloud.agent.api.StartupCommand;
|
||||
import com.cloud.agent.api.StartupRoutingCommand;
|
||||
import com.cloud.agent.api.routing.NetworkElementCommand;
|
||||
import com.cloud.agent.api.to.NicTO;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.configuration.ConfigurationManager;
|
||||
@ -215,6 +216,7 @@ import com.cloud.vm.dao.NicSecondaryIpDao;
|
||||
import com.cloud.vm.dao.NicSecondaryIpVO;
|
||||
import com.cloud.vm.dao.UserVmDao;
|
||||
import com.cloud.vm.dao.VMInstanceDao;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
/**
|
||||
* NetworkManagerImpl implements NetworkManager.
|
||||
@ -3488,6 +3490,39 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
|
||||
return profiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSystemVMAccessDetails(final VirtualMachine vm) {
|
||||
final Map<String, String> accessDetails = new HashMap<>();
|
||||
accessDetails.put(NetworkElementCommand.ROUTER_NAME, vm.getInstanceName());
|
||||
String privateIpAddress = null;
|
||||
for (final NicProfile profile : getNicProfiles(vm)) {
|
||||
if (profile == null) {
|
||||
continue;
|
||||
}
|
||||
final Network network = _networksDao.findById(profile.getNetworkId());
|
||||
if (network == null) {
|
||||
continue;
|
||||
}
|
||||
final String address = profile.getIPv4Address();
|
||||
if (network.getTrafficType() == Networks.TrafficType.Control) {
|
||||
accessDetails.put(NetworkElementCommand.ROUTER_IP, address);
|
||||
}
|
||||
if (network.getTrafficType() == Networks.TrafficType.Guest) {
|
||||
accessDetails.put(NetworkElementCommand.ROUTER_GUEST_IP, address);
|
||||
}
|
||||
if (network.getTrafficType() == Networks.TrafficType.Management) {
|
||||
privateIpAddress = address;
|
||||
}
|
||||
if (network.getTrafficType() != null && !Strings.isNullOrEmpty(address)) {
|
||||
accessDetails.put(network.getTrafficType().name(), address);
|
||||
}
|
||||
}
|
||||
if (privateIpAddress != null && Strings.isNullOrEmpty(accessDetails.get(NetworkElementCommand.ROUTER_IP))) {
|
||||
accessDetails.put(NetworkElementCommand.ROUTER_IP, privateIpAddress);
|
||||
}
|
||||
return accessDetails;
|
||||
}
|
||||
|
||||
protected boolean stateTransitTo(final NetworkVO network, final Network.Event e) throws NoTransitionException {
|
||||
return _stateMachine.transitTo(network, e, null, _networksDao);
|
||||
}
|
||||
|
||||
@ -130,6 +130,7 @@
|
||||
<bean id="autoScaleVmProfileDaoImpl" class="com.cloud.network.as.dao.AutoScaleVmProfileDaoImpl" />
|
||||
<bean id="capacityDaoImpl" class="com.cloud.capacity.dao.CapacityDaoImpl" />
|
||||
<bean id="certificateDaoImpl" class="com.cloud.certificate.dao.CertificateDaoImpl" />
|
||||
<bean id="crlDaoImpl" class="com.cloud.certificate.dao.CrlDaoImpl" />
|
||||
<bean id="clusterDaoImpl" class="com.cloud.dc.dao.ClusterDaoImpl" />
|
||||
<bean id="clusterDetailsDaoImpl" class="com.cloud.dc.ClusterDetailsDaoImpl" />
|
||||
<bean id="clusterVSMMapDaoImpl" class="com.cloud.dc.dao.ClusterVSMMapDaoImpl" />
|
||||
|
||||
85
engine/schema/src/com/cloud/certificate/CrlVO.java
Normal file
85
engine/schema/src/com/cloud/certificate/CrlVO.java
Normal file
@ -0,0 +1,85 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.certificate;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
|
||||
import org.apache.cloudstack.api.InternalIdentity;
|
||||
|
||||
@Entity
|
||||
@Table(name = "crl")
|
||||
public class CrlVO implements InternalIdentity {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id = null;
|
||||
|
||||
@Column(name = "serial")
|
||||
private String certSerial;
|
||||
|
||||
@Column(name = "cn")
|
||||
private String certCn;
|
||||
|
||||
@Column(name = "revoker_uuid")
|
||||
private String revokerUuid;
|
||||
|
||||
@Temporal(value = TemporalType.TIMESTAMP)
|
||||
@Column(name = "revoked", updatable = true)
|
||||
private Date revoked;
|
||||
|
||||
public CrlVO() {
|
||||
}
|
||||
|
||||
public CrlVO(final BigInteger certSerial, final String certCn, final String revokerUuid) {
|
||||
this.certSerial = certSerial.toString(16);
|
||||
this.certCn = certCn;
|
||||
this.revokerUuid = revokerUuid;
|
||||
this.revoked = new Date();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public BigInteger getCertSerial() {
|
||||
return new BigInteger(certSerial, 16);
|
||||
}
|
||||
|
||||
public String getCertCn() {
|
||||
return certCn;
|
||||
}
|
||||
|
||||
public String getRevokerUuid() {
|
||||
return revokerUuid;
|
||||
}
|
||||
|
||||
public Date getRevoked() {
|
||||
return revoked;
|
||||
}
|
||||
}
|
||||
28
engine/schema/src/com/cloud/certificate/dao/CrlDao.java
Normal file
28
engine/schema/src/com/cloud/certificate/dao/CrlDao.java
Normal file
@ -0,0 +1,28 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.certificate.dao;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.utils.db.GenericDao;
|
||||
|
||||
public interface CrlDao extends GenericDao<CrlVO, Long> {
|
||||
CrlVO findBySerial(final BigInteger certSerial);
|
||||
CrlVO revokeCertificate(final BigInteger certSerial, final String certCn);
|
||||
}
|
||||
57
engine/schema/src/com/cloud/certificate/dao/CrlDaoImpl.java
Normal file
57
engine/schema/src/com/cloud/certificate/dao/CrlDaoImpl.java
Normal file
@ -0,0 +1,57 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package com.cloud.certificate.dao;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.utils.db.DB;
|
||||
import com.cloud.utils.db.GenericDaoBase;
|
||||
import com.cloud.utils.db.SearchBuilder;
|
||||
import com.cloud.utils.db.SearchCriteria;
|
||||
|
||||
@DB
|
||||
public class CrlDaoImpl extends GenericDaoBase<CrlVO, Long> implements CrlDao {
|
||||
|
||||
private final SearchBuilder<CrlVO> CrlBySerialSearch;
|
||||
|
||||
public CrlDaoImpl() {
|
||||
super();
|
||||
|
||||
CrlBySerialSearch = createSearchBuilder();
|
||||
CrlBySerialSearch.and("certSerial", CrlBySerialSearch.entity().getCertSerial(), SearchCriteria.Op.EQ);
|
||||
CrlBySerialSearch.done();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrlVO findBySerial(final BigInteger certSerial) {
|
||||
if (certSerial == null) {
|
||||
return null;
|
||||
}
|
||||
final SearchCriteria<CrlVO> sc = CrlBySerialSearch.create("certSerial", certSerial.toString(16));
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CrlVO revokeCertificate(final BigInteger certSerial, final String certCn) {
|
||||
final CrlVO revokedCertificate = new CrlVO(certSerial, certCn == null ? "" : certCn, CallContext.current().getCallingUserUuid());
|
||||
return persist(revokedCertificate);
|
||||
}
|
||||
}
|
||||
@ -101,4 +101,6 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
|
||||
List<Long> listClustersByHostTag(String hostTagOnOffering);
|
||||
|
||||
List<HostVO> listByType(Type type);
|
||||
|
||||
HostVO findByIp(String ip);
|
||||
}
|
||||
|
||||
@ -89,6 +89,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
||||
protected SearchBuilder<HostVO> DcPrivateIpAddressSearch;
|
||||
protected SearchBuilder<HostVO> DcStorageIpAddressSearch;
|
||||
protected SearchBuilder<HostVO> PublicIpAddressSearch;
|
||||
protected SearchBuilder<HostVO> AnyIpAddressSearch;
|
||||
|
||||
protected SearchBuilder<HostVO> GuidSearch;
|
||||
protected SearchBuilder<HostVO> DcSearch;
|
||||
@ -216,6 +217,11 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
||||
PublicIpAddressSearch.and("publicIpAddress", PublicIpAddressSearch.entity().getPublicIpAddress(), SearchCriteria.Op.EQ);
|
||||
PublicIpAddressSearch.done();
|
||||
|
||||
AnyIpAddressSearch = createSearchBuilder();
|
||||
AnyIpAddressSearch.or("publicIpAddress", AnyIpAddressSearch.entity().getPublicIpAddress(), SearchCriteria.Op.EQ);
|
||||
AnyIpAddressSearch.or("privateIpAddress", AnyIpAddressSearch.entity().getPrivateIpAddress(), SearchCriteria.Op.EQ);
|
||||
AnyIpAddressSearch.done();
|
||||
|
||||
GuidSearch = createSearchBuilder();
|
||||
GuidSearch.and("guid", GuidSearch.entity().getGuid(), SearchCriteria.Op.EQ);
|
||||
GuidSearch.done();
|
||||
@ -1118,6 +1124,13 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HostVO findByIp(final String ipAddress) {
|
||||
SearchCriteria<HostVO> sc = AnyIpAddressSearch.create();
|
||||
sc.setParameters("publicIpAddress", ipAddress);
|
||||
sc.setParameters("privateIpAddress", ipAddress);
|
||||
return findOneBy(sc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<HostVO> findHypervisorHostInCluster(long clusterId) {
|
||||
|
||||
29
framework/ca/pom.xml
Normal file
29
framework/ca/pom.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<name>Apache CloudStack Framework - Certificate Authority</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-framework</artifactId>
|
||||
<version>4.11.0.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
</project>
|
||||
@ -0,0 +1,93 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.framework.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
public interface CAProvider {
|
||||
|
||||
/**
|
||||
* Method returns capability of the plugin to participate in certificate issuance, revocation and provisioning
|
||||
* @return returns true when CA provider can do certificate lifecycle tasks
|
||||
*/
|
||||
boolean canProvisionCertificates();
|
||||
|
||||
/**
|
||||
* Returns root CA certificate
|
||||
* @return returns concatenated root CA certificate string
|
||||
*/
|
||||
List<X509Certificate> getCaCertificate();
|
||||
|
||||
/**
|
||||
* Issues certificate with provided options
|
||||
* @param domainNames
|
||||
* @param ipAddresses
|
||||
* @param validityDays
|
||||
* @return returns issued certificate
|
||||
*/
|
||||
Certificate issueCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays);
|
||||
|
||||
/**
|
||||
* Issues certificate using given CSR and other options
|
||||
* @param csr
|
||||
* @param domainNames
|
||||
* @param ipAddresses
|
||||
* @param validityDays
|
||||
* @return returns issued certificate using provided CSR and other options
|
||||
*/
|
||||
Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays);
|
||||
|
||||
/**
|
||||
* Revokes certificate using certificate serial and CN
|
||||
* @param certSerial
|
||||
* @param certCn
|
||||
* @return returns true on success
|
||||
*/
|
||||
boolean revokeCertificate(final BigInteger certSerial, final String certCn);
|
||||
|
||||
/**
|
||||
* This method can add/inject custom TrustManagers for client connection validations.
|
||||
* @param sslContext The SSL context used while accepting a client connection
|
||||
* @param remoteAddress
|
||||
* @param certMap
|
||||
* @return returns created SSL engine instance
|
||||
* @throws GeneralSecurityException
|
||||
* @throws IOException
|
||||
*/
|
||||
SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress, final Map<String, X509Certificate> certMap) throws GeneralSecurityException, IOException;
|
||||
|
||||
/**
|
||||
* Returns the unique name of the provider
|
||||
* @return returns provider name
|
||||
*/
|
||||
String getProviderName();
|
||||
|
||||
/**
|
||||
* Returns description about the CA provider plugin
|
||||
* @return returns description
|
||||
*/
|
||||
String getDescription();
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.framework.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
public interface CAService {
|
||||
/**
|
||||
* Returns a SSLEngine to be used for handling client connections
|
||||
* @param context
|
||||
* @param remoteAddress
|
||||
* @return
|
||||
* @throws GeneralSecurityException
|
||||
* @throws IOException
|
||||
*/
|
||||
SSLEngine createSSLEngine(final SSLContext context, final String remoteAddress) throws GeneralSecurityException, IOException;
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.framework.ca;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
public class Certificate {
|
||||
private X509Certificate clientCertificate;
|
||||
private PrivateKey privateKey;
|
||||
private List<X509Certificate> caCertificates;
|
||||
|
||||
public Certificate(final X509Certificate clientCertificate, final PrivateKey privateKey, final List<X509Certificate> caCertificates) {
|
||||
this.clientCertificate = clientCertificate;
|
||||
this.privateKey = privateKey;
|
||||
this.caCertificates = caCertificates;
|
||||
}
|
||||
|
||||
public X509Certificate getClientCertificate() {
|
||||
return clientCertificate;
|
||||
}
|
||||
|
||||
public PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public List<X509Certificate> getCaCertificates() {
|
||||
return caCertificates;
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,7 @@
|
||||
</build>
|
||||
<modules>
|
||||
<module>ipc</module>
|
||||
<module>ca</module>
|
||||
<module>rest</module>
|
||||
<module>events</module>
|
||||
<module>jobs</module>
|
||||
|
||||
@ -499,12 +499,6 @@ else
|
||||
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
|
||||
fi
|
||||
|
||||
if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
|
||||
cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
|
||||
# make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
|
||||
mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
|
||||
fi
|
||||
|
||||
%preun agent
|
||||
/sbin/service cloudstack-agent stop || true
|
||||
if [ "$1" == "0" ] ; then
|
||||
|
||||
@ -464,12 +464,6 @@ else
|
||||
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
|
||||
fi
|
||||
|
||||
if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
|
||||
cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
|
||||
# make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
|
||||
mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
|
||||
fi
|
||||
|
||||
%preun agent
|
||||
/sbin/service cloudstack-agent stop || true
|
||||
if [ "$1" == "0" ] ; then
|
||||
|
||||
@ -464,12 +464,6 @@ else
|
||||
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
|
||||
fi
|
||||
|
||||
if [ -f "%{_sysconfdir}/cloud.rpmsave/management/cloud.keystore" ]; then
|
||||
cp -p %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/%{name}/management/cloudmanagementserver.keystore
|
||||
# make sure we only do this on the first install of this RPM, don't want to overwrite on a reinstall
|
||||
mv %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore %{_sysconfdir}/cloud.rpmsave/management/cloud.keystore.rpmsave
|
||||
fi
|
||||
|
||||
%preun agent
|
||||
/sbin/service cloudstack-agent stop || true
|
||||
if [ "$1" == "0" ] ; then
|
||||
|
||||
@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
|
||||
CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
|
||||
|
||||
|
||||
if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com "
|
||||
if [ -r "/etc/cloudstack/management/cloud.jks" ] ; then
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com "
|
||||
else
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m"
|
||||
fi
|
||||
|
||||
@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
|
||||
CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
|
||||
|
||||
|
||||
if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com "
|
||||
if [ -r "/etc/cloudstack/management/cloud.jks" ] ; then
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m -Djavax.net.ssl.trustStore=/etc/cloudstack/management/cloud.jks -Djavax.net.ssl.trustStorePassword=vmops.com "
|
||||
else
|
||||
JAVA_OPTS="-Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/cloudstack/management/ -XX:PermSize=512M -XX:MaxPermSize=800m"
|
||||
fi
|
||||
|
||||
46
plugins/ca/root-ca/pom.xml
Normal file
46
plugins/ca/root-ca/pom.xml
Normal file
@ -0,0 +1,46 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloud-plugin-ca-rootca</artifactId>
|
||||
<name>Apache CloudStack Plugin - Inbuilt Root Certificate Authority</name>
|
||||
<parent>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloudstack-plugins</artifactId>
|
||||
<version>4.11.0.0-SNAPSHOT</version>
|
||||
<relativePath>../../pom.xml</relativePath>
|
||||
</parent>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-utils</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,18 @@
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
name=root-ca
|
||||
parent=ca
|
||||
@ -0,0 +1,29 @@
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
-->
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
|
||||
>
|
||||
|
||||
<bean id="rootCAProvider" class="org.apache.cloudstack.ca.provider.RootCAProvider">
|
||||
<property name="name" value="RootCAProvider" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
@ -0,0 +1,146 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.ca.provider;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
public final class RootCACustomTrustManager implements X509TrustManager {
|
||||
private static final Logger LOG = Logger.getLogger(RootCACustomTrustManager.class);
|
||||
|
||||
private String clientAddress = "Unknown";
|
||||
private boolean authStrictness = true;
|
||||
private boolean allowExpiredCertificate = true;
|
||||
private CrlDao crlDao;
|
||||
private X509Certificate caCertificate;
|
||||
private Map<String, X509Certificate> activeCertMap;
|
||||
|
||||
public RootCACustomTrustManager(final String clientAddress, final boolean authStrictness, final boolean allowExpiredCertificate, final Map<String, X509Certificate> activeCertMap, final X509Certificate caCertificate, final CrlDao crlDao) {
|
||||
if (!Strings.isNullOrEmpty(clientAddress)) {
|
||||
this.clientAddress = clientAddress.replace("/", "").split(":")[0];
|
||||
}
|
||||
this.authStrictness = authStrictness;
|
||||
this.allowExpiredCertificate = allowExpiredCertificate;
|
||||
this.activeCertMap = activeCertMap;
|
||||
this.caCertificate = caCertificate;
|
||||
this.crlDao = crlDao;
|
||||
}
|
||||
|
||||
private void printCertificateChain(final X509Certificate[] certificates, final String s) throws CertificateException {
|
||||
if (certificates == null) {
|
||||
return;
|
||||
}
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("A client/agent attempting connection from address=").append(clientAddress).append(" has presented these certificate(s):");
|
||||
int counter = 1;
|
||||
for (final X509Certificate certificate: certificates) {
|
||||
builder.append("\nCertificate [").append(counter++).append("] :");
|
||||
builder.append(String.format("\n Serial: %x", certificate.getSerialNumber()));
|
||||
builder.append("\n Not Before:" + certificate.getNotBefore());
|
||||
builder.append("\n Not After:" + certificate.getNotAfter());
|
||||
builder.append("\n Signature Algorithm:" + certificate.getSigAlgName());
|
||||
builder.append("\n Version:" + certificate.getVersion());
|
||||
builder.append("\n Subject DN:" + certificate.getSubjectDN());
|
||||
builder.append("\n Issuer DN:" + certificate.getIssuerDN());
|
||||
builder.append("\n Alternative Names:" + certificate.getSubjectAlternativeNames());
|
||||
}
|
||||
LOG.debug(builder.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate[] certificates, final String s) throws CertificateException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
printCertificateChain(certificates, s);
|
||||
}
|
||||
if (!authStrictness) {
|
||||
return;
|
||||
}
|
||||
if (certificates == null || certificates.length < 1 || certificates[0] == null) {
|
||||
throw new CertificateException("In strict auth mode, certificate(s) are expected from client:" + clientAddress);
|
||||
}
|
||||
final X509Certificate primaryClientCertificate = certificates[0];
|
||||
|
||||
// Revocation check
|
||||
final BigInteger serialNumber = primaryClientCertificate.getSerialNumber();
|
||||
if (serialNumber == null || crlDao.findBySerial(serialNumber) != null) {
|
||||
final String errorMsg = String.format("Client is using revoked certificate of serial=%x, subject=%s from address=%s",
|
||||
primaryClientCertificate.getSerialNumber(), primaryClientCertificate.getSubjectDN(), clientAddress);
|
||||
LOG.error(errorMsg);
|
||||
throw new CertificateException(errorMsg);
|
||||
}
|
||||
|
||||
// Validity check
|
||||
if (!allowExpiredCertificate) {
|
||||
try {
|
||||
primaryClientCertificate.checkValidity();
|
||||
} catch (final CertificateExpiredException | CertificateNotYetValidException e) {
|
||||
final String errorMsg = String.format("Client certificate has expired with serial=%x, subject=%s from address=%s",
|
||||
primaryClientCertificate.getSerialNumber(), primaryClientCertificate.getSubjectDN(), clientAddress);
|
||||
LOG.error(errorMsg);
|
||||
throw new CertificateException(errorMsg); }
|
||||
}
|
||||
|
||||
// Ownership check
|
||||
boolean certMatchesOwnership = false;
|
||||
if (primaryClientCertificate.getSubjectAlternativeNames() != null) {
|
||||
for (final List<?> list : primaryClientCertificate.getSubjectAlternativeNames()) {
|
||||
if (list != null && list.size() == 2 && list.get(1) instanceof String) {
|
||||
final String alternativeName = (String) list.get(1);
|
||||
if (clientAddress.equals(alternativeName)) {
|
||||
certMatchesOwnership = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!certMatchesOwnership) {
|
||||
final String errorMsg = "Certificate ownership verification failed for client: " + clientAddress;
|
||||
LOG.error(errorMsg);
|
||||
throw new CertificateException(errorMsg);
|
||||
}
|
||||
if (activeCertMap != null && !Strings.isNullOrEmpty(clientAddress)) {
|
||||
activeCertMap.put(clientAddress, primaryClientCertificate);
|
||||
}
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Client/agent connection from ip=" + clientAddress + " has been validated and trusted.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
if (!authStrictness) {
|
||||
return null;
|
||||
}
|
||||
return new X509Certificate[]{caCertificate};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,465 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.ca.provider;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.SignatureException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.bouncycastle.jce.PKCS10CertificationRequest;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import com.cloud.utils.db.DbProperties;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.utils.nio.Link;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
public final class RootCAProvider extends AdapterBase implements CAProvider, Configurable {
|
||||
private static final Logger LOG = Logger.getLogger(RootCAProvider.class);
|
||||
|
||||
public static final Integer caValidityYears = 30;
|
||||
public static final String caAlias = "root";
|
||||
public static final String managementAlias = "management";
|
||||
|
||||
private static KeyPair caKeyPair = null;
|
||||
private static X509Certificate caCertificate = null;
|
||||
|
||||
@Inject
|
||||
private ConfigurationDao configDao;
|
||||
@Inject
|
||||
private CrlDao crlDao;
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
/////////////// Root CA Settings ///////////////////
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
private static ConfigKey<String> rootCAPrivateKey = new ConfigKey<>("Hidden", String.class,
|
||||
"ca.plugin.root.private.key",
|
||||
null,
|
||||
"The ROOT CA private key.", true);
|
||||
|
||||
private static ConfigKey<String> rootCAPublicKey = new ConfigKey<>("Hidden", String.class,
|
||||
"ca.plugin.root.public.key",
|
||||
null,
|
||||
"The ROOT CA public key.", true);
|
||||
|
||||
private static ConfigKey<String> rootCACertificate = new ConfigKey<>("Hidden", String.class,
|
||||
"ca.plugin.root.ca.certificate",
|
||||
null,
|
||||
"The ROOT CA certificate.", true);
|
||||
|
||||
private static ConfigKey<String> rootCAIssuerDN = new ConfigKey<>("Advanced", String.class,
|
||||
"ca.plugin.root.issuer.dn",
|
||||
"CN=ca.cloudstack.apache.org",
|
||||
"The ROOT CA issuer distinguished name.", true);
|
||||
|
||||
protected static ConfigKey<Boolean> rootCAAuthStrictness = new ConfigKey<>("Advanced", Boolean.class,
|
||||
"ca.plugin.root.auth.strictness",
|
||||
"false",
|
||||
"Set client authentication strictness, setting to true will enforce and require client certificate for authentication in applicable CA providers.", true);
|
||||
|
||||
private static ConfigKey<Boolean> rootCAAllowExpiredCert = new ConfigKey<>("Advanced", Boolean.class,
|
||||
"ca.plugin.root.allow.expired.cert",
|
||||
"true",
|
||||
"When set to true, it will allow expired client certificate during SSL handshake.", true);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
/////////////// Root CA Private Methods ///////////////////
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
private Certificate generateCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException {
|
||||
if (domainNames == null || domainNames.size() < 1 || Strings.isNullOrEmpty(domainNames.get(0))) {
|
||||
throw new CloudRuntimeException("No domain name is specified, cannot generate certificate");
|
||||
}
|
||||
final String subject = "CN=" + domainNames.get(0);
|
||||
|
||||
final KeyPair keyPair = CertUtils.generateRandomKeyPair(CAManager.CertKeySize.value());
|
||||
final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
|
||||
caCertificate,
|
||||
caKeyPair.getPrivate(),
|
||||
keyPair.getPublic(),
|
||||
subject,
|
||||
CAManager.CertSignatureAlgorithm.value(),
|
||||
validityDays,
|
||||
domainNames,
|
||||
ipAddresses);
|
||||
return new Certificate(clientCertificate, keyPair.getPrivate(), Collections.singletonList(caCertificate));
|
||||
}
|
||||
|
||||
private Certificate generateCertificateUsingCsr(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, CertificateException, SignatureException, IOException, OperatorCreationException {
|
||||
PemObject pemObject = null;
|
||||
|
||||
try {
|
||||
final PemReader pemReader = new PemReader(new StringReader(csr));
|
||||
pemObject = pemReader.readPemObject();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to read provided CSR string as a PEM object", e);
|
||||
}
|
||||
|
||||
if (pemObject == null) {
|
||||
throw new CloudRuntimeException("Unable to read/process CSR: " + csr);
|
||||
}
|
||||
|
||||
final PKCS10CertificationRequest request = new PKCS10CertificationRequest(pemObject.getContent());
|
||||
|
||||
final X509Certificate clientCertificate = CertUtils.generateV3Certificate(
|
||||
caCertificate, caKeyPair.getPrivate(),
|
||||
request.getPublicKey(),
|
||||
request.getCertificationRequestInfo().getSubject().toString(),
|
||||
CAManager.CertSignatureAlgorithm.value(),
|
||||
validityDays,
|
||||
domainNames,
|
||||
ipAddresses);
|
||||
return new Certificate(clientCertificate, null, Collections.singletonList(caCertificate));
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
/////////////// Root CA API Handlers ///////////////////
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public boolean canProvisionCertificates() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<X509Certificate> getCaCertificate() {
|
||||
return Collections.singletonList(caCertificate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate issueCertificate(final List<String> domainNames, final List<String> ipAddresses, final int validityDays) {
|
||||
try {
|
||||
return generateCertificate(domainNames, ipAddresses, validityDays);
|
||||
} catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) {
|
||||
LOG.error("Failed to create client certificate, due to: ", e);
|
||||
throw new CloudRuntimeException("Failed to generate certificate due to:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final int validityDays) {
|
||||
try {
|
||||
return generateCertificateUsingCsr(csr, domainNames, ipAddresses, validityDays);
|
||||
} catch (final CertificateException | IOException | SignatureException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | OperatorCreationException e) {
|
||||
LOG.error("Failed to generate certificate from CSR: ", e);
|
||||
throw new CloudRuntimeException("Failed to generate certificate using CSR due to:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean revokeCertificate(final BigInteger certSerial, final String certCn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
/////////////// Root CA Trust Management ///////////////////
|
||||
////////////////////////////////////////////////////////////
|
||||
|
||||
private char[] getCaKeyStorePassphrase() {
|
||||
return KeyStoreUtils.defaultKeystorePassphrase;
|
||||
}
|
||||
|
||||
private KeyStore getCaKeyStore() throws CertificateException, NoSuchAlgorithmException, IOException, KeyStoreException {
|
||||
final KeyStore ks = KeyStore.getInstance("JKS");
|
||||
ks.load(null, null);
|
||||
if (caKeyPair != null && caCertificate != null) {
|
||||
ks.setKeyEntry(caAlias, caKeyPair.getPrivate(), getCaKeyStorePassphrase(), new X509Certificate[]{caCertificate});
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return ks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress, final Map<String, X509Certificate> certMap) throws KeyManagementException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException {
|
||||
final KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
||||
final TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
||||
|
||||
final KeyStore ks = getCaKeyStore();
|
||||
kmf.init(ks, getCaKeyStorePassphrase());
|
||||
tmf.init(ks);
|
||||
|
||||
final boolean authStrictness = rootCAAuthStrictness.value();
|
||||
final boolean allowExpiredCertificate = rootCAAllowExpiredCert.value();
|
||||
|
||||
TrustManager[] tms = new TrustManager[]{new RootCACustomTrustManager(remoteAddress, authStrictness, allowExpiredCertificate, certMap, caCertificate, crlDao)};
|
||||
sslContext.init(kmf.getKeyManagers(), tms, new SecureRandom());
|
||||
final SSLEngine sslEngine = sslContext.createSSLEngine();
|
||||
sslEngine.setWantClientAuth(authStrictness);
|
||||
return sslEngine;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
/////////////// Root CA Config ///////////////////
|
||||
//////////////////////////////////////////////////
|
||||
|
||||
private char[] findKeyStorePassphrase() {
|
||||
char[] passphrase = KeyStoreUtils.defaultKeystorePassphrase;
|
||||
final String configuredPassphrase = DbProperties.getDbProperties().getProperty("db.cloud.keyStorePassphrase");
|
||||
if (configuredPassphrase != null) {
|
||||
passphrase = configuredPassphrase.toCharArray();
|
||||
}
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
private boolean createManagementServerKeystore(final String keyStoreFilePath, final char[] passphrase) {
|
||||
final Certificate managementServerCertificate = issueCertificate(Collections.singletonList(NetUtils.getHostName()),
|
||||
Collections.singletonList(NetUtils.getDefaultHostIp()), caValidityYears * 365);
|
||||
if (managementServerCertificate == null || managementServerCertificate.getPrivateKey() == null) {
|
||||
throw new CloudRuntimeException("Failed to generate certificate and setup management server keystore");
|
||||
}
|
||||
LOG.info("Creating new management server certificate and keystore");
|
||||
try {
|
||||
final KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
keyStore.load(null, null);
|
||||
keyStore.setCertificateEntry(caAlias, caCertificate);
|
||||
keyStore.setKeyEntry(managementAlias, managementServerCertificate.getPrivateKey(), passphrase,
|
||||
new X509Certificate[]{managementServerCertificate.getClientCertificate(), caCertificate});
|
||||
final String tmpFile = KeyStoreUtils.defaultTmpKeyStoreFile;
|
||||
final FileOutputStream stream = new FileOutputStream(tmpFile);
|
||||
keyStore.store(stream, passphrase);
|
||||
stream.close();
|
||||
KeyStoreUtils.copyKeystore(keyStoreFilePath, tmpFile);
|
||||
LOG.debug("Saved default root CA (server) keystore file at:" + keyStoreFilePath);
|
||||
} catch (final CertificateException | NoSuchAlgorithmException | KeyStoreException | IOException e) {
|
||||
LOG.error("Failed to save root CA (server) keystore due to exception: ", e);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkManagementServerKeystore() {
|
||||
final File confFile = PropertiesUtil.findConfigFile("db.properties");
|
||||
if (confFile == null) {
|
||||
return false;
|
||||
}
|
||||
final char[] passphrase = findKeyStorePassphrase();
|
||||
final String keystorePath = confFile.getParent() + "/" + KeyStoreUtils.defaultKeystoreFile;
|
||||
final File keystoreFile = new File(keystorePath);
|
||||
if (keystoreFile.exists()) {
|
||||
try {
|
||||
final KeyStore msKeystore = Link.loadKeyStore(new FileInputStream(keystorePath), passphrase);
|
||||
try {
|
||||
final java.security.cert.Certificate[] msCertificates = msKeystore.getCertificateChain(managementAlias);
|
||||
if (msCertificates != null && msCertificates.length > 1) {
|
||||
msCertificates[0].verify(caKeyPair.getPublic());
|
||||
((X509Certificate)msCertificates[0]).checkValidity();
|
||||
return true;
|
||||
}
|
||||
} catch (final CertificateException | NoSuchAlgorithmException | InvalidKeyException | NoSuchProviderException | SignatureException e) {
|
||||
LOG.info("Renewing management server keystore, current certificate has expired");
|
||||
return createManagementServerKeystore(keystoreFile.getAbsolutePath(), passphrase);
|
||||
}
|
||||
} catch (final GeneralSecurityException | IOException e) {
|
||||
LOG.error("Failed to read current management server keystore, renewing keystore!");
|
||||
}
|
||||
}
|
||||
return createManagementServerKeystore(keystoreFile.getAbsolutePath(), passphrase);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
/////////////// Root CA Setup ///////////////////
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
private boolean saveNewRootCAKeypair() {
|
||||
try {
|
||||
LOG.debug("Generating root CA public/private keys");
|
||||
final KeyPair keyPair = CertUtils.generateRandomKeyPair(2 * CAManager.CertKeySize.value());
|
||||
if (!configDao.update(rootCAPublicKey.key(), rootCAPublicKey.category(), CertUtils.publicKeyToPem(keyPair.getPublic()))) {
|
||||
LOG.error("Failed to save RootCA public key");
|
||||
}
|
||||
if (!configDao.update(rootCAPrivateKey.key(), rootCAPrivateKey.category(), CertUtils.privateKeyToPem(keyPair.getPrivate()))) {
|
||||
LOG.error("Failed to save RootCA private key");
|
||||
}
|
||||
} catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
|
||||
LOG.error("Failed to generate/save RootCA private/public keys due to exception:", e);
|
||||
}
|
||||
return loadRootCAKeyPair();
|
||||
}
|
||||
|
||||
private boolean saveNewRootCACertificate() {
|
||||
if (caKeyPair == null) {
|
||||
throw new CloudRuntimeException("Cannot issue self-signed root CA certificate as CA keypair is not initialized");
|
||||
}
|
||||
try {
|
||||
LOG.debug("Generating root CA certificate");
|
||||
final X509Certificate rootCaCertificate = CertUtils.generateV1Certificate(
|
||||
caKeyPair,
|
||||
rootCAIssuerDN.value(),
|
||||
rootCAIssuerDN.value(),
|
||||
caValidityYears,
|
||||
CAManager.CertSignatureAlgorithm.value());
|
||||
if (!configDao.update(rootCACertificate.key(), rootCACertificate.category(), CertUtils.x509CertificateToPem(rootCaCertificate))) {
|
||||
LOG.error("Failed to update RootCA public/x509 certificate");
|
||||
}
|
||||
} catch (final CertificateException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException | InvalidKeyException | OperatorCreationException | IOException e) {
|
||||
LOG.error("Failed to generate RootCA certificate from private/public keys due to exception:", e);
|
||||
return false;
|
||||
}
|
||||
return loadRootCACertificate();
|
||||
}
|
||||
|
||||
private boolean loadRootCAKeyPair() {
|
||||
if (Strings.isNullOrEmpty(rootCAPublicKey.value()) || Strings.isNullOrEmpty(rootCAPrivateKey.value())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
caKeyPair = new KeyPair(CertUtils.pemToPublicKey(rootCAPublicKey.value()), CertUtils.pemToPrivateKey(rootCAPrivateKey.value()));
|
||||
} catch (InvalidKeySpecException | IOException e) {
|
||||
LOG.error("Failed to load saved RootCA private/public keys due to exception:", e);
|
||||
return false;
|
||||
}
|
||||
return caKeyPair.getPrivate() != null && caKeyPair.getPublic() != null;
|
||||
}
|
||||
|
||||
private boolean loadRootCACertificate() {
|
||||
if (Strings.isNullOrEmpty(rootCACertificate.value())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
caCertificate = CertUtils.pemToX509Certificate(rootCACertificate.value());
|
||||
caCertificate.verify(caKeyPair.getPublic());
|
||||
} catch (final IOException | CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
|
||||
LOG.error("Failed to load saved RootCA certificate due to exception:", e);
|
||||
return false;
|
||||
}
|
||||
return caCertificate != null;
|
||||
}
|
||||
|
||||
private boolean setupCA() {
|
||||
if (!loadRootCAKeyPair() && !saveNewRootCAKeypair()) {
|
||||
LOG.error("Failed to save and load root CA keypair");
|
||||
return false;
|
||||
}
|
||||
if (!loadRootCACertificate() && !saveNewRootCACertificate()) {
|
||||
LOG.error("Failed to save and load root CA certificate");
|
||||
return false;
|
||||
}
|
||||
if (!checkManagementServerKeystore()) {
|
||||
LOG.error("Failed to check and configure management server keystore");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
return loadRootCAKeyPair() && loadRootCAKeyPair() && checkManagementServerKeystore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
|
||||
super.configure(name, params);
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
final GlobalLock caLock = GlobalLock.getInternLock("RootCAProviderSetup");
|
||||
try {
|
||||
if (caLock.lock(5 * 60)) {
|
||||
try {
|
||||
return setupCA();
|
||||
} finally {
|
||||
caLock.unlock();
|
||||
}
|
||||
} else {
|
||||
LOG.error("Failed to grab lock and setup CA, startup method will try to load the CA certificate and keypair.");
|
||||
}
|
||||
} finally {
|
||||
caLock.releaseRef();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
/////////////// Root CA Descriptors ///////////////////
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return RootCAProvider.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[]{
|
||||
rootCAPrivateKey,
|
||||
rootCAPublicKey,
|
||||
rootCACertificate,
|
||||
rootCAIssuerDN,
|
||||
rootCAAuthStrictness,
|
||||
rootCAAllowExpiredCert
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProviderName() {
|
||||
return "root";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return "CloudStack's Root CA provider plugin";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca.provider;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RootCACustomTrustManagerTest {
|
||||
|
||||
@Mock
|
||||
private CrlDao crlDao;
|
||||
private KeyPair caKeypair;
|
||||
private KeyPair clientKeypair;
|
||||
private X509Certificate caCertificate;
|
||||
private X509Certificate expiredClientCertificate;
|
||||
private String clientIp = "1.2.3.4";
|
||||
private Map<String, X509Certificate> certMap = new HashMap<>();
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
certMap.clear();
|
||||
caKeypair = CertUtils.generateRandomKeyPair(1024);
|
||||
clientKeypair = CertUtils.generateRandomKeyPair(1024);
|
||||
caCertificate = CertUtils.generateV1Certificate(caKeypair, "CN=ca", "CN=ca", 1,
|
||||
"SHA256withRSA");
|
||||
expiredClientCertificate = CertUtils.generateV3Certificate(caCertificate, caKeypair.getPrivate(), clientKeypair.getPublic(),
|
||||
"CN=cloudstack.apache.org", "SHA256withRSA", 0, Collections.singletonList("cloudstack.apache.org"), Collections.singletonList(clientIp));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthNotStrict() throws Exception {
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, false, true, certMap, caCertificate, crlDao);
|
||||
trustManager.checkClientTrusted(null, null);
|
||||
Assert.assertNull(trustManager.getAcceptedIssuers());
|
||||
}
|
||||
|
||||
@Test(expected = CertificateException.class)
|
||||
public void testAuthStrictWithInvalidCert() throws Exception {
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
|
||||
trustManager.checkClientTrusted(null, null);
|
||||
}
|
||||
|
||||
@Test(expected = CertificateException.class)
|
||||
public void testAuthStrictWithRevokedCert() throws Exception {
|
||||
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(new CrlVO());
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
|
||||
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
|
||||
}
|
||||
|
||||
@Test(expected = CertificateException.class)
|
||||
public void testAuthStrictWithInvalidCertOwnership() throws Exception {
|
||||
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
|
||||
trustManager.checkClientTrusted(new X509Certificate[]{caCertificate}, "RSA");
|
||||
}
|
||||
|
||||
@Test(expected = CertificateException.class)
|
||||
public void testAuthStrictWithDenyExpiredCertAndOwnership() throws Exception {
|
||||
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, false, certMap, caCertificate, crlDao);
|
||||
trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthStrictWithAllowExpiredCertAndOwnership() throws Exception {
|
||||
Mockito.when(crlDao.findBySerial(Mockito.any(BigInteger.class))).thenReturn(null);
|
||||
final RootCACustomTrustManager trustManager = new RootCACustomTrustManager(clientIp, true, true, certMap, caCertificate, crlDao);
|
||||
Assert.assertTrue(trustManager.getAcceptedIssuers() != null);
|
||||
Assert.assertTrue(trustManager.getAcceptedIssuers().length == 1);
|
||||
Assert.assertEquals(trustManager.getAcceptedIssuers()[0], caCertificate);
|
||||
trustManager.checkClientTrusted(new X509Certificate[]{expiredClientCertificate}, "RSA");
|
||||
Assert.assertTrue(certMap.containsKey(clientIp));
|
||||
Assert.assertEquals(certMap.get(clientIp), expiredClientCertificate);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca.provider;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.cloudstack.utils.security.SSLUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RootCAProviderTest {
|
||||
|
||||
private KeyPair caKeyPair;
|
||||
private X509Certificate caCertificate;
|
||||
|
||||
private RootCAProvider provider;
|
||||
|
||||
private void addField(final RootCAProvider provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field f = RootCAProvider.class.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
f.set(provider, o);
|
||||
}
|
||||
|
||||
private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field f = ConfigKey.class.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
f.set(configKey, o);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
caKeyPair = CertUtils.generateRandomKeyPair(1024);
|
||||
caCertificate = CertUtils.generateV1Certificate(caKeyPair, "CN=ca", "CN=ca", 1, "SHA256withRSA");
|
||||
|
||||
provider = new RootCAProvider();
|
||||
|
||||
addField(provider, "caKeyPair", caKeyPair);
|
||||
addField(provider, "caCertificate", caCertificate);
|
||||
addField(provider, "caKeyPair", caKeyPair);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanProvisionCertificates() {
|
||||
Assert.assertTrue(provider.canProvisionCertificates());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetCaCertificate() {
|
||||
Assert.assertTrue(provider.getCaCertificate().size() == 1);
|
||||
Assert.assertEquals(provider.getCaCertificate().get(0), caCertificate);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueCertificateWithoutCsr() throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
final Certificate certificate = provider.issueCertificate(Arrays.asList("domain1.com", "domain2.com"), null, 1);
|
||||
Assert.assertTrue(certificate != null);
|
||||
Assert.assertTrue(certificate.getPrivateKey() != null);
|
||||
Assert.assertEquals(certificate.getCaCertificates().get(0), caCertificate);
|
||||
Assert.assertEquals(certificate.getClientCertificate().getIssuerDN(), caCertificate.getIssuerDN());
|
||||
Assert.assertTrue(certificate.getClientCertificate().getNotAfter().before(new DateTime().plusDays(1).toDate()));
|
||||
certificate.getClientCertificate().verify(caCertificate.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueCertificateWithCsr() throws NoSuchProviderException, CertificateException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
final String csr = "-----BEGIN NEW CERTIFICATE REQUEST-----\n" +
|
||||
"MIICxTCCAa0CAQAwUDETMBEGA1UEBhMKY2xvdWRzdGFjazETMBEGA1UEChMKY2xvdWRzdGFjazET\n" +
|
||||
"MBEGA1UECxMKY2xvdWRzdGFjazEPMA0GA1UEAxMGdi0xLVZNMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" +
|
||||
"AQ8AMIIBCgKCAQEAhi3hOrt/p0hUmoW2A+2gFAMxSINItRrHfQ6VUnHhYKZGcTN9honVFuu30tz7\n" +
|
||||
"oSLUUx1laWEWLlIozpUcPSjOuPa5a0JS8kjplMd8DLfLNeQ6gcuEWznMRJqCaKM72qn/FAK3r11l\n" +
|
||||
"2NofEfWbHU5QVQ5CsYF0JndspLcnmf0tnmreAzz6vlSEPQd4g2hTSsPb72eAqYd0eJnl2oXe7cF3\n" +
|
||||
"iemg6/lWoxlh8njVFDKJ5ibNQA/RSc5syzzaQ8fn/AkZlChR5pml47elfC3GuqetfZPAEP4rebXV\n" +
|
||||
"zEw+UVbMo5bWx4AYm1S2HxhmsWC/1J5oxluZDtC6tjMqnkKQze8HbQIDAQABoDAwLgYJKoZIhvcN\n" +
|
||||
"AQkOMSEwHzAdBgNVHQ4EFgQUdgA1C/7vW3lUcb/dnolGjZB55/AwDQYJKoZIhvcNAQELBQADggEB\n" +
|
||||
"AH6ynWbyW5o4h2yEvmcr+upmu/LZYkpfwIWIo+dfrHX9OHu0rhHDIgMgqEStWzrOfhAkcEocQo21\n" +
|
||||
"E4Q39nECO+cgTCQ1nfH5BVqaMEg++n6tqXBwLmAQJkftEmB+YUPFB9OGn5TQY9Pcnof95Y8xnvtR\n" +
|
||||
"0DvVQa9RM9IsqxgvU4wQCcaNHuEC46Wzo7lyYJ6p//GLw8UQnHxsWktt8U+vyaqXjOvz0+nJobUz\n" +
|
||||
"Jv7r7DFkOwgS6ObBczaZsv1yx2YklcKfbsI7xVsvZAXFey2RsvSJi1QPEJC5XbwDenWnCSrPfjJg\n" +
|
||||
"SLJ0p9tV70D6v07r1OOmBtvU5AH4N+vioAZA0BE=\n" +
|
||||
"-----END NEW CERTIFICATE REQUEST-----\n";
|
||||
final Certificate certificate = provider.issueCertificate(csr, Arrays.asList("v-1-VM", "domain1.com", "domain2.com"), null, 1);
|
||||
Assert.assertTrue(certificate != null);
|
||||
Assert.assertTrue(certificate.getPrivateKey() == null);
|
||||
Assert.assertEquals(certificate.getCaCertificates().get(0), caCertificate);
|
||||
Assert.assertTrue(certificate.getClientCertificate().getSubjectDN().toString().startsWith("CN=v-1-VM,"));
|
||||
certificate.getClientCertificate().verify(caCertificate.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevokeCertificate() throws Exception {
|
||||
Assert.assertTrue(provider.revokeCertificate(CertUtils.generateRandomBigInt(), "anyString"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSSLEngineWithoutAuthStrictness() throws Exception {
|
||||
overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "false");
|
||||
final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
|
||||
Assert.assertFalse(e.getUseClientMode());
|
||||
Assert.assertFalse(e.getWantClientAuth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateSSLEngineWithAuthStrictness() throws Exception {
|
||||
overrideDefaultConfigValue(RootCAProvider.rootCAAuthStrictness, "_defaultValue", "true");
|
||||
final SSLEngine e = provider.createSSLEngine(SSLUtils.getSSLContext(), "/1.2.3.4:5678", null);
|
||||
Assert.assertFalse(e.getUseClientMode());
|
||||
Assert.assertTrue(e.getWantClientAuth());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetProviderName() throws Exception {
|
||||
Assert.assertEquals(provider.getProviderName(), "root");
|
||||
}
|
||||
|
||||
}
|
||||
@ -20,6 +20,9 @@ import java.util.Map;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.CheckHealthCommand;
|
||||
import com.cloud.agent.api.CheckNetworkCommand;
|
||||
@ -52,6 +55,10 @@ public interface MockAgentManager extends Manager {
|
||||
|
||||
Answer pingTest(PingTestCommand cmd);
|
||||
|
||||
Answer setupKeyStore(SetupKeyStoreCommand cmd);
|
||||
|
||||
Answer setupCertificate(SetupCertificateCommand cmd);
|
||||
|
||||
MockHost getHost(String guid);
|
||||
|
||||
Answer maintain(MaintainCommand cmd);
|
||||
|
||||
@ -31,7 +31,10 @@ import java.util.regex.PatternSyntaxException;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import com.cloud.user.AccountManager;
|
||||
import org.apache.cloudstack.ca.SetupCertificateAnswer;
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeystoreAnswer;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.springframework.stereotype.Component;
|
||||
@ -62,6 +65,7 @@ import com.cloud.simulator.MockHostVO;
|
||||
import com.cloud.simulator.MockVMVO;
|
||||
import com.cloud.simulator.dao.MockHostDao;
|
||||
import com.cloud.simulator.dao.MockVMDao;
|
||||
import com.cloud.user.AccountManager;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.concurrency.NamedThreadFactory;
|
||||
@ -459,6 +463,24 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage
|
||||
return new Answer(cmd);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer setupKeyStore(SetupKeyStoreCommand cmd) {
|
||||
return new SetupKeystoreAnswer(
|
||||
"-----BEGIN CERTIFICATE REQUEST-----\n" +
|
||||
"MIIBHjCByQIBADBkMQswCQYDVQQGEwJJTjELMAkGA1UECAwCSFIxETAPBgNVBAcM\n" +
|
||||
"CEd1cnVncmFtMQ8wDQYDVQQKDAZBcGFjaGUxEzARBgNVBAsMCkNsb3VkU3RhY2sx\n" +
|
||||
"DzANBgNVBAMMBnYtMS1WTTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQD46KFWKYrJ\n" +
|
||||
"F43Y1oqWUfrl4mj4Qm05Bgsi6nuigZv7ufiAKK0nO4iJKdRa2hFMUvBi2/bU3IyY\n" +
|
||||
"Nvg7cdJsn4K9AgMBAAGgADANBgkqhkiG9w0BAQUFAANBAIta9glu/ZSjA/ncyXix\n" +
|
||||
"yDOyAKmXXxsRIsdrEuIzakUuJS7C8IG0FjUbDyIaiwWQa5x+Lt4oMqCmpNqRzaGP\n" +
|
||||
"fOo=\n" + "-----END CERTIFICATE REQUEST-----");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Answer setupCertificate(SetupCertificateCommand cmd) {
|
||||
return new SetupCertificateAnswer(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
for (Discoverer discoverer : discoverers) {
|
||||
|
||||
@ -26,6 +26,8 @@ import java.util.Map;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.ca.SetupKeyStoreCommand;
|
||||
import org.apache.cloudstack.storage.command.DeleteCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
|
||||
@ -280,6 +282,10 @@ public class SimulatorManagerImpl extends ManagerBase implements SimulatorManage
|
||||
answer = _mockAgentMgr.checkHealth((CheckHealthCommand)cmd);
|
||||
} else if (cmd instanceof PingTestCommand) {
|
||||
answer = _mockAgentMgr.pingTest((PingTestCommand)cmd);
|
||||
} else if (cmd instanceof SetupKeyStoreCommand) {
|
||||
answer = _mockAgentMgr.setupKeyStore((SetupKeyStoreCommand)cmd);
|
||||
} else if (cmd instanceof SetupCertificateCommand) {
|
||||
answer = _mockAgentMgr.setupCertificate((SetupCertificateCommand)cmd);
|
||||
} else if (cmd instanceof PrepareForMigrationCommand) {
|
||||
answer = _mockVmMgr.prepareForMigrate((PrepareForMigrationCommand)cmd);
|
||||
} else if (cmd instanceof MigrateCommand) {
|
||||
|
||||
@ -53,6 +53,7 @@
|
||||
<module>acl/dynamic-role-based</module>
|
||||
<module>affinity-group-processors/host-anti-affinity</module>
|
||||
<module>affinity-group-processors/explicit-dedication</module>
|
||||
<module>ca/root-ca</module>
|
||||
<module>deployment-planners/user-concentrated-pod</module>
|
||||
<module>deployment-planners/user-dispersing</module>
|
||||
<module>deployment-planners/implicit-dedication</module>
|
||||
|
||||
@ -16,13 +16,34 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.saml;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.DomainManager;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserVO;
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
|
||||
import org.apache.cloudstack.api.command.AuthorizeSAMLSSOCmd;
|
||||
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
|
||||
import org.apache.cloudstack.api.command.ListAndSwitchSAMLAccountCmd;
|
||||
@ -34,9 +55,11 @@ import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.Configurable;
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreVO;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.httpclient.HttpClient;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.opensaml.DefaultBootstrap;
|
||||
import org.opensaml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml2.metadata.ContactPerson;
|
||||
@ -61,32 +84,13 @@ import org.opensaml.xml.security.credential.UsageType;
|
||||
import org.opensaml.xml.security.keyinfo.KeyInfoHelper;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutput;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.DomainManager;
|
||||
import com.cloud.user.User;
|
||||
import com.cloud.user.UserVO;
|
||||
import com.cloud.user.dao.UserDao;
|
||||
import com.cloud.utils.PropertiesUtil;
|
||||
import com.cloud.utils.component.AdapterBase;
|
||||
|
||||
@Component
|
||||
public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable {
|
||||
@ -141,12 +145,14 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
|
||||
KeystoreVO keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
|
||||
if (keyStoreVO == null) {
|
||||
try {
|
||||
KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
|
||||
_ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair");
|
||||
KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
|
||||
_ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR,
|
||||
CertUtils.privateKeyToPem(keyPair.getPrivate()),
|
||||
CertUtils.publicKeyToPem(keyPair.getPublic()), "samlsp-keypair");
|
||||
keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
|
||||
s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair");
|
||||
} catch (NoSuchProviderException | NoSuchAlgorithmException e) {
|
||||
s_logger.error("Unable to create and save SAML keypair: " + e.toString());
|
||||
} catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
|
||||
s_logger.error("Unable to create and save SAML keypair, due to: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,8 +166,19 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
|
||||
KeyPair spKeyPair = null;
|
||||
X509Certificate spX509Key = null;
|
||||
if (keyStoreVO != null) {
|
||||
PrivateKey privateKey = SAMLUtils.loadPrivateKey(keyStoreVO.getCertificate());
|
||||
PublicKey publicKey = SAMLUtils.loadPublicKey(keyStoreVO.getKey());
|
||||
|
||||
PrivateKey privateKey = null;
|
||||
try {
|
||||
privateKey = CertUtils.pemToPrivateKey(keyStoreVO.getCertificate());
|
||||
} catch (final InvalidKeySpecException | IOException e) {
|
||||
s_logger.error("Failed to read private key, due to error: ", e);
|
||||
}
|
||||
PublicKey publicKey = null;
|
||||
try {
|
||||
publicKey = CertUtils.pemToPublicKey(keyStoreVO.getKey());
|
||||
} catch (final InvalidKeySpecException | IOException e) {
|
||||
s_logger.error("Failed to read public key, due to error: ", e);
|
||||
}
|
||||
if (privateKey != null && publicKey != null) {
|
||||
spKeyPair = new KeyPair(publicKey, privateKey);
|
||||
KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT);
|
||||
@ -174,8 +191,8 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
|
||||
out.flush();
|
||||
_ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
|
||||
bos.close();
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) {
|
||||
s_logger.error("SAML Plugin won't be able to use X509 signed authentication");
|
||||
} catch (final NoSuchAlgorithmException | NoSuchProviderException | CertificateException | SignatureException | InvalidKeyException | IOException | OperatorCreationException e) {
|
||||
s_logger.error("SAML plugin won't be able to use X509 signed authentication", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
|
||||
@ -19,14 +19,41 @@
|
||||
|
||||
package org.apache.cloudstack.saml;
|
||||
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
|
||||
import org.apache.cloudstack.api.ApiConstants;
|
||||
import org.apache.cloudstack.api.response.LoginCmdResponse;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.x509.X509V1CertificateGenerator;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.opensaml.Configuration;
|
||||
import org.opensaml.DefaultBootstrap;
|
||||
import org.opensaml.common.SAMLVersion;
|
||||
@ -63,41 +90,7 @@ import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.List;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
|
||||
public class SAMLUtils {
|
||||
public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
|
||||
@ -271,89 +264,10 @@ public class SAMLUtils {
|
||||
return url;
|
||||
}
|
||||
|
||||
public static KeyFactory getKeyFactory() {
|
||||
KeyFactory keyFactory = null;
|
||||
try {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
keyFactory = KeyFactory.getInstance("RSA", "BC");
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||
s_logger.error("Unable to create KeyFactory:" + e.getMessage());
|
||||
}
|
||||
return keyFactory;
|
||||
}
|
||||
|
||||
public static String savePublicKey(PublicKey key) {
|
||||
try {
|
||||
KeyFactory keyFactory = SAMLUtils.getKeyFactory();
|
||||
if (keyFactory == null) return null;
|
||||
X509EncodedKeySpec spec = keyFactory.getKeySpec(key, X509EncodedKeySpec.class);
|
||||
return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()), Charset.forName("UTF-8"));
|
||||
} catch (InvalidKeySpecException e) {
|
||||
s_logger.error("Unable to create KeyFactory:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String savePrivateKey(PrivateKey key) {
|
||||
try {
|
||||
KeyFactory keyFactory = SAMLUtils.getKeyFactory();
|
||||
if (keyFactory == null) return null;
|
||||
PKCS8EncodedKeySpec spec = keyFactory.getKeySpec(key,
|
||||
PKCS8EncodedKeySpec.class);
|
||||
return new String(org.bouncycastle.util.encoders.Base64.encode(spec.getEncoded()), Charset.forName("UTF-8"));
|
||||
} catch (InvalidKeySpecException e) {
|
||||
s_logger.error("Unable to create KeyFactory:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PublicKey loadPublicKey(String publicKey) {
|
||||
byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(publicKey);
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(sigBytes);
|
||||
KeyFactory keyFact = SAMLUtils.getKeyFactory();
|
||||
if (keyFact == null)
|
||||
return null;
|
||||
try {
|
||||
return keyFact.generatePublic(x509KeySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static PrivateKey loadPrivateKey(String privateKey) {
|
||||
byte[] sigBytes = org.bouncycastle.util.encoders.Base64.decode(privateKey);
|
||||
PKCS8EncodedKeySpec pkscs8KeySpec = new PKCS8EncodedKeySpec(sigBytes);
|
||||
KeyFactory keyFact = SAMLUtils.getKeyFactory();
|
||||
if (keyFact == null)
|
||||
return null;
|
||||
try {
|
||||
return keyFact.generatePrivate(pkscs8KeySpec);
|
||||
} catch (InvalidKeySpecException e) {
|
||||
s_logger.error("Unable to create PrivateKey from privateKey string:" + e.getMessage());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static KeyPair generateRandomKeyPair() throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
|
||||
keyPairGenerator.initialize(4096, new SecureRandom());
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
}
|
||||
|
||||
public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateEncodingException, SignatureException, InvalidKeyException {
|
||||
DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
X500Principal dnName = new X500Principal("CN=ApacheCloudStack");
|
||||
X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
|
||||
certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
|
||||
certGen.setSubjectDN(dnName);
|
||||
certGen.setIssuerDN(dnName);
|
||||
certGen.setNotBefore(now.minusDays(1).toDate());
|
||||
certGen.setNotAfter(now.plusYears(3).toDate());
|
||||
certGen.setPublicKey(keyPair.getPublic());
|
||||
certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
|
||||
return certGen.generate(keyPair.getPrivate(), "BC");
|
||||
public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, SignatureException, InvalidKeyException, OperatorCreationException {
|
||||
return CertUtils.generateV1Certificate(keyPair,
|
||||
"CN=ApacheCloudStack", "CN=ApacheCloudStack",
|
||||
3, "SHA256WithRSA");
|
||||
}
|
||||
|
||||
public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException {
|
||||
|
||||
@ -19,13 +19,22 @@
|
||||
|
||||
package org.apache.cloudstack;
|
||||
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.cloudstack.api.ApiServerService;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
|
||||
import org.apache.cloudstack.saml.SAML2AuthManager;
|
||||
import org.apache.cloudstack.saml.SAMLProviderMetadata;
|
||||
import org.apache.cloudstack.saml.SAMLUtils;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -33,20 +42,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class GetServiceProviderMetaDataCmdTest {
|
||||
@ -67,7 +63,7 @@ public class GetServiceProviderMetaDataCmdTest {
|
||||
HttpServletRequest req;
|
||||
|
||||
@Test
|
||||
public void testAuthenticate() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, CertificateParsingException, CertificateEncodingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException, UnknownHostException {
|
||||
public void testAuthenticate() throws Exception {
|
||||
GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
|
||||
|
||||
Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
|
||||
@ -80,7 +76,7 @@ public class GetServiceProviderMetaDataCmdTest {
|
||||
|
||||
String spId = "someSPID";
|
||||
String url = "someUrl";
|
||||
KeyPair kp = SAMLUtils.generateRandomKeyPair();
|
||||
KeyPair kp = CertUtils.generateRandomKeyPair(4096);
|
||||
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
|
||||
|
||||
SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
|
||||
|
||||
@ -19,15 +19,17 @@
|
||||
|
||||
package org.apache.cloudstack;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import org.apache.cloudstack.saml.SAMLUtils;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.Test;
|
||||
import org.opensaml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml2.core.LogoutRequest;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class SAMLUtilsTest extends TestCase {
|
||||
|
||||
@ -60,13 +62,13 @@ public class SAMLUtilsTest extends TestCase {
|
||||
|
||||
@Test
|
||||
public void testX509Helpers() throws Exception {
|
||||
KeyPair keyPair = SAMLUtils.generateRandomKeyPair();
|
||||
KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
|
||||
|
||||
String privateKeyString = SAMLUtils.savePrivateKey(keyPair.getPrivate());
|
||||
String publicKeyString = SAMLUtils.savePublicKey(keyPair.getPublic());
|
||||
String privateKeyString = CertUtils.privateKeyToPem(keyPair.getPrivate());
|
||||
String publicKeyString = CertUtils.publicKeyToPem(keyPair.getPublic());
|
||||
|
||||
PrivateKey privateKey = SAMLUtils.loadPrivateKey(privateKeyString);
|
||||
PublicKey publicKey = SAMLUtils.loadPublicKey(publicKeyString);
|
||||
PrivateKey privateKey = CertUtils.pemToPrivateKey(privateKeyString);
|
||||
PublicKey publicKey = CertUtils.pemToPublicKey(publicKeyString);
|
||||
|
||||
assertTrue(privateKey.equals(keyPair.getPrivate()));
|
||||
assertTrue(publicKey.equals(keyPair.getPublic()));
|
||||
|
||||
@ -21,12 +21,16 @@ package org.apache.cloudstack.api.command;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.user.DomainManager;
|
||||
import com.cloud.user.UserAccountVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.cloudstack.api.ApiServerService;
|
||||
import org.apache.cloudstack.api.BaseCmd;
|
||||
@ -36,6 +40,7 @@ import org.apache.cloudstack.saml.SAML2AuthManager;
|
||||
import org.apache.cloudstack.saml.SAMLPluginConstants;
|
||||
import org.apache.cloudstack.saml.SAMLProviderMetadata;
|
||||
import org.apache.cloudstack.saml.SAMLUtils;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
@ -64,16 +69,12 @@ import org.opensaml.saml2.core.impl.StatusBuilder;
|
||||
import org.opensaml.saml2.core.impl.StatusCodeBuilder;
|
||||
import org.opensaml.saml2.core.impl.SubjectBuilder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.net.InetAddress;
|
||||
import com.cloud.domain.Domain;
|
||||
import com.cloud.user.AccountService;
|
||||
import com.cloud.user.DomainManager;
|
||||
import com.cloud.user.UserAccountVO;
|
||||
import com.cloud.user.dao.UserAccountDao;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SAML2LoginAPIAuthenticatorCmdTest {
|
||||
@ -158,7 +159,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
|
||||
userAccountDaoField.setAccessible(true);
|
||||
userAccountDaoField.set(cmd, userAccountDao);
|
||||
|
||||
KeyPair kp = SAMLUtils.generateRandomKeyPair();
|
||||
KeyPair kp = CertUtils.generateRandomKeyPair(4096);
|
||||
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
|
||||
|
||||
SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();
|
||||
|
||||
@ -19,11 +19,19 @@
|
||||
|
||||
package org.apache.cloudstack.api.command;
|
||||
|
||||
import com.cloud.utils.HttpUtils;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.apache.cloudstack.api.ApiServerService;
|
||||
import org.apache.cloudstack.api.auth.APIAuthenticationType;
|
||||
import org.apache.cloudstack.saml.SAML2AuthManager;
|
||||
import org.apache.cloudstack.saml.SAMLUtils;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@ -31,12 +39,7 @@ import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpSession;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.net.InetAddress;
|
||||
import com.cloud.utils.HttpUtils;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class SAML2LogoutAPIAuthenticatorCmdTest {
|
||||
@ -70,7 +73,7 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
|
||||
|
||||
String spId = "someSPID";
|
||||
String url = "someUrl";
|
||||
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair());
|
||||
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(CertUtils.generateRandomKeyPair(4096));
|
||||
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null);
|
||||
|
||||
cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
|
||||
|
||||
7
pom.xml
7
pom.xml
@ -64,7 +64,7 @@
|
||||
<cs.junit.version>4.12</cs.junit.version>
|
||||
<cs.hamcrest.version>1.3</cs.hamcrest.version>
|
||||
<cs.junit.dataprovider.version>1.12.0</cs.junit.dataprovider.version>
|
||||
<cs.bcprov.version>1.55</cs.bcprov.version>
|
||||
<cs.bcprov.version>1.57</cs.bcprov.version>
|
||||
<cs.jsch.version>0.1.54</cs.jsch.version>
|
||||
<cs.jpa.version>2.1.1</cs.jpa.version>
|
||||
<cs.jasypt.version>1.9.2</cs.jasypt.version>
|
||||
@ -225,6 +225,11 @@
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>${cs.bcprov.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>${cs.bcprov.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-css</artifactId>
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
|
||||
# Copies keys that enable SSH communication with system vms
|
||||
# $1 = new public key
|
||||
# $2 = new private key
|
||||
'''
|
||||
All imports go here...
|
||||
'''
|
||||
from subprocess import call
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import traceback
|
||||
|
||||
def generateSSLKey(outputPath):
|
||||
logf = open("ssl-keys.log", "w")
|
||||
hostName = socket.gethostbyname(socket.gethostname())
|
||||
keyFile = outputPath + os.sep + "cloudmanagementserver.keystore"
|
||||
logf.write("HostName = %s\n" % hostName)
|
||||
logf.write("OutputPath = %s\n" % keyFile)
|
||||
dname='cn="Cloudstack User",ou="' + hostName + '",o="' + hostName + '",c="Unknown"';
|
||||
logf.write("dname = %s\n" % dname)
|
||||
logf.flush()
|
||||
try :
|
||||
return_code = subprocess.Popen(["keytool", "-genkey", "-keystore", keyFile, "-storepass", "vmops.com", "-keypass", "vmops.com", "-keyalg", "RSA", "-validity", "3650", "-dname", dname],shell=True,stdout=logf, stderr=logf)
|
||||
return_code.wait()
|
||||
except OSError as e:
|
||||
logf.flush()
|
||||
traceback.print_exc(file=logf)
|
||||
logf.flush()
|
||||
logf.write("SSL key generated is : %s" % return_code)
|
||||
logf.flush()
|
||||
|
||||
argsSize=len(sys.argv)
|
||||
if argsSize != 2:
|
||||
print("Usage: ssl-keys.py <SSL File Key Path>")
|
||||
sys.exit(None)
|
||||
sslKeyPath=sys.argv[1]
|
||||
|
||||
generateSSLKey(sslKeyPath)
|
||||
@ -255,9 +255,6 @@
|
||||
<CustomAction Id="SetuptoolsInstallation" Directory='INSTALLDIR'
|
||||
ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\ez_setup.py"'
|
||||
Execute="deferred" Return="check" />
|
||||
<CustomAction Id="GenerateSSLKey" Directory='CSMANAGEMENT'
|
||||
ExeCommand='[PYTHON_HOME]\python "[CSMANAGEMENT]\webapps\client\WEB-INF\classes\scripts\common\keys\ssl-keys.py" "[CSMANAGEMENT]\lib"'
|
||||
Execute="deferred" Return="check" />
|
||||
<CustomAction Id="DbHostWithPort" Execute="immediate" Property="DB_HOSTNAME" Value="[DB_HOSTNAME]:[DB_PORT]" />
|
||||
<CustomAction Id="DeployDB" Directory='CSMANAGEMENT'
|
||||
ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\scripts\cloud-setup-databases" [DB_USERNAME]:[DB_PASSWORD]@[DB_HOSTNAME] --deploy-as=root:[DB_ROOT_PASSWORD] -c "[CSMANAGEMENT]\lib" -f "[CSMANAGEMENT]\setup" -j "[CSMANAGEMENT]\webapps\client\WEB-INF\lib\jasypt-1.9.2.jar" -n "[CSMANAGEMENT]\lib\key" -b "[MYSQL]\bin"'
|
||||
|
||||
@ -586,9 +586,6 @@
|
||||
<Component Id="cmp6001B626D853C4E4FAB8511246B8D2B4" Guid="{DB90C42E-A2DC-4742-8BC8-49AF7556E7D1}">
|
||||
<File Id="fil5A0CF0340938B7ACC3B69C1353BDC625" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\classpath.conf" />
|
||||
</Component>
|
||||
<Component Id="cmp71D36BFB6B214FAAD323C31A1CE3BC19" Guid="{EF4C61E1-F77E-4E4D-80EA-131511E0804E}">
|
||||
<File Id="fil44B623C422B90349A635A113C5F4D417" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\cloudmanagementserver.keystore" />
|
||||
</Component>
|
||||
<Component Id="cmp68E096BB729948107692341D8202CC5A" Guid="{0D5D3AF3-0BC0-48EE-ABA3-AF07535169BF}">
|
||||
<File Id="fil3E9BCB1A8CB8F8415FE3E71B65A94878" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\context.xml" />
|
||||
</Component>
|
||||
|
||||
@ -35,13 +35,11 @@ check_gw() {
|
||||
|
||||
cert="/root/.ssh/id_rsa.cloud"
|
||||
|
||||
script=$1
|
||||
shift
|
||||
|
||||
domRIp=$1
|
||||
shift
|
||||
script="$1"
|
||||
domRIp="$2"
|
||||
|
||||
check_gw "$domRIp"
|
||||
|
||||
ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script $*"
|
||||
ssh -p 3922 -q -o StrictHostKeyChecking=no -i $cert root@$domRIp "/opt/cloud/bin/$script ${@:3}"
|
||||
|
||||
exit $?
|
||||
|
||||
100
scripts/util/keystore-cert-import
Executable file
100
scripts/util/keystore-cert-import
Executable file
@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
PROPS_FILE="$1"
|
||||
KS_FILE="$2"
|
||||
MODE="$3"
|
||||
CERT_FILE="$4"
|
||||
CERT=$(echo "$5" | tr '^' '\n' | tr '~' ' ')
|
||||
CACERT_FILE="$6"
|
||||
CACERT=$(echo "$7" | tr '^' '\n' | tr '~' ' ')
|
||||
PRIVKEY_FILE="$8"
|
||||
PRIVKEY=$(echo "$9" | tr '^' '\n' | tr '~' ' ')
|
||||
|
||||
ALIAS="cloud"
|
||||
SYSTEM_FILE="/var/cache/cloud/cmdline"
|
||||
|
||||
# Find keystore password
|
||||
KS_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null)
|
||||
|
||||
if [ -z "${KS_PASS// }" ]; then
|
||||
echo "Failed to find keystore passphrase from file: $PROPS_FILE, quiting!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use a new keystore file
|
||||
NEW_KS_FILE="$KS_FILE.new"
|
||||
|
||||
# Import certificate
|
||||
if [ ! -z "${CERT// }" ]; then
|
||||
echo "$CERT" > "$CERT_FILE"
|
||||
fi
|
||||
|
||||
# Import ca certs
|
||||
if [ ! -z "${CACERT// }" ]; then
|
||||
echo "$CACERT" > "$CACERT_FILE"
|
||||
fi
|
||||
|
||||
# Import cacerts into the keystore
|
||||
awk '/-----BEGIN CERTIFICATE-----?/{n++}{print > "cloudca." n }' "$CACERT_FILE"
|
||||
for caChain in $(ls cloudca.*); do
|
||||
keytool -delete -noprompt -alias "$caChain" -keystore "$NEW_KS_FILE" -storepass "$KS_PASS" > /dev/null 2>&1 || true
|
||||
keytool -import -noprompt -storepass "$KS_PASS" -trustcacerts -alias "$caChain" -file "$caChain" -keystore "$NEW_KS_FILE" > /dev/null 2>&1
|
||||
done
|
||||
rm -f cloudca.*
|
||||
|
||||
# Import private key if available
|
||||
if [ ! -z "${PRIVKEY// }" ]; then
|
||||
echo "$PRIVKEY" > "$PRIVKEY_FILE"
|
||||
# Re-initialize keystore when private key is provided
|
||||
keytool -delete -noprompt -alias "$ALIAS" -keystore "$NEW_KS_FILE" -storepass "$KS_PASS" 2>/dev/null || true
|
||||
openssl pkcs12 -export -name "$ALIAS" -in "$CERT_FILE" -inkey "$PRIVKEY_FILE" -out "$NEW_KS_FILE.p12" -password pass:"$KS_PASS" > /dev/null 2>&1
|
||||
keytool -importkeystore -srckeystore "$NEW_KS_FILE.p12" -destkeystore "$NEW_KS_FILE" -srcstoretype PKCS12 -alias "$ALIAS" -deststorepass "$KS_PASS" -destkeypass "$KS_PASS" -srcstorepass "$KS_PASS" -srckeypass "$KS_PASS" > /dev/null 2>&1
|
||||
else
|
||||
# Import certificate into the keystore
|
||||
keytool -import -storepass "$KS_PASS" -alias "$ALIAS" -file "$CERT_FILE" -keystore "$NEW_KS_FILE" > /dev/null 2>&1 || true
|
||||
# Export private key from keystore
|
||||
rm -f "$PRIVKEY_FILE"
|
||||
keytool -importkeystore -srckeystore "$NEW_KS_FILE" -destkeystore "$NEW_KS_FILE.p12" -deststoretype PKCS12 -srcalias "$ALIAS" -deststorepass "$KS_PASS" -destkeypass "$KS_PASS" -srcstorepass "$KS_PASS" -srckeypass "$KS_PASS" > /dev/null 2>&1
|
||||
openssl pkcs12 -in "$NEW_KS_FILE.p12" -nodes -nocerts -nomac -password pass:"$KS_PASS" 2>/dev/null | openssl rsa -out "$PRIVKEY_FILE" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Commit the new keystore
|
||||
rm -f "$NEW_KS_FILE.p12"
|
||||
mv -f "$NEW_KS_FILE" "$KS_FILE"
|
||||
|
||||
# Update ca-certs if we're in systemvm
|
||||
if [ -f "$SYSTEM_FILE" ]; then
|
||||
mkdir -p /usr/local/share/ca-certificates/cloudstack
|
||||
cp "$CACERT_FILE" /usr/local/share/ca-certificates/cloudstack/ca.crt
|
||||
chmod 755 /usr/local/share/ca-certificates/cloudstack
|
||||
chmod 644 /usr/local/share/ca-certificates/cloudstack/ca.crt
|
||||
update-ca-certificates > /dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Restart cloud service if we're in systemvm
|
||||
if [ "$MODE" == "ssh" ] && [ -f $SYSTEM_FILE ]; then
|
||||
/etc/init.d/cloud stop > /dev/null 2>&1
|
||||
sleep 2
|
||||
/etc/init.d/cloud start > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Fix file permission
|
||||
chmod 600 $CACERT_FILE
|
||||
chmod 600 $CERT_FILE
|
||||
chmod 600 $PRIVKEY_FILE
|
||||
51
scripts/util/keystore-setup
Executable file
51
scripts/util/keystore-setup
Executable file
@ -0,0 +1,51 @@
|
||||
#!/bin/bash
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you under the Apache License, Version 2.0 (the
|
||||
# "License"); you may not use this file except in compliance
|
||||
# with the License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an
|
||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
PROPS_FILE="$1"
|
||||
KS_FILE="$2.new"
|
||||
KS_PASS="$3"
|
||||
KS_VALIDITY="$4"
|
||||
CSR_FILE="$5"
|
||||
|
||||
ALIAS="cloud"
|
||||
|
||||
# Re-use existing password or use the one provided
|
||||
if [ -f "$PROPS_FILE" ]; then
|
||||
OLD_PASS=$(sed -n '/keystore.passphrase/p' "$PROPS_FILE" 2>/dev/null | sed 's/keystore.passphrase=//g' 2>/dev/null)
|
||||
if [ ! -z "${OLD_PASS// }" ]; then
|
||||
KS_PASS="$OLD_PASS"
|
||||
else
|
||||
sed -i "/keystore.passphrase.*/d" $PROPS_FILE 2> /dev/null || true
|
||||
echo "keystore.passphrase=$KS_PASS" >> $PROPS_FILE
|
||||
fi
|
||||
fi
|
||||
|
||||
# Generate keystore
|
||||
rm -f "$KS_FILE"
|
||||
CN=$(hostname --fqdn)
|
||||
keytool -genkey -storepass "$KS_PASS" -keypass "$KS_PASS" -alias "$ALIAS" -keyalg RSA -validity "$KS_VALIDITY" -dname cn="$CN",ou="cloudstack",o="cloudstack",c="cloudstack" -keystore "$KS_FILE"
|
||||
|
||||
# Generate CSR
|
||||
rm -f "$CSR_FILE"
|
||||
keytool -certreq -storepass "$KS_PASS" -alias "$ALIAS" -file $CSR_FILE -keystore "$KS_FILE"
|
||||
cat "$CSR_FILE"
|
||||
|
||||
# Fix file permissions
|
||||
chmod 600 $KS_FILE
|
||||
chmod 600 $PROPS_FILE
|
||||
chmod 600 $CSR_FILE
|
||||
@ -52,6 +52,11 @@
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>${cs.httpcore.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-ca</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.cloudstack</groupId>
|
||||
<artifactId>cloud-framework-jobs</artifactId>
|
||||
|
||||
@ -87,8 +87,10 @@
|
||||
<property name="networkElements" value="#{networkElementsRegistry.registered}" />
|
||||
</bean>
|
||||
|
||||
|
||||
<bean id="configurationServerImpl" class="com.cloud.server.ConfigurationServerImpl" />
|
||||
|
||||
|
||||
<bean id="userVmManagerImpl" class="com.cloud.vm.UserVmManagerImpl" />
|
||||
|
||||
<bean id="consoleProxyManagerImpl" class="com.cloud.consoleproxy.ConsoleProxyManagerImpl">
|
||||
@ -277,4 +279,10 @@
|
||||
</bean>
|
||||
<bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" />
|
||||
<bean id="imageStoreUploadMonitorImpl" class="com.cloud.storage.ImageStoreUploadMonitorImpl" />
|
||||
|
||||
<!-- the new CA manager -->
|
||||
<bean id="caManager" class="org.apache.cloudstack.ca.CAManagerImpl">
|
||||
<property name="caProviders" value="#{caProvidersRegistry.registered}" />
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
|
||||
@ -759,7 +759,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) &&
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) &&
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) &&
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR)) {
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_OOBM_AUTH_ERROR) &&
|
||||
(alertType != AlertManager.AlertType.ALERT_TYPE_CA_CERT)) {
|
||||
alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId);
|
||||
}
|
||||
|
||||
|
||||
@ -110,6 +110,7 @@ import com.cloud.user.AccountManager;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.db.DB;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
@ -1354,7 +1355,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
|
||||
|
||||
StringBuilder buf = profile.getBootArgsBuilder();
|
||||
buf.append(" template=domP type=consoleproxy");
|
||||
buf.append(" host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value());
|
||||
buf.append(" host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
|
||||
buf.append(" port=").append(_mgmtPort);
|
||||
buf.append(" name=").append(profile.getVirtualMachine().getHostName());
|
||||
if (_sslEnabled) {
|
||||
|
||||
@ -16,6 +16,23 @@
|
||||
// under the License.
|
||||
package com.cloud.hypervisor.kvm.discoverer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.ca.CAManager;
|
||||
import org.apache.cloudstack.ca.SetupCertificateCommand;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.utils.security.KeyStoreUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.Listener;
|
||||
import com.cloud.agent.api.AgentControlAnswer;
|
||||
@ -42,17 +59,10 @@ import com.cloud.resource.DiscovererBase;
|
||||
import com.cloud.resource.ResourceStateAdapter;
|
||||
import com.cloud.resource.ServerResource;
|
||||
import com.cloud.resource.UnableDeleteHostException;
|
||||
import com.cloud.utils.PasswordGenerator;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.ssh.SSHCmdHelper;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import com.trilead.ssh2.Connection;
|
||||
|
||||
public abstract class LibvirtServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter {
|
||||
private static final Logger s_logger = Logger.getLogger(LibvirtServerDiscoverer.class);
|
||||
@ -62,7 +72,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
private String _kvmPublicNic;
|
||||
private String _kvmGuestNic;
|
||||
@Inject
|
||||
AgentManager _agentMgr;
|
||||
private AgentManager agentMgr;
|
||||
@Inject
|
||||
private CAManager caManager;
|
||||
|
||||
@Override
|
||||
public abstract Hypervisor.HypervisorType getHypervisorType();
|
||||
@ -125,6 +137,73 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setupAgentSecurity(final Connection sshConnection, final String agentIp, final String agentHostname) {
|
||||
if (!caManager.canProvisionCertificates()) {
|
||||
s_logger.warn("Cannot secure agent communication because configure CA plugin cannot provision client certificate");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sshConnection == null) {
|
||||
s_logger.warn("Cannot secure agent communication because ssh connection is invalid for host ip=" + agentIp);
|
||||
return;
|
||||
}
|
||||
|
||||
Integer validityPeriod = CAManager.CertValidityPeriod.value();
|
||||
if (validityPeriod < 1) {
|
||||
validityPeriod = 1;
|
||||
}
|
||||
|
||||
final SSHCmdHelper.SSHCmdResult keystoreSetupResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
|
||||
String.format("/usr/share/cloudstack-common/scripts/util/%s " +
|
||||
"/etc/cloudstack/agent/agent.properties " +
|
||||
"/etc/cloudstack/agent/%s " +
|
||||
"%s %d " +
|
||||
"/etc/cloudstack/agent/%s",
|
||||
KeyStoreUtils.keyStoreSetupScript,
|
||||
KeyStoreUtils.defaultKeystoreFile,
|
||||
PasswordGenerator.generateRandomPassword(16),
|
||||
validityPeriod,
|
||||
KeyStoreUtils.defaultCsrFile));
|
||||
|
||||
if (!keystoreSetupResult.isSuccess()) {
|
||||
s_logger.error("Failing, the keystore setup script failed execution on the KVM host: " + agentIp);
|
||||
return;
|
||||
}
|
||||
|
||||
final Certificate certificate = caManager.issueCertificate(keystoreSetupResult.getStdOut(), Collections.singletonList(agentHostname), Collections.singletonList(agentIp), null, null);
|
||||
if (certificate == null || certificate.getClientCertificate() == null) {
|
||||
s_logger.error("Failing, the configured CA plugin failed to issue certificates for KVM host agent: " + agentIp);
|
||||
return;
|
||||
}
|
||||
|
||||
final SetupCertificateCommand certificateCommand = new SetupCertificateCommand(certificate);
|
||||
final SSHCmdHelper.SSHCmdResult setupCertResult = SSHCmdHelper.sshExecuteCmdWithResult(sshConnection,
|
||||
String.format("/usr/share/cloudstack-common/scripts/util/%s " +
|
||||
"/etc/cloudstack/agent/agent.properties " +
|
||||
"/etc/cloudstack/agent/%s %s " +
|
||||
"/etc/cloudstack/agent/%s \"%s\" " +
|
||||
"/etc/cloudstack/agent/%s \"%s\" " +
|
||||
"/etc/cloudstack/agent/%s \"%s\"",
|
||||
KeyStoreUtils.keyStoreImportScript,
|
||||
KeyStoreUtils.defaultKeystoreFile,
|
||||
KeyStoreUtils.sshMode,
|
||||
KeyStoreUtils.defaultCertFile,
|
||||
certificateCommand.getEncodedCertificate(),
|
||||
KeyStoreUtils.defaultCaCertFile,
|
||||
certificateCommand.getEncodedCaCertificates(),
|
||||
KeyStoreUtils.defaultPrivateKeyFile,
|
||||
certificateCommand.getEncodedPrivateKey()));
|
||||
|
||||
if (setupCertResult != null && !setupCertResult.isSuccess()) {
|
||||
s_logger.error("Failed to setup certificate in the KVM agent's keystore file, please configure manually!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (s_logger.isDebugEnabled()) {
|
||||
s_logger.debug("Succeeded to import certificate in the keystore for agent on the KVM host: " + agentIp + ". Agent secured and trusted.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<? extends ServerResource, Map<String, String>>
|
||||
find(long dcId, Long podId, Long clusterId, URI uri, String username, String password, List<String> hostTags) throws DiscoveryException {
|
||||
@ -143,7 +222,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
s_logger.debug(msg);
|
||||
return null;
|
||||
}
|
||||
com.trilead.ssh2.Connection sshConnection = null;
|
||||
Connection sshConnection = null;
|
||||
String agentIp = null;
|
||||
try {
|
||||
|
||||
@ -162,7 +241,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
}
|
||||
}
|
||||
|
||||
sshConnection = new com.trilead.ssh2.Connection(agentIp, 22);
|
||||
sshConnection = new Connection(agentIp, 22);
|
||||
|
||||
sshConnection.connect(null, 60000, 60000);
|
||||
if (!sshConnection.authenticateWithPassword(username, password)) {
|
||||
@ -170,7 +249,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
throw new DiscoveredWithErrorException("Authentication error");
|
||||
}
|
||||
|
||||
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "lsmod|grep kvm", 3)) {
|
||||
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, "lsmod|grep kvm")) {
|
||||
s_logger.debug("It's not a KVM enabled machine");
|
||||
return null;
|
||||
}
|
||||
@ -210,7 +289,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
kvmGuestNic = (kvmPublicNic != null) ? kvmPublicNic : kvmPrivateNic;
|
||||
}
|
||||
|
||||
String parameters = " -m " + _hostIp + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
|
||||
setupAgentSecurity(sshConnection, agentIp, hostname);
|
||||
|
||||
String parameters = " -m " + StringUtils.shuffleCSVList(_hostIp) + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
|
||||
|
||||
parameters += " --pubNic=" + kvmPublicNic;
|
||||
parameters += " --prvNic=" + kvmPrivateNic;
|
||||
@ -221,8 +302,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
if (!username.equals("root")) {
|
||||
setupAgentCommand = "sudo cloudstack-setup-agent ";
|
||||
}
|
||||
if (!SSHCmdHelper.sshExecuteCmd(sshConnection,
|
||||
setupAgentCommand + parameters, 3)) {
|
||||
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, setupAgentCommand + parameters)) {
|
||||
s_logger.info("cloudstack agent setup command failed: "
|
||||
+ setupAgentCommand + parameters);
|
||||
return null;
|
||||
@ -392,7 +472,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
|
||||
_resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage);
|
||||
try {
|
||||
ShutdownCommand cmd = new ShutdownCommand(ShutdownCommand.DeleteHost, null);
|
||||
_agentMgr.send(host.getId(), cmd);
|
||||
agentMgr.send(host.getId(), cmd);
|
||||
} catch (AgentUnavailableException e) {
|
||||
s_logger.warn("Sending ShutdownCommand failed: ", e);
|
||||
} catch (OperationTimedoutException e) {
|
||||
|
||||
@ -2272,7 +2272,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
|
||||
}
|
||||
|
||||
try {
|
||||
SSHCmdHelper.sshExecuteCmdOneShot(connection, "service cloudstack-agent restart");
|
||||
SSHCmdHelper.SSHCmdResult result = SSHCmdHelper.sshExecuteCmdOneShot(connection, "service cloudstack-agent restart");
|
||||
s_logger.debug("cloudstack-agent restart result: " + result.toString());
|
||||
} catch (final SshException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -23,8 +23,6 @@ import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@ -36,24 +34,21 @@ import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import org.apache.cloudstack.config.ApiServiceConfiguration;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
|
||||
import com.cloud.configuration.Config;
|
||||
import com.cloud.configuration.ConfigurationManager;
|
||||
@ -117,7 +112,6 @@ import com.cloud.utils.db.TransactionLegacy;
|
||||
import com.cloud.utils.db.TransactionStatus;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.cloud.utils.net.NetUtils;
|
||||
import com.cloud.utils.nio.Link;
|
||||
import com.cloud.utils.script.Script;
|
||||
|
||||
public class ConfigurationServerImpl extends ManagerBase implements ConfigurationServer {
|
||||
@ -306,9 +300,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
|
||||
// Update resource count if needed
|
||||
updateResourceCount();
|
||||
|
||||
// keystore for SSL/TLS connection
|
||||
updateSSLKeystore();
|
||||
|
||||
// store the public and private keys in the database
|
||||
updateKeyPairs();
|
||||
|
||||
@ -544,117 +535,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
|
||||
}
|
||||
}
|
||||
|
||||
static String getBase64Keystore(String keystorePath) throws IOException {
|
||||
byte[] storeBytes = FileUtils.readFileToByteArray(new File(keystorePath));
|
||||
if (storeBytes.length > 3000) { // Base64 codec would enlarge data by 1/3, and we have 4094 bytes in database entry at most
|
||||
throw new IOException("KeyStore is too big for database! Length " + storeBytes.length);
|
||||
}
|
||||
|
||||
return new String(Base64.encodeBase64(storeBytes));
|
||||
}
|
||||
|
||||
private void generateDefaultKeystore(String keystorePath) throws IOException {
|
||||
String cn = "Cloudstack User";
|
||||
String ou;
|
||||
|
||||
try {
|
||||
ou = InetAddress.getLocalHost().getCanonicalHostName();
|
||||
String[] group = ou.split("\\.");
|
||||
|
||||
// Simple check to see if we got IP Address...
|
||||
boolean isIPAddress = Pattern.matches("[0-9]$", group[group.length - 1]);
|
||||
if (isIPAddress) {
|
||||
ou = "cloud.com"; // leaving this example reference to cloud.com as it has no real world relevance
|
||||
} else {
|
||||
ou = group[group.length - 1];
|
||||
for (int i = group.length - 2; i >= 0 && i >= group.length - 3; i--)
|
||||
ou = group[i] + "." + ou;
|
||||
}
|
||||
} catch (UnknownHostException ex) {
|
||||
s_logger.info("Fail to get user's domain name. Would use cloud.com. ", ex);
|
||||
ou = "cloud.com"; // leaving this example reference to cloud.com as it has no real world relevance
|
||||
}
|
||||
|
||||
String o = ou;
|
||||
String c = "Unknown";
|
||||
String dname = "cn=\"" + cn + "\",ou=\"" + ou + "\",o=\"" + o + "\",c=\"" + c + "\"";
|
||||
Script script = new Script(true, "keytool", 5000, null);
|
||||
script.add("-genkey");
|
||||
script.add("-keystore", keystorePath);
|
||||
script.add("-storepass", "vmops.com");
|
||||
script.add("-keypass", "vmops.com");
|
||||
script.add("-keyalg", "RSA");
|
||||
script.add("-validity", "3650");
|
||||
script.add("-dname", dname);
|
||||
String result = script.execute();
|
||||
if (result != null) {
|
||||
throw new IOException("Fail to generate certificate!: " + result);
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateSSLKeystore() {
|
||||
if (s_logger.isInfoEnabled()) {
|
||||
s_logger.info("Processing updateSSLKeyStore");
|
||||
}
|
||||
|
||||
String dbString = _configDao.getValue("ssl.keystore");
|
||||
|
||||
File confFile = PropertiesUtil.findConfigFile("db.properties");
|
||||
String confPath = null;
|
||||
String keystorePath = null;
|
||||
File keystoreFile = null;
|
||||
|
||||
if (null != confFile) {
|
||||
confPath = confFile.getParent();
|
||||
keystorePath = confPath + Link.keystoreFile;
|
||||
keystoreFile = new File(keystorePath);
|
||||
}
|
||||
|
||||
boolean dbExisted = (dbString != null && !dbString.isEmpty());
|
||||
|
||||
s_logger.info("SSL keystore located at " + keystorePath);
|
||||
try {
|
||||
if (!dbExisted && null != confFile) {
|
||||
if (!keystoreFile.exists()) {
|
||||
generateDefaultKeystore(keystorePath);
|
||||
s_logger.info("Generated SSL keystore.");
|
||||
}
|
||||
String base64Keystore = getBase64Keystore(keystorePath);
|
||||
ConfigurationVO configVO =
|
||||
new ConfigurationVO("Hidden", "DEFAULT", "management-server", "ssl.keystore", base64Keystore,
|
||||
"SSL Keystore for the management servers");
|
||||
_configDao.persist(configVO);
|
||||
s_logger.info("Stored SSL keystore to database.");
|
||||
} else { // !keystoreFile.exists() and dbExisted
|
||||
// Export keystore to local file
|
||||
byte[] storeBytes = Base64.decodeBase64(dbString);
|
||||
String tmpKeystorePath = "/tmp/tmpkey";
|
||||
try (
|
||||
FileOutputStream fo = new FileOutputStream(tmpKeystorePath);
|
||||
) {
|
||||
fo.write(storeBytes);
|
||||
Script script = new Script(true, "cp", 5000, null);
|
||||
script.add("-f");
|
||||
script.add(tmpKeystorePath);
|
||||
|
||||
//There is a chance, although small, that the keystorePath is null. In that case, do not add it to the script.
|
||||
if (null != keystorePath) {
|
||||
script.add(keystorePath);
|
||||
}
|
||||
String result = script.execute();
|
||||
if (result != null) {
|
||||
throw new IOException();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Fail to create keystore file!", e);
|
||||
}
|
||||
s_logger.info("Stored database keystore to local.");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
s_logger.warn("Would use fail-safe keystore to continue.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@DB
|
||||
protected void updateSystemvmPassword() {
|
||||
String userid = System.getProperty("user.name");
|
||||
|
||||
428
server/src/org/apache/cloudstack/ca/CAManagerImpl.java
Normal file
428
server/src/org/apache/cloudstack/ca/CAManagerImpl.java
Normal file
@ -0,0 +1,428 @@
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.cert.CertificateExpiredException;
|
||||
import java.security.cert.CertificateNotYetValidException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.naming.ConfigurationException;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.apache.cloudstack.api.ApiErrorCode;
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.api.command.admin.ca.IssueCertificateCmd;
|
||||
import org.apache.cloudstack.api.command.admin.ca.ListCAProvidersCmd;
|
||||
import org.apache.cloudstack.api.command.admin.ca.ListCaCertificateCmd;
|
||||
import org.apache.cloudstack.api.command.admin.ca.ProvisionCertificateCmd;
|
||||
import org.apache.cloudstack.api.command.admin.ca.RevokeCertificateCmd;
|
||||
import org.apache.cloudstack.context.CallContext;
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.managed.context.ManagedContextRunnable;
|
||||
import org.apache.cloudstack.poll.BackgroundPollManager;
|
||||
import org.apache.cloudstack.poll.BackgroundPollTask;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.alert.AlertManager;
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
import com.cloud.event.ActionEvent;
|
||||
import com.cloud.event.EventTypes;
|
||||
import com.cloud.exception.AgentUnavailableException;
|
||||
import com.cloud.exception.OperationTimedoutException;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.Status;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
import com.google.common.base.Strings;
|
||||
|
||||
public class CAManagerImpl extends ManagerBase implements CAManager {
|
||||
public static final Logger LOG = Logger.getLogger(CAManagerImpl.class);
|
||||
|
||||
@Inject
|
||||
private CrlDao crlDao;
|
||||
@Inject
|
||||
private HostDao hostDao;
|
||||
@Inject
|
||||
private AgentManager agentManager;
|
||||
@Inject
|
||||
private BackgroundPollManager backgroundPollManager;
|
||||
@Inject
|
||||
private AlertManager alertManager;
|
||||
|
||||
private static CAProvider configuredCaProvider;
|
||||
private static Map<String, CAProvider> caProviderMap = new HashMap<>();
|
||||
private static Map<String, Date> alertMap = new ConcurrentHashMap<>();
|
||||
private static Map<String, X509Certificate> activeCertMap = new ConcurrentHashMap<>();
|
||||
|
||||
private List<CAProvider> caProviders;
|
||||
|
||||
private CAProvider getConfiguredCaProvider() {
|
||||
if (configuredCaProvider != null) {
|
||||
return configuredCaProvider;
|
||||
}
|
||||
if (caProviderMap.containsKey(CAProviderPlugin.value()) && caProviderMap.get(CAProviderPlugin.value()) != null) {
|
||||
configuredCaProvider = caProviderMap.get(CAProviderPlugin.value());
|
||||
return configuredCaProvider;
|
||||
}
|
||||
throw new CloudRuntimeException("Failed to find default configured CA provider plugin");
|
||||
}
|
||||
|
||||
private CAProvider getCAProvider(final String provider) {
|
||||
if (Strings.isNullOrEmpty(provider)) {
|
||||
return getConfiguredCaProvider();
|
||||
}
|
||||
final String caProviderName = provider.toLowerCase();
|
||||
if (!caProviderMap.containsKey(caProviderName)) {
|
||||
throw new CloudRuntimeException(String.format("CA provider plugin '%s' not found", caProviderName));
|
||||
}
|
||||
final CAProvider caProvider = caProviderMap.get(caProviderName);
|
||||
if (caProvider == null) {
|
||||
throw new CloudRuntimeException(String.format("CA provider plugin '%s' returned is null", caProviderName));
|
||||
}
|
||||
return caProvider;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
/////////////// CA Manager API Handlers ///////////////////
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public List<CAProvider> getCaProviders() {
|
||||
return caProviders;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, X509Certificate> getActiveCertificatesMap() {
|
||||
return activeCertMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canProvisionCertificates() {
|
||||
return getConfiguredCaProvider().canProvisionCertificates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCaCertificate(final String caProvider) throws IOException {
|
||||
final CAProvider provider = getCAProvider(caProvider);
|
||||
return CertUtils.x509CertificatesToPem(provider.getCaCertificate());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_ISSUE, eventDescription = "issuing certificate", async = true)
|
||||
public Certificate issueCertificate(final String csr, final List<String> domainNames, final List<String> ipAddresses, final Integer validityDuration, final String caProvider) {
|
||||
CallContext.current().setEventDetails("domain(s): " + domainNames + " addresses: " + ipAddresses);
|
||||
final CAProvider provider = getCAProvider(caProvider);
|
||||
Integer validity = CAManager.CertValidityPeriod.value();
|
||||
if (validityDuration != null) {
|
||||
validity = validityDuration;
|
||||
}
|
||||
if (Strings.isNullOrEmpty(csr)) {
|
||||
if (domainNames == null || domainNames.isEmpty()) {
|
||||
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "No domains or CSR provided");
|
||||
}
|
||||
return provider.issueCertificate(domainNames, ipAddresses, validity);
|
||||
}
|
||||
return provider.issueCertificate(csr, domainNames, ipAddresses, validity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_REVOKE, eventDescription = "revoking certificate", async = true)
|
||||
public boolean revokeCertificate(final BigInteger certSerial, final String certCn, final String caProvider) {
|
||||
CallContext.current().setEventDetails("cert serial: " + certSerial);
|
||||
final CrlVO crl = crlDao.revokeCertificate(certSerial, certCn);
|
||||
if (crl != null && crl.getCertSerial().equals(certSerial)) {
|
||||
final CAProvider provider = getCAProvider(caProvider);
|
||||
return provider.revokeCertificate(certSerial, certCn);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_CA_CERTIFICATE_PROVISION, eventDescription = "provisioning certificate for host", async = true)
|
||||
public boolean provisionCertificate(final Host host, final Boolean reconnect, final String caProvider) {
|
||||
if (host == null) {
|
||||
throw new CloudRuntimeException("Unable to find valid host to renew certificate for");
|
||||
}
|
||||
CallContext.current().setEventDetails("host id: " + host.getId());
|
||||
CallContext.current().putContextParameter(Host.class, host.getUuid());
|
||||
final String csr;
|
||||
try {
|
||||
csr = generateKeyStoreAndCsr(host, null);
|
||||
if (Strings.isNullOrEmpty(csr)) {
|
||||
return false;
|
||||
}
|
||||
final Certificate certificate = issueCertificate(csr, Collections.singletonList(host.getName()), Arrays.asList(host.getPrivateIpAddress(), host.getPublicIpAddress(), host.getStorageIpAddress()), CAManager.CertValidityPeriod.value(), caProvider);
|
||||
return deployCertificate(host, certificate, reconnect, null);
|
||||
} catch (final AgentUnavailableException | OperationTimedoutException e) {
|
||||
LOG.error("Host/agent is not available or operation timed out, failed to setup keystore and generate CSR for host/agent id=" + host.getId() + ", due to: ", e);
|
||||
throw new CloudRuntimeException("Failed to generate keystore and get CSR from the host/agent id=" + host.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateKeyStoreAndCsr(final Host host, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
|
||||
final SetupKeyStoreCommand cmd = new SetupKeyStoreCommand(CertValidityPeriod.value());
|
||||
if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
|
||||
cmd.setAccessDetail(sshAccessDetails);
|
||||
}
|
||||
CallContext.current().setEventDetails("generating keystore and CSR for host id: " + host.getId());
|
||||
final SetupKeystoreAnswer answer = (SetupKeystoreAnswer) agentManager.send(host.getId(), cmd);
|
||||
return answer.getCsr();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deployCertificate(final Host host, final Certificate certificate, final Boolean reconnect, final Map<String, String> sshAccessDetails) throws AgentUnavailableException, OperationTimedoutException {
|
||||
final SetupCertificateCommand cmd = new SetupCertificateCommand(certificate);
|
||||
if (sshAccessDetails != null && !sshAccessDetails.isEmpty()) {
|
||||
cmd.setAccessDetail(sshAccessDetails);
|
||||
}
|
||||
CallContext.current().setEventDetails("deploying certificate for host id: " + host.getId());
|
||||
final SetupCertificateAnswer answer = (SetupCertificateAnswer) agentManager.send(host.getId(), cmd);
|
||||
if (answer.getResult()) {
|
||||
CallContext.current().setEventDetails("successfully deployed certificate for host id: " + host.getId());
|
||||
} else {
|
||||
CallContext.current().setEventDetails("failed to deploy certificate for host id: " + host.getId());
|
||||
}
|
||||
|
||||
if (answer.getResult()) {
|
||||
getActiveCertificatesMap().put(host.getPrivateIpAddress(), certificate.getClientCertificate());
|
||||
if (sshAccessDetails == null && reconnect != null && reconnect) {
|
||||
LOG.info(String.format("Successfully setup certificate on host, reconnecting with agent with id=%d, name=%s, address=%s",
|
||||
host.getId(), host.getName(), host.getPublicIpAddress()));
|
||||
return agentManager.reconnect(host.getId());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void purgeHostCertificate(final Host host) {
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
final String privateAddress = host.getPrivateIpAddress();
|
||||
final String publicAddress = host.getPublicIpAddress();
|
||||
final Map<String, X509Certificate> activeCertsMap = getActiveCertificatesMap();
|
||||
if (!Strings.isNullOrEmpty(privateAddress) && activeCertsMap.containsKey(privateAddress)) {
|
||||
activeCertsMap.remove(privateAddress);
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(publicAddress) && activeCertsMap.containsKey(publicAddress)) {
|
||||
activeCertsMap.remove(publicAddress);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendAlert(final Host host, final String subject, final String message) {
|
||||
if (host == null) {
|
||||
return;
|
||||
}
|
||||
alertManager.sendAlert(AlertManager.AlertType.ALERT_TYPE_CA_CERT,
|
||||
host.getDataCenterId(), host.getPodId(), subject, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SSLEngine createSSLEngine(final SSLContext sslContext, final String remoteAddress) throws GeneralSecurityException, IOException {
|
||||
if (sslContext == null) {
|
||||
throw new CloudRuntimeException("SSLContext provided to create SSLEngine is null, aborting");
|
||||
}
|
||||
if (Strings.isNullOrEmpty(remoteAddress)) {
|
||||
throw new CloudRuntimeException("Remote client address connecting to mgmt server cannot be empty/null");
|
||||
}
|
||||
return getConfiguredCaProvider().createSSLEngine(sslContext, remoteAddress, getActiveCertificatesMap());
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
/////////////// CA Manager Setup ///////////////////
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
public static final class CABackgroundTask extends ManagedContextRunnable implements BackgroundPollTask {
|
||||
private CAManager caManager;
|
||||
private HostDao hostDao;
|
||||
|
||||
public CABackgroundTask(final CAManager caManager, final HostDao hostDao) {
|
||||
this.caManager = caManager;
|
||||
this.hostDao = hostDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void runInContext() {
|
||||
try {
|
||||
if (LOG.isTraceEnabled()) {
|
||||
LOG.trace("CA background task is running...");
|
||||
}
|
||||
final DateTime now = DateTime.now(DateTimeZone.UTC);
|
||||
final Map<String, X509Certificate> certsMap = caManager.getActiveCertificatesMap();
|
||||
for (final Iterator<Map.Entry<String, X509Certificate>> it = certsMap.entrySet().iterator(); it.hasNext(); ) {
|
||||
final Map.Entry<String, X509Certificate> entry = it.next();
|
||||
if (entry == null) {
|
||||
continue;
|
||||
}
|
||||
final String hostIp = entry.getKey();
|
||||
final X509Certificate certificate = entry.getValue();
|
||||
if (certificate == null) {
|
||||
it.remove();
|
||||
continue;
|
||||
}
|
||||
final Host host = hostDao.findByIp(hostIp);
|
||||
if (host == null || host.getManagementServerId() == null ||
|
||||
host.getManagementServerId() != ManagementServerNode.getManagementServerId() ||
|
||||
host.getStatus() != Status.Up) {
|
||||
if (host == null ||
|
||||
(host.getManagementServerId() != null &&
|
||||
host.getManagementServerId() != ManagementServerNode.getManagementServerId())) {
|
||||
it.remove();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final String hostDescription = String.format("host id=%d, uuid=%s, name=%s, ip=%s, zone id=%d",
|
||||
host.getId(), host.getUuid(), host.getName(), hostIp, host.getDataCenterId());
|
||||
|
||||
try {
|
||||
certificate.checkValidity(now.plusDays(CertExpiryAlertPeriod.valueIn(host.getClusterId())).toDate());
|
||||
} catch (final CertificateExpiredException | CertificateNotYetValidException e) {
|
||||
LOG.warn("Certificate is going to expire for " + hostDescription);
|
||||
if (AutomaticCertRenewal.valueIn(host.getClusterId())) {
|
||||
try {
|
||||
LOG.debug("Attempting certificate auto-renewal for " + hostDescription);
|
||||
boolean result = caManager.provisionCertificate(host, false, null);
|
||||
if (result) {
|
||||
LOG.debug("Succeeded in auto-renewing certificate for " + hostDescription);
|
||||
} else {
|
||||
LOG.debug("Failed in auto-renewing certificate for " + hostDescription);
|
||||
}
|
||||
} catch (final Throwable ex) {
|
||||
LOG.warn("Failed to auto-renew certificate for " + hostDescription + ", with error=", ex);
|
||||
caManager.sendAlert(host, "Certificate auto-renewal failed for " + hostDescription,
|
||||
String.format("Certificate is going to expire for %s. Auto-renewal failed to renew the certificate, please renew it manually. It is not valid after %s.", hostDescription, certificate.getNotAfter()));
|
||||
}
|
||||
} else {
|
||||
if (alertMap.containsKey(hostIp)) {
|
||||
final Date lastSentDate = alertMap.get(hostIp);
|
||||
if (now.minusDays(1).toDate().before(lastSentDate)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
caManager.sendAlert(host, "Certificate expiring soon for " + hostDescription,
|
||||
String.format("Certificate is going to expire for %s. Please renew it, it is not valid after %s.",
|
||||
hostDescription, certificate.getNotAfter()));
|
||||
alertMap.put(hostIp, new Date());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final Throwable t) {
|
||||
LOG.error("Error trying to run CA background task", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDelay() {
|
||||
return CABackgroundJobDelay.value() * 1000L;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCaProviders(final List<CAProvider> caProviders) {
|
||||
this.caProviders = caProviders;
|
||||
initializeCaProviderMap();
|
||||
}
|
||||
|
||||
private void initializeCaProviderMap() {
|
||||
if (caProviderMap != null && caProviderMap.size() != caProviders.size()) {
|
||||
for (final CAProvider caProvider : caProviders) {
|
||||
caProviderMap.put(caProvider.getProviderName().toLowerCase(), caProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
super.start();
|
||||
initializeCaProviderMap();
|
||||
if (caProviderMap.containsKey(CAProviderPlugin.value())) {
|
||||
configuredCaProvider = caProviderMap.get(CAProviderPlugin.value());
|
||||
}
|
||||
if (configuredCaProvider == null) {
|
||||
LOG.error("Failed to find valid configured CA provider, please check!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException {
|
||||
backgroundPollManager.submitTask(new CABackgroundTask(this, hostDao));
|
||||
return true;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
/////////////// CA Manager Descriptors ///////////////////
|
||||
//////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public List<Class<?>> getCommands() {
|
||||
final List<Class<?>> cmdList = new ArrayList<Class<?>>();
|
||||
cmdList.add(ListCAProvidersCmd.class);
|
||||
cmdList.add(ListCaCertificateCmd.class);
|
||||
cmdList.add(IssueCertificateCmd.class);
|
||||
cmdList.add(ProvisionCertificateCmd.class);
|
||||
cmdList.add(RevokeCertificateCmd.class);
|
||||
return cmdList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConfigComponentName() {
|
||||
return CAManager.class.getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigKey<?>[] getConfigKeys() {
|
||||
return new ConfigKey<?>[]{
|
||||
CAProviderPlugin,
|
||||
CertKeySize,
|
||||
CertSignatureAlgorithm,
|
||||
CertValidityPeriod,
|
||||
AutomaticCertRenewal,
|
||||
CABackgroundJobDelay,
|
||||
CertExpiryAlertPeriod
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -577,5 +577,11 @@ public class OutOfBandManagementServiceImpl extends ManagerBase implements OutOf
|
||||
LOG.error("Error trying to retrieve host out-of-band management stats", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDelay() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,11 @@ public final class BackgroundPollManagerImpl extends ManagerBase implements Back
|
||||
}
|
||||
backgroundPollTaskScheduler = Executors.newScheduledThreadPool(submittedTasks.size() + 1, new NamedThreadFactory("BackgroundTaskPollManager"));
|
||||
for (final BackgroundPollTask task : submittedTasks) {
|
||||
backgroundPollTaskScheduler.scheduleWithFixedDelay(task, getInitialDelay(), getRoundDelay(), TimeUnit.MILLISECONDS);
|
||||
Long delay = task.getDelay();
|
||||
if (delay == null) {
|
||||
delay = getRoundDelay();
|
||||
}
|
||||
backgroundPollTaskScheduler.scheduleWithFixedDelay(task, getInitialDelay(), delay, TimeUnit.MILLISECONDS);
|
||||
LOG.debug("Scheduled background poll task: " + task.getClass().getName());
|
||||
}
|
||||
isConfiguredAndStarted = true;
|
||||
|
||||
@ -16,8 +16,17 @@
|
||||
// under the License.
|
||||
package com.cloud.server;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.configuration.ConfigurationManager;
|
||||
import com.cloud.configuration.dao.ResourceCountDao;
|
||||
@ -32,19 +41,6 @@ import com.cloud.service.dao.ServiceOfferingDao;
|
||||
import com.cloud.storage.dao.DiskOfferingDao;
|
||||
import com.cloud.user.dao.AccountDao;
|
||||
import com.cloud.utils.db.TransactionLegacy;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepot;
|
||||
import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
|
||||
import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.Spy;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ConfigurationServerImplTest {
|
||||
@ -101,41 +97,6 @@ public class ConfigurationServerImplTest {
|
||||
}
|
||||
};
|
||||
|
||||
final static String TEST = "the quick brown fox jumped over the lazy dog";
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testGetBase64KeystoreNoSuchFile() throws IOException {
|
||||
ConfigurationServerImpl.getBase64Keystore("notexisting" + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
public void testGetBase64KeystoreTooBigFile() throws IOException {
|
||||
File temp = File.createTempFile("keystore", "");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
builder.append("way too long...\n");
|
||||
}
|
||||
FileUtils.writeStringToFile(temp, builder.toString());
|
||||
try {
|
||||
ConfigurationServerImpl.getBase64Keystore(temp.getPath());
|
||||
} finally {
|
||||
temp.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBase64Keystore() throws IOException {
|
||||
File temp = File.createTempFile("keystore", "");
|
||||
try {
|
||||
FileUtils.writeStringToFile(temp, Base64.encodeBase64String(TEST.getBytes()));
|
||||
final String keystore = ConfigurationServerImpl.getBase64Keystore(temp.getPath());
|
||||
// let's decode it to make sure it makes sense
|
||||
Base64.decodeBase64(keystore);
|
||||
} finally {
|
||||
temp.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWindowsScript() {
|
||||
Assert.assertTrue(windowsImpl.isOnWindows());
|
||||
|
||||
@ -574,6 +574,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getSystemVMAccessDetails(VirtualMachine vm) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see com.cloud.network.NetworkManager#implementNetwork(long, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext)
|
||||
*/
|
||||
|
||||
152
server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java
Normal file
152
server/test/org/apache/cloudstack/ca/CABackgroundTaskTest.java
Normal file
@ -0,0 +1,152 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import static org.apache.cloudstack.ca.CAManager.AutomaticCertRenewal;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.KeyPair;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.apache.cloudstack.framework.config.ConfigKey;
|
||||
import org.apache.cloudstack.utils.identity.ManagementServerNode;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.HostVO;
|
||||
import com.cloud.host.Status;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
import com.cloud.storage.Storage;
|
||||
import com.cloud.utils.exception.CloudRuntimeException;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CABackgroundTaskTest {
|
||||
|
||||
@Mock
|
||||
private CAManager caManager;
|
||||
@Mock
|
||||
private HostDao hostDao;
|
||||
|
||||
private String hostIp = "1.2.3.4";
|
||||
private HostVO host = new HostVO(1L, "some.host",Host.Type.Routing, hostIp, "255.255.255.0", null, null, null, null, null, null, null, null, null, null,
|
||||
UUID.randomUUID().toString(), Status.Up, "1.0", null, null, 1L, null, 0, 0, "aa", 0, Storage.StoragePoolType.NetworkFilesystem);
|
||||
|
||||
private X509Certificate expiredCertificate;
|
||||
private Map<String, X509Certificate> certMap = new HashMap<>();
|
||||
private CAManagerImpl.CABackgroundTask task;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
host.setManagementServerId(ManagementServerNode.getManagementServerId());
|
||||
task = new CAManagerImpl.CABackgroundTask(caManager, hostDao);
|
||||
final KeyPair keypair = CertUtils.generateRandomKeyPair(1024);
|
||||
expiredCertificate = CertUtils.generateV1Certificate(keypair, "CN=ca", "CN=ca", 0,
|
||||
"SHA256withRSA");
|
||||
|
||||
Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(host);
|
||||
Mockito.when(caManager.getActiveCertificatesMap()).thenReturn(certMap);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
certMap.clear();
|
||||
Mockito.reset(caManager);
|
||||
Mockito.reset(hostDao);
|
||||
}
|
||||
|
||||
private void overrideDefaultConfigValue(final ConfigKey configKey, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field f = ConfigKey.class.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
f.set(configKey, o);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullCert() throws Exception {
|
||||
certMap.put(hostIp, null);
|
||||
Assert.assertTrue(certMap.size() == 1);
|
||||
task.runInContext();
|
||||
Assert.assertTrue(certMap.size() == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullHost() throws Exception {
|
||||
Mockito.when(hostDao.findByIp(Mockito.anyString())).thenReturn(null);
|
||||
certMap.put(hostIp, expiredCertificate);
|
||||
Assert.assertTrue(certMap.size() == 1);
|
||||
task.runInContext();
|
||||
Assert.assertTrue(certMap.size() == 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoRenewalEnabledWithNoExceptionsOnProvisioning() throws Exception {
|
||||
overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "true");
|
||||
host.setManagementServerId(ManagementServerNode.getManagementServerId());
|
||||
certMap.put(hostIp, expiredCertificate);
|
||||
Assert.assertTrue(certMap.size() == 1);
|
||||
task.runInContext();
|
||||
Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null);
|
||||
Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoRenewalEnabledWithExceptionsOnProvisioning() throws Exception {
|
||||
overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "true");
|
||||
Mockito.when(caManager.provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString())).thenThrow(new CloudRuntimeException("some error"));
|
||||
host.setManagementServerId(ManagementServerNode.getManagementServerId());
|
||||
certMap.put(hostIp, expiredCertificate);
|
||||
Assert.assertTrue(certMap.size() == 1);
|
||||
task.runInContext();
|
||||
Mockito.verify(caManager, Mockito.times(1)).provisionCertificate(host, false, null);
|
||||
Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoRenewalDisabled() throws Exception {
|
||||
overrideDefaultConfigValue(AutomaticCertRenewal, "_defaultValue", "false");
|
||||
certMap.put(hostIp, expiredCertificate);
|
||||
Assert.assertTrue(certMap.size() == 1);
|
||||
// First round
|
||||
task.runInContext();
|
||||
Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString());
|
||||
Mockito.verify(caManager, Mockito.times(1)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
|
||||
Mockito.reset(caManager);
|
||||
// Second round
|
||||
task.runInContext();
|
||||
Mockito.verify(caManager, Mockito.times(0)).provisionCertificate(Mockito.any(Host.class), Mockito.anyBoolean(), Mockito.anyString());
|
||||
Mockito.verify(caManager, Mockito.times(0)).sendAlert(Mockito.any(Host.class), Mockito.anyString(), Mockito.anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDelay() throws Exception {
|
||||
Assert.assertTrue(task.getDelay() == CAManager.CABackgroundJobDelay.value() * 1000L);
|
||||
}
|
||||
|
||||
}
|
||||
119
server/test/org/apache/cloudstack/ca/CAManagerImplTest.java
Normal file
119
server/test/org/apache/cloudstack/ca/CAManagerImplTest.java
Normal file
@ -0,0 +1,119 @@
|
||||
//
|
||||
// Licensed to the Apache Software Foundation (ASF) under one
|
||||
// or more contributor license agreements. See the NOTICE file
|
||||
// distributed with this work for additional information
|
||||
// regarding copyright ownership. The ASF licenses this file
|
||||
// to you under the Apache License, Version 2.0 (the
|
||||
// "License"); you may not use this file except in compliance
|
||||
// with the License. You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing,
|
||||
// software distributed under the License is distributed on an
|
||||
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
// KIND, either express or implied. See the License for the
|
||||
// specific language governing permissions and limitations
|
||||
// under the License.
|
||||
//
|
||||
|
||||
package org.apache.cloudstack.ca;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.cloudstack.api.ServerApiException;
|
||||
import org.apache.cloudstack.framework.ca.CAProvider;
|
||||
import org.apache.cloudstack.framework.ca.Certificate;
|
||||
import org.apache.cloudstack.utils.security.CertUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
|
||||
import com.cloud.agent.AgentManager;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.certificate.CrlVO;
|
||||
import com.cloud.certificate.dao.CrlDao;
|
||||
import com.cloud.host.Host;
|
||||
import com.cloud.host.dao.HostDao;
|
||||
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CAManagerImplTest {
|
||||
|
||||
@Mock
|
||||
private HostDao hostDao;
|
||||
@Mock
|
||||
private CrlDao crlDao;
|
||||
@Mock
|
||||
private AgentManager agentManager;
|
||||
@Mock
|
||||
private CAProvider caProvider;
|
||||
|
||||
private CAManagerImpl caManager;
|
||||
|
||||
private void addField(final CAManagerImpl provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException {
|
||||
Field f = CAManagerImpl.class.getDeclaredField(name);
|
||||
f.setAccessible(true);
|
||||
f.set(provider, o);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
caManager = new CAManagerImpl();
|
||||
addField(caManager, "crlDao", crlDao);
|
||||
addField(caManager, "hostDao", hostDao);
|
||||
addField(caManager, "agentManager", agentManager);
|
||||
addField(caManager, "configuredCaProvider", caProvider);
|
||||
|
||||
Mockito.when(caProvider.getProviderName()).thenReturn("root");
|
||||
caManager.setCaProviders(Collections.singletonList(caProvider));
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Mockito.reset(crlDao);
|
||||
Mockito.reset(agentManager);
|
||||
Mockito.reset(caProvider);
|
||||
}
|
||||
|
||||
@Test(expected = ServerApiException.class)
|
||||
public void testIssueCertificateThrowsException() throws Exception {
|
||||
caManager.issueCertificate(null, null, null, 1, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIssueCertificate() throws Exception {
|
||||
caManager.issueCertificate(null, Collections.singletonList("domain.example"), null, 1, null);
|
||||
Mockito.verify(caProvider, Mockito.times(1)).issueCertificate(Mockito.anyList(), Mockito.anyList(), Mockito.anyInt());
|
||||
Mockito.verify(caProvider, Mockito.times(0)).issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevokeCertificate() throws Exception {
|
||||
final CrlVO crl = new CrlVO(CertUtils.generateRandomBigInt(), "some.domain", "some-uuid");
|
||||
Mockito.when(crlDao.revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString())).thenReturn(crl);
|
||||
Mockito.when(caProvider.revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString())).thenReturn(true);
|
||||
Assert.assertTrue(caManager.revokeCertificate(crl.getCertSerial(), crl.getCertCn(), null));
|
||||
Mockito.verify(caProvider, Mockito.times(1)).revokeCertificate(Mockito.any(BigInteger.class), Mockito.anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvisionCertificate() throws Exception {
|
||||
final Host host = Mockito.mock(Host.class);
|
||||
Mockito.when(host.getPrivateIpAddress()).thenReturn("1.2.3.4");
|
||||
final X509Certificate certificate = CertUtils.generateV1Certificate(CertUtils.generateRandomKeyPair(1024), "CN=ca", "CN=ca", 1, "SHA256withRSA");
|
||||
Mockito.when(caProvider.issueCertificate(Mockito.anyString(), Mockito.anyList(), Mockito.anyList(), Mockito.anyInt())).thenReturn(new Certificate(certificate, null, Collections.singletonList(certificate)));
|
||||
Mockito.when(agentManager.send(Mockito.anyLong(), Mockito.any(SetupKeyStoreCommand.class))).thenReturn(new SetupKeystoreAnswer("someCsr"));
|
||||
Mockito.when(agentManager.reconnect(Mockito.anyLong())).thenReturn(true);
|
||||
Assert.assertTrue(caManager.provisionCertificate(host, true, null));
|
||||
Mockito.verify(agentManager, Mockito.times(2)).send(Mockito.anyLong(), Mockito.any(Answer.class));
|
||||
Mockito.verify(agentManager, Mockito.times(1)).reconnect(Mockito.anyLong());
|
||||
}
|
||||
}
|
||||
@ -45,6 +45,12 @@ public class BackgroundPollManagerImplTest {
|
||||
didIRun = true;
|
||||
counter++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getDelay() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Before
|
||||
|
||||
@ -123,6 +123,7 @@ import com.cloud.user.AccountService;
|
||||
import com.cloud.utils.DateUtil;
|
||||
import com.cloud.utils.NumbersUtil;
|
||||
import com.cloud.utils.Pair;
|
||||
import com.cloud.utils.StringUtils;
|
||||
import com.cloud.utils.component.ManagerBase;
|
||||
import com.cloud.utils.db.GlobalLock;
|
||||
import com.cloud.utils.db.QueryBuilder;
|
||||
@ -1118,7 +1119,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
|
||||
|
||||
StringBuilder buf = profile.getBootArgsBuilder();
|
||||
buf.append(" template=domP type=secstorage");
|
||||
buf.append(" host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value());
|
||||
buf.append(" host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
|
||||
buf.append(" port=").append(_mgmtPort);
|
||||
buf.append(" name=").append(profile.getVirtualMachine().getHostName());
|
||||
|
||||
|
||||
@ -16,6 +16,71 @@
|
||||
// under the License.
|
||||
package org.apache.cloudstack.storage.resource;
|
||||
|
||||
import static com.cloud.utils.StringUtils.join;
|
||||
import static com.cloud.utils.storage.S3.S3Utils.putFile;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.commons.lang.StringUtils.substringAfterLast;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.CopyCommand;
|
||||
import org.apache.cloudstack.storage.command.DeleteCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
|
||||
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusCommand;
|
||||
import org.apache.cloudstack.storage.template.DownloadManager;
|
||||
import org.apache.cloudstack.storage.template.DownloadManagerImpl;
|
||||
import org.apache.cloudstack.storage.template.DownloadManagerImpl.ZfsPathParser;
|
||||
import org.apache.cloudstack.storage.template.UploadEntity;
|
||||
import org.apache.cloudstack.storage.template.UploadManager;
|
||||
import org.apache.cloudstack.storage.template.UploadManagerImpl;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import com.amazonaws.services.s3.model.S3ObjectSummary;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.agent.api.CheckHealthAnswer;
|
||||
@ -83,6 +148,7 @@ import com.cloud.utils.storage.S3.S3Utils;
|
||||
import com.cloud.vm.SecondaryStorageVm;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
@ -96,69 +162,6 @@ import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
|
||||
import org.apache.cloudstack.storage.command.CopyCmdAnswer;
|
||||
import org.apache.cloudstack.storage.command.CopyCommand;
|
||||
import org.apache.cloudstack.storage.command.DeleteCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadCommand;
|
||||
import org.apache.cloudstack.storage.command.DownloadProgressCommand;
|
||||
import org.apache.cloudstack.storage.command.TemplateOrVolumePostUploadCommand;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusAnswer.UploadStatus;
|
||||
import org.apache.cloudstack.storage.command.UploadStatusCommand;
|
||||
import org.apache.cloudstack.storage.template.DownloadManager;
|
||||
import org.apache.cloudstack.storage.template.DownloadManagerImpl;
|
||||
import org.apache.cloudstack.storage.template.DownloadManagerImpl.ZfsPathParser;
|
||||
import org.apache.cloudstack.storage.template.UploadEntity;
|
||||
import org.apache.cloudstack.storage.template.UploadManager;
|
||||
import org.apache.cloudstack.storage.template.UploadManagerImpl;
|
||||
import org.apache.cloudstack.storage.to.SnapshotObjectTO;
|
||||
import org.apache.cloudstack.storage.to.TemplateObjectTO;
|
||||
import org.apache.cloudstack.storage.to.VolumeObjectTO;
|
||||
import org.apache.cloudstack.utils.imagestore.ImageStoreUtil;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.utils.URLEncodedUtils;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.log4j.Logger;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
|
||||
import javax.naming.ConfigurationException;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.cloud.utils.StringUtils.join;
|
||||
import static com.cloud.utils.storage.S3.S3Utils.putFile;
|
||||
import static java.lang.String.format;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.commons.lang.StringUtils.substringAfterLast;
|
||||
|
||||
public class NfsSecondaryStorageResource extends ServerResourceBase implements SecondaryStorageResource {
|
||||
|
||||
@ -2231,9 +2234,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
|
||||
if (_inSystemVM) {
|
||||
_localgw = (String)params.get("localgw");
|
||||
if (_localgw != null) { // can only happen inside service vm
|
||||
String mgmtHost = (String)params.get("host");
|
||||
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
|
||||
|
||||
String mgmtHosts = (String)params.get("host");
|
||||
for (final String mgmtHost : mgmtHosts.split(",")) {
|
||||
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
|
||||
}
|
||||
String internalDns1 = (String)params.get("internaldns1");
|
||||
if (internalDns1 == null) {
|
||||
s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");
|
||||
|
||||
@ -123,3 +123,18 @@ CREATE VIEW `template_view` AS
|
||||
OR (`resource_tags`.`resource_type` = 'ISO')))));
|
||||
|
||||
UPDATE `cloud`.`configuration` SET value = '600', default_value = '600' WHERE category = 'Advanced' AND name = 'router.aggregation.command.each.timeout';
|
||||
|
||||
-- CA framework changes
|
||||
DELETE from `cloud`.`configuration` where name='ssl.keystore';
|
||||
|
||||
-- Certificate Revocation List
|
||||
CREATE TABLE IF NOT EXISTS `cloud`.`crl` (
|
||||
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`serial` varchar(255) UNIQUE NOT NULL COMMENT 'certificate\'s serial number as hex string',
|
||||
`cn` varchar(255) COMMENT 'certificate\'s common name',
|
||||
`revoker_uuid` varchar(40) COMMENT 'revoker user account uuid',
|
||||
`revoked` datetime COMMENT 'date of revocation',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY (`serial`),
|
||||
UNIQUE KEY (`serial`, `cn`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
@ -27,3 +27,6 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value,
|
||||
|
||||
-- Enable dynamic RBAC by default for fresh deployments
|
||||
INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'RoleService', 'dynamic.apichecker.enabled', 'true');
|
||||
|
||||
-- Enable RootCA auth strictness for fresh deployments
|
||||
INSERT INTO `cloud`.`configuration` (category, instance, component, name, value) VALUES ('Advanced', 'DEFAULT', 'RootCAProvider', 'ca.plugin.root.auth.strictness', 'true');
|
||||
|
||||
@ -246,6 +246,13 @@ under the License.
|
||||
<value>true</value>
|
||||
</configuration>
|
||||
<!--
|
||||
Enable RootCA auth strictness for fresh installations
|
||||
-->
|
||||
<configuration>
|
||||
<name>ca.plugin.root.auth.strictness</name>
|
||||
<value>true</value>
|
||||
</configuration>
|
||||
<!--
|
||||
The instance.name parameter is tacked to the end of the names of the VMs you create.
|
||||
So, for example, with the TEST value as it ships by default, your VMs would be named:
|
||||
i-X-Y-TEST, where X is the account ID and Y is the serially incrementing VM ID.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user