[libvirt] [PATCH v3 00/10] Add support for LUKS encrypted devices

v2: http://www.redhat.com/archives/libvir-list/2016-June/msg01691.html Changes since v2 (all as a result of code review) Patch 1: New as a result of review comment regarding virSecretDefFormatUsage Patch 2: Change "id" to "name", fixed the html.in, remove whitespice, generated patch 1 due to virSecretDefFormatUsage comment Patch 3: Altered the html.in, don't believe the review comment for wrong type in qemuProcessGetVolumeQcowPassphrase is right, also modified the testdata output file to follow suggestion for changes made in patch 5 & 6 Patch 4: Was essentially ACK'd, but requires previous patches. Took care of the testdata output file. Patch 5: Adjusted html.in, modified virStorageEncryptionInfoDef to have both cipher_* and ivgen_* params in it - affected other places (and fixed those), Patch 6: Essentially ACK'd (the chmod was removed) Patch 7: NEW - going to need this for hot unplug... Patch 8: Adjusted per review of this series and the other 3 patch series for rbd disk hot plug/unplug Patch 9: NEW - Need to generate a different alias for LUKS Patch 10: Mostly unchanged except to utilze the luks specific alias generation... also moved the unplug to right place. John Ferlan (10): conf: No need to check for usage fields during Format conf: Add new secret type "passphrase" util: Add 'usage' for encryption encryption: Add luks parsing for storageencryption encryption: Add <cipher> and <ivgen> to encryption storage: Add support to create a luks volume qemu: Introduce helper qemuDomainSecretDiskCapable qemu: Add secinfo for hotplug virtio disk qemu: Alter the qemuDomainGetSecretAESAlias to add new arg qemu: Add luks support for domain disk docs/aclpolkit.html.in | 4 + docs/formatsecret.html.in | 62 ++++- docs/formatstorageencryption.html.in | 115 ++++++++- docs/schemas/secret.rng | 10 + docs/schemas/storagecommon.rng | 57 ++++- include/libvirt/libvirt-secret.h | 3 +- src/access/viraccessdriverpolkit.c | 13 ++ src/conf/domain_conf.c | 11 + src/conf/secret_conf.c | 36 ++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 + src/libvirt_private.syms | 1 + src/qemu/qemu_alias.c | 10 +- src/qemu/qemu_alias.h | 3 +- src/qemu/qemu_command.c | 9 + src/qemu/qemu_domain.c | 58 +++-- src/qemu/qemu_domain.h | 3 + src/qemu/qemu_hotplug.c | 123 +++++++++- src/qemu/qemu_process.c | 19 +- src/storage/storage_backend.c | 260 +++++++++++++++++++-- src/storage/storage_backend.h | 3 +- src/storage/storage_backend_fs.c | 10 +- src/storage/storage_backend_gluster.c | 2 + src/util/virqemu.c | 23 ++ src/util/virqemu.h | 6 + src/util/virstorageencryption.c | 166 +++++++++++-- src/util/virstorageencryption.h | 18 +- .../qemuxml2argv-encrypted-disk-usage.args | 24 ++ .../qemuxml2argv-encrypted-disk-usage.xml | 36 +++ .../qemuxml2argv-luks-disk-cipher.args | 36 +++ .../qemuxml2argv-luks-disk-cipher.xml | 45 ++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 +++ tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml | 45 ++++ tests/qemuxml2argvtest.c | 12 +- .../qemuxml2xmlout-encrypted-disk-usage.xml | 1 + .../qemuxml2xmlout-luks-disk-cipher.xml | 1 + .../qemuxml2xmlout-luks-disks.xml | 1 + tests/qemuxml2xmltest.c | 3 + tests/secretxml2xmlin/usage-passphrase.xml | 7 + tests/secretxml2xmltest.c | 1 + tests/storagevolxml2argvtest.c | 3 +- tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++ tests/storagevolxml2xmlin/vol-luks.xml | 21 ++ tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++ tests/storagevolxml2xmlout/vol-luks.xml | 21 ++ tests/storagevolxml2xmltest.c | 2 + 46 files changed, 1267 insertions(+), 105 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml create mode 100644 tests/secretxml2xmlin/usage-passphrase.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks.xml -- 2.5.5

Since the virSecretDefParseUsage ensures each of the fields is present, no need to check during virSecretDefFormatUsage Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/conf/secret_conf.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/conf/secret_conf.c b/src/conf/secret_conf.c index de9e6cf..d510645 100644 --- a/src/conf/secret_conf.c +++ b/src/conf/secret_conf.c @@ -286,23 +286,15 @@ virSecretDefFormatUsage(virBufferPtr buf, break; case VIR_SECRET_USAGE_TYPE_VOLUME: - if (def->usage.volume != NULL) - virBufferEscapeString(buf, "<volume>%s</volume>\n", - def->usage.volume); + virBufferEscapeString(buf, "<volume>%s</volume>\n", def->usage.volume); break; case VIR_SECRET_USAGE_TYPE_CEPH: - if (def->usage.ceph != NULL) { - virBufferEscapeString(buf, "<name>%s</name>\n", - def->usage.ceph); - } + virBufferEscapeString(buf, "<name>%s</name>\n", def->usage.ceph); break; case VIR_SECRET_USAGE_TYPE_ISCSI: - if (def->usage.target != NULL) { - virBufferEscapeString(buf, "<target>%s</target>\n", - def->usage.target); - } + virBufferEscapeString(buf, "<target>%s</target>\n", def->usage.target); break; default: -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:30PM -0400, John Ferlan wrote:
Since the virSecretDefParseUsage ensures each of the fields is present, no need to check during virSecretDefFormatUsage
Additionally, virBufferEscapeString is a no-op with a NULL argument.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/conf/secret_conf.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-)
ACK Jan

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'> <name>mumblyfratz</name> </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 | 22 +++++++++++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 +++ tests/secretxml2xmlin/usage-passphrase.xml | 7 ++++ tests/secretxml2xmltest.c | 1 + 10 files changed, 119 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..4d0307d 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_name</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 c39d2a7..0d53c11 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'> + <name>name_example</name> + </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 name_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..cac8560 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='name'> + <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..99b867f 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_name", secret->usage.name, + NULL, + }; + + return virAccessDriverPolkitCheck(manager, + "secret", + virAccessPermSecretTypeToString(perm), + attrs); + } break; } } diff --git a/src/conf/secret_conf.c b/src/conf/secret_conf.c index d510645..a973aa9 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.name; + 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.name); + break; + default: VIR_ERROR(_("unexpected secret usage type %d"), def->usage_type); break; @@ -145,6 +153,14 @@ virSecretDefParseUsage(xmlXPathContextPtr ctxt, } break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + if (!(def->usage.name = virXPathString("string(./usage/name)", ctxt))) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("passphrase usage specified, but name is missing")); + return -1; + } + break; + default: virReportError(VIR_ERR_INTERNAL_ERROR, _("unexpected secret usage type %d"), @@ -297,6 +313,10 @@ virSecretDefFormatUsage(virBufferPtr buf, virBufferEscapeString(buf, "<target>%s</target>\n", def->usage.target); break; + case VIR_SECRET_USAGE_TYPE_PASSPHRASE: + virBufferEscapeString(buf, "<name>%s</name>\n", def->usage.name); + 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..c34880f 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 *name; } usage; }; diff --git a/src/conf/virsecretobj.c b/src/conf/virsecretobj.c index 30a5e80..6714a00 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.name, 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..2b94b80 --- /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'> + <name>mumblyfratz</name> + </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

On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </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 | 22 +++++++++++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 +++ tests/secretxml2xmlin/usage-passphrase.xml | 7 ++++ tests/secretxml2xmltest.c | 1 + 10 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 tests/secretxml2xmlin/usage-passphrase.xml
ACK Jan

On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </usage> </secret>
I'm not seeing the purpose of adding this secret usage type. It also seems quite different from the usage types we have already. The essential purpose of the usage 'name' is to allow you to figure out what corresponding libvirt object is using the secret. So for example with usage type=volume, the name refers to the disk path of the volume. With usage type=iscsi or type=ceph, the name refers to the server name. This usage type=passphrase is not directly associating the secret with anything, and doesn't appear to have any defined sematics for what the 'name' should contain or refer to. This all feels quite odd & possibly wrong to me. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 07/04/2016 09:42 AM, Daniel P. Berrange wrote:
On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </usage> </secret>
I'm not seeing the purpose of adding this secret usage type. It also seems quite different from the usage types we have already.
The essential purpose of the usage 'name' is to allow you to figure out what corresponding libvirt object is using the secret. So for example with usage type=volume, the name refers to the disk path of the volume. With usage type=iscsi or type=ceph, the name refers to the server name.
This usage type=passphrase is not directly associating the secret with anything, and doesn't appear to have any defined sematics for what the 'name' should contain or refer to.
This all feels quite odd & possibly wrong to me.
FWIW: I'm on PTO this week, but I do have a few minutes of time to provide some feedback... This type of secret started out in my own branches as a "luks" secret, since that's what it was designed to (at least) initially support. After starting to think and work with the TLS code, I modified it to a "key" secret - mainly because they were the essentially the same type of secret. At that time I did consider passphrase, but wasn't convinced it was the best name, so "key" was it (plus less characters to type). I also figured it was better to use the same type of secret since they provided essentially the same functionality - a key/passphrase to be provided for some other object to unlock access to the object (for lack of a better term, at this moment). Both series were posted and I noted the common parts of both. The luks code was reviewed and the suggestion was to use "passphrase", so I went that way. If it needs to change again, that can be done when I'm back next week (or by someone before I get back). If the two need to be separated that's fine too. They'll share a lot of similarities though. I think other than the target service object they support, the iscsi and ceph secret are fairly similar. So it's not unprecedented. If they are separated, then does that mean there's a TCPTLS secret, a MIGRATIONTLS secret, and an NBDTLS secret? John

On Tue, Jul 05, 2016 at 09:33:30AM -0400, John Ferlan wrote:
On 07/04/2016 09:42 AM, Daniel P. Berrange wrote:
On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </usage> </secret>
I'm not seeing the purpose of adding this secret usage type. It also seems quite different from the usage types we have already.
The essential purpose of the usage 'name' is to allow you to figure out what corresponding libvirt object is using the secret. So for example with usage type=volume, the name refers to the disk path of the volume. With usage type=iscsi or type=ceph, the name refers to the server name.
This usage type=passphrase is not directly associating the secret with anything, and doesn't appear to have any defined sematics for what the 'name' should contain or refer to.
This all feels quite odd & possibly wrong to me.
FWIW: I'm on PTO this week, but I do have a few minutes of time to provide some feedback...
NP, we can wait until you return to resolve it - as long as we decide before the 2.1 release we're fine.
This type of secret started out in my own branches as a "luks" secret, since that's what it was designed to (at least) initially support.
After starting to think and work with the TLS code, I modified it to a "key" secret - mainly because they were the essentially the same type of secret. At that time I did consider passphrase, but wasn't convinced it was the best name, so "key" was it (plus less characters to type). I also figured it was better to use the same type of secret since they provided essentially the same functionality - a key/passphrase to be provided for some other object to unlock access to the object (for lack of a better term, at this moment).
Both series were posted and I noted the common parts of both. The luks code was reviewed and the suggestion was to use "passphrase", so I went that way.
Ok, so for LUKS i'd expect us to continue to just use the existing USAGE_TYPE_VOLUME we already have for this purpose.
If it needs to change again, that can be done when I'm back next week (or by someone before I get back). If the two need to be separated that's fine too. They'll share a lot of similarities though. I think other than the target service object they support, the iscsi and ceph secret are fairly similar. So it's not unprecedented. If they are separated, then does that mean there's a TCPTLS secret, a MIGRATIONTLS secret, and an NBDTLS secret?
The secret usage is associated with a particular type of object requiring credentials. In that case the object type is a TLS key. The fact that you can use the TLS key for different services is not relevant. So I'd say the usage would be USAGE_TYPE_TLS_KEY and the data associated with it would be the path of the TLS key pem file. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 07/05/2016 09:37 AM, Daniel P. Berrange wrote:
On Tue, Jul 05, 2016 at 09:33:30AM -0400, John Ferlan wrote:
On 07/04/2016 09:42 AM, Daniel P. Berrange wrote:
On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </usage> </secret>
I'm not seeing the purpose of adding this secret usage type. It also seems quite different from the usage types we have already.
The essential purpose of the usage 'name' is to allow you to figure out what corresponding libvirt object is using the secret. So for example with usage type=volume, the name refers to the disk path of the volume. With usage type=iscsi or type=ceph, the name refers to the server name.
This usage type=passphrase is not directly associating the secret with anything, and doesn't appear to have any defined sematics for what the 'name' should contain or refer to.
This all feels quite odd & possibly wrong to me.
FWIW: I'm on PTO this week, but I do have a few minutes of time to provide some feedback...
NP, we can wait until you return to resolve it - as long as we decide before the 2.1 release we're fine.
This type of secret started out in my own branches as a "luks" secret, since that's what it was designed to (at least) initially support.
After starting to think and work with the TLS code, I modified it to a "key" secret - mainly because they were the essentially the same type of secret. At that time I did consider passphrase, but wasn't convinced it was the best name, so "key" was it (plus less characters to type). I also figured it was better to use the same type of secret since they provided essentially the same functionality - a key/passphrase to be provided for some other object to unlock access to the object (for lack of a better term, at this moment).
Both series were posted and I noted the common parts of both. The luks code was reviewed and the suggestion was to use "passphrase", so I went that way.
Ok, so for LUKS i'd expect us to continue to just use the existing USAGE_TYPE_VOLUME we already have for this purpose.
That then requires the "usage" of a <secret> in the domain xml to list the volume path. So rather than: <encryption format='luks'> <secret type='passphrase' usage='luks_example'/> </encryption> it'd be: <encryption format='luks'> <secret type='volume' usage='$LUKS_VOLUME_PATH'/> </encryption> (or of course uuid='$UUID') I was looking to have a "more clear" delineation between a secret that "could be" generated automagically (e.g. some randomly generated passphrase) for a qcow volume and one that "someone" defines/sets for a luks volume. The automagic generation is done in virStorageGenerateQcowEncryption and virStorageGenerateQcowPassphrase John
If it needs to change again, that can be done when I'm back next week (or by someone before I get back). If the two need to be separated that's fine too. They'll share a lot of similarities though. I think other than the target service object they support, the iscsi and ceph secret are fairly similar. So it's not unprecedented. If they are separated, then does that mean there's a TCPTLS secret, a MIGRATIONTLS secret, and an NBDTLS secret?
The secret usage is associated with a particular type of object requiring credentials. In that case the object type is a TLS key. The fact that you can use the TLS key for different services is not relevant. So I'd say the usage would be USAGE_TYPE_TLS_KEY and the data associated with it would be the path of the TLS key pem file.
Regards, Daniel

On Tue, Jul 05, 2016 at 10:16:10AM -0400, John Ferlan wrote:
On 07/05/2016 09:37 AM, Daniel P. Berrange wrote:
On Tue, Jul 05, 2016 at 09:33:30AM -0400, John Ferlan wrote:
On 07/04/2016 09:42 AM, Daniel P. Berrange wrote:
On Fri, Jun 24, 2016 at 04:53:31PM -0400, John Ferlan wrote:
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'> <name>mumblyfratz</name> </usage> </secret>
I'm not seeing the purpose of adding this secret usage type. It also seems quite different from the usage types we have already.
The essential purpose of the usage 'name' is to allow you to figure out what corresponding libvirt object is using the secret. So for example with usage type=volume, the name refers to the disk path of the volume. With usage type=iscsi or type=ceph, the name refers to the server name.
This usage type=passphrase is not directly associating the secret with anything, and doesn't appear to have any defined sematics for what the 'name' should contain or refer to.
This all feels quite odd & possibly wrong to me.
FWIW: I'm on PTO this week, but I do have a few minutes of time to provide some feedback...
NP, we can wait until you return to resolve it - as long as we decide before the 2.1 release we're fine.
This type of secret started out in my own branches as a "luks" secret, since that's what it was designed to (at least) initially support.
After starting to think and work with the TLS code, I modified it to a "key" secret - mainly because they were the essentially the same type of secret. At that time I did consider passphrase, but wasn't convinced it was the best name, so "key" was it (plus less characters to type). I also figured it was better to use the same type of secret since they provided essentially the same functionality - a key/passphrase to be provided for some other object to unlock access to the object (for lack of a better term, at this moment).
Both series were posted and I noted the common parts of both. The luks code was reviewed and the suggestion was to use "passphrase", so I went that way.
Ok, so for LUKS i'd expect us to continue to just use the existing USAGE_TYPE_VOLUME we already have for this purpose.
That then requires the "usage" of a <secret> in the domain xml to list the volume path. So rather than:
<encryption format='luks'> <secret type='passphrase' usage='luks_example'/> </encryption>
it'd be:
<encryption format='luks'> <secret type='volume' usage='$LUKS_VOLUME_PATH'/> </encryption>
(or of course uuid='$UUID')
I was looking to have a "more clear" delineation between a secret that "could be" generated automagically (e.g. some randomly generated passphrase) for a qcow volume and one that "someone" defines/sets for a luks volume.
Why would we want any such delineation ? Regardless of where the secret is generated, it is still used in the same functional manner, so I don't see an obvious benefit to distinguish them ? Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

Ok, so for LUKS i'd expect us to continue to just use the existing USAGE_TYPE_VOLUME we already have for this purpose.
That then requires the "usage" of a <secret> in the domain xml to list the volume path. So rather than:
<encryption format='luks'> <secret type='passphrase' usage='luks_example'/> </encryption>
it'd be:
<encryption format='luks'> <secret type='volume' usage='$LUKS_VOLUME_PATH'/> </encryption>
(or of course uuid='$UUID')
I was looking to have a "more clear" delineation between a secret that "could be" generated automagically (e.g. some randomly generated passphrase) for a qcow volume and one that "someone" defines/sets for a luks volume.
Why would we want any such delineation ? Regardless of where the secret is generated, it is still used in the same functional manner, so I don't see an obvious benefit to distinguish them ?
One is generated for you (essentially) and one is provided by someone in order to unlock their luks volume. I guess I see a functional delineation between the two, although I do understand what your viewpoint is on this. There may have been another reason I felt the need to delineate, but that would mean more time to put the qcow volume encryption code back into my head than I have to process right now... John

On Tue, Jul 05, 2016 at 10:44:30AM -0400, John Ferlan wrote:
Ok, so for LUKS i'd expect us to continue to just use the existing USAGE_TYPE_VOLUME we already have for this purpose.
That then requires the "usage" of a <secret> in the domain xml to list the volume path. So rather than:
<encryption format='luks'> <secret type='passphrase' usage='luks_example'/> </encryption>
it'd be:
<encryption format='luks'> <secret type='volume' usage='$LUKS_VOLUME_PATH'/> </encryption>
(or of course uuid='$UUID')
I was looking to have a "more clear" delineation between a secret that "could be" generated automagically (e.g. some randomly generated passphrase) for a qcow volume and one that "someone" defines/sets for a luks volume.
Why would we want any such delineation ? Regardless of where the secret is generated, it is still used in the same functional manner, so I don't see an obvious benefit to distinguish them ?
One is generated for you (essentially) and one is provided by someone in order to unlock their luks volume. I guess I see a functional delineation between the two, although I do understand what your viewpoint is on this. There may have been another reason I felt the need to delineate, but that would mean more time to put the qcow volume encryption code back into my head than I have to process right now...
From the POV of the code that is consuming the secrets, there absolutely no functional difference. The usage type is associating with the consuming so I think the difference in generation approach is really irrelevant for
the consumer. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

In order to use more common code and set up for a future type, modify the encryption secret to allow the "usage" attribute or the "uuid" attribute to define the secret. The "usage" in the case of a volume secret would be the path to the volume. This code will make use of the virSecretLookup{Parse|Format}Secret common code. Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 12 +++++--- docs/schemas/storagecommon.rng | 11 +++++-- src/qemu/qemu_process.c | 13 +++----- src/storage/storage_backend.c | 3 +- src/storage/storage_backend_fs.c | 3 +- src/util/virstorageencryption.c | 26 ++++++---------- src/util/virstorageencryption.h | 3 +- .../qemuxml2argv-encrypted-disk-usage.args | 24 +++++++++++++++ .../qemuxml2argv-encrypted-disk-usage.xml | 36 ++++++++++++++++++++++ tests/qemuxml2argvtest.c | 1 + .../qemuxml2xmlout-encrypted-disk-usage.xml | 1 + tests/qemuxml2xmltest.c | 1 + 12 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in index 04c3346..540ab18 100644 --- a/docs/formatstorageencryption.html.in +++ b/docs/formatstorageencryption.html.in @@ -25,10 +25,14 @@ <p> The <code>encryption</code> tag can currently contain a sequence of <code>secret</code> tags, each with mandatory attributes <code>type</code> - and <code>uuid</code>. The only currently defined value of - <code>type</code> is <code>passphrase</code>. <code>uuid</code> - refers to a secret known to libvirt. libvirt can use a secret value - previously set using <code>virSecretSetValue()</code>, or, if supported + and either <code>uuid</code> or <code>usage</code> + (<span class="since">since 2.0.0</span>). The only currently defined + value of <code>type</code> is <code>passphrase</code>. The + <code>uuid</code> is "uuid" of the <code>secret</code> while + <code>usage</code> is the value "usage" subelement field. + A secret value can be set in libvirt by the + <a href="html/libvirt-libvirt-secret.html#virSecretSetValue"> + <code>virSecretSetValue</code></a> API. Alternatively, if supported by the particular volume format and driver, automatically generate a secret value at the time of volume creation, and store it using the specified <code>uuid</code>. diff --git a/docs/schemas/storagecommon.rng b/docs/schemas/storagecommon.rng index 7c04462..c5b71de 100644 --- a/docs/schemas/storagecommon.rng +++ b/docs/schemas/storagecommon.rng @@ -27,9 +27,14 @@ <value>passphrase</value> </choice> </attribute> - <attribute name='uuid'> - <ref name="UUID"/> - </attribute> + <choice> + <attribute name='uuid'> + <ref name="UUID"/> + </attribute> + <attribute name='usage'> + <text/> + </attribute> + </choice> </element> </define> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 63da600..7d56ec8 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -70,6 +70,7 @@ #include "virnuma.h" #include "virstring.h" #include "virhostdev.h" +#include "secret_util.h" #include "storage/storage_driver.h" #include "configmake.h" #include "nwfilter_conf.h" @@ -377,7 +378,6 @@ qemuProcessGetVolumeQcowPassphrase(virConnectPtr conn, char **secretRet, size_t *secretLen) { - virSecretPtr secret; char *passphrase; unsigned char *data; size_t size; @@ -416,14 +416,9 @@ qemuProcessGetVolumeQcowPassphrase(virConnectPtr conn, goto cleanup; } - secret = conn->secretDriver->secretLookupByUUID(conn, - enc->secrets[0]->uuid); - if (secret == NULL) - goto cleanup; - data = conn->secretDriver->secretGetValue(secret, &size, 0, - VIR_SECRET_GET_VALUE_INTERNAL_CALL); - virObjectUnref(secret); - if (data == NULL) + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_VOLUME, + &data, &size) < 0) goto cleanup; if (memchr(data, '\0', size) != NULL) { diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 5adf1fd..d6a451d 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -648,7 +648,8 @@ virStorageGenerateQcowEncryption(virConnectPtr conn, goto cleanup; enc_secret->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; - memcpy(enc_secret->uuid, secret->uuid, VIR_UUID_BUFLEN); + enc_secret->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; + memcpy(enc_secret->seclookupdef.u.uuid, secret->uuid, VIR_UUID_BUFLEN); enc->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; enc->secrets[0] = enc_secret; /* Space for secrets[0] allocated above */ enc_secret = NULL; diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 44dabf4..839a2c7 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -1312,7 +1312,8 @@ virStorageBackendFileSystemLoadDefaultSecrets(virConnectPtr conn, vol->target.encryption->secrets[0] = encsec; encsec->type = VIR_STORAGE_ENCRYPTION_SECRET_TYPE_PASSPHRASE; - virSecretGetUUID(sec, encsec->uuid); + encsec->seclookupdef.type = VIR_SECRET_LOOKUP_TYPE_UUID; + virSecretGetUUID(sec, encsec->seclookupdef.u.uuid); virObjectUnref(sec); return 0; diff --git a/src/util/virstorageencryption.c b/src/util/virstorageencryption.c index 8105158..afb44da 100644 --- a/src/util/virstorageencryption.c +++ b/src/util/virstorageencryption.c @@ -34,6 +34,7 @@ #include "virerror.h" #include "viruuid.h" #include "virfile.h" +#include "virsecret.h" #define VIR_FROM_THIS VIR_FROM_STORAGE @@ -114,6 +115,7 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt, virStorageEncryptionSecretPtr ret; char *type_str = NULL; char *uuidstr = NULL; + char *usagestr = NULL; if (VIR_ALLOC(ret) < 0) return NULL; @@ -133,21 +135,12 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt, type_str); goto cleanup; } - VIR_FREE(type_str); - if ((uuidstr = virXPathString("string(./@uuid)", ctxt))) { - if (virUUIDParse(uuidstr, ret->uuid) < 0) { - virReportError(VIR_ERR_XML_ERROR, - _("malformed volume encryption uuid '%s'"), - uuidstr); - goto cleanup; - } - VIR_FREE(uuidstr); - } else { - virReportError(VIR_ERR_XML_ERROR, "%s", - _("missing volume encryption uuid")); + if (virSecretLookupParseSecret(node, &ret->seclookupdef) < 0) goto cleanup; - } + + VIR_FREE(type_str); + ctxt->node = old_node; return ret; @@ -155,6 +148,7 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt, VIR_FREE(type_str); virStorageEncryptionSecretFree(ret); VIR_FREE(uuidstr); + VIR_FREE(usagestr); ctxt->node = old_node; return NULL; } @@ -244,7 +238,6 @@ virStorageEncryptionSecretFormat(virBufferPtr buf, virStorageEncryptionSecretPtr secret) { const char *type; - char uuidstr[VIR_UUID_STRING_BUFLEN]; if (!(type = virStorageEncryptionSecretTypeToString(secret->type))) { virReportError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -252,9 +245,8 @@ virStorageEncryptionSecretFormat(virBufferPtr buf, return -1; } - virUUIDFormat(secret->uuid, uuidstr); - virBufferAsprintf(buf, "<secret type='%s' uuid='%s'/>\n", - type, uuidstr); + virSecretLookupFormatSecret(buf, type, &secret->seclookupdef); + return 0; } diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h index 04641b1..c68c66e 100644 --- a/src/util/virstorageencryption.h +++ b/src/util/virstorageencryption.h @@ -25,6 +25,7 @@ # include "internal.h" # include "virbuffer.h" +# include "virsecret.h" # include "virutil.h" # include <libxml/tree.h> @@ -40,7 +41,7 @@ typedef struct _virStorageEncryptionSecret virStorageEncryptionSecret; typedef virStorageEncryptionSecret *virStorageEncryptionSecretPtr; struct _virStorageEncryptionSecret { int type; /* virStorageEncryptionSecretType */ - unsigned char uuid[VIR_UUID_BUFLEN]; + virSecretLookupTypeDef seclookupdef; }; typedef enum { diff --git a/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args b/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args new file mode 100644 index 0000000..4371413 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args @@ -0,0 +1,24 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name encryptdisk \ +-S \ +-M pc \ +-m 1024 \ +-smp 1 \ +-uuid 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 \ +-nographic \ +-nodefaults \ +-monitor unix:/tmp/lib/domain--1-encryptdisk/monitor.sock,server,nowait \ +-no-acpi \ +-boot c \ +-usb \ +-drive file=/storage/guest_disks/encryptdisk,format=qcow2,if=none,\ +id=drive-virtio-disk0 \ +-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\ +id=virtio-disk0 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml b/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml new file mode 100644 index 0000000..ec6413f --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml @@ -0,0 +1,36 @@ +<domain type='qemu'> + <name>encryptdisk</name> + <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>524288</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='file' device='disk'> + <driver name='qemu' type='qcow2'/> + <source file='/storage/guest_disks/encryptdisk'/> + <target dev='vda' bus='virtio'/> + <encryption format='qcow'> + <secret type='passphrase' usage='/storage/guest_disks/encryptdisk'/> + </encryption> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <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/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index a4b8bf4..ca52ab0 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -1341,6 +1341,7 @@ mymain(void) driver.caps->host.cpu = cpuDefault; DO_TEST("encrypted-disk", NONE); + DO_TEST("encrypted-disk-usage", NONE); DO_TEST("memtune", NONE); DO_TEST("memtune-unlimited", NONE); diff --git a/tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml new file mode 120000 index 0000000..824120a --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml @@ -0,0 +1 @@ +../qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml \ No newline at end of file diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 7db9cb7..d045fd4 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -501,6 +501,7 @@ mymain(void) DO_TEST("pci-serial-dev-chardev"); DO_TEST("encrypted-disk"); + DO_TEST("encrypted-disk-usage"); DO_TEST("memtune"); DO_TEST("memtune-unlimited"); DO_TEST("blkiotune"); -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:32PM -0400, John Ferlan wrote:
In order to use more common code and set up for a future type, modify the encryption secret to allow the "usage" attribute or the "uuid" attribute to define the secret. The "usage" in the case of a volume secret would be the path to the volume.
s/would/could/? IIUC it can be an arbitrary string.
This code will make use of the virSecretLookup{Parse|Format}Secret common code.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 12 +++++--- docs/schemas/storagecommon.rng | 11 +++++-- src/qemu/qemu_process.c | 13 +++----- src/storage/storage_backend.c | 3 +- src/storage/storage_backend_fs.c | 3 +- src/util/virstorageencryption.c | 26 ++++++---------- src/util/virstorageencryption.h | 3 +- .../qemuxml2argv-encrypted-disk-usage.args | 24 +++++++++++++++ .../qemuxml2argv-encrypted-disk-usage.xml | 36 ++++++++++++++++++++++ tests/qemuxml2argvtest.c | 1 + .../qemuxml2xmlout-encrypted-disk-usage.xml | 1 + tests/qemuxml2xmltest.c | 1 + 12 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml
ACK Jan

On 06/25/2016 01:27 AM, Ján Tomko wrote:
On Fri, Jun 24, 2016 at 04:53:32PM -0400, John Ferlan wrote:
In order to use more common code and set up for a future type, modify the encryption secret to allow the "usage" attribute or the "uuid" attribute to define the secret. The "usage" in the case of a volume secret would be the path to the volume.
s/would/could/?
IIUC it can be an arbitrary string.
In 3 out of the 4 cases, I agree; however, for qcow storage encryption, sadly it's the path to the volume, see virStorageGenerateQcowEncryption: def->usage_type = VIR_SECRET_USAGE_TYPE_VOLUME; if (VIR_STRDUP(def->usage.volume, vol->target.path) < 0) goto cleanup; This then would be carried over into virSecretObjListFindByUsageLocked where it searches through the secret objects comparing the 'value': <secret... <usage ...> <{volume, name, target, name}>value</{}> with the usage 'value' here: <auth...> or <encryption...> <secret type='{passphrase|ceph|iscsi|passphrase}' usage='value'/> (FYI: the on list TLS/TCP will have <serial type='tcp'> for <secret>) So for qcow disk encryption one has: <disk...> ... <source file='$path'> <driver name='qemu' type='{qcow|qcow2}'/> ... <encryption> <secret type='passphrase' {uuid,usage}='$string'/> </encryption> ... </disk> TBH: I suppose we "could" change that to be "any" string, but since this type of secret is not useful, it's probably not worth the effort. I added the usage field for two reasons #1 a comment in some changes a while back that we should try to use virSecretGetSecretString for all callers instead of conn->secretDriver->secretGetValue and #2 since I was going to reuse the <encryption> for <driver name='qemu' type='luks'/> processing and I wanted to be able to allow someone to provide "whatever" string they wanted there. I will adjust the commit message to provide details as to why for this one type of usage model it's the volume name. John
This code will make use of the virSecretLookup{Parse|Format}Secret common code.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 12 +++++--- docs/schemas/storagecommon.rng | 11 +++++-- src/qemu/qemu_process.c | 13 +++----- src/storage/storage_backend.c | 3 +- src/storage/storage_backend_fs.c | 3 +- src/util/virstorageencryption.c | 26 ++++++---------- src/util/virstorageencryption.h | 3 +- .../qemuxml2argv-encrypted-disk-usage.args | 24 +++++++++++++++ .../qemuxml2argv-encrypted-disk-usage.xml | 36 ++++++++++++++++++++++ tests/qemuxml2argvtest.c | 1 + .../qemuxml2xmlout-encrypted-disk-usage.xml | 1 + tests/qemuxml2xmltest.c | 1 + 12 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml
ACK
Jan

Add parse and format of the luks/passphrase secret including tests for volume XML parsing. Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatsecret.html.in | 7 +++- docs/formatstorageencryption.html.in | 26 ++++++++++++- docs/schemas/storagecommon.rng | 2 + src/qemu/qemu_process.c | 6 +++ src/storage/storage_backend.c | 3 +- src/storage/storage_backend_fs.c | 7 +++- src/storage/storage_backend_gluster.c | 2 + src/util/virstorageencryption.c | 2 +- src/util/virstorageencryption.h | 1 + tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml | 45 ++++++++++++++++++++++ .../qemuxml2xmlout-luks-disks.xml | 1 + tests/qemuxml2xmltest.c | 1 + tests/storagevolxml2xmlin/vol-luks.xml | 21 ++++++++++ tests/storagevolxml2xmlout/vol-luks.xml | 21 ++++++++++ tests/storagevolxml2xmltest.c | 1 + 15 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks.xml diff --git a/docs/formatsecret.html.in b/docs/formatsecret.html.in index 0d53c11..928f910 100644 --- a/docs/formatsecret.html.in +++ b/docs/formatsecret.html.in @@ -248,7 +248,12 @@ <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. + order to perform its authentication. For example, this secret will + be used either by the + <a href="formatstorage.html#StorageVol">storage volume</a> in order to + provide the passphrase to encrypt a luks volume or by the + <a href="formatdomain.html#elementsDisks">disk device</a> in order to + provide the passphrase to decrypt the luks volume for usage. <span class="since">Since 2.0.0</span>. The following is an example of a secret.xml file: </p> diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in index 540ab18..be73054 100644 --- a/docs/formatstorageencryption.html.in +++ b/docs/formatstorageencryption.html.in @@ -56,8 +56,20 @@ the <code>secret</code> element is not present during volume creation, a secret is automatically generated and attached to the volume. </p> + <h3><a name="StorageEncryptionLuks">"luks" format</a></h3> + <p> + The <code>luks</code> format is specific to a luks encrypted volume + and the secret used in order to either encrypt or decrypt the volume. + A single <code><secret type='passphrase'...></code> element is + expected. The secret may be referenced via either a <code>uuid</code> or + <code>usage</code> attribute. One of the two must be present. When + present for volume creation, the secret will be used in order for + volume encryption. When present for domain usage, the secret will + be used as the passphrase to decrypt the volume. + <span class="since">Since 2.0.0</span>. + </p> - <h2><a name="example">Example</a></h2> + <h2><a name="example">Examples</a></h2> <p> Here is a simple example, specifying use of the <code>qcow</code> format: @@ -67,5 +79,17 @@ <encryption format='qcow'> <secret type='passphrase' uuid='c1f11a6d-8c5d-4a3e-ac7a-4e171c5e0d4a' /> </encryption></pre> + + <p> + Here is a simple example, specifying use of the <code>luks</code> format + where it's assumed that a <code>secret</code> has been defined using a + <code>usage</code> element with a <code>id</code> of "luks_example": + </p> + <pre> + <encryption format='luks'> + <secret type='passphrase' usage='luks_example'/> + </encryption> + </pre> + </body> </html> diff --git a/docs/schemas/storagecommon.rng b/docs/schemas/storagecommon.rng index c5b71de..63b55b4 100644 --- a/docs/schemas/storagecommon.rng +++ b/docs/schemas/storagecommon.rng @@ -12,6 +12,7 @@ <choice> <value>default</value> <value>qcow</value> + <value>luks</value> </choice> </attribute> <zeroOrMore> @@ -81,6 +82,7 @@ <value>fat</value> <value>vhd</value> <value>ploop</value> + <value>luks</value> <ref name='storageFormatBacking'/> </choice> </define> diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c index 7d56ec8..d4c49eb 100644 --- a/src/qemu/qemu_process.c +++ b/src/qemu/qemu_process.c @@ -2411,6 +2411,12 @@ qemuProcessInitPasswords(virConnectPtr conn, !virDomainDiskGetSource(vm->def->disks[i])) continue; + if (vm->def->disks[i]->src->encryption->format != + VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT && + vm->def->disks[i]->src->encryption->format != + VIR_STORAGE_ENCRYPTION_FORMAT_QCOW) + continue; + VIR_FREE(secret); if (qemuProcessGetVolumeQcowPassphrase(conn, vm->def->disks[i], diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index d6a451d..97f6ffe 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -1027,8 +1027,7 @@ virStorageBackendCreateQemuImgCheckEncryption(int format, } } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, - _("qcow volume encryption unsupported with " - "volume format %s"), type); + _("volume encryption unsupported with format %s"), type); return -1; } diff --git a/src/storage/storage_backend_fs.c b/src/storage/storage_backend_fs.c index 839a2c7..0a12ecb 100644 --- a/src/storage/storage_backend_fs.c +++ b/src/storage/storage_backend_fs.c @@ -157,7 +157,12 @@ virStorageBackendProbeTarget(virStorageSourcePtr target, case VIR_STORAGE_FILE_QCOW2: (*encryption)->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; break; - default: + + case VIR_STORAGE_FILE_LUKS: + (*encryption)->format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS; + break; + + case VIR_STORAGE_ENCRYPTION_FORMAT_LAST: break; } diff --git a/src/storage/storage_backend_gluster.c b/src/storage/storage_backend_gluster.c index 0085052..eda060d 100644 --- a/src/storage/storage_backend_gluster.c +++ b/src/storage/storage_backend_gluster.c @@ -321,6 +321,8 @@ virStorageBackendGlusterRefreshVol(virStorageBackendGlusterStatePtr state, if (vol->target.format == VIR_STORAGE_FILE_QCOW || vol->target.format == VIR_STORAGE_FILE_QCOW2) vol->target.encryption->format = VIR_STORAGE_ENCRYPTION_FORMAT_QCOW; + if (vol->target.format == VIR_STORAGE_FILE_LUKS) + vol->target.encryption->format = VIR_STORAGE_ENCRYPTION_FORMAT_LUKS; } vol->target.features = meta->features; meta->features = NULL; diff --git a/src/util/virstorageencryption.c b/src/util/virstorageencryption.c index afb44da..8575416 100644 --- a/src/util/virstorageencryption.c +++ b/src/util/virstorageencryption.c @@ -43,7 +43,7 @@ VIR_ENUM_IMPL(virStorageEncryptionSecret, VIR_ENUM_IMPL(virStorageEncryptionFormat, VIR_STORAGE_ENCRYPTION_FORMAT_LAST, - "default", "qcow") + "default", "qcow", "luks") static void virStorageEncryptionSecretFree(virStorageEncryptionSecretPtr secret) diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h index c68c66e..5e1be3b 100644 --- a/src/util/virstorageencryption.h +++ b/src/util/virstorageencryption.h @@ -48,6 +48,7 @@ typedef enum { /* "default" is only valid for volume creation */ VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT = 0, VIR_STORAGE_ENCRYPTION_FORMAT_QCOW, /* Both qcow and qcow2 */ + VIR_STORAGE_ENCRYPTION_FORMAT_LUKS, VIR_STORAGE_ENCRYPTION_FORMAT_LAST, } virStorageEncryptionFormatType; diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml new file mode 100644 index 0000000..9ce15c0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml @@ -0,0 +1,45 @@ +<domain type='qemu'> + <name>encryptdisk</name> + <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>524288</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc-i440fx-2.1'>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='file' device='disk'> + <driver name='qemu' type='luks'/> + <source file='/storage/guest_disks/encryptdisk'/> + <target dev='vda' bus='virtio'/> + <encryption format='luks'> + <secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/> + </encryption> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='luks'/> + <source file='/storage/guest_disks/encryptdisk2'/> + <target dev='vdb' bus='virtio'/> + <encryption format='luks'> + <secret type='passphrase' usage='mycluster_myname'/> + </encryption> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <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/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml new file mode 120000 index 0000000..b59dc67 --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml @@ -0,0 +1 @@ +../qemuxml2argvdata/qemuxml2argv-luks-disks.xml \ No newline at end of file diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index d045fd4..6c566b3 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -502,6 +502,7 @@ mymain(void) DO_TEST("encrypted-disk"); DO_TEST("encrypted-disk-usage"); + DO_TEST("luks-disks"); DO_TEST("memtune"); DO_TEST("memtune-unlimited"); DO_TEST("blkiotune"); diff --git a/tests/storagevolxml2xmlin/vol-luks.xml b/tests/storagevolxml2xmlin/vol-luks.xml new file mode 100644 index 0000000..eb4dc41 --- /dev/null +++ b/tests/storagevolxml2xmlin/vol-luks.xml @@ -0,0 +1,21 @@ +<volume> + <name>LuksDemo.img</name> + <key>/var/lib/libvirt/images/LuksDemo.img</key> + <source> + </source> + <capacity unit="G">5</capacity> + <allocation>294912</allocation> + <target> + <path>/var/lib/libvirt/images/LuksDemo.img</path> + <format type='luks'/> + <permissions> + <mode>0644</mode> + <owner>0</owner> + <group>0</group> + <label>unconfined_u:object_r:virt_image_t:s0</label> + </permissions> + <encryption format='luks'> + <secret type='passphrase' usage='mumblyfratz'/> + </encryption> + </target> +</volume> diff --git a/tests/storagevolxml2xmlout/vol-luks.xml b/tests/storagevolxml2xmlout/vol-luks.xml new file mode 100644 index 0000000..5b764b7 --- /dev/null +++ b/tests/storagevolxml2xmlout/vol-luks.xml @@ -0,0 +1,21 @@ +<volume type='file'> + <name>LuksDemo.img</name> + <key>/var/lib/libvirt/images/LuksDemo.img</key> + <source> + </source> + <capacity unit='bytes'>5368709120</capacity> + <allocation unit='bytes'>294912</allocation> + <target> + <path>/var/lib/libvirt/images/LuksDemo.img</path> + <format type='luks'/> + <permissions> + <mode>0644</mode> + <owner>0</owner> + <group>0</group> + <label>unconfined_u:object_r:virt_image_t:s0</label> + </permissions> + <encryption format='luks'> + <secret type='passphrase' usage='mumblyfratz'/> + </encryption> + </target> +</volume> diff --git a/tests/storagevolxml2xmltest.c b/tests/storagevolxml2xmltest.c index f722452..a36a706 100644 --- a/tests/storagevolxml2xmltest.c +++ b/tests/storagevolxml2xmltest.c @@ -105,6 +105,7 @@ mymain(void) DO_TEST("pool-dir", "vol-qcow2-lazy"); DO_TEST("pool-dir", "vol-qcow2-0.10-lazy"); DO_TEST("pool-dir", "vol-qcow2-nobacking"); + DO_TEST("pool-dir", "vol-luks"); DO_TEST("pool-disk", "vol-partition"); DO_TEST("pool-logical", "vol-logical"); DO_TEST("pool-logical", "vol-logical-backing"); -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:33PM -0400, John Ferlan wrote:
Add parse and format of the luks/passphrase secret including tests for volume XML parsing.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatsecret.html.in | 7 +++- docs/formatstorageencryption.html.in | 26 ++++++++++++- docs/schemas/storagecommon.rng | 2 + src/qemu/qemu_process.c | 6 +++ src/storage/storage_backend.c | 3 +- src/storage/storage_backend_fs.c | 7 +++- src/storage/storage_backend_gluster.c | 2 + src/util/virstorageencryption.c | 2 +- src/util/virstorageencryption.h | 1 + tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml | 45 ++++++++++++++++++++++ .../qemuxml2xmlout-luks-disks.xml | 1 + tests/qemuxml2xmltest.c | 1 + tests/storagevolxml2xmlin/vol-luks.xml | 21 ++++++++++ tests/storagevolxml2xmlout/vol-luks.xml | 21 ++++++++++ tests/storagevolxml2xmltest.c | 1 + 15 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks.xml
ACK Jan

For a luks device, allow the configuration of a specific cipher to be used for encrypting the volume. Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 83 ++++++++++++- docs/schemas/storagecommon.rng | 44 ++++++- src/conf/domain_conf.c | 11 ++ src/util/virstorageencryption.c | 138 +++++++++++++++++++++ src/util/virstorageencryption.h | 14 +++ .../qemuxml2argv-luks-disk-cipher.xml | 45 +++++++ .../qemuxml2xmlout-luks-disk-cipher.xml | 1 + tests/qemuxml2xmltest.c | 1 + tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmltest.c | 1 + 11 files changed, 378 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml diff --git a/docs/formatstorageencryption.html.in b/docs/formatstorageencryption.html.in index be73054..0f8401e 100644 --- a/docs/formatstorageencryption.html.in +++ b/docs/formatstorageencryption.html.in @@ -68,6 +68,60 @@ be used as the passphrase to decrypt the volume. <span class="since">Since 2.0.0</span>. </p> + <p> + For volume creation, it is possible to specify the encryption + algorithm used to encrypt the luks volume. The following two + optional elements may be provided for that purpose. It is hypervisor + dependent as to which algorithms are supported. The default algorithm + used by the storage driver backend when using qemu-img to create + the volume is 'aes-256-cbc' using 'essiv' for initialization vector + generation and 'sha256' hash algorithm for both the cipher and the + initialization vector generation. + </p> + + <dl> + <dt><code>cipher</code></dt> + <dd>This element describes the cipher algorithm to be used to either + encrypt or decrypt the luks volume. This element has the following + attributes: + <dl> + <dt><code>name</code></dt> + <dd>The name of the cipher algorithm used for data encryption, + such as 'aes', 'des', 'cast5', 'serpent', 'twofish', etc. + Support of the specific algorithm is storage driver + implementation dependent.</dd> + <dt><code>size</code></dt> + <dd>The size of the cipher in bits, such as '256', '192', '128', + etc. Support of the specific size for a specific cipher is + hypervisor dependent.</dd> + <dt><code>mode</code></dt> + <dd>An optional cipher algorithm mode such as 'cbc', 'xts', + 'ecb', etc. Support of the specific cipher mode is + hypervisor dependent.</dd> + <dt><code>hash</code></dt> + <dd>An optional master key hash algorithm such as 'md5', 'sha1', + 'sha256', etc. Support of the specific hash algorithm is + hypervisor dependent.</dd> + </dl> + </dd> + <dt><code>ivgen</code></dt> + <dd>This optional element describes the initialization vector + generation algorithm used in conjunction with the + <code>cipher</code>. If the <code>cipher</code> is not provided, + then an error will be generated by the parser. + <dl> + <dt><code>name</code></dt> + <dd>The name of the algorithm, such as 'plain', 'plain64', + 'essiv', etc. Support of the specific algorithm is hypervisor + dependent.</dd> + <dt><code>hash</code></dt> + <dd>An optional hash algorithm such as 'md5', 'sha1', 'sha256', + etc. Support of the specific ivgen hash algorithm is hypervisor + dependent.</dd> + </dl> + </dd> + </dl> + <h2><a name="example">Examples</a></h2> @@ -81,9 +135,12 @@ </encryption></pre> <p> - Here is a simple example, specifying use of the <code>luks</code> format - where it's assumed that a <code>secret</code> has been defined using a - <code>usage</code> element with a <code>id</code> of "luks_example": + Assuming a <a href="formatsecret.html#luksUsageType"> + <code>luks secret</code></a> is already defined using a + <code>usage</code> element with an <code>name</code> of "luks_example", + a simple example specifying use of the <code>luks</code> format + for either volume creation without a specific cipher being defined or + as part of a domain volume definition: </p> <pre> <encryption format='luks'> @@ -91,5 +148,25 @@ </encryption> </pre> + <p> + Here is an example, specifying use of the <code>luks</code> format for + a specific cipher algorihm for volume creation: + </p> + <pre> + <volume> + <name>twofish.luks</name> + <capacity unit='G'>5</capacity> + <target> + <path>/var/lib/libvirt/images/demo.luks</path> + <format type='luks'/> + <encryption format='luks'> + <secret type='passphrase' usage='luks_example'/> + <cipher name='twofish' size='256' mode='cbc' hash='sha256'/> + <ivgen name='plain64' hash='sha256'/> + </encryption> + </target> + </volume> + </pre> + </body> </html> diff --git a/docs/schemas/storagecommon.rng b/docs/schemas/storagecommon.rng index 63b55b4..316fbae 100644 --- a/docs/schemas/storagecommon.rng +++ b/docs/schemas/storagecommon.rng @@ -15,9 +15,19 @@ <value>luks</value> </choice> </attribute> - <zeroOrMore> - <ref name='secret'/> - </zeroOrMore> + <interleave> + <zeroOrMore> + <ref name='secret'/> + </zeroOrMore> + <optional> + <element name='cipher'> + <ref name='keycipher'/> + </element> + <element name='ivgen'> + <ref name='keyivgen'/> + </element> + </optional> + </interleave> </element> </define> @@ -136,4 +146,32 @@ </optional> </define> + <define name='keycipher'> + <attribute name='name'> + <text/> + </attribute> + <attribute name='size'> + <ref name="unsignedInt"/> + </attribute> + <optional> + <attribute name='mode'> + <text/> + </attribute> + <attribute name='hash'> + <text/> + </attribute> + </optional> + </define> + + <define name='keyivgen'> + <attribute name='name'> + <text/> + </attribute> + <optional> + <attribute name='hash'> + <text/> + </attribute> + </optional> + </define> + </grammar> diff --git a/src/conf/domain_conf.c b/src/conf/domain_conf.c index 79d15c8..8d3a132 100644 --- a/src/conf/domain_conf.c +++ b/src/conf/domain_conf.c @@ -7857,6 +7857,17 @@ virDomainDiskDefParseXML(virDomainXMLOptionPtr xmlopt, def->startupPolicy = val; } + if (encryption) { + if (encryption->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && + encryption->encinfo.cipher_name) { + + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("supplying the <cipher> for a domain is " + "unnecessary")); + goto error; + } + } + def->dst = target; target = NULL; def->src->auth = authdef; diff --git a/src/util/virstorageencryption.c b/src/util/virstorageencryption.c index 8575416..754444d 100644 --- a/src/util/virstorageencryption.c +++ b/src/util/virstorageencryption.c @@ -35,6 +35,7 @@ #include "viruuid.h" #include "virfile.h" #include "virsecret.h" +#include "virstring.h" #define VIR_FROM_THIS VIR_FROM_STORAGE @@ -46,6 +47,17 @@ VIR_ENUM_IMPL(virStorageEncryptionFormat, "default", "qcow", "luks") static void +virStorageEncryptionInfoDefFree(virStorageEncryptionInfoDefPtr def) +{ + VIR_FREE(def->cipher_name); + VIR_FREE(def->cipher_mode); + VIR_FREE(def->cipher_hash); + VIR_FREE(def->ivgen_name); + VIR_FREE(def->ivgen_hash); +} + + +static void virStorageEncryptionSecretFree(virStorageEncryptionSecretPtr secret) { if (!secret) @@ -63,6 +75,7 @@ virStorageEncryptionFree(virStorageEncryptionPtr enc) for (i = 0; i < enc->nsecrets; i++) virStorageEncryptionSecretFree(enc->secrets[i]); + virStorageEncryptionInfoDefFree(&enc->encinfo); VIR_FREE(enc->secrets); VIR_FREE(enc); } @@ -80,6 +93,23 @@ virStorageEncryptionSecretCopy(const virStorageEncryptionSecret *src) return ret; } + +static int +virStorageEncryptionInfoDefCopy(const virStorageEncryptionInfoDef *src, + virStorageEncryptionInfoDefPtr dst) +{ + dst->cipher_size = src->cipher_size; + if (VIR_STRDUP(dst->cipher_name, src->cipher_name) < 0 || + VIR_STRDUP(dst->cipher_mode, src->cipher_mode) < 0 || + VIR_STRDUP(dst->cipher_hash, src->cipher_hash) < 0 || + VIR_STRDUP(dst->ivgen_name, src->ivgen_name) < 0 || + VIR_STRDUP(dst->ivgen_hash, src->ivgen_hash) < 0) + return -1; + + return 0; +} + + virStorageEncryptionPtr virStorageEncryptionCopy(const virStorageEncryption *src) { @@ -100,6 +130,9 @@ virStorageEncryptionCopy(const virStorageEncryption *src) goto error; } + if (virStorageEncryptionInfoDefCopy(&src->encinfo, &ret->encinfo) < 0) + goto error; + return ret; error: @@ -153,6 +186,63 @@ virStorageEncryptionSecretParse(xmlXPathContextPtr ctxt, return NULL; } + +static int +virStorageEncryptionInfoParseCipher(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + int ret = -1; + char *size_str = NULL; + + if (!(info->cipher_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing cipher info name string")); + goto cleanup; + } + + if ((size_str = virXMLPropString(info_node, "size")) && + virStrToLong_uip(size_str, NULL, 10, &info->cipher_size) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("cannot parse cipher info size string '%s'"), + size_str); + goto cleanup; + } + + if (!size_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cipher info missing 'size' attribute")); + goto cleanup; + } + + /* Optional */ + info->cipher_mode = virXMLPropString(info_node, "mode"); + info->cipher_hash = virXMLPropString(info_node, "hash"); + + ret = 0; + + cleanup: + VIR_FREE(size_str); + return ret; +} + + +static int +virStorageEncryptionInfoParseIvgen(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + if (!(info->ivgen_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing ivgen info name string")); + return -1; + } + + /* Optional */ + info->ivgen_hash = virXMLPropString(info_node, "hash"); + + return 0; +} + + static virStorageEncryptionPtr virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) { @@ -196,6 +286,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) VIR_FREE(nodes); } + if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + xmlNodePtr tmpnode; + + if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) { + if (virStorageEncryptionInfoParseCipher(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + + if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) { + /* If no cipher node, then fail */ + if (!ret->encinfo.cipher_name) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing storage encryption cipher")); + goto cleanup; + } + + if (virStorageEncryptionInfoParseIvgen(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + } + + return ret; cleanup: @@ -250,6 +362,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf, return 0; } + +static void +virStorageEncryptionInfoDefFormat(virBufferPtr buf, + const virStorageEncryptionInfoDef *enc) +{ + virBufferAsprintf(buf, "<cipher name='%s' size='%u'", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, " mode='%s'", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, " hash='%s'", enc->cipher_hash); + virBufferAddLit(buf, "/>\n"); + + if (enc->ivgen_name) { + virBufferAsprintf(buf, "<ivgen name='%s'", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, " hash='%s'", enc->ivgen_hash); + virBufferAddLit(buf, "/>\n"); + } +} + + int virStorageEncryptionFormat(virBufferPtr buf, virStorageEncryptionPtr enc) @@ -270,6 +404,10 @@ virStorageEncryptionFormat(virBufferPtr buf, return -1; } + if (enc->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS && + enc->encinfo.cipher_name) + virStorageEncryptionInfoDefFormat(buf, &enc->encinfo); + virBufferAdjustIndent(buf, -2); virBufferAddLit(buf, "</encryption>\n"); diff --git a/src/util/virstorageencryption.h b/src/util/virstorageencryption.h index 5e1be3b..fa439fb 100644 --- a/src/util/virstorageencryption.h +++ b/src/util/virstorageencryption.h @@ -44,6 +44,18 @@ struct _virStorageEncryptionSecret { virSecretLookupTypeDef seclookupdef; }; +/* It's possible to dictate the cipher and if necessary iv */ +typedef struct _virStorageEncryptionInfoDef virStorageEncryptionInfoDef; +typedef virStorageEncryptionInfoDef *virStorageEncryptionInfoDefPtr; +struct _virStorageEncryptionInfoDef { + unsigned int cipher_size; + char *cipher_name; + char *cipher_mode; + char *cipher_hash; + char *ivgen_name; + char *ivgen_hash; +}; + typedef enum { /* "default" is only valid for volume creation */ VIR_STORAGE_ENCRYPTION_FORMAT_DEFAULT = 0, @@ -61,6 +73,8 @@ struct _virStorageEncryption { size_t nsecrets; virStorageEncryptionSecretPtr *secrets; + + virStorageEncryptionInfoDef encinfo; }; virStorageEncryptionPtr virStorageEncryptionCopy(const virStorageEncryption *src) diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml new file mode 100644 index 0000000..9ce15c0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml @@ -0,0 +1,45 @@ +<domain type='qemu'> + <name>encryptdisk</name> + <uuid>496898a6-e6ff-f7c8-5dc2-3cf410945ee9</uuid> + <memory unit='KiB'>1048576</memory> + <currentMemory unit='KiB'>524288</currentMemory> + <vcpu placement='static'>1</vcpu> + <os> + <type arch='x86_64' machine='pc-i440fx-2.1'>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='file' device='disk'> + <driver name='qemu' type='luks'/> + <source file='/storage/guest_disks/encryptdisk'/> + <target dev='vda' bus='virtio'/> + <encryption format='luks'> + <secret type='passphrase' uuid='0a81f5b2-8403-7b23-c8d6-21ccc2f80d6f'/> + </encryption> + <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/> + </disk> + <disk type='file' device='disk'> + <driver name='qemu' type='luks'/> + <source file='/storage/guest_disks/encryptdisk2'/> + <target dev='vdb' bus='virtio'/> + <encryption format='luks'> + <secret type='passphrase' usage='mycluster_myname'/> + </encryption> + <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/> + </disk> + <controller type='usb' index='0'> + <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/> + </controller> + <controller type='pci' index='0' model='pci-root'/> + <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/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml new file mode 120000 index 0000000..fa55233 --- /dev/null +++ b/tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml @@ -0,0 +1 @@ +../qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml \ No newline at end of file diff --git a/tests/qemuxml2xmltest.c b/tests/qemuxml2xmltest.c index 6c566b3..2056c03 100644 --- a/tests/qemuxml2xmltest.c +++ b/tests/qemuxml2xmltest.c @@ -503,6 +503,7 @@ mymain(void) DO_TEST("encrypted-disk"); DO_TEST("encrypted-disk-usage"); DO_TEST("luks-disks"); + DO_TEST("luks-disk-cipher"); DO_TEST("memtune"); DO_TEST("memtune-unlimited"); DO_TEST("blkiotune"); diff --git a/tests/storagevolxml2xmlin/vol-luks-cipher.xml b/tests/storagevolxml2xmlin/vol-luks-cipher.xml new file mode 100644 index 0000000..009246f --- /dev/null +++ b/tests/storagevolxml2xmlin/vol-luks-cipher.xml @@ -0,0 +1,23 @@ +<volume> + <name>LuksDemo.img</name> + <key>/var/lib/libvirt/images/LuksDemo.img</key> + <source> + </source> + <capacity unit="G">5</capacity> + <allocation>294912</allocation> + <target> + <path>/var/lib/libvirt/images/LuksDemo.img</path> + <format type='luks'/> + <permissions> + <mode>0644</mode> + <owner>0</owner> + <group>0</group> + <label>unconfined_u:object_r:virt_image_t:s0</label> + </permissions> + <encryption format='luks'> + <secret type='passphrase' usage='mumblyfratz'/> + <cipher name='serpent' size='256' mode='cbc' hash='sha256'/> + <ivgen name='plain64' hash='sha256'/> + </encryption> + </target> +</volume> diff --git a/tests/storagevolxml2xmlout/vol-luks-cipher.xml b/tests/storagevolxml2xmlout/vol-luks-cipher.xml new file mode 100644 index 0000000..9014849 --- /dev/null +++ b/tests/storagevolxml2xmlout/vol-luks-cipher.xml @@ -0,0 +1,23 @@ +<volume type='file'> + <name>LuksDemo.img</name> + <key>/var/lib/libvirt/images/LuksDemo.img</key> + <source> + </source> + <capacity unit='bytes'>5368709120</capacity> + <allocation unit='bytes'>294912</allocation> + <target> + <path>/var/lib/libvirt/images/LuksDemo.img</path> + <format type='luks'/> + <permissions> + <mode>0644</mode> + <owner>0</owner> + <group>0</group> + <label>unconfined_u:object_r:virt_image_t:s0</label> + </permissions> + <encryption format='luks'> + <secret type='passphrase' usage='mumblyfratz'/> + <cipher name='serpent' size='256' mode='cbc' hash='sha256'/> + <ivgen name='plain64' hash='sha256'/> + </encryption> + </target> +</volume> diff --git a/tests/storagevolxml2xmltest.c b/tests/storagevolxml2xmltest.c index a36a706..db82bea 100644 --- a/tests/storagevolxml2xmltest.c +++ b/tests/storagevolxml2xmltest.c @@ -106,6 +106,7 @@ mymain(void) DO_TEST("pool-dir", "vol-qcow2-0.10-lazy"); DO_TEST("pool-dir", "vol-qcow2-nobacking"); DO_TEST("pool-dir", "vol-luks"); + DO_TEST("pool-dir", "vol-luks-cipher"); DO_TEST("pool-disk", "vol-partition"); DO_TEST("pool-logical", "vol-logical"); DO_TEST("pool-logical", "vol-logical-backing"); -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:34PM -0400, John Ferlan wrote:
For a luks device, allow the configuration of a specific cipher to be used for encrypting the volume.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 83 ++++++++++++- docs/schemas/storagecommon.rng | 44 ++++++- src/conf/domain_conf.c | 11 ++ src/util/virstorageencryption.c | 138 +++++++++++++++++++++ src/util/virstorageencryption.h | 14 +++ .../qemuxml2argv-luks-disk-cipher.xml | 45 +++++++ .../qemuxml2xmlout-luks-disk-cipher.xml | 1 + tests/qemuxml2xmltest.c | 1 + tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmltest.c | 1 + 11 files changed, 378 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml
+static int +virStorageEncryptionInfoParseCipher(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + int ret = -1; + char *size_str = NULL; + + if (!(info->cipher_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing cipher info name string"));
Either 'missing cipher name' or use "cipher info missing '%s' attribute"...
+ goto cleanup; + } + + if ((size_str = virXMLPropString(info_node, "size")) && + virStrToLong_uip(size_str, NULL, 10, &info->cipher_size) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("cannot parse cipher info size string '%s'"),
"cannot parse cipher size: '%s'"
+ size_str); + goto cleanup; + } + + if (!size_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cipher info missing 'size' attribute"));
to share the string with this error. Also, should we be validating these by converting them to an enum?
+ goto cleanup; + } +
+ /* Optional */
Hence no error following the calls.
+ info->cipher_mode = virXMLPropString(info_node, "mode"); + info->cipher_hash = virXMLPropString(info_node, "hash"); + + ret = 0; + + cleanup: + VIR_FREE(size_str); + return ret; +} + + +static int +virStorageEncryptionInfoParseIvgen(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + if (!(info->ivgen_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing ivgen info name string")); + return -1; + } +
+ /* Optional */
Also redundant.
+ info->ivgen_hash = virXMLPropString(info_node, "hash"); + + return 0; +} + + static virStorageEncryptionPtr virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) { @@ -196,6 +286,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) VIR_FREE(nodes); }
+ if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + xmlNodePtr tmpnode; + + if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) { + if (virStorageEncryptionInfoParseCipher(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + + if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) {
+ /* If no cipher node, then fail */
Rather than putting it in this comment,
+ if (!ret->encinfo.cipher_name) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing storage encryption cipher"));
how about putting it in the error: "ivgen element found but cipher is missing"
+ goto cleanup; + } + + if (virStorageEncryptionInfoParseIvgen(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + } + + return ret;
cleanup: @@ -250,6 +362,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf, return 0; }
+ +static void +virStorageEncryptionInfoDefFormat(virBufferPtr buf, + const virStorageEncryptionInfoDef *enc) +{ + virBufferAsprintf(buf, "<cipher name='%s' size='%u'", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, " mode='%s'", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, " hash='%s'", enc->cipher_hash); + virBufferAddLit(buf, "/>\n"); + + if (enc->ivgen_name) { + virBufferAsprintf(buf, "<ivgen name='%s'", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, " hash='%s'", enc->ivgen_hash); + virBufferAddLit(buf, "/>\n"); + }
The strings need to be escaped.
+} + + int virStorageEncryptionFormat(virBufferPtr buf, virStorageEncryptionPtr enc)
ACK with the strings escaped. Jan

On 06/25/2016 01:46 AM, Ján Tomko wrote:
On Fri, Jun 24, 2016 at 04:53:34PM -0400, John Ferlan wrote:
For a luks device, allow the configuration of a specific cipher to be used for encrypting the volume.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- docs/formatstorageencryption.html.in | 83 ++++++++++++- docs/schemas/storagecommon.rng | 44 ++++++- src/conf/domain_conf.c | 11 ++ src/util/virstorageencryption.c | 138 +++++++++++++++++++++ src/util/virstorageencryption.h | 14 +++ .../qemuxml2argv-luks-disk-cipher.xml | 45 +++++++ .../qemuxml2xmlout-luks-disk-cipher.xml | 1 + tests/qemuxml2xmltest.c | 1 + tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++++ tests/storagevolxml2xmltest.c | 1 + 11 files changed, 378 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml
+static int +virStorageEncryptionInfoParseCipher(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + int ret = -1; + char *size_str = NULL; + + if (!(info->cipher_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing cipher info name string"));
Either 'missing cipher name' or use "cipher info missing '%s' attribute"...
OK
+ goto cleanup; + } + + if ((size_str = virXMLPropString(info_node, "size")) && + virStrToLong_uip(size_str, NULL, 10, &info->cipher_size) < 0) { + virReportError(VIR_ERR_XML_ERROR, + _("cannot parse cipher info size string '%s'"),
"cannot parse cipher size: '%s'"
OK
+ size_str); + goto cleanup; + } + + if (!size_str) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("cipher info missing 'size' attribute"));
to share the string with this error.
OK
Also, should we be validating these by converting them to an enum?
Good question! Unfortunately we have no way of knowing what any driver supports so what are we validating them against? I thought of creating a connection API that could/would fetch all the cipher/ivgen algorithm information from the hypervisor that's going to be using them. Then I investigated qemu to see what type of API was available only to find nothing "obvious". So for now, I just left these as a string. I suppose we could "mock up" a qemu response, but it's a lot more effort saved for another day. If bad values are provided, qemu-img will fall over and play dead during the volume create.
+ goto cleanup; + } +
+ /* Optional */
Hence no error following the calls.
Gone.
+ info->cipher_mode = virXMLPropString(info_node, "mode"); + info->cipher_hash = virXMLPropString(info_node, "hash"); + + ret = 0; + + cleanup: + VIR_FREE(size_str); + return ret; +} + + +static int +virStorageEncryptionInfoParseIvgen(xmlNodePtr info_node, + virStorageEncryptionInfoDefPtr info) +{ + if (!(info->ivgen_name = virXMLPropString(info_node, "name"))) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing ivgen info name string")); + return -1; + } +
+ /* Optional */
Also redundant.
Gone
+ info->ivgen_hash = virXMLPropString(info_node, "hash"); + + return 0; +} + + static virStorageEncryptionPtr virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) { @@ -196,6 +286,28 @@ virStorageEncryptionParseXML(xmlXPathContextPtr ctxt) VIR_FREE(nodes); }
+ if (ret->format == VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + xmlNodePtr tmpnode; + + if ((tmpnode = virXPathNode("./cipher[1]", ctxt))) { + if (virStorageEncryptionInfoParseCipher(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + + if ((tmpnode = virXPathNode("./ivgen[1]", ctxt))) {
+ /* If no cipher node, then fail */
Rather than putting it in this comment,
+ if (!ret->encinfo.cipher_name) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("missing storage encryption cipher"));
how about putting it in the error: "ivgen element found but cipher is missing"
OK, done.
+ goto cleanup; + } + + if (virStorageEncryptionInfoParseIvgen(tmpnode, &ret->encinfo) < 0) + goto cleanup; + } + } + + return ret;
cleanup: @@ -250,6 +362,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf, return 0; }
+ +static void +virStorageEncryptionInfoDefFormat(virBufferPtr buf, + const virStorageEncryptionInfoDef *enc) +{ + virBufferAsprintf(buf, "<cipher name='%s' size='%u'", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, " mode='%s'", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, " hash='%s'", enc->cipher_hash); + virBufferAddLit(buf, "/>\n"); + + if (enc->ivgen_name) { + virBufferAsprintf(buf, "<ivgen name='%s'", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, " hash='%s'", enc->ivgen_hash); + virBufferAddLit(buf, "/>\n"); + }
The strings need to be escaped.
Done
+} + + int virStorageEncryptionFormat(virBufferPtr buf, virStorageEncryptionPtr enc)
ACK with the strings escaped.
Thanks for the look/time... John

Partially resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021 If the volume xml was looking to create a luks volume take the necessary steps in order to make that happen. The processing will be: 1. create a temporary file in the storage driver state dir path 1a. the file name will include the pool name, the volume name, secret, and XXXXXX for usage in the mkostemp call. 1b. mkostemp the file, initially setting mode to 0600 with current effective uid:gid as owner 1c. fetch the secret into a buffer and write that into the file 2. create a secret object 2a. use an alias combinding the volume name and "_luks0" 2b. add the file to the object 3. create/add luks options to the commandline 3a. at the very least a "key-secret" using the secret object alias 3b. if found in the XML the various "cipher" and "ivgen" options Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/libvirt_private.syms | 1 + src/storage/storage_backend.c | 254 ++++++++++++++++++++++++++++++++++++++--- src/storage/storage_backend.h | 3 +- src/util/virqemu.c | 23 ++++ src/util/virqemu.h | 6 + tests/storagevolxml2argvtest.c | 3 +- 6 files changed, 269 insertions(+), 21 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9ac033d..eea18dd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2169,6 +2169,7 @@ virProcessWait; # util/virqemu.h +virQEMUBuildLuksOpts; virQEMUBuildObjectCommandlineFromJSON; diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 97f6ffe..4fb77fc 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -55,11 +55,14 @@ #include "viralloc.h" #include "internal.h" #include "secret_conf.h" +#include "secret_util.h" #include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" #include "virfile.h" +#include "virjson.h" +#include "virqemu.h" #include "stat-time.h" #include "virstring.h" #include "virxml.h" @@ -880,6 +883,7 @@ virStoragePloopResize(virStorageVolDefPtr vol, enum { QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, + QEMU_IMG_FORMAT_LUKS, }; static bool @@ -907,6 +911,27 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg) return ret; } + +static bool +virStorageBackendQemuImgSupportsLuks(const char *qemuimg) +{ + bool ret = false; + int exitstatus = -1; + virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", + "-f", "luks", "/dev/null", NULL); + + if (virCommandRun(cmd, &exitstatus) < 0) + goto cleanup; + + if (exitstatus == 0) + ret = true; + + cleanup: + virCommandFree(cmd); + return ret; +} + + static int virStorageBackendQEMUImgBackingFormat(const char *qemuimg) { @@ -915,12 +940,18 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg) * out what else we have */ int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS; - /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer - * understands. Since we still support QEMU 0.12 and newer, we need - * to be able to handle the previous format as can be set via a - * compat=0.10 option. */ - if (virStorageBackendQemuImgSupportsCompat(qemuimg)) - ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + /* QEMU 2.6 added support for luks - let's check for that. + * If available, then we can also assume OPTIONS_COMPAT is present */ + if (virStorageBackendQemuImgSupportsLuks(qemuimg)) { + ret = QEMU_IMG_FORMAT_LUKS; + } else { + /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer + * understands. Since we still support QEMU 0.12 and newer, we need + * to be able to handle the previous format as can be set via a + * compat=0.10 option. */ + if (virStorageBackendQemuImgSupportsCompat(qemuimg)) + ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + } return ret; } @@ -941,21 +972,31 @@ struct _virStorageBackendQemuImgInfo { const char *inputPath; const char *inputFormatStr; int inputFormat; + + char *secretAlias; + const char *secretPath; }; + static int -virStorageBackendCreateQemuImgOpts(char **opts, +virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, + char **opts, struct _virStorageBackendQemuImgInfo info) { virBuffer buf = VIR_BUFFER_INITIALIZER; - if (info.backingPath) - virBufferAsprintf(&buf, "backing_fmt=%s,", - virStorageFileFormatTypeToString(info.backingFormat)); - if (info.encryption) - virBufferAddLit(&buf, "encryption=on,"); - if (info.preallocate) - virBufferAddLit(&buf, "preallocation=metadata,"); + if (info.format == VIR_STORAGE_FILE_LUKS) { + virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); + } else { + if (info.backingPath) + virBufferAsprintf(&buf, "backing_fmt=%s,", + virStorageFileFormatTypeToString(info.backingFormat)); + if (info.encryption) + virBufferAddLit(&buf, "encryption=on,"); + if (info.preallocate) + virBufferAddLit(&buf, "preallocation=metadata,"); + } + if (info.nocow) virBufferAddLit(&buf, "nocow=on,"); @@ -1025,6 +1066,22 @@ virStorageBackendCreateQemuImgCheckEncryption(int format, if (virStorageGenerateQcowEncryption(conn, vol) < 0) return -1; } + } else if (format == VIR_STORAGE_FILE_LUKS) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for luks encryption")); + return -1; + } + if (enc->nsecrets == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no secret provided for luks encryption")); + } } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("volume encryption unsupported with format %s"), type); @@ -1069,6 +1126,12 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, int accessRetCode = -1; char *absolutePath = NULL; + if (info->format == VIR_STORAGE_FILE_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot set backing store for luks volume")); + return -1; + } + info->backingFormat = vol->target.backingStore->format; info->backingPath = vol->target.backingStore->path; @@ -1121,6 +1184,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, static int virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, + virStorageVolDefPtr vol, int imgformat, struct _virStorageBackendQemuImgInfo info) { @@ -1130,7 +1194,8 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) info.compat = "0.10"; - if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0) + if (virStorageBackendCreateQemuImgOpts(&vol->target.encryption->encinfo, + &opts, info) < 0) return -1; if (opts) virCommandAddArgList(cmd, "-o", opts, NULL); @@ -1140,6 +1205,43 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, } +/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + char *str = NULL; + virJSONValuePtr props = NULL; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) { + VIR_FREE(str); + return -1; + } + VIR_FREE(str); + + if (virJSONValueObjectCreate(&props, "s:file", info->secretPath, NULL) < 0) + return -1; + + if (!(commandStr = virQEMUBuildObjectCommandlineFromJSON("secret", + info->secretAlias, + props))) { + virJSONValueFree(props); + return -1; + } + virJSONValueFree(props); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + return 0; +} + + /* Create a qemu-img virCommand from the supplied binary path, * volume definitions and imgformat */ @@ -1150,7 +1252,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat) + int imgformat, + const char *secretPath) { virCommandPtr cmd = NULL; const char *type; @@ -1162,6 +1265,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, .compat = vol->target.compat, .features = vol->target.features, .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, }; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); @@ -1192,6 +1297,18 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, _("format features only available with qcow2")); return NULL; } + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with luks volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + } if (inputvol && virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) @@ -1207,7 +1324,6 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, conn, vol) < 0) return NULL; - /* Size in KB */ info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024); @@ -1226,11 +1342,21 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, if (info.backingPath) virCommandAddArgList(cmd, "-b", info.backingPath, NULL); - if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, info) < 0) { + if (info.format == VIR_STORAGE_FILE_LUKS && + virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); virCommandFree(cmd); return NULL; } + if (virStorageBackendCreateQemuImgSetOptions(cmd, vol, imgformat, + info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + VIR_FREE(info.secretAlias); + if (info.inputPath) virCommandAddArg(cmd, info.inputPath); virCommandAddArg(cmd, info.path); @@ -1240,6 +1366,78 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, return cmd; } + +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } + + /* Since we don't have a file, just go to cleanup using NULL secretPath */ + if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen); + VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + int virStorageBackendCreateQemuImg(virConnectPtr conn, virStoragePoolObjPtr pool, @@ -1251,6 +1449,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, char *create_tool; int imgformat; virCommandPtr cmd; + char *secretPath = NULL; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); @@ -1266,8 +1465,21 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (imgformat < 0) goto cleanup; + if (vol->target.format == VIR_STORAGE_FILE_LUKS) { + if (imgformat < QEMU_IMG_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("this version of '%s' does not support luks"), + create_tool); + goto cleanup; + } + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, imgformat); + flags, create_tool, + imgformat, secretPath); if (!cmd) goto cleanup; @@ -1275,6 +1487,10 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, virCommandFree(cmd); cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } VIR_FREE(create_tool); return ret; } diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index 5bc622c..28e1a65 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -241,7 +241,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat); + int imgformat, + const char *secretPath); /* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; diff --git a/src/util/virqemu.c b/src/util/virqemu.c index 895168e..4e4a6dd 100644 --- a/src/util/virqemu.c +++ b/src/util/virqemu.c @@ -140,3 +140,26 @@ virQEMUBuildObjectCommandlineFromJSON(const char *type, virBufferFreeAndReset(&buf); return ret; } + + +void +virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) +{ + virBufferAsprintf(buf, "key-secret=%s,", alias); + + /* If there's any cipher, then add that to the command line */ + if (enc->cipher_name) { + virBufferAsprintf(buf, "cipher-alg=%s-%u,", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, "cipher-mode=%s,", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, "hash-alg=%s,", enc->cipher_hash); + if (enc->ivgen_name) + virBufferAsprintf(buf, "ivgen-alg=%s,", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, "ivgen-hash-alg=%s,", enc->ivgen_hash); + } +} diff --git a/src/util/virqemu.h b/src/util/virqemu.h index af4ec1d..46e7ae2 100644 --- a/src/util/virqemu.h +++ b/src/util/virqemu.h @@ -26,9 +26,15 @@ # include "internal.h" # include "virjson.h" +# include "virstorageencryption.h" char *virQEMUBuildObjectCommandlineFromJSON(const char *type, const char *alias, virJSONValuePtr props); +void virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + #endif /* __VIR_QEMU_H_ */ diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c index ccfe9ab..e300821 100644 --- a/tests/storagevolxml2argvtest.c +++ b/tests/storagevolxml2argvtest.c @@ -83,7 +83,8 @@ testCompareXMLToArgvFiles(bool shouldFail, cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, &poolobj, vol, inputvol, flags, - create_tool, imgformat); + create_tool, imgformat, + NULL); if (!cmd) { if (shouldFail) { virResetLastError(); -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:35PM -0400, John Ferlan wrote:
Partially resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021
If the volume xml was looking to create a luks volume take the necessary steps in order to make that happen.
The processing will be: 1. create a temporary file in the storage driver state dir path 1a. the file name will include the pool name, the volume name, secret, and XXXXXX for usage in the mkostemp call. 1b. mkostemp the file, initially setting mode to 0600 with current effective uid:gid as owner 1c. fetch the secret into a buffer and write that into the file
2. create a secret object 2a. use an alias combinding the volume name and "_luks0" 2b. add the file to the object
3. create/add luks options to the commandline 3a. at the very least a "key-secret" using the secret object alias 3b. if found in the XML the various "cipher" and "ivgen" options
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/libvirt_private.syms | 1 + src/storage/storage_backend.c | 254 ++++++++++++++++++++++++++++++++++++++--- src/storage/storage_backend.h | 3 +- src/util/virqemu.c | 23 ++++ src/util/virqemu.h | 6 + tests/storagevolxml2argvtest.c | 3 +- 6 files changed, 269 insertions(+), 21 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9ac033d..eea18dd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2169,6 +2169,7 @@ virProcessWait;
# util/virqemu.h +virQEMUBuildLuksOpts; virQEMUBuildObjectCommandlineFromJSON;
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 97f6ffe..4fb77fc 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -55,11 +55,14 @@ #include "viralloc.h" #include "internal.h" #include "secret_conf.h" +#include "secret_util.h" #include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" #include "virfile.h" +#include "virjson.h" +#include "virqemu.h" #include "stat-time.h" #include "virstring.h" #include "virxml.h"
@@ -880,6 +883,7 @@ virStoragePloopResize(virStorageVolDefPtr vol, enum { QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, + QEMU_IMG_FORMAT_LUKS, };
static bool @@ -907,6 +911,27 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg) return ret; }
+ +static bool +virStorageBackendQemuImgSupportsLuks(const char *qemuimg) +{ + bool ret = false; + int exitstatus = -1; + virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", + "-f", "luks", "/dev/null", NULL); + + if (virCommandRun(cmd, &exitstatus) < 0) + goto cleanup; + + if (exitstatus == 0) + ret = true; + + cleanup: + virCommandFree(cmd); + return ret; +} + +
NACK to this function. Old qemu-img supports the -f option and correctly errors out on unsupported LUKS format. There is no reason to probe for it.
static int virStorageBackendQEMUImgBackingFormat(const char *qemuimg) { @@ -915,12 +940,18 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg) * out what else we have */ int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS;
- /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer - * understands. Since we still support QEMU 0.12 and newer, we need - * to be able to handle the previous format as can be set via a - * compat=0.10 option. */ - if (virStorageBackendQemuImgSupportsCompat(qemuimg)) - ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + /* QEMU 2.6 added support for luks - let's check for that. + * If available, then we can also assume OPTIONS_COMPAT is present */ + if (virStorageBackendQemuImgSupportsLuks(qemuimg)) { + ret = QEMU_IMG_FORMAT_LUKS; + } else { + /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer + * understands. Since we still support QEMU 0.12 and newer, we need + * to be able to handle the previous format as can be set via a + * compat=0.10 option. */ + if (virStorageBackendQemuImgSupportsCompat(qemuimg)) + ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + }
return ret; }
This won't be needed either.
@@ -1121,6 +1184,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool,
static int virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, + virStorageVolDefPtr vol, int imgformat, struct _virStorageBackendQemuImgInfo info)
FWIW the whole point of _virStorageBackendQemuImgInfo was to separate command line building from the volume definition, to allow reuse in qemuDomainSnapshotCreateInactiveExternal where we don't deal with a volume. But I never got around to finishing it. Please use virStorageEncryptionInfoDefPtr instead of virStorageVolDefPtr here.
{ @@ -1130,7 +1194,8 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) info.compat = "0.10";
- if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0) + if (virStorageBackendCreateQemuImgOpts(&vol->target.encryption->encinfo,
Can vol->target.encryption be NULL here?
+ &opts, info) < 0) return -1; if (opts) virCommandAddArgList(cmd, "-o", opts, NULL); @@ -1140,6 +1205,43 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, }
+/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + char *str = NULL; + virJSONValuePtr props = NULL; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) { + VIR_FREE(str); + return -1; + } + VIR_FREE(str); + + if (virJSONValueObjectCreate(&props, "s:file", info->secretPath, NULL) < 0) + return -1; + + if (!(commandStr = virQEMUBuildObjectCommandlineFromJSON("secret", + info->secretAlias, + props))) { + virJSONValueFree(props); + return -1; + } + virJSONValueFree(props); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + return 0; +} + + /* Create a qemu-img virCommand from the supplied binary path, * volume definitions and imgformat */ @@ -1150,7 +1252,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat) + int imgformat, + const char *secretPath) { virCommandPtr cmd = NULL; const char *type; @@ -1162,6 +1265,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, .compat = vol->target.compat, .features = vol->target.features, .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, };
virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); @@ -1192,6 +1297,18 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, _("format features only available with qcow2")); return NULL; } + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with luks volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + }
if (inputvol && virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) @@ -1207,7 +1324,6 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, conn, vol) < 0) return NULL;
-
Spurious whitespace change.
/* Size in KB */ info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024);
@@ -1226,11 +1342,21 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, if (info.backingPath) virCommandAddArgList(cmd, "-b", info.backingPath, NULL);
- if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, info) < 0) { + if (info.format == VIR_STORAGE_FILE_LUKS && + virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); virCommandFree(cmd); return NULL; }
+ if (virStorageBackendCreateQemuImgSetOptions(cmd, vol, imgformat, + info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + VIR_FREE(info.secretAlias); + if (info.inputPath) virCommandAddArg(cmd, info.inputPath); virCommandAddArg(cmd, info.path); @@ -1240,6 +1366,78 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, return cmd; }
+ +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } +
+ /* Since we don't have a file, just go to cleanup using NULL secretPath */
What is the purpose of this comment?
+ if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen);
Using VIR_DISPOSE seems superstitious, considering that: we don't use it even once in the secret code and the secrets are in memory all the time.
+ VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + int virStorageBackendCreateQemuImg(virConnectPtr conn, virStoragePoolObjPtr pool, @@ -1251,6 +1449,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, char *create_tool; int imgformat; virCommandPtr cmd; + char *secretPath = NULL;
virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
@@ -1266,8 +1465,21 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (imgformat < 0) goto cleanup;
+ if (vol->target.format == VIR_STORAGE_FILE_LUKS) { + if (imgformat < QEMU_IMG_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("this version of '%s' does not support luks"), + create_tool); + goto cleanup; + } + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, imgformat); + flags, create_tool, + imgformat, secretPath); if (!cmd) goto cleanup;
@@ -1275,6 +1487,10 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
virCommandFree(cmd); cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } VIR_FREE(create_tool); return ret; } diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index 5bc622c..28e1a65 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -241,7 +241,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat); + int imgformat, + const char *secretPath);
/* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; diff --git a/src/util/virqemu.c b/src/util/virqemu.c index 895168e..4e4a6dd 100644 --- a/src/util/virqemu.c +++ b/src/util/virqemu.c @@ -140,3 +140,26 @@ virQEMUBuildObjectCommandlineFromJSON(const char *type, virBufferFreeAndReset(&buf); return ret; } + + +void +virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) +{ + virBufferAsprintf(buf, "key-secret=%s,", alias); + + /* If there's any cipher, then add that to the command line */ + if (enc->cipher_name) { + virBufferAsprintf(buf, "cipher-alg=%s-%u,", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, "cipher-mode=%s,", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, "hash-alg=%s,", enc->cipher_hash); + if (enc->ivgen_name) + virBufferAsprintf(buf, "ivgen-alg=%s,", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, "ivgen-hash-alg=%s,", enc->ivgen_hash);
These strings should be escaped or otherwise validated. ACK with the possible segfault fixed and virStorageBackendQemuImgSupportsLuks removed. Jan

On 06/25/2016 02:18 AM, Ján Tomko wrote:
On Fri, Jun 24, 2016 at 04:53:35PM -0400, John Ferlan wrote:
Partially resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021
If the volume xml was looking to create a luks volume take the necessary steps in order to make that happen.
The processing will be: 1. create a temporary file in the storage driver state dir path 1a. the file name will include the pool name, the volume name, secret, and XXXXXX for usage in the mkostemp call. 1b. mkostemp the file, initially setting mode to 0600 with current effective uid:gid as owner 1c. fetch the secret into a buffer and write that into the file
2. create a secret object 2a. use an alias combinding the volume name and "_luks0" 2b. add the file to the object
3. create/add luks options to the commandline 3a. at the very least a "key-secret" using the secret object alias 3b. if found in the XML the various "cipher" and "ivgen" options
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/libvirt_private.syms | 1 + src/storage/storage_backend.c | 254 ++++++++++++++++++++++++++++++++++++++--- src/storage/storage_backend.h | 3 +- src/util/virqemu.c | 23 ++++ src/util/virqemu.h | 6 + tests/storagevolxml2argvtest.c | 3 +- 6 files changed, 269 insertions(+), 21 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9ac033d..eea18dd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2169,6 +2169,7 @@ virProcessWait;
# util/virqemu.h +virQEMUBuildLuksOpts; virQEMUBuildObjectCommandlineFromJSON;
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 97f6ffe..4fb77fc 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -55,11 +55,14 @@ #include "viralloc.h" #include "internal.h" #include "secret_conf.h" +#include "secret_util.h" #include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" #include "virfile.h" +#include "virjson.h" +#include "virqemu.h" #include "stat-time.h" #include "virstring.h" #include "virxml.h"
@@ -880,6 +883,7 @@ virStoragePloopResize(virStorageVolDefPtr vol, enum { QEMU_IMG_BACKING_FORMAT_OPTIONS = 0, QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT, + QEMU_IMG_FORMAT_LUKS, };
static bool @@ -907,6 +911,27 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg) return ret; }
+ +static bool +virStorageBackendQemuImgSupportsLuks(const char *qemuimg) +{ + bool ret = false; + int exitstatus = -1; + virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?", + "-f", "luks", "/dev/null", NULL); + + if (virCommandRun(cmd, &exitstatus) < 0) + goto cleanup; + + if (exitstatus == 0) + ret = true; + + cleanup: + virCommandFree(cmd); + return ret; +} + +
NACK to this function.
Old qemu-img supports the -f option and correctly errors out on unsupported LUKS format. There is no reason to probe for it.
static int virStorageBackendQEMUImgBackingFormat(const char *qemuimg) { @@ -915,12 +940,18 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg) * out what else we have */ int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS;
- /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer - * understands. Since we still support QEMU 0.12 and newer, we need - * to be able to handle the previous format as can be set via a - * compat=0.10 option. */ - if (virStorageBackendQemuImgSupportsCompat(qemuimg)) - ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + /* QEMU 2.6 added support for luks - let's check for that. + * If available, then we can also assume OPTIONS_COMPAT is present */ + if (virStorageBackendQemuImgSupportsLuks(qemuimg)) { + ret = QEMU_IMG_FORMAT_LUKS; + } else { + /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer + * understands. Since we still support QEMU 0.12 and newer, we need + * to be able to handle the previous format as can be set via a + * compat=0.10 option. */ + if (virStorageBackendQemuImgSupportsCompat(qemuimg)) + ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT; + }
return ret; }
This won't be needed either.
That'll make Peter happier... It did seem though that the purpose of this function was to check for features available by the options that are found in qemu-img. Sure, qemu-img will fail, but that will be much later.
@@ -1121,6 +1184,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool,
static int virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, + virStorageVolDefPtr vol, int imgformat, struct _virStorageBackendQemuImgInfo info)
FWIW the whole point of _virStorageBackendQemuImgInfo was to separate command line building from the volume definition, to allow reuse in qemuDomainSnapshotCreateInactiveExternal where we don't deal with a volume. But I never got around to finishing it.
Please use virStorageEncryptionInfoDefPtr instead of virStorageVolDefPtr here.
OK - that works. I also added a bit of comments to the structure...
{ @@ -1130,7 +1194,8 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) info.compat = "0.10";
- if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0) + if (virStorageBackendCreateQemuImgOpts(&vol->target.encryption->encinfo,
Can vol->target.encryption be NULL here?
Good catch. Back in the caller virStorageBackendCreateQemuImgCmdFromVol, I added a local 'enc' initialized to NULL. Then only set it when info.format == VIR_STORAGE_FILE_LUKS && virStorageBackendCreateQemuImgSecretObject is successful. Then in virStorageBackendCreateQemuImgOpts, if info.format == VIR_STORAGE_FILE_LUKS, I'll ensure 'enc' is not NULL before trying to call virQEMUBuildLuksOpts. If it is NULL, then the code will just error out.
+ &opts, info) < 0) return -1; if (opts) virCommandAddArgList(cmd, "-o", opts, NULL); @@ -1140,6 +1205,43 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, }
+/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + char *str = NULL; + virJSONValuePtr props = NULL; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) { + VIR_FREE(str); + return -1; + } + VIR_FREE(str); + + if (virJSONValueObjectCreate(&props, "s:file", info->secretPath, NULL) < 0) + return -1; + + if (!(commandStr = virQEMUBuildObjectCommandlineFromJSON("secret", + info->secretAlias, + props))) { + virJSONValueFree(props); + return -1; + } + virJSONValueFree(props); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + return 0; +} + + /* Create a qemu-img virCommand from the supplied binary path, * volume definitions and imgformat */ @@ -1150,7 +1252,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat) + int imgformat, + const char *secretPath) { virCommandPtr cmd = NULL; const char *type; @@ -1162,6 +1265,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, .compat = vol->target.compat, .features = vol->target.features, .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, };
virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); @@ -1192,6 +1297,18 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, _("format features only available with qcow2")); return NULL; } + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with luks volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + }
if (inputvol && virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) @@ -1207,7 +1324,6 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, conn, vol) < 0) return NULL;
-
Spurious whitespace change.
OK
/* Size in KB */ info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024);
@@ -1226,11 +1342,21 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, if (info.backingPath) virCommandAddArgList(cmd, "-b", info.backingPath, NULL);
- if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, info) < 0) { + if (info.format == VIR_STORAGE_FILE_LUKS && + virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); virCommandFree(cmd); return NULL; }
+ if (virStorageBackendCreateQemuImgSetOptions(cmd, vol, imgformat, + info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + VIR_FREE(info.secretAlias); + if (info.inputPath) virCommandAddArg(cmd, info.inputPath); virCommandAddArg(cmd, info.path); @@ -1240,6 +1366,78 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, return cmd; }
+ +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } +
+ /* Since we don't have a file, just go to cleanup using NULL secretPath */
What is the purpose of this comment?
it's gone
+ if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen);
Using VIR_DISPOSE seems superstitious, considering that: we don't use it even once in the secret code and the secrets are in memory all the time.
Sure, but it's a "consistency" thing related to other uses where once the stack variable for secret is saved away, we clear out the whatever we had locally.
+ VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + int virStorageBackendCreateQemuImg(virConnectPtr conn, virStoragePoolObjPtr pool, @@ -1251,6 +1449,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, char *create_tool; int imgformat; virCommandPtr cmd; + char *secretPath = NULL;
virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
@@ -1266,8 +1465,21 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (imgformat < 0) goto cleanup;
+ if (vol->target.format == VIR_STORAGE_FILE_LUKS) { + if (imgformat < QEMU_IMG_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("this version of '%s' does not support luks"), + create_tool); + goto cleanup; + } + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, imgformat); + flags, create_tool, + imgformat, secretPath); if (!cmd) goto cleanup;
@@ -1275,6 +1487,10 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
virCommandFree(cmd); cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } VIR_FREE(create_tool); return ret; } diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index 5bc622c..28e1a65 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -241,7 +241,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat); + int imgformat, + const char *secretPath);
/* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; diff --git a/src/util/virqemu.c b/src/util/virqemu.c index 895168e..4e4a6dd 100644 --- a/src/util/virqemu.c +++ b/src/util/virqemu.c @@ -140,3 +140,26 @@ virQEMUBuildObjectCommandlineFromJSON(const char *type, virBufferFreeAndReset(&buf); return ret; } + + +void +virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) +{ + virBufferAsprintf(buf, "key-secret=%s,", alias); + + /* If there's any cipher, then add that to the command line */ + if (enc->cipher_name) { + virBufferAsprintf(buf, "cipher-alg=%s-%u,", + enc->cipher_name, enc->cipher_size); + if (enc->cipher_mode) + virBufferAsprintf(buf, "cipher-mode=%s,", enc->cipher_mode); + if (enc->cipher_hash) + virBufferAsprintf(buf, "hash-alg=%s,", enc->cipher_hash); + if (enc->ivgen_name) + virBufferAsprintf(buf, "ivgen-alg=%s,", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferAsprintf(buf, "ivgen-hash-alg=%s,", enc->ivgen_hash);
These strings should be escaped or otherwise validated.
Escaped
ACK with the possible segfault fixed and virStorageBackendQemuImgSupportsLuks removed.
Thanks - John

Introduce a helper to help determine if a disk src could be possibly used for a disk secret... Going to need this for hot unplug. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_domain.c | 20 +++++++++++++++----- src/qemu/qemu_domain.h | 3 +++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index da5bb79..938b524 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -972,6 +972,20 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk) } +bool +qemuDomainSecretDiskCapable(virStorageSourcePtr src) +{ + if (!virStorageSourceIsEmpty(src) && + virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_NETWORK && + src->auth && + (src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI || + src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD)) + return true; + + return false; +} + + /* qemuDomainSecretDiskPrepare: * @conn: Pointer to connection * @priv: pointer to domain private object @@ -989,11 +1003,7 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, virStorageSourcePtr src = disk->src; qemuDomainSecretInfoPtr secinfo = NULL; - if (conn && !virStorageSourceIsEmpty(src) && - virStorageSourceGetActualType(src) == VIR_STORAGE_TYPE_NETWORK && - src->auth && - (src->protocol == VIR_STORAGE_NET_PROTOCOL_ISCSI || - src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD)) { + if (conn && qemuDomainSecretDiskCapable(src)) { virSecretUsageType secretUsageType = VIR_SECRET_USAGE_TYPE_ISCSI; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h index c87dcc7..c49f31c 100644 --- a/src/qemu/qemu_domain.h +++ b/src/qemu/qemu_domain.h @@ -670,6 +670,9 @@ void qemuDomainMasterKeyRemove(qemuDomainObjPrivatePtr priv); void qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk) ATTRIBUTE_NONNULL(1); +bool qemuDomainSecretDiskCapable(virStorageSourcePtr src) + ATTRIBUTE_NONNULL(1); + int qemuDomainSecretDiskPrepare(virConnectPtr conn, qemuDomainObjPrivatePtr priv, virDomainDiskDefPtr disk) -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:36PM -0400, John Ferlan wrote:
Introduce a helper to help determine if a disk src could be possibly used for a disk secret... Going to need this for hot unplug.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_domain.c | 20 +++++++++++++++----- src/qemu/qemu_domain.h | 3 +++ 2 files changed, 18 insertions(+), 5 deletions(-)
ACK Jan

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 | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index f695903..fbe3cb8 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); @@ -2791,6 +2813,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, const char *src = virDomainDiskGetSource(disk); qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; + char *objAlias = NULL; VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2801,7 +2824,24 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, QEMU_DRIVE_HOST_PREFIX, disk->info.alias) < 0) return -1; + /* Let's look for some markers for a secret object and create an alias + * object to be used to attempt to delete the object that was created */ + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && + qemuDomainSecretDiskCapable(disk->src)) { + + if (!(objAlias = qemuDomainGetSecretAESAlias(disk->info.alias))) { + VIR_FREE(drivestr); + return -1; + } + } + qemuDomainObjEnterMonitor(driver, vm); + + /* If it fails, then so be it - it was a best shot */ + if (objAlias) + ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); + VIR_FREE(objAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:37PM -0400, John Ferlan wrote:
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 | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-)
ACK, terms and conditions apply.
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index f695903..fbe3cb8 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;
This seems to be the only usage of diskPriv. You can do QEMU_DOMAIN_DISK_PRIVATE(disk)->secinfo directly.
+ 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)
It would be nice to set secobjProps to NULL here right after it's invalid.
+ goto failaddobjsecret;
s/failaddobjsecret/exit_monitor/
+ 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));
This can be done if (secinfo) then. Also, the error should be saved since qemuMonitorDelObject can overwrite it.
+ + failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) releaseaddr = false; + secobjProps = NULL; /* qemuMonitorAddObject consumes props on failure too */
failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -2791,6 +2813,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, const char *src = virDomainDiskGetSource(disk); qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; + char *objAlias = NULL;
VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2801,7 +2824,24 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, QEMU_DRIVE_HOST_PREFIX, disk->info.alias) < 0) return -1;
+ /* Let's look for some markers for a secret object and create an alias + * object to be used to attempt to delete the object that was created */ + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && + qemuDomainSecretDiskCapable(disk->src)) { +
Do we still have QEMU_DOMAIN_DISK_PRIVATE to check secinfo, just like we did when adding the secret? Jan
+ if (!(objAlias = qemuDomainGetSecretAESAlias(disk->info.alias))) { + VIR_FREE(drivestr); + return -1; + } + } + qemuDomainObjEnterMonitor(driver, vm); + + /* If it fails, then so be it - it was a best shot */ + if (objAlias) + ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); + VIR_FREE(objAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) -- 2.5.5
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

On 06/25/2016 02:43 AM, Ján Tomko wrote:
On Fri, Jun 24, 2016 at 04:53:37PM -0400, John Ferlan wrote:
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 | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-)
ACK, terms and conditions apply.
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index f695903..fbe3cb8 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;
This seems to be the only usage of diskPriv. You can do QEMU_DOMAIN_DISK_PRIVATE(disk)->secinfo directly.
Understood, but I'm not a fan of that look. I'll keep it the way it is.
+ 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)
It would be nice to set secobjProps to NULL here right after it's invalid.
+ goto failaddobjsecret;
s/failaddobjsecret/exit_monitor/
So you think it looks better to: if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", secinfo->s.aes.alias, secobjProps) < 0) { secobjProps = NULL; goto some-label-to-ExitMonitor; } secobjProps = NULL; That seems to go against other exit/error paths. Is it wrong the way it is? If not, I see no reason to change it.
+ 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));
This can be done if (secinfo) then. Also, the error should be saved since qemuMonitorDelObject can overwrite it.
Just secinfo isn't enough, it needs to be secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES as well... If we're _TYPE_PLAIN, then there's nothing to do. I think keeping secobjProps is cleaner. I added code to save the error - although it seems that every one of those labels could end up in the same situation - going through code that could overwrite the error. It'll be fixed for the failure labels as a result of failure while in the monitor, but I think a future cleanup could/should encompass all those labels.
+ + failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) releaseaddr = false; + secobjProps = NULL; /* qemuMonitorAddObject consumes props on failure too */
failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -2791,6 +2813,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, const char *src = virDomainDiskGetSource(disk); qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; + char *objAlias = NULL;
VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2801,7 +2824,24 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, QEMU_DRIVE_HOST_PREFIX, disk->info.alias) < 0) return -1;
+ /* Let's look for some markers for a secret object and create an alias + * object to be used to attempt to delete the object that was created */ + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && + qemuDomainSecretDiskCapable(disk->src)) { +
Do we still have QEMU_DOMAIN_DISK_PRIVATE to check secinfo, just like we did when adding the secret?
From Peter's review comments of the previous series - no. In fact, the cleanup of qemuProcessLaunch was designed to remove the Secrets that we saved away temporarily so that they wouldn't be kept in memory forever (e.g call to qemuDomainSecretDiskDestroy). Hence why the Attach
processing needs the qemuDomainSecretDiskPrepare (and also calls qemuDomainSecretDiskDestroy). Thus, the detach processing, then can only work off the alias to remove. Tks - John
Jan
+ if (!(objAlias = qemuDomainGetSecretAESAlias(disk->info.alias))) { + VIR_FREE(drivestr); + return -1; + } + } + qemuDomainObjEnterMonitor(driver, vm); + + /* If it fails, then so be it - it was a best shot */ + if (objAlias) + ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); + VIR_FREE(objAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) -- 2.5.5
-- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list

Soon we will be adding luks encryption support. Since a volume could require both a luks secret and a secret to give to the server to use of the device, alter the alias generation to create a slightly different alias so that we don't have two objects with the same alias. Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_alias.c | 10 ++++++++-- src/qemu/qemu_alias.h | 3 ++- src/qemu/qemu_domain.c | 17 ++++++++++------- src/qemu/qemu_hotplug.c | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c index d624071..51a654a 100644 --- a/src/qemu/qemu_alias.c +++ b/src/qemu/qemu_alias.c @@ -485,13 +485,16 @@ qemuDomainGetMasterKeyAlias(void) /* qemuDomainGetSecretAESAlias: + * @srcalias: Source alias used to generate the secret alias + * @isLuks: True when we are generating a secret for LUKS encrypt/decrypt * * Generate and return an alias for the encrypted secret * * Returns NULL or a string containing the alias */ char * -qemuDomainGetSecretAESAlias(const char *srcalias) +qemuDomainGetSecretAESAlias(const char *srcalias, + bool isLuks) { char *alias; @@ -501,7 +504,10 @@ qemuDomainGetSecretAESAlias(const char *srcalias) return NULL; } - ignore_value(virAsprintf(&alias, "%s-secret0", srcalias)); + if (isLuks) + ignore_value(virAsprintf(&alias, "%s-luks-secret0", srcalias)); + else + ignore_value(virAsprintf(&alias, "%s-secret0", srcalias)); return alias; } diff --git a/src/qemu/qemu_alias.h b/src/qemu/qemu_alias.h index e328a9b..d1c6ba8 100644 --- a/src/qemu/qemu_alias.h +++ b/src/qemu/qemu_alias.h @@ -69,6 +69,7 @@ char *qemuAliasFromDisk(const virDomainDiskDef *disk); char *qemuDomainGetMasterKeyAlias(void); -char *qemuDomainGetSecretAESAlias(const char *srcalias); +char *qemuDomainGetSecretAESAlias(const char *srcalias, + bool isLuks); #endif /* __QEMU_ALIAS_H__*/ diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index 938b524..d51e82b 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -847,6 +847,7 @@ qemuDomainSecretPlainSetup(virConnectPtr conn, * @secretUsageType: The virSecretUsageType * @username: username to use for authentication (may be NULL) * @seclookupdef: Pointer to seclookupdef data + * @isLuks: True/False for is for luks (alias generation) * * Taking a secinfo, fill in the AES specific information using the * @@ -859,7 +860,8 @@ qemuDomainSecretAESSetup(virConnectPtr conn, const char *srcalias, virSecretUsageType secretUsageType, const char *username, - virSecretLookupTypeDefPtr seclookupdef) + virSecretLookupTypeDefPtr seclookupdef, + bool isLuks) { int ret = -1; uint8_t *raw_iv = NULL; @@ -873,7 +875,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, if (VIR_STRDUP(secinfo->s.aes.username, username) < 0) return -1; - if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias))) + if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias, isLuks))) return -1; /* Create a random initialization vector */ @@ -922,6 +924,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn, * @secretUsageType: The virSecretUsageType * @username: username to use for authentication (may be NULL) * @seclookupdef: Pointer to seclookupdef data + * @isLuks: True when is luks (generates different alias) * * 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 @@ -937,14 +940,15 @@ qemuDomainSecretSetup(virConnectPtr conn, const char *srcalias, virSecretUsageType secretUsageType, const char *username, - virSecretLookupTypeDefPtr seclookupdef) + virSecretLookupTypeDefPtr seclookupdef, + bool isLuks) { 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, username, - seclookupdef) < 0) + seclookupdef, isLuks) < 0) return -1; } else { if (qemuDomainSecretPlainSetup(conn, secinfo, secretUsageType, @@ -1004,7 +1008,6 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, qemuDomainSecretInfoPtr secinfo = NULL; if (conn && qemuDomainSecretDiskCapable(src)) { - virSecretUsageType secretUsageType = VIR_SECRET_USAGE_TYPE_ISCSI; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); @@ -1016,7 +1019,7 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias, secretUsageType, src->auth->username, - &src->auth->seclookupdef) < 0) + &src->auth->seclookupdef, false) < 0) goto error; diskPriv->secinfo = secinfo; @@ -1083,7 +1086,7 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn, if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias, VIR_SECRET_USAGE_TYPE_ISCSI, iscsisrc->auth->username, - &iscsisrc->auth->seclookupdef) < 0) + &iscsisrc->auth->seclookupdef, false) < 0) goto error; hostdevPriv->secinfo = secinfo; diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index fbe3cb8..8acb69d 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -2829,7 +2829,8 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && qemuDomainSecretDiskCapable(disk->src)) { - if (!(objAlias = qemuDomainGetSecretAESAlias(disk->info.alias))) { + if (!(objAlias = + qemuDomainGetSecretAESAlias(disk->info.alias, false))) { VIR_FREE(drivestr); return -1; } -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:38PM -0400, John Ferlan wrote:
Soon we will be adding luks encryption support. Since a volume could require both a luks secret and a secret to give to the server to use of the device, alter the alias generation to create a slightly different alias so that we don't have two objects with the same alias.
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_alias.c | 10 ++++++++-- src/qemu/qemu_alias.h | 3 ++- src/qemu/qemu_domain.c | 17 ++++++++++------- src/qemu/qemu_hotplug.c | 3 ++- 4 files changed, 22 insertions(+), 11 deletions(-)
ACK Jan

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021 Generate the luks command line using the AES secret key to encrypt the luks secret. A luks secret object will be in addition to a an AES secret. For hotplug, check if the encinfo exists and if so, add the AES secret for the passphrase for the secret object used to decrypt the device. Add tests for sample output Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 9 +++ src/qemu/qemu_domain.c | 25 ++++++- src/qemu/qemu_hotplug.c | 82 +++++++++++++++++++--- .../qemuxml2argv-luks-disk-cipher.args | 36 ++++++++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 ++++++++++ tests/qemuxml2argvtest.c | 11 ++- 6 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 71e9e63..038a60c 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1098,6 +1098,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, int actualType = virStorageSourceGetActualType(disk->src); qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; bool emitDeviceSyntax = qemuDiskBusNeedsDeviceArg(disk->bus); if (idx < 0) { @@ -1237,6 +1238,10 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, secinfo->s.aes.alias); } + if (encinfo) + virQEMUBuildLuksOpts(&opt, &disk->src->encryption->encinfo, + encinfo->s.aes.alias); + if (disk->src->format > 0 && disk->src->type != VIR_STORAGE_TYPE_DIR) virBufferAsprintf(&opt, "format=%s,", @@ -1939,6 +1944,7 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, virDomainDiskDefPtr disk = def->disks[i]; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; /* PowerPC pseries based VMs do not support floppy device */ if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY && @@ -1967,6 +1973,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0) return -1; + if (qemuBuildDiskSecinfoCommandLine(cmd, encinfo) < 0) + return -1; + virCommandAddArg(cmd, "-drive"); optstr = qemuBuildDriveStr(disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d51e82b..5405365 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -945,7 +945,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, isLuks) < 0) @@ -1005,11 +1006,14 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, virDomainDiskDefPtr disk) { virStorageSourcePtr src = disk->src; + qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = NULL; - if (conn && qemuDomainSecretDiskCapable(src)) { + if (!conn) + return 0; + + if (qemuDomainSecretDiskCapable(src)) { virSecretUsageType secretUsageType = VIR_SECRET_USAGE_TYPE_ISCSI; - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); if (VIR_ALLOC(secinfo) < 0) return -1; @@ -1025,6 +1029,21 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, diskPriv->secinfo = secinfo; } + if (!virStorageSourceIsEmpty(src) && src->encryption && + src->format == VIR_STORAGE_FILE_LUKS) { + + if (VIR_ALLOC(secinfo) < 0) + return -1; + + if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, NULL, + &src->encryption->secrets[0]->seclookupdef, + true) < 0) + goto error; + + diskPriv->encinfo = secinfo; + } + return 0; error: diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index 8acb69d..0b8b716 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -311,8 +311,10 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); const char *src = virDomainDiskGetSource(disk); virJSONValuePtr secobjProps = NULL; + virJSONValuePtr encProps = NULL; qemuDomainDiskPrivatePtr diskPriv; qemuDomainSecretInfoPtr secinfo; + qemuDomainSecretInfoPtr encinfo; if (!disk->info.type) { if (qemuDomainMachineIsS390CCW(vm->def) && @@ -352,6 +354,10 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto error; } + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(drivestr = qemuBuildDriveStr(disk, false, priv->qemuCaps))) goto error; @@ -364,7 +370,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error; - /* Attach the device - possible 3 step process */ + /* Attach the device - possible 4 step process */ qemuDomainObjEnterMonitor(driver, vm); if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", @@ -373,6 +379,12 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto failaddobjsecret; secobjProps = NULL; + if (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto failaddencsecret; + encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -391,6 +403,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, cleanup: virJSONValueFree(secobjProps); + virJSONValueFree(encProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -410,8 +423,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, } failadddrive: + if (encProps) + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias)); + + failaddencsecret: if (secobjProps) ignore_value(qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias)); + encProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) @@ -571,6 +589,9 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, char *devstr = NULL; int ret = -1; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + virJSONValuePtr encProps = NULL; + qemuDomainDiskPrivatePtr diskPriv; + qemuDomainSecretInfoPtr encinfo; if (qemuDomainPrepareDisk(driver, vm, disk, NULL, false) < 0) goto cleanup; @@ -589,6 +610,11 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error; + diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(devstr = qemuBuildDriveDevStr(vm->def, disk, 0, priv->qemuCaps))) goto error; @@ -598,9 +624,15 @@ qemuDomainAttachSCSIDisk(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 (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto failaddencsecret; + encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -627,7 +659,12 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, VIR_WARN("qemuMonitorAddDevice failed on %s (%s)", drivestr, devstr); failadddrive: + if (encProps) + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias)); + + failaddencsecret: ignore_value(qemuDomainObjExitMonitor(driver, vm)); + encProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -2814,6 +2851,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; char *objAlias = NULL; + char *encAlias = NULL; VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2836,6 +2874,21 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, } } + /* Similarly, if this is possible a device using LUKS encryption, we + * can remove the luks object password too + */ + if (!virStorageSourceIsEmpty(disk->src) && disk->src->encryption && + disk->src->format == VIR_STORAGE_FILE_LUKS) { + + if (!(encAlias = + qemuDomainGetSecretAESAlias(disk->info.alias, true))) { + VIR_FREE(objAlias); + VIR_FREE(drivestr); + return -1; + } + } + + qemuDomainObjEnterMonitor(driver, vm); /* If it fails, then so be it - it was a best shot */ @@ -2843,6 +2896,11 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); VIR_FREE(objAlias); + /* If it fails, then so be it - it was a best shot */ + if (encAlias) + ignore_value(qemuMonitorDelObject(priv->mon, encAlias)); + VIR_FREE(encAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) @@ -3487,6 +3545,8 @@ qemuDomainDetachDiskDevice(virQEMUDriverPtr driver, { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; + qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(detach); + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; if (qemuDomainDiskBlockJobIsActive(detach)) goto cleanup; @@ -3494,12 +3554,12 @@ qemuDomainDetachDiskDevice(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 (encinfo && qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias) < 0) + goto faildel; + + if (qemuMonitorDelDevice(priv->mon, detach->info.alias) < 0) + goto faildel; + if (qemuDomainObjExitMonitor(driver, vm) < 0) goto cleanup; @@ -3509,6 +3569,12 @@ qemuDomainDetachDiskDevice(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 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args new file mode 100644 index 0000000..efb5cb0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name encryptdisk \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-encryptdisk/master-key.aes \ +-M pc-i440fx-2.1 \ +-m 1024 \ +-smp 1 \ +-uuid 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 \ +-nographic \ +-nodefaults \ +-monitor unix:/tmp/lib/domain--1-encryptdisk/monitor.sock,server,nowait \ +-no-acpi \ +-boot c \ +-usb \ +-object secret,id=virtio-disk0-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk,\ +key-secret=virtio-disk0-luks-secret0,format=luks,if=none,id=drive-virtio-disk0 \ +-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\ +id=virtio-disk0 \ +-object secret,id=virtio-disk1-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk2,\ +key-secret=virtio-disk1-luks-secret0,format=luks,if=none,id=drive-virtio-disk1 \ +-device virtio-blk-pci,bus=pci.0,addr=0x5,drive=drive-virtio-disk1,\ +id=virtio-disk1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args new file mode 100644 index 0000000..efb5cb0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name encryptdisk \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-encryptdisk/master-key.aes \ +-M pc-i440fx-2.1 \ +-m 1024 \ +-smp 1 \ +-uuid 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 \ +-nographic \ +-nodefaults \ +-monitor unix:/tmp/lib/domain--1-encryptdisk/monitor.sock,server,nowait \ +-no-acpi \ +-boot c \ +-usb \ +-object secret,id=virtio-disk0-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk,\ +key-secret=virtio-disk0-luks-secret0,format=luks,if=none,id=drive-virtio-disk0 \ +-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\ +id=virtio-disk0 \ +-object secret,id=virtio-disk1-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk2,\ +key-secret=virtio-disk1-luks-secret0,format=luks,if=none,id=drive-virtio-disk1 \ +-device virtio-blk-pci,bus=pci.0,addr=0x5,drive=drive-virtio-disk1,\ +id=virtio-disk1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index ca52ab0..aba346d 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -62,10 +62,17 @@ fakeSecretLookupByUsage(virConnectPtr conn, return virGetSecret(conn, uuid, usageType, usageID); } +static virSecretPtr +fakeSecretLookupByUUID(virConnectPtr conn, + const unsigned char *uuid) +{ + return virGetSecret(conn, uuid, 0, ""); +} + static virSecretDriver fakeSecretDriver = { .connectNumOfSecrets = NULL, .connectListSecrets = NULL, - .secretLookupByUUID = NULL, + .secretLookupByUUID = fakeSecretLookupByUUID, .secretLookupByUsage = fakeSecretLookupByUsage, .secretDefineXML = NULL, .secretGetXMLDesc = NULL, @@ -1342,6 +1349,8 @@ mymain(void) DO_TEST("encrypted-disk", NONE); DO_TEST("encrypted-disk-usage", NONE); + DO_TEST("luks-disks", QEMU_CAPS_OBJECT_SECRET); + DO_TEST("luks-disk-cipher", QEMU_CAPS_OBJECT_SECRET); DO_TEST("memtune", NONE); DO_TEST("memtune-unlimited", NONE); -- 2.5.5

On Fri, Jun 24, 2016 at 04:53:39PM -0400, John Ferlan wrote:
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021
Generate the luks command line using the AES secret key to encrypt the luks secret. A luks secret object will be in addition to a an AES secret.
For hotplug, check if the encinfo exists and if so, add the AES secret for the passphrase for the secret object used to decrypt the device.
Add tests for sample output
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 9 +++ src/qemu/qemu_domain.c | 25 ++++++- src/qemu/qemu_hotplug.c | 82 +++++++++++++++++++--- .../qemuxml2argv-luks-disk-cipher.args | 36 ++++++++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 ++++++++++ tests/qemuxml2argvtest.c | 11 ++- 6 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 71e9e63..038a60c 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1098,6 +1098,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, int actualType = virStorageSourceGetActualType(disk->src); qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; bool emitDeviceSyntax = qemuDiskBusNeedsDeviceArg(disk->bus);
if (idx < 0) { @@ -1237,6 +1238,10 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, secinfo->s.aes.alias); }
+ if (encinfo) + virQEMUBuildLuksOpts(&opt, &disk->src->encryption->encinfo, + encinfo->s.aes.alias); + if (disk->src->format > 0 && disk->src->type != VIR_STORAGE_TYPE_DIR) virBufferAsprintf(&opt, "format=%s,", @@ -1939,6 +1944,7 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, virDomainDiskDefPtr disk = def->disks[i]; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo;
/* PowerPC pseries based VMs do not support floppy device */ if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY && @@ -1967,6 +1973,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0) return -1;
+ if (qemuBuildDiskSecinfoCommandLine(cmd, encinfo) < 0) + return -1; + virCommandAddArg(cmd, "-drive");
optstr = qemuBuildDriveStr(disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d51e82b..5405365 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -945,7 +945,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)) {
Since we have not allowed VIR_SECRET_USAGE_TYPE_PASSPHRASE before, can we error out if we don't have both VIR_CRYPTO_CIPHER_AES256CBC and QEMU_CAPS_OBJECT_SECRET to avoid putting plaintext on qemu command line?
if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, secretUsageType, username, seclookupdef, isLuks) < 0) @@ -364,7 +370,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error;
- /* Attach the device - possible 3 step process */ + /* Attach the device - possible 4 step process */
If you delete this outdated comment in 8/10, you don't have to update it here.
qemuDomainObjEnterMonitor(driver, vm);
if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", @@ -373,6 +379,12 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto failaddobjsecret; secobjProps = NULL;
+ if (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto failaddencsecret;
s/failaddencsecret/delete_secinfo/
+ encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive;
@@ -391,6 +403,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn,
cleanup: virJSONValueFree(secobjProps); + virJSONValueFree(encProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -410,8 +423,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, }
failadddrive: + if (encProps) + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias));
This can also overwrite the error.
+ + failaddencsecret: if (secobjProps) ignore_value(qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias)); + encProps = NULL; /* qemuMonitorAddObject consumes props on failure too */
failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) @@ -571,6 +589,9 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, char *devstr = NULL; int ret = -1; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + virJSONValuePtr encProps = NULL; + qemuDomainDiskPrivatePtr diskPriv; + qemuDomainSecretInfoPtr encinfo;
if (qemuDomainPrepareDisk(driver, vm, disk, NULL, false) < 0) goto cleanup; @@ -589,6 +610,11 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error;
+ diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(devstr = qemuBuildDriveDevStr(vm->def, disk, 0, priv->qemuCaps))) goto error;
@@ -598,9 +624,15 @@ qemuDomainAttachSCSIDisk(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 (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0)
Don't we need to deal with secinfo like we do for virtio disks?
+ goto failaddencsecret;
s/failaddencsecret/exit_monitor/
+ encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive;
ACK only if we don't need to deal with diskPriv->secinfo; Jan

On 06/25/2016 03:07 AM, Ján Tomko wrote:
On Fri, Jun 24, 2016 at 04:53:39PM -0400, John Ferlan wrote:
Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021
Generate the luks command line using the AES secret key to encrypt the luks secret. A luks secret object will be in addition to a an AES secret.
For hotplug, check if the encinfo exists and if so, add the AES secret for the passphrase for the secret object used to decrypt the device.
Add tests for sample output
Signed-off-by: John Ferlan <jferlan@redhat.com> --- src/qemu/qemu_command.c | 9 +++ src/qemu/qemu_domain.c | 25 ++++++- src/qemu/qemu_hotplug.c | 82 +++++++++++++++++++--- .../qemuxml2argv-luks-disk-cipher.args | 36 ++++++++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 ++++++++++ tests/qemuxml2argvtest.c | 11 ++- 6 files changed, 187 insertions(+), 12 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 71e9e63..038a60c 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1098,6 +1098,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, int actualType = virStorageSourceGetActualType(disk->src); qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; bool emitDeviceSyntax = qemuDiskBusNeedsDeviceArg(disk->bus);
if (idx < 0) { @@ -1237,6 +1238,10 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, secinfo->s.aes.alias); }
+ if (encinfo) + virQEMUBuildLuksOpts(&opt, &disk->src->encryption->encinfo, + encinfo->s.aes.alias); + if (disk->src->format > 0 && disk->src->type != VIR_STORAGE_TYPE_DIR) virBufferAsprintf(&opt, "format=%s,", @@ -1939,6 +1944,7 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, virDomainDiskDefPtr disk = def->disks[i]; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo;
/* PowerPC pseries based VMs do not support floppy device */ if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY && @@ -1967,6 +1973,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0) return -1;
+ if (qemuBuildDiskSecinfoCommandLine(cmd, encinfo) < 0) + return -1; + virCommandAddArg(cmd, "-drive");
optstr = qemuBuildDriveStr(disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d51e82b..5405365 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -945,7 +945,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)) {
Since we have not allowed VIR_SECRET_USAGE_TYPE_PASSPHRASE before, can we error out if we don't have both VIR_CRYPTO_CIPHER_AES256CBC and QEMU_CAPS_OBJECT_SECRET to avoid putting plaintext on qemu command line?
Unfortunately because of the iSCSI issue, no. Long story short, adding an AES secret for an iSCSI had an issue because the parameter used to process was only found on the -iscsi command line. In order to add the proper ID (alias) for an -iscsi command line involved providing the IQN which has a ':' in it and that character is illegal for an alias. A change Dan proposed to fix it in QEMU (late) for 2.6 was rejected: http://lists.nongnu.org/archive/html/qemu-devel/2016-04/msg02198.html
if (qemuDomainSecretAESSetup(conn, priv, secinfo, srcalias, secretUsageType, username, seclookupdef, isLuks) < 0) @@ -364,7 +370,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error;
- /* Attach the device - possible 3 step process */ + /* Attach the device - possible 4 step process */
If you delete this outdated comment in 8/10, you don't have to update it here.
OK gone in 8/10
qemuDomainObjEnterMonitor(driver, vm);
if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", @@ -373,6 +379,12 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto failaddobjsecret; secobjProps = NULL;
+ if (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto failaddencsecret;
s/failaddencsecret/delete_secinfo/
While it's not clear to me what the issue is with the label, this does bring up an interesting point about error recovery... It would seem the setting of secobjProps and encProps to NULL should not occur until after we exit the monitor; otherwise, they won't be properly deleted in the error path. So back in 8/10, rather than a secobjProps = NULL right away, it'll move to just before the call to virDomainAuditDisk
+ encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive;
@@ -391,6 +403,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn,
cleanup: virJSONValueFree(secobjProps); + virJSONValueFree(encProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -410,8 +423,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, }
failadddrive: + if (encProps) + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias));
This can also overwrite the error.
Fixed those
+ + failaddencsecret: if (secobjProps) ignore_value(qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias)); + encProps = NULL; /* qemuMonitorAddObject consumes props on failure too */
failaddobjsecret: if (qemuDomainObjExitMonitor(driver, vm) < 0) @@ -571,6 +589,9 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, char *devstr = NULL; int ret = -1; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + virJSONValuePtr encProps = NULL; + qemuDomainDiskPrivatePtr diskPriv; + qemuDomainSecretInfoPtr encinfo;
if (qemuDomainPrepareDisk(driver, vm, disk, NULL, false) < 0) goto cleanup; @@ -589,6 +610,11 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error;
+ diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(devstr = qemuBuildDriveDevStr(vm->def, disk, 0, priv->qemuCaps))) goto error;
@@ -598,9 +624,15 @@ qemuDomainAttachSCSIDisk(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 (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0)
Don't we need to deal with secinfo like we do for virtio disks?
No, iSCSI cannot support it. When/if that resolved in qemu, then yes, this follows that mechanism.
+ goto failaddencsecret;
s/failaddencsecret/exit_monitor/
I can change the label name to exit_monitor, it really doesn't matter that much to me.
+ encProps = NULL; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive;
ACK only if we don't need to deal with diskPriv->secinfo;
I assume this comment refers to -> secinfo for AttachSCSIDisk... However, I'm in a quandary. Even though I had requested to extend the deadline to generate an -rc1 for this libvirt release, it seems it wasn't. Now that we've frozen, IIUC technically according to our rules none of this can now go in; however, this is something important for the Red Hat release process timing. I can repost 8 & 10 just to be "safe" so it's "clear" what the changes were. John

Partially resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021 If the volume xml was looking to create a luks volume take the necessary steps in order to make that happen. The processing will be: 1. create a temporary file in the storage driver state dir path 1a. the file name will include the pool name, the volume name, secret, and XXXXXX for usage in the mkostemp call. 1b. mkostemp the file, initially setting mode to 0600 with current effective uid:gid as owner 1c. fetch the secret into a buffer and write that into the file 2. create a secret object 2a. use an alias combinding the volume name and "_luks0" 2b. add the file to the object 3. create/add luks options to the commandline 3a. at the very least a "key-secret" using the secret object alias 3b. if found in the XML the various "cipher" and "ivgen" options Signed-off-by: John Ferlan <jferlan@redhat.com> --- This would need to *replace* the v3 of the series. Difference are completely from jtomko review: 1. Remove virStorageBackendQemuImgSupportsLuks 2. Remove changes in virStorageBackendQEMUImgBackingFormat 3. Add comment prior to _virStorageBackendQemuImgInfo 4. Add check for NULL enc prior to call to virQEMUBuildLuksOpts 5. Use virStorageEncryptionInfoDefPtr as argument in virStorageBackendCreateQemuImgSetOptions rather than vol 6. Changed order of args too... 7. Add local 'enc' to virStorageBackendCreateQemuImgCmdFromVol and only set it if (info.format == VIR_STORAGE_FILE_LUKS) and virStorageBackendCreateQemuImgSecretObject succeeds. Then pass it as argument to virStorageBackendCreateQemuImgSetOptions 8. Removed some unnecessary comment 9. Remove if (imgformat < QEMU_IMG_FORMAT_LUKS) and allow failure of qemu-img to be the failure error message. 10. Use virBufferEscapeString in virQEMUBuildLuksOpts 11. Removed more unnecessary comments for optional... 12. Use requested error message from code review 13. Use virBufferEscapeString in virStorageEncryptionInfoDefFormat src/libvirt_private.syms | 1 + src/storage/storage_backend.c | 218 ++++++++++++++++++++++++++++++++++++++--- src/storage/storage_backend.h | 3 +- src/util/virqemu.c | 23 +++++ src/util/virqemu.h | 6 ++ tests/storagevolxml2argvtest.c | 3 +- 6 files changed, 240 insertions(+), 14 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 9ac033d..eea18dd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2169,6 +2169,7 @@ virProcessWait; # util/virqemu.h +virQEMUBuildLuksOpts; virQEMUBuildObjectCommandlineFromJSON; diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c index 97f6ffe..a9caa69 100644 --- a/src/storage/storage_backend.c +++ b/src/storage/storage_backend.c @@ -55,11 +55,14 @@ #include "viralloc.h" #include "internal.h" #include "secret_conf.h" +#include "secret_util.h" #include "viruuid.h" #include "virstoragefile.h" #include "storage_backend.h" #include "virlog.h" #include "virfile.h" +#include "virjson.h" +#include "virqemu.h" #include "stat-time.h" #include "virstring.h" #include "virxml.h" @@ -907,6 +910,7 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg) return ret; } + static int virStorageBackendQEMUImgBackingFormat(const char *qemuimg) { @@ -925,6 +929,10 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg) return ret; } +/* The _virStorageBackendQemuImgInfo separates the command line building from + * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can + * use it without needing to deal with a volume. + */ struct _virStorageBackendQemuImgInfo { int format; const char *path; @@ -941,21 +949,36 @@ struct _virStorageBackendQemuImgInfo { const char *inputPath; const char *inputFormatStr; int inputFormat; + + char *secretAlias; + const char *secretPath; }; + static int -virStorageBackendCreateQemuImgOpts(char **opts, +virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc, + char **opts, struct _virStorageBackendQemuImgInfo info) { virBuffer buf = VIR_BUFFER_INITIALIZER; - if (info.backingPath) - virBufferAsprintf(&buf, "backing_fmt=%s,", - virStorageFileFormatTypeToString(info.backingFormat)); - if (info.encryption) - virBufferAddLit(&buf, "encryption=on,"); - if (info.preallocate) - virBufferAddLit(&buf, "preallocation=metadata,"); + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (!enc) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("missing luks encryption information")); + goto error; + } + virQEMUBuildLuksOpts(&buf, enc, info.secretAlias); + } else { + if (info.backingPath) + virBufferAsprintf(&buf, "backing_fmt=%s,", + virStorageFileFormatTypeToString(info.backingFormat)); + if (info.encryption) + virBufferAddLit(&buf, "encryption=on,"); + if (info.preallocate) + virBufferAddLit(&buf, "preallocation=metadata,"); + } + if (info.nocow) virBufferAddLit(&buf, "nocow=on,"); @@ -1025,6 +1048,22 @@ virStorageBackendCreateQemuImgCheckEncryption(int format, if (virStorageGenerateQcowEncryption(conn, vol) < 0) return -1; } + } else if (format == VIR_STORAGE_FILE_LUKS) { + if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, + _("unsupported volume encryption format %d"), + vol->target.encryption->format); + return -1; + } + if (enc->nsecrets > 1) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("too many secrets for luks encryption")); + return -1; + } + if (enc->nsecrets == 0) { + virReportError(VIR_ERR_XML_ERROR, "%s", + _("no secret provided for luks encryption")); + } } else { virReportError(VIR_ERR_CONFIG_UNSUPPORTED, _("volume encryption unsupported with format %s"), type); @@ -1069,6 +1108,12 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, int accessRetCode = -1; char *absolutePath = NULL; + if (info->format == VIR_STORAGE_FILE_LUKS) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot set backing store for luks volume")); + return -1; + } + info->backingFormat = vol->target.backingStore->format; info->backingPath = vol->target.backingStore->path; @@ -1122,6 +1167,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool, static int virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, int imgformat, + virStorageEncryptionInfoDefPtr enc, struct _virStorageBackendQemuImgInfo info) { char *opts = NULL; @@ -1130,7 +1176,7 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT) info.compat = "0.10"; - if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0) + if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0) return -1; if (opts) virCommandAddArgList(cmd, "-o", opts, NULL); @@ -1140,6 +1186,43 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd, } +/* Add a secret object to the command line: + * --object secret,id=$secretAlias,file=$secretPath + * + * NB: format=raw is assumed + */ +static int +virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd, + virStorageVolDefPtr vol, + struct _virStorageBackendQemuImgInfo *info) +{ + char *str = NULL; + virJSONValuePtr props = NULL; + char *commandStr = NULL; + + if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0) { + VIR_FREE(str); + return -1; + } + VIR_FREE(str); + + if (virJSONValueObjectCreate(&props, "s:file", info->secretPath, NULL) < 0) + return -1; + + if (!(commandStr = virQEMUBuildObjectCommandlineFromJSON("secret", + info->secretAlias, + props))) { + virJSONValueFree(props); + return -1; + } + virJSONValueFree(props); + + virCommandAddArgList(cmd, "--object", commandStr, NULL); + + return 0; +} + + /* Create a qemu-img virCommand from the supplied binary path, * volume definitions and imgformat */ @@ -1150,7 +1233,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat) + int imgformat, + const char *secretPath) { virCommandPtr cmd = NULL; const char *type; @@ -1162,7 +1246,10 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, .compat = vol->target.compat, .features = vol->target.features, .nocow = vol->target.nocow, + .secretPath = secretPath, + .secretAlias = NULL, }; + virStorageEncryptionInfoDefPtr enc = NULL; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL); @@ -1192,6 +1279,18 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, _("format features only available with qcow2")); return NULL; } + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (inputvol) { + virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s", + _("cannot use inputvol with luks volume")); + return NULL; + } + if (!info.encryption) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + } if (inputvol && virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0) @@ -1226,10 +1325,22 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, if (info.backingPath) virCommandAddArgList(cmd, "-b", info.backingPath, NULL); - if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, info) < 0) { + if (info.format == VIR_STORAGE_FILE_LUKS) { + if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) { + VIR_FREE(info.secretAlias); + virCommandFree(cmd); + return NULL; + } + enc = &vol->target.encryption->encinfo; + } + + if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, + enc, info) < 0) { + VIR_FREE(info.secretAlias); virCommandFree(cmd); return NULL; } + VIR_FREE(info.secretAlias); if (info.inputPath) virCommandAddArg(cmd, info.inputPath); @@ -1240,6 +1351,77 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, return cmd; } + +static char * +virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn, + virStoragePoolObjPtr pool, + virStorageVolDefPtr vol) +{ + virStorageEncryptionPtr enc = vol->target.encryption; + char *secretPath = NULL; + int fd = -1; + uint8_t *secret = NULL; + size_t secretlen = 0; + + if (!enc) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("missing encryption description")); + return NULL; + } + + if (!conn || !conn->secretDriver || + !conn->secretDriver->secretLookupByUUID || + !conn->secretDriver->secretLookupByUsage || + !conn->secretDriver->secretGetValue) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("unable to look up encryption secret")); + return NULL; + } + + if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol))) + goto cleanup; + + if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) { + virReportSystemError(errno, "%s", + _("failed to open luks secret file for write")); + goto error; + } + + if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, + &secret, &secretlen) < 0) + goto error; + + if (safewrite(fd, secret, secretlen) < 0) { + virReportSystemError(errno, "%s", + _("failed to write luks secret file")); + goto error; + } + VIR_FORCE_CLOSE(fd); + + if ((vol->target.perms->uid != (uid_t) -1) && + (vol->target.perms->gid != (gid_t) -1)) { + if (chown(secretPath, vol->target.perms->uid, + vol->target.perms->gid) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("failed to chown luks secret file")); + goto error; + } + } + + cleanup: + VIR_DISPOSE_N(secret, secretlen); + VIR_FORCE_CLOSE(fd); + + return secretPath; + + error: + unlink(secretPath); + VIR_FREE(secretPath); + goto cleanup; +} + + int virStorageBackendCreateQemuImg(virConnectPtr conn, virStoragePoolObjPtr pool, @@ -1251,6 +1433,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, char *create_tool; int imgformat; virCommandPtr cmd; + char *secretPath = NULL; virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1); @@ -1266,8 +1449,15 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, if (imgformat < 0) goto cleanup; + if (vol->target.format == VIR_STORAGE_FILE_LUKS) { + if (!(secretPath = + virStorageBackendCreateQemuImgSecretPath(conn, pool, vol))) + goto cleanup; + } + cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol, - flags, create_tool, imgformat); + flags, create_tool, + imgformat, secretPath); if (!cmd) goto cleanup; @@ -1275,6 +1465,10 @@ virStorageBackendCreateQemuImg(virConnectPtr conn, virCommandFree(cmd); cleanup: + if (secretPath) { + unlink(secretPath); + VIR_FREE(secretPath); + } VIR_FREE(create_tool); return ret; } diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h index 5bc622c..28e1a65 100644 --- a/src/storage/storage_backend.h +++ b/src/storage/storage_backend.h @@ -241,7 +241,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn, virStorageVolDefPtr inputvol, unsigned int flags, const char *create_tool, - int imgformat); + int imgformat, + const char *secretPath); /* ------- virStorageFile backends ------------ */ typedef struct _virStorageFileBackend virStorageFileBackend; diff --git a/src/util/virqemu.c b/src/util/virqemu.c index 895168e..dd7a59f 100644 --- a/src/util/virqemu.c +++ b/src/util/virqemu.c @@ -140,3 +140,26 @@ virQEMUBuildObjectCommandlineFromJSON(const char *type, virBufferFreeAndReset(&buf); return ret; } + + +void +virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) +{ + virBufferAsprintf(buf, "key-secret=%s,", alias); + + /* If there's any cipher, then add that to the command line */ + if (enc->cipher_name) { + virBufferEscapeString(buf, "cipher-alg=%s-", enc->cipher_name); + virBufferAsprintf(buf, "%u,", enc->cipher_size); + if (enc->cipher_mode) + virBufferEscapeString(buf, "cipher-mode=%s,", enc->cipher_mode); + if (enc->cipher_hash) + virBufferEscapeString(buf, "hash-alg=%s,", enc->cipher_hash); + if (enc->ivgen_name) + virBufferEscapeString(buf, "ivgen-alg=%s,", enc->ivgen_name); + if (enc->ivgen_hash) + virBufferEscapeString(buf, "ivgen-hash-alg=%s,", enc->ivgen_hash); + } +} diff --git a/src/util/virqemu.h b/src/util/virqemu.h index af4ec1d..46e7ae2 100644 --- a/src/util/virqemu.h +++ b/src/util/virqemu.h @@ -26,9 +26,15 @@ # include "internal.h" # include "virjson.h" +# include "virstorageencryption.h" char *virQEMUBuildObjectCommandlineFromJSON(const char *type, const char *alias, virJSONValuePtr props); +void virQEMUBuildLuksOpts(virBufferPtr buf, + virStorageEncryptionInfoDefPtr enc, + const char *alias) + ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3); + #endif /* __VIR_QEMU_H_ */ diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c index ccfe9ab..e300821 100644 --- a/tests/storagevolxml2argvtest.c +++ b/tests/storagevolxml2argvtest.c @@ -83,7 +83,8 @@ testCompareXMLToArgvFiles(bool shouldFail, cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, &poolobj, vol, inputvol, flags, - create_tool, imgformat); + create_tool, imgformat, + NULL); if (!cmd) { if (shouldFail) { virResetLastError(); -- 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> --- This *replaces* the v3 of the series. The differences are entirely from code review comments. Differences are: 1. Initialize orig_err to NULL since there are multiple failure paths which will need to possible use it. 2. Remove the extraneous comment regarding 3 step process 3. Use exit_monitor instead of failaddobjsecret as label 4. Only clear secobjProps once successfully exit monitor 5. Check for secobjProps in failadddrive, set orig_err if not already set. src/qemu/qemu_hotplug.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index f695903..0638978 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -303,13 +303,16 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, { int ret = -1; qemuDomainObjPrivatePtr priv = vm->privateData; - virErrorPtr orig_err; + virErrorPtr orig_err = NULL; char *devstr = NULL; char *drivestr = NULL; char *drivealias = NULL; 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,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error; - /* Attach the device - 2 step process */ qemuDomainObjEnterMonitor(driver, vm); + if (secobjProps && qemuMonitorAddObject(priv->mon, "secret", + secinfo->s.aes.alias, + secobjProps) < 0) + goto exit_monitor; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -368,12 +382,18 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto failexitmonitor; } + /* qemuMonitorAddObject consumes the props, but we need to wait + * for successful exit from monitor to clear; otherwise, error + * paths wouldn't clean up properly */ + secobjProps = NULL; + virDomainAuditDisk(vm, NULL, disk->src, "attach", true); virDomainDiskInsertPreAlloced(vm->def, disk); ret = 0; cleanup: + virJSONValueFree(secobjProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -387,14 +407,23 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, VIR_WARN("Unable to remove drive %s (%s) after failed " "qemuMonitorAddDevice", drivealias, drivestr); } + + failadddrive: + if (secobjProps) { + if (!orig_err) + orig_err = virSaveLastError(); + ignore_value(qemuMonitorDelObject(priv->mon, secinfo->s.aes.alias)); + } + if (orig_err) { virSetError(orig_err); virFreeError(orig_err); } - failadddrive: + exit_monitor: if (qemuDomainObjExitMonitor(driver, vm) < 0) releaseaddr = false; + secobjProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -2791,6 +2820,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, const char *src = virDomainDiskGetSource(disk); qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; + char *objAlias = NULL; VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2801,7 +2831,24 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, QEMU_DRIVE_HOST_PREFIX, disk->info.alias) < 0) return -1; + /* Let's look for some markers for a secret object and create an alias + * object to be used to attempt to delete the object that was created */ + if (virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) && + qemuDomainSecretDiskCapable(disk->src)) { + + if (!(objAlias = qemuDomainGetSecretAESAlias(disk->info.alias))) { + VIR_FREE(drivestr); + return -1; + } + } + qemuDomainObjEnterMonitor(driver, vm); + + /* If it fails, then so be it - it was a best shot */ + if (objAlias) + ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); + VIR_FREE(objAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) -- 2.5.5

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1301021 Generate the luks command line using the AES secret key to encrypt the luks secret. A luks secret object will be in addition to a an AES secret. For hotplug, check if the encinfo exists and if so, add the AES secret for the passphrase for the secret object used to decrypt the device. Add tests for sample output Signed-off-by: John Ferlan <jferlan@redhat.com> --- This *replaces* the v3 of the series, the differences are mostly from code review, but with one addition change to remove an unnecessary change: 1. Delete unnecessary comment 2. Use exit_monitor label instead of failaddencsecret 3. Followed secobjProps w/r/t clearing encProps (including error path) 4. Followed similar processing in qemuDomainAttachSCSIDisk 5. Remove the encinfo cleanup in qemuDomainDetachDiskDevice (missed that during my merge/changes). src/qemu/qemu_command.c | 9 +++ src/qemu/qemu_domain.c | 25 +++++++- src/qemu/qemu_hotplug.c | 74 +++++++++++++++++++++- .../qemuxml2argv-luks-disk-cipher.args | 36 +++++++++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 +++++++++++ tests/qemuxml2argvtest.c | 11 +++- 6 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 71e9e63..038a60c 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -1098,6 +1098,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, int actualType = virStorageSourceGetActualType(disk->src); qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; bool emitDeviceSyntax = qemuDiskBusNeedsDeviceArg(disk->bus); if (idx < 0) { @@ -1237,6 +1238,10 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk, secinfo->s.aes.alias); } + if (encinfo) + virQEMUBuildLuksOpts(&opt, &disk->src->encryption->encinfo, + encinfo->s.aes.alias); + if (disk->src->format > 0 && disk->src->type != VIR_STORAGE_TYPE_DIR) virBufferAsprintf(&opt, "format=%s,", @@ -1939,6 +1944,7 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, virDomainDiskDefPtr disk = def->disks[i]; qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo; + qemuDomainSecretInfoPtr encinfo = diskPriv->encinfo; /* PowerPC pseries based VMs do not support floppy device */ if (disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY && @@ -1967,6 +1973,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd, if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0) return -1; + if (qemuBuildDiskSecinfoCommandLine(cmd, encinfo) < 0) + return -1; + virCommandAddArg(cmd, "-drive"); optstr = qemuBuildDriveStr(disk, diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c index d51e82b..5405365 100644 --- a/src/qemu/qemu_domain.c +++ b/src/qemu/qemu_domain.c @@ -945,7 +945,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, isLuks) < 0) @@ -1005,11 +1006,14 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, virDomainDiskDefPtr disk) { virStorageSourcePtr src = disk->src; + qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); qemuDomainSecretInfoPtr secinfo = NULL; - if (conn && qemuDomainSecretDiskCapable(src)) { + if (!conn) + return 0; + + if (qemuDomainSecretDiskCapable(src)) { virSecretUsageType secretUsageType = VIR_SECRET_USAGE_TYPE_ISCSI; - qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); if (VIR_ALLOC(secinfo) < 0) return -1; @@ -1025,6 +1029,21 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn, diskPriv->secinfo = secinfo; } + if (!virStorageSourceIsEmpty(src) && src->encryption && + src->format == VIR_STORAGE_FILE_LUKS) { + + if (VIR_ALLOC(secinfo) < 0) + return -1; + + if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias, + VIR_SECRET_USAGE_TYPE_PASSPHRASE, NULL, + &src->encryption->secrets[0]->seclookupdef, + true) < 0) + goto error; + + diskPriv->encinfo = secinfo; + } + return 0; error: diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c index da3e32a..ffbd8d9 100644 --- a/src/qemu/qemu_hotplug.c +++ b/src/qemu/qemu_hotplug.c @@ -311,8 +311,10 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); const char *src = virDomainDiskGetSource(disk); virJSONValuePtr secobjProps = NULL; + virJSONValuePtr encProps = NULL; qemuDomainDiskPrivatePtr diskPriv; qemuDomainSecretInfoPtr secinfo; + qemuDomainSecretInfoPtr encinfo; if (!disk->info.type) { if (qemuDomainMachineIsS390CCW(vm->def) && @@ -352,6 +354,10 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, goto error; } + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(drivestr = qemuBuildDriveStr(disk, false, priv->qemuCaps))) goto error; @@ -371,6 +377,11 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, secobjProps) < 0) goto exit_monitor; + if (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto failaddencsecret; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -386,6 +397,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, * for successful exit from monitor to clear; otherwise, error * paths wouldn't clean up properly */ secobjProps = NULL; + encProps = NULL; virDomainAuditDisk(vm, NULL, disk->src, "attach", true); @@ -394,6 +406,7 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, cleanup: virJSONValueFree(secobjProps); + virJSONValueFree(encProps); qemuDomainSecretDiskDestroy(disk); VIR_FREE(devstr); VIR_FREE(drivestr); @@ -409,6 +422,13 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, } failadddrive: + if (encProps) { + if (!orig_err) + orig_err = virSaveLastError(); + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias)); + } + + failaddencsecret: if (secobjProps) { if (!orig_err) orig_err = virSaveLastError(); @@ -423,7 +443,9 @@ qemuDomainAttachVirtioDiskDevice(virConnectPtr conn, exit_monitor: if (qemuDomainObjExitMonitor(driver, vm) < 0) releaseaddr = false; - secobjProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ + /* qemuMonitorAddObject consumes props on failure too */ + secobjProps = NULL; + encProps = NULL; failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -574,10 +596,14 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, virDomainDiskDefPtr disk) { qemuDomainObjPrivatePtr priv = vm->privateData; + virErrorPtr orig_err = NULL; char *drivestr = NULL; char *devstr = NULL; int ret = -1; virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver); + virJSONValuePtr encProps = NULL; + qemuDomainDiskPrivatePtr diskPriv; + qemuDomainSecretInfoPtr encinfo; if (qemuDomainPrepareDisk(driver, vm, disk, NULL, false) < 0) goto cleanup; @@ -596,6 +622,11 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (qemuDomainSecretDiskPrepare(conn, priv, disk) < 0) goto error; + diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk); + encinfo = diskPriv->encinfo; + if (encinfo && qemuBuildSecretInfoProps(encinfo, &encProps) < 0) + goto error; + if (!(devstr = qemuBuildDriveDevStr(vm->def, disk, 0, priv->qemuCaps))) goto error; @@ -605,9 +636,13 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (VIR_REALLOC_N(vm->def->disks, vm->def->ndisks+1) < 0) goto error; - /* Attach the device - 2 step process */ qemuDomainObjEnterMonitor(driver, vm); + if (encProps && qemuMonitorAddObject(priv->mon, "secret", + encinfo->s.aes.alias, + encProps) < 0) + goto exit_monitor; + if (qemuMonitorAddDrive(priv->mon, drivestr) < 0) goto failadddrive; @@ -617,6 +652,11 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, if (qemuDomainObjExitMonitor(driver, vm) < 0) goto failexitmonitor; + /* qemuMonitorAddObject consumes the props, but we need to wait + * for successful exit from monitor to clear; otherwise, error + * paths wouldn't clean up properly */ + encProps = NULL; + virDomainAuditDisk(vm, NULL, disk->src, "attach", true); virDomainDiskInsertPreAlloced(vm->def, disk); @@ -634,7 +674,17 @@ qemuDomainAttachSCSIDisk(virConnectPtr conn, VIR_WARN("qemuMonitorAddDevice failed on %s (%s)", drivestr, devstr); failadddrive: + orig_err = virSaveLastError(); + if (encProps) + ignore_value(qemuMonitorDelObject(priv->mon, encinfo->s.aes.alias)); + if (orig_err) { + virSetError(orig_err); + virFreeError(orig_err); + } + + exit_monitor: ignore_value(qemuDomainObjExitMonitor(driver, vm)); + encProps = NULL; /* qemuMonitorAddObject consumes props on failure too */ failexitmonitor: virDomainAuditDisk(vm, NULL, disk->src, "attach", false); @@ -2821,6 +2871,7 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, qemuDomainObjPrivatePtr priv = vm->privateData; char *drivestr; char *objAlias = NULL; + char *encAlias = NULL; VIR_DEBUG("Removing disk %s from domain %p %s", disk->info.alias, vm, vm->def->name); @@ -2843,6 +2894,20 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, } } + /* Similarly, if this is possible a device using LUKS encryption, we + * can remove the luks object password too + */ + if (!virStorageSourceIsEmpty(disk->src) && disk->src->encryption && + disk->src->format == VIR_STORAGE_FILE_LUKS) { + + if (!(encAlias = + qemuDomainGetSecretAESAlias(disk->info.alias, true))) { + VIR_FREE(objAlias); + VIR_FREE(drivestr); + return -1; + } + } + qemuDomainObjEnterMonitor(driver, vm); /* If it fails, then so be it - it was a best shot */ @@ -2850,6 +2915,11 @@ qemuDomainRemoveDiskDevice(virQEMUDriverPtr driver, ignore_value(qemuMonitorDelObject(priv->mon, objAlias)); VIR_FREE(objAlias); + /* If it fails, then so be it - it was a best shot */ + if (encAlias) + ignore_value(qemuMonitorDelObject(priv->mon, encAlias)); + VIR_FREE(encAlias); + qemuMonitorDriveDel(priv->mon, drivestr); VIR_FREE(drivestr); if (qemuDomainObjExitMonitor(driver, vm) < 0) diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args new file mode 100644 index 0000000..efb5cb0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name encryptdisk \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-encryptdisk/master-key.aes \ +-M pc-i440fx-2.1 \ +-m 1024 \ +-smp 1 \ +-uuid 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 \ +-nographic \ +-nodefaults \ +-monitor unix:/tmp/lib/domain--1-encryptdisk/monitor.sock,server,nowait \ +-no-acpi \ +-boot c \ +-usb \ +-object secret,id=virtio-disk0-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk,\ +key-secret=virtio-disk0-luks-secret0,format=luks,if=none,id=drive-virtio-disk0 \ +-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\ +id=virtio-disk0 \ +-object secret,id=virtio-disk1-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk2,\ +key-secret=virtio-disk1-luks-secret0,format=luks,if=none,id=drive-virtio-disk1 \ +-device virtio-blk-pci,bus=pci.0,addr=0x5,drive=drive-virtio-disk1,\ +id=virtio-disk1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args new file mode 100644 index 0000000..efb5cb0 --- /dev/null +++ b/tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args @@ -0,0 +1,36 @@ +LC_ALL=C \ +PATH=/bin \ +HOME=/home/test \ +USER=test \ +LOGNAME=test \ +QEMU_AUDIO_DRV=none \ +/usr/bin/qemu \ +-name encryptdisk \ +-S \ +-object secret,id=masterKey0,format=raw,\ +file=/tmp/lib/domain--1-encryptdisk/master-key.aes \ +-M pc-i440fx-2.1 \ +-m 1024 \ +-smp 1 \ +-uuid 496898a6-e6ff-f7c8-5dc2-3cf410945ee9 \ +-nographic \ +-nodefaults \ +-monitor unix:/tmp/lib/domain--1-encryptdisk/monitor.sock,server,nowait \ +-no-acpi \ +-boot c \ +-usb \ +-object secret,id=virtio-disk0-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk,\ +key-secret=virtio-disk0-luks-secret0,format=luks,if=none,id=drive-virtio-disk0 \ +-device virtio-blk-pci,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,\ +id=virtio-disk0 \ +-object secret,id=virtio-disk1-luks-secret0,\ +data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\ +keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \ +-drive file=/storage/guest_disks/encryptdisk2,\ +key-secret=virtio-disk1-luks-secret0,format=luks,if=none,id=drive-virtio-disk1 \ +-device virtio-blk-pci,bus=pci.0,addr=0x5,drive=drive-virtio-disk1,\ +id=virtio-disk1 \ +-device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3 diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c index ca52ab0..aba346d 100644 --- a/tests/qemuxml2argvtest.c +++ b/tests/qemuxml2argvtest.c @@ -62,10 +62,17 @@ fakeSecretLookupByUsage(virConnectPtr conn, return virGetSecret(conn, uuid, usageType, usageID); } +static virSecretPtr +fakeSecretLookupByUUID(virConnectPtr conn, + const unsigned char *uuid) +{ + return virGetSecret(conn, uuid, 0, ""); +} + static virSecretDriver fakeSecretDriver = { .connectNumOfSecrets = NULL, .connectListSecrets = NULL, - .secretLookupByUUID = NULL, + .secretLookupByUUID = fakeSecretLookupByUUID, .secretLookupByUsage = fakeSecretLookupByUsage, .secretDefineXML = NULL, .secretGetXMLDesc = NULL, @@ -1342,6 +1349,8 @@ mymain(void) DO_TEST("encrypted-disk", NONE); DO_TEST("encrypted-disk-usage", NONE); + DO_TEST("luks-disks", QEMU_CAPS_OBJECT_SECRET); + DO_TEST("luks-disk-cipher", QEMU_CAPS_OBJECT_SECRET); DO_TEST("memtune", NONE); DO_TEST("memtune-unlimited", NONE); -- 2.5.5

On 06/24/2016 04:53 PM, John Ferlan wrote: [...]
John Ferlan (10): conf: No need to check for usage fields during Format conf: Add new secret type "passphrase" util: Add 'usage' for encryption encryption: Add luks parsing for storageencryption encryption: Add <cipher> and <ivgen> to encryption storage: Add support to create a luks volume qemu: Introduce helper qemuDomainSecretDiskCapable qemu: Add secinfo for hotplug virtio disk qemu: Alter the qemuDomainGetSecretAESAlias to add new arg qemu: Add luks support for domain disk
docs/aclpolkit.html.in | 4 + docs/formatsecret.html.in | 62 ++++- docs/formatstorageencryption.html.in | 115 ++++++++- docs/schemas/secret.rng | 10 + docs/schemas/storagecommon.rng | 57 ++++- include/libvirt/libvirt-secret.h | 3 +- src/access/viraccessdriverpolkit.c | 13 ++ src/conf/domain_conf.c | 11 + src/conf/secret_conf.c | 36 ++- src/conf/secret_conf.h | 1 + src/conf/virsecretobj.c | 5 + src/libvirt_private.syms | 1 + src/qemu/qemu_alias.c | 10 +- src/qemu/qemu_alias.h | 3 +- src/qemu/qemu_command.c | 9 + src/qemu/qemu_domain.c | 58 +++-- src/qemu/qemu_domain.h | 3 + src/qemu/qemu_hotplug.c | 123 +++++++++- src/qemu/qemu_process.c | 19 +- src/storage/storage_backend.c | 260 +++++++++++++++++++-- src/storage/storage_backend.h | 3 +- src/storage/storage_backend_fs.c | 10 +- src/storage/storage_backend_gluster.c | 2 + src/util/virqemu.c | 23 ++ src/util/virqemu.h | 6 + src/util/virstorageencryption.c | 166 +++++++++++-- src/util/virstorageencryption.h | 18 +- .../qemuxml2argv-encrypted-disk-usage.args | 24 ++ .../qemuxml2argv-encrypted-disk-usage.xml | 36 +++ .../qemuxml2argv-luks-disk-cipher.args | 36 +++ .../qemuxml2argv-luks-disk-cipher.xml | 45 ++++ .../qemuxml2argvdata/qemuxml2argv-luks-disks.args | 36 +++ tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml | 45 ++++ tests/qemuxml2argvtest.c | 12 +- .../qemuxml2xmlout-encrypted-disk-usage.xml | 1 + .../qemuxml2xmlout-luks-disk-cipher.xml | 1 + .../qemuxml2xmlout-luks-disks.xml | 1 + tests/qemuxml2xmltest.c | 3 + tests/secretxml2xmlin/usage-passphrase.xml | 7 + tests/secretxml2xmltest.c | 1 + tests/storagevolxml2argvtest.c | 3 +- tests/storagevolxml2xmlin/vol-luks-cipher.xml | 23 ++ tests/storagevolxml2xmlin/vol-luks.xml | 21 ++ tests/storagevolxml2xmlout/vol-luks-cipher.xml | 23 ++ tests/storagevolxml2xmlout/vol-luks.xml | 21 ++ tests/storagevolxml2xmltest.c | 2 + 46 files changed, 1267 insertions(+), 105 deletions(-) create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-encrypted-disk-usage.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.args create mode 100644 tests/qemuxml2argvdata/qemuxml2argv-luks-disks.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-encrypted-disk-usage.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disk-cipher.xml create mode 120000 tests/qemuxml2xmloutdata/qemuxml2xmlout-luks-disks.xml create mode 100644 tests/secretxml2xmlin/usage-passphrase.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlin/vol-luks.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks-cipher.xml create mode 100644 tests/storagevolxml2xmlout/vol-luks.xml
Thanks for the reviews - I pushed 1-5 and 7, with 1 minor adjustment to use 2.1.0 for version instead of 2.0.0. Held off on 6, 8, 9, and 10 since I wanted to be sure the adjustments for 6, 8, & 10 were what was expected. John
participants (3)
-
Daniel P. Berrange
-
John Ferlan
-
Ján Tomko