On Tue, Jun 23, 2026 at 06:26:04 +0000, Abhisek Panda wrote:
> On the source host enable TLS-PSK based secure migration, if and only
> if the VIR_MIGRATE_TLS flag is set and ca-cert.pem does not exist.
> This is because the TLS X.509-based migration utilize the ca-cert.pem
> file to verify the destination host. Subsequently, the source generates
> a pre-shared key and transmits it to the destination via a migration
> cookie. On the destination host, Libvirt unconditionally enables
> PSK-based migration if it receives the key via the cookie.
>
> Suggested-by: Tejus GK <[email protected]>
> Signed-off-by: Abhisek Panda <[email protected]>
> ---
> include/libvirt/libvirt-domain.h | 13 ++++--
> src/qemu/qemu_migration.c | 74 ++++++++++++++++++++------------
> 2 files changed, 56 insertions(+), 31 deletions(-)
>
> diff --git a/include/libvirt/libvirt-domain.h
> b/include/libvirt/libvirt-domain.h
> index 5b67f8f897..e8b5d8451c 100644
> --- a/include/libvirt/libvirt-domain.h
> +++ b/include/libvirt/libvirt-domain.h
> @@ -1089,11 +1089,16 @@ 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 certificates and keys are present on the
> + * host, then TLS X.509 authentication scheme is used. However, if
> ca-cert.pem
> + * is missing on the source, then TLS PSK authentication scheme is used.
> + * In this case, the client must use a secure Libvirt to Libvirt
> communication
> + * channel because the pre-shared key is transmitted to the destination
> using the
The user ought to always use secure libvirt mode. No need to mention it
explicitly.
> + * migration cookie. If the certificate or the key file is corrupted or
> not
> + * properly configured on either source or destination, the migration
> will fail.
This will need to be updated after the changes I've suggested.
> *
> - * Since: 3.2.0
> + * Since: 12.4.0
You can't change version info of an existing flag. This shows since when
the flag exists.
> */
> VIR_MIGRATE_TLS = (1 << 16),
>
> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
> index 79b93fb6e9..bac5a953d1 100644
> --- a/src/qemu/qemu_migration.c
> +++ b/src/qemu/qemu_migration.c
> @@ -3360,6 +3360,7 @@ qemuMigrationDstPrepareActive(virQEMUDriver *driver,
> qemuDomainJobPrivate *jobPriv = vm->job->privateData;
> qemuProcessIncomingDef *incoming = NULL;
> g_autofree char *tlsx509Alias = NULL;
> + g_autofree char *tlsPSKAlias = NULL;
> virObjectEvent *event = NULL;
> virErrorPtr origErr = NULL;
> int dataFD[2] = { -1, -1 };
> @@ -3442,14 +3443,22 @@ 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,
> - &tlsx509Alias, NULL,
> - migParams) < 0)
> - goto error;
> + if (!mig->tlsPSK) {
> + if (qemuMigrationParamsEnableTLSx509(driver, vm, true,
> + VIR_ASYNC_JOB_MIGRATION_IN,
> + &tlsx509Alias, NULL,
> + migParams) < 0)
> + goto error;
> + } else {
> + if (qemuMigrationParamsEnableTLSPSK(driver, vm, true,
> + VIR_ASYNC_JOB_MIGRATION_IN,
> + &tlsPSKAlias, migParams) < 0)
> + goto error;
> + }
> } else {
> if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
> goto error;
> @@ -3556,6 +3565,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",
> @@ -3567,6 +3583,9 @@ qemuMigrationDstPrepareFresh(virQEMUDriver *driver,
> QEMU_MIGRATION_COOKIE_CAPS;
> }
>
> + if (flags & VIR_MIGRATE_TLS)
> + parseCookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
> +
> /* Let migration hook filter domain XML */
> if (virHookPresent(VIR_HOOK_DRIVER_QEMU)) {
> g_autofree char *xml = NULL;
> @@ -3613,14 +3632,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;
Destination should always parse the PSK. If it's missing there's nothing
to do but if the source sent it we should use it (to ensure
always-secure mode).
>
> if (!(vm = virDomainObjListAdd(driver->domains, def,
> @@ -5013,6 +5025,7 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
> qemuDomainObjPrivate *priv = vm->privateData;
> g_autoptr(qemuMigrationCookie) mig = NULL;
> g_autofree char *tlsx509Alias = NULL;
> + g_autofree char *tlsPSKAlias = NULL;
> qemuMigrationIOThread *iothread = NULL;
> VIR_AUTOCLOSE fd = -1;
> unsigned long restore_max_bandwidth = priv->migMaxBandwidth;
> @@ -5097,19 +5110,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,
> - &tlsx509Alias, hostname,
> - migParams) < 0)
> - goto error;
> + if (qemuMigrationCACertExists(driver)) {
Here this decision should happen based on whether the destination
libvirt instance accepted PSK or not; deciding based on existance of the
x509 key on the source will not allow ever using the PSK if the
destionation doesn't have those set up.
> + 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,
> + &tlsx509Alias, hostname,
> + migParams) < 0)
> + goto error;
> + } else {
> + if (qemuMigrationParamsEnableTLSPSK(driver, vm, false,
> + VIR_ASYNC_JOB_MIGRATION_OUT,
> + &tlsPSKAlias, migParams) < 0)
> + goto error;
> + }
> } else {
> if (qemuMigrationParamsDisableTLS(vm, migParams) < 0)
> goto error;
> --
> 2.43.7
>