From 93f18f1116f0304fa9e0d9c299bd32a3c7b5e993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=A1clav=20Rozsyp=C3=A1lek?= Date: Fri, 10 Jul 2026 14:43:04 +0200 Subject: [PATCH] kvm: encrypt KVM live-migration data stream with QEMU-native TLS CloudStack's secure KVM live migration only encrypts the libvirt control channel; the guest RAM (and, for storage migration, non-shared disk) data stream is transferred in plaintext over TCP. This adds an opt-in QEMU-native TLS path (VIR_MIGRATE_TLS) for the migration data stream, reusing the certificates the CA framework already provisions - mirroring the existing VNC-TLS pattern. - New zone-scoped global setting 'kvm.migrate.tls' (default false) on StorageManager; registered by StorageManagerImpl. - The agent advertises the 'host.migrate.tls' capability in StartupRoutingCommand when the QEMU migration certificates exist under /etc/pki/qemu and qemu.conf sets migrate_tls_x509_cert_dir. - The management server sets MigrateCommand.migrateTls only when the setting is enabled for the zone and BOTH the source and destination hosts advertise support; otherwise it silently falls back to the plaintext data stream, so mixed / partially-upgraded fleets keep migrating (VirtualMachineManagerImpl and StorageSystemDataMotionStrategy). - MigrateKVMAsync ORs in VIR_MIGRATE_TLS; the data URI stays "tcp:" because libvirt has no "tls:" migration URI scheme (valid: tcp/rdma/unix/fd) - TLS is negotiated over the normal tcp: connection. - Agent scripts provision the /etc/pki/qemu certificate set (keystore-cert-import) and write the qemu.conf migrate_tls_* keys (serviceConfig.py). - Unit test asserts the data URI stays "tcp:" when migrateTls is enabled. - PendingReleaseNotes entry. Verified end-to-end on a 3-node KVM/Ceph dev cluster (libvirt 12): a live migration between two secured hosts shows a TLS 1.2 handshake and ciphertext on the data ports (no QEVM magic / no legible guest RAM), with migration still succeeding. --- PendingReleaseNotes | 19 ++++++++++ api/src/main/java/com/cloud/host/Host.java | 1 + .../com/cloud/agent/api/MigrateCommand.java | 9 +++++ .../com/cloud/storage/StorageManager.java | 10 +++++ .../cloud/vm/VirtualMachineManagerImpl.java | 37 ++++++++++++++++++ .../StorageSystemDataMotionStrategy.java | 36 ++++++++++++++++++ .../resource/LibvirtComputingResource.java | 38 +++++++++++++++++++ .../kvm/resource/MigrateKVMAsync.java | 17 ++++++++- .../wrapper/LibvirtMigrateCommandWrapper.java | 2 +- .../kvm/resource/MigrateKVMAsyncTest.java | 23 ++++++++++- python/lib/cloudutils/serviceConfig.py | 4 ++ scripts/util/keystore-cert-import | 12 +++++- .../com/cloud/storage/StorageManagerImpl.java | 1 + 13 files changed, 203 insertions(+), 6 deletions(-) diff --git a/PendingReleaseNotes b/PendingReleaseNotes index 9670b6e7c13a..8f0106deba8f 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -39,3 +39,22 @@ example.ver.1 > example.ver.2: which can now be attached to Instances. This is to prevent the Secondary Storage to grow to enormous sizes as Linux Distributions keep growing in size while a stripped down Linux should fit on a 2.88MB floppy. + + +New features: + + * Encrypted KVM live-migration data stream (QEMU-native TLS). CloudStack's + secure KVM live migration previously encrypted only the libvirt control + channel, leaving the guest memory (and, for storage migration, the disk) + stream in plaintext on the migration network. A new zone-scoped global + setting 'kvm.migrate.tls' (default false) makes CloudStack request + VIR_MIGRATE_TLS so the migration data stream is encrypted and mutually + authenticated, reusing the certificates already provisioned by the CA + framework (a new /etc/pki/qemu cert set is created by the agent alongside + the existing libvirt and VNC ones). TLS is used only when both the source + and destination hosts are secured and advertise the 'host.migrate.tls' + capability; otherwise the migration transparently falls back to the + plaintext stream, so mixed/partially-upgraded fleets keep migrating. To + enable it, upgrade the agents fleet-wide, re-provision host certificates + (addHost does this automatically; existing hosts via "Deploy Host Keys"), + then set 'kvm.migrate.tls=true' for the zone. diff --git a/api/src/main/java/com/cloud/host/Host.java b/api/src/main/java/com/cloud/host/Host.java index c110e4ca94e1..12c9ce4c86f4 100644 --- a/api/src/main/java/com/cloud/host/Host.java +++ b/api/src/main/java/com/cloud/host/Host.java @@ -64,6 +64,7 @@ public static String[] toStrings(Host.Type... types) { String HOST_VIRTV2V_VERSION = "host.virtv2v.version"; String HOST_SSH_PORT = "host.ssh.port"; String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count"; + String HOST_MIGRATE_TLS = "host.migrate.tls"; String GUEST_OS_CATEGORY_ID = "guest.os.category.id"; String GUEST_OS_RULE = "guest.os.rule"; diff --git a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java index 7196247ffc23..f4001fc76dfd 100644 --- a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java +++ b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java @@ -44,6 +44,7 @@ public class MigrateCommand extends Command { private int newVmCpuShares; private boolean clvmCrossPoolMigration; + private boolean migrateTls; Map vlanToPersistenceMap = new HashMap<>(); @@ -159,6 +160,14 @@ public void setClvmCrossPoolMigration(boolean clvmCrossPoolMigration) { this.clvmCrossPoolMigration = clvmCrossPoolMigration; } + public boolean isMigrateTls() { + return migrateTls; + } + + public void setMigrateTls(boolean migrateTls) { + this.migrateTls = migrateTls; + } + public static class MigrateDiskInfo { public enum DiskType { FILE, BLOCK; diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 032fcbe76dce..0c15c55610f1 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -107,6 +107,16 @@ public interface StorageManager extends StorageService { true, ConfigKey.Scope.Global, null); + ConfigKey KvmMigrateTls = new ConfigKey<>(Boolean.class, + "kvm.migrate.tls", + "Storage", + "false", + "Setting this to 'true' encrypts the KVM live-migration data stream (guest memory and non-shared disk) with QEMU-native TLS (VIR_MIGRATE_TLS), " + + "provided both the source and destination hosts are secured and advertise host.migrate.tls support. If either host does not support it, the migration " + + "silently falls back to the plaintext data stream. Reuses the certificates provisioned by the CA framework.", + true, + ConfigKey.Scope.Zone, + null); ConfigKey MaxNumberOfManagedClusteredFileSystems = new ConfigKey<>(Integer.class, "max.number.managed.clustered.file.systems", "Storage", diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java index 364db685c9de..e2cf9d89af08 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -217,6 +217,7 @@ import com.cloud.exception.StorageUnavailableException; import com.cloud.ha.HighAvailabilityManager; import com.cloud.ha.HighAvailabilityManager.WorkType; +import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; @@ -3383,6 +3384,8 @@ protected MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMac migrateCommand.setAutoConvergence(StorageManager.KvmAutoConvergence.value()); migrateCommand.setHostGuid(destination.getHost().getGuid()); + migrateCommand.setMigrateTls(shouldMigrateWithTls(vmInstance, destination)); + PrepareForMigrationAnswer prepareForMigrationAnswer = (PrepareForMigrationAnswer) answer; Map answerDpdkInterfaceMapping = prepareForMigrationAnswer.getDpdkInterfaceMapping(); @@ -3402,6 +3405,40 @@ protected MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMac return migrateCommand; } + /** + * Decides whether the KVM live-migration data stream should be encrypted with QEMU-native TLS. + * TLS is used only when the {@code kvm.migrate.tls} setting is enabled for the destination zone + * AND both the source and destination hosts advertise {@code host.migrate.tls} support. If any of + * these conditions is not met the migration silently falls back to the plaintext {@code tcp:} stream, + * which keeps mixed / partially-upgraded fleets migrating without failures. + */ + protected boolean shouldMigrateWithTls(VMInstanceVO vmInstance, DeployDestination destination) { + final Long zoneId = destination.getHost().getDataCenterId(); + if (!StorageManager.KvmMigrateTls.valueIn(zoneId)) { + return false; + } + + final Long srcHostId = vmInstance.getHostId() != null ? vmInstance.getHostId() : vmInstance.getLastHostId(); + final Long destHostId = destination.getHost().getId(); + final boolean srcSupportsTls = srcHostId != null && hostAdvertisesMigrateTls(srcHostId); + final boolean destSupportsTls = destHostId != null && hostAdvertisesMigrateTls(destHostId); + + if (!srcSupportsTls || !destSupportsTls) { + logger.debug("kvm.migrate.tls is enabled but not both hosts advertise migration-TLS support (source [{}]={}, destination [{}]={}) for VM [{}]; " + + "falling back to the plaintext migration data stream.", srcHostId, srcSupportsTls, destHostId, destSupportsTls, vmInstance.getInstanceName()); + return false; + } + + logger.debug("Enabling QEMU-native TLS for the migration data stream of VM [{}] (source host [{}], destination host [{}]).", + vmInstance.getInstanceName(), srcHostId, destHostId); + return true; + } + + private boolean hostAdvertisesMigrateTls(Long hostId) { + final DetailVO detail = hostDetailsDao.findDetail(hostId, Host.HOST_MIGRATE_TLS); + return detail != null && Boolean.parseBoolean(detail.getValue()); + } + private void updateVmPod(VMInstanceVO vm, long dstHostId) { // update the VMs pod HostVO host = _hostDao.findById(dstHostId); diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java index 7674f1ce25a1..260510f8b505 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/StorageSystemDataMotionStrategy.java @@ -105,9 +105,11 @@ import com.cloud.dc.dao.ClusterDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.DetailVO; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.resource.ResourceState; import com.cloud.storage.DataStoreRole; @@ -179,6 +181,8 @@ public class StorageSystemDataMotionStrategy implements DataMotionStrategy { @Inject private HostDao _hostDao; @Inject + private HostDetailsDao _hostDetailsDao; + @Inject protected PrimaryDataStoreDao _storagePoolDao; @Inject private SnapshotDao _snapshotDao; @@ -2202,6 +2206,8 @@ public void copyAsync(Map volumeDataStoreMap, VirtualMach boolean kvmAutoConvergence = StorageManager.KvmAutoConvergence.value(); migrateCommand.setAutoConvergence(kvmAutoConvergence); + migrateCommand.setMigrateTls(shouldMigrateWithTls(srcHost, destHost)); + MigrateAnswer migrateAnswer = null; try { migrateAnswer = (MigrateAnswer)agentManager.send(srcHost.getId(), migrateCommand); @@ -2349,6 +2355,36 @@ protected boolean shouldMigrateVolume(StoragePoolVO sourceStoragePool, Host dest return true; } + /** + * Decides whether the KVM live-migration-with-volumes data stream should be encrypted with QEMU-native TLS. + * This path carries both guest memory and disk contents on the wire. TLS is used only when the + * {@code kvm.migrate.tls} setting is enabled for the destination zone AND both the source and destination + * hosts advertise {@code host.migrate.tls} support; otherwise it silently falls back to the plaintext stream, + * keeping mixed / partially-upgraded fleets migrating without failures. + */ + protected boolean shouldMigrateWithTls(Host srcHost, Host destHost) { + if (srcHost == null || destHost == null) { + return false; + } + if (!StorageManager.KvmMigrateTls.valueIn(destHost.getDataCenterId())) { + return false; + } + + final boolean srcSupportsTls = hostAdvertisesMigrateTls(srcHost.getId()); + final boolean destSupportsTls = hostAdvertisesMigrateTls(destHost.getId()); + if (!srcSupportsTls || !destSupportsTls) { + logger.debug("kvm.migrate.tls is enabled but not both hosts advertise migration-TLS support (source [{}]={}, destination [{}]={}); " + + "falling back to the plaintext migration data stream.", srcHost.getId(), srcSupportsTls, destHost.getId(), destSupportsTls); + return false; + } + return true; + } + + private boolean hostAdvertisesMigrateTls(Long hostId) { + final DetailVO detail = _hostDetailsDao.findDetail(hostId, Host.HOST_MIGRATE_TLS); + return detail != null && Boolean.parseBoolean(detail.getValue()); + } + /** * Returns true if the storage pool type is {@link StoragePoolType.Filesystem}. */ 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 4281036d9456..1bfdf6e5a8cc 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 @@ -18,6 +18,7 @@ import static com.cloud.host.Host.HOST_CDROM_MAX_COUNT; import static com.cloud.host.Host.HOST_INSTANCE_CONVERSION; +import static com.cloud.host.Host.HOST_MIGRATE_TLS; import static com.cloud.host.Host.HOST_OVFTOOL_VERSION; import static com.cloud.host.Host.HOST_VDDK_LIB_DIR; import static com.cloud.host.Host.HOST_VDDK_SUPPORT; @@ -4410,6 +4411,7 @@ public StartupCommand[] initialize() { cmd.setGatewayIpAddress(localGateway); cmd.setIqn(getIqn()); cmd.getHostDetails().put(HOST_VOLUME_ENCRYPTION, String.valueOf(hostSupportsVolumeEncryption())); + cmd.getHostDetails().put(HOST_MIGRATE_TLS, String.valueOf(hostSupportsMigrateTls())); cmd.setHostTags(getHostTags()); boolean instanceConversionSupported = hostSupportsInstanceConversion(); cmd.getHostDetails().put(HOST_INSTANCE_CONVERSION, String.valueOf(instanceConversionSupported)); @@ -6220,6 +6222,42 @@ public boolean hostSupportsVolumeEncryption() { return true; } + protected static final String QEMU_MIGRATE_TLS_CONF_FILE = "/etc/libvirt/qemu.conf"; + protected static final String QEMU_MIGRATE_TLS_CERT_DIR = "/etc/pki/qemu"; + + /** + * Determines whether this host can encrypt the live-migration data stream with QEMU-native TLS. + * Both conditions provisioned by the CA framework (keystore-cert-import + configure_libvirt_tls) + * must hold: the QEMU migration certificates exist under {@link #QEMU_MIGRATE_TLS_CERT_DIR} and + * qemu.conf points migration TLS at that directory via {@code migrate_tls_x509_cert_dir}. The + * result is advertised to the management server as the {@code host.migrate.tls} host detail. + */ + public boolean hostSupportsMigrateTls() { + final File certDir = new File(QEMU_MIGRATE_TLS_CERT_DIR); + final File serverCert = new File(certDir, "server-cert.pem"); + final File serverKey = new File(certDir, "server-key.pem"); + final File caCert = new File(certDir, "ca-cert.pem"); + if (!serverCert.exists() || !serverKey.exists() || !caCert.exists()) { + return false; + } + + final File qemuConf = new File(QEMU_MIGRATE_TLS_CONF_FILE); + if (!qemuConf.exists()) { + return false; + } + try { + for (final String line : Files.readAllLines(qemuConf.toPath())) { + final String normalized = line.trim(); + if (!normalized.startsWith("#") && normalized.replaceAll("\\s", "").startsWith("migrate_tls_x509_cert_dir=")) { + return true; + } + } + } catch (final IOException e) { + LOGGER.warn("Unable to read {} to determine migration-TLS support", QEMU_MIGRATE_TLS_CONF_FILE, e); + } + return false; + } + public boolean isSecureMode(String bootMode) { if (StringUtils.isNotBlank(bootMode) && "secure".equalsIgnoreCase(bootMode)) { return true; diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java index 8f027e01ca4f..65f2e4051e50 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsync.java @@ -44,6 +44,7 @@ public class MigrateKVMAsync implements Callable { private boolean migrateStorage; private boolean migrateNonSharedInc; private boolean autoConvergence; + private boolean migrateTls; protected Set migrateDiskLabels; @@ -90,6 +91,13 @@ public class MigrateKVMAsync implements Callable { // to tune the algorithm. private static final long VIR_MIGRATE_AUTO_CONVERGE = 8192L; + // Use TLS for the native (QEMU) migration data connection. When set, QEMU encrypts + // the guest memory (and non-shared disk) stream using the certificates configured + // via qemu.conf's migrate_tls_x509_cert_dir. TLS is negotiated over the normal + // "tcp:" data connection - libvirt has no "tls:" migration URI scheme (valid + // schemes are tcp/rdma/unix/fd), so the URI is unchanged and only this flag is set. + private static final long VIR_MIGRATE_TLS = 65536L; + // Libvirt 1.0.3 supports compression flag for migration. private static final int LIBVIRT_VERSION_SUPPORTS_MIGRATE_COMPRESSED = 1000003; @@ -97,7 +105,8 @@ public class MigrateKVMAsync implements Callable { private static final int LIBVIRT_VERSION_SUPPORTS_AUTO_CONVERGE = 1002003; public MigrateKVMAsync(final LibvirtComputingResource libvirtComputingResource, final Domain dm, final Connect dconn, final String dxml, - final boolean migrateStorage, final boolean migrateNonSharedInc, final boolean autoConvergence, final String vmName, final String destIp, Set migrateDiskLabels) { + final boolean migrateStorage, final boolean migrateNonSharedInc, final boolean autoConvergence, final String vmName, final String destIp, Set migrateDiskLabels, + final boolean migrateTls) { this.libvirtComputingResource = libvirtComputingResource; this.dm = dm; @@ -109,6 +118,7 @@ public MigrateKVMAsync(final LibvirtComputingResource libvirtComputingResource, this.vmName = vmName; this.destIp = destIp; this.migrateDiskLabels = migrateDiskLabels; + this.migrateTls = migrateTls; } @Override @@ -134,6 +144,11 @@ public Domain call() throws LibvirtException { flags |= VIR_MIGRATE_AUTO_CONVERGE; } + if (migrateTls) { + flags |= VIR_MIGRATE_TLS; + logger.debug("Setting VIR_MIGRATE_TLS to encrypt the migration data stream of {}.", vmName); + } + TypedParameter [] parameters = createTypedParameterList(); logger.debug(String.format("Migrating [%s] with flags [%s], destination [%s] and speed [%s]. The disks with the following labels will be migrated [%s].", vmName, flags, diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java index ed02ae6da38d..a9434e137c07 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtMigrateCommandWrapper.java @@ -252,7 +252,7 @@ Use VIR_DOMAIN_XML_SECURE (value = 1) prior to v1.0.0. final Callable worker = new MigrateKVMAsync(libvirtComputingResource, dm, dconn, xmlDesc, migrateStorage, migrateNonSharedInc, - command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels); + command.isAutoConvergence(), vmName, command.getDestinationIp(), migrateDiskLabels, command.isMigrateTls()); final Future migrateThread = executor.submit(worker); executor.shutdown(); long sleeptime = 0; diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsyncTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsyncTest.java index 28633b925b21..77641c36b4e3 100644 --- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsyncTest.java +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/MigrateKVMAsyncTest.java @@ -45,7 +45,7 @@ public class MigrateKVMAsyncTest { @Test public void createTypedParameterListTestNoMigrateDiskLabels() { MigrateKVMAsync migrateKVMAsync = new MigrateKVMAsync(libvirtComputingResource, domain, connect, "testxml", - false, false, false, "tst", "1.1.1.1", null); + false, false, false, "tst", "1.1.1.1", null, false); Mockito.doReturn(10).when(libvirtComputingResource).getMigrateSpeed(); @@ -60,11 +60,30 @@ public void createTypedParameterListTestNoMigrateDiskLabels() { } + @Test + public void createTypedParameterListTestWithMigrateTlsKeepsTcpUri() { + // TLS is enabled solely via the VIR_MIGRATE_TLS flag; the data URI must stay "tcp:" + // because libvirt has no "tls:" migration URI scheme. Guards against reintroducing it. + MigrateKVMAsync migrateKVMAsync = new MigrateKVMAsync(libvirtComputingResource, domain, connect, "testxml", + false, false, false, "tst", "1.1.1.1", null, true); + + Mockito.doReturn(10).when(libvirtComputingResource).getMigrateSpeed(); + + TypedParameter[] result = migrateKVMAsync.createTypedParameterList(); + + Assert.assertEquals(4, result.length); + + Assert.assertEquals("tst", result[0].getValueAsString()); + Assert.assertEquals("testxml", result[1].getValueAsString()); + Assert.assertEquals("tcp:1.1.1.1", result[2].getValueAsString()); + Assert.assertEquals("10", result[3].getValueAsString()); + } + @Test public void createTypedParameterListTestWithMigrateDiskLabels() { Set labels = Set.of("vda", "vdb"); MigrateKVMAsync migrateKVMAsync = new MigrateKVMAsync(libvirtComputingResource, domain, connect, "testxml", - false, false, false, "tst", "1.1.1.1", labels); + false, false, false, "tst", "1.1.1.1", labels, false); Mockito.doReturn(10).when(libvirtComputingResource).getMigrateSpeed(); diff --git a/python/lib/cloudutils/serviceConfig.py b/python/lib/cloudutils/serviceConfig.py index 0b4820dd7b65..c43e7b3617f2 100755 --- a/python/lib/cloudutils/serviceConfig.py +++ b/python/lib/cloudutils/serviceConfig.py @@ -598,6 +598,10 @@ def configure_libvirt_tls(tls_enabled=False, cfo=None): cfo.addEntry("vnc_tls", "1") cfo.addEntry("vnc_tls_x509_verify", "1") cfo.addEntry("vnc_tls_x509_cert_dir", "\"/etc/pki/libvirt-vnc\"") + # QEMU native-TLS for the live-migration data stream (VIR_MIGRATE_TLS). + # Reuses the CA-framework certificates provisioned under /etc/pki/qemu. + cfo.addEntry("migrate_tls_x509_cert_dir", "\"/etc/pki/qemu\"") + cfo.addEntry("migrate_tls_x509_verify", "1") else: cfo.addEntry("vnc_tls", "0") diff --git a/scripts/util/keystore-cert-import b/scripts/util/keystore-cert-import index 447dcd71745f..32334e6ea2ec 100755 --- a/scripts/util/keystore-cert-import +++ b/scripts/util/keystore-cert-import @@ -120,12 +120,20 @@ if [ -f "$LIBVIRTD_FILE" ]; then ln -sf /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem ln -sf /etc/pki/libvirt/servercert.pem /etc/pki/libvirt-vnc/server-cert.pem ln -sf /etc/pki/libvirt/private/serverkey.pem /etc/pki/libvirt-vnc/server-key.pem + + # QEMU native-TLS directory and certificates (encrypted live-migration data stream) + mkdir -p /etc/pki/qemu + ln -sf /etc/pki/CA/cacert.pem /etc/pki/qemu/ca-cert.pem + ln -sf /etc/pki/libvirt/servercert.pem /etc/pki/qemu/server-cert.pem + ln -sf /etc/pki/libvirt/private/serverkey.pem /etc/pki/qemu/server-key.pem + ln -sf /etc/pki/libvirt/clientcert.pem /etc/pki/qemu/client-cert.pem + ln -sf /etc/pki/libvirt/private/clientkey.pem /etc/pki/qemu/client-key.pem cloudstack-setup-agent -s > /dev/null QEMU_GROUP=$(sed -n 's/^group\s*=//p' /etc/libvirt/qemu.conf | tr -d '"' | tr -d ' ' | tr -d "'" | tail -n1) if [ ! -z "${QEMU_GROUP// }" ]; then - chgrp $QEMU_GROUP /etc/pki/libvirt /etc/pki/libvirt-vnc /etc/pki/CA /etc/pki/libvirt/private /etc/pki/libvirt/servercert.pem /etc/pki/libvirt/private/serverkey.pem /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem /etc/pki/libvirt-vnc/server-cert.pem /etc/pki/libvirt-vnc/server-key.pem - chmod 750 /etc/pki/libvirt /etc/pki/libvirt-vnc /etc/pki/CA /etc/pki/libvirt/private /etc/pki/libvirt/servercert.pem /etc/pki/libvirt/private/serverkey.pem /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem /etc/pki/libvirt-vnc/server-cert.pem /etc/pki/libvirt-vnc/server-key.pem + chgrp $QEMU_GROUP /etc/pki/libvirt /etc/pki/libvirt-vnc /etc/pki/qemu /etc/pki/CA /etc/pki/libvirt/private /etc/pki/libvirt/servercert.pem /etc/pki/libvirt/private/serverkey.pem /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem /etc/pki/libvirt-vnc/server-cert.pem /etc/pki/libvirt-vnc/server-key.pem /etc/pki/qemu/ca-cert.pem /etc/pki/qemu/server-cert.pem /etc/pki/qemu/server-key.pem /etc/pki/qemu/client-cert.pem /etc/pki/qemu/client-key.pem + chmod 750 /etc/pki/libvirt /etc/pki/libvirt-vnc /etc/pki/qemu /etc/pki/CA /etc/pki/libvirt/private /etc/pki/libvirt/servercert.pem /etc/pki/libvirt/private/serverkey.pem /etc/pki/CA/cacert.pem /etc/pki/libvirt-vnc/ca-cert.pem /etc/pki/libvirt-vnc/server-cert.pem /etc/pki/libvirt-vnc/server-key.pem /etc/pki/qemu/ca-cert.pem /etc/pki/qemu/server-cert.pem /etc/pki/qemu/server-key.pem /etc/pki/qemu/client-cert.pem /etc/pki/qemu/client-key.pem fi fi diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index d9b9bc629a6b..aa7abb5f9b93 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -4607,6 +4607,7 @@ public ConfigKey[] getConfigKeys() { KvmStorageOfflineMigrationWait, KvmStorageOnlineMigrationWait, KvmAutoConvergence, + KvmMigrateTls, MaxNumberOfManagedClusteredFileSystems, STORAGE_POOL_DISK_WAIT, STORAGE_POOL_CLIENT_TIMEOUT,