Enable TLS-PSK based secure migration, if and only if the
VIR_MIGRATE_TLS flag is set and the destination host lacks necessary
X.509 credentials (ca-cert.pem, server-cert.pem and server-key.pem).
Suggested-by: Tejus GK <tejus.gk@nutanix.com>
Signed-off-by: Abhisek Panda <abhisek.panda1@nutanix.com>
---
include/libvirt/libvirt-domain.h | 11 +++--
src/qemu/qemu_migration.c | 83 +++++++++++++++++++++-----------
2 files changed, 64 insertions(+), 30 deletions(-)
diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
index 5b67f8f897..70d955fd31 100644
--- a/include/libvirt/libvirt-domain.h
+++ b/include/libvirt/libvirt-domain.h
@@ -1089,9 +1089,14 @@ typedef enum {
VIR_MIGRATE_POSTCOPY = (1 << 15),
/* Setting the VIR_MIGRATE_TLS flag will cause the migration to attempt
- * to use the TLS environment configured by the hypervisor in order to
- * perform the migration. If incorrectly configured on either source or
- * destination, the migration will fail.
+ * to use either the TLS X.509 or TLS pre-shared key (PSK) authentication
+ * mechanisms. If valid X.509 certificates and keys exist on the destination host,
+ * then Libvirt will use TLS X.509 authentication. Otherwise, Libvirt falls back to
+ * TLS PSK authentication. If the certificate or the key file is corrupted or not
+ * properly configured on either source or destination, the migration will fail.
+ *
+ * Note: Since 12.6.0, Libvirt supports the TLS pre-shared key (PSK) authentication
+ * mechanism along with the TLS X.509 authentication scheme.
*
* Since: 3.2.0
*/
diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
index 943478fe68..5e381b96e9 100644
--- a/src/qemu/qemu_migration.c
+++ b/src/qemu/qemu_migration.c
@@ -3508,14 +3508,28 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
/* Save original migration parameters */
qemuDomainSaveStatus(vm);
- /* Migrations using TLS need to add the "tls-creds-x509" object and
+ /* Migrations using TLS need to add the "tls-creds-x509" object if the cert files are
+ * present on the host, else fallback to adding the "tls-creds-psk" object. Additionally,
* set the migration TLS parameters */
if (flags & VIR_MIGRATE_TLS) {
- if (qemuMigrationParamsEnableTLSx509(driver, vm, true,
- VIR_ASYNC_JOB_MIGRATION_IN,
- &tlsAlias, NULL,
- migParams) < 0)
- goto error;
+ if (!mig->tlsPSK) {
+ if (qemuMigrationParamsEnableTLSx509(driver, vm, true,
+ VIR_ASYNC_JOB_MIGRATION_IN,
+ &tlsAlias, NULL,
+ migParams) < 0)
+ goto error;
+ } else {
+ ret = qemuMigrationPersistPSK(driver, vm, mig->tlsPSK);
+ if (ret < 0) {
+ virSecureEraseString(mig->tlsPSK);
+ g_clear_pointer(&mig->tlsPSK, g_free);
+ goto error;
+ }
+ if (qemuMigrationParamsEnableTLSPSK(driver, vm, true,
+ VIR_ASYNC_JOB_MIGRATION_IN,
+ &tlsAlias, migParams) < 0)
+ goto error;
+ }
} else {
if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
goto error;
@@ -3622,6 +3636,13 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
g_autofree char *xmlout = NULL;
unsigned int cookieFlags = 0;
bool taint_hook = false;
+ unsigned int parseCookieFlags = QEMU_MIGRATION_COOKIE_LOCKSTATE |
+ QEMU_MIGRATION_COOKIE_NBD |
+ QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG |
+ QEMU_MIGRATION_COOKIE_CPU_HOTPLUG |
+ QEMU_MIGRATION_COOKIE_CPU |
+ QEMU_MIGRATION_COOKIE_CAPS |
+ QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS;
VIR_DEBUG("name=%s, origname=%s, protocol=%s, port=%hu, "
"listenAddress=%s, nbdPort=%d, nbdURI=%s, flags=0x%x",
@@ -3633,6 +3654,11 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
QEMU_MIGRATION_COOKIE_CAPS;
}
+ if (flags & VIR_MIGRATE_TLS) {
+ parseCookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
+ cookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
+ }
+
/* Let migration hook filter domain XML */
if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
g_autofree char *xml = NULL;
@@ -3679,14 +3705,7 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
* domain list. Parsing/validation may fail and there's no
* point in having the domain in the list at that point. */
if (!(mig = qemuMigrationCookieParse(driver, NULL, *def, origname, NULL,
- cookiein, cookieinlen,
- QEMU_MIGRATION_COOKIE_LOCKSTATE |
- QEMU_MIGRATION_COOKIE_NBD |
- QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG |
- QEMU_MIGRATION_COOKIE_CPU_HOTPLUG |
- QEMU_MIGRATION_COOKIE_CPU |
- QEMU_MIGRATION_COOKIE_CAPS |
- QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS)))
+ cookiein, cookieinlen, parseCookieFlags)))
goto cleanup;
if (!(vm = virDomainObjListAdd(driver->domains, def,
@@ -5112,6 +5131,9 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
if (storageMigration)
cookieFlags |= QEMU_MIGRATION_COOKIE_NBD;
+ if (flags & VIR_MIGRATE_TLS)
+ cookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
+
if (virLockManagerPluginUsesState(driver->lockManager) &&
!cookieout) {
virReportError(VIR_ERR_INTERNAL_ERROR,
@@ -5163,19 +5185,26 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
qemuDomainSaveStatus(vm);
if (flags & VIR_MIGRATE_TLS) {
- const char *hostname = NULL;
-
- /* We need to add tls-hostname whenever QEMU itself does not
- * connect directly to the destination. */
- if (spec->destType == MIGRATION_DEST_CONNECT_HOST ||
- spec->destType == MIGRATION_DEST_FD)
- hostname = spec->dest.host.name;
-
- if (qemuMigrationParamsEnableTLSx509(driver, vm, false,
- VIR_ASYNC_JOB_MIGRATION_OUT,
- &tlsAlias, hostname,
- migParams) < 0)
- goto error;
+ if (mig->flags & QEMU_MIGRATION_COOKIE_TLS_PSK) {
+ if (qemuMigrationParamsEnableTLSPSK(driver, vm, false,
+ VIR_ASYNC_JOB_MIGRATION_OUT,
+ &tlsAlias, migParams) < 0)
+ goto error;
+ } else {
+ const char *hostname = NULL;
+
+ /* We need to add tls-hostname whenever QEMU itself does not
+ * connect directly to the destination. */
+ if (spec->destType == MIGRATION_DEST_CONNECT_HOST ||
+ spec->destType == MIGRATION_DEST_FD)
+ hostname = spec->dest.host.name;
+
+ if (qemuMigrationParamsEnableTLSx509(driver, vm, false,
+ VIR_ASYNC_JOB_MIGRATION_OUT,
+ &tlsAlias, hostname,
+ migParams) < 0)
+ goto error;
+ }
} else {
if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
goto error;
--
2.43.7
On Wed, Jul 29, 2026 at 08:58:58 +0000, Abhisek Panda wrote:
> Enable TLS-PSK based secure migration, if and only if the
> VIR_MIGRATE_TLS flag is set and the destination host lacks necessary
> X.509 credentials (ca-cert.pem, server-cert.pem and server-key.pem).
>
> Suggested-by: Tejus GK <tejus.gk@nutanix.com>
> Signed-off-by: Abhisek Panda <abhisek.panda1@nutanix.com>
> ---
> include/libvirt/libvirt-domain.h | 11 +++--
> src/qemu/qemu_migration.c | 83 +++++++++++++++++++++-----------
> 2 files changed, 64 insertions(+), 30 deletions(-)
>
> diff --git a/include/libvirt/libvirt-domain.h b/include/libvirt/libvirt-domain.h
> index 5b67f8f897..70d955fd31 100644
> --- a/include/libvirt/libvirt-domain.h
> +++ b/include/libvirt/libvirt-domain.h
> @@ -1089,9 +1089,14 @@ typedef enum {
> VIR_MIGRATE_POSTCOPY = (1 << 15),
>
> /* Setting the VIR_MIGRATE_TLS flag will cause the migration to attempt
> - * to use the TLS environment configured by the hypervisor in order to
> - * perform the migration. If incorrectly configured on either source or
> - * destination, the migration will fail.
> + * to use either the TLS X.509 or TLS pre-shared key (PSK) authentication
> + * mechanisms. If valid X.509 certificates and keys exist on the destination host,
> + * then Libvirt will use TLS X.509 authentication. Otherwise, Libvirt falls back to
> + * TLS PSK authentication. If the certificate or the key file is corrupted or not
> + * properly configured on either source or destination, the migration will fail.
> + *
> + * Note: Since 12.6.0, Libvirt supports the TLS pre-shared key (PSK) authentication
> + * mechanism along with the TLS X.509 authentication scheme.
Per
<https://lists.libvirt.org/archives/list/devel@lists.libvirt.org/message/PLXLRIV33ZGREYCL745JXCF34TOLARGE/>
freeze is on monday, so the version statement might need to be bumped.
Once again I'm sorry about the review delay, but there's a lot of other
stuff I need to deal with.
> *
> * Since: 3.2.0
> */
> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index 943478fe68..5e381b96e9 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c
> @@ -3508,14 +3508,28 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
> /* Save original migration parameters */
> qemuDomainSaveStatus(vm);
>
> - /* Migrations using TLS need to add the "tls-creds-x509" object and
> + /* Migrations using TLS need to add the "tls-creds-x509" object if the cert files are
> + * present on the host, else fallback to adding the "tls-creds-psk" object. Additionally,
> * set the migration TLS parameters */
> if (flags & VIR_MIGRATE_TLS) {
> - if (qemuMigrationParamsEnableTLSx509(driver, vm, true,
> - VIR_ASYNC_JOB_MIGRATION_IN,
> - &tlsAlias, NULL,
> - migParams) < 0)
> - goto error;
> + if (!mig->tlsPSK) {
> + if (qemuMigrationParamsEnableTLSx509(driver, vm, true,
> + VIR_ASYNC_JOB_MIGRATION_IN,
> + &tlsAlias, NULL,
> + migParams) < 0)
> + goto error;
> + } else {
> + ret = qemuMigrationPersistPSK(driver, vm, mig->tlsPSK);
> + if (ret < 0) {
> + virSecureEraseString(mig->tlsPSK);
> + g_clear_pointer(&mig->tlsPSK, g_free);
> + goto error;
As noted before this belongs to the common cleanup. Also at a failure at
this point the key was not used yet so there's no need to consider it a
secret.
> + }
> + if (qemuMigrationParamsEnableTLSPSK(driver, vm, true,
> + VIR_ASYNC_JOB_MIGRATION_IN,
> + &tlsAlias, migParams) < 0)
> + goto error;
> + }
For future proofing (enabling PSK tls even without explicit flag) the
logic should be:
1) if PSK exists enable TLS psk
2) if VIR_MIGRATE_TLS and x509 certs exist enable x509
3) otherwise do nothing
> } else {
> if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
> goto error;
> @@ -3622,6 +3636,13 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
> g_autofree char *xmlout = NULL;
> unsigned int cookieFlags = 0;
> bool taint_hook = false;
> + unsigned int parseCookieFlags = QEMU_MIGRATION_COOKIE_LOCKSTATE |
> + QEMU_MIGRATION_COOKIE_NBD |
> + QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG |
> + QEMU_MIGRATION_COOKIE_CPU_HOTPLUG |
> + QEMU_MIGRATION_COOKIE_CPU |
> + QEMU_MIGRATION_COOKIE_CAPS |
> + QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS;
>
> VIR_DEBUG("name=%s, origname=%s, protocol=%s, port=%hu, "
> "listenAddress=%s, nbdPort=%d, nbdURI=%s, flags=0x%x",
> @@ -3633,6 +3654,11 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
> QEMU_MIGRATION_COOKIE_CAPS;
> }
>
> + if (flags & VIR_MIGRATE_TLS) {
> + parseCookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
> + cookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
Always parse PSK on destination. There's no need to gate this on VIR_MIGRATE_TLS
> + }
> +
> /* Let migration hook filter domain XML */
> if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
> g_autofree char *xml = NULL;
> @@ -3679,14 +3705,7 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
> * domain list. Parsing/validation may fail and there's no
> * point in having the domain in the list at that point. */
> if (!(mig = qemuMigrationCookieParse(driver, NULL, *def, origname, NULL,
> - cookiein, cookieinlen,
> - QEMU_MIGRATION_COOKIE_LOCKSTATE |
> - QEMU_MIGRATION_COOKIE_NBD |
> - QEMU_MIGRATION_COOKIE_MEMORY_HOTPLUG |
> - QEMU_MIGRATION_COOKIE_CPU_HOTPLUG |
> - QEMU_MIGRATION_COOKIE_CPU |
> - QEMU_MIGRATION_COOKIE_CAPS |
> - QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS)))
> + cookiein, cookieinlen, parseCookieFlags)))
> goto cleanup;
>
> if (!(vm = virDomainObjListAdd(driver->domains, def,
> @@ -5112,6 +5131,9 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
> if (storageMigration)
> cookieFlags |= QEMU_MIGRATION_COOKIE_NBD;
>
> + if (flags & VIR_MIGRATE_TLS)
> + cookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
> +
> if (virLockManagerPluginUsesState(driver->lockManager) &&
> !cookieout) {
> virReportError(VIR_ERR_INTERNAL_ERROR,
> @@ -5163,19 +5185,26 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
> qemuDomainSaveStatus(vm);
>
> if (flags & VIR_MIGRATE_TLS) {
> - const char *hostname = NULL;
> -
> - /* We need to add tls-hostname whenever QEMU itself does not
> - * connect directly to the destination. */
> - if (spec->destType == MIGRATION_DEST_CONNECT_HOST ||
> - spec->destType == MIGRATION_DEST_FD)
> - hostname = spec->dest.host.name;
> -
> - if (qemuMigrationParamsEnableTLSx509(driver, vm, false,
> - VIR_ASYNC_JOB_MIGRATION_OUT,
> - &tlsAlias, hostname,
> - migParams) < 0)
> - goto error;
> + if (mig->flags & QEMU_MIGRATION_COOKIE_TLS_PSK) {
> + if (qemuMigrationParamsEnableTLSPSK(driver, vm, false,
> + VIR_ASYNC_JOB_MIGRATION_OUT,
> + &tlsAlias, migParams) < 0)
> + goto error;
> + } else {
> + const char *hostname = NULL;
> +
> + /* We need to add tls-hostname whenever QEMU itself does not
> + * connect directly to the destination. */
> + if (spec->destType == MIGRATION_DEST_CONNECT_HOST ||
> + spec->destType == MIGRATION_DEST_FD)
> + hostname = spec->dest.host.name;
> +
> + if (qemuMigrationParamsEnableTLSx509(driver, vm, false,
> + VIR_ASYNC_JOB_MIGRATION_OUT,
> + &tlsAlias, hostname,
> + migParams) < 0)
> + goto error;
> + }
Same note as before. If the PSK got through the handshake, you can
always enable it rather than gating it behind VIR_MIGRATE_TLS.
x509 needs to be gated behind VIR_MIGRATE_TLS but only if PSK is not
agreed upon
> } else {
> if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
> goto error;
> --
> 2.43.7
>
© 2016 - 2026 Red Hat, Inc.