diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 405e1184af..b68da3db5f 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -695,6 +695,7 @@ qemuBuildRBDSecinfoURI(virBufferPtr buf, * @tlspath: path to the TLS credentials * @listen: boolen listen for client or server setting * @verifypeer: boolean to enable peer verification (form of authorization) + * @secalias: if one exists, the alias of the security object for passwordid * @qemuCaps: capabilities * @propsret: json properties to return * @@ -706,6 +707,7 @@ int qemuBuildTLSx509BackendProps(const char *tlspath, bool isListen, bool verifypeer, + const char *secalias, virQEMUCapsPtr qemuCaps, virJSONValuePtr *propsret) { @@ -731,6 +733,10 @@ qemuBuildTLSx509BackendProps(const char *tlspath, NULL) < 0) goto cleanup; + if (secalias && + virJSONValueObjectAdd(*propsret, "s:passwordid", secalias, NULL) < 0) + goto cleanup; + ret = 0; cleanup: @@ -745,6 +751,7 @@ qemuBuildTLSx509BackendProps(const char *tlspath, * @tlspath: path to the TLS credentials * @listen: boolen listen for client or server setting * @verifypeer: boolean to enable peer verification (form of authorization) + * @addpasswordid: boolean to handle adding passwordid to object * @inalias: Alias for the parent to generate object alias * @qemuCaps: capabilities * @@ -757,6 +764,7 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, const char *tlspath, bool isListen, bool verifypeer, + bool addpasswordid, const char *inalias, virQEMUCapsPtr qemuCaps) { @@ -764,11 +772,16 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, char *objalias = NULL; virJSONValuePtr props = NULL; char *tmp = NULL; + char *secalias = NULL; - if (qemuBuildTLSx509BackendProps(tlspath, isListen, verifypeer, - qemuCaps, &props) < 0) + if (addpasswordid && + !(secalias = qemuDomainGetSecretAESAlias(inalias, false))) return -1; + if (qemuBuildTLSx509BackendProps(tlspath, isListen, verifypeer, secalias, + qemuCaps, &props) < 0) + goto cleanup; + if (!(objalias = qemuAliasTLSObjFromChardevAlias(inalias))) goto cleanup; @@ -784,6 +797,7 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, virJSONValueFree(props); VIR_FREE(objalias); VIR_FREE(tmp); + VIR_FREE(secalias); return ret; } @@ -4957,11 +4971,23 @@ qemuBuildChrChardevStr(virLogManagerPtr logManager, virBufferAdd(&buf, nowait ? ",server,nowait" : ",server", -1); if (dev->data.tcp.haveTLS == VIR_TRISTATE_BOOL_YES) { + qemuDomainChrSourcePrivatePtr chrSourcePriv = + QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev); char *objalias = NULL; + /* Add the secret object first if necessary. The + * secinfo is added only to a TCP serial device during + * qemuDomainSecretChardevPrepare. Subsequently called + * functions can just check the config fields */ + if (chrSourcePriv && chrSourcePriv->secinfo && + qemuBuildObjectSecretCommandLine(cmd, + chrSourcePriv->secinfo) < 0) + goto error; + if (qemuBuildTLSx509CommandLine(cmd, cfg->chardevTLSx509certdir, dev->data.tcp.listen, cfg->chardevTLSx509verify, + !!cfg->chardevTLSx509secretUUID, charAlias, qemuCaps) < 0) goto error; diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index 2f2a6ff877..a793fb6c78 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -69,6 +69,7 @@ int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, int qemuBuildTLSx509BackendProps(const char *tlspath, bool isListen, bool verifypeer, + const char *secalias, virQEMUCapsPtr qemuCaps, virJSONValuePtr *propsret); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 94f793e8ce..838e838bad 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -1042,7 +1042,8 @@ qemuDomainSecretSetup(virConnectPtr conn, if (virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && (secretUsageType == VIR_SECRET_USAGE_TYPE_CEPH || - secretUsageType == VIR_SECRET_USAGE_TYPE_VOLUME)) { + secretUsageType == VIR_SECRET_USAGE_TYPE_VOLUME || + secretUsageType == VIR_SECRET_USAGE_TYPE_TLS)) { if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, secretUsageType, username, seclookupdef, isLuks) < 0) @@ -1220,6 +1221,93 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn, } +/* qemuDomainSecretChardevDestroy: + * @disk: Pointer to a chardev definition + * + * Clear and destroy memory associated with the secret + */ +void +qemuDomainSecretChardevDestroy(virDomainChrSourceDefPtr dev) +{ + qemuDomainChrSourcePrivatePtr chrSourcePriv = + QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev); + + if (!chrSourcePriv || !chrSourcePriv->secinfo) + return; + + qemuDomainSecretInfoFree(&chrSourcePriv->secinfo); +} + + +/* qemuDomainSecretChardevPrepare: + * @conn: Pointer to connection + * @cfg: Pointer to driver config object + * @priv: pointer to domain private object + * @chrAlias: Alias of the chr device + * @dev: Pointer to a char source definition + * + * For a TCP character device, generate a qemuDomainSecretInfo to be used + * by the command line code to generate the secret for the tls-creds to use. + * + * Returns 0 on success, -1 on failure + */ +int +qemuDomainSecretChardevPrepare(virConnectPtr conn, + virQEMUDriverConfigPtr cfg, + qemuDomainObjPrivatePtr priv, + const char *chrAlias, + virDomainChrSourceDefPtr dev) +{ + virSecretLookupTypeDef seclookupdef = {0}; + qemuDomainSecretInfoPtr secinfo = NULL; + char *charAlias = NULL; + + if (dev->type != VIR_DOMAIN_CHR_TYPE_TCP) + return 0; + + if (dev->data.tcp.haveTLS == VIR_TRISTATE_BOOL_YES && + cfg->chardevTLSx509secretUUID) { + qemuDomainChrSourcePrivatePtr chrSourcePriv = + QEMU_DOMAIN_CHR_SOURCE_PRIVATE(dev); + + if (virUUIDParse(cfg->chardevTLSx509secretUUID, + seclookupdef.u.uuid) < 0) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("malformed chardev TLS secret uuid in qemu.conf")); + goto error; + } + seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; + + if (VIR_ALLOC(secinfo) < 0) + goto error; + + if (!(charAlias = qemuAliasChardevFromDevAlias(chrAlias))) + goto error; + + if (qemuDomainSecretSetup(conn, priv, secinfo, charAlias, + VIR_SECRET_USAGE_TYPE_TLS, NULL, + &seclookupdef, false) < 0) + goto error; + + if (secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("TLS X.509 requires encrypted secrets " + "to be supported")); + goto error; + } + + chrSourcePriv->secinfo = secinfo; + } + + VIR_FREE(charAlias); + return 0; + + error: + qemuDomainSecretInfoFree(&secinfo); + return -1; +} + + /* qemuDomainSecretDestroy: * @vm: Domain object * @@ -1236,11 +1324,38 @@ qemuDomainSecretDestroy(virDomainObjPtr vm) for (i = 0; i < vm->def->nhostdevs; i++) qemuDomainSecretHostdevDestroy(vm->def->hostdevs[i]); + + for (i = 0; i < vm->def->nserials; i++) + qemuDomainSecretChardevDestroy(vm->def->serials[i]->source); + + for (i = 0; i < vm->def->nparallels; i++) + qemuDomainSecretChardevDestroy(vm->def->parallels[i]->source); + + for (i = 0; i < vm->def->nchannels; i++) + qemuDomainSecretChardevDestroy(vm->def->channels[i]->source); + + for (i = 0; i < vm->def->nconsoles; i++) + qemuDomainSecretChardevDestroy(vm->def->consoles[i]->source); + + for (i = 0; i < vm->def->nsmartcards; i++) { + if (vm->def->smartcards[i]->type == + VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH) + qemuDomainSecretChardevDestroy(vm->def->smartcards[i]->data.passthru); + } + + for (i = 0; i < vm->def->nrngs; i++) { + if (vm->def->rngs[i]->backend == VIR_DOMAIN_RNG_BACKEND_EGD) + qemuDomainSecretChardevDestroy(vm->def->rngs[i]->source.chardev); + } + + for (i = 0; i < vm->def->nredirdevs; i++) + qemuDomainSecretChardevDestroy(vm->def->redirdevs[i]->source); } /* qemuDomainSecretPrepare: * @conn: Pointer to connection + * @driver: Pointer to driver object * @vm: Domain object * * For any objects that may require an auth/secret setup, create a @@ -1253,23 +1368,81 @@ qemuDomainSecretDestroy(virDomainObjPtr vm) */ int qemuDomainSecretPrepare(virConnectPtr conn, + virQEMUDriverPtr driver, virDomainObjPtr vm) { qemuDomainObjPrivatePtr priv = vm->privateData; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); size_t i; + int ret = -1; for (i = 0; i < vm->def->ndisks; i++) { if (qemuDomainSecretDiskPrepare(conn, priv, vm->def->disks[i]) < 0) - return -1; + goto cleanup; } for (i = 0; i < vm->def->nhostdevs; i++) { if (qemuDomainSecretHostdevPrepare(conn, priv, vm->def->hostdevs[i]) < 0) - return -1; + goto cleanup; } - return 0; + for (i = 0; i < vm->def->nserials; i++) { + if (qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->serials[i]->info.alias, + vm->def->serials[i]->source) < 0) + goto cleanup; + } + + for (i = 0; i < vm->def->nparallels; i++) { + if (qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->parallels[i]->info.alias, + vm->def->parallels[i]->source) < 0) + goto cleanup; + } + + for (i = 0; i < vm->def->nchannels; i++) { + if (qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->channels[i]->info.alias, + vm->def->channels[i]->source) < 0) + goto cleanup; + } + + for (i = 0; i < vm->def->nconsoles; i++) { + if (qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->consoles[i]->info.alias, + vm->def->consoles[i]->source) < 0) + goto cleanup; + } + + for (i = 0; i < vm->def->nsmartcards; i++) + if (vm->def->smartcards[i]->type == + VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH && + qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->smartcards[i]->info.alias, + vm->def->smartcards[i]->data.passthru) < 0) + goto cleanup; + + for (i = 0; i < vm->def->nrngs; i++) { + if (vm->def->rngs[i]->backend == VIR_DOMAIN_RNG_BACKEND_EGD && + qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->rngs[i]->info.alias, + vm->def->rngs[i]->source.chardev) < 0) + goto cleanup; + } + + for (i = 0; i < vm->def->nredirdevs; i++) { + if (qemuDomainSecretChardevPrepare(conn, cfg, priv, + vm->def->redirdevs[i]->info.alias, + vm->def->redirdevs[i]->source) < 0) + goto cleanup; + } + + ret = 0; + + cleanup: + virObjectUnref(cfg); + return ret; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index d06ea88241..2ee1829c8e 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -731,11 +731,24 @@ int qemuDomainSecretHostdevPrepare(virConnectPtr conn, virDomainHostdevDefPtr hostdev) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); +void qemuDomainSecretChardevDestroy(virDomainChrSourceDefPtr dev) + ATTRIBUTE_NONNULL(1); + +int qemuDomainSecretChardevPrepare(virConnectPtr conn, + virQEMUDriverConfigPtr cfg, + qemuDomainObjPrivatePtr priv, + const char *chrAlias, + virDomainChrSourceDefPtr dev) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3) + ATTRIBUTE_NONNULL(4) ATTRIBUTE_NONNULL(5); + void qemuDomainSecretDestroy(virDomainObjPtr vm) ATTRIBUTE_NONNULL(1); -int qemuDomainSecretPrepare(virConnectPtr conn, virDomainObjPtr vm) - ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2); +int qemuDomainSecretPrepare(virConnectPtr conn, + virQEMUDriverPtr driver, + virDomainObjPtr vm) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); int qemuDomainDefValidateDiskLunSource(const virStorageSource *src) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 5e313e3972..1fea53f555 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1489,6 +1489,7 @@ qemuDomainGetChardevTLSObjects(virQEMUDriverConfigPtr cfg, if (qemuBuildTLSx509BackendProps(cfg->chardevTLSx509certdir, dev->data.tcp.listen, cfg->chardevTLSx509verify, + NULL, priv->qemuCaps, tlsProps) < 0) return -1; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 33b78b1c6c..1b67aee727 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -5157,8 +5157,11 @@ qemuProcessPrepareDomain(virConnectPtr conn, if (qemuDomainMasterKeyCreate(vm) < 0) goto cleanup; - VIR_DEBUG("Add secrets to disks and hostdevs"); - if (qemuDomainSecretPrepare(conn, vm) < 0) + VIR_DEBUG("Prepare chardev source backends for TLS"); + qemuDomainPrepareChardevSource(vm->def, driver); + + VIR_DEBUG("Add secrets to disks, hostdevs, and chardevs"); + if (qemuDomainSecretPrepare(conn, driver, vm) < 0) goto cleanup; for (i = 0; i < vm->def->nchannels; i++) { @@ -5167,8 +5170,6 @@ qemuProcessPrepareDomain(virConnectPtr conn, goto cleanup; } - qemuDomainPrepareChardevSource(vm->def, driver); - if (VIR_ALLOC(priv->monConfig) < 0) goto cleanup; diff --git a/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.args b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.args new file mode 100644 index 0000000000..7f9fedb6c2 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.args @@ -0,0 +1,38 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name QEMUGuest1 \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-QEMUGuest1/master-key.aes \ +-M pc \ +-m 214 \ +-smp 1,sockets=1,cores=1,threads=1 \ +-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \ +-nographic \ +-nodefconfig \ +-nodefaults \ +-chardev socket,id=charmonitor,path=/tmp/lib/domain--1-QEMUGuest1/monitor.sock,\ +server,nowait \ +-mon chardev=charmonitor,id=monitor,mode=readline \ +-no-acpi \ +-boot c \ +-usb \ +-drive file=/dev/HostVG/QEMUGuest1,format=raw,if=none,id=drive-ide0-0-0 \ +-device ide-drive,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0 \ +-chardev udp,id=charserial0,host=127.0.0.1,port=2222,localaddr=127.0.0.1,\ +localport=1111 \ +-device isa-serial,chardev=charserial0,id=serial0 \ +-object secret,id=charserial1-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-object tls-creds-x509,id=objcharserial1_tls0,dir=/etc/pki/libvirt-chardev,\ +endpoint=client,verify-peer=no,passwordid=charserial1-secret0 \ +-chardev socket,id=charserial1,host=127.0.0.1,port=5555,\ +tls-creds=objcharserial1_tls0 \ +-device isa-serial,chardev=charserial1,id=serial1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml new file mode 100644 index 0000000000..832e2a2c8b --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml @@ -0,0 +1,50 @@ + + QEMUGuest1 + c7a5fdbd-edaf-9455-926a-d65c16db1809 + 219136 + 219136 + 1 + + hvm + + + + destroy + restart + destroy + + /usr/bin/qemu + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 4126388316..8e7cac29d8 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1170,6 +1170,23 @@ mymain(void) DO_TEST("serial-tcp-tlsx509-chardev-notls", QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG, QEMU_CAPS_OBJECT_TLS_CREDS_X509); + VIR_FREE(driver.config->chardevTLSx509certdir); + if (VIR_STRDUP_QUIET(driver.config->chardevTLSx509certdir, "/etc/pki/libvirt-chardev") < 0) + return EXIT_FAILURE; + if (VIR_STRDUP_QUIET(driver.config->chardevTLSx509secretUUID, + "6fd3f62d-9fe7-4a4e-a869-7acd6376d8ea") < 0) + return EXIT_FAILURE; +# ifdef HAVE_GNUTLS_CIPHER_ENCRYPT + DO_TEST("serial-tcp-tlsx509-secret-chardev", + QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG, + QEMU_CAPS_OBJECT_SECRET, + QEMU_CAPS_OBJECT_TLS_CREDS_X509); +# else + DO_TEST_FAILURE("serial-tcp-tlsx509-secret-chardev", + QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG, + QEMU_CAPS_OBJECT_SECRET, + QEMU_CAPS_OBJECT_TLS_CREDS_X509); +# endif driver.config->chardevTLS = 0; VIR_FREE(driver.config->chardevTLSx509certdir); DO_TEST("serial-many-chardev",