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:
Rohit Yadav 2017-08-28 12:15:11 +02:00 committed by GitHub
parent 64e56a2159
commit 7ce54bf7a8
124 changed files with 5155 additions and 760 deletions

View File

@ -34,6 +34,7 @@ env:
matrix: matrix:
- TESTS="smoke/test_affinity_groups - TESTS="smoke/test_affinity_groups
smoke/test_affinity_groups_projects smoke/test_affinity_groups_projects
smoke/test_certauthority_root
smoke/test_deploy_vgpu_enabled_vm smoke/test_deploy_vgpu_enabled_vm
smoke/test_deploy_vm_iso smoke/test_deploy_vm_iso
smoke/test_deploy_vm_root_resize smoke/test_deploy_vm_root_resize

View File

@ -16,12 +16,14 @@
// under the License. // under the License.
package com.cloud.agent; package com.cloud.agent;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.channels.ClosedChannelException; import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -35,7 +37,13 @@ import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.ConfigurationException; 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.managed.context.ManagedContextTimerTask;
import org.apache.cloudstack.utils.security.KeyStoreUtils;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.slf4j.MDC; import org.slf4j.MDC;
@ -68,6 +76,7 @@ import com.cloud.utils.nio.NioConnection;
import com.cloud.utils.nio.Task; import com.cloud.utils.nio.Task;
import com.cloud.utils.script.OutputInterpreter; import com.cloud.utils.script.OutputInterpreter;
import com.cloud.utils.script.Script; import com.cloud.utils.script.Script;
import com.google.common.base.Strings;
/** /**
* @config * @config
@ -126,6 +135,9 @@ public class Agent implements HandlerFactory, IAgentControl {
private final ThreadPoolExecutor _ugentTaskPool; private final ThreadPoolExecutor _ugentTaskPool;
ExecutorService _executor; ExecutorService _executor;
private String _keystoreSetupPath;
private String _keystoreCertImportPath;
// for simulator use only // for simulator use only
public Agent(final IAgentShell shell) { public Agent(final IAgentShell shell) {
_shell = shell; _shell = shell;
@ -166,7 +178,8 @@ public class Agent implements HandlerFactory, IAgentControl {
throw new ConfigurationException("Unable to configure " + _resource.getName()); 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()); // ((NioClient)_connection).setBindAddress(_shell.getPrivateIp());
@ -182,7 +195,7 @@ public class Agent implements HandlerFactory, IAgentControl {
"agentRequest-Handler")); "agentRequest-Handler"));
s_logger.info("Agent [id = " + (_id != null ? _id : "new") + " : type = " + getResourceName() + " : zone = " + _shell.getZone() + " : pod = " + _shell.getPod() + 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() { public String getVersion() {
@ -224,6 +237,16 @@ public class Agent implements HandlerFactory, IAgentControl {
throw new CloudRuntimeException("Unable to start the resource: " + _resource.getName()); 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 { try {
_connection.start(); _connection.start();
} catch (final NioConnectionException e) { } 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..."); s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...");
} }
while (!_connection.isStartup()) { while (!_connection.isStartup()) {
final String host = _shell.getHost();
_shell.getBackoffAlgorithm().waitBeforeRetry(); _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 { try {
_connection.start(); _connection.start();
} catch (final NioConnectionException e) { } catch (final NioConnectionException e) {
@ -408,14 +433,21 @@ public class Agent implements HandlerFactory, IAgentControl {
_shell.getBackoffAlgorithm().waitBeforeRetry(); _shell.getBackoffAlgorithm().waitBeforeRetry();
} }
_connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); final String host = _shell.getHost();
do { do {
s_logger.info("Reconnecting..."); _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this);
s_logger.info("Reconnecting to host:" + host);
try { try {
_connection.start(); _connection.start();
} catch (final NioConnectionException e) { } catch (final NioConnectionException e) {
s_logger.warn("NIO Connection Exception " + e); s_logger.warn("NIO Connection Exception " + e);
s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); 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(); _shell.getBackoffAlgorithm().waitBeforeRetry();
} while (!_connection.isStartup()); } while (!_connection.isStartup());
@ -515,7 +547,10 @@ public class Agent implements HandlerFactory, IAgentControl {
s_logger.warn("No handler found to process cmd: " + cmd.toString()); s_logger.warn("No handler found to process cmd: " + cmd.toString());
answer = new AgentControlAnswer(cmd); 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 { } else {
if (cmd instanceof ReadyCommand) { if (cmd instanceof ReadyCommand) {
processReadyCommand(cmd); 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) { public void processResponse(final Response response, final Link link) {
final Answer answer = response.getAnswer(); final Answer answer = response.getAnswer();
if (s_logger.isDebugEnabled()) { if (s_logger.isDebugEnabled()) {

View File

@ -67,6 +67,7 @@ public class AgentShell implements IAgentShell, Daemon {
private int _proxyPort; private int _proxyPort;
private int _workers; private int _workers;
private String _guid; private String _guid;
private int _hostCounter = 0;
private int _nextAgentId = 1; private int _nextAgentId = 1;
private volatile boolean _exit = false; private volatile boolean _exit = false;
private int _pingRetries; private int _pingRetries;
@ -107,7 +108,17 @@ public class AgentShell implements IAgentShell, Daemon {
@Override @Override
public String getHost() { 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 @Override

View File

@ -51,6 +51,9 @@ public class PropertiesStorage implements StorageComponent {
@Override @Override
public synchronized void persist(String key, String value) { 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); _properties.setProperty(key, value);
FileOutputStream output = null; FileOutputStream output = null;
try { 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 @Override
public synchronized boolean configure(String name, Map<String, Object> params) { public synchronized boolean configure(String name, Map<String, Object> params) {
_name = name; _name = name;
@ -86,17 +103,7 @@ public class PropertiesStorage implements StorageComponent {
return false; return false;
} }
} }
try { return loadFromFile(file);
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 @Override

View File

@ -32,11 +32,8 @@ import java.util.Properties;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import org.apache.log4j.Logger;
import com.google.gson.Gson;
import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.managed.context.ManagedContextRunnable;
import org.apache.log4j.Logger;
import com.cloud.agent.Agent.ExitStatus; import com.cloud.agent.Agent.ExitStatus;
import com.cloud.agent.api.AgentControlAnswer; import com.cloud.agent.api.AgentControlAnswer;
@ -64,6 +61,7 @@ import com.cloud.resource.ServerResourceBase;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.net.NetUtils; import com.cloud.utils.net.NetUtils;
import com.cloud.utils.script.Script; 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); _proxyVmId = NumbersUtil.parseLong(value, 0);
if (_localgw != null) { if (_localgw != null) {
String mgmtHost = (String)params.get("host"); String mgmtHosts = (String)params.get("host");
if (_eth1ip != null) { 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"); String internalDns1 = (String) params.get("internaldns1");
if (internalDns1 == null) { if (internalDns1 == null) {
s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage"); s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");

View File

@ -16,6 +16,8 @@
// under the License. // under the License.
package com.cloud.agent; package com.cloud.agent;
import java.util.Arrays;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
@ -23,6 +25,8 @@ import javax.naming.ConfigurationException;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import com.cloud.utils.StringUtils;
public class AgentShellTest { public class AgentShellTest {
@Test @Test
public void parseCommand() throws ConfigurationException { public void parseCommand() throws ConfigurationException {
@ -44,4 +48,15 @@ public class AgentShellTest {
Assert.assertNotNull(shell.getProperties()); Assert.assertNotNull(shell.getProperties());
Assert.assertFalse(shell.getProperties().entrySet().isEmpty()); 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));
}
} }

View File

@ -51,6 +51,11 @@
<artifactId>cloud-framework-config</artifactId> <artifactId>cloud-framework-config</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-ca</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>

View File

@ -16,6 +16,14 @@
// under the License. // under the License.
package com.cloud.event; 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.DataCenter;
import com.cloud.dc.Pod; import com.cloud.dc.Pod;
import com.cloud.dc.StorageNetworkIpRange; import com.cloud.dc.StorageNetworkIpRange;
@ -54,10 +62,10 @@ import com.cloud.offering.NetworkOffering;
import com.cloud.offering.ServiceOffering; import com.cloud.offering.ServiceOffering;
import com.cloud.projects.Project; import com.cloud.projects.Project;
import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag;
import com.cloud.storage.StoragePool;
import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOS;
import com.cloud.storage.GuestOSHypervisor; import com.cloud.storage.GuestOSHypervisor;
import com.cloud.storage.Snapshot; import com.cloud.storage.Snapshot;
import com.cloud.storage.StoragePool;
import com.cloud.storage.Volume; import com.cloud.storage.Volume;
import com.cloud.storage.snapshot.SnapshotPolicy; import com.cloud.storage.snapshot.SnapshotPolicy;
import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate;
@ -66,13 +74,6 @@ import com.cloud.user.User;
import com.cloud.vm.Nic; import com.cloud.vm.Nic;
import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.NicSecondaryIp;
import com.cloud.vm.VirtualMachine; 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 { 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_UPDATE = "ROLE.PERMISSION.UPDATE";
public static final String EVENT_ROLE_PERMISSION_DELETE = "ROLE.PERMISSION.DELETE"; 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 // Account events
public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE"; public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE";
public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE"; public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE";

View File

@ -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_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_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_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() { public short getType() {
return type; return type;

View File

@ -37,10 +37,12 @@ public class ApiConstants {
public static final String BYTES_WRITE_RATE = "byteswriterate"; public static final String BYTES_WRITE_RATE = "byteswriterate";
public static final String CATEGORY = "category"; public static final String CATEGORY = "category";
public static final String CAN_REVERT = "canrevert"; 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 = "certificate";
public static final String CERTIFICATE_CHAIN = "certchain"; public static final String CERTIFICATE_CHAIN = "certchain";
public static final String CERTIFICATE_FINGERPRINT = "fingerprint"; public static final String CERTIFICATE_FINGERPRINT = "fingerprint";
public static final String CERTIFICATE_ID = "certid"; public static final String CERTIFICATE_ID = "certid";
public static final String CSR = "csr";
public static final String PRIVATE_KEY = "privatekey"; public static final String PRIVATE_KEY = "privatekey";
public static final String DOMAIN_SUFFIX = "domainsuffix"; public static final String DOMAIN_SUFFIX = "domainsuffix";
public static final String DNS_SEARCH_ORDER = "dnssearchorder"; 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_ID = "clusterid";
public static final String CLUSTER_NAME = "clustername"; public static final String CLUSTER_NAME = "clustername";
public static final String CLUSTER_TYPE = "clustertype"; public static final String CLUSTER_TYPE = "clustertype";
public static final String CN = "cn";
public static final String COMMAND = "command"; public static final String COMMAND = "command";
public static final String CMD_EVENT_TYPE = "cmdeventtype"; public static final String CMD_EVENT_TYPE = "cmdeventtype";
public static final String COMPONENT = "component"; 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_END_PORT = "publicendport";
public static final String PUBLIC_ZONE = "publiczone"; public static final String PUBLIC_ZONE = "publiczone";
public static final String RECEIVED_BYTES = "receivedbytes"; public static final String RECEIVED_BYTES = "receivedbytes";
public static final String RECONNECT = "reconnect";
public static final String REQUIRES_HVM = "requireshvm"; public static final String REQUIRES_HVM = "requireshvm";
public static final String RESOURCE_TYPE = "resourcetype"; public static final String RESOURCE_TYPE = "resourcetype";
public static final String RESPONSE = "response"; 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 SECURITY_GROUP_ID = "securitygroupid";
public static final String SENT = "sent"; public static final String SENT = "sent";
public static final String SENT_BYTES = "sentbytes"; 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 SERVICE_OFFERING_ID = "serviceofferingid";
public static final String SESSIONKEY = "sessionkey"; public static final String SESSIONKEY = "sessionkey";
public static final String SHOW_CAPACITIES = "showcapacities"; public static final String SHOW_CAPACITIES = "showcapacities";

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}

View File

@ -18,4 +18,10 @@
package org.apache.cloudstack.poll; package org.apache.cloudstack.poll;
public interface BackgroundPollTask extends Runnable { public interface BackgroundPollTask extends Runnable {
/**
* Returns delay in milliseconds between two rounds
* When it returns null a default value is used
* @return
*/
Long getDelay();
} }

View File

@ -63,6 +63,11 @@
<artifactId>cloud-plugin-acl-dynamic-role-based</artifactId> <artifactId>cloud-plugin-acl-dynamic-role-based</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-ca-rootca</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.cloudstack</groupId> <groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-dedicated-resources</artifactId> <artifactId>cloud-plugin-dedicated-resources</artifactId>
@ -274,6 +279,11 @@
<artifactId>cloud-mom-kafka</artifactId> <artifactId>cloud-mom-kafka</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-ca</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.cloudstack</groupId> <groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-ipc</artifactId> <artifactId>cloud-framework-ipc</artifactId>
@ -478,6 +488,7 @@
<webAppConfig> <webAppConfig>
<contextPath>/client</contextPath> <contextPath>/client</contextPath>
<extraClasspath>${project.build.directory}/utilities/scripts/db/;${project.build.directory}/utilities/scripts/db/db/</extraClasspath> <extraClasspath>${project.build.directory}/utilities/scripts/db/;${project.build.directory}/utilities/scripts/db/db/</extraClasspath>
<webInfIncludeJarPattern>.*/cloud.*jar$|.*/classes/.*</webInfIncludeJarPattern>
</webAppConfig> </webAppConfig>
<systemProperties> <systemProperties>

View File

@ -94,7 +94,7 @@
maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8" maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
clientAuth="false" sslProtocol="TLS" clientAuth="false" sslProtocol="TLS"
keystoreType="JKS" keystoreType="JKS"
keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore" keystoreFile="/etc/cloudstack/management/cloud.jks"
keystorePass="vmops.com"/> keystorePass="vmops.com"/>
<!-- Define an AJP 1.3 Connector on port 20400 --> <!-- Define an AJP 1.3 Connector on port 20400 -->

View File

@ -94,7 +94,7 @@
maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8" maxThreads="150" scheme="https" secure="true" URIEncoding="UTF-8"
clientAuth="false" sslProtocol="TLS" clientAuth="false" sslProtocol="TLS"
keystoreType="JKS" keystoreType="JKS"
keystoreFile="/etc/cloudstack/management/cloudmanagementserver.keystore" keystoreFile="/etc/cloudstack/management/cloud.jks"
keystorePass="vmops.com"/> keystorePass="vmops.com"/>
<!-- Define an AJP 1.3 Connector on port 20400 --> <!-- Define an AJP 1.3 Connector on port 20400 -->

View File

@ -40,7 +40,7 @@ CATALINA_TMPDIR="@MSENVIRON@/temp"
# Use JAVA_OPTS to set java.library.path for libtcnative.so # Use JAVA_OPTS to set java.library.path for libtcnative.so
#JAVA_OPTS="-Djava.library.path=/usr/lib64" #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 # What user should run tomcat
TOMCAT_USER="@MSUSER@" TOMCAT_USER="@MSUSER@"

View 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

View File

@ -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>

View File

@ -312,4 +312,8 @@
<property name="orderConfigDefault" value="IPMITOOL" /> <property name="orderConfigDefault" value="IPMITOOL" />
</bean> </bean>
<bean id="caProvidersRegistry"
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
</bean>
</beans> </beans>

View File

@ -20,6 +20,7 @@
package com.cloud.agent.api.routing; package com.cloud.agent.api.routing;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import com.cloud.agent.api.Command; import com.cloud.agent.api.Command;
@ -46,6 +47,18 @@ public abstract class NetworkElementCommand extends Command {
super(); 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) { public void setAccessDetail(final String name, final String value) {
accessDetails.put(name, value); accessDetails.put(name, value);
} }

View File

@ -35,6 +35,11 @@ import java.util.concurrent.locks.ReentrantLock;
import javax.naming.ConfigurationException; 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 org.apache.log4j.Logger;
import com.cloud.agent.api.Answer; import com.cloud.agent.api.Answer;
@ -108,6 +113,14 @@ public class VirtualRoutingResource {
return executeQueryCommand(cmd); return executeQueryCommand(cmd);
} }
if (cmd instanceof SetupKeyStoreCommand) {
return execute((SetupKeyStoreCommand) cmd);
}
if (cmd instanceof SetupCertificateCommand) {
return execute((SetupCertificateCommand) cmd);
}
if (cmd instanceof AggregationControlCommand) { if (cmd instanceof AggregationControlCommand) {
return execute((AggregationControlCommand)cmd); 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) { private Answer executeQueryCommand(NetworkElementCommand cmd) {
if (cmd instanceof CheckRouterCommand) { if (cmd instanceof CheckRouterCommand) {
return execute((CheckRouterCommand)cmd); return execute((CheckRouterCommand)cmd);

View 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.
//
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;
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -50,9 +50,6 @@ if [ "$1" = configure ]; then
cp -a $OLDCONFDIR/$FILE $NEWCONFDIR/$FILE cp -a $OLDCONFDIR/$FILE $NEWCONFDIR/$FILE
fi fi
done done
if [ -f "$OLDCONFDIR/cloud.keystore" ]; then
cp -a $OLDCONFDIR/cloud.keystore $NEWCONFDIR/cloudmanagementserver.keystore
fi
fi fi
CONFDIR="/etc/cloudstack/management" CONFDIR="/etc/cloudstack/management"

View File

@ -119,6 +119,11 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
VALUES ('Advanced', 'DEFAULT', 'RoleService', VALUES ('Advanced', 'DEFAULT', 'RoleService',
'dynamic.apichecker.enabled', 'true'); '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" -- 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) INSERT INTO `cloud`.`configuration` (category, instance, component, name, value)
VALUES ('Advanced', 'DEFAULT', 'management-server', VALUES ('Advanced', 'DEFAULT', 'management-server',

View File

@ -98,6 +98,8 @@ public interface NetworkOrchestrationService {
List<NicProfile> getNicProfiles(VirtualMachine vm); List<NicProfile> getNicProfiles(VirtualMachine vm);
Map<String, String> getSystemVMAccessDetails(VirtualMachine vm);
Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long networkId, DeployDestination dest, ReservationContext context) Pair<? extends NetworkGuru, ? extends Network> implementNetwork(long networkId, DeployDestination dest, ReservationContext context)
throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException;

View File

@ -37,6 +37,7 @@ import java.util.concurrent.locks.ReentrantLock;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; import javax.naming.ConfigurationException;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@ -134,6 +135,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl
protected int _monitorId = 0; protected int _monitorId = 0;
private final Lock _agentStatusLock = new ReentrantLock(); private final Lock _agentStatusLock = new ReentrantLock();
@Inject
protected CAManager caService;
@Inject @Inject
protected EntityManager _entityMgr; 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 // allow core threads to time out even when there are no items in the queue
_connectExecutor.allowCoreThreadTimeOut(true); _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"); s_logger.info("Listening on " + Port.value() + " with " + Workers.value() + " workers");
// executes all agent commands other than cron and ping // 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); s_logger.debug("The next status of agent " + hostId + "is " + nextStatus + ", current status is " + currentStatus);
} }
} }
caService.purgeHostCertificate(host);
} }
if (s_logger.isDebugEnabled()) { if (s_logger.isDebugEnabled()) {

View File

@ -495,6 +495,7 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
} }
final String ip = ms.getServiceIP(); final String ip = ms.getServiceIP();
InetAddress addr; InetAddress addr;
int port = Port.value();
try { try {
addr = InetAddress.getByName(ip); addr = InetAddress.getByName(ip);
} catch (final UnknownHostException e) { } catch (final UnknownHostException e) {
@ -502,21 +503,21 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
} }
SocketChannel ch1 = null; SocketChannel ch1 = null;
try { try {
ch1 = SocketChannel.open(new InetSocketAddress(addr, Port.value())); ch1 = SocketChannel.open(new InetSocketAddress(addr, port));
ch1.configureBlocking(false); ch1.configureBlocking(false);
ch1.socket().setKeepAlive(true); ch1.socket().setKeepAlive(true);
ch1.socket().setSoTimeout(60 * 1000); ch1.socket().setSoTimeout(60 * 1000);
try { try {
final SSLContext sslContext = Link.initSSLContext(true); SSLContext sslContext = Link.initClientSSLContext();
sslEngine = sslContext.createSSLEngine(ip, Port.value()); sslEngine = sslContext.createSSLEngine(ip, port);
sslEngine.setUseClientMode(true); sslEngine.setUseClientMode(true);
sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols())); sslEngine.setEnabledProtocols(SSLUtils.getSupportedProtocols(sslEngine.getEnabledProtocols()));
sslEngine.beginHandshake(); sslEngine.beginHandshake();
if (!Link.doHandshake(ch1, sslEngine, true)) { if (!Link.doHandshake(ch1, sslEngine, true)) {
ch1.close(); 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) { } catch (final Exception e) {
ch1.close(); ch1.close();
throw new IOException("SSL: Fail to init SSL! " + e); throw new IOException("SSL: Fail to init SSL! " + e);
@ -528,10 +529,12 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust
_sslEngines.put(peerName, sslEngine); _sslEngines.put(peerName, sslEngine);
return ch1; return ch1;
} catch (final IOException e) { } catch (final IOException e) {
try { if (ch1 != null) {
ch1.close(); try {
} catch (final IOException ex) { ch1.close();
s_logger.error("failed to close failed peer socket: " + ex); } 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); s_logger.warn("Unable to connect to peer management server: " + peerName + ", ip: " + ip + " due to " + e.getMessage(), e);
return null; return null;

View File

@ -22,6 +22,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -39,12 +40,14 @@ import javax.naming.ConfigurationException;
import com.cloud.agent.api.AttachOrDettachConfigDriveCommand; import com.cloud.agent.api.AttachOrDettachConfigDriveCommand;
import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao; import org.apache.cloudstack.affinity.dao.AffinityGroupVMMapDao;
import org.apache.cloudstack.ca.CAManager;
import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.context.CallContext;
import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; 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.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; 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.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable; 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.UnPlugNicAnswer;
import com.cloud.agent.api.UnPlugNicCommand; import com.cloud.agent.api.UnPlugNicCommand;
import com.cloud.agent.api.UnregisterVMCommand; 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.DiskTO;
import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.agent.api.to.GPUDeviceTO;
import com.cloud.agent.api.to.NicTO; 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.VMSnapshotManager;
import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.VMSnapshotVO;
import com.cloud.vm.snapshot.dao.VMSnapshotDao; import com.cloud.vm.snapshot.dao.VMSnapshotDao;
import com.google.common.base.Strings;
public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable { public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMachineManager, VmWorkJobHandler, Listener, Configurable {
private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class); private static final Logger s_logger = Logger.getLogger(VirtualMachineManagerImpl.class);
@ -284,7 +289,9 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
@Inject @Inject
protected UserVmDetailsDao _vmDetailsDao; protected UserVmDetailsDao _vmDetailsDao;
@Inject @Inject
ServiceOfferingDao _serviceOfferingDao = null; protected ServiceOfferingDao _serviceOfferingDao = null;
@Inject
protected CAManager caManager;
@Inject @Inject
ConfigDepot _configDepot; ConfigDepot _configDepot;
@ -1023,7 +1030,6 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType()))); cmds.addCommand(new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())));
vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx); vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx);
work = _workDao.findById(work.getId()); work = _workDao.findById(work.getId());
@ -1073,6 +1079,23 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac
if (s_logger.isDebugEnabled()) { if (s_logger.isDebugEnabled()) {
s_logger.debug("Start completed for VM " + vm); 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; return;
} else { } else {
if (s_logger.isDebugEnabled()) { if (s_logger.isDebugEnabled()) {

View File

@ -68,6 +68,7 @@ import com.cloud.agent.api.CheckNetworkCommand;
import com.cloud.agent.api.Command; import com.cloud.agent.api.Command;
import com.cloud.agent.api.StartupCommand; import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand; import com.cloud.agent.api.StartupRoutingCommand;
import com.cloud.agent.api.routing.NetworkElementCommand;
import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.NicTO;
import com.cloud.alert.AlertManager; import com.cloud.alert.AlertManager;
import com.cloud.configuration.ConfigurationManager; 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.NicSecondaryIpVO;
import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDao; import com.cloud.vm.dao.VMInstanceDao;
import com.google.common.base.Strings;
/** /**
* NetworkManagerImpl implements NetworkManager. * NetworkManagerImpl implements NetworkManager.
@ -3488,6 +3490,39 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra
return profiles; 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 { protected boolean stateTransitTo(final NetworkVO network, final Network.Event e) throws NoTransitionException {
return _stateMachine.transitTo(network, e, null, _networksDao); return _stateMachine.transitTo(network, e, null, _networksDao);
} }

View File

@ -130,6 +130,7 @@
<bean id="autoScaleVmProfileDaoImpl" class="com.cloud.network.as.dao.AutoScaleVmProfileDaoImpl" /> <bean id="autoScaleVmProfileDaoImpl" class="com.cloud.network.as.dao.AutoScaleVmProfileDaoImpl" />
<bean id="capacityDaoImpl" class="com.cloud.capacity.dao.CapacityDaoImpl" /> <bean id="capacityDaoImpl" class="com.cloud.capacity.dao.CapacityDaoImpl" />
<bean id="certificateDaoImpl" class="com.cloud.certificate.dao.CertificateDaoImpl" /> <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="clusterDaoImpl" class="com.cloud.dc.dao.ClusterDaoImpl" />
<bean id="clusterDetailsDaoImpl" class="com.cloud.dc.ClusterDetailsDaoImpl" /> <bean id="clusterDetailsDaoImpl" class="com.cloud.dc.ClusterDetailsDaoImpl" />
<bean id="clusterVSMMapDaoImpl" class="com.cloud.dc.dao.ClusterVSMMapDaoImpl" /> <bean id="clusterVSMMapDaoImpl" class="com.cloud.dc.dao.ClusterVSMMapDaoImpl" />

View 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;
}
}

View 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);
}

View 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);
}
}

View File

@ -101,4 +101,6 @@ public interface HostDao extends GenericDao<HostVO, Long>, StateDao<Status, Stat
List<Long> listClustersByHostTag(String hostTagOnOffering); List<Long> listClustersByHostTag(String hostTagOnOffering);
List<HostVO> listByType(Type type); List<HostVO> listByType(Type type);
HostVO findByIp(String ip);
} }

View File

@ -89,6 +89,7 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
protected SearchBuilder<HostVO> DcPrivateIpAddressSearch; protected SearchBuilder<HostVO> DcPrivateIpAddressSearch;
protected SearchBuilder<HostVO> DcStorageIpAddressSearch; protected SearchBuilder<HostVO> DcStorageIpAddressSearch;
protected SearchBuilder<HostVO> PublicIpAddressSearch; protected SearchBuilder<HostVO> PublicIpAddressSearch;
protected SearchBuilder<HostVO> AnyIpAddressSearch;
protected SearchBuilder<HostVO> GuidSearch; protected SearchBuilder<HostVO> GuidSearch;
protected SearchBuilder<HostVO> DcSearch; 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.and("publicIpAddress", PublicIpAddressSearch.entity().getPublicIpAddress(), SearchCriteria.Op.EQ);
PublicIpAddressSearch.done(); 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 = createSearchBuilder();
GuidSearch.and("guid", GuidSearch.entity().getGuid(), SearchCriteria.Op.EQ); GuidSearch.and("guid", GuidSearch.entity().getGuid(), SearchCriteria.Op.EQ);
GuidSearch.done(); GuidSearch.done();
@ -1118,6 +1124,13 @@ public class HostDaoImpl extends GenericDaoBase<HostVO, Long> implements HostDao
return findOneBy(sc); 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 @Override
public List<HostVO> findHypervisorHostInCluster(long clusterId) { public List<HostVO> findHypervisorHostInCluster(long clusterId) {

29
framework/ca/pom.xml Normal file
View 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>

View File

@ -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();
}

View File

@ -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;
}

View 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.
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;
}
}

View File

@ -44,6 +44,7 @@
</build> </build>
<modules> <modules>
<module>ipc</module> <module>ipc</module>
<module>ca</module>
<module>rest</module> <module>rest</module>
<module>events</module> <module>events</module>
<module>jobs</module> <module>jobs</module>

View File

@ -499,12 +499,6 @@ else
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually" echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
fi 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 %preun agent
/sbin/service cloudstack-agent stop || true /sbin/service cloudstack-agent stop || true
if [ "$1" == "0" ] ; then if [ "$1" == "0" ] ; then

View File

@ -464,12 +464,6 @@ else
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually" echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
fi 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 %preun agent
/sbin/service cloudstack-agent stop || true /sbin/service cloudstack-agent stop || true
if [ "$1" == "0" ] ; then if [ "$1" == "0" ] ; then

View File

@ -464,12 +464,6 @@ else
echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually" echo "Unable to determine ssl settings for tomcat.conf, please run cloudstack-setup-management manually"
fi 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 %preun agent
/sbin/service cloudstack-agent stop || true /sbin/service cloudstack-agent stop || true
if [ "$1" == "0" ] ; then if [ "$1" == "0" ] ; then

View File

@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
CATALINA_TMPDIR="/usr/share/cloudstack-management/temp" CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then 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/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com " 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 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" 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 fi

View File

@ -28,8 +28,8 @@ JASPER_HOME="/usr/share/cloudstack-management"
CATALINA_TMPDIR="/usr/share/cloudstack-management/temp" CATALINA_TMPDIR="/usr/share/cloudstack-management/temp"
if [ -r "/etc/cloudstack/management/cloudmanagementserver.keystore" ] ; then 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/cloudmanagementserver.keystore -Djavax.net.ssl.trustStorePassword=vmops.com " 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 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" 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 fi

View 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>

View File

@ -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

View 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.
-->
<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>

View File

@ -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};
}
}

View File

@ -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";
}
}

View File

@ -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);
}
}

View File

@ -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");
}
}

View File

@ -20,6 +20,9 @@ import java.util.Map;
import javax.naming.ConfigurationException; 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.Answer;
import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.CheckHealthCommand;
import com.cloud.agent.api.CheckNetworkCommand; import com.cloud.agent.api.CheckNetworkCommand;
@ -52,6 +55,10 @@ public interface MockAgentManager extends Manager {
Answer pingTest(PingTestCommand cmd); Answer pingTest(PingTestCommand cmd);
Answer setupKeyStore(SetupKeyStoreCommand cmd);
Answer setupCertificate(SetupCertificateCommand cmd);
MockHost getHost(String guid); MockHost getHost(String guid);
Answer maintain(MaintainCommand cmd); Answer maintain(MaintainCommand cmd);

View File

@ -31,7 +31,10 @@ import java.util.regex.PatternSyntaxException;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; 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.cloudstack.context.CallContext;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -62,6 +65,7 @@ import com.cloud.simulator.MockHostVO;
import com.cloud.simulator.MockVMVO; import com.cloud.simulator.MockVMVO;
import com.cloud.simulator.dao.MockHostDao; import com.cloud.simulator.dao.MockHostDao;
import com.cloud.simulator.dao.MockVMDao; import com.cloud.simulator.dao.MockVMDao;
import com.cloud.user.AccountManager;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.concurrency.NamedThreadFactory;
@ -459,6 +463,24 @@ public class MockAgentManagerImpl extends ManagerBase implements MockAgentManage
return new Answer(cmd); 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 @Override
public boolean start() { public boolean start() {
for (Discoverer discoverer : discoverers) { for (Discoverer discoverer : discoverers) {

View File

@ -26,6 +26,8 @@ import java.util.Map;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; 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.DeleteCommand;
import org.apache.cloudstack.storage.command.DownloadCommand; import org.apache.cloudstack.storage.command.DownloadCommand;
import org.apache.cloudstack.storage.command.DownloadProgressCommand; import org.apache.cloudstack.storage.command.DownloadProgressCommand;
@ -280,6 +282,10 @@ public class SimulatorManagerImpl extends ManagerBase implements SimulatorManage
answer = _mockAgentMgr.checkHealth((CheckHealthCommand)cmd); answer = _mockAgentMgr.checkHealth((CheckHealthCommand)cmd);
} else if (cmd instanceof PingTestCommand) { } else if (cmd instanceof PingTestCommand) {
answer = _mockAgentMgr.pingTest((PingTestCommand)cmd); 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) { } else if (cmd instanceof PrepareForMigrationCommand) {
answer = _mockVmMgr.prepareForMigrate((PrepareForMigrationCommand)cmd); answer = _mockVmMgr.prepareForMigrate((PrepareForMigrationCommand)cmd);
} else if (cmd instanceof MigrateCommand) { } else if (cmd instanceof MigrateCommand) {

View File

@ -53,6 +53,7 @@
<module>acl/dynamic-role-based</module> <module>acl/dynamic-role-based</module>
<module>affinity-group-processors/host-anti-affinity</module> <module>affinity-group-processors/host-anti-affinity</module>
<module>affinity-group-processors/explicit-dedication</module> <module>affinity-group-processors/explicit-dedication</module>
<module>ca/root-ca</module>
<module>deployment-planners/user-concentrated-pod</module> <module>deployment-planners/user-concentrated-pod</module>
<module>deployment-planners/user-dispersing</module> <module>deployment-planners/user-dispersing</module>
<module>deployment-planners/implicit-dedication</module> <module>deployment-planners/implicit-dedication</module>

View File

@ -16,13 +16,34 @@
// under the License. // under the License.
package org.apache.cloudstack.saml; package org.apache.cloudstack.saml;
import com.cloud.domain.Domain; import java.io.ByteArrayInputStream;
import com.cloud.user.DomainManager; import java.io.ByteArrayOutputStream;
import com.cloud.user.User; import java.io.File;
import com.cloud.user.UserVO; import java.io.IOException;
import com.cloud.user.dao.UserDao; import java.io.ObjectInputStream;
import com.cloud.utils.PropertiesUtil; import java.io.ObjectOutput;
import com.cloud.utils.component.AdapterBase; 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.AuthorizeSAMLSSOCmd;
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd; import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
import org.apache.cloudstack.api.command.ListAndSwitchSAMLAccountCmd; 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.config.Configurable;
import org.apache.cloudstack.framework.security.keystore.KeystoreDao; import org.apache.cloudstack.framework.security.keystore.KeystoreDao;
import org.apache.cloudstack.framework.security.keystore.KeystoreVO; 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.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpClient;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.bouncycastle.operator.OperatorCreationException;
import org.opensaml.DefaultBootstrap; import org.opensaml.DefaultBootstrap;
import org.opensaml.common.xml.SAMLConstants; import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.metadata.ContactPerson; 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.opensaml.xml.security.keyinfo.KeyInfoHelper;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.inject.Inject; import com.cloud.domain.Domain;
import javax.xml.stream.FactoryConfigurationError; import com.cloud.user.DomainManager;
import java.io.ByteArrayInputStream; import com.cloud.user.User;
import java.io.ByteArrayOutputStream; import com.cloud.user.UserVO;
import java.io.File; import com.cloud.user.dao.UserDao;
import java.io.IOException; import com.cloud.utils.PropertiesUtil;
import java.io.ObjectInputStream; import com.cloud.utils.component.AdapterBase;
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;
@Component @Component
public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManager, Configurable { 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); KeystoreVO keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
if (keyStoreVO == null) { if (keyStoreVO == null) {
try { try {
KeyPair keyPair = SAMLUtils.generateRandomKeyPair(); KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
_ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR, SAMLUtils.savePrivateKey(keyPair.getPrivate()), SAMLUtils.savePublicKey(keyPair.getPublic()), "samlsp-keypair"); _ksDao.save(SAMLPluginConstants.SAMLSP_KEYPAIR,
CertUtils.privateKeyToPem(keyPair.getPrivate()),
CertUtils.publicKeyToPem(keyPair.getPublic()), "samlsp-keypair");
keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR); keyStoreVO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_KEYPAIR);
s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair"); s_logger.info("No SAML keystore found, created and saved a new Service Provider keypair");
} catch (NoSuchProviderException | NoSuchAlgorithmException e) { } catch (final NoSuchProviderException | NoSuchAlgorithmException | IOException e) {
s_logger.error("Unable to create and save SAML keypair: " + e.toString()); 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; KeyPair spKeyPair = null;
X509Certificate spX509Key = null; X509Certificate spX509Key = null;
if (keyStoreVO != 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) { if (privateKey != null && publicKey != null) {
spKeyPair = new KeyPair(publicKey, privateKey); spKeyPair = new KeyPair(publicKey, privateKey);
KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT); KeystoreVO x509VO = _ksDao.findByName(SAMLPluginConstants.SAMLSP_X509CERT);
@ -174,8 +191,8 @@ public class SAML2AuthManagerImpl extends AdapterBase implements SAML2AuthManage
out.flush(); out.flush();
_ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert"); _ksDao.save(SAMLPluginConstants.SAMLSP_X509CERT, Base64.encodeBase64String(bos.toByteArray()), "", "samlsp-x509cert");
bos.close(); bos.close();
} catch (NoSuchAlgorithmException | NoSuchProviderException | CertificateEncodingException | SignatureException | InvalidKeyException | IOException e) { } catch (final NoSuchAlgorithmException | NoSuchProviderException | CertificateException | SignatureException | InvalidKeyException | IOException | OperatorCreationException e) {
s_logger.error("SAML Plugin won't be able to use X509 signed authentication"); s_logger.error("SAML plugin won't be able to use X509 signed authentication", e);
} }
} else { } else {
try { try {

View File

@ -19,14 +19,41 @@
package org.apache.cloudstack.saml; 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.ApiConstants;
import org.apache.cloudstack.api.response.LoginCmdResponse; import org.apache.cloudstack.api.response.LoginCmdResponse;
import org.apache.cloudstack.utils.security.CertUtils;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.opensaml.Configuration; import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap; import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion; import org.opensaml.common.SAMLVersion;
@ -63,41 +90,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import javax.security.auth.x500.X500Principal; import com.cloud.utils.HttpUtils;
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;
public class SAMLUtils { public class SAMLUtils {
public static final Logger s_logger = Logger.getLogger(SAMLUtils.class); public static final Logger s_logger = Logger.getLogger(SAMLUtils.class);
@ -271,89 +264,10 @@ public class SAMLUtils {
return url; return url;
} }
public static KeyFactory getKeyFactory() { public static X509Certificate generateRandomX509Certificate(KeyPair keyPair) throws NoSuchAlgorithmException, NoSuchProviderException, CertificateException, SignatureException, InvalidKeyException, OperatorCreationException {
KeyFactory keyFactory = null; return CertUtils.generateV1Certificate(keyPair,
try { "CN=ApacheCloudStack", "CN=ApacheCloudStack",
Security.addProvider(new BouncyCastleProvider()); 3, "SHA256WithRSA");
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 void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException { public static void setupSamlUserCookies(final LoginCmdResponse loginResponse, final HttpServletResponse resp) throws IOException {

View File

@ -19,13 +19,22 @@
package org.apache.cloudstack; 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.ApiServerService;
import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd; import org.apache.cloudstack.api.command.GetServiceProviderMetaDataCmd;
import org.apache.cloudstack.saml.SAML2AuthManager; import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.saml.SAMLProviderMetadata; import org.apache.cloudstack.saml.SAMLProviderMetadata;
import org.apache.cloudstack.saml.SAMLUtils; import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.cloudstack.utils.security.CertUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -33,20 +42,7 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest; import com.cloud.utils.HttpUtils;
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;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class GetServiceProviderMetaDataCmdTest { public class GetServiceProviderMetaDataCmdTest {
@ -67,7 +63,7 @@ public class GetServiceProviderMetaDataCmdTest {
HttpServletRequest req; HttpServletRequest req;
@Test @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(); GetServiceProviderMetaDataCmd cmd = new GetServiceProviderMetaDataCmd();
Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer"); Field apiServerField = GetServiceProviderMetaDataCmd.class.getDeclaredField("_apiServer");
@ -80,7 +76,7 @@ public class GetServiceProviderMetaDataCmdTest {
String spId = "someSPID"; String spId = "someSPID";
String url = "someUrl"; String url = "someUrl";
KeyPair kp = SAMLUtils.generateRandomKeyPair(); KeyPair kp = CertUtils.generateRandomKeyPair(4096);
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp); X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata(); SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();

View File

@ -19,15 +19,17 @@
package org.apache.cloudstack; 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.saml.SAMLUtils;
import org.apache.cloudstack.utils.security.CertUtils;
import org.junit.Test; import org.junit.Test;
import org.opensaml.saml2.core.AuthnRequest; import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.LogoutRequest; import org.opensaml.saml2.core.LogoutRequest;
import java.security.KeyPair; import junit.framework.TestCase;
import java.security.PrivateKey;
import java.security.PublicKey;
public class SAMLUtilsTest extends TestCase { public class SAMLUtilsTest extends TestCase {
@ -60,13 +62,13 @@ public class SAMLUtilsTest extends TestCase {
@Test @Test
public void testX509Helpers() throws Exception { public void testX509Helpers() throws Exception {
KeyPair keyPair = SAMLUtils.generateRandomKeyPair(); KeyPair keyPair = CertUtils.generateRandomKeyPair(4096);
String privateKeyString = SAMLUtils.savePrivateKey(keyPair.getPrivate()); String privateKeyString = CertUtils.privateKeyToPem(keyPair.getPrivate());
String publicKeyString = SAMLUtils.savePublicKey(keyPair.getPublic()); String publicKeyString = CertUtils.publicKeyToPem(keyPair.getPublic());
PrivateKey privateKey = SAMLUtils.loadPrivateKey(privateKeyString); PrivateKey privateKey = CertUtils.pemToPrivateKey(privateKeyString);
PublicKey publicKey = SAMLUtils.loadPublicKey(publicKeyString); PublicKey publicKey = CertUtils.pemToPublicKey(publicKeyString);
assertTrue(privateKey.equals(keyPair.getPrivate())); assertTrue(privateKey.equals(keyPair.getPrivate()));
assertTrue(publicKey.equals(keyPair.getPublic())); assertTrue(publicKey.equals(keyPair.getPublic()));

View File

@ -21,12 +21,16 @@ package org.apache.cloudstack.api.command;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import com.cloud.domain.Domain; import java.lang.reflect.Field;
import com.cloud.user.AccountService; import java.net.InetAddress;
import com.cloud.user.DomainManager; import java.security.KeyPair;
import com.cloud.user.UserAccountVO; import java.security.cert.X509Certificate;
import com.cloud.user.dao.UserAccountDao; import java.util.HashMap;
import com.cloud.utils.HttpUtils; 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.ApiServerService;
import org.apache.cloudstack.api.BaseCmd; 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.SAMLPluginConstants;
import org.apache.cloudstack.saml.SAMLProviderMetadata; import org.apache.cloudstack.saml.SAMLProviderMetadata;
import org.apache.cloudstack.saml.SAMLUtils; import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.cloudstack.utils.security.CertUtils;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; 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.StatusCodeBuilder;
import org.opensaml.saml2.core.impl.SubjectBuilder; import org.opensaml.saml2.core.impl.SubjectBuilder;
import javax.servlet.http.HttpServletRequest; import com.cloud.domain.Domain;
import javax.servlet.http.HttpServletResponse; import com.cloud.user.AccountService;
import javax.servlet.http.HttpSession; import com.cloud.user.DomainManager;
import com.cloud.user.UserAccountVO;
import java.lang.reflect.Field; import com.cloud.user.dao.UserAccountDao;
import java.security.KeyPair; import com.cloud.utils.HttpUtils;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
import java.net.InetAddress;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class SAML2LoginAPIAuthenticatorCmdTest { public class SAML2LoginAPIAuthenticatorCmdTest {
@ -158,7 +159,7 @@ public class SAML2LoginAPIAuthenticatorCmdTest {
userAccountDaoField.setAccessible(true); userAccountDaoField.setAccessible(true);
userAccountDaoField.set(cmd, userAccountDao); userAccountDaoField.set(cmd, userAccountDao);
KeyPair kp = SAMLUtils.generateRandomKeyPair(); KeyPair kp = CertUtils.generateRandomKeyPair(4096);
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp); X509Certificate cert = SAMLUtils.generateRandomX509Certificate(kp);
SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata(); SAMLProviderMetadata providerMetadata = new SAMLProviderMetadata();

View File

@ -19,11 +19,19 @@
package org.apache.cloudstack.api.command; 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.ApiServerService;
import org.apache.cloudstack.api.auth.APIAuthenticationType; import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.saml.SAML2AuthManager; import org.apache.cloudstack.saml.SAML2AuthManager;
import org.apache.cloudstack.saml.SAMLUtils; import org.apache.cloudstack.saml.SAMLUtils;
import org.apache.cloudstack.utils.security.CertUtils;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -31,12 +39,7 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.http.HttpServletRequest; import com.cloud.utils.HttpUtils;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.security.cert.X509Certificate;
import java.net.InetAddress;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class SAML2LogoutAPIAuthenticatorCmdTest { public class SAML2LogoutAPIAuthenticatorCmdTest {
@ -70,7 +73,7 @@ public class SAML2LogoutAPIAuthenticatorCmdTest {
String spId = "someSPID"; String spId = "someSPID";
String url = "someUrl"; String url = "someUrl";
X509Certificate cert = SAMLUtils.generateRandomX509Certificate(SAMLUtils.generateRandomKeyPair()); X509Certificate cert = SAMLUtils.generateRandomX509Certificate(CertUtils.generateRandomKeyPair(4096));
Mockito.when(session.getAttribute(Mockito.anyString())).thenReturn(null); 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); cmd.authenticate("command", null, session, InetAddress.getByName("127.0.0.1"), HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);

View File

@ -64,7 +64,7 @@
<cs.junit.version>4.12</cs.junit.version> <cs.junit.version>4.12</cs.junit.version>
<cs.hamcrest.version>1.3</cs.hamcrest.version> <cs.hamcrest.version>1.3</cs.hamcrest.version>
<cs.junit.dataprovider.version>1.12.0</cs.junit.dataprovider.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.jsch.version>0.1.54</cs.jsch.version>
<cs.jpa.version>2.1.1</cs.jpa.version> <cs.jpa.version>2.1.1</cs.jpa.version>
<cs.jasypt.version>1.9.2</cs.jasypt.version> <cs.jasypt.version>1.9.2</cs.jasypt.version>
@ -225,6 +225,11 @@
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
<version>${cs.bcprov.version}</version> <version>${cs.bcprov.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>${cs.bcprov.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.xmlgraphics</groupId> <groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-css</artifactId> <artifactId>batik-css</artifactId>

View File

@ -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)

View File

@ -255,9 +255,6 @@
<CustomAction Id="SetuptoolsInstallation" Directory='INSTALLDIR' <CustomAction Id="SetuptoolsInstallation" Directory='INSTALLDIR'
ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\ez_setup.py"' ExeCommand='[PYTHON_HOME]\python "[INSTALLDIR]\ez_setup.py"'
Execute="deferred" Return="check" /> 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="DbHostWithPort" Execute="immediate" Property="DB_HOSTNAME" Value="[DB_HOSTNAME]:[DB_PORT]" />
<CustomAction Id="DeployDB" Directory='CSMANAGEMENT' <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"' 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"'

View File

@ -586,9 +586,6 @@
<Component Id="cmp6001B626D853C4E4FAB8511246B8D2B4" Guid="{DB90C42E-A2DC-4742-8BC8-49AF7556E7D1}"> <Component Id="cmp6001B626D853C4E4FAB8511246B8D2B4" Guid="{DB90C42E-A2DC-4742-8BC8-49AF7556E7D1}">
<File Id="fil5A0CF0340938B7ACC3B69C1353BDC625" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\classpath.conf" /> <File Id="fil5A0CF0340938B7ACC3B69C1353BDC625" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\classpath.conf" />
</Component> </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}"> <Component Id="cmp68E096BB729948107692341D8202CC5A" Guid="{0D5D3AF3-0BC0-48EE-ABA3-AF07535169BF}">
<File Id="fil3E9BCB1A8CB8F8415FE3E71B65A94878" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\context.xml" /> <File Id="fil3E9BCB1A8CB8F8415FE3E71B65A94878" KeyPath="yes" Source="!(wix.SourceClient)\WEB-INF\classes\context.xml" />
</Component> </Component>

View File

@ -35,13 +35,11 @@ check_gw() {
cert="/root/.ssh/id_rsa.cloud" cert="/root/.ssh/id_rsa.cloud"
script=$1 script="$1"
shift domRIp="$2"
domRIp=$1
shift
check_gw "$domRIp" 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 $? exit $?

100
scripts/util/keystore-cert-import Executable file
View 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
View 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

View File

@ -52,6 +52,11 @@
<artifactId>httpcore</artifactId> <artifactId>httpcore</artifactId>
<version>${cs.httpcore.version}</version> <version>${cs.httpcore.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-ca</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.apache.cloudstack</groupId> <groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-framework-jobs</artifactId> <artifactId>cloud-framework-jobs</artifactId>

View File

@ -87,8 +87,10 @@
<property name="networkElements" value="#{networkElementsRegistry.registered}" /> <property name="networkElements" value="#{networkElementsRegistry.registered}" />
</bean> </bean>
<bean id="configurationServerImpl" class="com.cloud.server.ConfigurationServerImpl" /> <bean id="configurationServerImpl" class="com.cloud.server.ConfigurationServerImpl" />
<bean id="userVmManagerImpl" class="com.cloud.vm.UserVmManagerImpl" /> <bean id="userVmManagerImpl" class="com.cloud.vm.UserVmManagerImpl" />
<bean id="consoleProxyManagerImpl" class="com.cloud.consoleproxy.ConsoleProxyManagerImpl"> <bean id="consoleProxyManagerImpl" class="com.cloud.consoleproxy.ConsoleProxyManagerImpl">
@ -277,4 +279,10 @@
</bean> </bean>
<bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" /> <bean id="certServiceImpl" class="org.apache.cloudstack.network.ssl.CertServiceImpl" />
<bean id="imageStoreUploadMonitorImpl" class="com.cloud.storage.ImageStoreUploadMonitorImpl" /> <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> </beans>

View File

@ -759,7 +759,8 @@ public class AlertManagerImpl extends ManagerBase implements AlertManager, Confi
(alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) && (alertType != AlertManager.AlertType.ALERT_TYPE_MANAGMENT_NODE) &&
(alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) && (alertType != AlertManager.AlertType.ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED) &&
(alertType != AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED) && (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); alert = _alertDao.getLastAlert(alertType.getType(), dataCenterId, podId, clusterId);
} }

View File

@ -110,6 +110,7 @@ import com.cloud.user.AccountManager;
import com.cloud.utils.DateUtil; import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.DB; import com.cloud.utils.db.DB;
import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.GlobalLock;
@ -1354,7 +1355,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
StringBuilder buf = profile.getBootArgsBuilder(); StringBuilder buf = profile.getBootArgsBuilder();
buf.append(" template=domP type=consoleproxy"); 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(" port=").append(_mgmtPort);
buf.append(" name=").append(profile.getVirtualMachine().getHostName()); buf.append(" name=").append(profile.getVirtualMachine().getHostName());
if (_sslEnabled) { if (_sslEnabled) {

View File

@ -16,6 +16,23 @@
// under the License. // under the License.
package com.cloud.hypervisor.kvm.discoverer; 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.AgentManager;
import com.cloud.agent.Listener; import com.cloud.agent.Listener;
import com.cloud.agent.api.AgentControlAnswer; import com.cloud.agent.api.AgentControlAnswer;
@ -42,17 +59,10 @@ import com.cloud.resource.DiscovererBase;
import com.cloud.resource.ResourceStateAdapter; import com.cloud.resource.ResourceStateAdapter;
import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResource;
import com.cloud.resource.UnableDeleteHostException; import com.cloud.resource.UnableDeleteHostException;
import com.cloud.utils.PasswordGenerator;
import com.cloud.utils.StringUtils;
import com.cloud.utils.ssh.SSHCmdHelper; import com.cloud.utils.ssh.SSHCmdHelper;
import org.apache.log4j.Logger; import com.trilead.ssh2.Connection;
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;
public abstract class LibvirtServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter { public abstract class LibvirtServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter {
private static final Logger s_logger = Logger.getLogger(LibvirtServerDiscoverer.class); 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 _kvmPublicNic;
private String _kvmGuestNic; private String _kvmGuestNic;
@Inject @Inject
AgentManager _agentMgr; private AgentManager agentMgr;
@Inject
private CAManager caManager;
@Override @Override
public abstract Hypervisor.HypervisorType getHypervisorType(); public abstract Hypervisor.HypervisorType getHypervisorType();
@ -125,6 +137,73 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
return false; 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 @Override
public Map<? extends ServerResource, Map<String, String>> 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 { 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); s_logger.debug(msg);
return null; return null;
} }
com.trilead.ssh2.Connection sshConnection = null; Connection sshConnection = null;
String agentIp = null; String agentIp = null;
try { 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); sshConnection.connect(null, 60000, 60000);
if (!sshConnection.authenticateWithPassword(username, password)) { if (!sshConnection.authenticateWithPassword(username, password)) {
@ -170,7 +249,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
throw new DiscoveredWithErrorException("Authentication error"); 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"); s_logger.debug("It's not a KVM enabled machine");
return null; return null;
} }
@ -210,7 +289,9 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
kvmGuestNic = (kvmPublicNic != null) ? kvmPublicNic : kvmPrivateNic; 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 += " --pubNic=" + kvmPublicNic;
parameters += " --prvNic=" + kvmPrivateNic; parameters += " --prvNic=" + kvmPrivateNic;
@ -221,8 +302,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
if (!username.equals("root")) { if (!username.equals("root")) {
setupAgentCommand = "sudo cloudstack-setup-agent "; setupAgentCommand = "sudo cloudstack-setup-agent ";
} }
if (!SSHCmdHelper.sshExecuteCmd(sshConnection, if (!SSHCmdHelper.sshExecuteCmd(sshConnection, setupAgentCommand + parameters)) {
setupAgentCommand + parameters, 3)) {
s_logger.info("cloudstack agent setup command failed: " s_logger.info("cloudstack agent setup command failed: "
+ setupAgentCommand + parameters); + setupAgentCommand + parameters);
return null; return null;
@ -392,7 +472,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements
_resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage); _resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage);
try { try {
ShutdownCommand cmd = new ShutdownCommand(ShutdownCommand.DeleteHost, null); ShutdownCommand cmd = new ShutdownCommand(ShutdownCommand.DeleteHost, null);
_agentMgr.send(host.getId(), cmd); agentMgr.send(host.getId(), cmd);
} catch (AgentUnavailableException e) { } catch (AgentUnavailableException e) {
s_logger.warn("Sending ShutdownCommand failed: ", e); s_logger.warn("Sending ShutdownCommand failed: ", e);
} catch (OperationTimedoutException e) { } catch (OperationTimedoutException e) {

View File

@ -2272,7 +2272,8 @@ public class ResourceManagerImpl extends ManagerBase implements ResourceManager,
} }
try { 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) { } catch (final SshException e) {
return false; return false;
} }

View File

@ -23,8 +23,6 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -36,24 +34,21 @@ import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern;
import javax.crypto.KeyGenerator; import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.inject.Inject; import javax.inject.Inject;
import javax.naming.ConfigurationException; 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.config.ApiServiceConfiguration;
import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigDepot;
import org.apache.cloudstack.framework.config.ConfigDepotAdmin; import org.apache.cloudstack.framework.config.ConfigDepotAdmin;
import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.config.impl.ConfigurationVO; 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.Config;
import com.cloud.configuration.ConfigurationManager; 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.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils; import com.cloud.utils.net.NetUtils;
import com.cloud.utils.nio.Link;
import com.cloud.utils.script.Script; import com.cloud.utils.script.Script;
public class ConfigurationServerImpl extends ManagerBase implements ConfigurationServer { public class ConfigurationServerImpl extends ManagerBase implements ConfigurationServer {
@ -306,9 +300,6 @@ public class ConfigurationServerImpl extends ManagerBase implements Configuratio
// Update resource count if needed // Update resource count if needed
updateResourceCount(); updateResourceCount();
// keystore for SSL/TLS connection
updateSSLKeystore();
// store the public and private keys in the database // store the public and private keys in the database
updateKeyPairs(); 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 @DB
protected void updateSystemvmPassword() { protected void updateSystemvmPassword() {
String userid = System.getProperty("user.name"); String userid = System.getProperty("user.name");

View 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
};
}
}

View File

@ -577,5 +577,11 @@ public class OutOfBandManagementServiceImpl extends ManagerBase implements OutOf
LOG.error("Error trying to retrieve host out-of-band management stats", t); LOG.error("Error trying to retrieve host out-of-band management stats", t);
} }
} }
@Override
public Long getDelay() {
return null;
}
} }
} }

View File

@ -52,7 +52,11 @@ public final class BackgroundPollManagerImpl extends ManagerBase implements Back
} }
backgroundPollTaskScheduler = Executors.newScheduledThreadPool(submittedTasks.size() + 1, new NamedThreadFactory("BackgroundTaskPollManager")); backgroundPollTaskScheduler = Executors.newScheduledThreadPool(submittedTasks.size() + 1, new NamedThreadFactory("BackgroundTaskPollManager"));
for (final BackgroundPollTask task : submittedTasks) { 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()); LOG.debug("Scheduled background poll task: " + task.getClass().getName());
} }
isConfiguredAndStarted = true; isConfiguredAndStarted = true;

View File

@ -16,8 +16,17 @@
// under the License. // under the License.
package com.cloud.server; package com.cloud.server;
import java.io.File; import org.apache.cloudstack.framework.config.ConfigDepot;
import java.io.IOException; 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.ConfigurationManager;
import com.cloud.configuration.dao.ResourceCountDao; 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.storage.dao.DiskOfferingDao;
import com.cloud.user.dao.AccountDao; import com.cloud.user.dao.AccountDao;
import com.cloud.utils.db.TransactionLegacy; 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) @RunWith(MockitoJUnitRunner.class)
public class ConfigurationServerImplTest { 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 @Test
public void testWindowsScript() { public void testWindowsScript() {
Assert.assertTrue(windowsImpl.isOnWindows()); Assert.assertTrue(windowsImpl.isOnWindows());

View File

@ -574,6 +574,11 @@ public class MockNetworkManagerImpl extends ManagerBase implements NetworkOrches
return null; return null;
} }
@Override
public Map<String, String> getSystemVMAccessDetails(VirtualMachine vm) {
return null;
}
/* (non-Javadoc) /* (non-Javadoc)
* @see com.cloud.network.NetworkManager#implementNetwork(long, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext) * @see com.cloud.network.NetworkManager#implementNetwork(long, com.cloud.deploy.DeployDestination, com.cloud.vm.ReservationContext)
*/ */

View 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);
}
}

View 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());
}
}

View File

@ -45,6 +45,12 @@ public class BackgroundPollManagerImplTest {
didIRun = true; didIRun = true;
counter++; counter++;
} }
@Override
public Long getDelay() {
return null;
}
} }
@Before @Before

View File

@ -123,6 +123,7 @@ import com.cloud.user.AccountService;
import com.cloud.utils.DateUtil; import com.cloud.utils.DateUtil;
import com.cloud.utils.NumbersUtil; import com.cloud.utils.NumbersUtil;
import com.cloud.utils.Pair; import com.cloud.utils.Pair;
import com.cloud.utils.StringUtils;
import com.cloud.utils.component.ManagerBase; import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.db.GlobalLock; import com.cloud.utils.db.GlobalLock;
import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.QueryBuilder;
@ -1118,7 +1119,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar
StringBuilder buf = profile.getBootArgsBuilder(); StringBuilder buf = profile.getBootArgsBuilder();
buf.append(" template=domP type=secstorage"); 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(" port=").append(_mgmtPort);
buf.append(" name=").append(profile.getVirtualMachine().getHostName()); buf.append(" name=").append(profile.getVirtualMachine().getHostName());

View File

@ -16,6 +16,71 @@
// under the License. // under the License.
package org.apache.cloudstack.storage.resource; 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.amazonaws.services.s3.model.S3ObjectSummary;
import com.cloud.agent.api.Answer; import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckHealthAnswer; import com.cloud.agent.api.CheckHealthAnswer;
@ -83,6 +148,7 @@ import com.cloud.utils.storage.S3.S3Utils;
import com.cloud.vm.SecondaryStorageVm; import com.cloud.vm.SecondaryStorageVm;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import io.netty.bootstrap.ServerBootstrap; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; 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.codec.http.HttpResponseEncoder;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; 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 { public class NfsSecondaryStorageResource extends ServerResourceBase implements SecondaryStorageResource {
@ -2231,9 +2234,10 @@ public class NfsSecondaryStorageResource extends ServerResourceBase implements S
if (_inSystemVM) { if (_inSystemVM) {
_localgw = (String)params.get("localgw"); _localgw = (String)params.get("localgw");
if (_localgw != null) { // can only happen inside service vm if (_localgw != null) { // can only happen inside service vm
String mgmtHost = (String)params.get("host"); String mgmtHosts = (String)params.get("host");
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost); for (final String mgmtHost : mgmtHosts.split(",")) {
addRouteToInternalIpOrCidr(_localgw, _eth1ip, _eth1mask, mgmtHost);
}
String internalDns1 = (String)params.get("internaldns1"); String internalDns1 = (String)params.get("internaldns1");
if (internalDns1 == null) { if (internalDns1 == null) {
s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage"); s_logger.warn("No DNS entry found during configuration of NfsSecondaryStorage");

View File

@ -123,3 +123,18 @@ CREATE VIEW `template_view` AS
OR (`resource_tags`.`resource_type` = 'ISO'))))); 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'; 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;

View File

@ -27,3 +27,6 @@ INSERT INTO `cloud`.`configuration` (category, instance, component, name, value,
-- Enable dynamic RBAC by default for fresh deployments -- 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'); 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');

View File

@ -246,6 +246,13 @@ under the License.
<value>true</value> <value>true</value>
</configuration> </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. 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: 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. 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