]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: add cloned images encryption API
authorOr Ozeri <oro@il.ibm.com>
Thu, 4 Aug 2022 10:05:02 +0000 (13:05 +0300)
committerOr Ozeri <oro@il.ibm.com>
Thu, 25 Aug 2022 15:41:47 +0000 (18:41 +0300)
This commit extends encryption_format and adds encryption_load2 to librbd API,
which enables crypto formatting and loading of cloned images.
i.e. the child image is encrypted with a key different from its parent,
while keeping the child thinly-provisioned.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
src/include/rbd/librbd.h
src/include/rbd/librbd.hpp
src/librbd/api/Image.cc
src/librbd/api/Image.h
src/librbd/librbd.cc
src/pybind/rbd/c_rbd.pxd
src/pybind/rbd/mock_rbd.pxi
src/pybind/rbd/rbd.pyx
src/test/librbd/test_librbd.cc
src/test/pybind/test_rbd.py

index e0da63dd82218efe04e9a312bd36dfcee979d38a..0810794d54b45e229152d602e639163a08d35c80 100644 (file)
@@ -49,6 +49,7 @@ extern "C" {
 #define LIBRBD_SUPPORTS_WRITESAME 1
 #define LIBRBD_SUPPORTS_WRITE_ZEROES 1
 #define LIBRBD_SUPPORTS_ENCRYPTION 1
+#define LIBRBD_SUPPORTS_ENCRYPTION_LOAD2 1
 
 #if __GNUC__ >= 4
   #define CEPH_RBD_API          __attribute__ ((visibility ("default")))
@@ -386,6 +387,12 @@ typedef enum {
 
 typedef void *rbd_encryption_options_t;
 
+typedef struct {
+    rbd_encryption_format_t format;
+    rbd_encryption_options_t opts;
+    size_t opts_size;
+} rbd_encryption_spec_t;
+
 typedef struct {
     rbd_encryption_algorithm_t alg;
     const char* passphrase;
@@ -823,16 +830,49 @@ CEPH_RBD_API int rbd_deep_copy_with_progress(rbd_image_t image,
                                              void *cbdata);
 
 /* encryption */
+
+/*
+ * Format the image using the encryption spec specified by
+ * (format, opts, opts_size) tuple.
+ *
+ * For a flat (i.e. non-cloned) image, the new encryption is loaded
+ * implicitly, calling rbd_encryption_load() afterwards is not needed.
+ * If existing encryption is already loaded, it is automatically
+ * replaced with the new encryption.
+ *
+ * For a cloned image, the new encryption must be loaded explicitly.
+ * Existing encryption (if any) must not be loaded.
+ */
 CEPH_RBD_API int rbd_encryption_format(rbd_image_t image,
                                        rbd_encryption_format_t format,
                                        rbd_encryption_options_t opts,
                                        size_t opts_size);
-/* encryption will be loaded on all ancestor images,
- * until reaching an ancestor image which does not match any known format */
+/*
+ * Load the encryption spec specified by (format, opts, opts_size)
+ * tuple for the image and all ancestor images.  If an ancestor image
+ * which does not match any encryption format known to librbd is
+ * encountered, it - along with remaining ancestor images - is
+ * interpreted as plaintext.
+ */
 CEPH_RBD_API int rbd_encryption_load(rbd_image_t image,
                                      rbd_encryption_format_t format,
                                      rbd_encryption_options_t opts,
                                      size_t opts_size);
+/*
+ * Load encryption specs.  The first spec in the passed array is
+ * applied to the image itself, the second spec is applied to its
+ * ancestor image, the third spec is applied to the ancestor of
+ * that ancestor image and so on.
+ *
+ * If not enough specs are passed, the last spec is reused exactly as
+ * in rbd_encryption_load().  If an ancestor image for which the last
+ * spec is being reused turns out to not match any encryption format
+ * known to librbd, it - along with remaining ancestor images - is
+ * interpreted as plaintext.
+ */
+CEPH_RBD_API int rbd_encryption_load2(rbd_image_t image,
+                                      rbd_encryption_spec_t *specs,
+                                      size_t spec_count);
 
 /* snapshots */
 CEPH_RBD_API int rbd_snap_list(rbd_image_t image, rbd_snap_info_t *snaps,
index 6e84a0a0f5c9659c5979a036214ba48c5088aaca..14f48305c44b26e8473fb61d2428ec95704dc00a 100644 (file)
@@ -217,6 +217,7 @@ namespace librbd {
   typedef rbd_encryption_format_t encryption_format_t;
   typedef rbd_encryption_algorithm_t encryption_algorithm_t;
   typedef rbd_encryption_options_t encryption_options_t;
+  typedef rbd_encryption_spec_t encryption_spec_t;
 
   typedef struct {
     encryption_algorithm_t alg;
@@ -597,10 +598,9 @@ public:
   /* encryption */
   int encryption_format(encryption_format_t format, encryption_options_t opts,
                         size_t opts_size);
-  /* encryption will be loaded on all ancestor images,
-   * until reaching an ancestor image which does not match any known format */
   int encryption_load(encryption_format_t format, encryption_options_t opts,
                       size_t opts_size);
+  int encryption_load2(encryption_spec_t *specs, size_t spec_count);
 
   /* striping */
   uint64_t get_stripe_unit() const;
index ab62bf50c3016731176addd9f109bf39d7f19ee8..7f18ee5831400fa2bde3bdfb81c410fdfe9de9bc 100644 (file)
@@ -959,11 +959,6 @@ template <typename I>
 int Image<I>::encryption_format(I* ictx, encryption_format_t format,
                                 encryption_options_t opts, size_t opts_size,
                                 bool c_api) {
-  if (ictx->parent != nullptr) {
-    lderr(ictx->cct) << "cannot format a cloned image" << dendl;
-    return -ENOTSUP;
-  }
-
   crypto::EncryptionFormat<I>* result_format;
   auto r = util::create_encryption_format(
           ictx->cct, format, opts, opts_size, c_api, &result_format);
@@ -980,18 +975,21 @@ int Image<I>::encryption_format(I* ictx, encryption_format_t format,
 }
 
 template <typename I>
-int Image<I>::encryption_load(I* ictx, encryption_format_t format,
-                              encryption_options_t opts, size_t opts_size,
-                              bool c_api) {
-  crypto::EncryptionFormat<I>* result_format;
-  auto r = util::create_encryption_format(
-          ictx->cct, format, opts, opts_size, c_api, &result_format);
-  if (r != 0) {
-    return r;
-  }
-
+int Image<I>::encryption_load(I* ictx, encryption_spec_t *specs,
+                              size_t spec_count, bool c_api) {
   std::vector<std::unique_ptr<crypto::EncryptionFormat<I>>> formats;
-  formats.emplace_back(result_format);
+
+  for (size_t i = 0; i < spec_count; ++i) {
+    crypto::EncryptionFormat<I>* result_format;
+    auto r = util::create_encryption_format(
+            ictx->cct, specs[i].format, specs[i].opts, specs[i].opts_size,
+            c_api, &result_format);
+    if (r != 0) {
+      return r;
+    }
+
+    formats.emplace_back(result_format);
+  }
 
   C_SaferCond cond;
   auto req = librbd::crypto::LoadRequest<I>::create(
index 192f9b7a79983750548e0f62d93448c0042f0b50..e843f5b51e216b47f5b88da7dce28165598c360b 100644 (file)
@@ -73,9 +73,8 @@ struct Image {
   static int encryption_format(ImageCtxT *ictx, encryption_format_t format,
                                encryption_options_t opts, size_t opts_size,
                                bool c_api);
-  static int encryption_load(ImageCtxT *ictx, encryption_format_t format,
-                             encryption_options_t opts, size_t opts_size,
-                             bool c_api);
+  static int encryption_load(ImageCtxT *ictx, encryption_spec_t *specs,
+                             size_t spec_count, bool c_api);
 
 };
 
index 28bb2449d75208cde7f4453e53f6e0a13ead002f..3c0a411ea2595cda4d83834305922d84da3e89b2 100644 (file)
@@ -2095,10 +2095,17 @@ namespace librbd {
   int Image::encryption_load(encryption_format_t format,
                              encryption_options_t opts,
                              size_t opts_size)
+  {
+    ImageCtx *ictx = (ImageCtx *)ctx;
+    encryption_spec_t spec = {format, opts, opts_size};
+    return librbd::api::Image<>::encryption_load(ictx, &spec, 1, false);
+  }
+
+  int Image::encryption_load2(encryption_spec_t *specs, size_t spec_count)
   {
     ImageCtx *ictx = (ImageCtx *)ctx;
     return librbd::api::Image<>::encryption_load(
-            ictx, format, opts, opts_size, false);
+            ictx, specs, spec_count, false);
   }
 
   int Image::flatten()
@@ -4382,8 +4389,16 @@ extern "C" int rbd_encryption_load(rbd_image_t image,
                                    size_t opts_size)
 {
   librbd::ImageCtx *ictx = (librbd::ImageCtx *)image;
-  return librbd::api::Image<>::encryption_load(
-          ictx, format, opts, opts_size, true);
+  librbd::encryption_spec_t spec = {format, opts, opts_size};
+  return librbd::api::Image<>::encryption_load(ictx, &spec, 1, true);
+}
+
+extern "C" int rbd_encryption_load2(rbd_image_t image,
+                                    rbd_encryption_spec_t *specs,
+                                    size_t spec_count)
+{
+  librbd::ImageCtx *ictx = (librbd::ImageCtx *)image;
+  return librbd::api::Image<>::encryption_load(ictx, specs, spec_count, true);
 }
 
 extern "C" int rbd_flatten(rbd_image_t image)
index cca2c0327e5f66eb269d718646c5896c5122bdeb..2415290b175bb51208808699dfbbd583de65be28 100644 (file)
@@ -287,6 +287,11 @@ cdef extern from "rbd/librbd.h" nogil:
 
     ctypedef void* rbd_encryption_options_t
 
+    ctypedef struct rbd_encryption_spec_t:
+        rbd_encryption_format_t format
+        rbd_encryption_options_t opts
+        size_t opts_size
+
     ctypedef void (*rbd_callback_t)(rbd_completion_t cb, void *arg)
 
     void rbd_version(int *major, int *minor, int *extra)
@@ -720,5 +725,8 @@ cdef extern from "rbd/librbd.h" nogil:
                               rbd_encryption_format_t format,
                               rbd_encryption_options_t opts, size_t opts_size)
     int rbd_encryption_load(rbd_image_t image,
-                              rbd_encryption_format_t format,
-                              rbd_encryption_options_t opts, size_t opts_size)
+                            rbd_encryption_format_t format,
+                            rbd_encryption_options_t opts, size_t opts_size)
+    int rbd_encryption_load2(rbd_image_t image,
+                             rbd_encryption_spec_t *specs,
+                             size_t spec_count)
index 311d2b77f6bad8fe7d233bb1831040a68dce26f3..9d8df1328037abf1ebf0eb536f566f3295710673 100644 (file)
@@ -289,6 +289,11 @@ cdef nogil:
 
     ctypedef void* rbd_encryption_options_t
 
+    ctypedef struct rbd_encryption_spec_t:
+        rbd_encryption_format_t format
+        rbd_encryption_options_t opts
+        size_t opts_size
+
     void rbd_version(int *major, int *minor, int *extra):
         pass
     void rbd_image_spec_list_cleanup(rbd_image_spec_t *image, size_t num_images):
@@ -909,6 +914,10 @@ cdef nogil:
                               rbd_encryption_options_t opts, size_t opts_size):
         pass
     int rbd_encryption_load(rbd_image_t image,
-                              rbd_encryption_format_t format,
-                              rbd_encryption_options_t opts, size_t opts_size):
+                            rbd_encryption_format_t format,
+                            rbd_encryption_options_t opts, size_t opts_size):
+        pass
+    int rbd_encryption_load2(rbd_image_t image,
+                             rbd_encryption_spec_t *specs,
+                             size_t spec_count):
         pass
index ab14d35fb111a93e4c023fe1997aef60f8b05322..f2d22b4a16cba1a4356cc35faea08866ecb1aa50 100644 (file)
@@ -21,8 +21,8 @@ import sys
 from cpython cimport PyObject, ref, exc
 from libc cimport errno
 from libc.stdint cimport *
-from libc.stdlib cimport realloc, free
-from libc.string cimport strdup
+from libc.stdlib cimport malloc, realloc, free
+from libc.string cimport strdup, memset
 
 try:
     from collections.abc import Iterable
@@ -5173,7 +5173,7 @@ written." % (self.name, ret, length))
         passphrase = cstr(passphrase, "passphrase")
         cdef rbd_encryption_format_t _format = format
         cdef rbd_encryption_luks1_format_options_t _luks1_opts
-        cdef rbd_encryption_luks1_format_options_t _luks2_opts
+        cdef rbd_encryption_luks2_format_options_t _luks2_opts
         cdef char* _passphrase = passphrase
 
         if (format == RBD_ENCRYPTION_FORMAT_LUKS1):
@@ -5246,6 +5246,71 @@ written." % (self.name, ret, length))
         else:
             raise make_ex(-errno.ENOTSUP, 'Unsupported encryption format')
 
+    @requires_not_closed
+    def encryption_load2(self, specs):
+        cdef rbd_encryption_spec_t *_specs
+        cdef rbd_encryption_luks1_format_options_t* _luks1_opts
+        cdef rbd_encryption_luks2_format_options_t* _luks2_opts
+        cdef rbd_encryption_luks_format_options_t* _luks_opts
+        cdef size_t spec_count = len(specs)
+
+        _specs = <rbd_encryption_spec_t *>malloc(len(specs) *
+                                                 sizeof(rbd_encryption_spec_t))
+        if _specs == NULL:
+            raise MemoryError("malloc failed")
+
+        memset(<void *>_specs, 0, len(specs) * sizeof(rbd_encryption_spec_t))
+        try:
+            for i in range(len(specs)):
+                format, passphrase = specs[i]
+                passphrase = cstr(passphrase, "specs[%d][1]" % i)
+                _specs[i].format = format
+                if (format == RBD_ENCRYPTION_FORMAT_LUKS1):
+                    _luks1_opts = <rbd_encryption_luks1_format_options_t *>malloc(
+                        sizeof(rbd_encryption_luks1_format_options_t))
+                    if _luks1_opts == NULL:
+                        raise MemoryError("malloc failed")
+                    _luks1_opts.passphrase = passphrase
+                    _luks1_opts.passphrase_size = len(passphrase)
+                    _specs[i].opts = <rbd_encryption_options_t> _luks1_opts
+                    _specs[i].opts_size = sizeof(
+                    rbd_encryption_luks1_format_options_t)
+                elif (format == RBD_ENCRYPTION_FORMAT_LUKS2):
+                    _luks2_opts = <rbd_encryption_luks2_format_options_t *>malloc(
+                        sizeof(rbd_encryption_luks2_format_options_t))
+                    if _luks2_opts == NULL:
+                        raise MemoryError("malloc failed")
+                    _luks2_opts.passphrase = passphrase
+                    _luks2_opts.passphrase_size = len(passphrase)
+                    _specs[i].opts = <rbd_encryption_options_t> _luks2_opts
+                    _specs[i].opts_size = sizeof(
+                    rbd_encryption_luks2_format_options_t)
+                elif (format == RBD_ENCRYPTION_FORMAT_LUKS):
+                    _luks_opts = <rbd_encryption_luks_format_options_t *>malloc(
+                        sizeof(rbd_encryption_luks_format_options_t))
+                    if _luks_opts == NULL:
+                        raise MemoryError("malloc failed")
+                    _luks_opts.passphrase = passphrase
+                    _luks_opts.passphrase_size = len(passphrase)
+                    _specs[i].opts = <rbd_encryption_options_t> _luks_opts
+                    _specs[i].opts_size = sizeof(
+                    rbd_encryption_luks_format_options_t)
+                else:
+                    raise make_ex(
+                        -errno.ENOTSUP,
+                        'specs[%d][1]: Unsupported encryption format' % i)
+            with nogil:
+                ret = rbd_encryption_load2(self.image, _specs, spec_count)
+            if ret != 0:
+                raise make_ex(
+                    ret,
+                    'error loading encryption on image %s' % self.name)
+        finally:
+            for i in range(len(specs)):
+                if _specs[i].opts != NULL:
+                    free(_specs[i].opts)
+            free(_specs)
+
 
 cdef class ImageIterator(object):
     """
index b0c49220244e4b363890a6fe2cd35a4ad2631020..5d4ba4343152efbbf9b3e3fa5e69fd5b9379a503 100644 (file)
@@ -2268,16 +2268,13 @@ TEST_F(TestLibRBD, TestEncryptionLUKS1)
 
   test_io(image);
 
-  bool passed;
-  write_test_data(image, "test", 0, 4, 0, &passed);
-  ASSERT_TRUE(passed);
+  ASSERT_PASSED(write_test_data, image, "test", 0, 4, 0);
   ASSERT_EQ(0, rbd_close(image));
 
   ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
   ASSERT_EQ(0, rbd_encryption_load(
           image, RBD_ENCRYPTION_FORMAT_LUKS, &luks_opts, sizeof(luks_opts)));
-  read_test_data(image, "test", 0, 4, 0, &passed);
-  ASSERT_TRUE(passed);
+  ASSERT_PASSED(read_test_data, image, "test", 0, 4, 0);
 #endif
 
   ASSERT_EQ(0, rbd_close(image));
@@ -2330,22 +2327,209 @@ TEST_F(TestLibRBD, TestEncryptionLUKS2)
 
   test_io(image);
 
-  bool passed;
-  write_test_data(image, "test", 0, 4, 0, &passed);
-  ASSERT_TRUE(passed);
+  ASSERT_PASSED(write_test_data, image, "test", 0, 4, 0);
   ASSERT_EQ(0, rbd_close(image));
 
   ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
   ASSERT_EQ(0, rbd_encryption_load(
           image, RBD_ENCRYPTION_FORMAT_LUKS, &luks_opts, sizeof(luks_opts)));
-  read_test_data(image, "test", 0, 4, 0, &passed);
-  ASSERT_TRUE(passed);
+  ASSERT_PASSED(read_test_data, image, "test", 0, 4, 0);
 #endif
 
   ASSERT_EQ(0, rbd_close(image));
   rados_ioctx_destroy(ioctx);
 }
 
+#ifdef HAVE_LIBCRYPTSETUP
+
+TEST_F(TestLibRBD, TestCloneEncryption)
+{
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));
+  REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2));
+  REQUIRE_FEATURE(RBD_FEATURE_LAYERING);
+
+  rados_ioctx_t ioctx;
+  rados_ioctx_create(_cluster, m_pool_name.c_str(), &ioctx);
+  ASSERT_EQ(0, rados_conf_set(
+          _cluster, "rbd_read_from_replica_policy", "balance"));
+
+  // create base image, write 'a's
+  int order = 0;
+  std::string name = get_temp_image_name();
+  uint64_t size = 256 << 20;
+  ASSERT_EQ(0, create_image(ioctx, name.c_str(), size, &order));
+
+  rbd_image_t image;
+  ASSERT_EQ(0, rbd_open(ioctx, name.c_str(), &image, NULL));
+  ASSERT_PASSED(write_test_data, image, "aaaa", 0, 4, 0);
+  ASSERT_EQ(0, rbd_flush(image));
+
+  // clone, encrypt with LUKS1, write 'b's
+  ASSERT_EQ(0, rbd_snap_create(image, "snap"));
+  ASSERT_EQ(0, rbd_snap_protect(image, "snap"));
+
+  rbd_image_options_t image_opts;
+  rbd_image_options_create(&image_opts);
+  BOOST_SCOPE_EXIT_ALL( (&image_opts) ) {
+    rbd_image_options_destroy(image_opts);
+  };
+  std::string child1_name = get_temp_image_name();
+  ASSERT_EQ(0, rbd_clone3(ioctx, name.c_str(), "snap", ioctx,
+                          child1_name.c_str(), image_opts));
+
+  rbd_image_t child1;
+  ASSERT_EQ(0, rbd_open(ioctx, child1_name.c_str(), &child1, NULL));
+
+  rbd_encryption_luks1_format_options_t child1_opts = {
+          .alg = RBD_ENCRYPTION_ALGORITHM_AES256,
+          .passphrase = "password",
+          .passphrase_size = 8,
+  };
+  ASSERT_EQ(-EINVAL, rbd_encryption_load(
+          child1, RBD_ENCRYPTION_FORMAT_LUKS1, &child1_opts,
+          sizeof(child1_opts)));
+  ASSERT_EQ(0, rbd_encryption_format(
+          child1, RBD_ENCRYPTION_FORMAT_LUKS1, &child1_opts,
+          sizeof(child1_opts)));
+  ASSERT_EQ(0, rbd_encryption_load(
+          child1, RBD_ENCRYPTION_FORMAT_LUKS1, &child1_opts,
+          sizeof(child1_opts)));
+  ASSERT_PASSED(write_test_data, child1, "bbbb", 64 << 20, 4, 0);
+  ASSERT_EQ(0, rbd_flush(child1));
+
+  // clone, encrypt with LUKS2 (same passphrase), write 'c's
+  ASSERT_EQ(0, rbd_snap_create(child1, "snap"));
+  ASSERT_EQ(0, rbd_snap_protect(child1, "snap"));
+
+  std::string child2_name = get_temp_image_name();
+  ASSERT_EQ(0, rbd_clone3(ioctx, child1_name.c_str(), "snap", ioctx,
+                          child2_name.c_str(), image_opts));
+
+  rbd_image_t child2;
+  ASSERT_EQ(0, rbd_open(ioctx, child2_name.c_str(), &child2, NULL));
+
+  rbd_encryption_luks2_format_options_t child2_opts = {
+          .alg = RBD_ENCRYPTION_ALGORITHM_AES256,
+          .passphrase = "password",
+          .passphrase_size = 8,
+  };
+  ASSERT_EQ(0, rbd_encryption_format(
+          child2, RBD_ENCRYPTION_FORMAT_LUKS2, &child2_opts,
+          sizeof(child2_opts)));
+  ASSERT_EQ(0, rbd_encryption_load(
+        child2, RBD_ENCRYPTION_FORMAT_LUKS2, &child2_opts,
+        sizeof(child2_opts)));
+  ASSERT_PASSED(write_test_data, child2, "cccc", 128 << 20, 4, 0);
+  ASSERT_EQ(0, rbd_flush(child2));
+
+  // clone, encrypt with LUKS2 (different passphrase)
+  ASSERT_EQ(0, rbd_snap_create(child2, "snap"));
+  ASSERT_EQ(0, rbd_snap_protect(child2, "snap"));
+
+  std::string child3_name = get_temp_image_name();
+  ASSERT_EQ(0, rbd_clone3(ioctx, child2_name.c_str(), "snap", ioctx,
+                          child3_name.c_str(), image_opts));
+
+  rbd_image_t child3;
+  ASSERT_EQ(0, rbd_open(ioctx, child3_name.c_str(), &child3, NULL));
+
+  rbd_encryption_luks2_format_options_t child3_opts = {
+          .alg = RBD_ENCRYPTION_ALGORITHM_AES256,
+          .passphrase = "12345678",
+          .passphrase_size = 8,
+  };
+  ASSERT_EQ(0, rbd_encryption_format(
+          child3, RBD_ENCRYPTION_FORMAT_LUKS2, &child3_opts,
+          sizeof(child3_opts)));
+  ASSERT_EQ(-EPERM, rbd_encryption_load(
+        child3, RBD_ENCRYPTION_FORMAT_LUKS2, &child3_opts,
+        sizeof(child3_opts)));
+
+  // verify child3 data
+  rbd_encryption_spec_t specs[] = {
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS2,
+            .opts = &child3_opts,
+            .opts_size = sizeof(child3_opts)},
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS2,
+            .opts = &child2_opts,
+            .opts_size = sizeof(child2_opts)},
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS1,
+            .opts = &child1_opts,
+            .opts_size = sizeof(child1_opts)}
+  };
+
+  ASSERT_EQ(0, rbd_encryption_load2(child3, specs, 3));
+
+  ASSERT_PASSED(read_test_data, child3, "aaaa", 0, 4, 0);
+  ASSERT_PASSED(read_test_data, child3, "bbbb", 64 << 20, 4, 0);
+  ASSERT_PASSED(read_test_data, child3, "cccc", 128 << 20, 4, 0);
+
+  // clone without formatting
+  ASSERT_EQ(0, rbd_snap_create(child3, "snap"));
+  ASSERT_EQ(0, rbd_snap_protect(child3, "snap"));
+
+  std::string child4_name = get_temp_image_name();
+  ASSERT_EQ(0, rbd_clone3(ioctx, child3_name.c_str(), "snap", ioctx,
+                          child4_name.c_str(), image_opts));
+
+  rbd_image_t child4;
+  ASSERT_EQ(0, rbd_open(ioctx, child4_name.c_str(), &child4, NULL));
+
+  rbd_encryption_spec_t child4_specs[] = {
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS2,
+            .opts = &child3_opts,
+            .opts_size = sizeof(child3_opts)},
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS2,
+            .opts = &child3_opts,
+            .opts_size = sizeof(child3_opts)},
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS2,
+            .opts = &child2_opts,
+            .opts_size = sizeof(child2_opts)},
+          { .format = RBD_ENCRYPTION_FORMAT_LUKS1,
+            .opts = &child1_opts,
+            .opts_size = sizeof(child1_opts)}
+  };
+
+  ASSERT_EQ(0, rbd_encryption_load2(child4, child4_specs, 4));
+
+  // flatten child4
+  ASSERT_EQ(0, rbd_flatten(child4));
+
+  // reopen child4 and load encryption
+  ASSERT_EQ(0, rbd_close(child4));
+  ASSERT_EQ(0, rbd_open(ioctx, child4_name.c_str(), &child4, NULL));
+  ASSERT_EQ(0, rbd_encryption_load(
+          child4, RBD_ENCRYPTION_FORMAT_LUKS2, &child3_opts,
+          sizeof(child3_opts)));
+
+  // verify flattend image
+  ASSERT_PASSED(read_test_data, child4, "aaaa", 0, 4, 0);
+  ASSERT_PASSED(read_test_data, child4, "bbbb", 64 << 20, 4, 0);
+  ASSERT_PASSED(read_test_data, child4, "cccc", 128 << 20, 4, 0);
+
+  ASSERT_EQ(0, rbd_close(child4));
+  ASSERT_EQ(0, rbd_remove(ioctx, child4_name.c_str()));
+  ASSERT_EQ(0, rbd_snap_unprotect(child3, "snap"));
+  ASSERT_EQ(0, rbd_snap_remove(child3, "snap"));
+  ASSERT_EQ(0, rbd_close(child3));
+  ASSERT_EQ(0, rbd_remove(ioctx, child3_name.c_str()));
+  ASSERT_EQ(0, rbd_snap_unprotect(child2, "snap"));
+  ASSERT_EQ(0, rbd_snap_remove(child2, "snap"));
+  ASSERT_EQ(0, rbd_close(child2));
+  ASSERT_EQ(0, rbd_remove(ioctx, child2_name.c_str()));
+  ASSERT_EQ(0, rbd_snap_unprotect(child1, "snap"));
+  ASSERT_EQ(0, rbd_snap_remove(child1, "snap"));
+  ASSERT_EQ(0, rbd_close(child1));
+  ASSERT_EQ(0, rbd_remove(ioctx, child1_name.c_str()));
+  ASSERT_EQ(0, rbd_snap_unprotect(image, "snap"));
+  ASSERT_EQ(0, rbd_snap_remove(image, "snap"));
+  ASSERT_EQ(0, rbd_close(image));
+  ASSERT_EQ(0, rbd_remove(ioctx, name.c_str()));
+  rados_ioctx_destroy(ioctx);
+}
+
+#endif
+
 TEST_F(TestLibRBD, TestIOWithIOHint)
 {
   rados_ioctx_t ioctx;
index 11ca9a3350a0e5fd9286413c45655abb64a2a100..a7b74acbb86d168097a9d5e5d8475f89b0056609 100644 (file)
@@ -1876,6 +1876,34 @@ class TestClone(object):
         self.rbd.remove(ioctx, clone_name)
         eq([], [s for s in self.image.list_snaps() if s['name'] != 'snap1'])
 
+    @require_linux()
+    @blocklist_features([RBD_FEATURE_JOURNALING])
+    def test_encryption_luks1(self):
+        data = b'hello world'
+        offset = 16<<20
+        image_size = 32<<20
+
+        self.clone.resize(image_size)
+        self.clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, "password")
+        self.clone.encryption_load2(
+            ((RBD_ENCRYPTION_FORMAT_LUKS1, "password"),))
+        self.clone.write(data, offset)
+        eq(self.clone.read(0, 16), self.image.read(0, 16))
+
+    @require_linux()
+    @blocklist_features([RBD_FEATURE_JOURNALING])
+    def test_encryption_luks2(self):
+        data = b'hello world'
+        offset = 16<<20
+        image_size = 64<<20
+
+        self.clone.resize(image_size)
+        self.clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, "password")
+        self.clone.encryption_load2(
+            ((RBD_ENCRYPTION_FORMAT_LUKS2, "password"),))
+        self.clone.write(data, offset)
+        eq(self.clone.read(0, 16), self.image.read(0, 16))
+
 class TestExclusiveLock(object):
 
     @require_features([RBD_FEATURE_EXCLUSIVE_LOCK])