Add a masterKey to _qemuDomainObjPrivate to store a base64 encoded domain
master key in order to support the ability to encrypt/decrypt sensitive
data shared between libvirt and qemu. The base64 encoded value will be
written to the domain XML file for consistency between domain restarts.
Two APIs qemuDomainWriteMasterKeyFile and qemuDomainGetMasterKeyFilePath
will manage the details of the file manipulation.
The Get*Path API can be used in order to return a path to the master
secret key file, which will be a combination of the domain's libDir
(e.g. /var/lib/libvirt/qemu/domain-#-$NAME/) and a name 'master.key'.
Use of the domain's libDir was chosen as opposed to a more generic
/var/lib/libvirt/qemu/$NAME-master.key since it's a domain specific
area rather than repeating issues found as a result of using the
domain name in a file. The Get*Path API doesn't check if the
libDir exists, it just generates the path.
The Write*File API will be used to create the on disk master secret file
once the domain lib infrastructure is generated during qemuProcessLaunch.
The masterKey is generated as a series of 8 bit random numbers stored
as a 32 byte string and then base64 encoded before saving in the domain
private object.
A separate function will generate the key as it's expected to be utilized
by future patches to support the generation of the initialization vector.
Object removal will clear and free the secret as well as check for the
presence of the master key file and remove it if necessary (although it
shouldn't be necessary by this point in time, but being extra safe).
During process launch, the key value will additionally be stored in
the domain libDir. This is what will be used to share the secret with
qemu via a secret object. The secret file will only present while the
domain is active (e.g. create at Launch, delete at Stop).
During process stop, logic is added to check if the path to the
domain libDir secret exists and then to clear the contents of the
file before the directory tree is removed. The path will not exist
for a domain already running prior to support being added for the
master key and it'd be annoying to get errors indicating it doesn't
exist when it was never created.
Signed-off-by: John Ferlan <jferlan(a)redhat.com>
---
src/qemu/qemu_domain.c | 158 ++++++++++++++++++++++++++++++++++++++++++++++++
src/qemu/qemu_domain.h | 9 +++
src/qemu/qemu_process.c | 13 ++++
3 files changed, 180 insertions(+)
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index 9f9fae3..507ae9e 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -23,6 +23,7 @@
#include <config.h>
+#include <assert.h>
#include "qemu_domain.h"
#include "qemu_alias.h"
#include "qemu_command.h"
@@ -44,6 +45,8 @@
#include "virthreadjob.h"
#include "viratomic.h"
#include "virprocess.h"
+#include "virrandom.h"
+#include "base64.h"
#include "logging/log_manager.h"
#include "storage/storage_driver.h"
@@ -465,6 +468,151 @@ qemuDomainJobInfoToParams(qemuDomainJobInfoPtr jobInfo,
}
+/* qemuDomainGetMasterKeyFilePath:
+ * @libDir: Directory path to domain lib files
+ *
+ * Build the name of the master key file based on valid libDir path
+ *
+ * Returns path to memory containing the name of the file. It is up to the
+ * caller to free; otherwise, NULL on failure.
+ */
+char *
+qemuDomainGetMasterKeyFilePath(const char *libDir)
+{
+ if (!libDir) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("invalid path for master key file"));
+ return NULL;
+ }
+ return virFileBuildPath(libDir, "master.key", NULL);
+}
+
+
+/* qemuDomainWriteMasterKeyFile:
+ * @libDir: Directory path to domain lib files
+ * @masterKey: Key to write to the file
+ *
+ * Using the passed libDir and masterKey write the key to the master
+ * key file for the domain.
+ *
+ * Returns 0 on success, -1 on failure with error message indicating failure
+ */
+int
+qemuDomainWriteMasterKeyFile(const char *libDir,
+ const char *masterKey)
+{
+ char *path;
+ int ret = -1;
+
+ if (!(path = qemuDomainGetMasterKeyFilePath(libDir)))
+ return -1;
+
+ if (virFileWriteStr(path, masterKey, 0600) < 0) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to write master key file for domain"));
+ goto cleanup;
+ }
+
+ ret = 0;
+
+ cleanup:
+ VIR_FREE(path);
+ return ret;
+}
+
+
+/* qemuDomainGenerateRandomKey
+ * @nbytes: Size in bytes of random key to generate - expect multiple of 8
+ *
+ * Generate a random key of nbytes length and return it.
+ *
+ * Returns pointer memory containing key on success, NULL on failure
+ */
+static unsigned char *
+qemuDomainGenerateRandomKey(int nbytes)
+{
+ unsigned char *key;
+ size_t i;
+
+ assert((nbytes % 8) == 0);
+
+ if (VIR_ALLOC_N(key, nbytes) < 0)
+ return NULL;
+
+ /* Generate a random master key based on the nbytes passed */
+ for (i = 0; i < nbytes; i++)
+ key[i] = virRandomBits(8);
+
+ return key;
+}
+
+
+/*
+ * qemuDomainMasterKeyRemove:
+ * @priv: Pointer to the domain private object
+ *
+ * Remove the traces of the master key, clear the heap, clear the file,
+ * delete the file.
+ */
+static void
+qemuDomainMasterKeyRemove(qemuDomainObjPrivatePtr priv)
+{
+ char *path = NULL;
+
+ if (!priv->masterKey)
+ return;
+
+ /* Clear the heap */
+ memset(priv->masterKey, 0, QEMU_DOMAIN_MASTER_KEY_LEN);
+ VIR_FREE(priv->masterKey);
+
+ /* Clear and remove the file, if not already handled during process stop */
+ if (priv->libDir && virFileExists(priv->libDir)) {
+ path = qemuDomainGetMasterKeyFilePath(priv->libDir);
+ if (path && virFileExists(path)) {
+ ignore_value(qemuDomainWriteMasterKeyFile(priv->libDir, "0"));
+ unlink(path);
+ }
+ }
+ VIR_FREE(path);
+}
+
+
+/* qemuDomainMasterKeyCreate:
+ * @priv: Pointer to the domain private object
+ *
+ * Generate and store as a base64 encoded value a random 32-byte key
+ * to be used as a secret shared with qemu to share sensative data.
+ *
+ * Returns: 0 on success, -1 w/ error message on failure
+ */
+static int
+qemuDomainMasterKeyCreate(qemuDomainObjPrivatePtr priv)
+{
+ unsigned char *key = NULL;
+
+ if (!(key = qemuDomainGenerateRandomKey(QEMU_DOMAIN_MASTER_KEY_LEN)))
+ goto error;
+
+ /* base64 encode the key */
+ base64_encode_alloc((const char *)key, QEMU_DOMAIN_MASTER_KEY_LEN,
+ &priv->masterKey);
+ memset(key, 0, QEMU_DOMAIN_MASTER_KEY_LEN);
+ VIR_FREE(key);
+ if (!priv->masterKey) {
+ virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+ _("failed to encode master key"));
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ qemuDomainMasterKeyRemove(priv);
+ return -1;
+}
+
+
static virClassPtr qemuDomainDiskPrivateClass;
static int
@@ -574,6 +722,9 @@ qemuDomainObjPrivateAlloc(void)
if (!(priv->devs = virChrdevAlloc()))
goto error;
+ if (qemuDomainMasterKeyCreate(priv) < 0)
+ goto error;
+
priv->migMaxBandwidth = QEMU_DOMAIN_MIG_BANDWIDTH_MAX;
return priv;
@@ -590,6 +741,8 @@ qemuDomainObjPrivateFree(void *data)
virObjectUnref(priv->qemuCaps);
+ qemuDomainMasterKeyRemove(priv);
+
virCgroupFree(&priv->cgroup);
virDomainPCIAddressSetFree(priv->pciaddrs);
virDomainCCWAddressSetFree(priv->ccwaddrs);
@@ -746,6 +899,9 @@ qemuDomainObjPrivateXMLFormat(virBufferPtr buf,
virBufferEscapeString(buf, "<channelTargetDir
path='%s'/>\n",
priv->channelTargetDir);
+ if (priv->masterKey)
+ virBufferAsprintf(buf, "<masterKey>%s</masterKey>\n",
priv->masterKey);
+
return 0;
}
@@ -959,6 +1115,8 @@ qemuDomainObjPrivateXMLParse(xmlXPathContextPtr ctxt,
if (qemuDomainSetPrivatePathsOld(driver, vm) < 0)
goto error;
+ priv->masterKey = virXPathString("string(./masterKey)", ctxt);
+
return 0;
error:
diff --git a/src/qemu/qemu_domain.h b/src/qemu/qemu_domain.h
index 573968c..b24acdf 100644
--- a/src/qemu/qemu_domain.h
+++ b/src/qemu/qemu_domain.h
@@ -147,6 +147,7 @@ struct qemuDomainJobObj {
typedef void (*qemuDomainCleanupCallback)(virQEMUDriverPtr driver,
virDomainObjPtr vm);
+# define QEMU_DOMAIN_MASTER_KEY_LEN 32 /* For a 32-byte random AES key */
typedef struct _qemuDomainObjPrivate qemuDomainObjPrivate;
typedef qemuDomainObjPrivate *qemuDomainObjPrivatePtr;
struct _qemuDomainObjPrivate {
@@ -212,6 +213,8 @@ struct _qemuDomainObjPrivate {
char *machineName;
char *libDir; /* base path for per-domain files */
char *channelTargetDir; /* base path for per-domain channel targets */
+
+ char *masterKey; /* base64 encoded random key */
};
# define QEMU_DOMAIN_DISK_PRIVATE(disk) \
@@ -546,4 +549,10 @@ int qemuDomainSetPrivatePaths(char **domainLibDir,
const char *confChannelDir,
const char *domainName,
int domainId);
+
+char *qemuDomainGetMasterKeyFilePath(const char *libDir);
+
+int qemuDomainWriteMasterKeyFile(const char *libDir, const char *masterKey)
+ ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2);
+
#endif /* __QEMU_DOMAIN_H__ */
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index c332747..0784f1c 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -5141,6 +5141,10 @@ qemuProcessLaunch(virConnectPtr conn,
qemuProcessMakeDir(driver, vm, priv->channelTargetDir) < 0)
goto cleanup;
+ /* Write the masterKey to the file in libDir */
+ if (qemuDomainWriteMasterKeyFile(priv->libDir, priv->masterKey) < 0)
+ goto cleanup;
+
/* now that we know it is about to start call the hook if present */
if (qemuProcessStartHook(driver, vm,
VIR_HOOK_QEMU_OP_START,
@@ -5583,6 +5587,7 @@ void qemuProcessStop(virQEMUDriverPtr driver,
virNetDevVPortProfilePtr vport = NULL;
size_t i;
char *timestamp;
+ char *masterKeyPath = NULL;
virQEMUDriverConfigPtr cfg = virQEMUDriverGetConfig(driver);
qemuDomainLogContextPtr logCtxt = NULL;
@@ -5668,6 +5673,13 @@ void qemuProcessStop(virQEMUDriverPtr driver,
priv->monConfig = NULL;
}
+ /* If we have a masterKeyPath, then clear the masterKey from the file
+ * even though it's about to be deleted, let's not leave any traces.
+ */
+ masterKeyPath = qemuDomainGetMasterKeyFilePath(priv->libDir);
+ if (virFileExists(masterKeyPath))
+ ignore_value(qemuDomainWriteMasterKeyFile(priv->libDir, "0"));
+
virFileDeleteTree(priv->libDir);
virFileDeleteTree(priv->channelTargetDir);
@@ -5867,6 +5879,7 @@ void qemuProcessStop(virQEMUDriverPtr driver,
qemuDomainObjEndJob(driver, vm);
cleanup:
+ VIR_FREE(masterKeyPath);
if (orig_err) {
virSetError(orig_err);
virFreeError(orig_err);
--
2.5.0