All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-06-21 14:48 Or Ozeri
  2021-06-26 10:44 ` Ilya Dryomov
  0 siblings, 1 reply; 25+ messages in thread
From: Or Ozeri @ 2021-06-21 14:48 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, berrange, qemu-block, dannyh, oro, idryomov, to.my.trociny

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          | 367 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json | 110 ++++++++++++-
 2 files changed, 471 insertions(+), 6 deletions(-)

diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..7e282a8e94 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -73,6 +73,18 @@
 #define LIBRBD_USE_IOVEC 0
 #endif
 
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
+
 typedef enum {
     RBD_AIO_READ,
     RBD_AIO_WRITE,
@@ -341,6 +353,202 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    return qcrypto_secret_lookup(
+            luks_opts->key_secret, (uint8_t **)passphrase, passphrase_len, errp);
+}
+
+static int qemu_rbd_convert_luks_create_options(
+        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    int r = 0;
+
+    r = qemu_rbd_convert_luks_options(
+            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+            passphrase, passphrase_len, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case QCRYPTO_CIPHER_ALG_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
+                                      RbdEncryptionCreateOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    size_t opts_size;
+    uint64_t raw_size, effective_size;
+
+    r = rbd_get_size(image, &raw_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return r;
+    }
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+                    &luks_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
+                            &encrypt->u.luks2),
+                    &luks2_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_format(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption format fail");
+        return r;
+    }
+
+    r = rbd_get_size(image, &effective_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get effective image size");
+        return r;
+    }
+
+    r = rbd_resize(image, raw_size + (raw_size - effective_size));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot resize image after format");
+        return r;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+                                      RbdEncryptionOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    size_t opts_size;
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_load(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption load fail");
+    }
+
+    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 +566,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         return -EINVAL;
     }
 
+#ifndef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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 +598,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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_encryption_format(image, opts->encrypt, 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 +631,43 @@ 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_create_options(
+        QemuOpts *opts,
+        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +676,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionCreateOptions *encrypt = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +705,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+    rbd_opts->encrypt     = encrypt;
+    rbd_opts->has_encrypt = !!encrypt;
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -756,12 +1037,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1063,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 +1073,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);
@@ -1050,6 +1345,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
     return 0;
 }
 
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+                                                     Error **errp)
+{
+    BDRVRBDState *s = bs->opaque;
+    ImageInfoSpecific *spec_info;
+    uint64_t raw_size;
+    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
+    int r;
+
+    r = rbd_get_size(s->image, &raw_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return NULL;
+    }
+
+    if (raw_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+        r = rbd_read(s->image, 0,
+                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
+        if (r < 0) {
+            error_setg_errno(errp, -r, "cannot read image start for probe");
+            return NULL;
+        }
+    }
+
+    spec_info = g_new(ImageInfoSpecific, 1);
+    *spec_info = (ImageInfoSpecific){
+        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+    };
+
+    if (memcmp(buf, rbd_luks_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else if (memcmp(buf, rbd_luks2_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else {
+        spec_info->u.rbd.data->has_encryption_format = false;
+    }
+
+    return spec_info;
+}
+
 static int64_t qemu_rbd_getlength(BlockDriverState *bs)
 {
     BDRVRBDState *s = bs->opaque;
@@ -1243,6 +1585,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: 'luks', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.key-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
@@ -1272,6 +1630,7 @@ static BlockDriver bdrv_rbd = {
     .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
     .bdrv_has_zero_init     = bdrv_has_zero_init_1,
     .bdrv_get_info          = qemu_rbd_getinfo,
+    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
     .create_opts            = &qemu_rbd_create_opts,
     .bdrv_getlength         = qemu_rbd_getlength,
     .bdrv_co_truncate       = qemu_rbd_co_truncate,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..67cfd8c092 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -127,6 +127,18 @@
       'extents': ['ImageInfo']
   } }
 
+##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+  'data': {
+      '*encryption-format': 'RbdImageEncryptionFormat'
+  } }
+
 ##
 # @ImageInfoSpecific:
 #
@@ -141,7 +153,8 @@
       # If we need to add block driver specific parameters for
       # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
       # to define a ImageInfoSpecificLUKS
-      'luks': 'QCryptoBlockInfoLUKS'
+      'luks': 'QCryptoBlockInfoLUKS',
+      'rbd': 'ImageInfoSpecificRbd'
   } }
 
 ##
@@ -3609,6 +3622,94 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+#              for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { 'key-secret': 'str' }}
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+            'luks2': 'RbdEncryptionOptionsLUKS2'} }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+            'luks2': 'RbdEncryptionCreateOptionsLUKS2'} }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3725,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption options. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3749,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionOptions',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
@@ -4418,13 +4522,15 @@
 #            point to a snapshot.
 # @size: Size of the virtual disk in bytes
 # @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
 #
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsRbd',
   'data': { 'location':         'BlockdevOptionsRbd',
             'size':             'size',
-            '*cluster-size' :   'size' } }
+            '*cluster-size' :   'size',
+            '*encrypt' :        'RbdEncryptionCreateOptions'} }
 
 ##
 # @BlockdevVmdkSubformat:
-- 
2.27.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 14:48 [PATCH] block/rbd: Add support for rbd image encryption Or Ozeri
@ 2021-06-26 10:44 ` Ilya Dryomov
  0 siblings, 0 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-26 10:44 UTC (permalink / raw)
  To: Or Ozeri
  Cc: kwolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Mykola Golub, Danny Harnik

On Mon, Jun 21, 2021 at 4:49 PM Or Ozeri <oro@il.ibm.com> wrote:
>
> 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          | 367 ++++++++++++++++++++++++++++++++++++++++++-
>  qapi/block-core.json | 110 ++++++++++++-
>  2 files changed, 471 insertions(+), 6 deletions(-)
>
> diff --git a/block/rbd.c b/block/rbd.c
> index f098a89c7b..7e282a8e94 100644
> --- a/block/rbd.c
> +++ b/block/rbd.c
> @@ -73,6 +73,18 @@
>  #define LIBRBD_USE_IOVEC 0
>  #endif
>
> +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> +
> +static const char rbd_luks_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> +};
> +
> +static const char rbd_luks2_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> +};
> +
>  typedef enum {
>      RBD_AIO_READ,
>      RBD_AIO_WRITE,
> @@ -341,6 +353,202 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
>      }
>  }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +static int qemu_rbd_convert_luks_options(
> +        RbdEncryptionOptionsLUKSBase *luks_opts,
> +        char **passphrase,
> +        size_t *passphrase_len,
> +        Error **errp)
> +{
> +    return qcrypto_secret_lookup(
> +            luks_opts->key_secret, (uint8_t **)passphrase, passphrase_len, errp);

Hi Or,

This looks good to me, just a few nits to consider:

Why wrap after the paren?  The second line ends up going over 80
columns.  I'd wrap between passphrase and passphrase_len.

> +}
> +
> +static int qemu_rbd_convert_luks_create_options(
> +        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
> +        rbd_encryption_algorithm_t *alg,
> +        char **passphrase,
> +        size_t *passphrase_len,
> +        Error **errp)
> +{
> +    int r = 0;
> +
> +    r = qemu_rbd_convert_luks_options(
> +            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
> +            passphrase, passphrase_len, errp);
> +    if (r < 0) {
> +        return r;
> +    }
> +
> +    if (luks_opts->has_cipher_alg) {
> +        switch (luks_opts->cipher_alg) {
> +            case QCRYPTO_CIPHER_ALG_AES_128: {
> +                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
> +                break;
> +            }
> +            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
> +                                      RbdEncryptionCreateOptions *encrypt,
> +                                      Error **errp)
> +{
> +    int r = 0;
> +    g_autofree char *passphrase = NULL;

Should the passphrase be zeroed before being freed to avoid it lying
around in memory?  A quick grep didn't turn up a memzero_explicit or
memset_s like pattern in other places though...

> +    size_t passphrase_len;
> +    rbd_encryption_format_t format;
> +    rbd_encryption_options_t opts;
> +    rbd_encryption_luks1_format_options_t luks_opts;
> +    rbd_encryption_luks2_format_options_t luks2_opts;
> +    size_t opts_size;
> +    uint64_t raw_size, effective_size;
> +
> +    r = rbd_get_size(image, &raw_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get raw image size");
> +        return r;
> +    }
> +
> +    switch (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            memset(&luks_opts, 0, sizeof(luks_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = &luks_opts;
> +            opts_size = sizeof(luks_opts);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
> +                    &luks_opts.alg, &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks_opts.passphrase = passphrase;
> +            luks_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> +            memset(&luks2_opts, 0, sizeof(luks2_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> +            opts = &luks2_opts;
> +            opts_size = sizeof(luks2_opts);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
> +                            &encrypt->u.luks2),
> +                    &luks2_opts.alg, &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks2_opts.passphrase = passphrase;
> +            luks2_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +            return r;
> +        }
> +    }
> +
> +    r = rbd_encryption_format(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption format fail");
> +        return r;
> +    }
> +
> +    r = rbd_get_size(image, &effective_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get effective image size");
> +        return r;
> +    }
> +
> +    r = rbd_resize(image, raw_size + (raw_size - effective_size));
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot resize image after format");
> +        return r;
> +    }
> +
> +    return 0;
> +}
> +
> +static int qemu_rbd_encryption_load(rbd_image_t image,
> +                                      RbdEncryptionOptions *encrypt,
> +                                      Error **errp)
> +{
> +    int r = 0;
> +    g_autofree char *passphrase = NULL;

Same here.

> +    size_t passphrase_len;
> +    rbd_encryption_luks1_format_options_t luks_opts;
> +    rbd_encryption_luks2_format_options_t luks2_opts;
> +    rbd_encryption_format_t format;
> +    rbd_encryption_options_t opts;
> +    size_t opts_size;
> +
> +    switch (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            memset(&luks_opts, 0, sizeof(luks_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = &luks_opts;
> +            opts_size = sizeof(luks_opts);
> +            r = qemu_rbd_convert_luks_options(
> +                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
> +                    &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks_opts.passphrase = passphrase;
> +            luks_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> +            memset(&luks2_opts, 0, sizeof(luks2_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> +            opts = &luks2_opts;
> +            opts_size = sizeof(luks2_opts);
> +            r = qemu_rbd_convert_luks_options(
> +                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
> +                    &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks2_opts.passphrase = passphrase;
> +            luks2_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +            return r;
> +        }
> +    }
> +
> +    r = rbd_encryption_load(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption load fail");
> +    }
> +
> +    return r;

Could be return 0 for clarity?

> +}
> +#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 +566,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          return -EINVAL;
>      }
>
> +#ifndef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->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 +598,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          goto out;
>      }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->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);

I know you copied this from qemu_rbd_open() but mentioning image name
seems redundant (and not done anywhere else in this patch).  How about
something like "error opening image for encryption format"?

> +            goto out;
> +        }
> +
> +        ret = qemu_rbd_encryption_format(image, opts->encrypt, 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 +631,43 @@ 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_create_options(
> +        QemuOpts *opts,
> +        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +676,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
>      BlockdevCreateOptions *create_options;
>      BlockdevCreateOptionsRbd *rbd_opts;
>      BlockdevOptionsRbd *loc;
> +    RbdEncryptionCreateOptions *encrypt = NULL;
>      Error *local_err = NULL;
>      const char *keypairs, *password_secret;
>      QDict *options = NULL;
> @@ -431,6 +705,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
>          goto exit;
>      }
>
> +    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
> +    if (ret < 0) {
> +        goto exit;
> +    }
> +    rbd_opts->encrypt     = encrypt;
> +    rbd_opts->has_encrypt = !!encrypt;
> +
>      /*
>       * Caution: while qdict_get_try_str() is fine, getting non-string
>       * types would require more care.  When @options come from -blockdev
> @@ -756,12 +1037,25 @@ 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_encryption_load(s->image, opts->encrypt, errp);
> +        if (r < 0) {
> +            goto failed_post_open;
> +        }
> +#else
> +        r = -ENOTSUP;
> +        error_setg_errno(errp, -r,
> +                         "RBD library does not support image encryption");

I think this should be error_setg() like in qemu_rbd_do_create().
"Operation not supported" error message is clearly redundant here.

> +        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 +1063,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 +1073,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);
> @@ -1050,6 +1345,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
>      return 0;
>  }
>
> +static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
> +                                                     Error **errp)
> +{
> +    BDRVRBDState *s = bs->opaque;
> +    ImageInfoSpecific *spec_info;
> +    uint64_t raw_size;
> +    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
> +    int r;
> +
> +    r = rbd_get_size(s->image, &raw_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get raw image size");

This error message is a bit misleading because ->bdrv_get_specific_info
can be invoked on an image with a loaded encryption profile.  I'd drop
"raw" (and probably rename raw_size to e.g. image_size while at it).

> +        return NULL;
> +    }
> +
> +    if (raw_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
> +        r = rbd_read(s->image, 0,
> +                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
> +        if (r < 0) {
> +            error_setg_errno(errp, -r, "cannot read image start for probe");
> +            return NULL;
> +        }
> +    }
> +
> +    spec_info = g_new(ImageInfoSpecific, 1);
> +    *spec_info = (ImageInfoSpecific){
> +        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
> +        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
> +    };
> +
> +    if (memcmp(buf, rbd_luks_header_verification,
> +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> +        spec_info->u.rbd.data->encryption_format =
> +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
> +        spec_info->u.rbd.data->has_encryption_format = true;
> +    } else if (memcmp(buf, rbd_luks2_header_verification,
> +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> +        spec_info->u.rbd.data->encryption_format =
> +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
> +        spec_info->u.rbd.data->has_encryption_format = true;
> +    } else {
> +        spec_info->u.rbd.data->has_encryption_format = false;
> +    }
> +
> +    return spec_info;
> +}
> +
>  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
>  {
>      BDRVRBDState *s = bs->opaque;
> @@ -1243,6 +1585,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: 'luks', 'luks2'",
> +        },
> +        {
> +            .name = "encrypt.cipher-alg",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Name of encryption cipher algorithm"
> +                    " (allowed values: aes-128, aes-256)",
> +        },
> +        {
> +            .name = "encrypt.key-secret",
> +            .type = QEMU_OPT_STRING,
> +            .help = "ID of secret providing LUKS passphrase",
> +        },
>          { /* end of list */ }
>      }
>  };
> @@ -1272,6 +1630,7 @@ static BlockDriver bdrv_rbd = {
>      .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
>      .bdrv_has_zero_init     = bdrv_has_zero_init_1,
>      .bdrv_get_info          = qemu_rbd_getinfo,
> +    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
>      .create_opts            = &qemu_rbd_create_opts,
>      .bdrv_getlength         = qemu_rbd_getlength,
>      .bdrv_co_truncate       = qemu_rbd_co_truncate,
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 6d227924d0..67cfd8c092 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -127,6 +127,18 @@
>        'extents': ['ImageInfo']
>    } }
>
> +##
> +# @ImageInfoSpecificRbd:
> +#
> +# @encryption-format: Image encryption format
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'ImageInfoSpecificRbd',
> +  'data': {
> +      '*encryption-format': 'RbdImageEncryptionFormat'
> +  } }
> +
>  ##
>  # @ImageInfoSpecific:
>  #
> @@ -141,7 +153,8 @@
>        # If we need to add block driver specific parameters for
>        # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
>        # to define a ImageInfoSpecificLUKS
> -      'luks': 'QCryptoBlockInfoLUKS'
> +      'luks': 'QCryptoBlockInfoLUKS',
> +      'rbd': 'ImageInfoSpecificRbd'
>    } }
>
>  ##
> @@ -3609,6 +3622,94 @@
>  { 'enum': 'RbdAuthMode',
>    'data': [ 'cephx', 'none' ] }
>
> +##
> +# @RbdImageEncryptionFormat:
> +#
> +# Since: 6.1
> +##
> +{ 'enum': 'RbdImageEncryptionFormat',
> +  'data': [ 'luks', 'luks2' ] }
> +
> +##
> +# @RbdEncryptionOptionsLUKSBase:
> +#
> +# @key-secret: ID of a QCryptoSecret object providing a passphrase
> +#              for unlocking the encryption
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKSBase',
> +  'data': { 'key-secret': 'str' }}

'data': { 'key-secret': 'str' } }

> +
> +##
> +# @RbdEncryptionCreateOptionsLUKSBase:
> +#
> +# @cipher-alg: The encryption algorithm
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}

'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } }

> +
> +##
> +# @RbdEncryptionOptionsLUKS:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKS',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': {}}

'data': { } }

here and below.

> +
> +##
> +# @RbdEncryptionOptionsLUKS2:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKS2',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionCreateOptionsLUKS:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
> +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionCreateOptionsLUKS2:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
> +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionOptions:
> +#
> +# Since: 6.1
> +##
> +{ 'union': 'RbdEncryptionOptions',
> +  'base': { 'format': 'RbdImageEncryptionFormat' },
> +  'discriminator': 'format',
> +  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
> +            'luks2': 'RbdEncryptionOptionsLUKS2'} }

'luks2': 'RbdEncryptionOptionsLUKS2' } }

> +
> +##
> +# @RbdEncryptionCreateOptions:
> +#
> +# Since: 6.1
> +##
> +{ 'union': 'RbdEncryptionCreateOptions',
> +  'base': { 'format': 'RbdImageEncryptionFormat' },
> +  'discriminator': 'format',
> +  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
> +            'luks2': 'RbdEncryptionCreateOptionsLUKS2'} }

'luks2': 'RbdEncryptionCreateOptionsLUKS2' } }

> +
>  ##
>  # @BlockdevOptionsRbd:
>  #
> @@ -3624,6 +3725,8 @@
>  #
>  # @snapshot: Ceph snapshot name.
>  #
> +# @encrypt: Image encryption options. (Since 6.1)
> +#
>  # @user: Ceph id name.
>  #
>  # @auth-client-required: Acceptable authentication modes.
> @@ -3646,6 +3749,7 @@
>              'image': 'str',
>              '*conf': 'str',
>              '*snapshot': 'str',
> +            '*encrypt': 'RbdEncryptionOptions',
>              '*user': 'str',
>              '*auth-client-required': ['RbdAuthMode'],
>              '*key-secret': 'str',
> @@ -4418,13 +4522,15 @@
>  #            point to a snapshot.
>  # @size: Size of the virtual disk in bytes
>  # @cluster-size: RBD object size
> +# @encrypt: Image encryption options. (Since 6.1)
>  #
>  # Since: 2.12
>  ##
>  { 'struct': 'BlockdevCreateOptionsRbd',
>    'data': { 'location':         'BlockdevOptionsRbd',
>              'size':             'size',
> -            '*cluster-size' :   'size' } }
> +            '*cluster-size' :   'size',
> +            '*encrypt' :        'RbdEncryptionCreateOptions'} }

'*encrypt' :        'RbdEncryptionCreateOptions' } }

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-27 11:09   ` Or Ozeri
@ 2021-06-27 11:24     ` Ilya Dryomov
  0 siblings, 0 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-27 11:24 UTC (permalink / raw)
  To: Or Ozeri
  Cc: kwolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Mykola Golub, Danny Harnik

On Sun, Jun 27, 2021 at 1:09 PM Or Ozeri <ORO@il.ibm.com> wrote:
>
> Should I still leave the encryption format part of the state, and just not report it? or should I also remove it from the state?

I'd remove it, reverting to the previous version of the patch
except instead of fetching the size with rbd_get_size() and storing
it in a local variable keep using s->image_size.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-27  8:44 ` Or Ozeri
  2021-06-27 11:00   ` Ilya Dryomov
@ 2021-06-27 11:09   ` Or Ozeri
  2021-06-27 11:24     ` Ilya Dryomov
  1 sibling, 1 reply; 25+ messages in thread
From: Or Ozeri @ 2021-06-27 11:09 UTC (permalink / raw)
  To: Ilya Dryomov
  Cc: kwolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Mykola Golub, Danny Harnik

[-- Attachment #1: Type: text/html, Size: 22245 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-27  8:44 ` Or Ozeri
@ 2021-06-27 11:00   ` Ilya Dryomov
  2021-06-27 11:09   ` Or Ozeri
  1 sibling, 0 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-27 11:00 UTC (permalink / raw)
  To: Or Ozeri
  Cc: kwolf, Daniel P. Berrangé,
	qemu-block, qemu-devel, Mykola Golub, Danny Harnik

On Sun, Jun 27, 2021 at 10:44 AM Or Ozeri <ORO@il.ibm.com> wrote:
>
> Ilya,
>
> I fixed according to your suggestions, except for the passphrase zeroing.
> Looks like it's not a one-liner, but rather a long list of ifdefs to try and cover all possible platforms/compilers (this is what I've seen they do in k5-int.h).
> I didn't want to copy this into rbd.c.
> I guess that the right solution would be adding a qemu utility function (not sure where exactly), but anyways perhaps this, like the changes I previously made to raw_format.c, should be made in different patch.

Hi Or,

Yes, given that there doesn't seem to be an existing straightforward
API for it, I don't think it is a blocker.  Just something to keep in
mind.

You also implemented one change that I didn't suggest -- storing
the encryption status in BDRVRBDState.  BTW it is a good practice
to include the version in the subject (e.g. [PATCH v3] ...) and
a per-version changelog in the description.

The way the encryption format is reported in this patch actually begs
more questions because now I think we need to differentiate between an
encrypted image for which the encryption profile has been loaded and
one for which it hasn't been loaded and probably also report the raw
size...

The previous approach of reporting the encryption format only for "raw"
images was a good starting point IMO.  I'd keep the bit that switches
from rbd_get_size() to s->image_size and drop everything else for now.

>
> Thanks,
> Or
>
> -----"Or Ozeri" <oro@il.ibm.com> wrote: -----
> To: qemu-devel@nongnu.org
> From: "Or Ozeri" <oro@il.ibm.com>
> Date: 06/27/2021 11:31AM
> Cc: qemu-block@nongnu.org, oro@il.ibm.com, kwolf@redhat.com, to.my.trociny@gmail.com, dannyh@il.ibm.com, berrange@redhat.com, idryomov@gmail.com
> Subject: [PATCH] block/rbd: Add support for rbd image encryption
>
> 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          | 380 ++++++++++++++++++++++++++++++++++++++++++-
>  qapi/block-core.json | 110 ++++++++++++-
>  2 files changed, 484 insertions(+), 6 deletions(-)
>
> diff --git a/block/rbd.c b/block/rbd.c
> index f098a89c7b..dadecaf3da 100644
> --- a/block/rbd.c
> +++ b/block/rbd.c
> @@ -73,6 +73,18 @@
>  #define LIBRBD_USE_IOVEC 0
>  #endif
>
> +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> +
> +static const char rbd_luks_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> +};
> +
> +static const char rbd_luks2_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> +};
> +
>  typedef enum {
>      RBD_AIO_READ,
>      RBD_AIO_WRITE,
> @@ -106,6 +118,7 @@ typedef struct BDRVRBDState {
>      char *snap;
>      char *namespace;
>      uint64_t image_size;
> +    ImageInfoSpecificRbd image_info;
>  } BDRVRBDState;
>
>  static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
> @@ -341,6 +354,207 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
>      }
>  }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +static int qemu_rbd_convert_luks_options(
> +        RbdEncryptionOptionsLUKSBase *luks_opts,
> +        char **passphrase,
> +        size_t *passphrase_len,
> +        Error **errp)
> +{
> +    return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
> +                                 passphrase_len, errp);
> +}
> +
> +static int qemu_rbd_convert_luks_create_options(
> +        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
> +        rbd_encryption_algorithm_t *alg,
> +        char **passphrase,
> +        size_t *passphrase_len,
> +        Error **errp)
> +{
> +    int r = 0;
> +
> +    r = qemu_rbd_convert_luks_options(
> +            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
> +            passphrase, passphrase_len, errp);
> +    if (r < 0) {
> +        return r;
> +    }
> +
> +    if (luks_opts->has_cipher_alg) {
> +        switch (luks_opts->cipher_alg) {
> +            case QCRYPTO_CIPHER_ALG_AES_128: {
> +                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
> +                break;
> +            }
> +            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
> +                                      RbdEncryptionCreateOptions *encrypt,
> +                                      Error **errp)
> +{
> +    int r = 0;
> +    g_autofree char *passphrase = NULL;
> +    size_t passphrase_len;
> +    rbd_encryption_format_t format;
> +    rbd_encryption_options_t opts;
> +    rbd_encryption_luks1_format_options_t luks_opts;
> +    rbd_encryption_luks2_format_options_t luks2_opts;
> +    size_t opts_size;
> +    uint64_t raw_size, effective_size;
> +
> +    r = rbd_get_size(image, &raw_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get raw image size");
> +        return r;
> +    }
> +
> +    switch (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            memset(&luks_opts, 0, sizeof(luks_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = &luks_opts;
> +            opts_size = sizeof(luks_opts);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
> +                    &luks_opts.alg, &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks_opts.passphrase = passphrase;
> +            luks_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> +            memset(&luks2_opts, 0, sizeof(luks2_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> +            opts = &luks2_opts;
> +            opts_size = sizeof(luks2_opts);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
> +                            &encrypt->u.luks2),
> +                    &luks2_opts.alg, &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks2_opts.passphrase = passphrase;
> +            luks2_opts.passphrase_size = passphrase_len;
> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +            return r;
> +        }
> +    }
> +
> +    r = rbd_encryption_format(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption format fail");
> +        return r;
> +    }
> +
> +    r = rbd_get_size(image, &effective_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get effective image size");
> +        return r;
> +    }
> +
> +    r = rbd_resize(image, raw_size + (raw_size - effective_size));
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot resize image after format");
> +        return r;
> +    }
> +
> +    return 0;
> +}
> +
> +static int qemu_rbd_encryption_load(rbd_image_t image,
> +                                    RbdEncryptionOptions *encrypt,
> +                                    ImageInfoSpecificRbd *image_info,
> +                                    Error **errp)
> +{
> +    int r = 0;
> +    g_autofree char *passphrase = NULL;
> +    size_t passphrase_len;
> +    rbd_encryption_luks1_format_options_t luks_opts;
> +    rbd_encryption_luks2_format_options_t luks2_opts;
> +    rbd_encryption_format_t format;
> +    rbd_encryption_options_t opts;
> +    size_t opts_size;
> +
> +    switch (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            memset(&luks_opts, 0, sizeof(luks_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = &luks_opts;
> +            opts_size = sizeof(luks_opts);
> +            r = qemu_rbd_convert_luks_options(
> +                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
> +                    &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks_opts.passphrase = passphrase;
> +            luks_opts.passphrase_size = passphrase_len;
> +            image_info->encryption_format = RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
> +            break;
> +        }
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> +            memset(&luks2_opts, 0, sizeof(luks2_opts));
> +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> +            opts = &luks2_opts;
> +            opts_size = sizeof(luks2_opts);
> +            r = qemu_rbd_convert_luks_options(
> +                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
> +                    &passphrase, &passphrase_len, errp);
> +            if (r < 0) {
> +                return r;
> +            }
> +            luks2_opts.passphrase = passphrase;
> +            luks2_opts.passphrase_size = passphrase_len;
> +            image_info->encryption_format = RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +            return r;
> +        }
> +    }
> +
> +    r = rbd_encryption_load(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption load fail");
> +        return r;
> +    }
> +
> +    image_info->has_encryption_format = true;
> +    return 0;
> +}
> +#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 +572,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          return -EINVAL;
>      }
>
> +#ifndef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->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 +604,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          goto out;
>      }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->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 opening image '%s' for encryption format",
> +                             opts->location->image);

No need to include image name here.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-27  8:30 Or Ozeri
@ 2021-06-27  8:44 ` Or Ozeri
  2021-06-27 11:00   ` Ilya Dryomov
  2021-06-27 11:09   ` Or Ozeri
  0 siblings, 2 replies; 25+ messages in thread
From: Or Ozeri @ 2021-06-27  8:44 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, berrange, qemu-block, Danny Harnik, Or Ozeri, idryomov,
	to.my.trociny

[-- Attachment #1: Type: text/html, Size: 35563 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-06-27  8:30 Or Ozeri
  2021-06-27  8:44 ` Or Ozeri
  0 siblings, 1 reply; 25+ messages in thread
From: Or Ozeri @ 2021-06-27  8:30 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, berrange, qemu-block, dannyh, oro, idryomov, to.my.trociny

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          | 380 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json | 110 ++++++++++++-
 2 files changed, 484 insertions(+), 6 deletions(-)

diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..dadecaf3da 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -73,6 +73,18 @@
 #define LIBRBD_USE_IOVEC 0
 #endif
 
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
+
 typedef enum {
     RBD_AIO_READ,
     RBD_AIO_WRITE,
@@ -106,6 +118,7 @@ typedef struct BDRVRBDState {
     char *snap;
     char *namespace;
     uint64_t image_size;
+    ImageInfoSpecificRbd image_info;
 } BDRVRBDState;
 
 static int qemu_rbd_connect(rados_t *cluster, rados_ioctx_t *io_ctx,
@@ -341,6 +354,207 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    return qcrypto_secret_lookup(luks_opts->key_secret, (uint8_t **)passphrase,
+                                 passphrase_len, errp);
+}
+
+static int qemu_rbd_convert_luks_create_options(
+        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    int r = 0;
+
+    r = qemu_rbd_convert_luks_options(
+            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+            passphrase, passphrase_len, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case QCRYPTO_CIPHER_ALG_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
+                                      RbdEncryptionCreateOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    size_t opts_size;
+    uint64_t raw_size, effective_size;
+
+    r = rbd_get_size(image, &raw_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return r;
+    }
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+                    &luks_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
+                            &encrypt->u.luks2),
+                    &luks2_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_format(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption format fail");
+        return r;
+    }
+
+    r = rbd_get_size(image, &effective_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get effective image size");
+        return r;
+    }
+
+    r = rbd_resize(image, raw_size + (raw_size - effective_size));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot resize image after format");
+        return r;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+                                    RbdEncryptionOptions *encrypt,
+                                    ImageInfoSpecificRbd *image_info,
+                                    Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    size_t opts_size;
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            image_info->encryption_format = RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            image_info->encryption_format = RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_load(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption load fail");
+        return r;
+    }
+
+    image_info->has_encryption_format = true;
+    return 0;
+}
+#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 +572,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         return -EINVAL;
     }
 
+#ifndef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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 +604,28 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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 opening image '%s' for encryption format",
+                             opts->location->image);
+            goto out;
+        }
+
+        ret = qemu_rbd_encryption_format(image, opts->encrypt, 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 +638,43 @@ 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_create_options(
+        QemuOpts *opts,
+        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +683,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionCreateOptions *encrypt = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +712,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+    rbd_opts->encrypt     = encrypt;
+    rbd_opts->has_encrypt = !!encrypt;
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -756,12 +1044,26 @@ static int qemu_rbd_open(BlockDriverState *bs, QDict *options, int flags,
         goto failed_open;
     }
 
+    s->image_info.has_encryption_format = false;
+    if (opts->has_encrypt) {
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+        r = qemu_rbd_encryption_load(s->image, opts->encrypt, &s->image_info,
+                                     errp);
+        if (r < 0) {
+            goto failed_post_open;
+        }
+#else
+        r = -ENOTSUP;
+        error_setg(errp, "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 +1071,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 +1081,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);
@@ -1050,6 +1353,58 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
     return 0;
 }
 
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+                                                     Error **errp)
+{
+    BDRVRBDState *s = bs->opaque;
+    ImageInfoSpecific *spec_info;
+    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
+    int r;
+
+    if (s->image_info.has_encryption_format) {
+        spec_info = g_new(ImageInfoSpecific, 1);
+        *spec_info = (ImageInfoSpecific){
+            .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+            .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+        };
+        spec_info->u.rbd.data->has_encryption_format = true;
+        spec_info->u.rbd.data->encryption_format =
+                s->image_info.encryption_format;
+        return spec_info;
+    }
+
+    if (s->image_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+        r = rbd_read(s->image, 0,
+                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
+        if (r < 0) {
+            error_setg_errno(errp, -r, "cannot read image start for probe");
+            return NULL;
+        }
+    }
+
+    spec_info = g_new(ImageInfoSpecific, 1);
+    *spec_info = (ImageInfoSpecific){
+        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+    };
+
+    if (memcmp(buf, rbd_luks_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else if (memcmp(buf, rbd_luks2_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else {
+        spec_info->u.rbd.data->has_encryption_format = false;
+    }
+
+    return spec_info;
+}
+
 static int64_t qemu_rbd_getlength(BlockDriverState *bs)
 {
     BDRVRBDState *s = bs->opaque;
@@ -1243,6 +1598,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: 'luks', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.key-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
@@ -1272,6 +1643,7 @@ static BlockDriver bdrv_rbd = {
     .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
     .bdrv_has_zero_init     = bdrv_has_zero_init_1,
     .bdrv_get_info          = qemu_rbd_getinfo,
+    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
     .create_opts            = &qemu_rbd_create_opts,
     .bdrv_getlength         = qemu_rbd_getlength,
     .bdrv_co_truncate       = qemu_rbd_co_truncate,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..6cf67d796e 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -127,6 +127,18 @@
       'extents': ['ImageInfo']
   } }
 
+##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+  'data': {
+      '*encryption-format': 'RbdImageEncryptionFormat'
+  } }
+
 ##
 # @ImageInfoSpecific:
 #
@@ -141,7 +153,8 @@
       # If we need to add block driver specific parameters for
       # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
       # to define a ImageInfoSpecificLUKS
-      'luks': 'QCryptoBlockInfoLUKS'
+      'luks': 'QCryptoBlockInfoLUKS',
+      'rbd': 'ImageInfoSpecificRbd'
   } }
 
 ##
@@ -3609,6 +3622,94 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+#              for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { 'key-secret': 'str' } }
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm' } }
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { } }
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { } }
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': { } }
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': { } }
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+            'luks2': 'RbdEncryptionOptionsLUKS2' } }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+            'luks2': 'RbdEncryptionCreateOptionsLUKS2' } }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3725,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption options. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3749,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionOptions',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
@@ -4418,13 +4522,15 @@
 #            point to a snapshot.
 # @size: Size of the virtual disk in bytes
 # @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
 #
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsRbd',
   'data': { 'location':         'BlockdevOptionsRbd',
             'size':             'size',
-            '*cluster-size' :   'size' } }
+            '*cluster-size' :   'size',
+            '*encrypt' :        'RbdEncryptionCreateOptions' } }
 
 ##
 # @BlockdevVmdkSubformat:
-- 
2.27.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 14:21 Or Ozeri
@ 2021-06-21 14:27 ` no-reply
  0 siblings, 0 replies; 25+ messages in thread
From: no-reply @ 2021-06-21 14:27 UTC (permalink / raw)
  To: oro
  Cc: kwolf, berrange, qemu-block, qemu-devel, to.my.trociny, oro,
	idryomov, dannyh

Patchew URL: https://patchew.org/QEMU/20210621142103.1417408-1-oro@il.ibm.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20210621142103.1417408-1-oro@il.ibm.com
Subject: [PATCH] block/rbd: Add support for rbd image encryption

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
 - [tag update]      patchew/20210614083800.1166166-1-richard.henderson@linaro.org -> patchew/20210614083800.1166166-1-richard.henderson@linaro.org
 * [new tag]         patchew/20210621142103.1417408-1-oro@il.ibm.com -> patchew/20210621142103.1417408-1-oro@il.ibm.com
Switched to a new branch 'test'
19faaee block/rbd: Add support for rbd image encryption

=== OUTPUT BEGIN ===
ERROR: "(foo**)" should be "(foo **)"
#60: FILE: block/rbd.c:374:
+            luks_opts->key_secret, (uint8_t**)passphrase, passphrase_len, errp);

total: 1 errors, 0 warnings, 601 lines checked

Commit 19faaee12b6d (block/rbd: Add support for rbd image encryption) has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20210621142103.1417408-1-oro@il.ibm.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-06-21 14:21 Or Ozeri
  2021-06-21 14:27 ` no-reply
  0 siblings, 1 reply; 25+ messages in thread
From: Or Ozeri @ 2021-06-21 14:21 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, berrange, qemu-block, dannyh, oro, idryomov, to.my.trociny

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          | 367 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json | 110 ++++++++++++-
 2 files changed, 471 insertions(+), 6 deletions(-)

diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..be1419a9bd 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -73,6 +73,18 @@
 #define LIBRBD_USE_IOVEC 0
 #endif
 
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
+
 typedef enum {
     RBD_AIO_READ,
     RBD_AIO_WRITE,
@@ -341,6 +353,202 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    return qcrypto_secret_lookup(
+            luks_opts->key_secret, (uint8_t**)passphrase, passphrase_len, errp);
+}
+
+static int qemu_rbd_convert_luks_create_options(
+        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char **passphrase,
+        size_t *passphrase_len,
+        Error **errp)
+{
+    int r = 0;
+
+    r = qemu_rbd_convert_luks_options(
+            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+            passphrase, passphrase_len, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case QCRYPTO_CIPHER_ALG_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
+                                      RbdEncryptionCreateOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    size_t opts_size;
+    uint64_t raw_size, effective_size;
+
+    r = rbd_get_size(image, &raw_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return r;
+    }
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+                    &luks_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
+                            &encrypt->u.luks2),
+                    &luks2_opts.alg, &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_format(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption format fail");
+        return r;
+    }
+
+    r = rbd_get_size(image, &effective_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get effective image size");
+        return r;
+    }
+
+    r = rbd_resize(image, raw_size + (raw_size - effective_size));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot resize image after format");
+        return r;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+                                      RbdEncryptionOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    size_t passphrase_len;
+    rbd_encryption_luks1_format_options_t luks_opts;
+    rbd_encryption_luks2_format_options_t luks2_opts;
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    size_t opts_size;
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            memset(&luks_opts, 0, sizeof(luks_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = &luks_opts;
+            opts_size = sizeof(luks_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks_opts.passphrase = passphrase;
+            luks_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
+            memset(&luks2_opts, 0, sizeof(luks2_opts));
+            format = RBD_ENCRYPTION_FORMAT_LUKS2;
+            opts = &luks2_opts;
+            opts_size = sizeof(luks2_opts);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS2_base(&encrypt->u.luks2),
+                    &passphrase, &passphrase_len, errp);
+            if (r < 0) {
+                return r;
+            }
+            luks2_opts.passphrase = passphrase;
+            luks2_opts.passphrase_size = passphrase_len;
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+            return r;
+        }
+    }
+
+    r = rbd_encryption_load(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption load fail");
+    }
+
+    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 +566,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         return -EINVAL;
     }
 
+#ifndef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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 +598,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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_encryption_format(image, opts->encrypt, 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 +631,43 @@ 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_create_options(
+        QemuOpts *opts,
+        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +676,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionCreateOptions *encrypt = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +705,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+    rbd_opts->encrypt     = encrypt;
+    rbd_opts->has_encrypt = !!encrypt;
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -756,12 +1037,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1063,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 +1073,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);
@@ -1050,6 +1345,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
     return 0;
 }
 
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+                                                     Error **errp)
+{
+    BDRVRBDState *s = bs->opaque;
+    ImageInfoSpecific *spec_info;
+    uint64_t raw_size;
+    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
+    int r;
+
+    r = rbd_get_size(s->image, &raw_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return NULL;
+    }
+
+    if (raw_size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+        r = rbd_read(s->image, 0,
+                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf);
+        if (r < 0) {
+            error_setg_errno(errp, -r, "cannot read image start for probe");
+            return NULL;
+        }
+    }
+
+    spec_info = g_new(ImageInfoSpecific, 1);
+    *spec_info = (ImageInfoSpecific){
+        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+    };
+
+    if (memcmp(buf, rbd_luks_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else if (memcmp(buf, rbd_luks2_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else {
+        spec_info->u.rbd.data->has_encryption_format = false;
+    }
+
+    return spec_info;
+}
+
 static int64_t qemu_rbd_getlength(BlockDriverState *bs)
 {
     BDRVRBDState *s = bs->opaque;
@@ -1243,6 +1585,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: 'luks', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.key-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
@@ -1272,6 +1630,7 @@ static BlockDriver bdrv_rbd = {
     .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
     .bdrv_has_zero_init     = bdrv_has_zero_init_1,
     .bdrv_get_info          = qemu_rbd_getinfo,
+    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
     .create_opts            = &qemu_rbd_create_opts,
     .bdrv_getlength         = qemu_rbd_getlength,
     .bdrv_co_truncate       = qemu_rbd_co_truncate,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..67cfd8c092 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -127,6 +127,18 @@
       'extents': ['ImageInfo']
   } }
 
+##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+  'data': {
+      '*encryption-format': 'RbdImageEncryptionFormat'
+  } }
+
 ##
 # @ImageInfoSpecific:
 #
@@ -141,7 +153,8 @@
       # If we need to add block driver specific parameters for
       # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
       # to define a ImageInfoSpecificLUKS
-      'luks': 'QCryptoBlockInfoLUKS'
+      'luks': 'QCryptoBlockInfoLUKS',
+      'rbd': 'ImageInfoSpecificRbd'
   } }
 
 ##
@@ -3609,6 +3622,94 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+#              for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { 'key-secret': 'str' }}
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+            'luks2': 'RbdEncryptionOptionsLUKS2'} }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+            'luks2': 'RbdEncryptionCreateOptionsLUKS2'} }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3725,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption options. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3749,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionOptions',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
@@ -4418,13 +4522,15 @@
 #            point to a snapshot.
 # @size: Size of the virtual disk in bytes
 # @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
 #
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsRbd',
   'data': { 'location':         'BlockdevOptionsRbd',
             'size':             'size',
-            '*cluster-size' :   'size' } }
+            '*cluster-size' :   'size',
+            '*encrypt' :        'RbdEncryptionCreateOptions'} }
 
 ##
 # @BlockdevVmdkSubformat:
-- 
2.27.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 11:27           ` Daniel P. Berrangé
@ 2021-06-21 11:52             ` Ilya Dryomov
  0 siblings, 0 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-21 11:52 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Mon, Jun 21, 2021 at 1:27 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> On Mon, Jun 21, 2021 at 01:23:46PM +0200, Ilya Dryomov wrote:
> > On Mon, Jun 21, 2021 at 1:04 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> > >
> > > On Mon, Jun 21, 2021 at 12:59:37PM +0200, Ilya Dryomov wrote:
> > > > On Mon, Jun 21, 2021 at 10:32 AM Daniel P. Berrangé <berrange@redhat.com> wrote:
> > > > >
> > > > > On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> > > > > > On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> > > > > > >
> > > > > > > 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/raw-format.c   |   7 +
> > > > > > >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> > > > > > >  qapi/block-core.json | 110 ++++++++++++-
> > > > > > >  3 files changed, 482 insertions(+), 6 deletions(-)
> > > > >
> > > > >
> > > > > > > diff --git a/block/rbd.c b/block/rbd.c
> > > > > > > index f098a89c7b..183b17cd84 100644
> > > > > > > --- a/block/rbd.c
> > > > > > > +++ b/block/rbd.c
> > > > > > > @@ -73,6 +73,18 @@
> > > > > > >  #define LIBRBD_USE_IOVEC 0
> > > > > > >  #endif
> > > > > > >
> > > > > > > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > > > > > > +
> > > > > > > +static const char rbd_luks_header_verification[
> > > > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > > > > > > +};
> > > > > > > +
> > > > > > > +static const char rbd_luks2_header_verification[
> > > > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > > > > > > +};
> > > > > > > +
> > > > > > >  typedef enum {
> > > > > > >      RBD_AIO_READ,
> > > > > > >      RBD_AIO_WRITE,
> > > > > > > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> > > > > > >      }
> > > > > > >  }
> > > > > > >
> > > > > > > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > > > > > > +static int qemu_rbd_convert_luks_options(
> > > > > > > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > > > > > > +        char **passphrase,
> > > > > > > +        Error **errp)
> > > > > > > +{
> > > > > > > +    int r = 0;
> > > > > > > +
> > > > > > > +    if (!luks_opts->has_key_secret) {
> > > > > > > +        r = -EINVAL;
> > > > > > > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > > > > > > +        return r;
> > > > > > > +    }
> > > > > >
> > > > > > Why is key-secret optional?
> > > > >
> > > > > It doesn't look like it is handled correctly here, but we need to
> > > > > be able to run 'qemu-img info <volume>' and get information back
> > > > > on the size of the image, and whether or not it is encrypted,
> > > > > without having to supply a passphrase upfront. So it is right that
> > > > > key-secret be optional, but also we shouldn't return an fatal
> > > > > error like this.
> > > >
> > > > Hi Daniel,
> > > >
> > > > The key-secret lives inside RbdEncryptionOptions (or
> > > > RbdEncryptionCreateOptions) which are already optional:
> > > >
> > > >     '*encrypt': 'RbdEncryptionOptions'
> > > >
> > > >     '*encrypt' :        'RbdEncryptionCreateOptions'
> > > >
> > > > The image is opened as usual and then, if "encrypt" is specified,
> > > > the encryption profile is loaded (or created and laid down).  It does
> > > > not make sense to attempt to load or create the encryption profile
> > > > without the passphrase -- it would always fail.
> > >
> > > Ah, that sounds like it is probably ok then.
> > >
> > >
> > > > > Only if BDRV_O_NO_IO is NOT set, should this error be reported
> > > > >
> > > > >
> > > > >
> > > > >
> > > > > > >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> > > > > > >  {
> > > > > > >      BDRVRBDState *s = bs->opaque;
> > > > > > > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> > > > > >
> > > > > > I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> > > > > > "rbd encryption format" command.
> > > > >
> > > > > No, it should stay "luks" not "luks1", to match the existing QEMU
> > > > > terminology for its LUKS v1 encryption support.
> > > >
> > > > If you insist on following the QEMU nomenclature here it's fine with
> > > > me but I want to point out that encryption-formatted clones won't be
> > > > interoperable with QEMU LUKS driver or dm-crypt so making the names
> > > > match QEMU instead of librbd and rbd CLI seems a bit misleading.
> > >
> > > In what way is it not interoperable ?
> > >
> > > If we don't specify any 'encrypt' option, does the guest see the
> > > raw LUKS header + payload, or is the header completely hidden
> > > and only ever accessible RBD ?
> >
> > I think it would see the LUKS header but wouldn't be able to open the
> > LUKS container or do anything else that requires the passphrase.
>
> Hmm, that probably means that QEMU's existing general "luks" driver
> could be layered on top of an encrypted RBD volume too, at least for
> a v1 format. QEMU doesn't support the v2 format at this time.

Yes, as long as the image is not an encryption-formatted clone.  This
was one of the design goals.

>
> I wonder what the tradeoffs are in choosing between RBD's builtin
> LUKS vs QEMU's LUKS driver.

Thin-provisioned COW clones: getting rid of a major obstacle that an
RBD clone must be encrypted with same key as its parent.  The only way
to avoid that today is to basically replace a thin clone with a thick
copy.

>
> RBD's builtin LUKS will do decrypt on the client host I presume ?
> Is it a pure userspace impl in terms of crypto ?

Yes, on the client.  Pure userspace (openssl) at this point.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 11:23         ` Ilya Dryomov
@ 2021-06-21 11:27           ` Daniel P. Berrangé
  2021-06-21 11:52             ` Ilya Dryomov
  0 siblings, 1 reply; 25+ messages in thread
From: Daniel P. Berrangé @ 2021-06-21 11:27 UTC (permalink / raw)
  To: Ilya Dryomov
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Mon, Jun 21, 2021 at 01:23:46PM +0200, Ilya Dryomov wrote:
> On Mon, Jun 21, 2021 at 1:04 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > On Mon, Jun 21, 2021 at 12:59:37PM +0200, Ilya Dryomov wrote:
> > > On Mon, Jun 21, 2021 at 10:32 AM Daniel P. Berrangé <berrange@redhat.com> wrote:
> > > >
> > > > On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> > > > > On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> > > > > >
> > > > > > 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/raw-format.c   |   7 +
> > > > > >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> > > > > >  qapi/block-core.json | 110 ++++++++++++-
> > > > > >  3 files changed, 482 insertions(+), 6 deletions(-)
> > > >
> > > >
> > > > > > diff --git a/block/rbd.c b/block/rbd.c
> > > > > > index f098a89c7b..183b17cd84 100644
> > > > > > --- a/block/rbd.c
> > > > > > +++ b/block/rbd.c
> > > > > > @@ -73,6 +73,18 @@
> > > > > >  #define LIBRBD_USE_IOVEC 0
> > > > > >  #endif
> > > > > >
> > > > > > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > > > > > +
> > > > > > +static const char rbd_luks_header_verification[
> > > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > > > > > +};
> > > > > > +
> > > > > > +static const char rbd_luks2_header_verification[
> > > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > > > > > +};
> > > > > > +
> > > > > >  typedef enum {
> > > > > >      RBD_AIO_READ,
> > > > > >      RBD_AIO_WRITE,
> > > > > > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> > > > > >      }
> > > > > >  }
> > > > > >
> > > > > > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > > > > > +static int qemu_rbd_convert_luks_options(
> > > > > > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > > > > > +        char **passphrase,
> > > > > > +        Error **errp)
> > > > > > +{
> > > > > > +    int r = 0;
> > > > > > +
> > > > > > +    if (!luks_opts->has_key_secret) {
> > > > > > +        r = -EINVAL;
> > > > > > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > > > > > +        return r;
> > > > > > +    }
> > > > >
> > > > > Why is key-secret optional?
> > > >
> > > > It doesn't look like it is handled correctly here, but we need to
> > > > be able to run 'qemu-img info <volume>' and get information back
> > > > on the size of the image, and whether or not it is encrypted,
> > > > without having to supply a passphrase upfront. So it is right that
> > > > key-secret be optional, but also we shouldn't return an fatal
> > > > error like this.
> > >
> > > Hi Daniel,
> > >
> > > The key-secret lives inside RbdEncryptionOptions (or
> > > RbdEncryptionCreateOptions) which are already optional:
> > >
> > >     '*encrypt': 'RbdEncryptionOptions'
> > >
> > >     '*encrypt' :        'RbdEncryptionCreateOptions'
> > >
> > > The image is opened as usual and then, if "encrypt" is specified,
> > > the encryption profile is loaded (or created and laid down).  It does
> > > not make sense to attempt to load or create the encryption profile
> > > without the passphrase -- it would always fail.
> >
> > Ah, that sounds like it is probably ok then.
> >
> >
> > > > Only if BDRV_O_NO_IO is NOT set, should this error be reported
> > > >
> > > >
> > > >
> > > >
> > > > > >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> > > > > >  {
> > > > > >      BDRVRBDState *s = bs->opaque;
> > > > > > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> > > > >
> > > > > I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> > > > > "rbd encryption format" command.
> > > >
> > > > No, it should stay "luks" not "luks1", to match the existing QEMU
> > > > terminology for its LUKS v1 encryption support.
> > >
> > > If you insist on following the QEMU nomenclature here it's fine with
> > > me but I want to point out that encryption-formatted clones won't be
> > > interoperable with QEMU LUKS driver or dm-crypt so making the names
> > > match QEMU instead of librbd and rbd CLI seems a bit misleading.
> >
> > In what way is it not interoperable ?
> >
> > If we don't specify any 'encrypt' option, does the guest see the
> > raw LUKS header + payload, or is the header completely hidden
> > and only ever accessible RBD ?
> 
> I think it would see the LUKS header but wouldn't be able to open the
> LUKS container or do anything else that requires the passphrase.

Hmm, that probably means that QEMU's existing general "luks" driver
could be layered on top of an encrypted RBD volume too, at least for
a v1 format. QEMU doesn't support the v2 format at this time.

I wonder what the tradeoffs are in choosing between RBD's builtin
LUKS vs QEMU's LUKS driver.

RBD's builtin LUKS will do decrypt on the client host I presume ?
Is it a pure userspace impl in terms of crypto ?


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 11:04       ` Daniel P. Berrangé
@ 2021-06-21 11:23         ` Ilya Dryomov
  2021-06-21 11:27           ` Daniel P. Berrangé
  0 siblings, 1 reply; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-21 11:23 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Mon, Jun 21, 2021 at 1:04 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> On Mon, Jun 21, 2021 at 12:59:37PM +0200, Ilya Dryomov wrote:
> > On Mon, Jun 21, 2021 at 10:32 AM Daniel P. Berrangé <berrange@redhat.com> wrote:
> > >
> > > On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> > > > On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> > > > >
> > > > > 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/raw-format.c   |   7 +
> > > > >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> > > > >  qapi/block-core.json | 110 ++++++++++++-
> > > > >  3 files changed, 482 insertions(+), 6 deletions(-)
> > >
> > >
> > > > > diff --git a/block/rbd.c b/block/rbd.c
> > > > > index f098a89c7b..183b17cd84 100644
> > > > > --- a/block/rbd.c
> > > > > +++ b/block/rbd.c
> > > > > @@ -73,6 +73,18 @@
> > > > >  #define LIBRBD_USE_IOVEC 0
> > > > >  #endif
> > > > >
> > > > > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > > > > +
> > > > > +static const char rbd_luks_header_verification[
> > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > > > > +};
> > > > > +
> > > > > +static const char rbd_luks2_header_verification[
> > > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > > > > +};
> > > > > +
> > > > >  typedef enum {
> > > > >      RBD_AIO_READ,
> > > > >      RBD_AIO_WRITE,
> > > > > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> > > > >      }
> > > > >  }
> > > > >
> > > > > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > > > > +static int qemu_rbd_convert_luks_options(
> > > > > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > > > > +        char **passphrase,
> > > > > +        Error **errp)
> > > > > +{
> > > > > +    int r = 0;
> > > > > +
> > > > > +    if (!luks_opts->has_key_secret) {
> > > > > +        r = -EINVAL;
> > > > > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > > > > +        return r;
> > > > > +    }
> > > >
> > > > Why is key-secret optional?
> > >
> > > It doesn't look like it is handled correctly here, but we need to
> > > be able to run 'qemu-img info <volume>' and get information back
> > > on the size of the image, and whether or not it is encrypted,
> > > without having to supply a passphrase upfront. So it is right that
> > > key-secret be optional, but also we shouldn't return an fatal
> > > error like this.
> >
> > Hi Daniel,
> >
> > The key-secret lives inside RbdEncryptionOptions (or
> > RbdEncryptionCreateOptions) which are already optional:
> >
> >     '*encrypt': 'RbdEncryptionOptions'
> >
> >     '*encrypt' :        'RbdEncryptionCreateOptions'
> >
> > The image is opened as usual and then, if "encrypt" is specified,
> > the encryption profile is loaded (or created and laid down).  It does
> > not make sense to attempt to load or create the encryption profile
> > without the passphrase -- it would always fail.
>
> Ah, that sounds like it is probably ok then.
>
>
> > > Only if BDRV_O_NO_IO is NOT set, should this error be reported
> > >
> > >
> > >
> > >
> > > > >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> > > > >  {
> > > > >      BDRVRBDState *s = bs->opaque;
> > > > > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> > > >
> > > > I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> > > > "rbd encryption format" command.
> > >
> > > No, it should stay "luks" not "luks1", to match the existing QEMU
> > > terminology for its LUKS v1 encryption support.
> >
> > If you insist on following the QEMU nomenclature here it's fine with
> > me but I want to point out that encryption-formatted clones won't be
> > interoperable with QEMU LUKS driver or dm-crypt so making the names
> > match QEMU instead of librbd and rbd CLI seems a bit misleading.
>
> In what way is it not interoperable ?
>
> If we don't specify any 'encrypt' option, does the guest see the
> raw LUKS header + payload, or is the header completely hidden
> and only ever accessible RBD ?

I think it would see the LUKS header but wouldn't be able to open the
LUKS container or do anything else that requires the passphrase.

>
>
> > > > > +##
> > > > > +# @RbdEncryptionCreateOptionsLUKSBase:
> > > > > +#
> > > > > +# @cipher-alg: The encryption algorithm
> > > > > +#
> > > > > +# Since: 6.1
> > > > > +##
> > > > > +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> > > > > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > > > > +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
> > > >
> > > > Why QCryptoCipherAlgorithm instead of just enumerating the two
> > > > algorithms that librbd supports?  An early failure when parsing
> > > > seems better than failing in qemu_rbd_convert_luks_create_options()
> > > > and having to clean up the newly created image.
> > >
> > > We don't want to duplicate algorithm names that already have
> > > a defined enum data type.
> >
> > Did you see my other comment on this?  Quoting it just in case:
> >
> >     > ... QCryptoCipherAlgorithm is a set of 12 algorithms of
> >     > which librbd supports only two.  On top of that, e.g. "aes-256" for
> >     > librbd really means "aes-256" + "xts" + "plain64" -- it bundles
> >     > QCryptoCipherAlgorithm, QCryptoCipherMode and QCryptoIVGenAlgorithm
> >     > with the latter two being hard-coded.
> >
> > This is not a big deal, but I just don't see how confusing everyone
> > who introspects the QAPI into thinking that all these algorithms are
> > supported (and forgoing an early parsing failure as a side effect) is
> > worth avoiding a trivial [ 'aes-128', 'aes-256' ] definition here.
>
> Even for the existing LUKS code in QEMU there is no guarantee that the
> impl supports all the ciphers listed in the enum. You can't rely on the
> introspection to that degree.

I see, that makes it clearer.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21 10:59     ` Ilya Dryomov
@ 2021-06-21 11:04       ` Daniel P. Berrangé
  2021-06-21 11:23         ` Ilya Dryomov
  0 siblings, 1 reply; 25+ messages in thread
From: Daniel P. Berrangé @ 2021-06-21 11:04 UTC (permalink / raw)
  To: Ilya Dryomov
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Mon, Jun 21, 2021 at 12:59:37PM +0200, Ilya Dryomov wrote:
> On Mon, Jun 21, 2021 at 10:32 AM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> > > On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> > > >
> > > > 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/raw-format.c   |   7 +
> > > >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> > > >  qapi/block-core.json | 110 ++++++++++++-
> > > >  3 files changed, 482 insertions(+), 6 deletions(-)
> >
> >
> > > > diff --git a/block/rbd.c b/block/rbd.c
> > > > index f098a89c7b..183b17cd84 100644
> > > > --- a/block/rbd.c
> > > > +++ b/block/rbd.c
> > > > @@ -73,6 +73,18 @@
> > > >  #define LIBRBD_USE_IOVEC 0
> > > >  #endif
> > > >
> > > > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > > > +
> > > > +static const char rbd_luks_header_verification[
> > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > > > +};
> > > > +
> > > > +static const char rbd_luks2_header_verification[
> > > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > > > +};
> > > > +
> > > >  typedef enum {
> > > >      RBD_AIO_READ,
> > > >      RBD_AIO_WRITE,
> > > > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> > > >      }
> > > >  }
> > > >
> > > > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > > > +static int qemu_rbd_convert_luks_options(
> > > > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > > > +        char **passphrase,
> > > > +        Error **errp)
> > > > +{
> > > > +    int r = 0;
> > > > +
> > > > +    if (!luks_opts->has_key_secret) {
> > > > +        r = -EINVAL;
> > > > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > > > +        return r;
> > > > +    }
> > >
> > > Why is key-secret optional?
> >
> > It doesn't look like it is handled correctly here, but we need to
> > be able to run 'qemu-img info <volume>' and get information back
> > on the size of the image, and whether or not it is encrypted,
> > without having to supply a passphrase upfront. So it is right that
> > key-secret be optional, but also we shouldn't return an fatal
> > error like this.
> 
> Hi Daniel,
> 
> The key-secret lives inside RbdEncryptionOptions (or
> RbdEncryptionCreateOptions) which are already optional:
> 
>     '*encrypt': 'RbdEncryptionOptions'
> 
>     '*encrypt' :        'RbdEncryptionCreateOptions'
> 
> The image is opened as usual and then, if "encrypt" is specified,
> the encryption profile is loaded (or created and laid down).  It does
> not make sense to attempt to load or create the encryption profile
> without the passphrase -- it would always fail.

Ah, that sounds like it is probably ok then.


> > Only if BDRV_O_NO_IO is NOT set, should this error be reported
> >
> >
> >
> >
> > > >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> > > >  {
> > > >      BDRVRBDState *s = bs->opaque;
> > > > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> > >
> > > I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> > > "rbd encryption format" command.
> >
> > No, it should stay "luks" not "luks1", to match the existing QEMU
> > terminology for its LUKS v1 encryption support.
> 
> If you insist on following the QEMU nomenclature here it's fine with
> me but I want to point out that encryption-formatted clones won't be
> interoperable with QEMU LUKS driver or dm-crypt so making the names
> match QEMU instead of librbd and rbd CLI seems a bit misleading.

In what way is it not interoperable ?

If we don't specify any 'encrypt' option, does the guest see the
raw LUKS header + payload, or is the header completely hidden
and only ever accessible RBD ?


> > > > +##
> > > > +# @RbdEncryptionCreateOptionsLUKSBase:
> > > > +#
> > > > +# @cipher-alg: The encryption algorithm
> > > > +#
> > > > +# Since: 6.1
> > > > +##
> > > > +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> > > > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > > > +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
> > >
> > > Why QCryptoCipherAlgorithm instead of just enumerating the two
> > > algorithms that librbd supports?  An early failure when parsing
> > > seems better than failing in qemu_rbd_convert_luks_create_options()
> > > and having to clean up the newly created image.
> >
> > We don't want to duplicate algorithm names that already have
> > a defined enum data type.
> 
> Did you see my other comment on this?  Quoting it just in case:
> 
>     > ... QCryptoCipherAlgorithm is a set of 12 algorithms of
>     > which librbd supports only two.  On top of that, e.g. "aes-256" for
>     > librbd really means "aes-256" + "xts" + "plain64" -- it bundles
>     > QCryptoCipherAlgorithm, QCryptoCipherMode and QCryptoIVGenAlgorithm
>     > with the latter two being hard-coded.
> 
> This is not a big deal, but I just don't see how confusing everyone
> who introspects the QAPI into thinking that all these algorithms are
> supported (and forgoing an early parsing failure as a side effect) is
> worth avoiding a trivial [ 'aes-128', 'aes-256' ] definition here.

Even for the existing LUKS code in QEMU there is no guarantee that the
impl supports all the ciphers listed in the enum. You can't rely on the
introspection to that degree.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-21  8:32   ` Daniel P. Berrangé
@ 2021-06-21 10:59     ` Ilya Dryomov
  2021-06-21 11:04       ` Daniel P. Berrangé
  0 siblings, 1 reply; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-21 10:59 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Mon, Jun 21, 2021 at 10:32 AM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> > On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> > >
> > > 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/raw-format.c   |   7 +
> > >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> > >  qapi/block-core.json | 110 ++++++++++++-
> > >  3 files changed, 482 insertions(+), 6 deletions(-)
>
>
> > > diff --git a/block/rbd.c b/block/rbd.c
> > > index f098a89c7b..183b17cd84 100644
> > > --- a/block/rbd.c
> > > +++ b/block/rbd.c
> > > @@ -73,6 +73,18 @@
> > >  #define LIBRBD_USE_IOVEC 0
> > >  #endif
> > >
> > > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > > +
> > > +static const char rbd_luks_header_verification[
> > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > > +};
> > > +
> > > +static const char rbd_luks2_header_verification[
> > > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > > +};
> > > +
> > >  typedef enum {
> > >      RBD_AIO_READ,
> > >      RBD_AIO_WRITE,
> > > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> > >      }
> > >  }
> > >
> > > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > > +static int qemu_rbd_convert_luks_options(
> > > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > > +        char **passphrase,
> > > +        Error **errp)
> > > +{
> > > +    int r = 0;
> > > +
> > > +    if (!luks_opts->has_key_secret) {
> > > +        r = -EINVAL;
> > > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > > +        return r;
> > > +    }
> >
> > Why is key-secret optional?
>
> It doesn't look like it is handled correctly here, but we need to
> be able to run 'qemu-img info <volume>' and get information back
> on the size of the image, and whether or not it is encrypted,
> without having to supply a passphrase upfront. So it is right that
> key-secret be optional, but also we shouldn't return an fatal
> error like this.

Hi Daniel,

The key-secret lives inside RbdEncryptionOptions (or
RbdEncryptionCreateOptions) which are already optional:

    '*encrypt': 'RbdEncryptionOptions'

    '*encrypt' :        'RbdEncryptionCreateOptions'

The image is opened as usual and then, if "encrypt" is specified,
the encryption profile is loaded (or created and laid down).  It does
not make sense to attempt to load or create the encryption profile
without the passphrase -- it would always fail.

"qemu-img info" can get the size, etc without loading the encryption
profile (i.e. ->bdrv_get_info and ->bdrv_get_specific_info don't need
it).  But note that once the encryption profile is loaded, the size
changes because librbd subtracts the LUKS header overhead.  So

    $ qemu-img info --image-opts driver=rbd,pool=foo,image=bar

and

    $ qemu-img info --object secret,file=passphrase.txt,id=sec0
--image-opts driver=rbd,pool=foo,image=bar,encrypt.format=luks2,encrypt.key-secret=sec0

would give different results.

>
> Only if BDRV_O_NO_IO is NOT set, should this error be reported
>
>
>
>
> > >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> > >  {
> > >      BDRVRBDState *s = bs->opaque;
> > > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> >
> > I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> > "rbd encryption format" command.
>
> No, it should stay "luks" not "luks1", to match the existing QEMU
> terminology for its LUKS v1 encryption support.

If you insist on following the QEMU nomenclature here it's fine with
me but I want to point out that encryption-formatted clones won't be
interoperable with QEMU LUKS driver or dm-crypt so making the names
match QEMU instead of librbd and rbd CLI seems a bit misleading.

>
>
> > > @@ -3609,6 +3622,94 @@
> > >  { 'enum': 'RbdAuthMode',
> > >    'data': [ 'cephx', 'none' ] }
> > >
> > > +##
> > > +# @RbdImageEncryptionFormat:
> > > +#
> > > +# Since: 6.1
> > > +##
> > > +{ 'enum': 'RbdImageEncryptionFormat',
> > > +  'data': [ 'luks', 'luks2' ] }
> >
> > Ditto -- "luks1" and "luks2".
>
> No, the patch is correct as is.
>
> > > +# @RbdEncryptionOptionsLUKSBase:
> > > +#
> > > +# @key-secret: ID of a QCryptoSecret object providing a passphrase
> > > +#              for unlocking the encryption
> > > +#
> > > +# Since: 6.1
> > > +##
> > > +{ 'struct': 'RbdEncryptionOptionsLUKSBase',
> > > +  'data': { '*key-secret': 'str' }}
> >
> > When would we not need a passphrase?  I think it should be required.
>
> When running 'qemu-img info'
>
> > > +##
> > > +# @RbdEncryptionCreateOptionsLUKSBase:
> > > +#
> > > +# @cipher-alg: The encryption algorithm
> > > +#
> > > +# Since: 6.1
> > > +##
> > > +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> > > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > > +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
> >
> > Why QCryptoCipherAlgorithm instead of just enumerating the two
> > algorithms that librbd supports?  An early failure when parsing
> > seems better than failing in qemu_rbd_convert_luks_create_options()
> > and having to clean up the newly created image.
>
> We don't want to duplicate algorithm names that already have
> a defined enum data type.

Did you see my other comment on this?  Quoting it just in case:

    > ... QCryptoCipherAlgorithm is a set of 12 algorithms of
    > which librbd supports only two.  On top of that, e.g. "aes-256" for
    > librbd really means "aes-256" + "xts" + "plain64" -- it bundles
    > QCryptoCipherAlgorithm, QCryptoCipherMode and QCryptoIVGenAlgorithm
    > with the latter two being hard-coded.

This is not a big deal, but I just don't see how confusing everyone
who introspects the QAPI into thinking that all these algorithms are
supported (and forgoing an early parsing failure as a side effect) is
worth avoiding a trivial [ 'aes-128', 'aes-256' ] definition here.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-19 19:44 ` Ilya Dryomov
  2021-06-20 10:35   ` Ilya Dryomov
@ 2021-06-21  8:32   ` Daniel P. Berrangé
  2021-06-21 10:59     ` Ilya Dryomov
  1 sibling, 1 reply; 25+ messages in thread
From: Daniel P. Berrangé @ 2021-06-21  8:32 UTC (permalink / raw)
  To: Ilya Dryomov
  Cc: kwolf, qemu-block, qemu-devel, Mykola Golub, Or Ozeri, Danny Harnik

On Sat, Jun 19, 2021 at 09:44:32PM +0200, Ilya Dryomov wrote:
> On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> >
> > 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/raw-format.c   |   7 +
> >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> >  qapi/block-core.json | 110 ++++++++++++-
> >  3 files changed, 482 insertions(+), 6 deletions(-)


> > diff --git a/block/rbd.c b/block/rbd.c
> > index f098a89c7b..183b17cd84 100644
> > --- a/block/rbd.c
> > +++ b/block/rbd.c
> > @@ -73,6 +73,18 @@
> >  #define LIBRBD_USE_IOVEC 0
> >  #endif
> >
> > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > +
> > +static const char rbd_luks_header_verification[
> > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > +};
> > +
> > +static const char rbd_luks2_header_verification[
> > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > +};
> > +
> >  typedef enum {
> >      RBD_AIO_READ,
> >      RBD_AIO_WRITE,
> > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> >      }
> >  }
> >
> > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > +static int qemu_rbd_convert_luks_options(
> > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > +        char **passphrase,
> > +        Error **errp)
> > +{
> > +    int r = 0;
> > +
> > +    if (!luks_opts->has_key_secret) {
> > +        r = -EINVAL;
> > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > +        return r;
> > +    }
> 
> Why is key-secret optional?

It doesn't look like it is handled correctly here, but we need to
be able to run 'qemu-img info <volume>' and get information back
on the size of the image, and whether or not it is encrypted,
without having to supply a passphrase upfront. So it is right that
key-secret be optional, but also we shouldn't return an fatal
error like this.

Only if BDRV_O_NO_IO is NOT set, should this error be reported




> >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> >  {
> >      BDRVRBDState *s = bs->opaque;
> > @@ -1243,6 +1589,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: 'luks', 'luks2'",
> 
> I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> "rbd encryption format" command.

No, it should stay "luks" not "luks1", to match the existing QEMU
terminology for its LUKS v1 encryption support.


> > @@ -3609,6 +3622,94 @@
> >  { 'enum': 'RbdAuthMode',
> >    'data': [ 'cephx', 'none' ] }
> >
> > +##
> > +# @RbdImageEncryptionFormat:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'enum': 'RbdImageEncryptionFormat',
> > +  'data': [ 'luks', 'luks2' ] }
> 
> Ditto -- "luks1" and "luks2".

No, the patch is correct as is.

> > +# @RbdEncryptionOptionsLUKSBase:
> > +#
> > +# @key-secret: ID of a QCryptoSecret object providing a passphrase
> > +#              for unlocking the encryption
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': { '*key-secret': 'str' }}
> 
> When would we not need a passphrase?  I think it should be required.

When running 'qemu-img info'

> > +##
> > +# @RbdEncryptionCreateOptionsLUKSBase:
> > +#
> > +# @cipher-alg: The encryption algorithm
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
> 
> Why QCryptoCipherAlgorithm instead of just enumerating the two
> algorithms that librbd supports?  An early failure when parsing
> seems better than failing in qemu_rbd_convert_luks_create_options()
> and having to clean up the newly created image.

We don't want to duplicate algorithm names that already have
a defined enum data type.

> 
> > +
> > +##
> > +# @RbdEncryptionOptionsLUKS:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionOptionsLUKS',
> > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': {}}
> > +
> > +##
> > +# @RbdEncryptionOptionsLUKS2:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionOptionsLUKS2',
> > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': {}}
> > +
> > +##
> > +# @RbdEncryptionCreateOptionsLUKS:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
> > +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> > +  'data': {}}
> > +
> > +##
> > +# @RbdEncryptionCreateOptionsLUKS2:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
> > +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> > +  'data': {}}
> 
> This appears over-engineered to me.  A three-deep hierarchy for
> a structure with two fields (key-secret and cipher-alg) seems like
> it could be simplified.
> 
> Why differentiate between luks1 and luks2 if the fields are exactly
> the same?  Do you expect one of these empty derived structures to be
> extended in the future?

The current QEMU luks driver already supports alot more options
for creation than this, so it is conceivable we'll need more
here in future. Also LUKSv2 has many more features than v1, so
I wouldn't be surprised if the structs diverge eventually.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-17 16:05 Or Ozeri
  2021-06-19 19:44 ` Ilya Dryomov
@ 2021-06-20 12:46 ` Or Ozeri
  1 sibling, 0 replies; 25+ messages in thread
From: Or Ozeri @ 2021-06-20 12:46 UTC (permalink / raw)
  To: Ilya Dryomov
  Cc: kwolf, berrange, qemu-block, qemu-devel, Mykola Golub, Danny Harnik

[-- Attachment #1: Type: text/html, Size: 39087 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-19 19:44 ` Ilya Dryomov
@ 2021-06-20 10:35   ` Ilya Dryomov
  2021-06-21  8:32   ` Daniel P. Berrangé
  1 sibling, 0 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-20 10:35 UTC (permalink / raw)
  To: Or Ozeri
  Cc: kwolf, berrange, qemu-block, qemu-devel, Mykola Golub, Danny Harnik

On Sat, Jun 19, 2021 at 9:44 PM Ilya Dryomov <idryomov@gmail.com> wrote:
>
> On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
> >
> > 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/raw-format.c   |   7 +
> >  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
> >  qapi/block-core.json | 110 ++++++++++++-
> >  3 files changed, 482 insertions(+), 6 deletions(-)
> >
> > diff --git a/block/raw-format.c b/block/raw-format.c
> > index 7717578ed6..f6e70e2356 100644
> > --- a/block/raw-format.c
> > +++ b/block/raw-format.c
> > @@ -369,6 +369,12 @@ static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
> >      return bdrv_get_info(bs->file->bs, bdi);
> >  }
> >
> > +static ImageInfoSpecific *raw_get_specific_info(BlockDriverState *bs,
> > +                                                Error **errp)
> > +{
> > +    return bdrv_get_specific_info(bs->file->bs, errp);
> > +}
> > +
> >  static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
> >  {
> >      if (bs->probed) {
> > @@ -603,6 +609,7 @@ BlockDriver bdrv_raw = {
> >      .has_variable_length  = true,
> >      .bdrv_measure         = &raw_measure,
> >      .bdrv_get_info        = &raw_get_info,
> > +    .bdrv_get_specific_info = &raw_get_specific_info,
>
> Hi Or,
>
> This feels a bit contentious to me.
>
> AFAIU ImageInfoSpecific is for format-specfic information.  "raw"
> is a format and passing the request down the stack this way results
> in a somewhat confusing output such as
>
>     $ qemu-img info rbd:foo/bar
>     image: json:{"driver": "raw", "file": {"pool": "foo", "image":
> "bar", "driver": "rbd", "namespace": ""}}
>     file format: raw
>     ...
>     Format specific information:
>        <information for rbd format>
>
> I think this should be broken out into its own patch and get separate
> acks.
>
> >      .bdrv_refresh_limits  = &raw_refresh_limits,
> >      .bdrv_probe_blocksizes = &raw_probe_blocksizes,
> >      .bdrv_probe_geometry  = &raw_probe_geometry,
> > diff --git a/block/rbd.c b/block/rbd.c
> > index f098a89c7b..183b17cd84 100644
> > --- a/block/rbd.c
> > +++ b/block/rbd.c
> > @@ -73,6 +73,18 @@
> >  #define LIBRBD_USE_IOVEC 0
> >  #endif
> >
> > +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> > +
> > +static const char rbd_luks_header_verification[
> > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> > +};
> > +
> > +static const char rbd_luks2_header_verification[
> > +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> > +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> > +};
> > +
> >  typedef enum {
> >      RBD_AIO_READ,
> >      RBD_AIO_WRITE,
> > @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
> >      }
> >  }
> >
> > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > +static int qemu_rbd_convert_luks_options(
> > +        RbdEncryptionOptionsLUKSBase *luks_opts,
> > +        char **passphrase,
> > +        Error **errp)
> > +{
> > +    int r = 0;
> > +
> > +    if (!luks_opts->has_key_secret) {
> > +        r = -EINVAL;
> > +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> > +        return r;
> > +    }
>
> Why is key-secret optional?
>
> > +
> > +    *passphrase = qcrypto_secret_lookup_as_utf8(luks_opts->key_secret, errp);
> > +    if (!*passphrase) {
> > +        return -EIO;
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static int qemu_rbd_convert_luks_create_options(
> > +        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
> > +        rbd_encryption_algorithm_t *alg,
> > +        char **passphrase,
> > +        Error **errp)
> > +{
> > +    int r = 0;
> > +
> > +    r = qemu_rbd_convert_luks_options(
> > +            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
> > +            passphrase, errp);
> > +    if (r < 0) {
> > +        return r;
> > +    }
> > +
> > +    if (luks_opts->has_cipher_alg) {
> > +        switch (luks_opts->cipher_alg) {
> > +            case QCRYPTO_CIPHER_ALG_AES_128: {
> > +                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
> > +                break;
> > +            }
> > +            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
> > +                                      RbdEncryptionCreateOptions *encrypt,
> > +                                      Error **errp)
> > +{
> > +    int r = 0;
> > +    g_autofree char *passphrase = NULL;
> > +    g_autofree rbd_encryption_options_t opts = NULL;
> > +    rbd_encryption_format_t format;
> > +    rbd_image_info_t info;
> > +    size_t opts_size;
> > +    uint64_t raw_size;
> > +
> > +    r = rbd_stat(image, &info, sizeof(info));
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "cannot get raw image size");
> > +        return r;
> > +    }
> > +    raw_size = info.size;
>
> Using rbd_get_size() to put size directly into raw_size would be
> neater and avoid rbd_image_info_t.
>
> > +
> > +    switch (encrypt->format) {
> > +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> > +            rbd_encryption_luks1_format_options_t *luks_opts =
> > +                    g_new0(rbd_encryption_luks1_format_options_t, 1);
>
> Why is this dynamically allocated?  It doesn't matter that much, but
> would rbd_encryption_luks1_format_options_t on the stack not work?
>
> > +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> > +            opts = luks_opts;
> > +            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
> > +            r = qemu_rbd_convert_luks_create_options(
> > +                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
> > +                    &luks_opts->alg, &passphrase, errp);
> > +            luks_opts->passphrase = passphrase;
> > +            luks_opts->passphrase_size = strlen(passphrase);
>
> r needs to be checked before strlen() is called, as passphrase
> would be NULL on error.
>
> More importantly, what about randomly-generated (i.e. binary)
> passphrases?  I think qemu_rbd_convert_luks_options() should be
> switched to qcrypto_secret_lookup() to cover this case.  Then
> this strlen() would go away entirely.
>
> > +            break;
> > +        }
> > +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> > +            rbd_encryption_luks2_format_options_t *luks2_opts =
> > +                    g_new0(rbd_encryption_luks2_format_options_t, 1);
>
> Same here.
>
> > +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> > +            opts = luks2_opts;
> > +            opts_size = sizeof(rbd_encryption_luks2_format_options_t);
> > +            r = qemu_rbd_convert_luks_create_options(
> > +                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
> > +                            &encrypt->u.luks2),
> > +                    &luks2_opts->alg, &passphrase, errp);
> > +            luks2_opts->passphrase = passphrase;
> > +            luks2_opts->passphrase_size = strlen(passphrase);
>
> And here.
>
> > +            break;
> > +        }
> > +        default: {
> > +            r = -ENOTSUP;
> > +            error_setg_errno(
> > +                    errp, -r, "unknown image encryption format: %u",
> > +                    encrypt->format);
> > +        }
> > +    }
> > +
> > +    if (r < 0) {
> > +        return r;
> > +    }
> > +
> > +    r = rbd_encryption_format(image, format, opts, opts_size);
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "encryption format fail");
> > +        return r;
> > +    }
> > +
> > +    r = rbd_stat(image, &info, sizeof(info));
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "cannot get effective image size");
> > +        return r;
> > +    }
> > +
> > +    r = rbd_resize(image, raw_size + (raw_size - info.size));
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "cannot resize image after format");
> > +        return r;
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static int qemu_rbd_encryption_load(rbd_image_t image,
> > +                                      RbdEncryptionOptions *encrypt,
> > +                                      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 (encrypt->format) {
> > +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> > +            rbd_encryption_luks1_format_options_t *luks_opts =
> > +                    g_new0(rbd_encryption_luks1_format_options_t, 1);
> > +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> > +            opts = luks_opts;
> > +            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
> > +            r = qemu_rbd_convert_luks_options(
> > +                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
> > +                    &passphrase, errp);
> > +            luks_opts->passphrase = passphrase;
> > +            luks_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(&encrypt->u.luks2),
> > +                    &passphrase, errp);
> > +            luks2_opts->passphrase = passphrase;
> > +            luks2_opts->passphrase_size = strlen(passphrase);
>
> Same as in qemu_rbd_encryption_format().
>
> > +            break;
> > +        }
> > +        default: {
> > +            r = -ENOTSUP;
> > +            error_setg_errno(
> > +                    errp, -r, "unknown image encryption format: %u",
> > +                    encrypt->format);
> > +        }
> > +    }
> > +
> > +    if (r < 0) {
> > +        return r;
> > +    }
> > +
> > +    r = rbd_encryption_load(image, format, opts, opts_size);
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "encryption load fail");
> > +    }
> > +
> > +    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 +570,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
> >          return -EINVAL;
> >      }
> >
> > +#ifndef LIBRBD_SUPPORTS_ENCRYPTION
> > +    if (opts->location->has_encrypt) {
>
> Here opts->location->encrypt is being checked
>
> > +        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 +602,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
> >          goto out;
> >      }
> >
> > +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> > +    if (opts->has_encrypt) {
>
> ... but opts->encrypt is being acted on.  A request to create an
> encrypted image with an old librbd would not be failed.
>
> > +        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_encryption_format(image, opts->encrypt, 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 +635,43 @@ 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_create_options(
> > +        QemuOpts *opts,
> > +        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +680,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
> >      BlockdevCreateOptions *create_options;
> >      BlockdevCreateOptionsRbd *rbd_opts;
> >      BlockdevOptionsRbd *loc;
> > +    RbdEncryptionCreateOptions *encrypt = NULL;
> >      Error *local_err = NULL;
> >      const char *keypairs, *password_secret;
> >      QDict *options = NULL;
> > @@ -431,6 +709,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
> >          goto exit;
> >      }
> >
> > +    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
> > +    if (ret < 0) {
> > +        goto exit;
> > +    }
> > +    rbd_opts->encrypt     = encrypt;
> > +    rbd_opts->has_encrypt = !!encrypt;
> > +
> >      /*
> >       * Caution: while qdict_get_try_str() is fine, getting non-string
> >       * types would require more care.  When @options come from -blockdev
> > @@ -756,12 +1041,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1067,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 +1077,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);
> > @@ -1050,6 +1349,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
> >      return 0;
> >  }
> >
> > +static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
> > +                                                     Error **errp)
> > +{
> > +    BDRVRBDState *s = bs->opaque;
> > +    ImageInfoSpecific *spec_info;
> > +    rbd_image_info_t info;
> > +    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
> > +    int r;
> > +
> > +    r = rbd_stat(s->image, &info, sizeof(info));
>
> Ditto -- rbd_get_size().
>
> > +    if (r < 0) {
> > +        error_setg_errno(errp, -r, "cannot get image size");
> > +        return NULL;
> > +    }
> > +
> > +    if (info.size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
> > +        r = rbd_read(s->image, 0,
> > +                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf) ;
>
> Stray space before semicolon.
>
> > +        if (r < 0) {
> > +            error_setg_errno(errp, -r, "cannot read image start for probe");
> > +            return NULL;
> > +        }
> > +    }
> > +
> > +    spec_info = g_new(ImageInfoSpecific, 1);
> > +    *spec_info = (ImageInfoSpecific){
> > +        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
> > +        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
> > +    };
> > +
> > +    if (memcmp(buf, rbd_luks_header_verification,
> > +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> > +        spec_info->u.rbd.data->encryption_format =
> > +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
> > +        spec_info->u.rbd.data->has_encryption_format = true;
> > +    } else if (memcmp(buf, rbd_luks2_header_verification,
> > +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> > +        spec_info->u.rbd.data->encryption_format =
> > +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
> > +        spec_info->u.rbd.data->has_encryption_format = true;
> > +    } else {
> > +        spec_info->u.rbd.data->has_encryption_format = false;
> > +    }
> > +
> > +    return spec_info;
> > +}
> > +
> >  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
> >  {
> >      BDRVRBDState *s = bs->opaque;
> > @@ -1243,6 +1589,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: 'luks', 'luks2'",
>
> I think it should be "luks1" and "luks2" to match rbd/librbd.h and
> "rbd encryption format" command.
>
> > +        },
> > +        {
> > +            .name = "encrypt.cipher-alg",
> > +            .type = QEMU_OPT_STRING,
> > +            .help = "Name of encryption cipher algorithm"
> > +                    " (allowed values: aes-128, aes-256)",
> > +        },
> > +        {
> > +            .name = "encrypt.key-secret",
> > +            .type = QEMU_OPT_STRING,
> > +            .help = "ID of secret providing LUKS passphrase",
> > +        },
>
> A question probably better suited for QEMU maintainers but just in
> case you have already asked and clarified this matter...
>
> There seem to be two image creation APIs: the old option-based thing
> and the new QAPI-based x-blockdev-create command.  This patch extends
> both and implements the old -> new translation.  Is this the right way
> to do it?  Is the option-based interface supposed to be extended when
> adding new functionality?
>
> >          { /* end of list */ }
> >      }
> >  };
> > @@ -1272,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
> >      .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
> >      .bdrv_has_zero_init     = bdrv_has_zero_init_1,
> >      .bdrv_get_info          = qemu_rbd_getinfo,
> > +    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
> >      .create_opts            = &qemu_rbd_create_opts,
> >      .bdrv_getlength         = qemu_rbd_getlength,
> >      .bdrv_co_truncate       = qemu_rbd_co_truncate,
> > diff --git a/qapi/block-core.json b/qapi/block-core.json
> > index 6d227924d0..60d2ff0d1a 100644
> > --- a/qapi/block-core.json
> > +++ b/qapi/block-core.json
> > @@ -127,6 +127,18 @@
> >        'extents': ['ImageInfo']
> >    } }
> >
> > +##
> > +# @ImageInfoSpecificRbd:
> > +#
> > +# @encryption-format: Image encryption format
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'ImageInfoSpecificRbd',
> > +  'data': {
> > +      '*encryption-format': 'RbdImageEncryptionFormat'
> > +  } }
> > +
> >  ##
> >  # @ImageInfoSpecific:
> >  #
> > @@ -141,7 +153,8 @@
> >        # If we need to add block driver specific parameters for
> >        # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
> >        # to define a ImageInfoSpecificLUKS
> > -      'luks': 'QCryptoBlockInfoLUKS'
> > +      'luks': 'QCryptoBlockInfoLUKS',
> > +      'rbd': 'ImageInfoSpecificRbd'
> >    } }
> >
> >  ##
> > @@ -3609,6 +3622,94 @@
> >  { 'enum': 'RbdAuthMode',
> >    'data': [ 'cephx', 'none' ] }
> >
> > +##
> > +# @RbdImageEncryptionFormat:
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'enum': 'RbdImageEncryptionFormat',
> > +  'data': [ 'luks', 'luks2' ] }
>
> Ditto -- "luks1" and "luks2".

Or pointed out that it used to be "luks1" and was changed to "luks"
after Daniel's review.  I would like to stand by my suggestion to go
back to "luks1".  Not only it would be consistent with librbd and rbd
CLI but "luks" is actually ambiguous in the LUKS world.  If you look at
cryptsetup tool, there "luks" can mean either "luks1" or "luks2"
depending on cryptsetup version (and possibly how it was compiled).
Being explicit is always better IMO.

>
> > +
> > +##
> > +# @RbdEncryptionOptionsLUKSBase:
> > +#
> > +# @key-secret: ID of a QCryptoSecret object providing a passphrase
> > +#              for unlocking the encryption
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': { '*key-secret': 'str' }}
>
> When would we not need a passphrase?  I think it should be required.
>
> > +
> > +##
> > +# @RbdEncryptionCreateOptionsLUKSBase:
> > +#
> > +# @cipher-alg: The encryption algorithm
> > +#
> > +# Since: 6.1
> > +##
> > +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> > +  'base': 'RbdEncryptionOptionsLUKSBase',
> > +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
>
> Why QCryptoCipherAlgorithm instead of just enumerating the two
> algorithms that librbd supports?  An early failure when parsing
> seems better than failing in qemu_rbd_convert_luks_create_options()
> and having to clean up the newly created image.

Same here, it appears that using QCryptoCipherAlgorithm was Daniel's
suggestion.  But QCryptoCipherAlgorithm is a set of 12 algorithms of
which librbd supports only two.  On top of that, e.g. "aes-256" for
librbd really means "aes-256" + "xts" + "plain64" -- it bundles
QCryptoCipherAlgorithm, QCryptoCipherMode and QCryptoIVGenAlgorithm
with the latter two being hard-coded.

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-06-17 16:05 Or Ozeri
@ 2021-06-19 19:44 ` Ilya Dryomov
  2021-06-20 10:35   ` Ilya Dryomov
  2021-06-21  8:32   ` Daniel P. Berrangé
  2021-06-20 12:46 ` Or Ozeri
  1 sibling, 2 replies; 25+ messages in thread
From: Ilya Dryomov @ 2021-06-19 19:44 UTC (permalink / raw)
  To: Or Ozeri
  Cc: kwolf, berrange, qemu-block, qemu-devel, Mykola Golub, Danny Harnik

On Thu, Jun 17, 2021 at 6:05 PM Or Ozeri <oro@il.ibm.com> wrote:
>
> 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/raw-format.c   |   7 +
>  block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
>  qapi/block-core.json | 110 ++++++++++++-
>  3 files changed, 482 insertions(+), 6 deletions(-)
>
> diff --git a/block/raw-format.c b/block/raw-format.c
> index 7717578ed6..f6e70e2356 100644
> --- a/block/raw-format.c
> +++ b/block/raw-format.c
> @@ -369,6 +369,12 @@ static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
>      return bdrv_get_info(bs->file->bs, bdi);
>  }
>
> +static ImageInfoSpecific *raw_get_specific_info(BlockDriverState *bs,
> +                                                Error **errp)
> +{
> +    return bdrv_get_specific_info(bs->file->bs, errp);
> +}
> +
>  static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
>  {
>      if (bs->probed) {
> @@ -603,6 +609,7 @@ BlockDriver bdrv_raw = {
>      .has_variable_length  = true,
>      .bdrv_measure         = &raw_measure,
>      .bdrv_get_info        = &raw_get_info,
> +    .bdrv_get_specific_info = &raw_get_specific_info,

Hi Or,

This feels a bit contentious to me.

AFAIU ImageInfoSpecific is for format-specfic information.  "raw"
is a format and passing the request down the stack this way results
in a somewhat confusing output such as

    $ qemu-img info rbd:foo/bar
    image: json:{"driver": "raw", "file": {"pool": "foo", "image":
"bar", "driver": "rbd", "namespace": ""}}
    file format: raw
    ...
    Format specific information:
       <information for rbd format>

I think this should be broken out into its own patch and get separate
acks.

>      .bdrv_refresh_limits  = &raw_refresh_limits,
>      .bdrv_probe_blocksizes = &raw_probe_blocksizes,
>      .bdrv_probe_geometry  = &raw_probe_geometry,
> diff --git a/block/rbd.c b/block/rbd.c
> index f098a89c7b..183b17cd84 100644
> --- a/block/rbd.c
> +++ b/block/rbd.c
> @@ -73,6 +73,18 @@
>  #define LIBRBD_USE_IOVEC 0
>  #endif
>
> +#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
> +
> +static const char rbd_luks_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
> +};
> +
> +static const char rbd_luks2_header_verification[
> +        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
> +    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
> +};
> +
>  typedef enum {
>      RBD_AIO_READ,
>      RBD_AIO_WRITE,
> @@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
>      }
>  }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +static int qemu_rbd_convert_luks_options(
> +        RbdEncryptionOptionsLUKSBase *luks_opts,
> +        char **passphrase,
> +        Error **errp)
> +{
> +    int r = 0;
> +
> +    if (!luks_opts->has_key_secret) {
> +        r = -EINVAL;
> +        error_setg_errno(errp, -r, "missing encrypt.key-secret");
> +        return r;
> +    }

Why is key-secret optional?

> +
> +    *passphrase = qcrypto_secret_lookup_as_utf8(luks_opts->key_secret, errp);
> +    if (!*passphrase) {
> +        return -EIO;
> +    }
> +
> +    return 0;
> +}
> +
> +static int qemu_rbd_convert_luks_create_options(
> +        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
> +        rbd_encryption_algorithm_t *alg,
> +        char **passphrase,
> +        Error **errp)
> +{
> +    int r = 0;
> +
> +    r = qemu_rbd_convert_luks_options(
> +            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
> +            passphrase, errp);
> +    if (r < 0) {
> +        return r;
> +    }
> +
> +    if (luks_opts->has_cipher_alg) {
> +        switch (luks_opts->cipher_alg) {
> +            case QCRYPTO_CIPHER_ALG_AES_128: {
> +                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
> +                break;
> +            }
> +            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
> +                                      RbdEncryptionCreateOptions *encrypt,
> +                                      Error **errp)
> +{
> +    int r = 0;
> +    g_autofree char *passphrase = NULL;
> +    g_autofree rbd_encryption_options_t opts = NULL;
> +    rbd_encryption_format_t format;
> +    rbd_image_info_t info;
> +    size_t opts_size;
> +    uint64_t raw_size;
> +
> +    r = rbd_stat(image, &info, sizeof(info));
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get raw image size");
> +        return r;
> +    }
> +    raw_size = info.size;

Using rbd_get_size() to put size directly into raw_size would be
neater and avoid rbd_image_info_t.

> +
> +    switch (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            rbd_encryption_luks1_format_options_t *luks_opts =
> +                    g_new0(rbd_encryption_luks1_format_options_t, 1);

Why is this dynamically allocated?  It doesn't matter that much, but
would rbd_encryption_luks1_format_options_t on the stack not work?

> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = luks_opts;
> +            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
> +                    &luks_opts->alg, &passphrase, errp);
> +            luks_opts->passphrase = passphrase;
> +            luks_opts->passphrase_size = strlen(passphrase);

r needs to be checked before strlen() is called, as passphrase
would be NULL on error.

More importantly, what about randomly-generated (i.e. binary)
passphrases?  I think qemu_rbd_convert_luks_options() should be
switched to qcrypto_secret_lookup() to cover this case.  Then
this strlen() would go away entirely.

> +            break;
> +        }
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2: {
> +            rbd_encryption_luks2_format_options_t *luks2_opts =
> +                    g_new0(rbd_encryption_luks2_format_options_t, 1);

Same here.

> +            format = RBD_ENCRYPTION_FORMAT_LUKS2;
> +            opts = luks2_opts;
> +            opts_size = sizeof(rbd_encryption_luks2_format_options_t);
> +            r = qemu_rbd_convert_luks_create_options(
> +                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
> +                            &encrypt->u.luks2),
> +                    &luks2_opts->alg, &passphrase, errp);
> +            luks2_opts->passphrase = passphrase;
> +            luks2_opts->passphrase_size = strlen(passphrase);

And here.

> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +        }
> +    }
> +
> +    if (r < 0) {
> +        return r;
> +    }
> +
> +    r = rbd_encryption_format(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption format fail");
> +        return r;
> +    }
> +
> +    r = rbd_stat(image, &info, sizeof(info));
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get effective image size");
> +        return r;
> +    }
> +
> +    r = rbd_resize(image, raw_size + (raw_size - info.size));
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot resize image after format");
> +        return r;
> +    }
> +
> +    return 0;
> +}
> +
> +static int qemu_rbd_encryption_load(rbd_image_t image,
> +                                      RbdEncryptionOptions *encrypt,
> +                                      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 (encrypt->format) {
> +        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
> +            rbd_encryption_luks1_format_options_t *luks_opts =
> +                    g_new0(rbd_encryption_luks1_format_options_t, 1);
> +            format = RBD_ENCRYPTION_FORMAT_LUKS1;
> +            opts = luks_opts;
> +            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
> +            r = qemu_rbd_convert_luks_options(
> +                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
> +                    &passphrase, errp);
> +            luks_opts->passphrase = passphrase;
> +            luks_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(&encrypt->u.luks2),
> +                    &passphrase, errp);
> +            luks2_opts->passphrase = passphrase;
> +            luks2_opts->passphrase_size = strlen(passphrase);

Same as in qemu_rbd_encryption_format().

> +            break;
> +        }
> +        default: {
> +            r = -ENOTSUP;
> +            error_setg_errno(
> +                    errp, -r, "unknown image encryption format: %u",
> +                    encrypt->format);
> +        }
> +    }
> +
> +    if (r < 0) {
> +        return r;
> +    }
> +
> +    r = rbd_encryption_load(image, format, opts, opts_size);
> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "encryption load fail");
> +    }
> +
> +    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 +570,13 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          return -EINVAL;
>      }
>
> +#ifndef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->location->has_encrypt) {

Here opts->location->encrypt is being checked

> +        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 +602,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
>          goto out;
>      }
>
> +#ifdef LIBRBD_SUPPORTS_ENCRYPTION
> +    if (opts->has_encrypt) {

... but opts->encrypt is being acted on.  A request to create an
encrypted image with an old librbd would not be failed.

> +        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_encryption_format(image, opts->encrypt, 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 +635,43 @@ 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_create_options(
> +        QemuOpts *opts,
> +        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +680,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
>      BlockdevCreateOptions *create_options;
>      BlockdevCreateOptionsRbd *rbd_opts;
>      BlockdevOptionsRbd *loc;
> +    RbdEncryptionCreateOptions *encrypt = NULL;
>      Error *local_err = NULL;
>      const char *keypairs, *password_secret;
>      QDict *options = NULL;
> @@ -431,6 +709,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
>          goto exit;
>      }
>
> +    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
> +    if (ret < 0) {
> +        goto exit;
> +    }
> +    rbd_opts->encrypt     = encrypt;
> +    rbd_opts->has_encrypt = !!encrypt;
> +
>      /*
>       * Caution: while qdict_get_try_str() is fine, getting non-string
>       * types would require more care.  When @options come from -blockdev
> @@ -756,12 +1041,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1067,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 +1077,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);
> @@ -1050,6 +1349,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
>      return 0;
>  }
>
> +static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
> +                                                     Error **errp)
> +{
> +    BDRVRBDState *s = bs->opaque;
> +    ImageInfoSpecific *spec_info;
> +    rbd_image_info_t info;
> +    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
> +    int r;
> +
> +    r = rbd_stat(s->image, &info, sizeof(info));

Ditto -- rbd_get_size().

> +    if (r < 0) {
> +        error_setg_errno(errp, -r, "cannot get image size");
> +        return NULL;
> +    }
> +
> +    if (info.size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
> +        r = rbd_read(s->image, 0,
> +                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf) ;

Stray space before semicolon.

> +        if (r < 0) {
> +            error_setg_errno(errp, -r, "cannot read image start for probe");
> +            return NULL;
> +        }
> +    }
> +
> +    spec_info = g_new(ImageInfoSpecific, 1);
> +    *spec_info = (ImageInfoSpecific){
> +        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
> +        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
> +    };
> +
> +    if (memcmp(buf, rbd_luks_header_verification,
> +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> +        spec_info->u.rbd.data->encryption_format =
> +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
> +        spec_info->u.rbd.data->has_encryption_format = true;
> +    } else if (memcmp(buf, rbd_luks2_header_verification,
> +               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
> +        spec_info->u.rbd.data->encryption_format =
> +                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
> +        spec_info->u.rbd.data->has_encryption_format = true;
> +    } else {
> +        spec_info->u.rbd.data->has_encryption_format = false;
> +    }
> +
> +    return spec_info;
> +}
> +
>  static int64_t qemu_rbd_getlength(BlockDriverState *bs)
>  {
>      BDRVRBDState *s = bs->opaque;
> @@ -1243,6 +1589,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: 'luks', 'luks2'",

I think it should be "luks1" and "luks2" to match rbd/librbd.h and
"rbd encryption format" command.

> +        },
> +        {
> +            .name = "encrypt.cipher-alg",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Name of encryption cipher algorithm"
> +                    " (allowed values: aes-128, aes-256)",
> +        },
> +        {
> +            .name = "encrypt.key-secret",
> +            .type = QEMU_OPT_STRING,
> +            .help = "ID of secret providing LUKS passphrase",
> +        },

A question probably better suited for QEMU maintainers but just in
case you have already asked and clarified this matter...

There seem to be two image creation APIs: the old option-based thing
and the new QAPI-based x-blockdev-create command.  This patch extends
both and implements the old -> new translation.  Is this the right way
to do it?  Is the option-based interface supposed to be extended when
adding new functionality?

>          { /* end of list */ }
>      }
>  };
> @@ -1272,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
>      .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
>      .bdrv_has_zero_init     = bdrv_has_zero_init_1,
>      .bdrv_get_info          = qemu_rbd_getinfo,
> +    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
>      .create_opts            = &qemu_rbd_create_opts,
>      .bdrv_getlength         = qemu_rbd_getlength,
>      .bdrv_co_truncate       = qemu_rbd_co_truncate,
> diff --git a/qapi/block-core.json b/qapi/block-core.json
> index 6d227924d0..60d2ff0d1a 100644
> --- a/qapi/block-core.json
> +++ b/qapi/block-core.json
> @@ -127,6 +127,18 @@
>        'extents': ['ImageInfo']
>    } }
>
> +##
> +# @ImageInfoSpecificRbd:
> +#
> +# @encryption-format: Image encryption format
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'ImageInfoSpecificRbd',
> +  'data': {
> +      '*encryption-format': 'RbdImageEncryptionFormat'
> +  } }
> +
>  ##
>  # @ImageInfoSpecific:
>  #
> @@ -141,7 +153,8 @@
>        # If we need to add block driver specific parameters for
>        # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
>        # to define a ImageInfoSpecificLUKS
> -      'luks': 'QCryptoBlockInfoLUKS'
> +      'luks': 'QCryptoBlockInfoLUKS',
> +      'rbd': 'ImageInfoSpecificRbd'
>    } }
>
>  ##
> @@ -3609,6 +3622,94 @@
>  { 'enum': 'RbdAuthMode',
>    'data': [ 'cephx', 'none' ] }
>
> +##
> +# @RbdImageEncryptionFormat:
> +#
> +# Since: 6.1
> +##
> +{ 'enum': 'RbdImageEncryptionFormat',
> +  'data': [ 'luks', 'luks2' ] }

Ditto -- "luks1" and "luks2".

> +
> +##
> +# @RbdEncryptionOptionsLUKSBase:
> +#
> +# @key-secret: ID of a QCryptoSecret object providing a passphrase
> +#              for unlocking the encryption
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKSBase',
> +  'data': { '*key-secret': 'str' }}

When would we not need a passphrase?  I think it should be required.

> +
> +##
> +# @RbdEncryptionCreateOptionsLUKSBase:
> +#
> +# @cipher-alg: The encryption algorithm
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}

Why QCryptoCipherAlgorithm instead of just enumerating the two
algorithms that librbd supports?  An early failure when parsing
seems better than failing in qemu_rbd_convert_luks_create_options()
and having to clean up the newly created image.

> +
> +##
> +# @RbdEncryptionOptionsLUKS:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKS',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionOptionsLUKS2:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionOptionsLUKS2',
> +  'base': 'RbdEncryptionOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionCreateOptionsLUKS:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
> +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'data': {}}
> +
> +##
> +# @RbdEncryptionCreateOptionsLUKS2:
> +#
> +# Since: 6.1
> +##
> +{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
> +  'base': 'RbdEncryptionCreateOptionsLUKSBase',
> +  'data': {}}

This appears over-engineered to me.  A three-deep hierarchy for
a structure with two fields (key-secret and cipher-alg) seems like
it could be simplified.

Why differentiate between luks1 and luks2 if the fields are exactly
the same?  Do you expect one of these empty derived structures to be
extended in the future?

Thanks,

                Ilya


^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-06-17 16:05 Or Ozeri
  2021-06-19 19:44 ` Ilya Dryomov
  2021-06-20 12:46 ` Or Ozeri
  0 siblings, 2 replies; 25+ messages in thread
From: Or Ozeri @ 2021-06-17 16:05 UTC (permalink / raw)
  To: qemu-devel
  Cc: kwolf, berrange, qemu-block, dannyh, oro, idryomov, to.my.trociny

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/raw-format.c   |   7 +
 block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json | 110 ++++++++++++-
 3 files changed, 482 insertions(+), 6 deletions(-)

diff --git a/block/raw-format.c b/block/raw-format.c
index 7717578ed6..f6e70e2356 100644
--- a/block/raw-format.c
+++ b/block/raw-format.c
@@ -369,6 +369,12 @@ static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
     return bdrv_get_info(bs->file->bs, bdi);
 }
 
+static ImageInfoSpecific *raw_get_specific_info(BlockDriverState *bs,
+                                                Error **errp)
+{
+    return bdrv_get_specific_info(bs->file->bs, errp);
+}
+
 static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
 {
     if (bs->probed) {
@@ -603,6 +609,7 @@ BlockDriver bdrv_raw = {
     .has_variable_length  = true,
     .bdrv_measure         = &raw_measure,
     .bdrv_get_info        = &raw_get_info,
+    .bdrv_get_specific_info = &raw_get_specific_info,
     .bdrv_refresh_limits  = &raw_refresh_limits,
     .bdrv_probe_blocksizes = &raw_probe_blocksizes,
     .bdrv_probe_geometry  = &raw_probe_geometry,
diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..183b17cd84 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -73,6 +73,18 @@
 #define LIBRBD_USE_IOVEC 0
 #endif
 
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
+
 typedef enum {
     RBD_AIO_READ,
     RBD_AIO_WRITE,
@@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        char **passphrase,
+        Error **errp)
+{
+    int r = 0;
+
+    if (!luks_opts->has_key_secret) {
+        r = -EINVAL;
+        error_setg_errno(errp, -r, "missing encrypt.key-secret");
+        return r;
+    }
+
+    *passphrase = qcrypto_secret_lookup_as_utf8(luks_opts->key_secret, errp);
+    if (!*passphrase) {
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_convert_luks_create_options(
+        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char **passphrase,
+        Error **errp)
+{
+    int r = 0;
+
+    r = qemu_rbd_convert_luks_options(
+            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+            passphrase, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case QCRYPTO_CIPHER_ALG_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
+                                      RbdEncryptionCreateOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    g_autofree rbd_encryption_options_t opts = NULL;
+    rbd_encryption_format_t format;
+    rbd_image_info_t info;
+    size_t opts_size;
+    uint64_t raw_size;
+
+    r = rbd_stat(image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return r;
+    }
+    raw_size = info.size;
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            rbd_encryption_luks1_format_options_t *luks_opts =
+                    g_new0(rbd_encryption_luks1_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = luks_opts;
+            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+                    &luks_opts->alg, &passphrase, errp);
+            luks_opts->passphrase = passphrase;
+            luks_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_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
+                            &encrypt->u.luks2),
+                    &luks2_opts->alg, &passphrase, errp);
+            luks2_opts->passphrase = passphrase;
+            luks2_opts->passphrase_size = strlen(passphrase);
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+        }
+    }
+
+    if (r < 0) {
+        return r;
+    }
+
+    r = rbd_encryption_format(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption format fail");
+        return r;
+    }
+
+    r = rbd_stat(image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get effective image size");
+        return r;
+    }
+
+    r = rbd_resize(image, raw_size + (raw_size - info.size));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot resize image after format");
+        return r;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+                                      RbdEncryptionOptions *encrypt,
+                                      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 (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            rbd_encryption_luks1_format_options_t *luks_opts =
+                    g_new0(rbd_encryption_luks1_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = luks_opts;
+            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+                    &passphrase, errp);
+            luks_opts->passphrase = passphrase;
+            luks_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(&encrypt->u.luks2),
+                    &passphrase, errp);
+            luks2_opts->passphrase = passphrase;
+            luks2_opts->passphrase_size = strlen(passphrase);
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+        }
+    }
+
+    if (r < 0) {
+        return r;
+    }
+
+    r = rbd_encryption_load(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption load fail");
+    }
+
+    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 +570,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 +602,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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_encryption_format(image, opts->encrypt, 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 +635,43 @@ 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_create_options(
+        QemuOpts *opts,
+        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +680,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionCreateOptions *encrypt = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +709,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+    rbd_opts->encrypt     = encrypt;
+    rbd_opts->has_encrypt = !!encrypt;
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -756,12 +1041,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1067,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 +1077,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);
@@ -1050,6 +1349,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
     return 0;
 }
 
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+                                                     Error **errp)
+{
+    BDRVRBDState *s = bs->opaque;
+    ImageInfoSpecific *spec_info;
+    rbd_image_info_t info;
+    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
+    int r;
+
+    r = rbd_stat(s->image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get image size");
+        return NULL;
+    }
+
+    if (info.size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+        r = rbd_read(s->image, 0,
+                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf) ;
+        if (r < 0) {
+            error_setg_errno(errp, -r, "cannot read image start for probe");
+            return NULL;
+        }
+    }
+
+    spec_info = g_new(ImageInfoSpecific, 1);
+    *spec_info = (ImageInfoSpecific){
+        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+    };
+
+    if (memcmp(buf, rbd_luks_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else if (memcmp(buf, rbd_luks2_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else {
+        spec_info->u.rbd.data->has_encryption_format = false;
+    }
+
+    return spec_info;
+}
+
 static int64_t qemu_rbd_getlength(BlockDriverState *bs)
 {
     BDRVRBDState *s = bs->opaque;
@@ -1243,6 +1589,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: 'luks', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.key-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
@@ -1272,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
     .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
     .bdrv_has_zero_init     = bdrv_has_zero_init_1,
     .bdrv_get_info          = qemu_rbd_getinfo,
+    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
     .create_opts            = &qemu_rbd_create_opts,
     .bdrv_getlength         = qemu_rbd_getlength,
     .bdrv_co_truncate       = qemu_rbd_co_truncate,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..60d2ff0d1a 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -127,6 +127,18 @@
       'extents': ['ImageInfo']
   } }
 
+##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+  'data': {
+      '*encryption-format': 'RbdImageEncryptionFormat'
+  } }
+
 ##
 # @ImageInfoSpecific:
 #
@@ -141,7 +153,8 @@
       # If we need to add block driver specific parameters for
       # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
       # to define a ImageInfoSpecificLUKS
-      'luks': 'QCryptoBlockInfoLUKS'
+      'luks': 'QCryptoBlockInfoLUKS',
+      'rbd': 'ImageInfoSpecificRbd'
   } }
 
 ##
@@ -3609,6 +3622,94 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+#              for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*key-secret': 'str' }}
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+            'luks2': 'RbdEncryptionOptionsLUKS2'} }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+            'luks2': 'RbdEncryptionCreateOptionsLUKS2'} }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3725,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption options. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3749,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionOptions',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
@@ -4418,13 +4522,15 @@
 #            point to a snapshot.
 # @size: Size of the virtual disk in bytes
 # @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
 #
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsRbd',
   'data': { 'location':         'BlockdevOptionsRbd',
             'size':             'size',
-            '*cluster-size' :   'size' } }
+            '*cluster-size' :   'size',
+            '*encrypt' :        'RbdEncryptionCreateOptions'} }
 
 ##
 # @BlockdevVmdkSubformat:
-- 
2.27.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-05-05 16:48 Or Ozeri
  0 siblings, 0 replies; 25+ messages in thread
From: Or Ozeri @ 2021-05-05 16:48 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, berrange, qemu-block, dannyh, oro, to.my.trociny

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/raw-format.c   |   7 +
 block/rbd.c          | 371 ++++++++++++++++++++++++++++++++++++++++++-
 qapi/block-core.json | 110 ++++++++++++-
 3 files changed, 482 insertions(+), 6 deletions(-)

diff --git a/block/raw-format.c b/block/raw-format.c
index 7717578ed6..f6e70e2356 100644
--- a/block/raw-format.c
+++ b/block/raw-format.c
@@ -369,6 +369,12 @@ static int raw_get_info(BlockDriverState *bs, BlockDriverInfo *bdi)
     return bdrv_get_info(bs->file->bs, bdi);
 }
 
+static ImageInfoSpecific *raw_get_specific_info(BlockDriverState *bs,
+                                                Error **errp)
+{
+    return bdrv_get_specific_info(bs->file->bs, errp);
+}
+
 static void raw_refresh_limits(BlockDriverState *bs, Error **errp)
 {
     if (bs->probed) {
@@ -603,6 +609,7 @@ BlockDriver bdrv_raw = {
     .has_variable_length  = true,
     .bdrv_measure         = &raw_measure,
     .bdrv_get_info        = &raw_get_info,
+    .bdrv_get_specific_info = &raw_get_specific_info,
     .bdrv_refresh_limits  = &raw_refresh_limits,
     .bdrv_probe_blocksizes = &raw_probe_blocksizes,
     .bdrv_probe_geometry  = &raw_probe_geometry,
diff --git a/block/rbd.c b/block/rbd.c
index f098a89c7b..183b17cd84 100644
--- a/block/rbd.c
+++ b/block/rbd.c
@@ -73,6 +73,18 @@
 #define LIBRBD_USE_IOVEC 0
 #endif
 
+#define RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN 8
+
+static const char rbd_luks_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 1
+};
+
+static const char rbd_luks2_header_verification[
+        RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {
+    'L', 'U', 'K', 'S', 0xBA, 0xBE, 0, 2
+};
+
 typedef enum {
     RBD_AIO_READ,
     RBD_AIO_WRITE,
@@ -341,6 +353,206 @@ static void qemu_rbd_memset(RADOSCB *rcb, int64_t offs)
     }
 }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+static int qemu_rbd_convert_luks_options(
+        RbdEncryptionOptionsLUKSBase *luks_opts,
+        char **passphrase,
+        Error **errp)
+{
+    int r = 0;
+
+    if (!luks_opts->has_key_secret) {
+        r = -EINVAL;
+        error_setg_errno(errp, -r, "missing encrypt.key-secret");
+        return r;
+    }
+
+    *passphrase = qcrypto_secret_lookup_as_utf8(luks_opts->key_secret, errp);
+    if (!*passphrase) {
+        return -EIO;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_convert_luks_create_options(
+        RbdEncryptionCreateOptionsLUKSBase *luks_opts,
+        rbd_encryption_algorithm_t *alg,
+        char **passphrase,
+        Error **errp)
+{
+    int r = 0;
+
+    r = qemu_rbd_convert_luks_options(
+            qapi_RbdEncryptionCreateOptionsLUKSBase_base(luks_opts),
+            passphrase, errp);
+    if (r < 0) {
+        return r;
+    }
+
+    if (luks_opts->has_cipher_alg) {
+        switch (luks_opts->cipher_alg) {
+            case QCRYPTO_CIPHER_ALG_AES_128: {
+                *alg = RBD_ENCRYPTION_ALGORITHM_AES128;
+                break;
+            }
+            case QCRYPTO_CIPHER_ALG_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_encryption_format(rbd_image_t image,
+                                      RbdEncryptionCreateOptions *encrypt,
+                                      Error **errp)
+{
+    int r = 0;
+    g_autofree char *passphrase = NULL;
+    g_autofree rbd_encryption_options_t opts = NULL;
+    rbd_encryption_format_t format;
+    rbd_image_info_t info;
+    size_t opts_size;
+    uint64_t raw_size;
+
+    r = rbd_stat(image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get raw image size");
+        return r;
+    }
+    raw_size = info.size;
+
+    switch (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            rbd_encryption_luks1_format_options_t *luks_opts =
+                    g_new0(rbd_encryption_luks1_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = luks_opts;
+            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
+            r = qemu_rbd_convert_luks_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS_base(&encrypt->u.luks),
+                    &luks_opts->alg, &passphrase, errp);
+            luks_opts->passphrase = passphrase;
+            luks_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_create_options(
+                    qapi_RbdEncryptionCreateOptionsLUKS2_base(
+                            &encrypt->u.luks2),
+                    &luks2_opts->alg, &passphrase, errp);
+            luks2_opts->passphrase = passphrase;
+            luks2_opts->passphrase_size = strlen(passphrase);
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+        }
+    }
+
+    if (r < 0) {
+        return r;
+    }
+
+    r = rbd_encryption_format(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption format fail");
+        return r;
+    }
+
+    r = rbd_stat(image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get effective image size");
+        return r;
+    }
+
+    r = rbd_resize(image, raw_size + (raw_size - info.size));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot resize image after format");
+        return r;
+    }
+
+    return 0;
+}
+
+static int qemu_rbd_encryption_load(rbd_image_t image,
+                                      RbdEncryptionOptions *encrypt,
+                                      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 (encrypt->format) {
+        case RBD_IMAGE_ENCRYPTION_FORMAT_LUKS: {
+            rbd_encryption_luks1_format_options_t *luks_opts =
+                    g_new0(rbd_encryption_luks1_format_options_t, 1);
+            format = RBD_ENCRYPTION_FORMAT_LUKS1;
+            opts = luks_opts;
+            opts_size = sizeof(rbd_encryption_luks1_format_options_t);
+            r = qemu_rbd_convert_luks_options(
+                    qapi_RbdEncryptionOptionsLUKS_base(&encrypt->u.luks),
+                    &passphrase, errp);
+            luks_opts->passphrase = passphrase;
+            luks_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(&encrypt->u.luks2),
+                    &passphrase, errp);
+            luks2_opts->passphrase = passphrase;
+            luks2_opts->passphrase_size = strlen(passphrase);
+            break;
+        }
+        default: {
+            r = -ENOTSUP;
+            error_setg_errno(
+                    errp, -r, "unknown image encryption format: %u",
+                    encrypt->format);
+        }
+    }
+
+    if (r < 0) {
+        return r;
+    }
+
+    r = rbd_encryption_load(image, format, opts, opts_size);
+    if (r < 0) {
+        error_setg_errno(errp, -r, "encryption load fail");
+    }
+
+    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 +570,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 +602,27 @@ static int qemu_rbd_do_create(BlockdevCreateOptions *options,
         goto out;
     }
 
+#ifdef LIBRBD_SUPPORTS_ENCRYPTION
+    if (opts->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_encryption_format(image, opts->encrypt, 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 +635,43 @@ 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_create_options(
+        QemuOpts *opts,
+        RbdEncryptionCreateOptions **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_RbdEncryptionCreateOptions(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 +680,7 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
     BlockdevCreateOptions *create_options;
     BlockdevCreateOptionsRbd *rbd_opts;
     BlockdevOptionsRbd *loc;
+    RbdEncryptionCreateOptions *encrypt = NULL;
     Error *local_err = NULL;
     const char *keypairs, *password_secret;
     QDict *options = NULL;
@@ -431,6 +709,13 @@ static int coroutine_fn qemu_rbd_co_create_opts(BlockDriver *drv,
         goto exit;
     }
 
+    ret = qemu_rbd_extract_encryption_create_options(opts, &encrypt, errp);
+    if (ret < 0) {
+        goto exit;
+    }
+    rbd_opts->encrypt     = encrypt;
+    rbd_opts->has_encrypt = !!encrypt;
+
     /*
      * Caution: while qdict_get_try_str() is fine, getting non-string
      * types would require more care.  When @options come from -blockdev
@@ -756,12 +1041,25 @@ 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_encryption_load(s->image, opts->encrypt, 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 +1067,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 +1077,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);
@@ -1050,6 +1349,53 @@ static int qemu_rbd_getinfo(BlockDriverState *bs, BlockDriverInfo *bdi)
     return 0;
 }
 
+static ImageInfoSpecific *qemu_rbd_get_specific_info(BlockDriverState *bs,
+                                                     Error **errp)
+{
+    BDRVRBDState *s = bs->opaque;
+    ImageInfoSpecific *spec_info;
+    rbd_image_info_t info;
+    char buf[RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN] = {0};
+    int r;
+
+    r = rbd_stat(s->image, &info, sizeof(info));
+    if (r < 0) {
+        error_setg_errno(errp, -r, "cannot get image size");
+        return NULL;
+    }
+
+    if (info.size >= RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) {
+        r = rbd_read(s->image, 0,
+                     RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN, buf) ;
+        if (r < 0) {
+            error_setg_errno(errp, -r, "cannot read image start for probe");
+            return NULL;
+        }
+    }
+
+    spec_info = g_new(ImageInfoSpecific, 1);
+    *spec_info = (ImageInfoSpecific){
+        .type  = IMAGE_INFO_SPECIFIC_KIND_RBD,
+        .u.rbd.data = g_new0(ImageInfoSpecificRbd, 1),
+    };
+
+    if (memcmp(buf, rbd_luks_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else if (memcmp(buf, rbd_luks2_header_verification,
+               RBD_ENCRYPTION_LUKS_HEADER_VERIFICATION_LEN) == 0) {
+        spec_info->u.rbd.data->encryption_format =
+                RBD_IMAGE_ENCRYPTION_FORMAT_LUKS2;
+        spec_info->u.rbd.data->has_encryption_format = true;
+    } else {
+        spec_info->u.rbd.data->has_encryption_format = false;
+    }
+
+    return spec_info;
+}
+
 static int64_t qemu_rbd_getlength(BlockDriverState *bs)
 {
     BDRVRBDState *s = bs->opaque;
@@ -1243,6 +1589,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: 'luks', 'luks2'",
+        },
+        {
+            .name = "encrypt.cipher-alg",
+            .type = QEMU_OPT_STRING,
+            .help = "Name of encryption cipher algorithm"
+                    " (allowed values: aes-128, aes-256)",
+        },
+        {
+            .name = "encrypt.key-secret",
+            .type = QEMU_OPT_STRING,
+            .help = "ID of secret providing LUKS passphrase",
+        },
         { /* end of list */ }
     }
 };
@@ -1272,6 +1634,7 @@ static BlockDriver bdrv_rbd = {
     .bdrv_co_create_opts    = qemu_rbd_co_create_opts,
     .bdrv_has_zero_init     = bdrv_has_zero_init_1,
     .bdrv_get_info          = qemu_rbd_getinfo,
+    .bdrv_get_specific_info = qemu_rbd_get_specific_info,
     .create_opts            = &qemu_rbd_create_opts,
     .bdrv_getlength         = qemu_rbd_getlength,
     .bdrv_co_truncate       = qemu_rbd_co_truncate,
diff --git a/qapi/block-core.json b/qapi/block-core.json
index 6d227924d0..60d2ff0d1a 100644
--- a/qapi/block-core.json
+++ b/qapi/block-core.json
@@ -127,6 +127,18 @@
       'extents': ['ImageInfo']
   } }
 
+##
+# @ImageInfoSpecificRbd:
+#
+# @encryption-format: Image encryption format
+#
+# Since: 6.1
+##
+{ 'struct': 'ImageInfoSpecificRbd',
+  'data': {
+      '*encryption-format': 'RbdImageEncryptionFormat'
+  } }
+
 ##
 # @ImageInfoSpecific:
 #
@@ -141,7 +153,8 @@
       # If we need to add block driver specific parameters for
       # LUKS in future, then we'll subclass QCryptoBlockInfoLUKS
       # to define a ImageInfoSpecificLUKS
-      'luks': 'QCryptoBlockInfoLUKS'
+      'luks': 'QCryptoBlockInfoLUKS',
+      'rbd': 'ImageInfoSpecificRbd'
   } }
 
 ##
@@ -3609,6 +3622,94 @@
 { 'enum': 'RbdAuthMode',
   'data': [ 'cephx', 'none' ] }
 
+##
+# @RbdImageEncryptionFormat:
+#
+# Since: 6.1
+##
+{ 'enum': 'RbdImageEncryptionFormat',
+  'data': [ 'luks', 'luks2' ] }
+
+##
+# @RbdEncryptionOptionsLUKSBase:
+#
+# @key-secret: ID of a QCryptoSecret object providing a passphrase
+#              for unlocking the encryption
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*key-secret': 'str' }}
+
+##
+# @RbdEncryptionCreateOptionsLUKSBase:
+#
+# @cipher-alg: The encryption algorithm
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKSBase',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': { '*cipher-alg': 'QCryptoCipherAlgorithm'}}
+
+##
+# @RbdEncryptionOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionOptionsLUKS2',
+  'base': 'RbdEncryptionOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionCreateOptionsLUKS2:
+#
+# Since: 6.1
+##
+{ 'struct': 'RbdEncryptionCreateOptionsLUKS2',
+  'base': 'RbdEncryptionCreateOptionsLUKSBase',
+  'data': {}}
+
+##
+# @RbdEncryptionOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionOptionsLUKS',
+            'luks2': 'RbdEncryptionOptionsLUKS2'} }
+
+##
+# @RbdEncryptionCreateOptions:
+#
+# Since: 6.1
+##
+{ 'union': 'RbdEncryptionCreateOptions',
+  'base': { 'format': 'RbdImageEncryptionFormat' },
+  'discriminator': 'format',
+  'data': { 'luks': 'RbdEncryptionCreateOptionsLUKS',
+            'luks2': 'RbdEncryptionCreateOptionsLUKS2'} }
+
 ##
 # @BlockdevOptionsRbd:
 #
@@ -3624,6 +3725,8 @@
 #
 # @snapshot: Ceph snapshot name.
 #
+# @encrypt: Image encryption options. (Since 6.1)
+#
 # @user: Ceph id name.
 #
 # @auth-client-required: Acceptable authentication modes.
@@ -3646,6 +3749,7 @@
             'image': 'str',
             '*conf': 'str',
             '*snapshot': 'str',
+            '*encrypt': 'RbdEncryptionOptions',
             '*user': 'str',
             '*auth-client-required': ['RbdAuthMode'],
             '*key-secret': 'str',
@@ -4418,13 +4522,15 @@
 #            point to a snapshot.
 # @size: Size of the virtual disk in bytes
 # @cluster-size: RBD object size
+# @encrypt: Image encryption options. (Since 6.1)
 #
 # Since: 2.12
 ##
 { 'struct': 'BlockdevCreateOptionsRbd',
   'data': { 'location':         'BlockdevOptionsRbd',
             'size':             'size',
-            '*cluster-size' :   'size' } }
+            '*cluster-size' :   'size',
+            '*encrypt' :        'RbdEncryptionCreateOptions'} }
 
 ##
 # @BlockdevVmdkSubformat:
-- 
2.27.0



^ permalink raw reply related	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-05-05 15:32 ` Or Ozeri
@ 2021-05-05 15:36   ` Daniel P. Berrangé
  0 siblings, 0 replies; 25+ messages in thread
From: Daniel P. Berrangé @ 2021-05-05 15:36 UTC (permalink / raw)
  To: Or Ozeri; +Cc: kwolf, Danny Harnik, qemu-devel, qemu-block, to.my.trociny

On Wed, May 05, 2021 at 03:32:09PM +0000, Or Ozeri wrote:
>    Thanks Daniel!
>    I prepared a modified patch addressing all of your suggestions (including
>    resizing after formatting to increase the image size).
>    The only thing I'm not sure about is your last point regarding reporting
>    image is encrypted.
>    When I followed the flow of "qemu-img info" on an "rbd:pool/image" I saw
>    that this information is extracted from the root BlockDriverState.
>    In our case, the root BlockDriverState comes from:
>    BlockDriver bdrv_raw = {
>        .format_name          = "raw",
>    ...
>    The RBD driver is a child of this root raw driver.
>    Given this situation, it is not clear to me how can I set:
>    bs->drv->format_name="luks2", bs->encrypted=true
>    On the root BlockDriverState.
>    Any advice?


In the QAPI schema  block-core.json there is a "ImageInfoSpecific"
union that gives format specific information. IIUC, you should
need to implement that for RBD to provide extra info. I can't
remember whether qemu-img info will automagically print this or
if extra printing code is needed too.

>    -----"Daniel P. Berrangé" <[1]berrange@redhat.com> wrote: -----
>    To: Or Ozeri <[2]oro@il.ibm.com>
>    From: "Daniel P. Berrangé" <[3]berrange@redhat.com>
>    Date: 05/04/2021 05:47PM
>    Cc: [4]qemu-devel@nongnu.org, [5]kwolf@redhat.com,
>    [6]to.my.trociny@gmail.com, [7]qemu-block@nongnu.org, [8]dannyh@il.ibm.com
>    Subject: [EXTERNAL] Re: [PATCH] block/rbd: Add support for rbd image
>    encryption
> 
>    On Sun, May 02, 2021 at 10:36:17AM +0300, Or Ozeri wrote:
>    > 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 <[9]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
> 
>    What happens to the size of the image when creating with luks ? LUKS
>    will typically have at least 1 MB of overhead for the headers. When
>    QEMU gets told to create an image with a size "512MB" that refers to
>    the guest exposed size. So with LUKS QEMU might allocate 513 MB instead
>    on the host POV, to ensure the guest POV is still 512 MB.  Does RBD
>    do this correctly and if not, can QEMU adjust to take this into account ?
> 
>    > +
>    >      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' ] }
> 
>    We already have LUKS support in QEMU that we use with raw files
>    and with qcow2, and here we call LUKSv1 simply "luks".
> 
>    IOW, I think we should just have "luks" and "luks2"
> 
>    > +
>    > +##
>    > +# @RbdEncryptionAlgorithm:
>    > +#
>    > +# Since: 6.1
>    > +##
>    > +{ 'enum': 'RbdEncryptionAlgorithm',
>    > +  'data': [ 'aes-128', 'aes-256' ] }
> 
>    We already have a QCryptoCipherAlgorithm we should be using
> 
>    > +
>    > +##
>    > +# @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' }}
> 
>    For other block devices we've called it "key-secret", so please
>    lets keep the same terminology
> 
>    IIUC, cipher-alg is only relevant when creating a new
>    disk image.
> 
>    IOW, we ought to have separate option structs for the code
>    paths for opening and creating images, since the format
>    only wants a secret.
> 
>    > +
>    > +##
>    > +# @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',
> 
>    Is there any way to report that LUKS is enabled for a given image ?
> 
>    For an application to specify the "key-secret" for encryption, we
>    need to know that the image is encrypted.  For the existing raw
>    and qcow2 file luks support we have this reported with "qemu-img info"
> 
>    Regards,
>    Daniel
>    --
>    |: [10]https://berrange.com       -o-  
>     [11]https://www.flickr.com/photos/dberrange  :|
>    |: [12]https://libvirt.org          -o-          
>     [13]https://fstop138.berrange.com  :|
>    |: [14]https://entangle-photo.org     -o-  
>     [15]https://www.instagram.com/dberrange  :|
> 
> References
> 
>    Visible links
>    1. mailto:berrange@redhat.com
>    2. mailto:oro@il.ibm.com
>    3. mailto:berrange@redhat.com
>    4. mailto:qemu-devel@nongnu.org
>    5. mailto:kwolf@redhat.com
>    6. mailto:to.my.trociny@gmail.com
>    7. mailto:qemu-block@nongnu.org
>    8. mailto:dannyh@il.ibm.com
>    9. mailto:oro@il.ibm.com
>   10. https://berrange.com/
>   11. https://www.flickr.com/photos/dberrange
>   12. https://libvirt.org/
>   13. https://fstop138.berrange.com/
>   14. https://entangle-photo.org/
>   15. https://www.instagram.com/dberrange

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 25+ messages in thread

* RE: [PATCH] block/rbd: Add support for rbd image encryption
  2021-05-02  7:36 Or Ozeri
  2021-05-02 14:00 ` no-reply
  2021-05-04 14:46 ` Daniel P. Berrangé
@ 2021-05-05 15:32 ` Or Ozeri
  2021-05-05 15:36   ` Daniel P. Berrangé
  2 siblings, 1 reply; 25+ messages in thread
From: Or Ozeri @ 2021-05-05 15:32 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: kwolf, Danny Harnik, qemu-devel, qemu-block, to.my.trociny

[-- Attachment #1: Type: text/html, Size: 30133 bytes --]

^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-05-02  7:36 Or Ozeri
  2021-05-02 14:00 ` no-reply
@ 2021-05-04 14:46 ` Daniel P. Berrangé
  2021-05-05 15:32 ` Or Ozeri
  2 siblings, 0 replies; 25+ messages in thread
From: Daniel P. Berrangé @ 2021-05-04 14:46 UTC (permalink / raw)
  To: Or Ozeri; +Cc: kwolf, dannyh, qemu-devel, qemu-block, to.my.trociny

On Sun, May 02, 2021 at 10:36:17AM +0300, Or Ozeri wrote:
> 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

What happens to the size of the image when creating with luks ? LUKS
will typically have at least 1 MB of overhead for the headers. When
QEMU gets told to create an image with a size "512MB" that refers to
the guest exposed size. So with LUKS QEMU might allocate 513 MB instead
on the host POV, to ensure the guest POV is still 512 MB.  Does RBD
do this correctly and if not, can QEMU adjust to take this into account ?



> +
>      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' ] }

We already have LUKS support in QEMU that we use with raw files
and with qcow2, and here we call LUKSv1 simply "luks".

IOW, I think we should just have "luks" and "luks2" 

> +
> +##
> +# @RbdEncryptionAlgorithm:
> +#
> +# Since: 6.1
> +##
> +{ 'enum': 'RbdEncryptionAlgorithm',
> +  'data': [ 'aes-128', 'aes-256' ] }

We already have a QCryptoCipherAlgorithm we should be using

> +
> +##
> +# @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' }}

For other block devices we've called it "key-secret", so please
lets keep the same terminology

IIUC, cipher-alg is only relevant when creating a new
disk image.

IOW, we ought to have separate option structs for the code
paths for opening and creating images, since the format
only wants a secret.

> +
> +##
> +# @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',


Is there any way to report that LUKS is enabled for a given image ?

For an application to specify the "key-secret" for encryption, we
need to know that the image is encrypted.  For the existing raw
and qcow2 file luks support we have this reported with "qemu-img info"


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|



^ permalink raw reply	[flat|nested] 25+ messages in thread

* Re: [PATCH] block/rbd: Add support for rbd image encryption
  2021-05-02  7:36 Or Ozeri
@ 2021-05-02 14:00 ` no-reply
  2021-05-04 14:46 ` Daniel P. Berrangé
  2021-05-05 15:32 ` Or Ozeri
  2 siblings, 0 replies; 25+ messages in thread
From: no-reply @ 2021-05-02 14:00 UTC (permalink / raw)
  To: oro; +Cc: kwolf, qemu-block, qemu-devel, to.my.trociny, oro, dannyh

Patchew URL: https://patchew.org/QEMU/20210502073617.2978836-1-oro@il.ibm.com/



Hi,

This series seems to have some coding style problems. See output below for
more information:

Type: series
Message-id: 20210502073617.2978836-1-oro@il.ibm.com
Subject: [PATCH] block/rbd: Add support for rbd image encryption

=== TEST SCRIPT BEGIN ===
#!/bin/bash
git rev-parse base > /dev/null || exit 0
git config --local diff.renamelimit 0
git config --local diff.renames True
git config --local diff.algorithm histogram
./scripts/checkpatch.pl --mailback base..
=== TEST SCRIPT END ===

Updating 3c8cf5a9c21ff8782164d1def7f44bd888713384
From https://github.com/patchew-project/qemu
   8f860d2..53c5433  master     -> master
 * [new tag]         patchew/20210502073617.2978836-1-oro@il.ibm.com -> patchew/20210502073617.2978836-1-oro@il.ibm.com
Switched to a new branch 'test'
11af1d7 block/rbd: Add support for rbd image encryption

=== OUTPUT BEGIN ===
ERROR: "foo** bar" should be "foo **bar"
#51: FILE: block/rbd.c:355:
+        char** passphrase,

ERROR: "foo* bar" should be "foo *bar"
#94: FILE: block/rbd.c:398:
+                                              RbdEncryptionSpec* spec,

total: 2 errors, 0 warnings, 385 lines checked

Commit 11af1d704fbe (block/rbd: Add support for rbd image encryption) has style problems, please review.  If any of these errors
are false positives report them to the maintainer, see
CHECKPATCH in MAINTAINERS.
=== OUTPUT END ===

Test command exited with code: 1


The full log is available at
http://patchew.org/logs/20210502073617.2978836-1-oro@il.ibm.com/testing.checkpatch/?type=message.
---
Email generated automatically by Patchew [https://patchew.org/].
Please send your feedback to patchew-devel@redhat.com

^ permalink raw reply	[flat|nested] 25+ messages in thread

* [PATCH] block/rbd: Add support for rbd image encryption
@ 2021-05-02  7:36 Or Ozeri
  2021-05-02 14:00 ` no-reply
                   ` (2 more replies)
  0 siblings, 3 replies; 25+ messages in thread
From: Or Ozeri @ 2021-05-02  7:36 UTC (permalink / raw)
  To: qemu-devel; +Cc: kwolf, oro, to.my.trociny, qemu-block, dannyh

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



^ permalink raw reply related	[flat|nested] 25+ messages in thread

end of thread, other threads:[~2021-06-27 11:26 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-21 14:48 [PATCH] block/rbd: Add support for rbd image encryption Or Ozeri
2021-06-26 10:44 ` Ilya Dryomov
  -- strict thread matches above, loose matches on Subject: below --
2021-06-27  8:30 Or Ozeri
2021-06-27  8:44 ` Or Ozeri
2021-06-27 11:00   ` Ilya Dryomov
2021-06-27 11:09   ` Or Ozeri
2021-06-27 11:24     ` Ilya Dryomov
2021-06-21 14:21 Or Ozeri
2021-06-21 14:27 ` no-reply
2021-06-17 16:05 Or Ozeri
2021-06-19 19:44 ` Ilya Dryomov
2021-06-20 10:35   ` Ilya Dryomov
2021-06-21  8:32   ` Daniel P. Berrangé
2021-06-21 10:59     ` Ilya Dryomov
2021-06-21 11:04       ` Daniel P. Berrangé
2021-06-21 11:23         ` Ilya Dryomov
2021-06-21 11:27           ` Daniel P. Berrangé
2021-06-21 11:52             ` Ilya Dryomov
2021-06-20 12:46 ` Or Ozeri
2021-05-05 16:48 Or Ozeri
2021-05-02  7:36 Or Ozeri
2021-05-02 14:00 ` no-reply
2021-05-04 14:46 ` Daniel P. Berrangé
2021-05-05 15:32 ` Or Ozeri
2021-05-05 15:36   ` Daniel P. Berrangé

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.