https://bugzilla.redhat.com/show_bug.cgi?id=1182074
If they're available and we need to pass secrets to qemu, then use the
qemu domain secret object in order to pass the secrets for RBD volumes
instead of passing the base64 encoded secret on the command line.
The goal is to make AES secrets the default and have no user interaction
required in order to allow using the AES mechanism. If the mechanism
is not available, then fall back to the current plain mechanism using
a base64 encoded secret.
New API's:
qemu_command.c:
qemuBuildSecretInfoProps: (private)
Generate/return a JSON properties object for the AES secret to
be used by both the command building and eventually the hotplug
code in order to add the secret object. Code was designed so that
in the future perhaps hotplug could use it if it made sense.
qemuBuildSecretAESCommandLine (private)
Generate and add to the command line the -object secret for the
AES secret. This will be required for the subsequent RBD reference
to the object.
qemuBuildDiskSecinfoCommandLine (private)
Handle adding the AES secret object.
Adjustments:
Command Building:
Adjust the qemuBuildRBDSecinfoURI API's in order to generate the
specific command options for an AES secret, such as:
-object secret,id=$alias,keyid=$masterKey,data=$base64encodedencrypted,
format=base64
-drive file=rbd:pool/image:id=myname:auth_supported=cephx\;none:\
mon_host=mon1.example.org\:6321,password-secret=$alias,...
where the 'id=' value is the secret object alias generated by
concatenating the disk alias and "-aesKey0". The 'keyid= $masterKey'
is the master key shared with qemu, and the -drive syntax will
reference that alias as the 'password-secret'. For the -drive
syntax, the 'id=myname' is kept to define the username, while the
'key=$base64 encoded secret' is removed.
While according to the syntax described for qemu commit '60390a21'
or as seen in the email archive:
https://lists.gnu.org/archive/html/qemu-devel/2016-01/msg04083.html
it is possible to pass a plaintext password via a file, the qemu
commit 'ac1d8878' describes the more feature rich 'keyid=' option
based upon the shared masterKey.
qemu_domain.c
Use the qemuDomainSecretSetup in qemuDomainSecretDiskPrepare
and qemuDomainSecretHostdevPrepare to setup the secret rather
than assuming plain secret setup.
Add test for checking/comparing output.
NB: For hotplug, since the hotplug code doesn't add command line
arguments, passing the encoded secret directly to the monitor
will suffice.
Signed-off-by: John Ferlan <jferlan(a)redhat.com>
---
src/qemu/qemu_command.c | 122 ++++++++++++++++++++-
src/qemu/qemu_domain.c | 16 +--
...muxml2argv-disk-drive-network-rbd-auth-AES.args | 31 ++++++
...emuxml2argv-disk-drive-network-rbd-auth-AES.xml | 42 +++++++
tests/qemuxml2argvtest.c | 2 +
5 files changed, 204 insertions(+), 9 deletions(-)
create mode 100644
tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.args
create mode 100644
tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.xml
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index 4a2fa1d..e2e0b3c 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -616,6 +616,110 @@ 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;
+ virJSONValuePtr props = NULL;
+
+ *type = "secret";
+
+ if (!(keyid = qemuDomainGetMasterKeyAlias()))
+ return -1;
+
+ if (virJSONValueObjectCreate(&props,
+ "s:data", secinfo->s.aes.ciphertext,
+ "s:keyid", keyid,
+ "s:iv", secinfo->s.aes.iv,
+ "s:format", "base64", NULL) < 0)
+ goto cleanup;
+
+ *propsret = props;
+ props = NULL;
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(keyid);
+ virJSONValueFree(props);
+
+ return ret;
+}
+
+
+/**
+ * qemuBuildSecretAESCommandLine:
+ * @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
+qemuBuildSecretAESCommandLine(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 qemuBuildSecretAESCommandLine(cmd, secinfo);
+}
+
+
/* qemuBuildGeneralSecinfoURI:
* @uri: Pointer to the URI structure to add to
* @secinfo: Pointer to the secret info data (if present)
@@ -697,6 +801,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;
}
@@ -1094,6 +1202,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
char *source = NULL;
int actualType = virStorageSourceGetActualType(disk->src);
qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+ qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
if (idx < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
@@ -1175,7 +1284,7 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
break;
}
- if (qemuGetDriveSourceString(disk->src, diskPriv->secinfo, &source) <
0)
+ if (qemuGetDriveSourceString(disk->src, secinfo, &source) < 0)
goto error;
if (source &&
@@ -1227,6 +1336,12 @@ qemuBuildDriveStr(virDomainDiskDefPtr disk,
qemuBufferEscapeComma(&opt, source);
virBufferAddLit(&opt, ",");
+ if (disk->src->protocol == VIR_STORAGE_NET_PROTOCOL_RBD &&
+ 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,",
@@ -1906,6 +2021,8 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
virDomainDiskDefPtr disk = def->disks[i];
bool withDeviceArg = false;
bool deviceFlagMasked = false;
+ qemuDomainDiskPrivatePtr diskPriv = QEMU_DOMAIN_DISK_PRIVATE(disk);
+ qemuDomainSecretInfoPtr secinfo = diskPriv->secinfo;
/* Unless we have -device, then USB disks need special
handling */
@@ -1948,6 +2065,9 @@ qemuBuildDiskDriveCommandLine(virCommandPtr cmd,
break;
}
+ if (qemuBuildDiskSecinfoCommandLine(cmd, secinfo) < 0)
+ return -1;
+
virCommandAddArg(cmd, "-drive");
/* Unfortunately it is not possible to use
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index edc3ac7..ac3ecd1 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -1052,7 +1052,7 @@ qemuDomainSecretAESSetup(virConnectPtr conn ATTRIBUTE_UNUSED,
*
* Returns 0 on success, -1 on failure
*/
-static int ATTRIBUTE_UNUSED
+static int
qemuDomainSecretSetup(virConnectPtr conn,
qemuDomainObjPrivatePtr priv,
qemuDomainSecretInfoPtr secinfo,
@@ -1102,7 +1102,7 @@ qemuDomainSecretDiskDestroy(virDomainDiskDefPtr disk)
*/
int
qemuDomainSecretDiskPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainDiskDefPtr disk)
{
virStorageSourcePtr src = disk->src;
@@ -1119,8 +1119,8 @@ qemuDomainSecretDiskPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretPlainSetup(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;
@@ -1163,7 +1163,7 @@ qemuDomainSecretHostdevDestroy(virDomainHostdevDefPtr hostdev)
*/
int
qemuDomainSecretHostdevPrepare(virConnectPtr conn,
- qemuDomainObjPrivatePtr priv ATTRIBUTE_UNUSED,
+ qemuDomainObjPrivatePtr priv,
virDomainHostdevDefPtr hostdev)
{
virDomainHostdevSubsysPtr subsys = &hostdev->source.subsys;
@@ -1184,9 +1184,9 @@ qemuDomainSecretHostdevPrepare(virConnectPtr conn,
if (VIR_ALLOC(secinfo) < 0)
return -1;
- if (qemuDomainSecretPlainSetup(conn, secinfo,
- VIR_STORAGE_NET_PROTOCOL_ISCSI,
- iscsisrc->auth) < 0)
+ if (qemuDomainSecretSetup(conn, priv, secinfo, hostdev->info->alias,
+ VIR_STORAGE_NET_PROTOCOL_ISCSI,
+ iscsisrc->auth) < 0)
goto error;
hostdevPriv->secinfo = secinfo;
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 0000000..ef9c968
--- /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-aesKey0,\
+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-aesKey0,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 0000000..ac2e942
--- /dev/null
+++ b/tests/qemuxml2argvdata/qemuxml2argv-disk-drive-network-rbd-auth-AES.xml
@@ -0,0 +1,42 @@
+<domain type='qemu'>
+ <name>QEMUGuest1</name>
+ <uuid>c7a5fdbd-edaf-9455-926a-d65c16db1809</uuid>
+ <memory unit='KiB'>219136</memory>
+ <currentMemory unit='KiB'>219136</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='block' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <source dev='/dev/HostVG/QEMUGuest1'/>
+ <target dev='hda' bus='ide'/>
+ <address type='drive' controller='0' bus='0'
target='0' unit='0'/>
+ </disk>
+ <disk type='network' device='disk'>
+ <driver name='qemu' type='raw'/>
+ <auth username='myname'>
+ <secret type='ceph' usage='mycluster_myname'/>
+ </auth>
+ <source protocol='rbd' name='pool/image'>
+ <host name='mon1.example.org' port='6321'/>
+ <host name='mon2.example.org' port='6322'/>
+ <host name='mon3.example.org' port='6322'/>
+ </source>
+ <target dev='vda' bus='virtio'/>
+ </disk>
+ <controller type='usb' index='0'/>
+ <controller type='ide' index='0'/>
+ <controller type='pci' index='0' model='pci-root'/>
+ <input type='mouse' bus='ps2'/>
+ <input type='keyboard' bus='ps2'/>
+ <memballoon model='none'/>
+ </devices>
+</domain>
diff --git a/tests/qemuxml2argvtest.c b/tests/qemuxml2argvtest.c
index 25caca7..db2f781 100644
--- a/tests/qemuxml2argvtest.c
+++ b/tests/qemuxml2argvtest.c
@@ -776,6 +776,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",
--
2.5.5