[libvirt] [PATCH v2 11/15] storage: Add support to create a luks volume

John Ferlan jferlan at redhat.com
Thu Jun 23 17:29:07 UTC 2016


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 in the storage driver state dir path
   1a. the file name will include the pool name, the volume name, secret,
       and XXXXXX for usage in the mkostemp call.
   1b. mkostemp 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 an alias combinding the volume name and "_luks0"
   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  | 260 ++++++++++++++++++++++++++++++++++++++---
 src/storage/storage_backend.h  |   3 +-
 src/util/virqemu.c             |  23 ++++
 src/util/virqemu.h             |   6 +
 tests/storagevolxml2argvtest.c |   3 +-
 6 files changed, 275 insertions(+), 21 deletions(-)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index b118d1e..9160b22 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2163,6 +2163,7 @@ virProcessWait;
 
 
 # util/virqemu.h
+virQEMUBuildLuksOpts;
 virQEMUBuildObjectCommandlineFromJSON;
 
 
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c
index 89d5962..8918f3e 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,43 @@ 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)
+{
+    char *str = NULL;
+    virJSONValuePtr props = NULL;
+    char *commandStr = NULL;
+
+    if (virAsprintf(&info->secretAlias, "%s_luks0", vol->name) < 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 +1252,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 +1265,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 +1297,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 +1324,6 @@ virStorageBackendCreateQemuImgCmdFromVol(virConnectPtr conn,
                                                       conn, vol) < 0)
         return NULL;
 
-
     /* Size in KB */
     info.size_arg = VIR_DIV_UP(vol->target.capacity, 1024);
 
@@ -1226,11 +1342,21 @@ 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);
     virCommandAddArg(cmd, info.path);
@@ -1240,6 +1366,84 @@ 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;
+    }
+
+    /* Since we don't have a file, just go to cleanup using NULL secretPath */
+    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_PASSPHRASE,
+                                 &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;
+        }
+    }
+
+    if (chmod(secretPath, 0400) < 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 +1455,7 @@ virStorageBackendCreateQemuImg(virConnectPtr conn,
     char *create_tool;
     int imgformat;
     virCommandPtr cmd;
+    char *secretPath = NULL;
 
     virCheckFlags(VIR_STORAGE_VOL_CREATE_PREALLOC_METADATA, -1);
 
@@ -1266,8 +1471,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 +1493,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