diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index 0dc5b8211e0..f2fcfd83eb1 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -78,6 +78,12 @@ zone=default # Generated with "uuidgen". local.storage.uuid= +# Enable TLS for image server transfers. +# When enabled, certificate and key paths must both be configured. +# image.server.tls.enabled=false +# image.server.tls.cert.file=/etc/cloudstack/agent/cloud.crt +# image.server.tls.key.file=/etc/cloudstack/agent/cloud.key + # Location for KVM virtual router scripts. # The path defined in this property is relative to the directory "/usr/share/cloudstack-common/". domr.scripts.dir=scripts/network/domr/kvm diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index 3364f9708cf..22a25eaa6d8 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -123,6 +123,27 @@ public class AgentProperties{ */ public static final Property LOCAL_STORAGE_PATH = new Property<>("local.storage.path", "/var/lib/libvirt/images/"); + /** + * Enables TLS on the KVM image server transfer endpoint.
+ * Data type: Boolean.
+ * Default value: false + */ + public static final Property IMAGE_SERVER_TLS_ENABLED = new Property<>("image.server.tls.enabled", false); + + /** + * PEM certificate file used by the KVM image server when TLS is enabled.
+ * Data type: String.
+ * Default value: null + */ + public static final Property IMAGE_SERVER_TLS_CERT_FILE = new Property<>("image.server.tls.cert.file", null, String.class); + + /** + * PEM private key file used by the KVM image server when TLS is enabled.
+ * Data type: String.
+ * Default value: null + */ + public static final Property IMAGE_SERVER_TLS_KEY_FILE = new Property<>("image.server.tls.key.file", null, String.class); + /** * Directory where Qemu sockets are placed.
* These sockets are for the Qemu Guest Agent and SSVM provisioning.
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 34f166a7fb6..675c9cde266 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -398,6 +398,9 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String vmActivityCheckPath; private String nasBackupPath; private String imageServerPath; + private boolean imageServerTlsEnabled = false; + private String imageServerTlsCertFile; + private String imageServerTlsKeyFile; private String securityGroupPath; private String ovsPvlanDhcpHostPath; private String ovsPvlanVmPath; @@ -816,6 +819,18 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return imageServerPath; } + public boolean isImageServerTlsEnabled() { + return imageServerTlsEnabled; + } + + public String getImageServerTlsCertFile() { + return imageServerTlsCertFile; + } + + public String getImageServerTlsKeyFile() { + return imageServerTlsKeyFile; + } + public String getOvsPvlanDhcpHostPath() { return ovsPvlanDhcpHostPath; } @@ -1034,6 +1049,14 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv cachePath = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HOST_CACHE_LOCATION); + imageServerTlsEnabled = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_TLS_ENABLED); + imageServerTlsCertFile = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_TLS_CERT_FILE); + imageServerTlsKeyFile = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.IMAGE_SERVER_TLS_KEY_FILE); + + if (imageServerTlsEnabled && (StringUtils.isBlank(imageServerTlsCertFile) || StringUtils.isBlank(imageServerTlsKeyFile))) { + throw new ConfigurationException("image server TLS is enabled but image.server.tls.cert.file or image.server.tls.key.file is missing"); + } + params.put("domr.scripts.dir", domrScriptsDir); virtRouterResource = new VirtualRoutingResource(this); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java index 859b7498859..1b9b33f83a9 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreateImageTransferCommandWrapper.java @@ -40,10 +40,23 @@ import com.cloud.utils.script.Script; public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper { protected Logger logger = LogManager.getLogger(getClass()); + private void resetService(String unitName) { + Script resetScript = new Script("/bin/bash", logger); + resetScript.add("-c"); + resetScript.add(String.format("systemctl reset-failed %s || true", unitName)); + resetScript.execute(); + } + + private static String shellQuote(String value) { + return "'" + value.replace("'", "'\\''") + "'"; + } + private boolean startImageServerIfNotRunning(int imageServerPort, LibvirtComputingResource resource) { final String imageServerPackageDir = resource.getImageServerPath(); final String imageServerParentDir = new File(imageServerPackageDir).getParent(); final String imageServerModuleName = new File(imageServerPackageDir).getName(); + final String listenAddress = "0.0.0.0"; + final boolean tlsEnabled = resource.isImageServerTlsEnabled(); String unitName = "cloudstack-image-server"; Script checkScript = new Script("/bin/bash", logger); @@ -54,14 +67,21 @@ public class LibvirtCreateImageTransferCommandWrapper extends CommandWrapper None: default=CONTROL_SOCKET, help="Path to the Unix domain control socket", ) + parser.add_argument( + "--tls-enabled", + action="store_true", + help="Enable TLS for the HTTP transfer endpoint", + ) + parser.add_argument( + "--tls-cert-file", + default=None, + help="Path to PEM certificate file used when TLS is enabled", + ) + parser.add_argument( + "--tls-key-file", + default=None, + help="Path to PEM private key file used when TLS is enabled", + ) args = parser.parse_args() + if args.tls_enabled and (not args.tls_cert_file or not args.tls_key_file): + parser.error("--tls-enabled requires --tls-cert-file and --tls-key-file") + logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", @@ -204,5 +223,23 @@ def main() -> None: addr = (args.listen, args.port) httpd = ThreadingHTTPServer(addr, handler_cls) - logging.info("listening on http://%s:%d", args.listen, args.port) + + scheme = "http" + if args.tls_enabled: + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + + if hasattr(ssl, "TLSVersion") and hasattr(context, "minimum_version"): + context.minimum_version = ssl.TLSVersion.TLSv1_2 + else: + if hasattr(ssl, "OP_NO_TLSv1"): + context.options |= ssl.OP_NO_TLSv1 + if hasattr(ssl, "OP_NO_TLSv1_1"): + context.options |= ssl.OP_NO_TLSv1_1 + + context.load_cert_chain(certfile=args.tls_cert_file, keyfile=args.tls_key_file) + + httpd.socket = context.wrap_socket(httpd.socket, server_side=True) + scheme = "https" + + logging.info("listening on %s://%s:%d", scheme, args.listen, args.port) httpd.serve_forever()