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(a)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