[libvirt] [PATCH 17/19] storage: Add support to create a luks volume

John Ferlan jferlan at redhat.com
Tue Jun 14 00:27:56 UTC 2016


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 in the storage pool target path
   1a. the file name will either be $volname_$UUID.tmp or $volname_$USAGE.tmp
       depending on how the encryption xml specified fetching the secret
   1b. create/open the file, initially setting mode to 0600 with current
       effective uid:gid as owner
   1c. fetch the secret into a buffer and write that into the file
   1d. change file protections to 0400

 2. create a secret object
   2a. use a similarly crafted alias to the file name
   2b. add the file to the object

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

Signed-off-by: John Ferlan <jferlan at redhat.com>
---
 src/libvirt_private.syms       |   1 +
 src/storage/storage_backend.c  | 285 ++++++++++++++++++++++++++++++++++++++---
 src/storage/storage_backend.h  |   3 +-
 src/util/virqemu.c             |  23 ++++
 src/util/virqemu.h             |   6 +
 tests/storagevolxml2argvtest.c |   3 +-
 6 files changed, 300 insertions(+), 21 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index fdf06ae..0d6d080 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2153,6 +2153,7 @@ virProcessWait;
 
 
 # util/virqemu.h
+virQEMUBuildLuksOpts;
 virQEMUBuildObjectCommandlineFromJSON;
 
 
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c
index 4965c9e..fc50807 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"
@@ -880,6 +883,7 @@ virStoragePloopResize(virStorageVolDefPtr vol,
 enum {
     QEMU_IMG_BACKING_FORMAT_OPTIONS = 0,
     QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT,
+    QEMU_IMG_FORMAT_LUKS,
 };
 
 static bool
@@ -907,6 +911,27 @@ virStorageBackendQemuImgSupportsCompat(const char *qemuimg)
     return ret;
 }
 
+
+static bool
+virStorageBackendQemuImgSupportsLuks(const char *qemuimg)
+{
+    bool ret = false;
+    int exitstatus = -1;
+    virCommandPtr cmd = virCommandNewArgList(qemuimg, "create", "-o", "?",
+                                             "-f", "luks", "/dev/null", NULL);
+
+    if (virCommandRun(cmd, &exitstatus) < 0)
+        goto cleanup;
+
+    if (exitstatus == 0)
+        ret = true;
+
+ cleanup:
+    virCommandFree(cmd);
+    return ret;
+}
+
+
 static int
 virStorageBackendQEMUImgBackingFormat(const char *qemuimg)
 {
@@ -915,12 +940,18 @@ virStorageBackendQEMUImgBackingFormat(const char *qemuimg)
      * out what else we have */
     int ret = QEMU_IMG_BACKING_FORMAT_OPTIONS;
 
-    /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer
-     * understands. Since we still support QEMU 0.12 and newer, we need
-     * to be able to handle the previous format as can be set via a
-     * compat=0.10 option. */
-    if (virStorageBackendQemuImgSupportsCompat(qemuimg))
-        ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT;
+    /* QEMU 2.6 added support for luks - let's check for that.
+     * If available, then we can also assume OPTIONS_COMPAT is present */
+    if (virStorageBackendQemuImgSupportsLuks(qemuimg)) {
+        ret = QEMU_IMG_FORMAT_LUKS;
+    } else {
+        /* QEMU 2.0 changed to using a format that only QEMU 1.1 and newer
+         * understands. Since we still support QEMU 0.12 and newer, we need
+         * to be able to handle the previous format as can be set via a
+         * compat=0.10 option. */
+        if (virStorageBackendQemuImgSupportsCompat(qemuimg))
+            ret = QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT;
+    }
 
     return ret;
 }
@@ -941,21 +972,31 @@ struct _virStorageBackendQemuImgInfo {
     const char *inputPath;
     const char *inputFormatStr;
     int inputFormat;
+
+    char *secretAlias;
+    const char *secretPath;
 };
 
+
 static int
-virStorageBackendCreateQemuImgOpts(char **opts,
+virStorageBackendCreateQemuImgOpts(virStorageEncryptionPtr 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) {
+        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 +1066,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 +1126,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;
 
@@ -1121,6 +1184,7 @@ virStorageBackendCreateQemuImgSetBacking(virStoragePoolObjPtr pool,
 
 static int
 virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
+                                         virStorageVolDefPtr vol,
                                          int imgformat,
                                          struct _virStorageBackendQemuImgInfo info)
 {
@@ -1130,7 +1194,8 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
         imgformat >= QEMU_IMG_BACKING_FORMAT_OPTIONS_COMPAT)
         info.compat = "0.10";
 
-    if (virStorageBackendCreateQemuImgOpts(&opts, info) < 0)
+    if (virStorageBackendCreateQemuImgOpts(vol->target.encryption,
+                                           &opts, info) < 0)
         return -1;
     if (opts)
         virCommandAddArgList(cmd, "-o", opts, NULL);
@@ -1140,6 +1205,66 @@ virStorageBackendCreateQemuImgSetOptions(virCommandPtr cmd,
 }
 
 
+/* Create a hopefully unique enough name to be used for both the
+ * secretPath and secretAlias generation
+ */
+static char *
+virStorageBackendCreateQemuImgLuksName(const char *volname,
+                                       virStorageEncryptionPtr enc)
+{
+    char *ret;
+
+    if (enc->secrets[0]->secdef.type == VIR_SECRET_LOOKUP_TYPE_UUID)
+        ignore_value(virAsprintf(&ret, "%s_%s", volname,
+                                 enc->secrets[0]->secdef.u.uuid));
+    else
+        ignore_value(virAsprintf(&ret, "%s_%s", volname,
+                                 enc->secrets[0]->secdef.u.usage));
+    return ret;
+}
+
+
+/* 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)
+{
+    virStorageEncryptionPtr enc = vol->target.encryption;
+    char *str = NULL;
+    virJSONValuePtr props = NULL;
+    char *commandStr = NULL;
+
+    if (!(str = virStorageBackendCreateQemuImgLuksName(vol->name, enc)))
+        return -1;
+
+    if (virAsprintf(&info->secretAlias, "%s_luks0", str) < 0) {
+        VIR_FREE(str);
+        return -1;
+    }
+    VIR_FREE(str);
+
+    if (virJSONValueObjectCreate(&props, "s:file", info->secretPath, NULL) < 0)
+        return -1;
+
+    if (!(commandStr = virQEMUBuildObjectCommandlineFromJSON("secret",
+                                                             info->secretAlias,
+                                                             props))) {
+        virJSONValueFree(props);
+        return -1;
+    }
+    virJSONValueFree(props);
+
+    virCommandAddArgList(cmd, "--object", commandStr, NULL);
+
+    return 0;
+}
+
+
 /* Create a qemu-img virCommand from the supplied binary path,
  * volume definitions and imgformat
  */
@@ -1150,7 +1275,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,6 +1288,8 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
         .compat = vol->target.compat,
         .features = vol->target.features,
         .nocow = vol->target.nocow,
+        .secretPath = secretPath,
+        .secretAlias = NULL,
     };
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, NULL);
@@ -1192,6 +1320,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)
@@ -1207,7 +1347,6 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
                                                       conn, vol) < 0)
         return NULL;
 
-
     /* Size in KB */
     info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024);
 
@@ -1226,10 +1365,20 @@ 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 &&
+        virStorageBackendCreateQemuImgSecretObject(cmd, vol, &info) < 0) {
+        VIR_FREE(info.secretAlias);
+        virCommandFree(cmd);
+        return NULL;
+    }
+
+    if (virStorageBackendCreateQemuImgSetOptions(cmd, vol, imgformat,
+                                                 info) < 0) {
+        VIR_FREE(info.secretAlias);
         virCommandFree(cmd);
         return NULL;
     }
+    VIR_FREE(info.secretAlias);
 
     if (info.inputPath)
         virCommandAddArg(cmd, info.inputPath);
@@ -1240,6 +1389,86 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
     return cmd;
 }
 
+
+static char *
+virStorageBackendCreateQemuImgSecretPath(virConnectPtr conn,
+                                         virStoragePoolObjPtr pool,
+                                         virStorageVolDefPtr vol)
+{
+    virStorageEncryptionPtr enc = vol->target.encryption;
+    char *str = NULL;
+    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;
+    }
+
+    /* Create a temporary secret file in the pool using */
+    if (!(str = virStorageBackendCreateQemuImgLuksName(vol->name, enc)))
+        return NULL;
+
+    /* Since we don't have a file, just go to cleanup using NULL secretPath */
+    if (!(secretPath = virFileBuildPath(pool->def->target.path, str, ".tmp")))
+        goto cleanup;
+
+    if ((fd = open(secretPath, O_WRONLY|O_TRUNC|O_CREAT, 0600)) < 0) {
+        virReportSystemError(errno, "%s",
+                             _("failed to open luks secret file for write"));
+        goto error;
+    }
+
+    if (virSecretGetSecretString(conn, &enc->secrets[0]->secdef,
+                                 VIR_SECRET_USAGE_TYPE_KEY,
+                                 &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 (chown(secretPath, geteuid(), getegid()) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to chown luks secret file"));
+        goto error;
+    }
+
+    if (chmod(secretPath, 0400) < 0) {
+        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                       _("failed to chown luks secret file"));
+        goto error;
+    }
+
+ cleanup:
+    VIR_FREE(str);
+    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 +1480,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
     char *create_tool;
     int imgformat;
     virCommandPtr cmd;
+    char *secretPath = NULL;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
 
@@ -1266,8 +1496,21 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
     if (imgformat < 0)
         goto cleanup;
 
+    if (vol->target.format == VIR_STORAGE_FILE_LUKS) {
+        if (imgformat < QEMU_IMG_FORMAT_LUKS) {
+            virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+                           _("this version of '%s' does not support luks"),
+                           create_tool);
+            goto cleanup;
+        }
+        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 +1518,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 895168e..a56fb3f 100644
--- a/src/util/virqemu.c
+++ b/src/util/virqemu.c
@@ -140,3 +140,26 @@ virQEMUBuildObjectCommandlineFromJSON(const char *type,
     virBufferFreeAndReset(&buf);
     return ret;
 }
+
+
+void
+virQEMUBuildLuksOpts(virBufferPtr buf,
+                     virStorageEncryptionPtr enc,
+                     const char *alias)
+{
+    virBufferAsprintf(buf, "key-secret=%s,", alias);
+
+    /* If there's any cipher, then add that to the command line */
+    if (enc->cipher.name) {
+        virBufferAsprintf(buf, "cipher-alg=%s-%u,",
+                          enc->cipher.name, enc->cipher.size);
+        if (enc->cipher.mode)
+            virBufferAsprintf(buf, "cipher-mode=%s,", enc->cipher.mode);
+        if (enc->cipher.hash)
+            virBufferAsprintf(buf, "hash-alg=%s,", enc->cipher.hash);
+        if (enc->ivgen.name)
+            virBufferAsprintf(buf, "ivgen-alg=%s,", enc->ivgen.name);
+        if (enc->ivgen.hash)
+            virBufferAsprintf(buf, "ivgen-hash-alg=%s,", enc->ivgen.hash);
+    }
+}
diff --git a/src/util/virqemu.h b/src/util/virqemu.h
index af4ec1d..dfb79b9 100644
--- a/src/util/virqemu.h
+++ b/src/util/virqemu.h
@@ -26,9 +26,15 @@
 
 # include "internal.h"
 # include "virjson.h"
+# include "virstorageencryption.h"
 
 char *virQEMUBuildObjectCommandlineFromJSON(const char *type,
                                             const char *alias,
                                             virJSONValuePtr props);
 
+void virQEMUBuildLuksOpts(virBufferPtr buf,
+                          virStorageEncryptionPtr 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.5.5




More information about the libvir-list mailing list