Blob Blame History Raw
From 0e29f2d3041b0ab8dfcbc6bd4032aba6a69b56f6 Mon Sep 17 00:00:00 2001
Message-Id: <0e29f2d3041b0ab8dfcbc6bd4032aba6a69b56f6@dist-git>
From: John Ferlan <jferlan@redhat.com>
Date: Mon, 25 Jul 2016 12:42:57 -0400
Subject: [PATCH] storage: Add support to create a luks volume

Partially resolves:
https://bugzilla.redhat.com/show_bug.cgi?id=1301021

If the volume xml was looking to create a luks volume take the necessary
steps in order to make that happen.

The processing will be:
 1. create a temporary file (virStorageBackendCreateQemuImgSecretPath)
   1a. use the storage driver state dir path that uses the pool and
       volume name as a base.

 2. create a secret object (virStorageBackendCreateQemuImgSecretObject)
   2a. use an alias combinding the volume name and "_luks0"
   2b. add the file to the object

 3. create/add luks options to the commandline (virQEMUBuildLuksOpts)
   3a. at the very least a "key-secret=%s" using the secret object alias
   3b. if found in the XML the various "cipher" and "ivgen" options

Signed-off-by: John Ferlan <jferlan@redhat.com>
(cherry picked from commit 5e46d7d6b693c1e3c9197c182302ac7125a856d9)
---
 src/libvirt_private.syms       |   1 +
 src/storage/storage_backend.c  | 214 ++++++++++++++++++++++++++++++++++++++---
 src/storage/storage_backend.h  |   3 +-
 src/util/virqemu.c             |  58 +++++++++++
 src/util/virqemu.h             |   6 ++
 tests/storagevolxml2argvtest.c |   3 +-
 6 files changed, 271 insertions(+), 14 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index c205fd8..f73cfa2 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2193,6 +2193,7 @@ virProcessWait;
 
 # util/virqemu.h
 virQEMUBuildBufferEscapeComma;
+virQEMUBuildLuksOpts;
 virQEMUBuildObjectCommandlineFromJSON;
 
 
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c
index 97f6ffe..84ef4f2 100644
--- a/src/storage/storage_backend.c
+++ b/src/storage/storage_backend.c
@@ -55,11 +55,14 @@
 #include "viralloc.h"
 #include "internal.h"
 #include "secret_conf.h"
+#include "secret_util.h"
 #include "viruuid.h"
 #include "virstoragefile.h"
 #include "storage_backend.h"
 #include "virlog.h"
 #include "virfile.h"
+#include "virjson.h"
+#include "virqemu.h"
 #include "stat-time.h"
 #include "virstring.h"
 #include "virxml.h"
@@ -907,6 +910,7 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg)
     return ret;
 }
 
+
 static int
 virStorageBackendQEMUImgBackingFormat(const char *qemuimg)
 {
@@ -925,6 +929,10 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg)
     return ret;
 }
 
+/* The _virStorageBackendQemuImgInfo separates the command line building from
+ * the volume definition so that qemuDomainSnapshotCreateInactiveExternal can
+ * use it without needing to deal with a volume.
+ */
 struct _virStorageBackendQemuImgInfo {
     int format;
     const char *path;
@@ -941,21 +949,36 @@ struct _virStorageBackendQemuImgInfo {
     const char *inputPath;
     const char *inputFormatStr;
     int inputFormat;
+
+    char *secretAlias;
+    const char *secretPath;
 };
 
+
 static int
-virStorageBackendCreateQemuImgOpts(char **opts,
+virStorageBackendCreateQemuImgOpts(virStorageEncryptionInfoDefPtr enc,
+                                   char **opts,
                                    struct _virStorageBackendQemuImgInfo info)
 {
     virBuffer buf = VIR_BUFFER_INITIALIZER;
 
-    if (info.backingPath)
-        virBufferAsprintf(&buf, "backing_fmt=%s,",
-                          virStorageFileFormatTypeToString(info.backingFormat));
-    if (info.encryption)
-        virBufferAddLit(&buf, "encryption=on,");
-    if (info.preallocate)
-        virBufferAddLit(&buf, "preallocation=metadata,");
+    if (info.format == VIR_STORAGE_FILE_LUKS) {
+        if (!enc) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("missing luks encryption information"));
+            goto error;
+        }
+        virQEMUBuildLuksOpts(&buf, enc, info.secretAlias);
+    } else {
+        if (info.backingPath)
+            virBufferAsprintf(&buf, "backing_fmt=%s,",
+                              virStorageFileFormatTypeToString(info.backingFormat));
+        if (info.encryption)
+            virBufferAddLit(&buf, "encryption=on,");
+        if (info.preallocate)
+            virBufferAddLit(&buf, "preallocation=metadata,");
+    }
+
     if (info.nocow)
         virBufferAddLit(&buf, "nocow=on,");
 
@@ -1025,6 +1048,22 @@ virStorageBackendCreateQemuImgCheckEncryption(int format,
             if (virStorageGenerateQcowEncryption(conn, vol) < 0)
                 return -1;
         }
+    } else if (format == VIR_STORAGE_FILE_LUKS) {
+        if (enc->format != VIR_STORAGE_ENCRYPTION_FORMAT_LUKS) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("unsupported volume encryption format %d"),
+                           vol->target.encryption->format);
+            return -1;
+        }
+        if (enc->nsecrets > 1) {
+            virReportError(VIR_ERR_XML_ERROR, "%s",
+                           _("too many secrets for luks encryption"));
+            return -1;
+        }
+        if (enc->nsecrets == 0) {
+            virReportError(VIR_ERR_XML_ERROR, "%s",
+                           _("no secret provided for luks encryption"));
+        }
     } else {
         virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
                        _("volume encryption unsupported with format %s"), type);
@@ -1069,6 +1108,12 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool,
     int accessRetCode = -1;
     char *absolutePath = NULL;
 
+    if (info->format == VIR_STORAGE_FILE_LUKS) {
+        virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                       _("cannot set backing store for luks volume"));
+        return -1;
+    }
+
     info->backingFormat = vol->target.backingStore->format;
     info->backingPath = vol->target.backingStore->path;
 
@@ -1122,6 +1167,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool,
 static int
 virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
                                          int imgformat,
+                                         virStorageEncryptionInfoDefPtr enc,
                                          struct _virStorageBackendQemuImgInfo info)
 {
     char *opts = NULL;
@@ -1130,7 +1176,7 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
         imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT)
         info.compat = "0.10";
 
-    if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0)
+    if (virStorageBackendCreateQemuImgOpts(enc, &opts, info) < 0)
         return -1;
     if (opts)
         virCommandAddArgList(cmd, "-o", opts, NULL);
@@ -1140,6 +1186,39 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
 }
 
 
+/* Add a secret object to the command line:
+ *    --object secret,id=$secretAlias,file=$secretPath
+ *
+ *    NB: format=raw is assumed
+ */
+static int
+virStorageBackendCreateQemuImgSecretObject(virCommandPtr cmd,
+                                           virStorageVolDefPtr vol,
+                                           struct _virStorageBackendQemuImgInfo *info)
+{
+    virBuffer buf = VIR_BUFFER_INITIALIZER;
+    char *commandStr = NULL;
+
+    if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 0)
+        return -1;
+
+    virBufferAsprintf(&buf, "secret,id=%s,file=", info->secretAlias);
+    virQEMUBuildBufferEscapeComma(&buf, info->secretPath);
+
+    if (virBufferCheckError(&buf) < 0) {
+        virBufferFreeAndReset(&buf);
+        return -1;
+    }
+
+    commandStr = virBufferContentAndReset(&buf);
+
+    virCommandAddArgList(cmd, "--object", commandStr, NULL);
+
+    VIR_FREE(commandStr);
+    return 0;
+}
+
+
 /* Create a qemu-img virCommand from the supplied binary path,
  * volume definitions and imgformat
  */
@@ -1150,7 +1229,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
                                          virStorageVolDefPtr inputvol,
                                          unsigned int flags,
                                          const char *create_tool,
-                                         int imgformat)
+                                         int imgformat,
+                                         const char *secretPath)
 {
     virCommandPtr cmd = NULL;
     const char *type;
@@ -1162,7 +1242,10 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
         .compat = vol->target.compat,
         .features = vol->target.features,
         .nocow = vol->target.nocow,
+        .secretPath = secretPath,
+        .secretAlias = NULL,
     };
+    virStorageEncryptionInfoDefPtr enc = NULL;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL);
 
@@ -1192,6 +1275,18 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
                        _("format features only available with qcow2"));
         return NULL;
     }
+    if (info.format == VIR_STORAGE_FILE_LUKS) {
+        if (inputvol) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                           _("cannot use inputvol with luks volume"));
+            return NULL;
+        }
+        if (!info.encryption) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("missing encryption description"));
+            return NULL;
+        }
+    }
 
     if (inputvol &&
         virStorageBackendCreateQemuImgSetInput(inputvol, &info) < 0)
@@ -1226,10 +1321,22 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
     if (info.backingPath)
         virCommandAddArgList(cmd, "-b", info.backingPath, NULL);
 
-    if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat, info) < 0) {
+    if (info.format == VIR_STORAGE_FILE_LUKS) {
+        if (virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) {
+            VIR_FREE(info.secretAlias);
+            virCommandFree(cmd);
+            return NULL;
+        }
+        enc = &vol->target.encryption->encinfo;
+    }
+
+    if (virStorageBackendCreateQemuImgSetOptions(cmd, imgformat,
+                                                 enc, info) < 0) {
+        VIR_FREE(info.secretAlias);
         virCommandFree(cmd);
         return NULL;
     }
+    VIR_FREE(info.secretAlias);
 
     if (info.inputPath)
         virCommandAddArg(cmd, info.inputPath);
@@ -1240,6 +1347,77 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
     return cmd;
 }
 
+
+static char *
+virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn,
+                                         virStoragePoolObjPtr pool,
+                                         virStorageVolDefPtr vol)
+{
+    virStorageEncryptionPtr enc = vol->target.encryption;
+    char *secretPath = NULL;
+    int fd = -1;
+    uint8_t *secret = NULL;
+    size_t secretlen = 0;
+
+    if (!enc) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("missing encryption description"));
+        return NULL;
+    }
+
+    if (!conn || !conn->secretDriver ||
+        !conn->secretDriver->secretLookupByUUID ||
+        !conn->secretDriver->secretLookupByUsage ||
+        !conn->secretDriver->secretGetValue) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("unable to look up encryption secret"));
+        return NULL;
+    }
+
+    if (!(secretPath = virStoragePoolObjBuildTempFilePath(pool, vol)))
+        goto cleanup;
+
+    if ((fd = mkostemp(secretPath, O_CLOEXEC)) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to open luks secret file for write"));
+        goto error;
+    }
+
+    if (virSecretGetSecretString(conn, &enc->secrets[0]->seclookupdef,
+                                 VIR_SECRET_USAGE_TYPE_VOLUME,
+                                 &secret, &secretlen) < 0)
+        goto error;
+
+    if (safewrite(fd, secret, secretlen) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to write luks secret file"));
+        goto error;
+    }
+    VIR_FORCE_CLOSE(fd);
+
+    if ((vol->target.perms->uid != (uid_t) -1) &&
+        (vol->target.perms->gid != (gid_t) -1)) {
+        if (chown(secretPath, vol->target.perms->uid,
+                  vol->target.perms->gid) < 0) {
+            virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                           _("failed to chown luks secret file"));
+            goto error;
+        }
+    }
+
+ cleanup:
+    VIR_DISPOSE_N(secret, secretlen);
+    VIR_FORCE_CLOSE(fd);
+
+    return secretPath;
+
+ error:
+    unlink(secretPath);
+    VIR_FREE(secretPath);
+    goto cleanup;
+}
+
+
 int
 virStorageBackendCreateQemuImg(virConnectPtr conn,
                                virStoragePoolObjPtr pool,
@@ -1251,6 +1429,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
     char *create_tool;
     int imgformat;
     virCommandPtr cmd;
+    char *secretPath = NULL;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
 
@@ -1266,8 +1445,15 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
     if (imgformat < 0)
         goto cleanup;
 
+    if (vol->target.format == VIR_STORAGE_FILE_LUKS) {
+        if (!(secretPath =
+              virStorageBackendCreateQemuImgSecretPath(conn, pool, vol)))
+            goto cleanup;
+    }
+
     cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, pool, vol, inputvol,
-                                                   flags, create_tool, imgformat);
+                                                   flags, create_tool,
+                                                   imgformat, secretPath);
     if (!cmd)
         goto cleanup;
 
@@ -1275,6 +1461,10 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
 
     virCommandFree(cmd);
  cleanup:
+    if (secretPath) {
+        unlink(secretPath);
+        VIR_FREE(secretPath);
+    }
     VIR_FREE(create_tool);
     return ret;
 }
diff --git a/src/storage/storage_backend.h b/src/storage/storage_backend.h
index 5bc622c..28e1a65 100644
--- a/src/storage/storage_backend.h
+++ b/src/storage/storage_backend.h
@@ -241,7 +241,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
                                          virStorageVolDefPtr inputvol,
                                          unsigned int flags,
                                          const char *create_tool,
-                                         int imgformat);
+                                         int imgformat,
+                                         const char *secretPath);
 
 /* ------- virStorageFile backends ------------ */
 typedef struct _virStorageFileBackend virStorageFileBackend;
diff --git a/src/util/virqemu.c b/src/util/virqemu.c
index 79a4292..7d181e1 100644
--- a/src/util/virqemu.c
+++ b/src/util/virqemu.c
@@ -155,3 +155,61 @@ virQEMUBuildBufferEscapeComma(virBufferPtr buf, const char *str)
 {
     virBufferEscape(buf, ',', ",", "%s", str);
 }
+
+
+/**
+ * virQEMUBuildLuksOpts:
+ * @buf: buffer to build the string into
+ * @enc: pointer to encryption info
+ * @alias: alias to use
+ *
+ * Generate the string for id=$alias and any encryption options for
+ * into the buffer.
+ *
+ * Important note, a trailing comma (",") is built into the return since
+ * it's expected other arguments are appended after the id=$alias string.
+ * So either turn something like:
+ *
+ *     "key-secret=$alias,"
+ *
+ * or
+ *     "key-secret=$alias,cipher-alg=twofish-256,cipher-mode=cbc,
+ *     hash-alg=sha256,ivgen-alg=plain64,igven-hash-alg=sha256,"
+ *
+ */
+void
+virQEMUBuildLuksOpts(virBufferPtr buf,
+                     virStorageEncryptionInfoDefPtr enc,
+                     const char *alias)
+{
+    virBufferAsprintf(buf, "key-secret=%s,", alias);
+
+    if (!enc->cipher_name)
+        return;
+
+    virBufferAddLit(buf, "cipher-alg=");
+    virQEMUBuildBufferEscapeComma(buf, enc->cipher_name);
+    virBufferAsprintf(buf, "-%u,", enc->cipher_size);
+    if (enc->cipher_mode) {
+        virBufferAddLit(buf, "cipher-mode=");
+        virQEMUBuildBufferEscapeComma(buf, enc->cipher_mode);
+        virBufferAddLit(buf, ",");
+    }
+    if (enc->cipher_hash) {
+        virBufferAddLit(buf, "hash-alg=");
+        virQEMUBuildBufferEscapeComma(buf, enc->cipher_hash);
+        virBufferAddLit(buf, ",");
+    }
+    if (!enc->ivgen_name)
+        return;
+
+    virBufferAddLit(buf, "ivgen-alg=");
+    virQEMUBuildBufferEscapeComma(buf, enc->ivgen_name);
+    virBufferAddLit(buf, ",");
+
+    if (enc->ivgen_hash) {
+        virBufferAddLit(buf, "ivgen-hash-alg=");
+        virQEMUBuildBufferEscapeComma(buf, enc->ivgen_hash);
+        virBufferAddLit(buf, ",");
+    }
+}
diff --git a/src/util/virqemu.h b/src/util/virqemu.h
index 1033412..eb75d1b 100644
--- a/src/util/virqemu.h
+++ b/src/util/virqemu.h
@@ -27,10 +27,16 @@
 # include "internal.h"
 # include "virbuffer.h"
 # include "virjson.h"
+# include "virstorageencryption.h"
 
 char *virQEMUBuildObjectCommandlineFromJSON(const char *type,
                                             const char *alias,
                                             virJSONValuePtr props);
 
 void virQEMUBuildBufferEscapeComma(virBufferPtr buf, const char *str);
+void virQEMUBuildLuksOpts(virBufferPtr buf,
+                          virStorageEncryptionInfoDefPtr enc,
+                          const char *alias)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(3);
+
 #endif /* __VIR_QEMU_H_ */
diff --git a/tests/storagevolxml2argvtest.c b/tests/storagevolxml2argvtest.c
index ccfe9ab..e300821 100644
--- a/tests/storagevolxml2argvtest.c
+++ b/tests/storagevolxml2argvtest.c
@@ -83,7 +83,8 @@ testCompareXMLToArgvFiles(bool shouldFail,
 
     cmd = virStorageBackendCreateQemuImgCmdFromVol(conn, &poolobj, vol,
                                                    inputvol, flags,
-                                                   create_tool, imgformat);
+                                                   create_tool, imgformat,
+                                                   NULL);
     if (!cmd) {
         if (shouldFail) {
             virResetLastError();
-- 
2.9.2