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@xxxxxxxxxx> --- 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list