[libvirt] [PATCH v3 2/2] qemu: Utilize qemu secret objects for RBD auth/secret

Peter Krempa pkrempa at redhat.com
Wed May 11 12:21:28 UTC 2016


On Thu, May 05, 2016 at 15:00:41 -0400, John Ferlan wrote:
> Partial fix for:
> https://bugzilla.redhat.com/show_bug.cgi?id=1182074
> 
> If they're available and we need to pass secrets to qemu, then use the
> qemu domain secret object in order to pass the secrets for RBD volumes
> instead of passing the base64 encoded secret on the command line.
> 
> The goal is to make IV secrets the default and have no user interaction
> required in order to allow using the IV mechanism. If the mechanism
> is not available, then fall back to the current plain mechanism using
> a base64 encoded secret.
> 
> New API's:
> 
> qemu_command.c:
>   qemuBuildSecretInfoProps: (private)
>     Generate/return a JSON properties object for the IV secret to
>     be used by both the command building and eventually the hotplug
>     code in order to add the secret object. Code was designed so that
>     in the future perhaps hotplug could use it if it made sense.
> 
>   qemuBuildSecretIVCommandLine (private)
>     Generate and add to the command line the -object secret for the
>     IV secret. This will be required for the subsequent RBD reference
>     to the object.
> 
>   qemuBuildDiskSecinfoCommandLine (private)
>     Handle adding the IV secret object.
> 
> qemu_domain.c
>   qemuDomainSecretSetup: (private)
>     Shim to call either the IV or Plain Setup functions based upon
>     whether IV secrets are possible (we have the encryption API) or not,
>     we have secrets, and of course if the protocol source is RBD.
> 
>     Use the qemuDomainSecretSetup in qemuDomainSecretDiskPrepare and
>     qemuDomainSecretHostdevPrepare to add the secret rather than assuming
>     plain. In the future when iSCSI secrets can use this mechanism for
>     either a disk or hostdev there will be less change.
> 
> Command Building:
> 
>   Adjust the qemuBuildRBDSecinfoURI API's in order to generate the
>   specific command options for an IV secret, such as:
> 
>     -object secret,id=$alias,keyid=$masterKey,data=$base64encodedencrypted,
>             format=base64
>     -drive file=rbd:pool/image:id=myname:auth_supported=cephx\;none:\
>            mon_host=mon1.example.org\:6321,password-secret=$alias,...
> 
>   where the 'id=' value is the secret object alias generated by
>   concatenating the disk alias and "-ivKey0". The 'keyid= $masterKey'
>   is the master key shared with qemu, and the -drive syntax will
>   reference that alias as the 'password-secret'. For the -drive
>   syntax, the 'id=myname' is kept to define the username, while the
>   'key=$base64 encoded secret' is removed.
> 
>   While according to the syntax described for qemu commit '60390a21'
>   or as seen in the email archive:
> 
>     https://lists.gnu.org/archive/html/qemu-devel/2016-01/msg04083.html
> 
>   it is possible to pass a plaintext password via a file, the qemu
>   commit 'ac1d8878' describes the more feature rich 'keyid=' option
>   based upon the shared masterKey.
> 
> Tests:
> 
>   Add mock's for virRandomBytes and gnutls_rnd in order to return a
>   constant stream of '0xff' in the bytes for a non random key in order
>   to generate "constant" values for the secrets so that the tests can
>   use those results to compare results.
> 
> Hotplug:
> 
>   Since the hotplug code doesn't add command line arguments, passing
>   the encoded secret directly to the monitor will suffice.
> 
> Signed-off-by: John Ferlan <jferlan at redhat.com>
> ---
>  src/qemu/qemu_command.c                            | 130 ++++++++++++++++++++-
>  src/qemu/qemu_domain.c                             |  56 +++++++--
>  ...emuxml2argv-disk-drive-network-rbd-auth-IV.args |  31 +++++
>  ...qemuxml2argv-disk-drive-network-rbd-auth-IV.xml |  42 +++++++
>  tests/qemuxml2argvmock.c                           |  31 ++++-
>  tests/qemuxml2argvtest.c                           |  11 ++
>  6 files changed, 290 insertions(+), 11 deletions(-)
>  create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-IV.args
>  create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-IV.xml


I think this patch is doing too much in one place. You can definitely
split out the mocking code.

> 
> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
> index 93aaf2a..9b0bd7a 100644
> --- a/src/qemu/qemu_command.c
> +++ b/src/qemu/qemu_command.c
> @@ -607,6 +607,119 @@ 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.
> + *
> + * Returns 0 on success with the filled in JSON property; otherwise,
> + * returns -1 on failure error message set.
> + */
> +static int
> +qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo,
> +                         const char **type,
> +                         virJSONValuePtr *propsret)
> +{
> +    int ret = -1;
> +    char *keyid = NULL;
> +    virJSONValuePtr props = NULL;
> +
> +    *type = "secret";
> +
> +    if (!(keyid = qemuDomainGetMasterKeyAlias()))
> +        return -1;
> +
> +    if (!(props = virJSONValueNewObject()))
> +        goto cleanup;
> +
> +    if (virJSONValueObjectAdd(props,
> +                              "s:data", secinfo->s.iv.ciphertext,
> +                              "s:keyid", keyid,
> +                              "s:iv", secinfo->s.iv.iv,
> +                              "s:format", "base64", NULL) < 0)
> +        goto cleanup;

You can use virJSONValueObjectCreate instead of the two calls above.

> +
> +    *propsret = props;
> +    props = NULL;
> +    ret = 0;
> +
> + cleanup:
> +    VIR_FREE(keyid);
> +    virJSONValueFree(props);
> +
> +    return ret;
> +}
> +
> +
> +/**
> + * qemuBuildSecretIVCommandLine:
> + * @cmd: the command to modify
> + * @secinfo: pointer to the secret info object
> + *
> + * If the secinfo is available and associated with an IV secret,
> + * then format the command line for the secret object. This object
> + * will be referenced by the device that needs/uses it, so it needs
> + * to be in place first.
> + *
> + * Returns 0 on success, -1 w/ error message on failure
> + */
> +static int
> +qemuBuildSecretIVCommandLine(virCommandPtr cmd,

Why does this contain "IV" you are creating a secret object.

> +                             qemuDomainSecretInfoPtr secinfo)
> +{
> +    int ret = -1;
> +    virJSONValuePtr props = NULL;
> +    const char *type;
> +    char *tmp = NULL;
> +
> +    if (qemuBuildSecretInfoProps(secinfo, &type, &props) < 0)
> +        return -1;
> +
> +    if (!(tmp = qemuBuildObjectCommandlineFromJSON(type, secinfo->s.iv.alias,
> +                                                   props)))
> +        goto cleanup;
> +
> +    virCommandAddArgList(cmd, "-object", tmp, NULL);
> +    ret = 0;
> +
> + cleanup:
> +    virJSONValueFree(props);
> +    VIR_FREE(tmp);
> +
> +    return ret;
> +}
> +
> +
> +/* qemuBuildDiskSecinfoCommandLine:
> + * @cmd: Pointer to the command string
> + * @secinfo: Pointer to a possible secinfo
> + *
> + * Adding an IV secret to the command line for an iSCSI disk currently
> + * does not work. As of qemu 2.6, in order to add the options "user=" and
> + * "password-secret=", one would have to do so using an "-iscsi" command
> + * line option. However, that requires supplying an "id=" that can be used
> + * by some "-drive" command in order to "find" the correct IQN. Unfortunately,
> + * an IQN consists of characters ':' and '/' that cannot be used for an "id=".

I don't think this kind of information belongs into a function comment.
It also doesn't really describe what is going on.

> + *
> + * Returns 0 on success, -1 w/ error on some sort of failure.
> + */
> +static int
> +qemuBuildDiskSecinfoCommandLine(virCommandPtr cmd,
> +                                qemuDomainSecretInfoPtr secinfo)
> +{
> +    /* Not necessary for non IV secrets */
> +    if (!secinfo || secinfo->type != VIR_DOMAIN_SECRET_INFO_TYPE_IV)

Again, what is IV supposed to mean? The declaration of the enum is not
commented.

> +        return 0;
> +
> +    /* For now we'll just build the IV. In the future more may be required
> +     * in order to support iSCSI */

This comment is weird. This patch is dealing with RBD.

> +    return qemuBuildSecretIVCommandLine(cmd, secinfo);
> +}
> +
> +
>  /* qemuBuildGeneralSecinfoURI:
>   * @uri: Pointer to the URI structure to add to
>   * @secinfo: Pointer to the secret info data (if present)
> @@ -677,6 +790,10 @@ qemuBuildRBDSecinfoURI(virBufferPtr buf,
>          break;
>  
>      case VIR_DOMAIN_SECRET_INFO_TYPE_IV:
> +        virBufferEscape(buf, '\\', ":", ":id=%s:auth_supported=cephx\\;none",
> +                        secinfo->s.iv.username);
> +        break;
> +
>      case VIR_DOMAIN_SECRET_INFO_TYPE_LAST:
>          return -1;
>      }
> @@ -1083,6 +1200,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
>      char *source = NULL;
>      int actualType = virStorageSourceGetActualType(disk->src);
>      qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
> +    qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
>  
>      if (idx < 0) {
>          virReportError(VIR_ERR_INTERNAL_ERROR,
> @@ -1164,7 +1282,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
>          break;
>      }
>  
> -    if (qemuGetDriveSourceString(disk->src, diskPriv->secinfo, &source) < 0)
> +    if (qemuGetDriveSourceString(disk->src, secinfo, &source) < 0)
>          goto error;
>  
>      if (source &&
> @@ -1215,6 +1333,11 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
>  
>          virBufferEscape(&opt, ',', ",", "%s,", source);
>  
> +        if (disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD &&
> +            secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_IV) {
> +            virBufferAsprintf(&opt, "password-secret=%s,", secinfo->s.iv.alias);

This could be added to the switch above the place where you are doing
this.

> +        }
> +
>          if (disk->src->format > 0 &&
>              disk->src->type != VIR_STORAGE_TYPE_DIR)
>              virBufferAsprintf(&opt, "format=%s,",
> @@ -1905,6 +2028,8 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
>          virDomainDiskDefPtr disk = def->disks[i];
>          bool withDeviceArg = false;
>          bool deviceFlagMasked = false;
> +        qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
> +        qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
>  
>          /* Unless we have -device, then USB disks need special
>             handling */
> @@ -1947,6 +2072,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
>              break;
>          }
>  
> +        if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0)

qemuBuildDiskSecretCommanLine?

> +            return -1;
> +
>          virCommandAddArg(cmd, "-drive");
>  
>          /* Unfortunately it is not possible to use
> diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
> index b174caa..3e627a5 100644
> --- a/src/qemu/qemu_domain.c
> +++ b/src/qemu/qemu_domain.c
> @@ -897,7 +897,7 @@ qemuDomainSecretPlainSetup(virConnectPtr conn,
>   *
>   * Returns true if we can support the encryption code, false otherwise
>   */
> -static bool ATTRIBUTE_UNUSED
> +static bool
>  qemuDomainSecretHaveEncrypt(void)
>  {
>  #ifdef HAVE_GNUTLS_CIPHER_ENCRYPT
> @@ -920,7 +920,7 @@ qemuDomainSecretHaveEncrypt(void)
>   *
>   * Returns 0 on success, -1 on failure with error message
>   */
> -static int ATTRIBUTE_UNUSED
> +static int
>  qemuDomainSecretIVSetup(virConnectPtr conn,
>                          qemuDomainObjPrivatePtr priv,
>                          qemuDomainSecretInfoPtr secinfo,
> @@ -1030,6 +1030,44 @@ qemuDomainSecretIVSetup(virConnectPtr conn,
>  }
>  
>  
> +/* qemuDomainSecretSetup:
> + * @conn: Pointer to connection
> + * @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
> + * @authdef: Pointer to auth data
> + *
> + * If we have the encryption API present and can support a secret object, then
> + * build the IV secret; otherwise, build the Plain secret. This is the magic
> + * decision point for utilizing the IV secrets for an RBD disk. For now iSCSI
> + * disks and hostdevs will not be able to utilize this mechanism. For details,
> + * see qemuBuildDiskSecinfoCommandLine in qemu_command.c
> + *
> + * Returns 0 on success, -1 on failure
> + */
> +static int
> +qemuDomainSecretSetup(virConnectPtr conn,
> +                      qemuDomainObjPrivatePtr priv,
> +                      qemuDomainSecretInfoPtr secinfo,
> +                      const char *srcalias,
> +                      virStorageNetProtocol protocol,
> +                      virStorageAuthDefPtr authdef)
> +{
> +    if (qemuDomainSecretHaveEncrypt() &&
> +        virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) &&
> +        protocol == VIR_STORAGE_NET_PROTOCOL_RBD) {
> +        if (qemuDomainSecretIVSetup(conn, priv, secinfo,
> +                                    srcalias, protocol, authdef) < 0)
> +            return -1;
> +    } else {
> +        if (qemuDomainSecretPlainSetup(conn, secinfo, protocol, authdef) < 0)
> +            return -1;
> +    }
> +    return 0;

Looks like this should be in the previous patch so that the function
doesn't need to be unused.

> +}
> +
> +
>  /* qemuDomainSecretDiskDestroy:
>   * @disk: Pointer to a disk definition
>   *
> @@ -1058,7 +1096,7 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk)
>   */
>  int
>  qemuDomainSecretDiskPrepare(virConnectPtr conn,
> -                            qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
> +                            qemuDomainObjPrivatePtr priv,
>                              virDomainDiskDefPtr disk)
>  {
>      virStorageSourcePtr src = disk->src;
> @@ -1075,8 +1113,8 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn,
>          if (VIR_ALLOC(secinfo) < 0)
>              return -1;
>  
> -        if (qemuDomainSecretPlainSetup(conn, secinfo, src->protocol,
> -                                       src->auth) < 0)
> +        if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias,
> +                                  src->protocol, src->auth) < 0)
>              goto error;
>  
>          diskPriv->secinfo = secinfo;
> @@ -1119,7 +1157,7 @@ qemuDomainSecretHostdevDestroy(virDomainHostdevDefPtr hostdev)
>   */
>  int
>  qemuDomainSecretHostdevPrepare(virConnectPtr conn,
> -                               qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
> +                               qemuDomainObjPrivatePtr priv,
>                                 virDomainHostdevDefPtr hostdev)
>  {
>      virDomainHostdevSubsysPtr subsys = &hostdev->source.subsys;
> @@ -1140,9 +1178,9 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn,
>              if (VIR_ALLOC(secinfo) < 0)
>                  return -1;
>  
> -            if (qemuDomainSecretPlainSetup(conn, secinfo,
> -                                           VIR_STORAGE_NET_PROTOCOL_ISCSI,
> -                                           iscsisrc->auth) < 0)
> +            if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias,
> +                                      VIR_STORAGE_NET_PROTOCOL_ISCSI,
> +                                      iscsisrc->auth) < 0)
>                  goto error;
>  
>              hostdevPriv->secinfo = secinfo;
> diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-IV.args b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-IV.args
> new file mode 100644
> index 0000000..f6c0906
> --- /dev/null
> +++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-IV.args
> @@ -0,0 +1,31 @@
> +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 \
> +-nodefaults \
> +-monitor unix:/tmp/lib/domain--1-QEMUGuest1/monitor.sock,server,nowait \
> +-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 \
> +-object secret,id=virtio-disk0-ivKey0,\
> +data=/i1QO1S81K4UELoLXmtCNQzxOaE1Tc4DvecFBfyvFKKWUAawbjGD+yZaz9oyEcnW,\
> +keyid=masterKey0,iv=/////////////////////w==,format=base64 \
> +-drive 'file=rbd:pool/image:id=myname:auth_supported=cephx\;none:\
> +mon_host=mon1.example.org\:6321\;mon2.example.org\:6322\;mon3.example.org\:6322,\
> +password-secret=virtio-disk0-ivKey0,format=raw,if=none,id=drive-virtio-disk0' \
> +-device virtio-blk-pci,bus=pci.0,addr=0x3,drive=drive-virtio-disk0,\
> +id=virtio-disk0

[...]

> diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c
> index 1616eed..2bfbf6b 100644
> --- a/tests/qemuxml2argvmock.c
> +++ b/tests/qemuxml2argvmock.c
> @@ -1,5 +1,5 @@
>  /*
> - * Copyright (C) 2014 Red Hat, Inc.
> + * Copyright (C) 2014-2016 Red Hat, Inc.

Doesn't look like we've touched it in 2015 ...

>   *
>   * This library is free software; you can redistribute it and/or
>   * modify it under the terms of the GNU Lesser General Public
> @@ -30,6 +30,13 @@
>  #include "virstring.h"
>  #include "virtpm.h"
>  #include "virutil.h"
> +#include "virrandom.h"
> +#ifdef WITH_GNUTLS
> +# include <gnutls/gnutls.h>
> +# if HAVE_GNUTLS_CRYPTO_H
> +#  include <gnutls/crypto.h>
> +# endif
> +#endif
>  #include <time.h>
>  #include <unistd.h>

If you use the approach I've chosen you won't need to include gnutls
headers.

>  
> @@ -145,3 +152,25 @@ virCommandPassFD(virCommandPtr cmd ATTRIBUTE_UNUSED,
>  {
>      /* nada */
>  }
> +
> +int
> +virRandomBytes(unsigned char *buf,
> +               size_t buflen)
> +{
> +    size_t i;
> +
> +    for (i = 0; i < buflen; i++)
> +        buf[i] = 0xff;
> +
> +    return 0;
> +}
> +
> +#ifdef WITH_GNUTLS
> +int
> +gnutls_rnd(gnutls_rnd_level_t level ATTRIBUTE_UNUSED,
> +           void *data,
> +           size_t len)
> +{
> +    return virRandomBytes(data, len);
> +#endif
> +}

This won't compile without gnutls.

> diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
> index e41444d..1f2577a 100644
> --- a/tests/qemuxml2argvtest.c
> +++ b/tests/qemuxml2argvtest.c

...

> @@ -330,6 +331,14 @@ static int testCompareXMLToArgvFiles(const char *xml,
>          }
>      }
>  
> +    /* Create a fake master key */
> +    if (VIR_ALLOC_N(priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN) < 0)
> +        goto out;
> +
> +    if (virRandomBytes(priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN) < 0)
> +        goto out;
> +    priv->masterKeyLen = QEMU_DOMAIN_MASTER_KEY_LEN;
> +
>      if (!(cmd = qemuProcessCreatePretendCmd(conn, &driver, vm, migrateURI,
>                                              (flags & FLAG_FIPS), false,
>                                              VIR_QEMU_PROCESS_START_COLD))) {

This calls qemuProcessPrepareDomain which calls
qemuDomainMasterKeyCreate so the above call to generating the master key
is not necessary.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: Digital signature
URL: <http://listman.redhat.com/archives/libvir-list/attachments/20160511/f797edff/attachment-0001.sig>


More information about the libvir-list mailing list