[libvirt] [PATCH v2 2/4] storage_util: Fix qemu qcow[2] encryption convert processing

John Ferlan jferlan at redhat.com
Wed May 16 12:29:29 UTC 2018


As with qcow[2] encryption create processing, the convert processing
requires usage of the "encrypt.key-secret" option and secret objects
for converting an input volume to use qcow[2] encryption. Assuming
an input file sparse.img exists (e.g. qemu-img create -f raw sparse 500K):

$ qemu-img convert -f raw -O qcow2 -o encryption=on sparse.img demo.img
qemu-img: demo.img: error while converting qcow2: Parameter
'encrypt.key-secret' is required for cipher
$

Unlike create processing, the convert processing cannot be done in
one command option, such as:

$ qemu-img convert -f raw -O qcow2 \
    --object secret,id=demo.img_encrypt0,file=/path/to/secretFile \
    -o encrypt.format=aes,encrypt.key-secret=demo.img_encrypt0 \
    sparse.img demo.img
qemu-img: Could not open 'demo.img': Parameter 'encrypt.key-secret' is
required for cipher
$

What convert processing requires is a two step process which first creates
the object using the sizing parameters from the input source and then uses
the --image-opts, -n, and --target-image-opts options along with inline
driver options to describe the input and output files, thus resulting in:

$ qemu-img create -f qcow2 \
    --object secret,id=demo.img_encrypt0,file=/path/to/secretFile \
    -o encrypt.format=aes,encrypt.key-secret=demo.img_encrypt0 \
    demo.img 500K
Formatting 'demo.img', fmt=qcow2 size=512000 encrypt.format=aes
encrypt.key-secret=sec0 cluster_size=65536 lazy_refcounts=off refcount_bits=16
$ qemu-img convert --image-opts -n --target-image-opts \
    --object secret,id=demo.img_encrypt0,file=/path/to/secretFile \
    driver=raw,file.filename=sparse.img \
    driver=qcow2,file.filename=demo.img,encrypt.key-secret=demo.img_encrypt0
$

Similar processing would be used for LUKS encryption, except the
"encrypt.format=aes" is not provided and the "encrypt.key-secret"
is only "key-secret", e.g.:

$ qemu-img create -f luks \
    --object secret,id=demo.img_encrypt0,file=/path/to/secretFile \
    -o key-secret=demo.img_encrypt0 \
    demo.img 500K
Formatting 'demo.img', fmt=luks size=512000 key-secret=demo.img_encrypt0
$ qemu-img convert --image-opts -n --target-image-opts \
    --object secret,id=demo.img_encrypt0,file=/path/to/secretFile \
    driver=raw,file.filename=sparse.img \
    driver=luks,file.filename=demo.img,key-secret=demo.img_encrypt0
$

This patch handles the convert processing by running the processing
in a do..while loop essentially reusing the existing create logic and
arguments to create the target vol from the inputvol and then converting
the inputvol using new arguments.

Signed-off-by: John Ferlan <jferlan at redhat.com>
---
 docs/formatstorageencryption.html.in               |  10 ++
 src/storage/storage_util.c                         | 113 ++++++++++++++++-----
 src/storage/storage_util.h                         |  10 +-
 .../qcow2-from-logical-compat.argv                 |   9 +-
 .../qcow2-nobacking-convert-prealloc-compat.argv   |   9 +-
 .../qcow2-nocapacity-convert-prealloc.argv         |   9 +-
 tests/storagevolxml2argvtest.c                     |  61 ++++++++---
 7 files changed, 178 insertions(+), 43 deletions(-)

diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in
index 23efbf932e..984c7d8b8b 100644
--- a/docs/formatstorageencryption.html.in
+++ b/docs/formatstorageencryption.html.in
@@ -38,6 +38,16 @@
       secret value at the time of volume creation, and store it using the
       specified <code>uuid</code>.
     </p>
+    <p>
+      <span class="since">Since 4.4.0,</span> the command line generated
+      by libvirt to create a <code>default</code> or <code>qcow</code>
+      encrypted volume has changed. This is a result of changes made
+      to qemu-img in QEMU 2.9 which requires different arguments to be
+      provided in order to create a qcow encrypted volume. This change
+      is not compatible with older qemu-img images and there is no plan
+      to provide backwards compatibility. It is <b>strongly recommended</b>
+      to use the "luks" encryption format.
+    </p>
     <h3><a id="StorageEncryptionDefault">"default" format</a></h3>
     <p>
       <code><encryption format="default"/></code> can be specified only
diff --git a/src/storage/storage_util.c b/src/storage/storage_util.c
index a8a6a3e401..29adf0cdbe 100644
--- a/src/storage/storage_util.c
+++ b/src/storage/storage_util.c
@@ -943,12 +943,15 @@ storageBackendCreateQemuImgCheckEncryption(int format,
 
 static int
 storageBackendCreateQemuImgSetInput(virStorageVolDefPtr inputvol,
+                                    virStorageVolEncryptConvertStep convertStep,
                                     struct _virStorageBackendQemuImgInfo *info)
 {
-    if (!(info->inputPath = inputvol->target.path)) {
-        virReportError(VIR_ERR_INVALID_ARG, "%s",
-                       _("missing input volume target path"));
-        return -1;
+    if (convertStep != VIR_STORAGE_VOL_ENCRYPT_CREATE) {
+        if (!(info->inputPath = inputvol->target.path)) {
+            virReportError(VIR_ERR_INVALID_ARG, "%s",
+                           _("missing input volume target path"));
+            return -1;
+        }
     }
 
     info->inputFormat = inputvol->target.format;
@@ -1119,6 +1122,7 @@ static int
 virStorageBackendCreateQemuImgSetInfo(virStoragePoolObjPtr pool,
                                       virStorageVolDefPtr vol,
                                       virStorageVolDefPtr inputvol,
+                                      virStorageVolEncryptConvertStep convertStep,
                                       struct _virStorageBackendQemuImgInfo *info)
 {
     /* Treat output block devices as 'raw' format */
@@ -1166,7 +1170,7 @@ virStorageBackendCreateQemuImgSetInfo(virStoragePoolObjPtr pool,
     }
 
     if (inputvol &&
-        storageBackendCreateQemuImgSetInput(inputvol, info) < 0)
+        storageBackendCreateQemuImgSetInput(inputvol, convertStep, info) < 0)
         return -1;
 
     if (virStorageSourceHasBacking(&vol->target) &&
@@ -1185,6 +1189,27 @@ virStorageBackendCreateQemuImgSetInfo(virStoragePoolObjPtr pool,
 }
 
 
+static void
+virStorageBackendCreateQemuImgCmdEncryptConvert(virCommandPtr cmd,
+                                                virStorageEncryptionPtr enc,
+                                                struct _virStorageBackendQemuImgInfo info)
+{
+    /* source */
+    virCommandAddArgFormat(cmd, "driver=raw,file.filename=%s", info.inputPath);
+
+    /* dest */
+    if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
+        virCommandAddArgFormat(cmd,
+                               "driver=luks,file.filename=%s,key-secret=%s",
+                               info.path, info.secretAlias);
+    } else {
+        virCommandAddArgFormat(cmd,
+                               "driver=qcow2,file.filename=%s,encrypt.key-secret=%s",
+                               info.path, info.secretAlias);
+    }
+}
+
+
 /* Create a qemu-img virCommand from the supplied arguments */
 virCommandPtr
 virStorageBackendCreateQemuImgCmdFromVol(virStoragePoolObjPtr pool,
@@ -1192,7 +1217,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virStoragePoolObjPtr pool,
                                          virStorageVolDefPtr inputvol,
                                          unsigned int flags,
                                          const char *create_tool,
-                                         const char *secretPath)
+                                         const char *secretPath,
+                                         virStorageVolEncryptConvertStep convertStep)
 {
     virCommandPtr cmd = NULL;
     struct _virStorageBackendQemuImgInfo info = {
@@ -1208,22 +1234,30 @@ virStorageBackendCreateQemuImgCmdFromVol(virStoragePoolObjPtr pool,
         .secretPath = secretPath,
         .secretAlias = NULL,
     };
-    virStorageEncryptionInfoDefPtr enc = NULL;
+    virStorageEncryptionPtr enc = NULL;
+    virStorageEncryptionInfoDefPtr encinfo = NULL;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL);
 
-    if (virStorageBackendCreateQemuImgSetInfo(pool, vol, inputvol, &info) < 0)
+    if (virStorageBackendCreateQemuImgSetInfo(pool, vol, inputvol,
+                                              convertStep, &info) < 0)
         goto error;
 
     cmd = virCommandNew(create_tool);
 
-    /* ignore the backing volume when we're converting a volume */
-    if (info.inputPath)
+    /* ignore the backing volume when we're converting a volume
+     * including when we're doing a two step convert during create */
+    if (info.inputPath || convertStep == VIR_STORAGE_VOL_ENCRYPT_CREATE)
         info.backingPath = NULL;
 
-    if (info.inputPath)
+    /* Converting to use encryption is a two step process - step 1 is to
+     * create the image and step 2 is to convert it using special arguments */
+    if (info.inputPath && convertStep == VIR_STORAGE_VOL_ENCRYPT_NONE)
         virCommandAddArgList(cmd, "convert", "-f", info.inputFormatStr,
                              "-O", info.type, NULL);
+    else if (info.inputPath && convertStep == VIR_STORAGE_VOL_ENCRYPT_CONVERT)
+        virCommandAddArgList(cmd, "convert", "--image-opts", "-n",
+                             "--target-image-opts", NULL);
     else
         virCommandAddArgList(cmd, "create", "-f", info.type, NULL);
 
@@ -1241,19 +1275,23 @@ virStorageBackendCreateQemuImgCmdFromVol(virStoragePoolObjPtr pool,
         if (storageBackendCreateQemuImgSecretObject(cmd, info.secretPath,
                                                     info.secretAlias) < 0)
             goto error;
-        enc = &vol->target.encryption->encinfo;
+        enc = vol->target.encryption;
+        encinfo = &enc->encinfo;
     }
 
-    if (storageBackendCreateQemuImgSetOptions(cmd, enc, info) < 0)
-        goto error;
+    if (convertStep != VIR_STORAGE_VOL_ENCRYPT_CONVERT) {
+        if (storageBackendCreateQemuImgSetOptions(cmd, encinfo, info) < 0)
+            goto error;
+        if (info.inputPath)
+            virCommandAddArg(cmd, info.inputPath);
+        virCommandAddArg(cmd, info.path);
+        if (!info.inputPath && (info.size_arg || !info.backingPath))
+            virCommandAddArgFormat(cmd, "%lluK", info.size_arg);
+    } else {
+        virStorageBackendCreateQemuImgCmdEncryptConvert(cmd, enc, info);
+    }
     VIR_FREE(info.secretAlias);
 
-    if (info.inputPath)
-        virCommandAddArg(cmd, info.inputPath);
-    virCommandAddArg(cmd, info.path);
-    if (!info.inputPath && (info.size_arg || !info.backingPath))
-        virCommandAddArgFormat(cmd, "%lluK", info.size_arg);
-
     return cmd;
 
  error:
@@ -1360,14 +1398,15 @@ storageBackendDoCreateQemuImg(virStoragePoolObjPtr pool,
                               virStorageVolDefPtr inputvol,
                               unsigned int flags,
                               const char *create_tool,
-                              const char *secretPath)
+                              const char *secretPath,
+                              virStorageVolEncryptConvertStep convertStep)
 {
     int ret;
     virCommandPtr cmd;
 
     cmd = virStorageBackendCreateQemuImgCmdFromVol(pool, vol, inputvol,
                                                    flags, create_tool,
-                                                   secretPath);
+                                                   secretPath, convertStep);
     if (!cmd)
         return -1;
 
@@ -1388,6 +1427,7 @@ storageBackendCreateQemuImg(virStoragePoolObjPtr pool,
     int ret = -1;
     char *create_tool;
     char *secretPath = NULL;
+    virStorageVolEncryptConvertStep convertStep = VIR_STORAGE_VOL_ENCRYPT_NONE;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
 
@@ -1402,8 +1442,33 @@ storageBackendCreateQemuImg(virStoragePoolObjPtr pool,
     if (storageBackendGenerateSecretData(pool, vol, &secretPath) < 0)
         goto cleanup;
 
-    ret = storageBackendDoCreateQemuImg(pool, vol, inputvol, flags,
-                                        create_tool, secretPath);
+    /* Using an input file for encryption requires a multi-step process
+     * to create an image of the same size as the inputvol and then to
+     * convert the inputvol afterwards. */
+    if (secretPath && inputvol)
+        convertStep = VIR_STORAGE_VOL_ENCRYPT_CREATE;
+
+    do {
+        ret = storageBackendDoCreateQemuImg(pool, vol, inputvol, flags,
+                                            create_tool, secretPath,
+                                            convertStep);
+
+        /* Failure to convert, attempt to delete what we created */
+        if (ret < 0 && convertStep == VIR_STORAGE_VOL_ENCRYPT_CONVERT)
+            ignore_value(virFileRemove(vol->target.path,
+                                       vol->target.perms->uid,
+                                       vol->target.perms->gid));
+
+        if (ret < 0 || convertStep == VIR_STORAGE_VOL_ENCRYPT_NONE)
+            goto cleanup;
+
+        if (convertStep == VIR_STORAGE_VOL_ENCRYPT_CREATE)
+            convertStep = VIR_STORAGE_VOL_ENCRYPT_CONVERT;
+        else if (convertStep == VIR_STORAGE_VOL_ENCRYPT_CONVERT)
+            convertStep = VIR_STORAGE_VOL_ENCRYPT_DONE;
+    } while (convertStep != VIR_STORAGE_VOL_ENCRYPT_DONE);
+
+
  cleanup:
     if (secretPath) {
         unlink(secretPath);
diff --git a/src/storage/storage_util.h b/src/storage/storage_util.h
index 9307702754..6fc8e8972c 100644
--- a/src/storage/storage_util.h
+++ b/src/storage/storage_util.h
@@ -153,13 +153,21 @@ char *virStorageBackendStablePath(virStoragePoolObjPtr pool,
                                   const char *devpath,
                                   bool loop);
 
+typedef enum {
+    VIR_STORAGE_VOL_ENCRYPT_NONE = 0,
+    VIR_STORAGE_VOL_ENCRYPT_CREATE,
+    VIR_STORAGE_VOL_ENCRYPT_CONVERT,
+    VIR_STORAGE_VOL_ENCRYPT_DONE,
+} virStorageVolEncryptConvertStep;
+
 virCommandPtr
 virStorageBackendCreateQemuImgCmdFromVol(virStoragePoolObjPtr pool,
                                          virStorageVolDefPtr vol,
                                          virStorageVolDefPtr inputvol,
                                          unsigned int flags,
                                          const char *create_tool,
-                                         const char *secretPath);
+                                         const char *secretPath,
+                                         virStorageVolEncryptConvertStep convertStep);
 
 int virStorageBackendSCSIFindLUs(virStoragePoolObjPtr pool,
                                  uint32_t scanhost);
diff --git a/tests/storagevolxml2argvdata/qcow2-from-logical-compat.argv b/tests/storagevolxml2argvdata/qcow2-from-logical-compat.argv
index 849c5f0218..46d54d01c6 100644
--- a/tests/storagevolxml2argvdata/qcow2-from-logical-compat.argv
+++ b/tests/storagevolxml2argvdata/qcow2-from-logical-compat.argv
@@ -1,4 +1,9 @@
-qemu-img convert -f raw -O qcow2 \
+qemu-img create -f qcow2 \
 --object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
 -o encrypt.format=aes,encrypt.key-secret=OtherDemo.img_encrypt0,compat=0.10 \
-/dev/HostVG/Swap /var/lib/libvirt/images/OtherDemo.img
+/var/lib/libvirt/images/OtherDemo.img 5242880K
+qemu-img convert --image-opts -n --target-image-opts \
+--object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
+driver=raw,file.filename=/dev/HostVG/Swap \
+driver=qcow2,file.filename=/var/lib/libvirt/images/OtherDemo.img,\
+encrypt.key-secret=OtherDemo.img_encrypt0
diff --git a/tests/storagevolxml2argvdata/qcow2-nobacking-convert-prealloc-compat.argv b/tests/storagevolxml2argvdata/qcow2-nobacking-convert-prealloc-compat.argv
index a95749eafa..b755c1e9c4 100644
--- a/tests/storagevolxml2argvdata/qcow2-nobacking-convert-prealloc-compat.argv
+++ b/tests/storagevolxml2argvdata/qcow2-nobacking-convert-prealloc-compat.argv
@@ -1,5 +1,10 @@
-qemu-img convert -f raw -O qcow2 \
+qemu-img create -f qcow2 \
 --object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
 -o encrypt.format=aes,encrypt.key-secret=OtherDemo.img_encrypt0,\
 preallocation=metadata,compat=0.10 \
-/var/lib/libvirt/images/sparse.img /var/lib/libvirt/images/OtherDemo.img
+/var/lib/libvirt/images/OtherDemo.img 5242880K
+qemu-img convert --image-opts -n --target-image-opts \
+--object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
+driver=raw,file.filename=/var/lib/libvirt/images/sparse.img \
+driver=qcow2,file.filename=/var/lib/libvirt/images/OtherDemo.img,\
+encrypt.key-secret=OtherDemo.img_encrypt0
diff --git a/tests/storagevolxml2argvdata/qcow2-nocapacity-convert-prealloc.argv b/tests/storagevolxml2argvdata/qcow2-nocapacity-convert-prealloc.argv
index 51bdaaf684..fca8cba49b 100644
--- a/tests/storagevolxml2argvdata/qcow2-nocapacity-convert-prealloc.argv
+++ b/tests/storagevolxml2argvdata/qcow2-nocapacity-convert-prealloc.argv
@@ -1,5 +1,10 @@
-qemu-img convert -f raw -O qcow2 \
+qemu-img create -f qcow2 \
 --object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
 -o encrypt.format=aes,encrypt.key-secret=OtherDemo.img_encrypt0,\
 preallocation=falloc,compat=0.10 \
-/var/lib/libvirt/images/sparse.img /var/lib/libvirt/images/OtherDemo.img
+/var/lib/libvirt/images/OtherDemo.img 0K
+qemu-img convert --image-opts -n --target-image-opts \
+--object secret,id=OtherDemo.img_encrypt0,file=/path/to/secretFile \
+driver=raw,file.filename=/var/lib/libvirt/images/sparse.img \
+driver=qcow2,file.filename=/var/lib/libvirt/images/OtherDemo.img,\
+encrypt.key-secret=OtherDemo.img_encrypt0
diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c
index 4286c50c6e..e72e08a7d2 100644
--- a/tests/storagevolxml2argvtest.c
+++ b/tests/storagevolxml2argvtest.c
@@ -43,6 +43,7 @@ testCompareXMLToArgvFiles(bool shouldFail,
                           unsigned long parse_flags)
 {
     char *actualCmdline = NULL;
+    virStorageVolEncryptConvertStep convertStep = VIR_STORAGE_VOL_ENCRYPT_NONE;
     int ret = -1;
 
     virCommandPtr cmd = NULL;
@@ -79,20 +80,56 @@ testCompareXMLToArgvFiles(bool shouldFail,
     testSetVolumeType(vol, def);
     testSetVolumeType(inputvol, inputpool);
 
-    cmd = virStorageBackendCreateQemuImgCmdFromVol(obj, vol,
-                                                   inputvol, flags,
-                                                   create_tool,
-                                                   "/path/to/secretFile");
-    if (!cmd) {
-        if (shouldFail) {
-            virResetLastError();
-            ret = 0;
+    /* Using an input file for encryption requires a multi-step process
+     * to create an image of the same size as the inputvol and then to
+     * convert the inputvol afterwards. Since we only care about the
+     * command line we have to copy code from storageBackendCreateQemuImg
+     * and adjust it for the test needs. */
+    if (inputvol && vol->target.encryption)
+        convertStep = VIR_STORAGE_VOL_ENCRYPT_CREATE;
+
+    do {
+        cmd = virStorageBackendCreateQemuImgCmdFromVol(obj, vol,
+                                                       inputvol, flags,
+                                                       create_tool,
+                                                       "/path/to/secretFile",
+                                                       convertStep);
+        if (!cmd) {
+            if (shouldFail) {
+                virResetLastError();
+                ret = 0;
+            }
+            goto cleanup;
         }
-        goto cleanup;
-    }
 
-    if (!(actualCmdline = virCommandToString(cmd)))
-        goto cleanup;
+        if (convertStep != VIR_STORAGE_VOL_ENCRYPT_CONVERT) {
+            if (!(actualCmdline = virCommandToString(cmd)))
+                goto cleanup;
+        } else {
+            char *createCmdline = actualCmdline;
+            char *cvtCmdline;
+            int rc;
+
+            if (!(cvtCmdline = virCommandToString(cmd)))
+                goto cleanup;
+
+            rc = virAsprintf(&actualCmdline, "%s\n%s",
+                             createCmdline, cvtCmdline);
+
+            VIR_FREE(createCmdline);
+            VIR_FREE(cvtCmdline);
+            if (rc < 0)
+                goto cleanup;
+        }
+
+        if (convertStep == VIR_STORAGE_VOL_ENCRYPT_NONE)
+            convertStep = VIR_STORAGE_VOL_ENCRYPT_DONE;
+        else if (convertStep == VIR_STORAGE_VOL_ENCRYPT_CREATE)
+            convertStep = VIR_STORAGE_VOL_ENCRYPT_CONVERT;
+        else if (convertStep == VIR_STORAGE_VOL_ENCRYPT_CONVERT)
+            convertStep = VIR_STORAGE_VOL_ENCRYPT_DONE;
+
+    } while (convertStep != VIR_STORAGE_VOL_ENCRYPT_DONE);
 
     if (virTestCompareToFile(actualCmdline, cmdline) < 0)
         goto cleanup;
-- 
2.14.3




More information about the libvir-list mailing list