[libvirt] [PATCH v4 00/14] Add native TLS encrypted chardev TCP support

v3: http://www.redhat.com/archives/libvir-list/2016-June/msg01094.html Yes, I know another long series, but there are some duplicates with the LUKS series: http://www.redhat.com/archives/libvir-list/2016-June/msg01691.html In particular: Patches 1-3 match the LUKS patches 1-3 Patches 4-6 match the LUKS patches 12-14 They are needed for "parts" for this series. Changes since v3: Patches 7-10 are patches 1-4 of the v3 series with perhaps a few adjustments these were reviewed and had partial ACK's see the v3 series cover for details Patches 11->14 are NEW. Patch 11 needs the "passphrase" secret from LUKS in order to allow the addition of a <secret> for a <serial type='tcp'...> to provide the passphrase for the TLS environment. Patch 12 provides the means (like Disk and Hostdev) to store the secinfo required to generate an AES secret Patch 13 adds the secret for the command line startup Patch 14 could be combined w/ 13, but I just kept it a separate way to add the secret for the hotplug (and unplug) John Ferlan (14): qemu: Change protocol parameter for secret setup qemu: Remove authdef from secret setup conf: Add new secret type "passphrase" qemu: Remove type from qemuBuildSecretInfoProps qemu: Make qemuBuildSecretInfoProps global qemu: Add secinfo for hotplug virtio disk conf: Add new default TLS X.509 certificate default directory conf: Introduce chartcp_tls_x509_cert_dir qemu: Add support for TLS X.509 path to TCP chardev backend qemu: Add the ability to hotplug the TLS X.509 environment conf: Add new secret element for tcp chardev qemu: Introduce qemuDomainChardevPrivatePtr qemu: Add a secret object to/for a chardev tcp with secret qemu: Add the ability to hotplug a secret object for TCP chardev TLS docs/aclpolkit.html.in | 4 + docs/formatdomain.html.in | 29 +++ docs/formatsecret.html.in | 57 +++++- docs/schemas/domaincommon.rng | 21 +++ docs/schemas/secret.rng | 10 + include/libvirt/libvirt-secret.h | 3 +- src/access/viraccessdriverpolkit.c | 13 ++ src/conf/domain_conf.c | 62 ++++++- src/conf/domain_conf.h | 8 +- src/conf/secret_conf.c | 26 ++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 + src/libxl/libxl_domain.c | 2 +- src/lxc/lxc_native.c | 2 +- src/qemu/libvirtd_qemu.aug | 11 +- src/qemu/qemu.conf | 83 +++++++-- src/qemu/qemu_command.c | 148 ++++++++++++++- src/qemu/qemu_command.h | 13 ++ src/qemu/qemu_conf.c | 59 +++++- src/qemu/qemu_conf.h | 7 + src/qemu/qemu_domain.c | 202 +++++++++++++++------ src/qemu/qemu_domain.h | 22 +++ src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_hotplug.c | 141 ++++++++++++-- src/qemu/qemu_hotplug.h | 3 +- src/qemu/qemu_monitor_json.c | 9 + src/qemu/qemu_parse_command.c | 4 +- src/qemu/qemu_process.c | 2 +- src/qemu/test_libvirtd_qemu.aug.in | 5 + src/vz/vz_sdk.c | 2 +- src/xenconfig/xen_sxpr.c | 2 +- tests/qemuhotplugtest.c | 2 +- .../qemuxml2argv-serial-tcp-tlsx509-chardev.args | 33 ++++ .../qemuxml2argv-serial-tcp-tlsx509-chardev.xml | 41 +++++ ...xml2argv-serial-tcp-tlsx509-secret-chardev.args | 38 ++++ ...uxml2argv-serial-tcp-tlsx509-secret-chardev.xml | 42 +++++ tests/qemuxml2argvtest.c | 14 ++ .../qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml | 50 +++++ ...ml2xmlout-serial-tcp-tlsx509-secret-chardev.xml | 51 ++++++ tests/qemuxml2xmltest.c | 2 + tests/secretxml2xmlin/usage-passphrase.xml | 7 + tests/secretxml2xmltest.c | 1 + 42 files changed, 1116 insertions(+), 123 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-secret-chardev.xml create mode 100644 tests/secretxml2xmlin/usage-passphrase.xml -- 2.5.5

Rather than assume/pass the protocol to the qemuDomainSecretPlainSetup and qemuDomainSecretAESSetup, set and pass the secretUsageType based on the src->protocol type. This will eventually be used by the virSecretGetSecretString call Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_domain.c | 62 ++++++++++++++++---------------------------------- 1 file changed, 20 insertions(+), 42 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 4a5378f..ca49db1 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -814,7 +814,7 @@ qemuDomainHostdevPrivateDispose(void *obj) /* qemuDomainSecretPlainSetup: * @conn: Pointer to connection * @secinfo: Pointer to secret info - * @protocol: Protocol for secret + * @secretUsageType: The virSecretUsageType * @authdef: Pointer to auth data * * Taking a secinfo, fill in the plaintext information @@ -824,19 +824,15 @@ qemuDomainHostdevPrivateDispose(void *obj) static int qemuDomainSecretPlainSetup(virConnectPtr conn, qemuDomainSecretInfoPtr secinfo, - virStorageNetProtocol protocol, + virSecretUsageType secretUsageType, virStorageAuthDefPtr authdef) { - int secretType = VIR_SECRET_USAGE_TYPE_ISCSI; - secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN; if (VIR_STRDUP(secinfo->s.plain.username, authdef->username) < 0) return -1; - if (protocol == VIR_STORAGE_NET_PROTOCOL_RBD) - secretType = VIR_SECRET_USAGE_TYPE_CEPH; - - return virSecretGetSecretString(conn, &authdef->seclookupdef, secretType, + return virSecretGetSecretString(conn, &authdef->seclookupdef, + secretUsageType, &secinfo->s.plain.secret, &secinfo->s.plain.secretlen); } @@ -847,7 +843,7 @@ qemuDomainSecretPlainSetup(virConnectPtr conn, * @priv: pointer to domain private object * @secinfo: Pointer to secret info * @srcalias: Alias of the disk/hostdev used to generate the secret alias - * @protocol: Protocol for secret + * @secretUsageType: The virSecretUsageType * @authdef: Pointer to auth data * * Taking a secinfo, fill in the AES specific information using the @@ -859,7 +855,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, qemuDomainObjPrivatePtr priv, qemuDomainSecretInfoPtr secinfo, const char *srcalias, - virStorageNetProtocol protocol, + virSecretUsageType secretUsageType, virStorageAuthDefPtr authdef) { int ret = -1; @@ -869,34 +865,11 @@ qemuDomainSecretAESSetup(virConnectPtr conn, size_t secretlen = 0; uint8_t *ciphertext = NULL; size_t ciphertextlen = 0; - int secretType = VIR_SECRET_USAGE_TYPE_NONE; secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES; if (VIR_STRDUP(secinfo->s.aes.username, authdef->username) < 0) return -1; - switch ((virStorageNetProtocol)protocol) { - case VIR_STORAGE_NET_PROTOCOL_RBD: - secretType = VIR_SECRET_USAGE_TYPE_CEPH; - break; - - case VIR_STORAGE_NET_PROTOCOL_NONE: - case VIR_STORAGE_NET_PROTOCOL_NBD: - case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: - case VIR_STORAGE_NET_PROTOCOL_GLUSTER: - case VIR_STORAGE_NET_PROTOCOL_ISCSI: - case VIR_STORAGE_NET_PROTOCOL_HTTP: - case VIR_STORAGE_NET_PROTOCOL_HTTPS: - case VIR_STORAGE_NET_PROTOCOL_FTP: - case VIR_STORAGE_NET_PROTOCOL_FTPS: - case VIR_STORAGE_NET_PROTOCOL_TFTP: - case VIR_STORAGE_NET_PROTOCOL_LAST: - virReportError(VIR_ERR_INTERNAL_ERROR, - _("protocol '%s' cannot be used for encrypted secrets"), - virStorageNetProtocolTypeToString(protocol)); - return -1; - } - if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias))) return -1; @@ -909,7 +882,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, goto cleanup; /* Grab the unencoded secret */ - if (virSecretGetSecretString(conn, &authdef->seclookupdef, secretType, + if (virSecretGetSecretString(conn, &authdef->seclookupdef, secretUsageType, &secret, &secretlen) < 0) goto cleanup; @@ -943,7 +916,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, * @priv: pointer to domain private object * @secinfo: Pointer to secret info * @srcalias: Alias of the disk/hostdev used to generate the secret alias - * @protocol: Protocol for secret + * @secretUsageType: The virSecretUsageType * @authdef: Pointer to auth data * * If we have the encryption API present and can support a secret object, then @@ -958,17 +931,18 @@ qemuDomainSecretSetup(virConnectPtr conn, qemuDomainObjPrivatePtr priv, qemuDomainSecretInfoPtr secinfo, const char *srcalias, - virStorageNetProtocol protocol, + virSecretUsageType secretUsageType, virStorageAuthDefPtr authdef) { if (virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && - protocol == VIR_STORAGE_NET_PROTOCOL_RBD) { - if (qemuDomainSecretAESSetup(conn, priv, secinfo, - srcalias, protocol, authdef) < 0) + secretUsageType == VIR_SECRET_USAGE_TYPE_CEPH) { + if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, + secretUsageType, authdef) < 0) return -1; } else { - if (qemuDomainSecretPlainSetup(conn, secinfo, protocol, authdef) < 0) + if (qemuDomainSecretPlainSetup(conn, secinfo, secretUsageType, + authdef) < 0) return -1; } return 0; @@ -1015,13 +989,17 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, (src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI || src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD)) { + virSecretUsageType secretUsageType = VIR_SECRET_USAGE_TYPE_ISCSI; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); if (VIR_ALLOC(secinfo) < 0) return -1; + if (src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD) + secretUsageType = VIR_SECRET_USAGE_TYPE_CEPH; + if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias, - src->protocol, src->auth) < 0) + secretUsageType, src->auth) < 0) goto error; diskPriv->secinfo = secinfo; @@ -1086,7 +1064,7 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn, return -1; if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias, - VIR_STORAGE_NET_PROTOCOL_ISCSI, + VIR_SECRET_USAGE_TYPE_ISCSI, iscsisrc->auth) < 0) goto error; -- 2.5.5

Rather than pass authdef, pass the 'authdef->username' and the '&authdef->secdef' Note that a username may be NULL. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_domain.c | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index ca49db1..dca8970 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -815,7 +815,8 @@ qemuDomainHostdevPrivateDispose(void *obj) * @conn: Pointer to connection * @secinfo: Pointer to secret info * @secretUsageType: The virSecretUsageType - * @authdef: Pointer to auth data + * @username: username to use for authentication (may be NULL) + * @seclookupdef: Pointer to seclookupdef data * * Taking a secinfo, fill in the plaintext information * @@ -825,14 +826,14 @@ static int qemuDomainSecretPlainSetup(virConnectPtr conn, qemuDomainSecretInfoPtr secinfo, virSecretUsageType secretUsageType, - virStorageAuthDefPtr authdef) + const char *username, + virSecretLookupTypeDefPtr seclookupdef) { secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_PLAIN; - if (VIR_STRDUP(secinfo->s.plain.username, authdef->username) < 0) + if (VIR_STRDUP(secinfo->s.plain.username, username) < 0) return -1; - return virSecretGetSecretString(conn, &authdef->seclookupdef, - secretUsageType, + return virSecretGetSecretString(conn, seclookupdef, secretUsageType, &secinfo->s.plain.secret, &secinfo->s.plain.secretlen); } @@ -844,7 +845,8 @@ qemuDomainSecretPlainSetup(virConnectPtr conn, * @secinfo: Pointer to secret info * @srcalias: Alias of the disk/hostdev used to generate the secret alias * @secretUsageType: The virSecretUsageType - * @authdef: Pointer to auth data + * @username: username to use for authentication (may be NULL) + * @seclookupdef: Pointer to seclookupdef data * * Taking a secinfo, fill in the AES specific information using the * @@ -856,7 +858,8 @@ qemuDomainSecretAESSetup(virConnectPtr conn, qemuDomainSecretInfoPtr secinfo, const char *srcalias, virSecretUsageType secretUsageType, - virStorageAuthDefPtr authdef) + const char *username, + virSecretLookupTypeDefPtr seclookupdef) { int ret = -1; uint8_t *raw_iv = NULL; @@ -867,7 +870,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, size_t ciphertextlen = 0; secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES; - if (VIR_STRDUP(secinfo->s.aes.username, authdef->username) < 0) + if (VIR_STRDUP(secinfo->s.aes.username, username) < 0) return -1; if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias))) @@ -882,7 +885,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, goto cleanup; /* Grab the unencoded secret */ - if (virSecretGetSecretString(conn, &authdef->seclookupdef, secretUsageType, + if (virSecretGetSecretString(conn, seclookupdef, secretUsageType, &secret, &secretlen) < 0) goto cleanup; @@ -917,7 +920,8 @@ qemuDomainSecretAESSetup(virConnectPtr conn, * @secinfo: Pointer to secret info * @srcalias: Alias of the disk/hostdev used to generate the secret alias * @secretUsageType: The virSecretUsageType - * @authdef: Pointer to auth data + * @username: username to use for authentication (may be NULL) + * @seclookupdef: Pointer to seclookupdef data * * If we have the encryption API present and can support a secret object, then * build the AES secret; otherwise, build the Plain secret. This is the magic @@ -932,17 +936,19 @@ qemuDomainSecretSetup(virConnectPtr conn, qemuDomainSecretInfoPtr secinfo, const char *srcalias, virSecretUsageType secretUsageType, - virStorageAuthDefPtr authdef) + const char *username, + virSecretLookupTypeDefPtr seclookupdef) { if (virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC) && virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && secretUsageType == VIR_SECRET_USAGE_TYPE_CEPH) { if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, - secretUsageType, authdef) < 0) + secretUsageType, username, + seclookupdef) < 0) return -1; } else { if (qemuDomainSecretPlainSetup(conn, secinfo, secretUsageType, - authdef) < 0) + username, seclookupdef) < 0) return -1; } return 0; @@ -999,7 +1005,8 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, secretUsageType = VIR_SECRET_USAGE_TYPE_CEPH; if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias, - secretUsageType, src->auth) < 0) + secretUsageType, src->auth->username, + &src->auth->seclookupdef) < 0) goto error; diskPriv->secinfo = secinfo; @@ -1065,7 +1072,8 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn, if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias, VIR_SECRET_USAGE_TYPE_ISCSI, - iscsisrc->auth) < 0) + iscsisrc->auth->username, + &iscsisrc->auth->seclookupdef) < 0) goto error; hostdevPriv->secinfo = secinfo; -- 2.5.5

Add a new secret type known as "passphrase" - it will handle adding the secret objects that need a passphrase without a specific username. The format is: <secret ...> <uuid>...</uuid> ... <usage type='passphrase'> <id>mumblyfratz</id> </usage> </secret> Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/aclpolkit.html.in | 4 +++ docs/formatsecret.html.in | 57 ++++++++++++++++++++++++++++-- docs/schemas/secret.rng | 10 ++++++ include/libvirt/libvirt-secret.h | 3 +- src/access/viraccessdriverpolkit.c | 13 +++++++ src/conf/secret_conf.c | 26 +++++++++++++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 +++ tests/secretxml2xmlin/usage-passphrase.xml | 7 ++++ tests/secretxml2xmltest.c | 1 + 10 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 tests/secretxml2xmlin/usage-passphrase.xml diff --git a/docs/aclpolkit.html.in b/docs/aclpolkit.html.in index dae0814..1d31b6d 100644 --- a/docs/aclpolkit.html.in +++ b/docs/aclpolkit.html.in @@ -224,6 +224,10 @@ <td>secret_usage_target</td> <td>Name of the associated iSCSI target, if any</td> </tr> + <tr> + <td>secret_usage_id</td> + <td>Name of be associated passphrase secret, if any</td> + </tr> </tbody> </table> diff --git a/docs/formatsecret.html.in b/docs/formatsecret.html.in index 599cb38..79c4082 100644 --- a/docs/formatsecret.html.in +++ b/docs/formatsecret.html.in @@ -41,8 +41,9 @@ <dd> Specifies what this secret is used for. A mandatory <code>type</code> attribute specifies the usage category, currently - only <code>volume</code>, <code>ceph</code> and <code>iscsi</code> - are defined. Specific usage categories are described below. + only <code>volume</code>, <code>ceph</code>, <code>iscsi</code>, + and <code>passphrase</code> are defined. Specific usage categories + are described below. </dd> </dl> @@ -241,5 +242,57 @@ <secret usage='libvirtiscsi'/> </auth> </pre> + + <h3><a name="passphraseUsageType">Usage type "passphrase"</a></h3> + + <p> + This secret is a general purpose secret to be used by various libvirt + objects to provide a single passphrase as required by the object in + order to perform its authentication. + <span class="since">Since 2.0.0</span>. The following is an example + of a secret.xml file: + </p> + + <pre> + # cat secret.xml + <secret ephemeral='no' private='yes'> + <description>sample passphrase secret</description> + <usage type='passphrase'> + <id>id_example</id> + </usage> + </secret> + + # virsh secret-define secret.xml + Secret 718c71bd-67b5-4a2b-87ec-a24e8ca200dc created + + # virsh secret-list + UUID Usage + ----------------------------------------------------------- + 718c71bd-67b5-4a2b-87ec-a24e8ca200dc passphrase id_example + # + + </pre> + + <p> + A secret may also be defined via the + <a href="html/libvirt-libvirt-secret.html#virSecretDefineXML"> + <code>virSecretDefineXML</code></a> API. + + Once the secret is defined, a secret value will need to be set. This + value would be the same used to create and use the volume. + The following is a simple example of using + <code>virsh secret-set-value</code> to set the secret value. The + <a href="html/libvirt-libvirt-secret.html#virSecretSetValue"> + <code>virSecretSetValue</code></a> API may also be used to set + a more secure secret without using printable/readable characters. + </p> + + <pre> + # MYSECRET=`printf %s "letmein" | base64` + # virsh secret-set-value 718c71bd-67b5-4a2b-87ec-a24e8ca200dc $MYSECRET + Secret value set + + </pre> + </body> </html> diff --git a/docs/schemas/secret.rng b/docs/schemas/secret.rng index e21e700..fc188ba 100644 --- a/docs/schemas/secret.rng +++ b/docs/schemas/secret.rng @@ -36,6 +36,7 @@ <ref name='usagevolume'/> <ref name='usageceph'/> <ref name='usageiscsi'/> + <ref name='usagepassphrase'/> <!-- More choices later --> </choice> </element> @@ -71,4 +72,13 @@ </element> </define> + <define name='usagepassphrase'> + <attribute name='type'> + <value>passphrase</value> + </attribute> + <element name='id'> + <ref name='genericName'/> + </element> + </define> + </grammar> diff --git a/include/libvirt/libvirt-secret.h b/include/libvirt/libvirt-secret.h index 3e5cdf6..55b11e0 100644 --- a/include/libvirt/libvirt-secret.h +++ b/include/libvirt/libvirt-secret.h @@ -4,7 +4,7 @@ * Description: Provides APIs for the management of secrets * Author: Daniel Veillard <veillard@redhat.com> * - * Copyright (C) 2006-2014 Red Hat, Inc. + * Copyright (C) 2006-2014, 2016 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -43,6 +43,7 @@ typedef enum { VIR_SECRET_USAGE_TYPE_VOLUME = 1, VIR_SECRET_USAGE_TYPE_CEPH = 2, VIR_SECRET_USAGE_TYPE_ISCSI = 3, + VIR_SECRET_USAGE_TYPE_PASSPHRASE = 4, # ifdef VIR_ENUM_SENTINELS VIR_SECRET_USAGE_TYPE_LAST diff --git a/src/access/viraccessdriverpolkit.c b/src/access/viraccessdriverpolkit.c index 89bc890..1f955f0 100644 --- a/src/access/viraccessdriverpolkit.c +++ b/src/access/viraccessdriverpolkit.c @@ -338,6 +338,19 @@ virAccessDriverPolkitCheckSecret(virAccessManagerPtr manager, virAccessPermSecretTypeToString(perm), attrs); } break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: { + const char *attrs[] = { + "connect_driver", driverName, + "secret_uuid", uuidstr, + "secret_usage_id", secret->usage.id, + NULL, + }; + + return virAccessDriverPolkitCheck(manager, + "secret", + virAccessPermSecretTypeToString(perm), + attrs); + } break; } } diff --git a/src/conf/secret_conf.c b/src/conf/secret_conf.c index de9e6cf..77477b6 100644 --- a/src/conf/secret_conf.c +++ b/src/conf/secret_conf.c @@ -29,6 +29,7 @@ #include "viralloc.h" #include "secret_conf.h" #include "virsecretobj.h" +#include "virstring.h" #include "virerror.h" #include "virxml.h" #include "viruuid.h" @@ -38,7 +39,7 @@ VIR_LOG_INIT("conf.secret_conf"); VIR_ENUM_IMPL(virSecretUsage, VIR_SECRET_USAGE_TYPE_LAST, - "none", "volume", "ceph", "iscsi") + "none", "volume", "ceph", "iscsi", "passphrase") const char * virSecretUsageIDForDef(virSecretDefPtr def) @@ -56,6 +57,9 @@ virSecretUsageIDForDef(virSecretDefPtr def) case VIR_SECRET_USAGE_TYPE_ISCSI: return def->usage.target; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + return def->usage.id; + default: return NULL; } @@ -85,6 +89,10 @@ virSecretDefFree(virSecretDefPtr def) VIR_FREE(def->usage.target); break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + VIR_FREE(def->usage.id); + break; + default: VIR_ERROR(_("unexpected secret usage type %d"), def->usage_type); break; @@ -92,6 +100,7 @@ virSecretDefFree(virSecretDefPtr def) VIR_FREE(def); } + static int virSecretDefParseUsage(xmlXPathContextPtr ctxt, virSecretDefPtr def) @@ -145,6 +154,14 @@ virSecretDefParseUsage(xmlXPathContextPtr ctxt, } break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + if (!(def->usage.id = virXPathString("string(./usage/id)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("passphrase usage specified, but id is missing")); + return -1; + } + break; + default: virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected secret usage type %d"), @@ -305,6 +322,13 @@ virSecretDefFormatUsage(virBufferPtr buf, } break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + if (def->usage.id != NULL) { + virBufferEscapeString(buf, "<id>%s</id>\n", + def->usage.id); + } + break; + default: virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected secret usage type %d"), diff --git a/src/conf/secret_conf.h b/src/conf/secret_conf.h index 4584403..768e6e9 100644 --- a/src/conf/secret_conf.h +++ b/src/conf/secret_conf.h @@ -40,6 +40,7 @@ struct _virSecretDef { char *volume; /* May be NULL */ char *ceph; char *target; + char *id; } usage; }; diff --git a/src/conf/virsecretobj.c b/src/conf/virsecretobj.c index c46d22c..a2b4801 100644 --- a/src/conf/virsecretobj.c +++ b/src/conf/virsecretobj.c @@ -237,6 +237,11 @@ virSecretObjSearchName(const void *payload, if (STREQ(secret->def->usage.target, data->usageID)) found = 1; break; + + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + if (STREQ(secret->def->usage.id, data->usageID)) + found = 1; + break; } cleanup: diff --git a/tests/secretxml2xmlin/usage-passphrase.xml b/tests/secretxml2xmlin/usage-passphrase.xml new file mode 100644 index 0000000..7ebe0a4 --- /dev/null +++ b/tests/secretxml2xmlin/usage-passphrase.xml @@ -0,0 +1,7 @@ +<secret ephemeral='no' private='no'> + <uuid>f52a81b2-424e-490c-823d-6bd4235bc572</uuid> + <description>Sample Passphrase Secret</description> + <usage type='passphrase'> + <id>mumblyfratz</id> + </usage> +</secret> diff --git a/tests/secretxml2xmltest.c b/tests/secretxml2xmltest.c index 8dcbb40..c444e4d 100644 --- a/tests/secretxml2xmltest.c +++ b/tests/secretxml2xmltest.c @@ -80,6 +80,7 @@ mymain(void) DO_TEST("usage-volume"); DO_TEST("usage-ceph"); DO_TEST("usage-iscsi"); + DO_TEST("usage-passphrase"); return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } -- 2.5.5

It's just a constant "secret" string anyway Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 4fdb410..9331e65 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -510,7 +510,6 @@ qemuNetworkDriveGetPort(int protocol, /** * qemuBuildSecretInfoProps: * @secinfo: pointer to the secret info object - * @type: returns a pointer to a character string for object name * @props: json properties to return * * Build the JSON properties for the secret info type. @@ -520,14 +519,11 @@ qemuNetworkDriveGetPort(int protocol, */ static int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, - const char **type, virJSONValuePtr *propsret) { int ret = -1; char *keyid = NULL; - *type = "secret"; - if (!(keyid = qemuDomainGetMasterKeyAlias())) return -1; @@ -565,13 +561,12 @@ qemuBuildObjectSecretCommandLine(virCommandPtr cmd, { int ret = -1; virJSONValuePtr props = NULL; - const char *type; char *tmp = NULL; - if (qemuBuildSecretInfoProps(secinfo, &type, &props) < 0) + if (qemuBuildSecretInfoProps(secinfo, &props) < 0) return -1; - if (!(tmp = virQEMUBuildObjectCommandlineFromJSON(type, + if (!(tmp = virQEMUBuildObjectCommandlineFromJSON("secret", secinfo->s.aes.alias, props))) goto cleanup; -- 2.5.5

Need to create the object for a hotplug disk Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 2 +- src/qemu/qemu_command.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 9331e65..5d82a4d 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -517,7 +517,7 @@ qemuNetworkDriveGetPort(int protocol, * Returns 0 on success with the filled in JSON property; otherwise, * returns -1 on failure error message set. */ -static int +int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, virJSONValuePtr *propsret) { diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index 9ff4edb..c4d0567 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -61,6 +61,10 @@ virCommandPtr qemuBuildCommandLine(virQEMUDriverPtr driver, const char *domainLibDir) ATTRIBUTE_NONNULL(15); +/* Generate the object properties for a secret */ +int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, + virJSONValuePtr *propsret); + /* Generate '-device' string for chardev device */ int qemuBuildChrDeviceStr(char **deviceStr, -- 2.5.5

Commit id 'a1344f70a' added AES secret processing for RBD when starting up a guest. As such, when the hotplug code calls qemuDomainSecretDiskPrepare an AES secret could be added to the disk about to be hotplugged. If an AES secret was added, then the hotplug code would need to generate the secret object because qemuBuildDriveStr would add the "password-secret=" to the returned 'driveStr' rather than the base64 encoded password. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_hotplug.c | 44 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index f695903..e4cbbf0 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -310,6 +310,9 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, bool releaseaddr = false; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); const char *src = virDomainDiskGetSource(disk); + virJSONValuePtr secobjProps = NULL; + qemuDomainDiskPrivatePtr diskPriv; + qemuDomainSecretInfoPtr secinfo; if (!disk->info.type) { if (qemuDomainMachineIsS390CCW(vm->def) && @@ -342,6 +345,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error; + diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + secinfo = diskPriv->secinfo; + if (secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES) { + if (qemuBuildSecretInfoProps(secinfo, &secobjProps) < 0) + goto error; + } + if (!(drivestr = qemuBuildDriveStr(disk, false, priv->qemuCaps))) goto error; @@ -354,9 +364,15 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error; - /* Attach the device - 2 step process */ + /* Attach the device - possible 3 step process */ qemuDomainObjEnterMonitor(driver, vm); + if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", + secinfo->s.aes.alias, + secobjProps) < 0) + goto failaddobjsecret; + secobjProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -374,6 +390,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, ret = 0; cleanup: + virJSONValueFree(secobjProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -393,8 +410,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, } failadddrive: + if (secobjProps) + ignore_value(qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias)); + + failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) releaseaddr = false; + secobjProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -3389,6 +3411,8 @@ qemuDomainDetachVirtioDiskDevice(virQEMUDriverPtr driver, { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; + qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(detach); + qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; if (qemuIsMultiFunctionDevice(vm->def, &detach->info)) { virReportError(VIR_ERR_OPERATION_FAILED, @@ -3422,12 +3446,14 @@ qemuDomainDetachVirtioDiskDevice(virQEMUDriverPtr driver, qemuDomainMarkDeviceForRemoval(vm, &detach->info); qemuDomainObjEnterMonitor(driver, vm); - if (qemuMonitorDelDevice(priv->mon, detach->info.alias) < 0) { - if (qemuDomainObjExitMonitor(driver, vm) < 0) - goto cleanup; - virDomainAuditDisk(vm, detach->src, NULL, "detach", false); - goto cleanup; + if (secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES) { + if (qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias) < 0) + goto faildel; } + + if (qemuMonitorDelDevice(priv->mon, detach->info.alias) < 0) + goto faildel; + if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; @@ -3437,6 +3463,12 @@ qemuDomainDetachVirtioDiskDevice(virQEMUDriverPtr driver, cleanup: qemuDomainResetDeviceRemoval(vm); return ret; + + faildel: + if (qemuDomainObjExitMonitor(driver, vm) < 0) + goto cleanup; + virDomainAuditDisk(vm, detach->src, NULL, "detach", false); + goto cleanup; } static int -- 2.5.5

Rather than specify perhaps multiple TLS X.509 certificate directories, let's create a "default" directory which can then be used if the service (e.g. for now vnc and spice) does not supply a default directory. Since the default for vnc and spice may have existed before without being supplied, the default check will first check if the service specific path exists and if so, set the cfg entry to that; otherwise, the default will be set to the (now) new defaultTLSx509certdir. Additionally add a "default_tls_x509_verify" entry which can also be used to force the peer verification option (for vnc it's a x509verify option). Add/alter the macro for the option being found in the config file to accept the default value. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/libvirtd_qemu.aug | 6 ++++- src/qemu/qemu.conf | 55 +++++++++++++++++++++++++------------- src/qemu/qemu_conf.c | 53 +++++++++++++++++++++++++++++++----- src/qemu/qemu_conf.h | 3 +++ src/qemu/test_libvirtd_qemu.aug.in | 2 ++ 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 8bc23ba..06d9b98 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -24,6 +24,9 @@ module Libvirtd_qemu = (* Config entry grouped by function - same order as example config *) + let default_tls_entry = str_entry "default_tls_x509_cert_dir" + | bool_entry "default_tls_x509_verify" + let vnc_entry = str_entry "vnc_listen" | bool_entry "vnc_auto_unix_socket" | bool_entry "vnc_tls" @@ -93,7 +96,8 @@ module Libvirtd_qemu = let nvram_entry = str_array_entry "nvram" (* Each entry in the config is one of the following ... *) - let entry = vnc_entry + let entry = default_tls_entry + | vnc_entry | spice_entry | nogfx_entry | remote_display_entry diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index 7964273..fb6b843 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -2,6 +2,32 @@ # All settings described here are optional - if omitted, sensible # defaults are used. +# Use of TLS requires that x509 certificates be issued. The default is +# to keep them in /etc/pki/qemu. This directory must contain +# +# ca-cert.pem - the CA master certificate +# server-cert.pem - the server certificate signed with ca-cert.pem +# server-key.pem - the server private key +# +# and optionally may contain +# +# dh-params.pem - the DH params configuration file +# +#default_tls_x509_cert_dir = "/etc/pki/qemu" + + +# The default TLS configuration only uses certificates for the server +# allowing the client to verify the server's identity and establish +# an encrypted channel. +# +# It is possible to use x509 certificates for authentication too, by +# issuing a x509 certificate to every client who needs to connect. +# +# Enabling this option will reject any client who does not have a +# certificate signed by the CA in /etc/pki/qemu/ca-cert.pem +# +#default_tls_x509_verify = 1 + # VNC is configured to listen on 127.0.0.1 by default. # To make it listen on all public interfaces, uncomment # this next option. @@ -32,15 +58,10 @@ #vnc_tls = 1 -# Use of TLS requires that x509 certificates be issued. The -# default it to keep them in /etc/pki/libvirt-vnc. This directory -# must contain -# -# ca-cert.pem - the CA master certificate -# server-cert.pem - the server certificate signed with ca-cert.pem -# server-key.pem - the server private key -# -# This option allows the certificate directory to be changed +# In order to override the default TLS certificate location for +# vnc certificates, supply a valid path to the certificate directory. +# If the provided path does not exist then the default_tls_x509_cert_dir +# path will be used. # #vnc_tls_x509_cert_dir = "/etc/pki/libvirt-vnc" @@ -55,6 +76,9 @@ # Enabling this option will reject any client who does not have a # certificate signed by the CA in /etc/pki/libvirt-vnc/ca-cert.pem # +# If this option is not supplied, it will be set to the value of +# "default_tls_x509_verify". +# #vnc_tls_x509_verify = 1 @@ -117,15 +141,10 @@ #spice_tls = 1 -# Use of TLS requires that x509 certificates be issued. The -# default it to keep them in /etc/pki/libvirt-spice. This directory -# must contain -# -# ca-cert.pem - the CA master certificate -# server-cert.pem - the server certificate signed with ca-cert.pem -# server-key.pem - the server private key -# -# This option allows the certificate directory to be changed. +# In order to override the default TLS certificate location for +# spice certificates, supply a valid path to the certificate directory. +# If the provided path does not exist then the default_tls_x509_cert_dir +# path will be used. # #spice_tls_x509_cert_dir = "/etc/pki/libvirt-spice" diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 6dfa738..3091843 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -236,19 +236,44 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) if (virAsprintf(&cfg->autostartDir, "%s/qemu/autostart", cfg->configBaseDir) < 0) goto error; - - if (VIR_STRDUP(cfg->vncListen, "127.0.0.1") < 0) + /* Set the default directory to find TLS X.509 certificates. + * This will then be used as a fallback if the service specific + * directory doesn't exist (although we don't check if this exists). + */ + if (VIR_STRDUP(cfg->defaultTLSx509certdir, + SYSCONFDIR "/pki/qemu") < 0) goto error; - if (VIR_STRDUP(cfg->vncTLSx509certdir, SYSCONFDIR "/pki/libvirt-vnc") < 0) + if (VIR_STRDUP(cfg->vncListen, "127.0.0.1") < 0) goto error; if (VIR_STRDUP(cfg->spiceListen, "127.0.0.1") < 0) goto error; - if (VIR_STRDUP(cfg->spiceTLSx509certdir, - SYSCONFDIR "/pki/libvirt-spice") < 0) - goto error; + /* + * If a "SYSCONFDIR" + "pki/libvirt-<val>" exists, then assume someone + * has created a val specific area to place service specific certificates. + * + * If the service specific directory doesn't exist, 'assume' that the + * user has created and populated the "SYSCONFDIR" + "pki/libvirt-default". + */ +#define SET_TLS_X509_CERT_DEFAULT(val) \ + do { \ + if (virFileExists(SYSCONFDIR "/pki/libvirt-"#val)) { \ + if (VIR_STRDUP(cfg->val ## TLSx509certdir, \ + SYSCONFDIR "/pki/libvirt-"#val) < 0) \ + goto error; \ + } else { \ + if (VIR_STRDUP(cfg->val ## TLSx509certdir, \ + cfg->defaultTLSx509certdir) < 0) \ + goto error; \ + } \ + } while (false); + + SET_TLS_X509_CERT_DEFAULT(vnc); + SET_TLS_X509_CERT_DEFAULT(spice); + +#undef SET_TLS_X509_CERT_DEFAULT cfg->remotePortMin = QEMU_REMOTE_PORT_MIN; cfg->remotePortMax = QEMU_REMOTE_PORT_MAX; @@ -333,6 +358,8 @@ static void virQEMUDriverConfigDispose(void *obj) VIR_FREE(cfg->channelTargetDir); VIR_FREE(cfg->nvramDir); + VIR_FREE(cfg->defaultTLSx509certdir); + VIR_FREE(cfg->vncTLSx509certdir); VIR_FREE(cfg->vncListen); VIR_FREE(cfg->vncPassword); @@ -436,6 +463,14 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, if (p) \ VAR = p->l != 0; +#define GET_VALUE_BOOL_DFLT(NAME, VAR, DFLT) \ + p = virConfGetValue(conf, NAME); \ + CHECK_TYPE(NAME, VIR_CONF_ULONG); \ + if (p) \ + VAR = p->l != 0; \ + else \ + VAR = DFLT; + #define GET_VALUE_STR(NAME, VAR) \ p = virConfGetValue(conf, NAME); \ CHECK_TYPE(NAME, VIR_CONF_STRING); \ @@ -445,9 +480,13 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, goto cleanup; \ } + GET_VALUE_STR("default_tls_x509_cert_dir", cfg->defaultTLSx509certdir); + GET_VALUE_BOOL("default_tls_x509_verify", cfg->defaultTLSx509verify); + GET_VALUE_BOOL("vnc_auto_unix_socket", cfg->vncAutoUnixSocket); GET_VALUE_BOOL("vnc_tls", cfg->vncTLS); - GET_VALUE_BOOL("vnc_tls_x509_verify", cfg->vncTLSx509verify); + GET_VALUE_BOOL_DFLT("vnc_tls_x509_verify", cfg->vncTLSx509verify, + cfg->defaultTLSx509verify); GET_VALUE_STR("vnc_tls_x509_cert_dir", cfg->vncTLSx509certdir); GET_VALUE_STR("vnc_listen", cfg->vncListen); GET_VALUE_STR("vnc_password", cfg->vncPassword); diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index a09c81d..1843693 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -109,6 +109,9 @@ struct _virQEMUDriverConfig { char *channelTargetDir; char *nvramDir; + char *defaultTLSx509certdir; + bool defaultTLSx509verify; + bool vncAutoUnixSocket; bool vncTLS; bool vncTLSx509verify; diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index c4d4f19..01351b4 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -2,6 +2,8 @@ module Test_libvirtd_qemu = ::CONFIG:: test Libvirtd_qemu.lns get conf = +{ "default_tls_x509_cert_dir" = "/etc/pki/qemu" } +{ "default_tls_x509_verify" = "1" } { "vnc_listen" = "0.0.0.0" } { "vnc_auto_unix_socket" = "1" } { "vnc_tls" = "1" } -- 2.5.5

Add a new TLS X.509 certificate type - "chardev". This will handle the creation of a TLS certificate capability (and possibly repository) for properly configured character device TCP backends. Unlike the vnc and spice there is no "listen" or "passwd" associated. The credentials will be handled via a libvirt secret provided to a specific backend. Make use of the default verify option as well. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/libvirtd_qemu.aug | 5 +++ src/qemu/qemu.conf | 28 ++++++++++++ src/qemu/qemu_conf.c | 6 +++ src/qemu/qemu_conf.h | 4 ++ src/qemu/test_libvirtd_qemu.aug.in | 3 ++ .../qemuxml2argv-serial-tcp-tlsx509-chardev.xml | 41 ++++++++++++++++++ .../qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml | 50 ++++++++++++++++++++++ tests/qemuxml2xmltest.c | 1 + 8 files changed, 138 insertions(+) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.xml create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml diff --git a/src/qemu/libvirtd_qemu.aug b/src/qemu/libvirtd_qemu.aug index 06d9b98..25b4645 100644 --- a/src/qemu/libvirtd_qemu.aug +++ b/src/qemu/libvirtd_qemu.aug @@ -45,6 +45,10 @@ module Libvirtd_qemu = | bool_entry "spice_sasl" | str_entry "spice_sasl_dir" + let chardev_entry = bool_entry "chardev_tls" + | str_entry "chardev_tls_x509_cert_dir" + | bool_entry "chardev_tls_x509_verify" + let nogfx_entry = bool_entry "nographics_allow_host_audio" let remote_display_entry = int_entry "remote_display_port_min" @@ -99,6 +103,7 @@ module Libvirtd_qemu = let entry = default_tls_entry | vnc_entry | spice_entry + | chardev_entry | nogfx_entry | remote_display_entry | security_entry diff --git a/src/qemu/qemu.conf b/src/qemu/qemu.conf index fb6b843..8634e9f 100644 --- a/src/qemu/qemu.conf +++ b/src/qemu/qemu.conf @@ -185,6 +185,34 @@ # #spice_sasl_dir = "/some/directory/sasl2" +# Enable use of TLS encryption on the chardev TCP transports. +# +# It is necessary to setup CA and issue a server certificate +# before enabling this. +# +#chardev_tls = 1 + + +# In order to override the default TLS certificate location for character +# device TCP certificates, supply a valid path to the certificate directory. +# If the provided path does not exist then the default_tls_x509_cert_dir +# path will be used. +# +#chardev_tls_x509_cert_dir = "/etc/pki/qemu-chardev" + + +# The default TLS configuration only uses certificates for the server +# allowing the client to verify the server's identity and establish +# an encrypted channel. +# +# It is possible to use x509 certificates for authentication too, by +# issuing a x509 certificate to every client who needs to connect. +# +# Enabling this option will reject any client who does not have a +# certificate signed by the CA in /etc/pki/libvirt-chardev/ca-cert.pem +# +#chardev_tls_x509_verify = 1 + # By default, if no graphical front end is configured, libvirt will disable # QEMU audio output since directly talking to alsa/pulseaudio may not work diff --git a/src/qemu/qemu_conf.c b/src/qemu/qemu_conf.c index 3091843..b1c6215 100644 --- a/src/qemu/qemu_conf.c +++ b/src/qemu/qemu_conf.c @@ -272,6 +272,7 @@ virQEMUDriverConfigPtr virQEMUDriverConfigNew(bool privileged) SET_TLS_X509_CERT_DEFAULT(vnc); SET_TLS_X509_CERT_DEFAULT(spice); + SET_TLS_X509_CERT_DEFAULT(chardev); #undef SET_TLS_X509_CERT_DEFAULT @@ -370,6 +371,8 @@ static void virQEMUDriverConfigDispose(void *obj) VIR_FREE(cfg->spicePassword); VIR_FREE(cfg->spiceSASLdir); + VIR_FREE(cfg->chardevTLSx509certdir); + while (cfg->nhugetlbfs) { cfg->nhugetlbfs--; VIR_FREE(cfg->hugetlbfs[cfg->nhugetlbfs].mnt_dir); @@ -547,6 +550,9 @@ int virQEMUDriverConfigLoadFile(virQEMUDriverConfigPtr cfg, GET_VALUE_STR("spice_password", cfg->spicePassword); GET_VALUE_BOOL("spice_auto_unix_socket", cfg->spiceAutoUnixSocket); + GET_VALUE_STR("chardev_tls_x509_cert_dir", cfg->chardevTLSx509certdir); + GET_VALUE_BOOL_DFLT("chardev_tls_x509_verify", cfg->chardevTLSx509verify, + cfg->defaultTLSx509verify); GET_VALUE_ULONG("remote_websocket_port_min", cfg->webSocketPortMin); if (cfg->webSocketPortMin < QEMU_WEBSOCKET_PORT_MIN) { diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 1843693..7138a7b 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -129,6 +129,10 @@ struct _virQEMUDriverConfig { char *spicePassword; bool spiceAutoUnixSocket; + bool chardevTLS; + char *chardevTLSx509certdir; + bool chardevTLSx509verify; + int remotePortMin; int remotePortMax; diff --git a/src/qemu/test_libvirtd_qemu.aug.in b/src/qemu/test_libvirtd_qemu.aug.in index 01351b4..44ee451 100644 --- a/src/qemu/test_libvirtd_qemu.aug.in +++ b/src/qemu/test_libvirtd_qemu.aug.in @@ -20,6 +20,9 @@ module Test_libvirtd_qemu = { "spice_password" = "XYZ12345" } { "spice_sasl" = "1" } { "spice_sasl_dir" = "/some/directory/sasl2" } +{ "chardev_tls" = "1" } +{ "chardev_tls_x509_cert_dir" = "/etc/pki/qemu-chardev" } +{ "chardev_tls_x509_verify" = "1" } { "nographics_allow_host_audio" = "1" } { "remote_display_port_min" = "5900" } { "remote_display_port_max" = "65535" } diff --git a/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.xml b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.xml new file mode 100644 index 0000000..1618b02 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.xml @@ -0,0 +1,41 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <serial type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target port='0'/> + </serial> + <serial type='tcp'> + <source mode='connect' host='127.0.0.1' service='5555'/> + <protocol type='raw'/> + <target port='0'/> + </serial> + <console type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target type='serial' port='0'/> + </console> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml new file mode 100644 index 0000000..832e2a2 --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-chardev.xml @@ -0,0 +1,50 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <serial type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target port='0'/> + </serial> + <serial type='tcp'> + <source mode='connect' host='127.0.0.1' service='5555'/> + <protocol type='raw'/> + <target port='0'/> + </serial> + <console type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target type='serial' port='0'/> + </console> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 7db9cb7..3064458 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -483,6 +483,7 @@ mymain(void) DO_TEST("serial-tcp"); DO_TEST("serial-udp"); DO_TEST("serial-tcp-telnet"); + DO_TEST("serial-tcp-tlsx509-chardev"); DO_TEST("serial-many"); DO_TEST("serial-spiceport"); DO_TEST("serial-spiceport-nospice"); -- 2.5.5

When building a chardev device string for tcp, add the necessary pieces to access provide the TLS X.509 path to qemu. This includes generating the 'tls-creds-x509' object and then adding the 'tls-creds' parameter to the VIR_DOMAIN_CHR_TYPE_TCP command line. Finally add the tests for the qemu command line. This test will make use of the "new(ish)" /etc/pki/qemu setting for a TLS certificate environment by *not* "resetting" the chardevTLSx509certdir prior to running the test. Also use the default "verify" option (which is "no"). Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 109 ++++++++++++++++++++- .../qemuxml2argv-serial-tcp-tlsx509-chardev.args | 33 +++++++ tests/qemuxml2argvtest.c | 6 ++ 3 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.args diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 5d82a4d..12f357a 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -696,6 +696,103 @@ qemuBuildRBDSecinfoURI(virBufferPtr buf, } +/* qemuBuildTLSx509BackendProps: + * @tlspath: path to the TLS credentials + * @listen: boolen listen for client or server setting + * @verifypeer: boolean to enable peer verification (form of authorization) + * @qemuCaps: capabilities + * @propsret: json properties to return + * + * Create a backend string for the tls-creds-x509 object. + * + * Returns 0 on success, -1 on failure with error set. + */ +static int +qemuBuildTLSx509BackendProps(const char *tlspath, + bool listen, + bool verifypeer, + virQEMUCapsPtr qemuCaps, + virJSONValuePtr *propsret) +{ + virBuffer buf = VIR_BUFFER_INITIALIZER; + char *path = NULL; + int ret = -1; + + if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_OBJECT_TLS_CREDS_X509)) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("tls-creds-x509 not supported in this QEMU binary")); + return -1; + } + + qemuBufferEscapeComma(&buf, tlspath); + if (virBufferCheckError(&buf) < 0) + goto cleanup; + path = virBufferContentAndReset(&buf); + + if (virJSONValueObjectCreate(propsret, + "s:dir", path, + "s:endpoint", (listen ? "server": "client"), + "b:verify-peer", verifypeer, + NULL) < 0) + goto cleanup; + + ret = 0; + + cleanup: + virBufferFreeAndReset(&buf); + VIR_FREE(path); + return ret; +} + + +/* qemuBuildTLSx509CommandLine: + * @cmd: Pointer to command + * @tlspath: path to the TLS credentials + * @listen: boolen listen for client or server setting + * @verifypeer: boolean to enable peer verification (form of authorization) + * @inalias: Alias for the parent (this code will add a "_tls0" to alias) + * @qemuCaps: capabilities + * + * Create the command line for a TLS object + * + * Returns 0 on success, -1 on failure with error set. + */ +static int +qemuBuildTLSx509CommandLine(virCommandPtr cmd, + const char *tlspath, + bool listen, + bool verifypeer, + const char *inalias, + virQEMUCapsPtr qemuCaps) +{ + int ret = -1; + char *alias = NULL; + virJSONValuePtr props = NULL; + char *tmp = NULL; + + if (qemuBuildTLSx509BackendProps(tlspath, listen, verifypeer, + qemuCaps, &props) < 0) + return -1; + + if (virAsprintf(&alias, "obj%s_tls0", inalias) < 0) + goto cleanup; + + if (!(tmp = virQEMUBuildObjectCommandlineFromJSON("tls-creds-x509", + alias, props))) + goto cleanup; + + virCommandAddArgList(cmd, "-object", tmp, NULL); + + ret = 0; + + cleanup: + virJSONValueFree(props); + VIR_FREE(alias); + VIR_FREE(tmp); + return ret; +} + + #define QEMU_DEFAULT_NBD_PORT "10809" static char * @@ -4689,7 +4786,7 @@ qemuBuildChrChardevFileStr(virLogManagerPtr logManager, static char * qemuBuildChrChardevStr(virLogManagerPtr logManager, virCommandPtr cmd, - virQEMUDriverConfigPtr cfg ATTRIBUTE_UNUSED, + virQEMUDriverConfigPtr cfg, const virDomainDef *def, const virDomainChrSourceDef *dev, const char *alias, @@ -4772,6 +4869,16 @@ qemuBuildChrChardevStr(virLogManagerPtr logManager, dev->data.tcp.service, telnet ? ",telnet" : "", dev->data.tcp.listen ? ",server,nowait" : ""); + + if (cfg->chardevTLS) { + if (qemuBuildTLSx509CommandLine(cmd, cfg->chardevTLSx509certdir, + dev->data.tcp.listen, + cfg->chardevTLSx509verify, + alias, qemuCaps) < 0) + goto error; + + virBufferAsprintf(&buf, ",tls-creds=obj%s_tls0", alias); + } break; case VIR_DOMAIN_CHR_TYPE_UNIX: diff --git a/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.args b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.args new file mode 100644 index 0000000..5b3016d --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-chardev.args @@ -0,0 +1,33 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name QEMUGuest1 \ +-S \ +-M pc \ +-m 214 \ +-smp 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 tls-creds-x509,id=objserial1_tls0,dir=/etc/pki/qemu,endpoint=client,\ +verify-peer=no \ +-chardev socket,id=charserial1,host=127.0.0.1,port=5555,\ +tls-creds=objserial1_tls0 \ +-device isa-serial,chardev=charserial1,id=serial1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index a4b8bf4..281d943 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1073,6 +1073,12 @@ mymain(void) QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG); DO_TEST("serial-tcp-telnet-chardev", QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG); + driver.config->chardevTLS = 1; + DO_TEST("serial-tcp-tlsx509-chardev", + QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG, + QEMU_CAPS_OBJECT_TLS_CREDS_X509); + driver.config->chardevTLS = 0; + VIR_FREE(driver.config->chardevTLSx509certdir); DO_TEST("serial-many-chardev", QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG); DO_TEST("parallel-tcp-chardev", -- 2.5.5

If the incoming XML defined a path to a TLS X.509 certificate environment, add the necessary 'tls-creds-x509' object to the VIR_DOMAIN_CHR_TYPE_TCP character device. Likewise, if the environment exists the hot unplug needs adjustment as well. Note that all the return ret were changed to goto cleanup since the cfg needs to be unref'd Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/conf/domain_conf.h | 1 + src/qemu/qemu_command.c | 2 +- src/qemu/qemu_command.h | 8 +++++++ src/qemu/qemu_hotplug.c | 57 +++++++++++++++++++++++++++++++++++++------- src/qemu/qemu_monitor_json.c | 9 +++++++ 5 files changed, 68 insertions(+), 9 deletions(-) diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 6e81e52..a06281c 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1097,6 +1097,7 @@ struct _virDomainChrSourceDef { char *service; bool listen; int protocol; + bool tlscreds; } tcp; struct { char *bindHost; diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 12f357a..8b0bd90 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -707,7 +707,7 @@ qemuBuildRBDSecinfoURI(virBufferPtr buf, * * Returns 0 on success, -1 on failure with error set. */ -static int +int qemuBuildTLSx509BackendProps(const char *tlspath, bool listen, bool verifypeer, diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index c4d0567..c22a251 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -61,10 +61,18 @@ virCommandPtr qemuBuildCommandLine(virQEMUDriverPtr driver, const char *domainLibDir) ATTRIBUTE_NONNULL(15); + /* Generate the object properties for a secret */ int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, virJSONValuePtr *propsret); +/* Generate the object properties for a tls-creds-x509 */ +int qemuBuildTLSx509BackendProps(const char *tlspath, + bool listen, + bool verifypeer, + virQEMUCapsPtr qemuCaps, + virJSONValuePtr *propsret); + /* Generate '-device' string for chardev device */ int qemuBuildChrDeviceStr(char **deviceStr, diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index e4cbbf0..8251444 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1518,10 +1518,14 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, virDomainChrDefPtr chr) { int ret = -1, rc; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDefPtr vmdef = vm->def; char *devstr = NULL; + virDomainChrSourceDefPtr dev = &chr->source; char *charAlias = NULL; + virJSONValuePtr props = NULL; + char *objAlias = NULL; bool need_release = false; if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL && @@ -1545,8 +1549,26 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, if (qemuDomainChrPreInsert(vmdef, chr) < 0) goto cleanup; + if (cfg->chardevTLS) { + if (qemuBuildTLSx509BackendProps(cfg->chardevTLSx509certdir, + dev->data.tcp.listen, + cfg->chardevTLSx509verify, + priv->qemuCaps, + &props) < 0) + goto cleanup; + + if (virAsprintf(&objAlias, "obj%s_tls0", chr->info.alias) < 0) + goto cleanup; + dev->data.tcp.tlscreds = true; + } + qemuDomainObjEnterMonitor(driver, vm); - if (qemuMonitorAttachCharDev(priv->mon, charAlias, &chr->source) < 0) + + if (objAlias && qemuMonitorAddObject(priv->mon, "tls-creds-x509", + objAlias, props) < 0) + goto failobject; + + if (qemuMonitorAttachCharDev(priv->mon, charAlias, dev) < 0) goto failchardev; if (qemuMonitorAddDevice(priv->mon, devstr) < 0) @@ -1564,14 +1586,20 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, qemuDomainChrInsertPreAllocCleanup(vmdef, chr); if (ret < 0 && need_release) qemuDomainReleaseDeviceAddress(vm, &chr->info, NULL); + VIR_FREE(objAlias); + virJSONValueFree(props); VIR_FREE(charAlias); VIR_FREE(devstr); + virObjectUnref(cfg); return ret; failadddev: /* detach associated chardev on error */ qemuMonitorDetachCharDev(priv->mon, charAlias); failchardev: + /* Remove the object */ + ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); + failobject: ignore_value(qemuDomainObjExitMonitor(driver, vm)); goto audit; } @@ -4082,32 +4110,40 @@ int qemuDomainDetachChrDevice(virQEMUDriverPtr driver, virDomainChrDefPtr chr) { int ret = -1; + virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDefPtr vmdef = vm->def; virDomainChrDefPtr tmpChr; + char *objAlias = NULL; char *devstr = NULL; if (!(tmpChr = virDomainChrFind(vmdef, chr))) { virReportError(VIR_ERR_OPERATION_INVALID, "%s", _("device not present in domain configuration")); - return ret; + goto cleanup; } if (!tmpChr->info.alias && qemuAssignDeviceChrAlias(vmdef, tmpChr, -1) < 0) - return ret; + goto cleanup; sa_assert(tmpChr->info.alias); + if (cfg->chardevTLS && + virAsprintf(&objAlias, "obj%s_tls0", tmpChr->info.alias) < 0) + goto cleanup; + if (qemuBuildChrDeviceStr(&devstr, vmdef, chr, priv->qemuCaps) < 0) - return ret; + goto cleanup; qemuDomainMarkDeviceForRemoval(vm, &tmpChr->info); qemuDomainObjEnterMonitor(driver, vm); - if (devstr && qemuMonitorDelDevice(priv->mon, tmpChr->info.alias) < 0) { - ignore_value(qemuDomainObjExitMonitor(driver, vm)); - goto cleanup; - } + if (objAlias && qemuMonitorDelObject(priv->mon, objAlias) < 0) + goto faildel; + + if (devstr && qemuMonitorDelDevice(priv->mon, tmpChr->info.alias) < 0) + goto faildel; + if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; @@ -4119,7 +4155,12 @@ int qemuDomainDetachChrDevice(virQEMUDriverPtr driver, cleanup: qemuDomainResetDeviceRemoval(vm); VIR_FREE(devstr); + virObjectUnref(cfg); return ret; + + faildel: + ignore_value(qemuDomainObjExitMonitor(driver, vm)); + goto cleanup; } diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c index bb426dc..548ee99 100644 --- a/src/qemu/qemu_monitor_json.c +++ b/src/qemu/qemu_monitor_json.c @@ -6131,6 +6131,7 @@ qemuMonitorJSONAttachCharDevCommand(const char *chrID, virJSONValuePtr data = NULL; virJSONValuePtr addr = NULL; const char *backend_type = NULL; + char *tlsalias = NULL; bool telnet; if (!(backend = virJSONValueNewObject()) || @@ -6176,6 +6177,13 @@ qemuMonitorJSONAttachCharDevCommand(const char *chrID, virJSONValueObjectAppendBoolean(data, "telnet", telnet) < 0 || virJSONValueObjectAppendBoolean(data, "server", chr->data.tcp.listen) < 0) goto error; + if (chr->data.tcp.tlscreds) { + if (virAsprintf(&tlsalias, "obj%s_tls0", chrID) < 0) + goto error; + + if (virJSONValueObjectAppendString(data, "tls-creds", tlsalias) < 0) + goto error; + } break; case VIR_DOMAIN_CHR_TYPE_UDP: @@ -6241,6 +6249,7 @@ qemuMonitorJSONAttachCharDevCommand(const char *chrID, return ret; error: + VIR_FREE(tlsalias); virJSONValueFree(addr); virJSONValueFree(data); virJSONValueFree(backend); -- 2.5.5

Define, parse, and format a key secret element for a chardev tcp backend. This secret will be used in conjunction with the chartcp_tls_x509_cert_dir in order to provide the secret to the TLS encrypted TCP chardev. <secret type='passphrase' usage='keyexample'/> Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatdomain.html.in | 29 ++++++++++++ docs/schemas/domaincommon.rng | 21 +++++++++ src/conf/domain_conf.c | 35 +++++++++++++++ src/conf/domain_conf.h | 3 ++ ...uxml2argv-serial-tcp-tlsx509-secret-chardev.xml | 42 ++++++++++++++++++ ...ml2xmlout-serial-tcp-tlsx509-secret-chardev.xml | 51 ++++++++++++++++++++++ tests/qemuxml2xmltest.c | 1 + 7 files changed, 182 insertions(+) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml create mode 100644 tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-secret-chardev.xml diff --git a/docs/formatdomain.html.in b/docs/formatdomain.html.in index f660aa6..5803c40 100644 --- a/docs/formatdomain.html.in +++ b/docs/formatdomain.html.in @@ -6031,6 +6031,35 @@ qemu-kvm -net nic,model=? /dev/null </devices> ...</pre> + <p> + <span class="since">Since 2.0.0,</span> some hypervisors support using + a TLS X.509 certificate environment in order to encrypt the TCP. In + order to provide the passphrase for the certificates, provide a + <code>secret</code> element. The <code>secret</code> element takes + two required attributes <code>type</code> and either <code>UUID</code> + or <code>usage</code>. The supported <code>type</code> is a "passphrase" + secret referenced via either attribute <code>uuid</code> or + <code>usage</code>. + </p> +<pre> + ... + <devices> + <serial type="tcp"> + <source mode="connect" host="0.0.0.0" service="2445"/> + <protocol type="raw"/> + <secret type='passphrase' usage='keyexample'/> + <target port="1"/> + </serial> + ... + <serial type="tcp"> + <source mode="bind" host="127.0.0.1" service="2445"/> + <protocol type="raw"/> + <target port="1"/> + <secret type='passphrase' usage='keyexample'/> + </serial> + </devices> + ...</pre> + <h6><a name="elementsCharUDP">UDP network console</a></h6> <p> diff --git a/docs/schemas/domaincommon.rng b/docs/schemas/domaincommon.rng index 162c2e0..eb08f3d 100644 --- a/docs/schemas/domaincommon.rng +++ b/docs/schemas/domaincommon.rng @@ -3221,6 +3221,9 @@ <ref name="qemucdevTgtDef"/> </optional> <optional> + <ref name="qemucdevSecret"/> + </optional> + <optional> <ref name="alias"/> </optional> <optional> @@ -3272,6 +3275,24 @@ </element> </define> + <define name="qemucdevSecret"> + <element name='secret'> + <attribute name='type'> + <choice> + <value>passphrase</value> + </choice> + </attribute> + <choice> + <attribute name='uuid'> + <ref name="UUID"/> + </attribute> + <attribute name='usage'> + <ref name='genericName'/> + </attribute> + </choice> + </element> + </define> + <define name="qemucdevSrcTypeChoice"> <choice> <value>dev</value> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 9443281..f614ff9 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -1843,6 +1843,7 @@ virDomainChrSourceDefClear(virDomainChrSourceDefPtr def) case VIR_DOMAIN_CHR_TYPE_TCP: VIR_FREE(def->data.tcp.host); VIR_FREE(def->data.tcp.service); + virSecretLookupDefClear(&def->data.tcp.seclookupdef); break; case VIR_DOMAIN_CHR_TYPE_UNIX: @@ -1899,6 +1900,10 @@ virDomainChrSourceDefCopy(virDomainChrSourceDefPtr dest, if (VIR_STRDUP(dest->data.tcp.service, src->data.tcp.service) < 0) return -1; + + if (virSecretLookupDefCopy(&dest->data.tcp.seclookupdef, + &src->data.tcp.seclookupdef) < 0) + return -1; break; case VIR_DOMAIN_CHR_TYPE_UNIX: @@ -9900,6 +9905,8 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, char *master = NULL; char *slave = NULL; char *append = NULL; + xmlNodePtr secret = NULL; + char *sectypestr = NULL; int remaining = 0; while (cur != NULL) { @@ -9989,6 +9996,8 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, } else if (xmlStrEqual(cur->name, BAD_CAST "protocol")) { if (!protocol) protocol = virXMLPropString(cur, "type"); + } else if (xmlStrEqual(cur->name, BAD_CAST "secret")) { + secret = cur; } else { remaining++; } @@ -10092,6 +10101,25 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, goto error; } + if (secret) { + + if (!(sectypestr = virXMLPropString(secret, "type"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing TCP chardev secret type")); + goto error; + } + if ((def->data.tcp.sectype = + virSecretUsageTypeFromString(sectypestr)) != + VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + virReportError(VIR_ERR_XML_ERROR, + _("invalid TCP chardev secret type '%s'"), + sectypestr); + goto error; + } + if (virSecretLookupParseSecret(secret, + &def->data.tcp.seclookupdef) < 0) + goto error; + } break; case VIR_DOMAIN_CHR_TYPE_UDP: @@ -10166,6 +10194,7 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, VIR_FREE(append); VIR_FREE(logappend); VIR_FREE(logfile); + VIR_FREE(sectypestr); return remaining; @@ -21065,6 +21094,12 @@ virDomainChrSourceDefFormat(virBufferPtr buf, virBufferAsprintf(buf, "<protocol type='%s'/>\n", virDomainChrTcpProtocolTypeToString( def->data.tcp.protocol)); + if (def->data.tcp.sectype == VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + const char *typestr = + virSecretUsageTypeToString(def->data.tcp.sectype); + virSecretLookupFormatSecret(buf, typestr, + &def->data.tcp.seclookupdef); + } break; case VIR_DOMAIN_CHR_TYPE_UNIX: diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index a06281c..8d4a9ec 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -52,6 +52,7 @@ # include "virprocess.h" # include "virgic.h" # include "virperf.h" +# include "virsecret.h" # include "virtypedparam.h" /* forward declarations of all device types, required by @@ -1098,6 +1099,8 @@ struct _virDomainChrSourceDef { bool listen; int protocol; bool tlscreds; + int sectype; /* virSecretUsage */ + virSecretLookupTypeDef seclookupdef; } tcp; struct { char *bindHost; 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 0000000..e889072 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.xml @@ -0,0 +1,42 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'/> + <controller type='ide' index='0'/> + <serial type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target port='0'/> + </serial> + <serial type='tcp'> + <source mode='connect' host='127.0.0.1' service='5555'/> + <protocol type='raw'/> + <target port='0'/> + <secret type='passphrase' usage='mycluster_myname'/> + </serial> + <console type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target type='serial' port='0'/> + </console> + <memballoon model='virtio'/> + </devices> +</domain> diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-secret-chardev.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-secret-chardev.xml new file mode 100644 index 0000000..0a6cdbb --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-serial-tcp-tlsx509-secret-chardev.xml @@ -0,0 +1,51 @@ +<domain type='qemu'> + <name>QEMUGuest1</name> + <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid> + <memory unit='KiB'>219136</memory> + <currentMemory unit='KiB'>219136</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='i686' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> + <clock offset='utc'/> + <on_poweroff>destroy</on_poweroff> + <on_reboot>restart</on_reboot> + <on_crash>destroy</on_crash> + <devices> + <emulator>/usr/bin/qemu</emulator> + <disk type='block' device='disk'> + <source dev='/dev/HostVG/QEMUGuest1'/> + <target dev='hda' bus='ide'/> + <address type='drive' controller='0' bus='0' target='0' unit='0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='ide' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <serial type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target port='0'/> + </serial> + <serial type='tcp'> + <source mode='connect' host='127.0.0.1' service='5555'/> + <protocol type='raw'/> + <secret type='passphrase' usage='mycluster_myname'/> + <target port='0'/> + </serial> + <console type='udp'> + <source mode='bind' host='127.0.0.1' service='1111'/> + <source mode='connect' host='127.0.0.1' service='2222'/> + <target type='serial' port='0'/> + </console> + <input type='mouse' bus='ps2'/> + <input type='keyboard' bus='ps2'/> + <memballoon model='virtio'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/> + </memballoon> + </devices> +</domain> diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 3064458..cef459e 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -484,6 +484,7 @@ mymain(void) DO_TEST("serial-udp"); DO_TEST("serial-tcp-telnet"); DO_TEST("serial-tcp-tlsx509-chardev"); + DO_TEST("serial-tcp-tlsx509-secret-chardev"); DO_TEST("serial-many"); DO_TEST("serial-spiceport"); DO_TEST("serial-spiceport-nospice"); -- 2.5.5

Modeled after the qemuDomainHostdevPrivatePtr (commit id '27726d8c'), create a privateData pointer in the _virDomainChardevDef to allow storage of private data for a hypervisor in order to at least temporarily store secret data for usage during qemuBuildCommandLine. NB: Since the qemu_parse_command (qemuParseCommandLine) code is not expecting to restore the secret data, there's no need to add code code to handle this new structure there. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/conf/domain_conf.c | 27 ++++++++++++++++++-------- src/conf/domain_conf.h | 4 +++- src/libxl/libxl_domain.c | 2 +- src/lxc/lxc_native.c | 2 +- src/qemu/qemu_domain.c | 44 +++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_domain.h | 14 ++++++++++++++ src/qemu/qemu_parse_command.c | 4 ++-- src/vz/vz_sdk.c | 2 +- src/xenconfig/xen_sxpr.c | 2 +- 9 files changed, 86 insertions(+), 15 deletions(-) diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index f614ff9..18dfc1c 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -10208,7 +10208,7 @@ virDomainChrSourceDefParseXML(virDomainChrSourceDefPtr def, * default port. */ virDomainChrDefPtr -virDomainChrDefNew(void) +virDomainChrDefNew(virDomainXMLOptionPtr xmlopt) { virDomainChrDefPtr def = NULL; @@ -10216,6 +10216,11 @@ virDomainChrDefNew(void) return NULL; def->target.port = -1; + + if (xmlopt && xmlopt->privateData.chardevNew && + !(def->privateData = xmlopt->privateData.chardevNew())) + VIR_FREE(def); + return def; } @@ -10263,7 +10268,8 @@ virDomainChrDefNew(void) * */ static virDomainChrDefPtr -virDomainChrDefParseXML(xmlXPathContextPtr ctxt, +virDomainChrDefParseXML(virDomainXMLOptionPtr xmlopt, + xmlXPathContextPtr ctxt, xmlNodePtr node, virSecurityLabelDefPtr* vmSeclabels, int nvmSeclabels, @@ -10275,7 +10281,7 @@ virDomainChrDefParseXML(xmlXPathContextPtr ctxt, virDomainChrDefPtr def; bool seenTarget = false; - if (!(def = virDomainChrDefNew())) + if (!(def = virDomainChrDefNew(xmlopt))) return NULL; type = virXMLPropString(node, "type"); @@ -13424,7 +13430,8 @@ virDomainDeviceDefParse(const char *xmlStr, goto error; break; case VIR_DOMAIN_DEVICE_CHR: - if (!(dev->data.chr = virDomainChrDefParseXML(ctxt, + if (!(dev->data.chr = virDomainChrDefParseXML(xmlopt, + ctxt, node, def->seclabels, def->nseclabels, @@ -16859,7 +16866,8 @@ virDomainDefParseXML(xmlDocPtr xml, goto error; for (i = 0; i < n; i++) { - virDomainChrDefPtr chr = virDomainChrDefParseXML(ctxt, + virDomainChrDefPtr chr = virDomainChrDefParseXML(xmlopt, + ctxt, nodes[i], def->seclabels, def->nseclabels, @@ -16886,7 +16894,8 @@ virDomainDefParseXML(xmlDocPtr xml, goto error; for (i = 0; i < n; i++) { - virDomainChrDefPtr chr = virDomainChrDefParseXML(ctxt, + virDomainChrDefPtr chr = virDomainChrDefParseXML(xmlopt, + ctxt, nodes[i], def->seclabels, def->nseclabels, @@ -16915,7 +16924,8 @@ virDomainDefParseXML(xmlDocPtr xml, goto error; for (i = 0; i < n; i++) { - virDomainChrDefPtr chr = virDomainChrDefParseXML(ctxt, + virDomainChrDefPtr chr = virDomainChrDefParseXML(xmlopt, + ctxt, nodes[i], def->seclabels, def->nseclabels, @@ -16934,7 +16944,8 @@ virDomainDefParseXML(xmlDocPtr xml, goto error; for (i = 0; i < n; i++) { - virDomainChrDefPtr chr = virDomainChrDefParseXML(ctxt, + virDomainChrDefPtr chr = virDomainChrDefParseXML(xmlopt, + ctxt, nodes[i], def->seclabels, def->nseclabels, diff --git a/src/conf/domain_conf.h b/src/conf/domain_conf.h index 8d4a9ec..f1d38fa 100644 --- a/src/conf/domain_conf.h +++ b/src/conf/domain_conf.h @@ -1124,6 +1124,7 @@ struct _virDomainChrSourceDef { /* A complete character device, both host and domain views. */ struct _virDomainChrDef { int deviceType; /* enum virDomainChrDeviceType */ + virObjectPtr privateData; bool targetTypeAttr; int targetType; /* enum virDomainChrConsoleTargetType || @@ -2424,6 +2425,7 @@ struct _virDomainXMLPrivateDataCallbacks { virDomainXMLPrivateDataFreeFunc free; virDomainXMLPrivateDataNewFunc diskNew; virDomainXMLPrivateDataNewFunc hostdevNew; + virDomainXMLPrivateDataNewFunc chardevNew; virDomainXMLPrivateDataFormatFunc format; virDomainXMLPrivateDataParseFunc parse; }; @@ -2546,7 +2548,7 @@ bool virDomainDefHasDeviceAddress(virDomainDefPtr def, void virDomainDefFree(virDomainDefPtr vm); -virDomainChrDefPtr virDomainChrDefNew(void); +virDomainChrDefPtr virDomainChrDefNew(virDomainXMLOptionPtr xmlopt); virDomainDefPtr virDomainDefNew(void); virDomainDefPtr virDomainDefNewFull(const char *name, diff --git a/src/libxl/libxl_domain.c b/src/libxl/libxl_domain.c index 6bcb4d9..f264c89 100644 --- a/src/libxl/libxl_domain.c +++ b/src/libxl/libxl_domain.c @@ -386,7 +386,7 @@ libxlDomainDefPostParse(virDomainDefPtr def, if (def->os.type != VIR_DOMAIN_OSTYPE_HVM && def->nconsoles == 0) { virDomainChrDefPtr chrdef; - if (!(chrdef = virDomainChrDefNew())) + if (!(chrdef = virDomainChrDefNew(NULL))) return -1; chrdef->source.type = VIR_DOMAIN_CHR_TYPE_PTY; diff --git a/src/lxc/lxc_native.c b/src/lxc/lxc_native.c index 7f8e904..b7db904 100644 --- a/src/lxc/lxc_native.c +++ b/src/lxc/lxc_native.c @@ -702,7 +702,7 @@ lxcCreateConsoles(virDomainDefPtr def, virConfPtr properties) def->nconsoles = nbttys; for (i = 0; i < nbttys; i++) { - if (!(console = virDomainChrDefNew())) + if (!(console = virDomainChrDefNew(NULL))) goto error; console->deviceType = VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE; diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index dca8970..72fe5ca 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -811,6 +811,49 @@ qemuDomainHostdevPrivateDispose(void *obj) } +static virClassPtr qemuDomainChardevPrivateClass; +static void qemuDomainChardevPrivateDispose(void *obj); + +static int +qemuDomainChardevPrivateOnceInit(void) +{ + qemuDomainChardevPrivateClass = + virClassNew(virClassForObject(), + "qemuDomainChardevPrivate", + sizeof(qemuDomainChardevPrivate), + qemuDomainChardevPrivateDispose); + if (!qemuDomainChardevPrivateClass) + return -1; + else + return 0; +} + +VIR_ONCE_GLOBAL_INIT(qemuDomainChardevPrivate) + +static virObjectPtr +qemuDomainChardevPrivateNew(void) +{ + qemuDomainChardevPrivatePtr priv; + + if (qemuDomainChardevPrivateInitialize() < 0) + return NULL; + + if (!(priv = virObjectNew(qemuDomainChardevPrivateClass))) + return NULL; + + return (virObjectPtr) priv; +} + + +static void +qemuDomainChardevPrivateDispose(void *obj) +{ + qemuDomainChardevPrivatePtr priv = obj; + + qemuDomainSecretInfoFree(&priv->secinfo); +} + + /* qemuDomainSecretPlainSetup: * @conn: Pointer to connection * @secinfo: Pointer to secret info @@ -1627,6 +1670,7 @@ virDomainXMLPrivateDataCallbacks virQEMUDriverPrivateDataCallbacks = { .free = qemuDomainObjPrivateFree, .diskNew = qemuDomainDiskPrivateNew, .hostdevNew = qemuDomainHostdevPrivateNew, + .chardevNew = qemuDomainChardevPrivateNew, .parse = qemuDomainObjPrivateXMLParse, .format = qemuDomainObjPrivateXMLFormat, }; diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index fa536e0..d718917 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -331,6 +331,20 @@ struct _qemuDomainHostdevPrivate { qemuDomainSecretInfoPtr secinfo; }; +# define QEMU_DOMAIN_CHARDEV_PRIVATE(chardev) \ + ((qemuDomainChardevPrivatePtr) (chardev)->privateData) + +typedef struct _qemuDomainChardevPrivate qemuDomainChardevPrivate; +typedef qemuDomainChardevPrivate *qemuDomainChardevPrivatePtr; +struct _qemuDomainChardevPrivate { + virObject parent; + + /* for char devices using secret + * NB: *not* to be written to qemu domain object XML */ + qemuDomainSecretInfoPtr secinfo; +}; + + typedef enum { QEMU_PROCESS_EVENT_WATCHDOG = 0, QEMU_PROCESS_EVENT_GUESTPANIC, diff --git a/src/qemu/qemu_parse_command.c b/src/qemu/qemu_parse_command.c index 1d54a68..083b6f7 100644 --- a/src/qemu/qemu_parse_command.c +++ b/src/qemu/qemu_parse_command.c @@ -2187,7 +2187,7 @@ qemuParseCommandLine(virCapsPtr caps, if (STRNEQ(val, "none")) { virDomainChrDefPtr chr; - if (!(chr = virDomainChrDefNew())) + if (!(chr = virDomainChrDefNew(NULL))) goto error; if (qemuParseCommandLineChr(&chr->source, val) < 0) { @@ -2206,7 +2206,7 @@ qemuParseCommandLine(virCapsPtr caps, if (STRNEQ(val, "none")) { virDomainChrDefPtr chr; - if (!(chr = virDomainChrDefNew())) + if (!(chr = virDomainChrDefNew(NULL))) goto error; if (qemuParseCommandLineChr(&chr->source, val) < 0) { diff --git a/src/vz/vz_sdk.c b/src/vz/vz_sdk.c index cb06240..5e82545 100644 --- a/src/vz/vz_sdk.c +++ b/src/vz/vz_sdk.c @@ -944,7 +944,7 @@ prlsdkAddSerialInfo(PRL_HANDLE sdkdom, ret = PrlVmCfg_GetSerialPort(sdkdom, i, &serialPort); prlsdkCheckRetGoto(ret, cleanup); - if (!(chr = virDomainChrDefNew())) + if (!(chr = virDomainChrDefNew(NULL))) goto cleanup; if (prlsdkGetSerialInfo(serialPort, chr)) diff --git a/src/xenconfig/xen_sxpr.c b/src/xenconfig/xen_sxpr.c index 653b7b3..5732b15 100644 --- a/src/xenconfig/xen_sxpr.c +++ b/src/xenconfig/xen_sxpr.c @@ -190,7 +190,7 @@ xenParseSxprChar(const char *value, char *tmp; virDomainChrDefPtr def; - if (!(def = virDomainChrDefNew())) + if (!(def = virDomainChrDefNew(NULL))) return NULL; prefix = value; -- 2.5.5

Add the secret object prior to the chardev tcp so the 'passwordid=' can be added if the domain XML has a <secret> for the chardev TLS. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 32 +++++++++- src/qemu/qemu_command.h | 1 + src/qemu/qemu_domain.c | 72 +++++++++++++++++++++- src/qemu/qemu_domain.h | 8 +++ src/qemu/qemu_hotplug.c | 1 + src/qemu/qemu_process.c | 2 +- ...xml2argv-serial-tcp-tlsx509-secret-chardev.args | 38 ++++++++++++ tests/qemuxml2argvtest.c | 8 +++ 8 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-serial-tcp-tlsx509-secret-chardev.args diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 8b0bd90..e5fd2b2 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -700,6 +700,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 * @@ -711,6 +712,7 @@ int qemuBuildTLSx509BackendProps(const char *tlspath, bool listen, bool verifypeer, + const char *secalias, virQEMUCapsPtr qemuCaps, virJSONValuePtr *propsret) { @@ -736,6 +738,10 @@ qemuBuildTLSx509BackendProps(const char *tlspath, NULL) < 0) goto cleanup; + if (secalias && + virJSONValueObjectAdd(*propsret, "s:passwordid", secalias, NULL) < 0) + goto cleanup; + ret = 0; cleanup: @@ -750,6 +756,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 (this code will add a "_tls0" to alias) * @qemuCaps: capabilities * @@ -762,6 +769,7 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, const char *tlspath, bool listen, bool verifypeer, + bool addpasswordid, const char *inalias, virQEMUCapsPtr qemuCaps) { @@ -769,11 +777,15 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, char *alias = NULL; virJSONValuePtr props = NULL; char *tmp = NULL; + char *secalias = NULL; - if (qemuBuildTLSx509BackendProps(tlspath, listen, verifypeer, - qemuCaps, &props) < 0) + if (addpasswordid && !(secalias = qemuDomainGetSecretAESAlias(inalias))) return -1; + if (qemuBuildTLSx509BackendProps(tlspath, listen, verifypeer, secalias, + qemuCaps, &props) < 0) + goto cleanup; + if (virAsprintf(&alias, "obj%s_tls0", inalias) < 0) goto cleanup; @@ -789,6 +801,7 @@ qemuBuildTLSx509CommandLine(virCommandPtr cmd, virJSONValueFree(props); VIR_FREE(alias); VIR_FREE(tmp); + VIR_FREE(secalias); return ret; } @@ -4871,9 +4884,13 @@ qemuBuildChrChardevStr(virLogManagerPtr logManager, dev->data.tcp.listen ? ",server,nowait" : ""); if (cfg->chardevTLS) { + bool addpasswordid = (dev->data.tcp.sectype == + VIR_SECRET_USAGE_TYPE_PASSPHRASE); + if (qemuBuildTLSx509CommandLine(cmd, cfg->chardevTLSx509certdir, dev->data.tcp.listen, cfg->chardevTLSx509verify, + addpasswordid, alias, qemuCaps) < 0) goto error; @@ -8482,6 +8499,17 @@ qemuBuildSerialCommandLine(virLogManagerPtr logManager, /* Use -chardev with -device if they are available */ if (virQEMUCapsSupportsChardev(def, qemuCaps, serial)) { + /* Add the secret object first if necessary */ + if (serial->source.data.tcp.sectype == + VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + qemuDomainChardevPrivatePtr chardevPriv = + QEMU_DOMAIN_CHARDEV_PRIVATE(serial); + qemuDomainSecretInfoPtr secinfo = chardevPriv->secinfo; + + if (qemuBuildObjectSecretCommandLine(cmd, secinfo) < 0) + return -1; + } + if (!(devstr = qemuBuildChrChardevStr(logManager, cmd, cfg, def, &serial->source, serial->info.alias, diff --git a/src/qemu/qemu_command.h b/src/qemu/qemu_command.h index c22a251..cb9b550 100644 --- a/src/qemu/qemu_command.h +++ b/src/qemu/qemu_command.h @@ -70,6 +70,7 @@ int qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo, int qemuBuildTLSx509BackendProps(const char *tlspath, bool listen, bool verifypeer, + const char *secalias, virQEMUCapsPtr qemuCaps, virJSONValuePtr *propsret); diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 72fe5ca..207cff5 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -984,7 +984,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_CEPH || + secretUsageType == VIR_SECRET_USAGE_TYPE_PASSPHRASE)) { if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, secretUsageType, username, seclookupdef) < 0) @@ -1131,6 +1132,66 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn, } +/* qemuDomainSecretChardevDestroy: + * @disk: Pointer to a chardev definition + * + * Clear and destroy memory associated with the secret + */ +void +qemuDomainSecretChardevDestroy(virDomainChrDefPtr chardev) +{ + qemuDomainChardevPrivatePtr chardevPriv = + QEMU_DOMAIN_CHARDEV_PRIVATE(chardev); + + if (!chardevPriv || !chardevPriv->secinfo) + return; + + qemuDomainSecretInfoFree(&chardevPriv->secinfo); +} + + +/* qemuDomainSecretChardevPrepare: + * @conn: Pointer to connection + * @priv: pointer to domain private object + * @chardev: Pointer to a chardev definition + * + * For the right character device, generate the qemuDomainSecretInfo structure. + * + * Returns 0 on success, -1 on failure + */ +int +qemuDomainSecretChardevPrepare(virConnectPtr conn, + qemuDomainObjPrivatePtr priv, + virDomainChrDefPtr chardev) +{ + qemuDomainSecretInfoPtr secinfo = NULL; + + if (conn && chardev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_SERIAL && + chardev->source.type == VIR_DOMAIN_CHR_TYPE_TCP && + chardev->source.data.tcp.sectype == VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + + qemuDomainChardevPrivatePtr chardevPriv = + QEMU_DOMAIN_CHARDEV_PRIVATE(chardev); + + if (VIR_ALLOC(secinfo) < 0) + return -1; + + if (qemuDomainSecretSetup(conn, priv, secinfo, chardev->info.alias, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, NULL, + &chardev->source.data.tcp.seclookupdef) < 0) + goto error; + + chardevPriv->secinfo = secinfo; + } + + return 0; + + error: + qemuDomainSecretInfoFree(&secinfo); + return -1; +} + + /* qemuDomainSecretDestroy: * @vm: Domain object * @@ -1147,6 +1208,9 @@ 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]); } @@ -1180,6 +1244,12 @@ qemuDomainSecretPrepare(virConnectPtr conn, return -1; } + for (i = 0; i < vm->def->nserials; i++) { + if (qemuDomainSecretChardevPrepare(conn, priv, + vm->def->serials[i]) < 0) + return -1; + } + return 0; } diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index d718917..4996082 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -696,6 +696,14 @@ int qemuDomainSecretHostdevPrepare(virConnectPtr conn, virDomainHostdevDefPtr hostdev) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); +void qemuDomainSecretChardevDestroy(virDomainChrDefPtr chardev) + ATTRIBUTE_NONNULL(1); + +int qemuDomainSecretChardevPrepare(virConnectPtr conn, + qemuDomainObjPrivatePtr priv, + virDomainChrDefPtr chardev) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + void qemuDomainSecretDestroy(virDomainObjPtr vm) ATTRIBUTE_NONNULL(1); diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 8251444..1a07a32 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1553,6 +1553,7 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, if (qemuBuildTLSx509BackendProps(cfg->chardevTLSx509certdir, dev->data.tcp.listen, cfg->chardevTLSx509verify, + NULL, priv->qemuCaps, &props) < 0) goto cleanup; diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 63da600..12adb5f 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -4909,7 +4909,7 @@ qemuProcessPrepareDomain(virConnectPtr conn, if (qemuDomainMasterKeyCreate(vm) < 0) goto cleanup; - VIR_DEBUG("Add secrets to disks and hostdevs"); + VIR_DEBUG("Add secrets to disks, hostdevs, and chardevs"); if (qemuDomainSecretPrepare(conn, vm) < 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 0000000..26624eb --- /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 \ +-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=serial1-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-object tls-creds-x509,id=objserial1_tls0,dir=/etc/pki/qemu-chardev,\ +endpoint=client,verify-peer=yes,passwordid=serial1-secret0 \ +-chardev socket,id=charserial1,host=127.0.0.1,port=5555,\ +tls-creds=objserial1_tls0 \ +-device isa-serial,chardev=charserial1,id=serial1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index 281d943..0831813 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1077,6 +1077,14 @@ mymain(void) DO_TEST("serial-tcp-tlsx509-chardev", 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/qemu-chardev") < 0) + return EXIT_FAILURE; + driver.config->chardevTLSx509verify = 1; + DO_TEST("serial-tcp-tlsx509-secret-chardev", + QEMU_CAPS_CHARDEV, QEMU_CAPS_NODEFCONFIG, + QEMU_CAPS_OBJECT_SECRET, + QEMU_CAPS_OBJECT_TLS_CREDS_X509); driver.config->chardevTLS = 0; VIR_FREE(driver.config->chardevTLSx509certdir); DO_TEST("serial-many-chardev", -- 2.5.5

https://bugzilla.redhat.com/show_bug.cgi?id=1300776 Complete the implementation of support for TLS encryption on chardev TCP transports by adding the hotplug ability of a secret to generate the passwordid for the TLS object Likewise, add the ability to hot unplug that secret object as well Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_driver.c | 2 +- src/qemu/qemu_hotplug.c | 43 +++++++++++++++++++++++++++++++++++++++++-- src/qemu/qemu_hotplug.h | 3 ++- tests/qemuhotplugtest.c | 2 +- 4 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ee717f0..aba5a69 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -7516,7 +7516,7 @@ qemuDomainAttachDeviceLive(virDomainObjPtr vm, break; case VIR_DOMAIN_DEVICE_CHR: - ret = qemuDomainAttachChrDevice(driver, vm, + ret = qemuDomainAttachChrDevice(dom->conn, driver, vm, dev->data.chr); if (!ret) { alias = dev->data.chr->info.alias; diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 1a07a32..42b5778 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -1513,7 +1513,8 @@ qemuDomainAttachChrDeviceAssignAddr(qemuDomainObjPrivatePtr priv, return 0; } -int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, +int qemuDomainAttachChrDevice(virConnectPtr conn, + virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainChrDefPtr chr) { @@ -1526,6 +1527,8 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, char *charAlias = NULL; virJSONValuePtr props = NULL; char *objAlias = NULL; + virJSONValuePtr secprops = NULL; + char *secAlias = NULL; bool need_release = false; if (chr->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CHANNEL && @@ -1549,11 +1552,28 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, if (qemuDomainChrPreInsert(vmdef, chr) < 0) goto cleanup; + if (qemuDomainSecretChardevPrepare(conn, priv, chr) < 0) + goto cleanup; + if (cfg->chardevTLS) { + /* Add a secret object in order to access the TLS environment + * if provided of course */ + if (dev->data.tcp.sectype == VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + qemuDomainChardevPrivatePtr chardevPriv = + QEMU_DOMAIN_CHARDEV_PRIVATE(chr); + qemuDomainSecretInfoPtr secinfo = chardevPriv->secinfo; + + if (qemuBuildSecretInfoProps(secinfo, &secprops) < 0) + goto cleanup; + + if (!(secAlias = qemuDomainGetSecretAESAlias(charAlias))) + goto cleanup; + } + if (qemuBuildTLSx509BackendProps(cfg->chardevTLSx509certdir, dev->data.tcp.listen, cfg->chardevTLSx509verify, - NULL, + secAlias, priv->qemuCaps, &props) < 0) goto cleanup; @@ -1565,6 +1585,10 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, qemuDomainObjEnterMonitor(driver, vm); + if (secAlias && qemuMonitorAddObject(priv->mon, "secret", + secAlias, secprops) < 0) + goto failsecobject; + if (objAlias && qemuMonitorAddObject(priv->mon, "tls-creds-x509", objAlias, props) < 0) goto failobject; @@ -1589,6 +1613,8 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, qemuDomainReleaseDeviceAddress(vm, &chr->info, NULL); VIR_FREE(objAlias); virJSONValueFree(props); + VIR_FREE(secAlias); + virJSONValueFree(secprops); VIR_FREE(charAlias); VIR_FREE(devstr); virObjectUnref(cfg); @@ -1601,6 +1627,9 @@ int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, /* Remove the object */ ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); failobject: + /* Remove the secobject */ + ignore_value(qemuMonitorDelObject(priv->mon, secAlias)); + failsecobject: ignore_value(qemuDomainObjExitMonitor(driver, vm)); goto audit; } @@ -4115,6 +4144,7 @@ int qemuDomainDetachChrDevice(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; virDomainDefPtr vmdef = vm->def; virDomainChrDefPtr tmpChr; + virDomainChrSourceDefPtr dev = &chr->source; char *objAlias = NULL; char *devstr = NULL; @@ -4139,6 +4169,15 @@ int qemuDomainDetachChrDevice(virQEMUDriverPtr driver, qemuDomainMarkDeviceForRemoval(vm, &tmpChr->info); qemuDomainObjEnterMonitor(driver, vm); + if (dev->data.tcp.sectype == VIR_SECRET_USAGE_TYPE_PASSPHRASE) { + qemuDomainChardevPrivatePtr chardevPriv = + QEMU_DOMAIN_CHARDEV_PRIVATE(chr); + qemuDomainSecretInfoPtr secinfo = chardevPriv->secinfo; + + if (qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias) < 0) + goto faildel; + } + if (objAlias && qemuMonitorDelObject(priv->mon, objAlias) < 0) goto faildel; diff --git a/src/qemu/qemu_hotplug.h b/src/qemu/qemu_hotplug.h index 165d345..a299ea1 100644 --- a/src/qemu/qemu_hotplug.h +++ b/src/qemu/qemu_hotplug.h @@ -92,7 +92,8 @@ int qemuDomainAttachLease(virQEMUDriverPtr driver, int qemuDomainDetachLease(virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainLeaseDefPtr lease); -int qemuDomainAttachChrDevice(virQEMUDriverPtr driver, +int qemuDomainAttachChrDevice(virConnectPtr conn, + virQEMUDriverPtr driver, virDomainObjPtr vm, virDomainChrDefPtr chr); int qemuDomainDetachChrDevice(virQEMUDriverPtr driver, diff --git a/tests/qemuhotplugtest.c b/tests/qemuhotplugtest.c index 91bf331..c4412b6 100644 --- a/tests/qemuhotplugtest.c +++ b/tests/qemuhotplugtest.c @@ -116,7 +116,7 @@ testQemuHotplugAttach(virDomainObjPtr vm, ret = qemuDomainAttachDeviceDiskLive(NULL, &driver, vm, dev); break; case VIR_DOMAIN_DEVICE_CHR: - ret = qemuDomainAttachChrDevice(&driver, vm, dev->data.chr); + ret = qemuDomainAttachChrDevice(NULL, &driver, vm, dev->data.chr); break; default: VIR_TEST_VERBOSE("device type '%s' cannot be attached\n", -- 2.5.5
participants (1)
-
John Ferlan