encryption: Add <cipher> and <ivgen> to encryption

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>
This commit is contained in:
John Ferlan 2016-06-01 19:21:26 -04:00
parent 9bbf0d7e64
commit 2552fec248
11 changed files with 376 additions and 6 deletions

View File

@ -68,6 +68,60 @@
be used as the passphrase to decrypt the volume.
<span class="since">Since 2.1.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 @@
&lt;/encryption&gt;</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>
&lt;encryption format='luks'&gt;
@ -91,5 +148,25 @@
&lt;/encryption&gt;
</pre>
<p>
Here is an example, specifying use of the <code>luks</code> format for
a specific cipher algorihm for volume creation:
</p>
<pre>
&lt;volume&gt;
&lt;name&gt;twofish.luks&lt;/name&gt;
&lt;capacity unit='G'&gt;5&lt;/capacity&gt;
&lt;target&gt;
&lt;path&gt;/var/lib/libvirt/images/demo.luks&lt;/path&gt;
&lt;format type='luks'/&gt;
&lt;encryption format='luks'&gt;
&lt;secret type='passphrase' usage='luks_example'/&gt;
&lt;cipher name='twofish' size='256' mode='cbc' hash='sha256'/&gt;
&lt;ivgen name='plain64' hash='sha256'/&gt;
&lt;/encryption&gt;
&lt;/target&gt;
&lt;/volume&gt;
</pre>
</body>
</html>

View File

@ -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>

View File

@ -7802,6 +7802,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;

View File

@ -35,6 +35,7 @@
#include "viruuid.h"
#include "virfile.h"
#include "virsecret.h"
#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_STORAGE
@ -45,6 +46,17 @@ VIR_ENUM_IMPL(virStorageEncryptionFormat,
VIR_STORAGE_ENCRYPTION_FORMAT_LAST,
"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)
{
@ -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,61 @@ 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",
_("cipher info missing 'name' 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 size: '%s'"),
size_str);
goto cleanup;
}
if (!size_str) {
virReportError(VIR_ERR_XML_ERROR, "%s",
_("cipher info missing 'size' attribute"));
goto cleanup;
}
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;
}
info->ivgen_hash = virXMLPropString(info_node, "hash");
return 0;
}
static virStorageEncryptionPtr
virStorageEncryptionParseXML(xmlXPathContextPtr ctxt)
{
@ -196,6 +284,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",
_("ivgen element found, but cipher is missing"));
goto cleanup;
}
if (virStorageEncryptionInfoParseIvgen(tmpnode, &ret->encinfo) < 0)
goto cleanup;
}
}
return ret;
cleanup:
@ -250,6 +360,28 @@ virStorageEncryptionSecretFormat(virBufferPtr buf,
return 0;
}
static void
virStorageEncryptionInfoDefFormat(virBufferPtr buf,
const virStorageEncryptionInfoDef *enc)
{
virBufferEscapeString(buf, "<cipher name='%s'", enc->cipher_name);
virBufferAsprintf(buf, " size='%u'", enc->cipher_size);
if (enc->cipher_mode)
virBufferEscapeString(buf, " mode='%s'", enc->cipher_mode);
if (enc->cipher_hash)
virBufferEscapeString(buf, " hash='%s'", enc->cipher_hash);
virBufferAddLit(buf, "/>\n");
if (enc->ivgen_name) {
virBufferEscapeString(buf, "<ivgen name='%s'", enc->ivgen_name);
if (enc->ivgen_hash)
virBufferEscapeString(buf, " hash='%s'", enc->ivgen_hash);
virBufferAddLit(buf, "/>\n");
}
}
int
virStorageEncryptionFormat(virBufferPtr buf,
virStorageEncryptionPtr enc)
@ -270,6 +402,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");

View File

@ -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)

View File

@ -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>

View File

@ -0,0 +1 @@
../qemuxml2argvdata/qemuxml2argv-luks-disk-cipher.xml

View File

@ -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");

View File

@ -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>

View File

@ -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>

View File

@ -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");