qemu-block
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] block/rbd: Add support for rbd image encryption


From: Or Ozeri
Subject: [PATCH] block/rbd: Add support for rbd image encryption
Date: Sun, 2 May 2021 10:36:17 +0300

Starting from ceph Pacific, RBD has built-in support for image-level encryption.
Currently supported formats are LUKS version 1 and 2.

There are 2 new relevant librbd APIs for controlling encryption, both expect an
open image context:

rbd_encryption_format: formats an image (i.e. writes the LUKS header)
rbd_encryption_load: loads encryptor/decryptor to the image IO stack

This commit extends the qemu rbd driver API to support the above.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
---
 block/rbd.c          | 230 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json |  61 ++++++++++++
 2 files changed, 287 insertions(+), 4 deletions(-)

diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..1239e97889 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -108,6 +108,13 @@ typedef struct BDRVRBDState {
     uint64_t image_size;
 } BDRVRBDState;
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+typedef int (*RbdEncryptionFunc)(rbd_image_t image,
+                                 rbd_encryption_format_t format,
+                                 rbd_encryption_options_t opts,
+                                 size_t opts_size);
+#endif
+
 static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
                             BlockdevOptionsRbd *opts, bool cache,
                             const char *keypairs, const char *secretid,
@@ -341,6 +348,115 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char** passphrase,
+        Error **errp)
+{
+    int r = 0;
+
+    if (!luks_opts->has_passphrase_secret) {
+        r = -EINVAL;
+        error_setg_errno(errp, -r, "missing encrypt.passphrase-secret");
+        return r;
+    }
+
+    *passphrase = qcrypto_secret_lookup_as_utf8(luks_opts->passphrase_secret,
+                                                errp);
+    if (!*passphrase) {
+        return -EIO;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case RBD_ENCRYPTION_ALGORITHM_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case RBD_ENCRYPTION_ALGORITHM_AES_256: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
+                break;
+            }
+            default: {
+                r = -ENOTSUP;
+                error_setg_errno(errp, -r, "unknown encryption algorithm: %u",
+                                 luks_opts->cipher_alg);
+                return r;
+            }
+        }
+    } else {
+        /* default alg */
+        *alg = RBD_ENCRYPTION_ALGORITHM_AES256;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_apply_encryption_function(rbd_image_t image,
+                                              RbdEncryptionSpec* spec,
+                                              RbdEncryptionFunc func,
+                                              Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    g_autofree rbd_encryption_options_t opts = NULL;
+    rbd_encryption_format_t format;
+    size_t opts_size;
+
+    switch (spec->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS1: {
+            rbd_encryption_luks1_format_options_t *luks1_opts =
+                    g_new0(rbd_encryption_luks1_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = luks1_opts;
+            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS1_base(&spec->u.luks1),
+                    &luks1_opts->alg, &passphrase, errp);
+            if (passphrase) {
+                luks1_opts->passphrase = passphrase;
+                luks1_opts->passphrase_size = strlen(passphrase);
+            }
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            rbd_encryption_luks2_format_options_t *luks2_opts =
+                    g_new0(rbd_encryption_luks2_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = luks2_opts;
+            opts_size = sizeof(rbd_encryption_luks2_format_options_t);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS2_base(&spec->u.luks2),
+                    &luks2_opts->alg, &passphrase, errp);
+            if (passphrase) {
+                luks2_opts->passphrase = passphrase;
+                luks2_opts->passphrase_size = strlen(passphrase);
+            }
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    spec->format);
+        }
+    }
+
+    if (r < 0) {
+        return r;
+    }
+
+    r = func(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "error applying encryption function");
+    }
+
+    return r;
+}
+#endif
+
 /* FIXME Deprecate and remove keypairs or make it available in QMP. */
 static int qemu_rbd_do_create(BlockdevCreateOptions *options,
                               const char *keypairs, const char 
*password_secret,
@@ -358,6 +474,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions 
*options,
         return -EINVAL;
     }
 
+#ifndef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->location->has_encrypt) {
+        error_setg(errp, "RBD library does not support image encryption");
+        return -ENOTSUP;
+    }
+#endif
+
     if (opts->has_cluster_size) {
         int64_t objsize = opts->cluster_size;
         if ((objsize - 1) & objsize) {    /* not a power of 2? */
@@ -383,6 +506,30 @@ static int qemu_rbd_do_create(BlockdevCreateOptions 
*options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->location->has_encrypt) {
+        rbd_image_t image;
+
+        ret = rbd_open(io_ctx, opts->location->image, &image, NULL);
+        if (ret < 0) {
+            error_setg_errno(errp, -ret, "error reading header from %s",
+                             opts->location->image);
+            goto out;
+        }
+
+        ret = qemu_rbd_apply_encryption_function(image,
+                                                 opts->location->encrypt,
+                                                 &rbd_encryption_format,
+                                                 errp);
+        rbd_close(image);
+        if (ret < 0) {
+            /* encryption format fail, try removing the image */
+            rbd_remove(io_ctx, opts->location->image);
+            goto out;
+        }
+    }
+#endif
+
     ret = 0;
 out:
     rados_ioctx_destroy(io_ctx);
@@ -395,6 +542,42 @@ static int qemu_rbd_co_create(BlockdevCreateOptions 
*options, Error **errp)
     return qemu_rbd_do_create(options, NULL, NULL, errp);
 }
 
+static int qemu_rbd_extract_encryption_spec(QemuOpts *opts,
+                                            RbdEncryptionSpec **spec,
+                                            Error **errp)
+{
+    QDict *opts_qdict;
+    QDict *encrypt_qdict;
+    Visitor *v;
+    int ret = 0;
+
+    opts_qdict = qemu_opts_to_qdict(opts, NULL);
+    qdict_extract_subqdict(opts_qdict, &encrypt_qdict, "encrypt.");
+    qobject_unref(opts_qdict);
+    if (!qdict_size(encrypt_qdict)) {
+        *spec = NULL;
+        goto exit;
+    }
+
+    /* Convert options into a QAPI object */
+    v = qobject_input_visitor_new_flat_confused(encrypt_qdict, errp);
+    if (!v) {
+        ret = -EINVAL;
+        goto exit;
+    }
+
+    visit_type_RbdEncryptionSpec(v, NULL, spec, errp);
+    visit_free(v);
+    if (!*spec) {
+        ret = -EINVAL;
+        goto exit;
+    }
+
+exit:
+    qobject_unref(encrypt_qdict);
+    return ret;
+}
+
 static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
                                                 const char *filename,
                                                 QemuOpts *opts,
@@ -403,6 +586,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver 
*drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionSpec *encryption_spec = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +615,11 @@ static int coroutine_fn 
qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_spec(opts, &encryption_spec, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -446,6 +635,8 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver 
*drv,
     loc->q_namespace = g_strdup(qdict_get_try_str(options, "namespace"));
     loc->has_q_namespace = !!loc->q_namespace;
     loc->image       = g_strdup(qdict_get_try_str(options, "image"));
+    loc->encrypt     = encryption_spec;
+    loc->has_encrypt = !!encryption_spec;
     keypairs         = qdict_get_try_str(options, "=keyvalue-pairs");
 
     ret = qemu_rbd_do_create(create_options, keypairs, password_secret, errp);
@@ -756,12 +947,26 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict 
*options, int flags,
         goto failed_open;
     }
 
+    if (opts->has_encrypt) {
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+        r = qemu_rbd_apply_encryption_function(s->image, opts->encrypt,
+                                               &rbd_encryption_load, errp);
+        if (r < 0) {
+            goto failed_post_open;
+        }
+#else
+        r = -ENOTSUP;
+        error_setg_errno(errp, -r,
+                         "RBD library does not support image encryption");
+        goto failed_post_open;
+#endif
+    }
+
     r = rbd_get_size(s->image, &s->image_size);
     if (r < 0) {
         error_setg_errno(errp, -r, "error getting image size from %s",
                          s->image_name);
-        rbd_close(s->image);
-        goto failed_open;
+        goto failed_post_open;
     }
 
     /* If we are using an rbd snapshot, we must be r/o, otherwise
@@ -769,8 +974,7 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict 
*options, int flags,
     if (s->snap != NULL) {
         r = bdrv_apply_auto_read_only(bs, "rbd snapshots are read-only", errp);
         if (r < 0) {
-            rbd_close(s->image);
-            goto failed_open;
+            goto failed_post_open;
         }
     }
 
@@ -780,6 +984,8 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict 
*options, int flags,
     r = 0;
     goto out;
 
+failed_post_open:
+    rbd_close(s->image);
 failed_open:
     rados_ioctx_destroy(s->io_ctx);
     g_free(s->snap);
@@ -1243,6 +1449,22 @@ static QemuOptsList qemu_rbd_create_opts = {
             .type = QEMU_OPT_STRING,
             .help = "ID of secret providing the password",
         },
+        {
+            .name = "encrypt.format",
+            .type = QEMU_OPT_STRING,
+            .help = "Encrypt the image, format choices: 'luks1', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.passphrase-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..a1f3230e29 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -3609,6 +3609,64 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks1', 'luks2' ] }
+
+##
+# @RbdEncryptionAlgorithm:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdEncryptionAlgorithm',
+  'data': [ 'aes-128', 'aes-256' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+# @passphrase-secret: ID of a QCryptoSecret object providing a passphrase
+#                     for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'RbdEncryptionAlgorithm',
+            '*passphrase-secret': 'str' }}
+
+##
+# @RbdEncryptionOptionsLUKS1:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS1',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionSpec:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionSpec',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks1': 'RbdEncryptionOptionsLUKS1',
+            'luks2': 'RbdEncryptionOptionsLUKS2'} }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3682,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption specification. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3706,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionSpec',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
-- 
2.27.0




reply via email to

[Prev in Thread] Current Thread [Next in Thread]