]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
tools/rbd: add encryption format support for cloned image
authorOr Ozeri <oro@il.ibm.com>
Sun, 21 Aug 2022 09:48:58 +0000 (12:48 +0300)
committerOr Ozeri <oro@il.ibm.com>
Thu, 25 Aug 2022 15:41:47 +0000 (18:41 +0300)
This commit adds the encryption format support for cloned images via the RBD cli,
making the child image be encrypted with a key different from it parent,
while keeping the child thinly-provisioned.
Additionally, other APIs are extended to support flattening of such images.

Signed-off-by: Or Ozeri <oro@il.ibm.com>
doc/man/8/rbd.rst
doc/rbd/rbd-encryption.rst
qa/workunits/rbd/luks-encryption.sh
src/test/cli/rbd/help.t
src/tools/rbd/ArgumentTypes.cc
src/tools/rbd/ArgumentTypes.h
src/tools/rbd/Utils.cc
src/tools/rbd/Utils.h
src/tools/rbd/action/Flatten.cc
src/tools/rbd_nbd/rbd-nbd.cc

index 27b176e1ba70a4d0f2af71c874f78eae50390fa5..0527d0081b4a1a8382e75db28f35c8dfac15faaa 100644 (file)
@@ -308,7 +308,6 @@ Commands
 :command:`encryption format` *image-spec* *format* *passphrase-file* [--cipher-alg *alg*]
   Formats image to an encrypted format.
   All data previously written to the image will become unreadable.
-  A cloned image cannot be formatted, although encrypted images can be cloned.
   Supported formats: *luks1*, *luks2*.
   Supported cipher algorithms: *aes-128*, *aes-256* (default).
 
@@ -333,7 +332,7 @@ Commands
   Enable the specified feature on the specified image. Multiple features can
   be specified.
 
-:command:`flatten` *image-spec*
+:command:`flatten` [--encryption-format *encryption-format* --encryption-passphrase-file *passphrase-file*]... *image-spec*
   If image is a clone, copy all shared blocks from the parent snapshot and
   make the child independent of the parent, severing the link between
   parent snap and child.  The parent snapshot can be unprotected and
index 0d0664b703dda9def73396734b785a74b4f87c3e..d452120094b1910150bf7c86e696fd4c894acc92 100644 (file)
@@ -36,9 +36,12 @@ be lower than the raw image size. See the `Supported Formats`_ section for more
 details.
 
 .. note::
-   Currently only flat images (i.e. not cloned) can be formatted.
-   Clones of an encrypted image are inherently encrypted using the same format
-   and secret.
+   Unless explicitly (re-)formatted, clones of an encrypted image are
+   inherently encrypted using the same format and secret.
+
+.. note::
+   Clones of an encrypted image are always encrypted.
+   Re-formatting to plaintext is not supported.
 
 .. note::
    Any data written to the image prior to its format may become unreadable,
index 710eb632da5a422f859d62a91dd966f2ff314136..a7cbf9bc3c7739566b42215ee72fff4710632419 100755 (executable)
@@ -2,7 +2,7 @@
 set -ex
 
 CEPH_ID=${CEPH_ID:-admin}
-TMP_FILES="/tmp/passphrase /tmp/testdata1 /tmp/testdata2 /tmp/cmpdata"
+TMP_FILES="/tmp/passphrase /tmp/passphrase2 /tmp/testdata1 /tmp/testdata2 /tmp/cmpdata"
 
 _sudo()
 {
@@ -26,6 +26,10 @@ function drop_caches {
   echo 3 | sudo tee /proc/sys/vm/drop_caches
 }
 
+function expect_false() {
+  if "$@"; then return 1; else return 0; fi
+}
+
 function test_encryption_format() {
   local format=$1
   clean_up_cryptsetup
@@ -51,10 +55,93 @@ function test_encryption_format() {
   dd if=/tmp/testdata2 of=/dev/mapper/cryptsetupdev oflag=direct bs=1M
   dd if=$LIBRBD_DEV of=/tmp/cmpdata iflag=direct bs=4M count=4
   cmp -n 16MB /tmp/cmpdata /tmp/testdata2
+
+  sudo rbd device unmap -t nbd $LIBRBD_DEV
+}
+
+function test_clone_encryption() {
+  clean_up_cryptsetup
+
+  # write 1MB plaintext
+  dd if=/tmp/testdata1 of=$RAW_DEV oflag=direct bs=1M count=1
+
+  # clone (luks1)
+  rbd snap create testimg@snap
+  rbd snap protect testimg@snap
+  rbd clone testimg@snap testimg1
+  rbd encryption format testimg1 luks1 /tmp/passphrase
+
+  # open encryption with librbd, write one more MB, close
+  LIBRBD_DEV=$(_sudo rbd -p rbd map testimg1 -t nbd -o encryption-format=luks1,encryption-passphrase-file=/tmp/passphrase)
+  sudo chmod 666 $LIBRBD_DEV
+  dd if=$LIBRBD_DEV of=/tmp/cmpdata iflag=direct bs=1M count=1
+  cmp -n 1MB /tmp/cmpdata /tmp/testdata1
+  dd if=/tmp/testdata1 of=$LIBRBD_DEV seek=1 skip=1 oflag=direct bs=1M count=1
+  sudo rbd device unmap -t nbd $LIBRBD_DEV
+
+  # second clone (luks2)
+  rbd snap create testimg1@snap
+  rbd snap protect testimg1@snap
+  rbd clone testimg1@snap testimg2
+  rbd encryption format testimg2 luks2 /tmp/passphrase2
+
+  # open encryption with librbd, write one more MB, close
+  LIBRBD_DEV=$(_sudo rbd -p rbd map testimg2 -t nbd -o encryption-format=luks2,encryption-passphrase-file=/tmp/passphrase2,encryption-format=luks1,encryption-passphrase-file=/tmp/passphrase)
+  sudo chmod 666 $LIBRBD_DEV
+  dd if=$LIBRBD_DEV of=/tmp/cmpdata iflag=direct bs=1M count=2
+  cmp -n 2MB /tmp/cmpdata /tmp/testdata1
+  dd if=/tmp/testdata1 of=$LIBRBD_DEV seek=2 skip=2 oflag=direct bs=1M count=1
+  sudo rbd device unmap -t nbd $LIBRBD_DEV
+
+  # flatten
+  rbd flatten testimg2 --encryption-format luks --encryption-format luks --encryption-passphrase-file /tmp/passphrase2 --encryption-passphrase-file /tmp/passphrase
+
+  # verify with cryptsetup
+  RAW_FLAT_DEV=$(_sudo rbd -p rbd map testimg2 -t nbd)
+  sudo cryptsetup open $RAW_FLAT_DEV --type luks cryptsetupdev -d /tmp/passphrase2
+  sudo chmod 666 /dev/mapper/cryptsetupdev
+  dd if=/dev/mapper/cryptsetupdev of=/tmp/cmpdata iflag=direct bs=1M count=3
+  cmp -n 3MB /tmp/cmpdata /tmp/testdata1
+  sudo rbd device unmap -t nbd $RAW_FLAT_DEV
+}
+
+function test_clone_and_load_with_a_single_passphrase {
+  local expectedfail=$1
+
+  # clone and format
+  rbd snap create testimg@snap
+  rbd snap protect testimg@snap
+  rbd clone testimg@snap testimg1
+  rbd encryption format testimg1 luks2 /tmp/passphrase2
+
+  if [ "$expectedfail" = "true" ]
+  then
+    expect_false rbd flatten testimg1 --encryption-format luks --encryption-passphrase-file /tmp/passphrase2
+  else
+    rbd flatten testimg1 --encryption-format luks --encryption-passphrase-file /tmp/passphrase2
+  fi
+
+  rbd remove testimg1
+  rbd snap unprotect testimg@snap
+  rbd snap remove testimg@snap
+}
+
+function test_plaintext_detection {
+  # 16k LUKS header
+  sudo cryptsetup -q luksFormat --type luks2 --luks2-metadata-size 16k $RAW_DEV /tmp/passphrase
+  test_clone_and_load_with_a_single_passphrase true
+
+  # 4m LUKS header
+  sudo cryptsetup -q luksFormat --type luks2 --luks2-metadata-size 4m $RAW_DEV /tmp/passphrase
+  test_clone_and_load_with_a_single_passphrase true
+
+  # no luks header
+  dd if=/dev/zero of=$RAW_DEV oflag=direct bs=4M count=8
+  test_clone_and_load_with_a_single_passphrase false
 }
 
 function get_nbd_device_paths {
-       rbd device list -t nbd | tail -n +2 | egrep "\s+rbd\s+testimg\s+" | awk '{print $5;}'
+  rbd device list -t nbd | tail -n +2 | egrep "\s+rbd\s+testimg" | awk '{print $5;}'
 }
 
 function clean_up_cryptsetup() {
@@ -64,10 +151,17 @@ function clean_up_cryptsetup() {
 function clean_up {
   sudo rm -f $TMP_FILES
   clean_up_cryptsetup
-       for device in $(get_nbd_device_paths); do
-         _sudo rbd device unmap -t nbd $device
+  for device in $(get_nbd_device_paths); do
+    _sudo rbd device unmap -t nbd $device
   done
-       rbd ls | grep testimg > /dev/null && rbd rm testimg || true
+
+  rbd remove testimg2 || true
+  rbd snap unprotect testimg1@snap || true
+  rbd snap remove testimg1@snap || true
+  rbd remove testimg1 || true
+  rbd snap unprotect testimg@snap || true
+  rbd snap remove testimg@snap || true
+  rbd remove testimg || true
 }
 
 if [[ $(uname) != "Linux" ]]; then
@@ -89,16 +183,22 @@ trap clean_up INT TERM EXIT
 dd if=/dev/urandom of=/tmp/testdata1 bs=4M count=4
 dd if=/dev/urandom of=/tmp/testdata2 bs=4M count=4
 
-# create passphrase file
+# create passphrase files
 echo -n "password" > /tmp/passphrase
+echo -n "password2" > /tmp/passphrase2
 
 # create an image
 rbd create testimg --size=32M
 
 # map raw data to nbd device
 RAW_DEV=$(_sudo rbd -p rbd map testimg -t nbd)
+sudo chmod 666 $RAW_DEV
+
+test_plaintext_detection
 
 test_encryption_format luks1
 test_encryption_format luks2
 
+test_clone_encryption
+
 echo OK
index 59de21c03661939f1fff54dc5739c426d6c6a482..91012f2ccac32eaf279c44976485dfd195d6497f 100644 (file)
   
   rbd help flatten
   usage: rbd flatten [--pool <pool>] [--namespace <namespace>] [--image <image>] 
-                     [--no-progress] 
+                     [--no-progress] [--encryption-format <encryption-format>] 
+                     [--encryption-passphrase-file <encryption-passphrase-file>] 
                      <image-spec> 
   
   Fill clone with parent data (make it independent).
   
   Positional arguments
-    <image-spec>         image specification
-                         (example: [<pool-name>/[<namespace>/]]<image-name>)
+    <image-spec>                     image specification
+                                     (example:
+                                     [<pool-name>/[<namespace>/]]<image-name>)
   
   Optional arguments
-    -p [ --pool ] arg    pool name
-    --namespace arg      namespace name
-    --image arg          image name
-    --no-progress        disable progress output
+    -p [ --pool ] arg                pool name
+    --namespace arg                  namespace name
+    --image arg                      image name
+    --no-progress                    disable progress output
+    --encryption-format arg          encryption formats [possible values: luks]
+    --encryption-passphrase-file arg path to file containing passphrase for
+                                     unlocking the image
   
   rbd help group create
   usage: rbd group create [--pool <pool>] [--namespace <namespace>] 
     --namespace arg      namespace name
     --image arg          image name
   
+
index 8236f7edbb016b8a84d385b96fd287c04d41307c..5d75d02abb34e41599f5193189b457c7b6bdc938 100644 (file)
@@ -329,6 +329,18 @@ void add_snap_create_options(po::options_description *opt) {
      "ignore quiesce hook error");
 }
 
+void add_encryption_options(boost::program_options::options_description *opt) {
+  opt->add_options()
+  (ENCRYPTION_FORMAT.c_str(),
+   po::value<std::vector<EncryptionFormat>>(),
+   "encryption formats [possible values: luks]");
+
+  opt->add_options()
+    (ENCRYPTION_PASSPHRASE_FILE.c_str(),
+     po::value<std::vector<std::string>>(),
+     "path to file containing passphrase for unlocking the image");
+}
+
 std::string get_short_features_help(bool append_suffix) {
   std::ostringstream oss;
   bool first_feature = true;
@@ -522,6 +534,20 @@ void validate(boost::any& v, const std::vector<std::string>& values,
   }
 }
 
+void validate(boost::any& v, const std::vector<std::string>& values,
+              EncryptionFormat *target_type, int) {
+  po::validators::check_first_occurrence(v);
+  const std::string &s = po::validators::get_single_string(values);
+  EncryptionFormat format;
+  if (s == "luks") {
+    format.format = RBD_ENCRYPTION_FORMAT_LUKS;
+  } else {
+    throw po::validation_error(po::validation_error::invalid_option_value);
+  }
+
+  v = boost::any(format);
+}
+
 void validate(boost::any& v, const std::vector<std::string>& values,
               ExportFormat *target_type, int) {
   po::validators::check_first_occurrence(v);
index 39d374c64c3cd5e2ad86e2c4b3aecc851b7b7d64..db16b4b3cf01b538339cfc1cb5998cd1e642f4c0 100644 (file)
@@ -56,6 +56,10 @@ static const std::string PATH("path");
 static const std::string FROM_SNAPSHOT_NAME("from-snap");
 static const std::string WHOLE_OBJECT("whole-object");
 
+// encryption arguments
+static const std::string ENCRYPTION_FORMAT("encryption-format");
+static const std::string ENCRYPTION_PASSPHRASE_FILE("encryption-passphrase-file");
+
 static const std::string IMAGE_FORMAT("image-format");
 static const std::string IMAGE_NEW_FORMAT("new-format");
 static const std::string IMAGE_ORDER("order");
@@ -127,6 +131,9 @@ struct ExportFormat {};
 struct Secret {};
 
 struct EncryptionAlgorithm {};
+struct EncryptionFormat {
+  uint64_t format;
+};
 
 void add_export_format_option(boost::program_options::options_description *opt);
 
@@ -198,6 +205,8 @@ void add_flatten_option(boost::program_options::options_description *opt);
 
 void add_snap_create_options(boost::program_options::options_description *opt);
 
+void add_encryption_options(boost::program_options::options_description *opt);
+
 std::string get_short_features_help(bool append_suffix);
 std::string get_long_features_help();
 
@@ -221,6 +230,8 @@ void validate(boost::any& v, const std::vector<std::string>& values,
               JournalObjectSize *target_type, int);
 void validate(boost::any& v, const std::vector<std::string>& values,
               EncryptionAlgorithm *target_type, int);
+void validate(boost::any& v, const std::vector<std::string>& values,
+              EncryptionFormat *target_type, int);
 void validate(boost::any& v, const std::vector<std::string>& values,
               Secret *target_type, int);
 
index a47b45a8be1be527162980856862272ca8466b81..4aa34bec027f11d5171d801757c3907667997118 100644 (file)
@@ -13,6 +13,7 @@
 #include "common/escape.h"
 #include "common/safe_io.h"
 #include "global/global_context.h"
+#include <fstream>
 #include <iostream>
 #include <regex>
 #include <boost/algorithm/string.hpp>
@@ -668,6 +669,78 @@ int get_snap_create_flags(const po::variables_map &vm, uint32_t *flags) {
   return 0;
 }
 
+int get_encryption_options(const boost::program_options::variables_map &vm,
+                           EncryptionOptions* opts) {
+  std::vector<std::string> passphrase_files;
+  if (vm.count(at::ENCRYPTION_PASSPHRASE_FILE)) {
+    passphrase_files =
+            vm[at::ENCRYPTION_PASSPHRASE_FILE].as<std::vector<std::string>>();
+  }
+
+  std::vector<librbd::encryption_format_t> formats;
+  if (vm.count(at::ENCRYPTION_FORMAT)) {
+    auto& format_structs =
+            vm[at::ENCRYPTION_FORMAT].as<std::vector<at::EncryptionFormat>>();
+    for (auto& format_struct : format_structs) {
+      formats.push_back((librbd::encryption_format_t)format_struct.format);
+    }
+  }
+
+  if (formats.size() != passphrase_files.size()) {
+    std::cerr << "rbd: encryption formats count does not match "
+              << "passphrase files count" << std::endl;
+    return -EINVAL;
+  }
+
+  auto spec_count = formats.size();
+  if (spec_count == 0) {
+    return 0;
+  }
+
+  opts->luks_opts.reserve(spec_count);
+
+  auto& specs = opts->specs;
+  specs.resize(spec_count);
+  for (size_t i = 0; i < spec_count; ++i) {
+    std::ifstream file(passphrase_files[i].c_str());
+    auto sg = make_scope_guard([&] { file.close(); });
+
+    specs[i].format = formats[i];
+    std::string* passphrase;
+    switch (specs[i].format) {
+      case RBD_ENCRYPTION_FORMAT_LUKS: {
+        auto& luks_opts = opts->luks_opts;
+        luks_opts.emplace_back();
+        specs[i].opts = &luks_opts.back();
+        specs[i].opts_size = sizeof(luks_opts.back());
+        passphrase = &luks_opts.back().passphrase;
+        break;
+      }
+      default:
+        std::cerr << "rbd: unsupported encryption format: " << specs[i].format
+                  << std::endl;
+        return -ENOTSUP;
+    }
+
+    passphrase->assign((std::istreambuf_iterator<char>(file)),
+                       (std::istreambuf_iterator<char>()));
+
+    if (file.fail()) {
+      std::cerr << "rbd: unable to open passphrase file '"
+                << passphrase_files[i] << "': " << cpp_strerror(errno)
+                << std::endl;
+      return -errno;
+    }
+
+    if (!passphrase->empty() &&
+        (*passphrase)[passphrase->length() - 1] == '\n') {
+      passphrase->erase(passphrase->length() - 1);
+    }
+  }
+
+  return 0;
+}
+
 void init_context() {
   g_conf().set_val_or_die("rbd_cache_writethrough_until_flush", "false");
   g_conf().apply_changes(nullptr);
@@ -815,6 +888,7 @@ int init_and_open_image(const std::string &pool_name,
       return r;
     }
   }
+
   return 0;
 }
 
index 3ed5bfddbcb6a4e58e14233f68445ee8945f54c6..227fe0b36ac2fbe4d1f1fe28b29cf75dc256dbdc 100644 (file)
@@ -4,6 +4,7 @@
 #ifndef CEPH_RBD_UTILS_H
 #define CEPH_RBD_UTILS_H
 
+#include "include/compat.h"
 #include "include/int_types.h"
 #include "include/rados/librados.hpp"
 #include "include/rbd/librbd.hpp"
@@ -84,6 +85,18 @@ struct ProgressContext : public librbd::ProgressContext {
 
 int get_percentage(uint64_t part, uint64_t whole);
 
+struct EncryptionOptions {
+    std::vector<librbd::encryption_spec_t> specs;
+    std::vector<librbd::encryption_luks_format_options_t> luks_opts;
+
+    ~EncryptionOptions() {
+      for (auto& opts : luks_opts) {
+        auto& passphrase = opts.passphrase;
+        ceph_memzero_s(&passphrase[0], passphrase.size(), passphrase.size());
+      }
+    }
+};
+
 template <typename T, void(T::*MF)(int)>
 librbd::RBD::AioCompletion *create_aio_completion(T *t) {
   return new librbd::RBD::AioCompletion(
@@ -156,6 +169,9 @@ int get_formatter(const boost::program_options::variables_map &vm,
 int get_snap_create_flags(const boost::program_options::variables_map &vm,
                           uint32_t *flags);
 
+int get_encryption_options(const boost::program_options::variables_map &vm,
+                           EncryptionOptions* opts);
+
 void init_context();
 
 int init_rados(librados::Rados *rados);
index ec4e837a871141b0c8fa91336556b3ae6cff3fbf..87fadc99998a41413a073e2eca4a40bbb096569f 100644 (file)
@@ -31,6 +31,7 @@ void get_arguments(po::options_description *positional,
                    po::options_description *options) {
   at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE);
   at::add_no_progress_option(options);
+  at::add_encryption_options(options);
 }
 
 int execute(const po::variables_map &vm,
@@ -48,6 +49,12 @@ int execute(const po::variables_map &vm,
     return r;
   }
 
+  utils::EncryptionOptions encryption_options;
+  r = utils::get_encryption_options(vm, &encryption_options);
+  if (r < 0) {
+    return r;
+  }
+
   librados::Rados rados;
   librados::IoCtx io_ctx;
   librbd::Image image;
@@ -57,6 +64,17 @@ int execute(const po::variables_map &vm,
     return r;
   }
 
+  auto spec_count = encryption_options.specs.size();
+  if (spec_count > 0) {
+    r = image.encryption_load2(&encryption_options.specs[0], spec_count);
+
+    if (r < 0) {
+      std::cerr << "rbd: encryption load failed: " << cpp_strerror(r)
+                << std::endl;
+      return r;
+    }
+  }
+
   r = do_flatten(image, vm[at::NO_PROGRESS].as<bool>());
   if (r < 0) {
     std::cerr << "rbd: flatten error: " << cpp_strerror(r) << std::endl;
index b089e1d5559322c3593a87081721f3dfdd4150f1..8ddeeb39423f402d802711a0bf63eb7c96860067 100644 (file)
@@ -119,8 +119,8 @@ struct Config {
   std::string format;
   bool pretty_format = false;
 
-  std::optional<librbd::encryption_format_t> encryption_format;
-  std::optional<std::string> encryption_passphrase_file;
+  std::vector<librbd::encryption_format_t> encryption_format;
+  std::vector<std::string> encryption_passphrase_file;
 
   Command command = None;
   int pid = 0;
@@ -1582,6 +1582,7 @@ static int do_map(int argc, const char *argv[], Config *cfg, bool reconnect)
   unsigned long size;
   unsigned long blksize = RBD_NBD_BLKSIZE;
   bool use_netlink;
+  auto encryption_format_count = cfg->encryption_format.size();
 
   int fd[2];
 
@@ -1662,64 +1663,68 @@ static int do_map(int argc, const char *argv[], Config *cfg, bool reconnect)
       goto close_fd;
   }
 
-  if (cfg->encryption_format.has_value()) {
-    if (!cfg->encryption_passphrase_file.has_value()) {
-      r = -EINVAL;
-      cerr << "rbd-nbd: missing encryption-passphrase-file" << std::endl;
-      goto close_fd;
-    }
-    std::ifstream file(cfg->encryption_passphrase_file.value().c_str());
-    if (file.fail()) {
-      r = -errno;
-      std::cerr << "rbd-nbd: unable to open passphrase file:"
-                << cpp_strerror(errno) << std::endl;
-      goto close_fd;
-    }
-    std::string passphrase((std::istreambuf_iterator<char>(file)),
-                           (std::istreambuf_iterator<char>()));
-    auto sg = make_scope_guard([&] {
-      ceph_memzero_s(&passphrase[0], passphrase.size(), passphrase.size()); });
-    file.close();
-    if (!passphrase.empty() && passphrase[passphrase.length() - 1] == '\n') {
-      passphrase.erase(passphrase.length() - 1);
-    }
+  if (encryption_format_count > 0) {
+    std::vector<librbd::encryption_spec_t> specs(encryption_format_count);
+    std::vector<librbd::encryption_luks_format_options_t> luks_opts;
 
-    switch (cfg->encryption_format.value()) {
-      case RBD_ENCRYPTION_FORMAT_LUKS1: {
-        librbd::encryption_luks1_format_options_t opts = {};
-        opts.passphrase = passphrase;
-        r = image.encryption_load(
-                RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts));
-        blksize = 4096;
-        break;
-      }
-      case RBD_ENCRYPTION_FORMAT_LUKS2: {
-        librbd::encryption_luks2_format_options_t opts = {};
-        opts.passphrase = passphrase;
-        r = image.encryption_load(
-                RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts));
-        blksize = 4096;
-        break;
+    luks_opts.reserve(encryption_format_count);
+
+    auto sg = make_scope_guard([&] {
+      for (auto& opts : luks_opts) {
+        auto& passphrase = opts.passphrase;
+        ceph_memzero_s(&passphrase[0], passphrase.size(), passphrase.size());
       }
-      case RBD_ENCRYPTION_FORMAT_LUKS: {
-        librbd::encryption_luks_format_options_t opts = {};
-        opts.passphrase = passphrase;
-        r = image.encryption_load(
-                RBD_ENCRYPTION_FORMAT_LUKS, &opts, sizeof(opts));
-        blksize = 4096;
-        break;
+    });
+
+    for (size_t i = 0; i < encryption_format_count; ++i) {
+      std::ifstream file(cfg->encryption_passphrase_file[i].c_str());
+      auto sg2 = make_scope_guard([&] { file.close(); });
+
+      specs[i].format = cfg->encryption_format[i];
+      std::string* passphrase;
+      switch (specs[i].format) {
+        case RBD_ENCRYPTION_FORMAT_LUKS: {
+          luks_opts.emplace_back();
+          specs[i].opts = &luks_opts.back();
+          specs[i].opts_size = sizeof(luks_opts.back());
+          passphrase = &luks_opts.back().passphrase;
+          break;
+        }
+        default:
+          r = -ENOTSUP;
+          cerr << "rbd-nbd: unsupported encryption format: " << specs[i].format
+               << std::endl;
+          goto close_fd;
       }
-      default:
-        r = -ENOTSUP;
-        cerr << "rbd-nbd: unsupported encryption format" << std::endl;
+
+      passphrase->assign((std::istreambuf_iterator<char>(file)),
+                         (std::istreambuf_iterator<char>()));
+
+      if (file.fail()) {
+        r = -errno;
+        std::cerr << "rbd-nbd: unable to open passphrase file '"
+                  << cfg->encryption_passphrase_file[i] << "': "
+                  << cpp_strerror(errno) << std::endl;
         goto close_fd;
+      }
+
+      if (!passphrase->empty() &&
+          (*passphrase)[passphrase->length() - 1] == '\n') {
+        passphrase->erase(passphrase->length() - 1);
+      }
     }
 
+    r = image.encryption_load2(&specs[0], encryption_format_count);
+
     if (r != 0) {
       cerr << "rbd-nbd: failed to load encryption: " << cpp_strerror(r)
            << std::endl;
       goto close_fd;
     }
+
+    // luks2 block size can vary upto 4096, while luks1 always uses 512
+    // currently we don't have an rbd API for querying the loaded encryption
+    blksize = 4096;
   }
 
   r = image.stat(info, sizeof(info));
@@ -2112,18 +2117,15 @@ static int parse_args(vector<const char*>& args, std::ostream *err_msg,
     } else if (ceph_argparse_witharg(args, i, &arg_value,
                                      "--encryption-format", (char *)NULL)) {
       if (arg_value == "luks1") {
-        cfg->encryption_format =
-                std::make_optional(RBD_ENCRYPTION_FORMAT_LUKS);
+        cfg->encryption_format.push_back(RBD_ENCRYPTION_FORMAT_LUKS);
         *err_msg << "rbd-nbd: specifying luks1 when loading encryption "
                     "is deprecated, use luks";
       } else if (arg_value == "luks2") {
-        cfg->encryption_format =
-                std::make_optional(RBD_ENCRYPTION_FORMAT_LUKS);
+        cfg->encryption_format.push_back(RBD_ENCRYPTION_FORMAT_LUKS);
         *err_msg << "rbd-nbd: specifying luks2 when loading encryption "
                     "is deprecated, use luks";
       } else if (arg_value == "luks") {
-        cfg->encryption_format =
-                std::make_optional(RBD_ENCRYPTION_FORMAT_LUKS);
+        cfg->encryption_format.push_back(RBD_ENCRYPTION_FORMAT_LUKS);
       } else {
         *err_msg << "rbd-nbd: Invalid encryption format";
         return -EINVAL;
@@ -2131,12 +2133,18 @@ static int parse_args(vector<const char*>& args, std::ostream *err_msg,
     } else if (ceph_argparse_witharg(args, i, &arg_value,
                                      "--encryption-passphrase-file",
                                      (char *)NULL)) {
-      cfg->encryption_passphrase_file = std::make_optional(arg_value);
+      cfg->encryption_passphrase_file.push_back(arg_value);
     } else {
       ++i;
     }
   }
 
+  if (cfg->encryption_format.size() != cfg->encryption_passphrase_file.size()) {
+    *err_msg << "rbd-nbd: Encryption formats count does not match "
+             << "passphrase files count";
+    return -EINVAL;
+  }
+
   Command cmd = None;
   if (args.begin() != args.end()) {
     if (strcmp(*args.begin(), "map") == 0) {