> On 2 Oct 2026, at 5:59 PM, Peter Krempa <[email protected]> wrote:
>
> !-------------------------------------------------------------------|
> CAUTION: External Email
>
> |-------------------------------------------------------------------!
>
> On Mon, Sep 07, 2026 at 08:55:20 +0000, Abhisek Panda wrote:
>> Libvirt by default attempt to use the TLS-PSK-enabled VM migration,
>> if the both source and destination support the tls-creds-psk object.
>> The source host adds the pre-shared key in the migration cookie if the
>> source supports the tls-creds-psk object. Upon parsing the migration
>> cookie, the destination host checks for the same capability and informs
>> the source host whether to use TLS X.509 or TLS PSK during VM migration
>> via the migration cookie.
>>
>> For a migration session, Libvirt generates a random key of the
>> specified length, and then stores the content, "qemu:<random key>", at
>> <tls_psk_state_dir>/$ID-$VMNAME/keys.psk on the source host. This is
>> because QEMU's tls-creds-psk object does not accept a raw key string
>> as a parameter, it only accepts a dir argument pointing to a directory
>> from which it can read the key file. Subsequently, it sends the key to
>> destination by embedding it within the migration cookie. The
>> destination's Libvirt extracts the key from the migration cookie.
>> Upon migration completion or any failure, both source and destination
>> Libvirt must delete the directory containing the session's keys.psk.
>>
>> Signed-off-by: Abhisek Panda <[email protected]>
>> ---
>> src/qemu/qemu_capabilities.c | 2 +
>> src/qemu/qemu_capabilities.h | 1 +
>> src/qemu/qemu_conf.c | 4 +
>> src/qemu/qemu_conf.h | 1 +
>> src/qemu/qemu_domain.c | 1 +
>> src/qemu/qemu_domain.h | 1 +
>> src/qemu/qemu_driver.c | 6 ++
>> src/qemu/qemu_migration.c | 98 +++++++++++++++++++
>> src/qemu/qemu_migration.h | 3 +
>> src/qemu/qemu_migration_cookie.c | 79 ++++++++++++++-
>> src/qemu/qemu_migration_cookie.h | 5 +
>> src/qemu/qemu_process.c | 3 +
>> .../caps_10.0.0_aarch64.xml | 1 +
>> .../caps_10.0.0_ppc64.xml | 1 +
>> .../caps_10.0.0_s390x.xml | 1 +
>> .../caps_10.0.0_x86_64+amdsev.xml | 1 +
>> .../caps_10.0.0_x86_64.xml | 1 +
>> .../caps_10.1.0_s390x.xml | 1 +
>> .../caps_10.1.0_x86_64+inteltdx.xml | 1 +
>> .../caps_10.1.0_x86_64.xml | 1 +
>> .../caps_10.2.0_aarch64.xml | 1 +
>> .../caps_10.2.0_x86_64+mshv.xml | 1 +
>> .../caps_10.2.0_x86_64.xml | 1 +
>> .../caps_11.0.0_aarch64.xml | 1 +
>> .../caps_11.0.0_s390x.xml | 1 +
>> .../caps_11.0.0_x86_64+sgx.xml | 1 +
>> .../caps_11.0.0_x86_64.xml | 1 +
>> .../caps_11.1.0_aarch64.xml | 1 +
>> .../caps_11.1.0_s390x.xml | 1 +
>> .../caps_11.1.0_x86_64.xml | 1 +
>> tests/qemucapabilitiesdata/caps_7.2.0_ppc.xml | 1 +
>> .../caps_7.2.0_x86_64+hvf.xml | 1 +
>> .../caps_7.2.0_x86_64.xml | 1 +
>> .../caps_8.0.0_x86_64.xml | 1 +
>> .../qemucapabilitiesdata/caps_8.1.0_s390x.xml | 1 +
>> .../caps_8.1.0_x86_64.xml | 1 +
>> .../caps_8.2.0_aarch64.xml | 1 +
>> .../caps_8.2.0_armv7l.xml | 1 +
>> .../caps_8.2.0_loongarch64.xml | 1 +
>> .../qemucapabilitiesdata/caps_8.2.0_s390x.xml | 1 +
>> .../caps_8.2.0_x86_64.xml | 1 +
>> .../qemucapabilitiesdata/caps_9.0.0_sparc.xml | 1 +
>> .../caps_9.0.0_x86_64.xml | 1 +
>> .../caps_9.1.0_riscv64.xml | 1 +
>> .../qemucapabilitiesdata/caps_9.1.0_s390x.xml | 1 +
>> .../caps_9.1.0_x86_64.xml | 1 +
>> .../caps_9.2.0_aarch64+hvf.xml | 1 +
>> .../qemucapabilitiesdata/caps_9.2.0_s390x.xml | 1 +
>> .../caps_9.2.0_x86_64+amdsev.xml | 1 +
>> .../caps_9.2.0_x86_64.xml | 1 +
>> tests/qemumigrationcookiexmltest.c | 12 ++-
>> 51 files changed, 249 insertions(+), 5 deletions(-)
>>
>> diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
>> index bc2b0412dc..3db4710dee 100644
>> --- a/src/qemu/qemu_capabilities.c
>> +++ b/src/qemu/qemu_capabilities.c
>> @@ -776,6 +776,7 @@ VIR_ENUM_IMPL(virQEMUCaps,
>> "arm-smmuv3.cmdqv", /* QEMU_CAPS_ARM_SMMUV3_CMDQV */
>> "iothread.poll-weight", /* QEMU_CAPS_IOTHREAD_POLL_WEIGHT */
>> "win-dmp.guest-aware", /* QEMU_CAPS_WIN_DMP_GUEST_AWARE */
>> + "tls-creds-psk", /* QEMU_CAPS_OBJECT_TLS_CREDS_PSK */
>> );
>>
>>
>> @@ -1490,6 +1491,7 @@ struct virQEMUCapsStringFlags virQEMUCapsObjectTypes[]
>> = {
>> { "uefi-vars-sysbus", QEMU_CAPS_DEVICE_UEFI_VARS },
>> { "monitor-qmp", QEMU_CAPS_OBJECT_MONITOR_QMP },
>> { "arm-smmuv3", QEMU_CAPS_DEVICE_ARM_SMMUV3 },
>> + { "tls-creds-psk", QEMU_CAPS_OBJECT_TLS_CREDS_PSK },
>
> Any reason why the capability is introduced? I didn't see it mentioned
> in our previous conversation.
>
> In case if the support can be compiled out it's okay to add a
> capability, but new capabilities need to go into a separate commit
> because they make existing commits really massive and hard to backport.
>
> So if the capability is needed you'll have to split the hunks that add
> the capability and the detection into a separate commit which will need
> to preceed this one which makes use of it.
The rationale behind adding this capability was to check if the source and
destination’s
QEMU supports PSK before attempting to perform PSK-based migration. Since
Libvirt’s
minimum supported QEMU version is now >= 7.2, and tls-creds-psk objects have
been
supported since QEMU 3.0, we can safely assume this capability exists on both
the source
and destination and drop this capability stuff.
>
>> diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
>> index 28303fd8f0..1e432cdc41 100644
>> --- a/src/qemu/qemu_capabilities.h
>> +++ b/src/qemu/qemu_capabilities.h
>> @@ -750,6 +750,7 @@ typedef enum { /* virQEMUCapsFlags grouping marker for
>> syntax-check */
>> QEMU_CAPS_ARM_SMMUV3_CMDQV, /* arm-smmuv3.cmdqv */
>> QEMU_CAPS_IOTHREAD_POLL_WEIGHT, /* -object iothread.poll-weight */
>> QEMU_CAPS_WIN_DMP_GUEST_AWARE, /* 'win-dmp' is offered only to a guest
>> that can use it */
>> + QEMU_CAPS_OBJECT_TLS_CREDS_PSK, /* -object tls-creds-psk */
>>
>> QEMU_CAPS_LAST /* this must always be the last item */
>> } virQEMUCapsFlags;
>> diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c
>> index cca7776b80..f5c5437c89 100644
>> --- a/src/qemu/qemu_conf.c
>> +++ b/src/qemu/qemu_conf.c
>> @@ -168,6 +168,7 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool
>> privileged,
>> cfg->cacheDir = g_strdup_printf("%s/cache/qemu", root);
>> cfg->libDir = g_strdup_printf("%s/lib/qemu", root);
>> cfg->swtpmStorageDir = g_strdup_printf("%s/lib/swtpm", root);
>> + cfg->tlsPSKStateDir = g_strdup_printf("%s/run/psk", root);
>>
>> cfg->saveDir = g_strdup_printf("%s/save", cfg->libDir);
>> cfg->snapshotDir = g_strdup_printf("%s/snapshot", cfg->libDir);
>> @@ -187,6 +188,7 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool
>> privileged,
>> cfg->stateDir = g_strdup_printf("%s/libvirt/qemu", RUNSTATEDIR);
>> cfg->swtpmStateDir = g_strdup_printf("%s/swtpm", cfg->stateDir);
>> cfg->channelTargetDir = g_strdup_printf("%s/channel", cfg->stateDir);
>> + cfg->tlsPSKStateDir = g_strdup_printf("%s/psk", cfg->stateDir);
>>
>> cfg->cacheDir = g_strdup_printf("%s/cache/libvirt/qemu",
>> LOCALSTATEDIR);
>>
>> @@ -214,6 +216,7 @@ virQEMUDriverConfig *virQEMUDriverConfigNew(bool
>> privileged,
>> cfg->stateDir = g_strdup_printf("%s/qemu/run", rundir);
>> cfg->swtpmStateDir = g_strdup_printf("%s/swtpm", cfg->stateDir);
>> cfg->channelTargetDir = g_strdup_printf("%s/channel", cfg->stateDir);
>> + cfg->tlsPSKStateDir = g_strdup_printf("%s/psk", cfg->stateDir);
>>
>> cfg->configBaseDir = virGetUserConfigDirectory();
>>
>> @@ -375,6 +378,7 @@ static void virQEMUDriverConfigDispose(void *obj)
>> g_free(cfg->dbusStateDir);
>> g_free(cfg->rdpStateDir);
>> g_free(cfg->vncStateDir);
>> + g_free(cfg->tlsPSKStateDir);
>>
>> g_free(cfg->libDir);
>> g_free(cfg->cacheDir);
>> diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h
>> index b700829342..2db3440514 100644
>> --- a/src/qemu/qemu_conf.h
>> +++ b/src/qemu/qemu_conf.h
>> @@ -113,6 +113,7 @@ struct _virQEMUDriverConfig {
>> char *dbusStateDir;
>> char *rdpStateDir;
>> char *vncStateDir;
>> + char *tlsPSKStateDir;
>> /* These two directories are ones QEMU processes use (so must match
>> * the QEMU user/group */
>> char *libDir;
>> diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
>> index a4e5f92840..f4d470553d 100644
>> --- a/src/qemu/qemu_domain.c
>> +++ b/src/qemu/qemu_domain.c
>> @@ -1990,6 +1990,7 @@ qemuDomainObjPrivateFree(void *data)
>> virObjectUnref(priv->monConfig);
>> g_free(priv->lockState);
>> g_free(priv->origname);
>> + g_free(priv->migTLSPSK);
>>
>> virChrdevFree(priv->devs);
>>
>> diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
>> index 23e99dc68c..053140f7ec 100644
>> --- a/src/qemu/qemu_domain.h
>> +++ b/src/qemu/qemu_domain.h
>> @@ -139,6 +139,7 @@ struct _qemuDomainObjPrivate {
>> char *origname;
>> int nbdPort; /* Port used for migration with NBD */
>> unsigned short migrationPort;
>> + char *migTLSPSK; /* Hex-encoded pre-shared key for TLS-PSK-enabled VM
>> migration session */
>> unsigned short backupNBDPort;
>> int preMigrationState;
>> unsigned long long preMigrationMemlock; /* Original RLIMIT_MEMLOCK in
>> case
>> diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
>> index 8498568623..5548d6f7bd 100644
>> --- a/src/qemu/qemu_driver.c
>> +++ b/src/qemu/qemu_driver.c
>> @@ -664,6 +664,12 @@ qemuStateInitialize(bool privileged,
>> cfg->vncStateDir);
>> goto error;
>> }
>> + if (virDirCreate(cfg->tlsPSKStateDir, 0700, cfg->user, cfg->group,
>> + VIR_DIR_CREATE_ALLOW_EXIST) < 0) {
>> + virReportSystemError(errno, _("Failed to create TLS PSK state dir
>> %1$s"),
>> + cfg->tlsPSKStateDir);
>> + goto error;
>
> So, recently we've had a few reports about "security" bugs regarding
> directories owned by "qemu:qemu" which are filled by virtqemud with some
> objects.
>
> That makes me think two things:
> 1) does this need to be in a separate directory? We really should have
> a per-VM directory for all the per-instance helper files which will
> live in /run so that it's removed on reboot
>
> 2) the directory shouldn't be owned by qemu:qemu, but rather root:root
> for the system instance and have 0711 mode so that qemu can open the
> files but not create them.
>
> I know this is a more general thing, as our directory in /run/ is
> nonsensically split to e.g. /run/libvirt/qemu/channel/$VMID rather than
> have /run/libvirt/qemu/(possibly some subdir such as 'instance'/$ID'
> which will include all the files.
>
> For this series, what definitely applies is the ownership and
> permissions point.
>
>
>
>> + }
>>
>> qemu_driver->inhibitor = virInhibitorNew(
>> VIR_INHIBITOR_WHAT_SHUTDOWN,
>> diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c
>> index 4a43ab83b0..b9971286fd 100644
>> --- a/src/qemu/qemu_migration.c
>> +++ b/src/qemu/qemu_migration.c
>> @@ -59,6 +59,7 @@
>> #include "virprocess.h"
>> #include "virdomainsnapshotobjlist.h"
>> #include "virutil.h"
>> +#include "virsecureerase.h"
>>
>> #define VIR_FROM_THIS VIR_FROM_QEMU
>>
>> @@ -1503,6 +1504,89 @@ qemuMigrationSrcIsAllowedHostdev(const virDomainDef
>> *def)
>> }
>>
>>
>> +void
>> +qemuMigrationDeletePSKDir(virQEMUDriver *driver, virDomainObj *vm)
>> +{
>> + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
>> + qemuDomainObjPrivate *priv = vm->privateData;
>> + g_autofree char *dir_path = NULL;
>> + g_autofree char *shortName = NULL;
>> +
>> + if (priv->migTLSPSK) {
>> + virSecureEraseString(priv->migTLSPSK);
>> + g_clear_pointer(&priv->migTLSPSK, g_free);
>> + }
>> +
>> + if (!(shortName = virDomainDefGetShortName(vm->def)))
>
> This failure ...
>
>> + return;
>> +
>> + dir_path = g_strdup_printf("%s/%s", cfg->tlsPSKStateDir, shortName);
>> +
>> + if (virFileIsDir(dir_path) &&
>> + virFileDeleteTree(dir_path) < 0)
>> + VIR_WARN("Failed to delete the directory %s containing the
>> pre-shared keys for migration of domain %s",
>> + dir_path, vm->def->name);
>
> ... has the same implication.
>
>
>> +}
>> +
>> +
>> +static int
>> +qemuPersistTLSPSKHelper(int pskFD,
>> + const char *pskPath,
>> + const void *opaque)
>> +{
>> + const char *key = opaque;
>> +
>> + if (safewrite(pskFD, "qemu:", 5) < 0 ||
>> + safewrite(pskFD, key, strlen(key)) < 0) {
>> + virReportSystemError(errno,
>> + _("Unable to write the pre-shared key to file
>> '%1$s'"),
>> + pskPath);
>> + return -1;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +
>> +static int
>> +qemuMigrationPersistPSK(virQEMUDriver *driver, virDomainObj *vm, const char
>> *tlsPSK)
>> +{
>> + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
>> + g_autofree char *dir_path = NULL;
>> + g_autofree char *key_path = NULL;
>> + g_autofree char *shortName = NULL;
>> +
>> + if (!(shortName = virDomainDefGetShortName(vm->def))) {
>> + virReportError(VIR_ERR_INTERNAL_ERROR,
>> + _("Could not get the shortened domain name for
>> domain: %1$s"),
>> + vm->def->name);
>> + return -1;
>> + }
>> +
>> + dir_path = g_strdup_printf("%s/%s", cfg->tlsPSKStateDir, shortName);
>> + key_path = g_strdup_printf("%s/keys.psk", dir_path);
>> +
>> + if (virDirCreate(dir_path, 0700, cfg->user, cfg->group,
>> + VIR_DIR_CREATE_ALLOW_EXIST) < 0) {
>
> Same here, the directory IMO doesn't need to be owned by qemu because
> qemu will never attempt to create a file here. It needs to be accessible
> by 'other' though.
>
>
>> + virReportSystemError(errno,
>> + _("Could not create the directory %1$s for
>> storing PSKs"),
>> + dir_path);
>> + goto error;
>> + }
>> +
>> + if (virFileRewrite(key_path, S_IRUSR, cfg->user,
>> + cfg->group, qemuPersistTLSPSKHelper,
>> + tlsPSK) < 0)
>> + goto error;
>> +
>> + return 0;
>> +
>> + error:
>> + qemuMigrationDeletePSKDir(driver, vm);
>> + return -1;
>> +}
>> +
>> +
>> static int
>> qemuDomainGetMigrationBlockers(virDomainObj *vm,
>> int asyncJob,
>> @@ -2722,6 +2806,9 @@ qemuMigrationSrcBeginXML(virDomainObj *vm,
>> if (priv->origCPU)
>> cookieFlags |= QEMU_MIGRATION_COOKIE_CPU;
>>
>> + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_TLS_CREDS_PSK))
>> + cookieFlags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
>> +
>> if (!(flags & VIR_MIGRATE_OFFLINE))
>> cookieFlags |= QEMU_MIGRATION_COOKIE_CAPS;
>>
>> @@ -2738,6 +2825,9 @@ qemuMigrationSrcBeginXML(virDomainObj *vm,
>> cookieFlags) < 0)
>> return NULL;
>>
>> + if (mig->tlsPSK && qemuMigrationPersistPSK(driver, vm, mig->tlsPSK) < 0)
>> + return NULL;
>> +
>> if (xmlin) {
>> g_autoptr(virDomainDef) def = NULL;
>>
>> @@ -4232,6 +4322,8 @@ qemuMigrationSrcConfirmPhase(virQEMUDriver *driver,
>> privJob->stats.mig.downtime = privMigJob->stats.mig.downtime;
>> }
>>
>> + qemuMigrationDeletePSKDir(driver, vm);
>> +
>> if (flags & VIR_MIGRATE_OFFLINE)
>> return 0;
>>
>> @@ -5274,6 +5366,7 @@ qemuMigrationSrcRun(virQEMUDriver *driver,
>>
>> error:
>> virErrorPreserveLast(&orig_err);
>> + qemuMigrationDeletePSKDir(driver, vm);
>>
>> if (qemuDomainObjIsActive(vm)) {
>> int reason;
>> @@ -7029,6 +7122,8 @@ qemuMigrationDstFinishActive(virQEMUDriver *driver,
>> QEMU_MIGRATION_COOKIE_STATS) < 0)
>> VIR_WARN("Unable to encode migration cookie");
>>
>> + qemuMigrationDeletePSKDir(driver, vm);
>> +
>> qemuMigrationDstComplete(driver, vm, inPostCopy,
>> VIR_ASYNC_JOB_MIGRATION_IN, vm->job);
>>
>> @@ -7039,6 +7134,8 @@ qemuMigrationDstFinishActive(virQEMUDriver *driver,
>> * overwrites it. */
>> virErrorPreserveLast(&orig_err);
>>
>> + qemuMigrationDeletePSKDir(driver, vm);
>> +
>> if (qemuDomainObjIsActive(vm)) {
>> if (doKill) {
>> qemuProcessStop(vm, VIR_DOMAIN_SHUTOFF_FAILED,
>> @@ -7197,6 +7294,7 @@ qemuMigrationProcessUnattended(virQEMUDriver *driver,
>> else
>> qemuMigrationSrcComplete(driver, vm, job);
>>
>> + qemuMigrationDeletePSKDir(driver, vm);
>> qemuMigrationJobFinish(vm);
>>
>> if (!virDomainObjIsActive(vm))
>> diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h
>> index 7e9410e1f7..ce15f8024b 100644
>> --- a/src/qemu/qemu_migration.h
>> +++ b/src/qemu/qemu_migration.h
>> @@ -279,3 +279,6 @@ int
>> qemuMigrationAnyRefreshStatus(virDomainObj *vm,
>> virDomainAsyncJob asyncJob,
>> virDomainJobStatus *status);
>> +
>> +void
>> +qemuMigrationDeletePSKDir(virQEMUDriver *driver, virDomainObj *vm);
>> diff --git a/src/qemu/qemu_migration_cookie.c
>> b/src/qemu/qemu_migration_cookie.c
>> index 7311a8294b..0cf1ab9590 100644
>> --- a/src/qemu/qemu_migration_cookie.c
>> +++ b/src/qemu/qemu_migration_cookie.c
>> @@ -20,6 +20,7 @@
>>
>> #include <gnutls/gnutls.h>
>> #include <gnutls/x509.h>
>> +#include <inttypes.h>
>>
>> #include "locking/domain_lock.h"
>> #include "virerror.h"
>> @@ -27,6 +28,7 @@
>> #include "virnetdevopenvswitch.h"
>> #include "virstring.h"
>> #include "virutil.h"
>> +#include "virsecureerase.h"
>>
>> #include "qemu_domain.h"
>> #include "qemu_migration_cookie.h"
>> @@ -52,6 +54,7 @@ VIR_ENUM_IMPL(qemuMigrationCookieFlag,
>> "allowReboot",
>> "capabilities",
>> "block-dirty-bitmaps",
>> + "psk",
>> );
>>
>>
>> @@ -165,6 +168,9 @@ qemuMigrationCookieFree(qemuMigrationCookie *mig)
>> g_free(mig->name);
>> g_free(mig->lockState);
>> g_free(mig->lockDriver);
>> + if (mig->tlsPSK)
>> + virSecureEraseString(mig->tlsPSK);
>> + g_free(mig->tlsPSK);
>> g_clear_pointer(&mig->jobData, virDomainJobDataFree);
>> virCPUDefFree(mig->cpu);
>> qemuMigrationCookieCapsFree(mig->caps);
>> @@ -575,6 +581,51 @@ qemuMigrationCookieAddCaps(qemuMigrationCookie *mig,
>> }
>>
>>
>> +static int
>> +qemuMigrationCookieAddTLSPSK(qemuMigrationCookie *mig,
>> + virQEMUDriver *driver,
>> + virDomainObj *vm)
>> +{
>> + g_autoptr(virQEMUDriverConfig) cfg = virQEMUDriverGetConfig(driver);
>> + qemuDomainObjPrivate *priv = vm->privateData;
>> + gnutls_datum_t psk_key = {NULL, 0};
>> + g_autofree char *key = NULL;
>> + size_t key_len;
>> + int ret;
>> +
>> + /* Generate the pre-shared key exactly once for a migration session*/
>> + if (priv->migTLSPSK) {
>> + mig->tlsPSK = g_strdup(priv->migTLSPSK);
>> + mig->flags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
>> + return 0;
>> + }
>> +
>> + ret = gnutls_key_generate(&psk_key, cfg->migrateTLSPSKLength);
>> + if (ret < 0) {
>> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> + _("Generation of a pre-shared key failed"));
>> + return -1;
>> + }
>> + key_len = (psk_key.size*2) + 1;
>> + key = g_new0(char, key_len);
>> +
>> + ret = gnutls_hex_encode(&psk_key, key, &key_len);
>> + if (ret < 0) {
>> + gnutls_free(psk_key.data);
>> + virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> + _("Hex encoding of a PSK key failed"));
>> + return -1;
>> + }
>> +
>> + priv->migTLSPSK = g_strdup(key);
>> + mig->tlsPSK = g_steal_pointer(&key);
>> + mig->flags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
>> +
>> + gnutls_free(psk_key.data);
>> + return 0;
>> +}
>> +
>> +
>> static void
>> qemuMigrationCookieGraphicsXMLFormat(virBuffer *buf,
>> qemuMigrationCookieGraphics *grap)
>> @@ -890,6 +941,9 @@ qemuMigrationCookieXMLFormat(virQEMUDriver *driver,
>> if (mig->flags & QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS)
>> qemuMigrationCookieBlockDirtyBitmapsFormat(buf,
>> mig->blockDirtyBitmaps);
>>
>> + if ((mig->flags & QEMU_MIGRATION_COOKIE_TLS_PSK) && mig->tlsPSK)
>> + virBufferAsprintf(buf, "<migration-key>%s</migration-key>\n",
>> mig->tlsPSK);
>> +
>> virBufferAdjustIndent(buf, -2);
>> virBufferAddLit(buf, "</qemu-migration>\n");
>> return 0;
>> @@ -1396,6 +1450,12 @@ qemuMigrationCookieXMLParse(qemuMigrationCookie *mig,
>> qemuMigrationCookieBlockDirtyBitmapsParse(ctxt, mig) < 0)
>> return -1;
>>
>> + if (flags & QEMU_MIGRATION_COOKIE_TLS_PSK) {
>> + mig->tlsPSK = virXPathString("string(./migration-key[1])", ctxt);
>> + if (mig->tlsPSK)
>> + mig->flags |= QEMU_MIGRATION_COOKIE_TLS_PSK;
>> + }
>> +
>> return 0;
>> }
>>
>> @@ -1471,14 +1531,29 @@ qemuMigrationCookieFormat(qemuMigrationCookie *mig,
>> qemuMigrationCookieAddCaps(mig, dom, party) < 0)
>> return -1;
>>
>> + if (flags & QEMU_MIGRATION_COOKIE_TLS_PSK) {
>> + switch (party) {
>> + case QEMU_MIGRATION_SOURCE:
>> + if (qemuMigrationCookieAddTLSPSK(mig, driver, dom) < 0)
>> + return -1;
>> + break;
>> + case QEMU_MIGRATION_DESTINATION:
>> + if (!virQEMUCapsGet(priv->qemuCaps,
>> QEMU_CAPS_OBJECT_TLS_CREDS_PSK)) {
>
> IMO any sane build of qemu must have TLS enabled nowadays. I wonder if
> it even makes sense to have this capability. If tls can be compiled out,
> techincally it'd mean that there could be such a setup but IMO that
> would be still insane to have a unencrypted connection.
>
> I'd be okay just assuming that any qemu we support has TLS support. On
> the offchance that someone uses an insane build they will get an error,
> but I don't think I'd care for such setups.
IIUC, Libvirt will automatically enforce TLS-PSK encryption for migrations
between
two newer Libvirt hosts, even if the user does not specify the VIR_MIGRATE_TLS
flag.
Consequently, TLS-X509 would only be triggered during a forward migration from
an older
Libvirt host to a newer Libvirt destination (when VIR_MIGRATE_TLS is set). In
that scenario,
the older source wouldn't include the pre-shared key in the migration cookie,
naturally
bypassing the PSK pathway.
I am assuming we do not need to account for newer-to-older migrations.
I wanted to ask a follow up question regarding this design:
Given that with encrypted migration, there will be a performance impact to the
live migrations of the VMs,
due to auxiliary encrypt and decrypt operations at the source and destination,
and also due to a lack of
MSG_ZEROCOPY support for encrypted migrations in QEMU. Some users might be
running VM migration
within a cluster of trusted nodes, for which encrypted migration might not be a
performant solution.
Can we gate this behaviour of by-default enablement of PSK behind a
configuration parameter in qemu.conf.
This design is as follows:
1. If the configuration parameter is set we do it as per our above discussed
design.
2. If the configuration parameter is not set, then we attempt PSK if
VIR_MIGRATE_TLS flag
is specified.
>
>
>> + if (mig->tlsPSK)
>> + virSecureEraseString(mig->tlsPSK);
>> + mig->flags &= ~QEMU_MIGRATION_COOKIE_TLS_PSK;
>> + g_clear_pointer(&mig->tlsPSK, g_free);
>> + }
>> + break;
>> + }
>> + }
>> +
>> if (qemuMigrationCookieXMLFormat(driver, priv->qemuCaps, &buf, mig) < 0)
>> return -1;
>>
>> *cookieoutlen = virBufferUse(&buf) + 1;
>> *cookieout = virBufferContentAndReset(&buf);
>>
>> - VIR_DEBUG("cookielen=%d cookie=%s", *cookieoutlen, *cookieout);
>> -
>> return 0;
>> }
>>
>> diff --git a/src/qemu/qemu_migration_cookie.h
>> b/src/qemu/qemu_migration_cookie.h
>> index 254372234d..fd3b4c5a56 100644
>> --- a/src/qemu/qemu_migration_cookie.h
>> +++ b/src/qemu/qemu_migration_cookie.h
>> @@ -35,6 +35,7 @@ typedef enum {
>> QEMU_MIGRATION_COOKIE_FLAG_ALLOW_REBOOT,
>> QEMU_MIGRATION_COOKIE_FLAG_CAPS,
>> QEMU_MIGRATION_COOKIE_FLAG_BLOCK_DIRTY_BITMAPS,
>> + QEMU_MIGRATION_COOKIE_FLAG_TLS_PSK,
>>
>> QEMU_MIGRATION_COOKIE_FLAG_LAST
>> } qemuMigrationCookieFlags;
>> @@ -53,6 +54,7 @@ typedef enum {
>> QEMU_MIGRATION_COOKIE_CPU = (1 << QEMU_MIGRATION_COOKIE_FLAG_CPU),
>> QEMU_MIGRATION_COOKIE_CAPS = (1 << QEMU_MIGRATION_COOKIE_FLAG_CAPS),
>> QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS = (1 <<
>> QEMU_MIGRATION_COOKIE_FLAG_BLOCK_DIRTY_BITMAPS),
>> + QEMU_MIGRATION_COOKIE_TLS_PSK = (1 <<
>> QEMU_MIGRATION_COOKIE_FLAG_TLS_PSK),
>> } qemuMigrationCookieFeatures;
>>
>> typedef struct _qemuMigrationCookieGraphics qemuMigrationCookieGraphics;
>> @@ -171,6 +173,9 @@ struct _qemuMigrationCookie {
>>
>> /* If flags & QEMU_MIGRATION_COOKIE_BLOCK_DIRTY_BITMAPS */
>> GSList *blockDirtyBitmaps;
>> +
>> + /* If flags & QEMU_MIGRATION_COOKIE_TLS_PSK */
>> + char *tlsPSK;
>> };