diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
index cb102ec682e0d1e0c7e2f34d42243e4c6e75c8c4..d6240710ab34000da76fdf08a6a7444a9af23eeb 100644
--- a/src/qemu/qemu_alias.c
+++ b/src/qemu/qemu_alias.c
@@ -482,3 +482,26 @@ qemuDomainGetMasterKeyAlias(void)
return alias;
}
+
+
+/* qemuDomainGetSecretAESAlias:
+ *
+ * Generate and return an alias for the encrypted secret
+ *
+ * Returns NULL or a string containing the alias
+ */
+char *
+qemuDomainGetSecretAESAlias(const char *srcalias)
+{
+ char *alias;
+
+ if (!srcalias) {
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
+ _("encrypted secret alias requires valid source alias"));
+ return NULL;
+ }
+
+ ignore_value(virAsprintf(&alias, "%s-secret0", srcalias));
+
+ return alias;
+}
diff --git a/src/qemu/qemu_alias.h b/src/qemu/qemu_alias.h
index 2d7bc9b376c50652d5a304d116ffd58698f5ec8c..e328a9b66420b2aa6891d93f69b05cdf7d70e728 100644
--- a/src/qemu/qemu_alias.h
+++ b/src/qemu/qemu_alias.h
@@ -69,4 +69,6 @@ char *qemuAliasFromDisk(const virDomainDiskDef *disk);
char *qemuDomainGetMasterKeyAlias(void);
+char *qemuDomainGetSecretAESAlias(const char *srcalias);
+
#endif /* __QEMU_ALIAS_H__*/
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index ce034b7c100ae6cf937949c783b5c624e82c3e91..a9fe5e8c93ca71077aa59aacc3e316b7c1c25343 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -616,6 +616,106 @@ qemuNetworkDriveGetPort(int protocol,
}
+/**
+ * qemuBuildSecretInfoProps:
+ * @secinfo: pointer to the secret info object
+ * @type: returns a pointer to a character string for object name
+ * @props: json properties to return
+ *
+ * Build the JSON properties for the secret info type.
+ *
+ * Returns 0 on success with the filled in JSON property; otherwise,
+ * returns -1 on failure error message set.
+ */
+static int
+qemuBuildSecretInfoProps(qemuDomainSecretInfoPtr secinfo,
+ const char **type,
+ virJSONValuePtr *propsret)
+{
+ int ret = -1;
+ char *keyid = NULL;
+
+ *type = "secret";
+
+ if (!(keyid = qemuDomainGetMasterKeyAlias()))
+ return -1;
+
+ if (virJSONValueObjectCreate(propsret,
+ "s:data", secinfo->s.aes.ciphertext,
+ "s:keyid", keyid,
+ "s:iv", secinfo->s.aes.iv,
+ "s:format", "base64", NULL) < 0)
+ goto cleanup;
+
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(keyid);
+
+ return ret;
+}
+
+
+/**
+ * qemuBuildObjectSecretCommandLine:
+ * @cmd: the command to modify
+ * @secinfo: pointer to the secret info object
+ *
+ * If the secinfo is available and associated with an AES secret,
+ * then format the command line for the secret object. This object
+ * will be referenced by the device that needs/uses it, so it needs
+ * to be in place first.
+ *
+ * Returns 0 on success, -1 w/ error message on failure
+ */
+static int
+qemuBuildObjectSecretCommandLine(virCommandPtr cmd,
+ qemuDomainSecretInfoPtr secinfo)
+{
+ int ret = -1;
+ virJSONValuePtr props = NULL;
+ const char *type;
+ char *tmp = NULL;
+
+ if (qemuBuildSecretInfoProps(secinfo, &type, &props) < 0)
+ return -1;
+
+ if (!(tmp = qemuBuildObjectCommandlineFromJSON(type, secinfo->s.aes.alias,
+ props)))
+ goto cleanup;
+
+ virCommandAddArgList(cmd, "-object", tmp, NULL);
+ ret = 0;
+
+ cleanup:
+ virJSONValueFree(props);
+ VIR_FREE(tmp);
+
+ return ret;
+}
+
+
+/* qemuBuildDiskSecinfoCommandLine:
+ * @cmd: Pointer to the command string
+ * @secinfo: Pointer to a possible secinfo
+ *
+ * Add the secret object for the disks that will be using it to perform
+ * their authentication.
+ *
+ * Returns 0 on success, -1 w/ error on some sort of failure.
+ */
+static int
+qemuBuildDiskSecinfoCommandLine(virCommandPtr cmd,
+ qemuDomainSecretInfoPtr secinfo)
+{
+ /* Not necessary for non AES secrets */
+ if (!secinfo || secinfo->type != VIR_DOMAIN_SECRET_INFO_TYPE_AES)
+ return 0;
+
+ return qemuBuildObjectSecretCommandLine(cmd, secinfo);
+}
+
+
/* qemuBuildGeneralSecinfoURI:
* @uri: Pointer to the URI structure to add to
* @secinfo: Pointer to the secret info data (if present)
@@ -697,6 +797,10 @@ qemuBuildRBDSecinfoURI(virBufferPtr buf,
break;
case VIR_DOMAIN_SECRET_INFO_TYPE_AES:
+ virBufferEscape(buf, '\\', ":", ":id=%s:auth_supported=cephx\\;none",
+ secinfo->s.aes.username);
+ break;
+
case VIR_DOMAIN_SECRET_INFO_TYPE_LAST:
return -1;
}
@@ -1106,6 +1210,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
char *source = NULL;
int actualType = virStorageSourceGetActualType(disk->src);
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+ qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
bool emitDeviceSyntax = qemuDiskBusNeedsDeviceArg(disk->bus);
if (idx < 0) {
@@ -1188,7 +1293,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
break;
}
- if (qemuGetDriveSourceString(disk->src, diskPriv->secinfo, &source) < 0)
+ if (qemuGetDriveSourceString(disk->src, secinfo, &source) < 0)
goto error;
if (source &&
@@ -1240,6 +1345,11 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
qemuBufferEscapeComma(&opt, source);
virBufferAddLit(&opt, ",");
+ if (secinfo && secinfo->type == VIR_DOMAIN_SECRET_INFO_TYPE_AES) {
+ virBufferAsprintf(&opt, "password-secret=%s,",
+ secinfo->s.aes.alias);
+ }
+
if (disk->src->format > 0 &&
disk->src->type != VIR_STORAGE_TYPE_DIR)
virBufferAsprintf(&opt, "format=%s,",
@@ -1917,6 +2027,8 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
char *optstr;
unsigned int bootindex = 0;
virDomainDiskDefPtr disk = def->disks[i];
+ qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+ qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
/* PowerPC pseries based VMs do not support floppy device */
if ((disk->device == VIR_DOMAIN_DISK_DEVICE_FLOPPY) &&
@@ -1943,6 +2055,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
break;
}
+ if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0)
+ return -1;
+
virCommandAddArg(cmd, "-drive");
optstr = qemuBuildDriveStr(disk,
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 3b3f008daa1b33f2caf20ed131f31b1f74b6ff06..cb9d5eb8dd15169baa172151aa9687c12b84f5d1 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -835,23 +835,136 @@ qemuDomainSecretPlainSetup(virConnectPtr conn,
}
+/* qemuDomainSecretAESSetup:
+ * @conn: Pointer to connection
+ * @priv: pointer to domain private object
+ * @secinfo: Pointer to secret info
+ * @srcalias: Alias of the disk/hostdev used to generate the secret alias
+ * @protocol: Protocol for secret
+ * @authdef: Pointer to auth data
+ *
+ * Taking a secinfo, fill in the AES specific information using the
+ *
+ * Returns 0 on success, -1 on failure with error message
+ */
+static int
+qemuDomainSecretAESSetup(virConnectPtr conn,
+ qemuDomainObjPrivatePtr priv,
+ qemuDomainSecretInfoPtr secinfo,
+ const char *srcalias,
+ virStorageNetProtocol protocol,
+ virStorageAuthDefPtr authdef)
+{
+ int ret = -1;
+ uint8_t *raw_iv = NULL;
+ size_t ivlen = QEMU_DOMAIN_AES_IV_LEN;
+ uint8_t *secret = NULL;
+ size_t secretlen = 0;
+ uint8_t *ciphertext = NULL;
+ size_t ciphertextlen = 0;
+ int secretType = VIR_SECRET_USAGE_TYPE_NONE;
+
+ secinfo->type = VIR_DOMAIN_SECRET_INFO_TYPE_AES;
+ if (VIR_STRDUP(secinfo->s.aes.username, authdef->username) < 0)
+ return -1;
+
+ switch ((virStorageNetProtocol)protocol) {
+ case VIR_STORAGE_NET_PROTOCOL_RBD:
+ secretType = VIR_SECRET_USAGE_TYPE_CEPH;
+ break;
+
+ case VIR_STORAGE_NET_PROTOCOL_NONE:
+ case VIR_STORAGE_NET_PROTOCOL_NBD:
+ case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
+ case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
+ case VIR_STORAGE_NET_PROTOCOL_ISCSI:
+ case VIR_STORAGE_NET_PROTOCOL_HTTP:
+ case VIR_STORAGE_NET_PROTOCOL_HTTPS:
+ case VIR_STORAGE_NET_PROTOCOL_FTP:
+ case VIR_STORAGE_NET_PROTOCOL_FTPS:
+ case VIR_STORAGE_NET_PROTOCOL_TFTP:
+ case VIR_STORAGE_NET_PROTOCOL_LAST:
+ virReportError(VIR_ERR_INTERNAL_ERROR,
+ _("protocol '%s' cannot be used for encrypted secrets"),
+ virStorageNetProtocolTypeToString(protocol));
+ return -1;
+ }
+
+ if (!(secinfo->s.aes.alias = qemuDomainGetSecretAESAlias(srcalias)))
+ return -1;
+
+ /* Create a random initialization vector */
+ if (!(raw_iv = virCryptoGenerateRandom(ivlen)))
+ return -1;
+
+ /* Encode the IV and save that since qemu will need it */
+ if (!(secinfo->s.aes.iv = virStringEncodeBase64(raw_iv, ivlen)))
+ goto cleanup;
+
+ /* Grab the unencoded secret */
+ if (virSecretGetSecretString(conn, authdef, secretType,
+ &secret, &secretlen) < 0)
+ goto cleanup;
+
+ if (virCryptoEncryptData(VIR_CRYPTO_CIPHER_AES256CBC,
+ priv->masterKey, QEMU_DOMAIN_MASTER_KEY_LEN,
+ raw_iv, ivlen, secret, secretlen,
+ &ciphertext, &ciphertextlen) < 0)
+ goto cleanup;
+
+ /* Clear out the secret */
+ memset(secret, 0, secretlen);
+
+ /* Now encode the ciphertext and store to be passed to qemu */
+ if (!(secinfo->s.aes.ciphertext = virStringEncodeBase64(ciphertext,
+ ciphertextlen)))
+ goto cleanup;
+
+ ret = 0;
+
+ cleanup:
+ VIR_DISPOSE_N(raw_iv, ivlen);
+ VIR_DISPOSE_N(secret, secretlen);
+ VIR_DISPOSE_N(ciphertext, ciphertextlen);
+
+ return ret;
+}
+
+
/* qemuDomainSecretSetup:
* @conn: Pointer to connection
+ * @priv: pointer to domain private object
* @secinfo: Pointer to secret info
+ * @srcalias: Alias of the disk/hostdev used to generate the secret alias
* @protocol: Protocol for secret
* @authdef: Pointer to auth data
*
- * A shim to call plain setup.
+ * 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
+ * decision point for utilizing the AES secrets for an RBD disk. For now iSCSI
+ * disks and hostdevs will not be able to utilize this mechanism.
*
* Returns 0 on success, -1 on failure
*/
static int
qemuDomainSecretSetup(virConnectPtr conn,
+ qemuDomainObjPrivatePtr priv,
qemuDomainSecretInfoPtr secinfo,
+ const char *srcalias,
virStorageNetProtocol protocol,
virStorageAuthDefPtr authdef)
{
- return qemuDomainSecretPlainSetup(conn, secinfo, protocol, authdef);
+ if (virCryptoHaveCipher(VIR_CRYPTO_CIPHER_AES256CBC) &&
+ virQEMUCapsGet(priv->qemuCaps, QEMU_CAPS_OBJECT_SECRET) &&
+ protocol == VIR_STORAGE_NET_PROTOCOL_RBD) {
+ if (qemuDomainSecretAESSetup(conn, priv, secinfo,
+ srcalias, protocol, authdef) < 0)
+ return -1;
+ } else {
+ if (qemuDomainSecretPlainSetup(conn, secinfo, protocol, authdef) < 0)
+ return -1;
+ }
+ return 0;
}
@@ -883,7 +996,7 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk)
*/
int
qemuDomainSecretDiskPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainDiskDefPtr disk)
{
virStorageSourcePtr src = disk->src;
@@ -900,8 +1013,8 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretSetup(conn, secinfo, src->protocol,
- src->auth) < 0)
+ if (qemuDomainSecretSetup(conn, priv, secinfo, disk->info.alias,
+ src->protocol, src->auth) < 0)
goto error;
diskPriv->secinfo = secinfo;
@@ -944,7 +1057,7 @@ qemuDomainSecretHostdevDestroy(virDomainHostdevDefPtr hostdev)
*/
int
qemuDomainSecretHostdevPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainHostdevDefPtr hostdev)
{
virDomainHostdevSubsysPtr subsys = &hostdev->source.subsys;
@@ -965,7 +1078,7 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretSetup(conn, secinfo,
+ if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias,
VIR_STORAGE_NET_PROTOCOL_ISCSI,
iscsisrc->auth) < 0)
goto error;
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.args b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.args
new file mode 100644
index 0000000000000000000000000000000000000000..7100d2d6d4343280af141364b8f9004cb28640d7
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.args
@@ -0,0 +1,31 @@
+LC_ALL=C \
+PATH=/bin \
+HOME=/home/test \
+USER=test \
+LOGNAME=test \
+QEMU_AUDIO_DRV=none \
+/usr/bin/qemu \
+-name QEMUGuest1 \
+-S \
+-object secret,id=masterKey0,format=raw,\
+file=/tmp/lib/domain--1-QEMUGuest1/master-key.aes \
+-M pc \
+-m 214 \
+-smp 1 \
+-uuid c7a5fdbd-edaf-9455-926a-d65c16db1809 \
+-nographic \
+-nodefaults \
+-monitor unix:/tmp/lib/domain--1-QEMUGuest1/monitor.sock,server,nowait \
+-no-acpi \
+-boot c \
+-usb \
+-drive file=/dev/HostVG/QEMUGuest1,format=raw,if=none,id=drive-ide0-0-0 \
+-device ide-drive,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0 \
+-object secret,id=virtio-disk0-secret0,\
+data=9eao5F8qtkGt+seB1HYivWIxbtwUu6MQtg1zpj/oDtUsPr1q8wBYM91uEHCn6j/1,\
+keyid=masterKey0,iv=AAECAwQFBgcICQoLDA0ODw==,format=base64 \
+-drive 'file=rbd:pool/image:id=myname:auth_supported=cephx\;none:\
+mon_host=mon1.example.org\:6321\;mon2.example.org\:6322\;mon3.example.org\:6322,\
+password-secret=virtio-disk0-secret0,format=raw,if=none,id=drive-virtio-disk0' \
+-device virtio-blk-pci,bus=pci.0,addr=0x3,drive=drive-virtio-disk0,\
+id=virtio-disk0
diff --git a/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.xml b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ac2e942090c64769b328dc4159bd645bb21838a6
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.xml
@@ -0,0 +1,42 @@
+
+ QEMUGuest1
+ c7a5fdbd-edaf-9455-926a-d65c16db1809
+ 219136
+ 219136
+ 1
+
+ hvm
+
+
+
+ destroy
+ restart
+ destroy
+
+ /usr/bin/qemu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/qemuxml2argvmock.c b/tests/qemuxml2argvmock.c
index 1616eeddee8183594357917c962bfa108d4dcbc5..c6a1f98814273dcbdbd94d73aae0e67ad565d962 100644
--- a/tests/qemuxml2argvmock.c
+++ b/tests/qemuxml2argvmock.c
@@ -21,11 +21,14 @@
#include
#include "internal.h"
+#include "viralloc.h"
#include "vircommand.h"
+#include "vircrypto.h"
#include "virmock.h"
#include "virnetdev.h"
#include "virnetdevtap.h"
#include "virnuma.h"
+#include "virrandom.h"
#include "virscsi.h"
#include "virstring.h"
#include "virtpm.h"
@@ -145,3 +148,16 @@ virCommandPassFD(virCommandPtr cmd ATTRIBUTE_UNUSED,
{
/* nada */
}
+
+uint8_t *
+virCryptoGenerateRandom(size_t nbytes)
+{
+ uint8_t *buf;
+
+ if (VIR_ALLOC_N(buf, nbytes) < 0)
+ return NULL;
+
+ ignore_value(virRandomBytes(buf, nbytes));
+
+ return buf;
+}
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 540b3de81376da9ebd5684b92772c969f686364b..58b58e70832a8d070a213f85b7fc50d6a8fa401b 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -798,6 +798,8 @@ mymain(void)
DO_TEST("disk-drive-network-rbd", NONE);
DO_TEST("disk-drive-network-sheepdog", NONE);
DO_TEST("disk-drive-network-rbd-auth", NONE);
+ DO_TEST("disk-drive-network-rbd-auth-AES",
+ QEMU_CAPS_OBJECT_SECRET);
DO_TEST("disk-drive-network-rbd-ipv6", NONE);
DO_TEST_FAILURE("disk-drive-network-rbd-no-colon", NONE);
DO_TEST("disk-drive-no-boot",
@@ -1983,7 +1985,8 @@ mymain(void)
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
-VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/qemuxml2argvmock.so")
+VIRT_TEST_MAIN_PRELOAD(mymain, abs_builddir "/.libs/qemuxml2argvmock.so",
+ abs_builddir "/.libs/virrandommock.so")
#else