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@xxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list