]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: add encryption cli 38838/head
authorOr Ozeri <oro@il.ibm.com>
Sun, 10 Jan 2021 18:57:32 +0000 (20:57 +0200)
committerOr Ozeri <oro@il.ibm.com>
Wed, 13 Jan 2021 15:57:17 +0000 (17:57 +0200)
This commit adds rbd encryption cli (encryption format and nbd map)

Signed-off-by: Or Ozeri <oro@il.ibm.com>
doc/man/8/rbd-nbd.rst
doc/man/8/rbd.rst
src/test/cli/rbd/help.t
src/tools/rbd/ArgumentTypes.cc
src/tools/rbd/ArgumentTypes.h
src/tools/rbd/CMakeLists.txt
src/tools/rbd/action/Encryption.cc [new file with mode: 0644]
src/tools/rbd_nbd/rbd-nbd.cc

index dce7f032cc062b897c99cc96e516eb24392ac158..8fcc5799de84ec1686a5d19898c61e7c4017518c 100644 (file)
@@ -9,7 +9,7 @@
 Synopsis
 ========
 
-| **rbd-nbd** [-c conf] [--read-only] [--device *nbd device*] [--nbds_max *limit*] [--max_part *limit*] [--exclusive] [--io-timeout *seconds*] [--reattach-timeout *seconds*] map *image-spec* | *snap-spec*
+| **rbd-nbd** [-c conf] [--read-only] [--device *nbd device*] [--nbds_max *limit*] [--max_part *limit*] [--exclusive] [--encryption-format *format*] [--encryption-passphrase-file *passphrase-file*] [--io-timeout *seconds*] [--reattach-timeout *seconds*] map *image-spec* | *snap-spec*
 | **rbd-nbd** unmap *nbd device* | *image-spec* | *snap-spec*
 | **rbd-nbd** list-mapped
 | **rbd-nbd** attach --device *nbd device* *image-spec* | *snap-spec*
@@ -47,6 +47,15 @@ Options
 
    Forbid writes by other clients.
 
+.. option:: --encryption-format
+
+   Image encryption format.
+   Possible values: *luks1*, *luks2*
+
+.. option:: --encryption-passphrase-file
+
+   Path of file containing a passphrase for unlocking image encryption.
+
 .. option:: --io-timeout *seconds*
 
    Override device timeout. Linux kernel will default to a 30 second request timeout.
index 89ce32bac9cd8ba91d2496a89edeedd8cf6f107a..94cdd0bfdde17bdf6230d64b56b196815407f5f0 100644 (file)
@@ -284,6 +284,13 @@ Commands
 
   The --merge-snapshots will merge snapshots used space into their parent images.
 
+: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).
+
 :command:`export` [--export-format *format (1 or 2)*] (*image-spec* | *snap-spec*) [*dest-path*]
   Export image to dest path (use - for stdout).
   The --export-format accepts '1' or '2' currently. Format 2 allow us to export not only the content
index fb9f1b4737c9a71a25f710d7a791a1a81871a441..8b9390713b643cb868bc819e90f3d14664352bb4 100644 (file)
@@ -34,6 +34,7 @@
                                         previous snap, or image creation.
       disk-usage (du)                   Show disk usage stats for pool, image or
                                         snapshot.
+      encryption format                 Format image to an encrypted format.
       export                            Export image to file.
       export-diff                       Export incremental diff to file.
       feature disable                   Disable the specified image feature.
     --exact               compute exact disk usage (slow)
     --merge-snapshots     merge snapshot sizes with its image
   
+  rbd help encryption format
+  usage: rbd encryption format [--pool <pool>] [--namespace <namespace>] 
+                               [--image <image>] [--cipher-alg <cipher-alg>] 
+                               <image-spec> <format> <passphrase-file> 
+  
+  Format image to an encrypted format.
+  
+  Positional arguments
+    <image-spec>         image specification
+                         (example: [<pool-name>/[<namespace>/]]<image-name>)
+    <format>             encryption format [possible values: luks1, luks2]
+    <passphrase-file>    path of file containing passphrase for unlocking the
+                         image
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --namespace arg      namespace name
+    --image arg          image name
+    --cipher-alg arg     encryption algorithm [possible values: aes-128, aes-256
+                         (default)]
+  
   rbd help export
   usage: rbd export [--pool <pool>] [--namespace <namespace>] [--image <image>] 
                     [--snap <snap>] [--path <path>] [--no-progress] 
index b9eb6f5340f9e6f61dd4cc3af18c9a438f895768..7b111b811a3b792a3be4bcd3b4753160f11e5f65 100644 (file)
@@ -508,6 +508,19 @@ void validate(boost::any& v, const std::vector<std::string>& values,
   throw po::validation_error(po::validation_error::invalid_option_value);
 }
 
+void validate(boost::any& v, const std::vector<std::string>& values,
+              EncryptionAlgorithm *target_type, int) {
+  po::validators::check_first_occurrence(v);
+  const std::string &s = po::validators::get_single_string(values);
+  if (s == "aes-128") {
+    v = boost::any(RBD_ENCRYPTION_ALGORITHM_AES128);
+  } else if (s == "aes-256") {
+    v = boost::any(RBD_ENCRYPTION_ALGORITHM_AES256);
+  } else {
+    throw po::validation_error(po::validation_error::invalid_option_value);
+  }
+}
+
 void validate(boost::any& v, const std::vector<std::string>& values,
               ExportFormat *target_type, int) {
   po::validators::check_first_occurrence(v);
index 5f500e52072c47c51beeb1f8102a8e5cbfa65a20..57473f31c6564cb455f4c18e795bafabd23a0f35 100644 (file)
@@ -125,6 +125,8 @@ struct ExportFormat {};
 
 struct Secret {};
 
+struct EncryptionAlgorithm {};
+
 void add_export_format_option(boost::program_options::options_description *opt);
 
 std::string get_name_prefix(ArgumentModifier modifier);
@@ -216,6 +218,8 @@ void validate(boost::any& v, const std::vector<std::string>& values,
               Format *target_type, int);
 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,
               Secret *target_type, int);
 
index 8ab1db3d2a82b6d91fbd6c6815979370095f33b8..d3052cff1137c1e9b1cb56cf54450539b7e966e0 100644 (file)
@@ -20,6 +20,7 @@ set(rbd_srcs
   action/Device.cc
   action/Diff.cc
   action/DiskUsage.cc
+  action/Encryption.cc
   action/Export.cc
   action/Feature.cc
   action/Flatten.cc
diff --git a/src/tools/rbd/action/Encryption.cc b/src/tools/rbd/action/Encryption.cc
new file mode 100644 (file)
index 0000000..7796eee
--- /dev/null
@@ -0,0 +1,119 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "include/scope_guard.h"
+#include "tools/rbd/ArgumentTypes.h"
+#include "tools/rbd/Shell.h"
+#include "tools/rbd/Utils.h"
+#include "common/errno.h"
+#include <fstream>
+#include <iostream>
+#include <boost/program_options.hpp>
+
+namespace rbd {
+namespace action {
+namespace encryption {
+
+namespace at = argument_types;
+namespace po = boost::program_options;
+
+void get_arguments(po::options_description *positional,
+                   po::options_description *options) {
+  at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE);
+  positional->add_options()
+    ("format", "encryption format [possible values: luks1, luks2]")
+    ("passphrase-file",
+      "path of file containing passphrase for unlocking the image");
+  options->add_options()
+    ("cipher-alg", po::value<at::EncryptionAlgorithm>(),
+     "encryption algorithm [possible values: aes-128, aes-256 (default)]");
+}
+
+int execute(const po::variables_map &vm,
+            const std::vector<std::string> &ceph_global_init_args) {
+  size_t arg_index = 0;
+  std::string pool_name;
+  std::string namespace_name;
+  std::string image_name;
+  std::string snap_name;
+  int r = utils::get_pool_image_snapshot_names(
+    vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &namespace_name,
+    &image_name, &snap_name, true, utils::SNAPSHOT_PRESENCE_NONE,
+    utils::SPEC_VALIDATION_NONE);
+  if (r < 0) {
+    return r;
+  }
+
+  std::string format_str = utils::get_positional_argument(vm, arg_index++);
+  if (format_str.empty()) {
+    std::cerr << "rbd: must specify format." << std::endl;
+    return -EINVAL;
+  }
+
+  std::string passphrase_file =
+          utils::get_positional_argument(vm, arg_index++);
+  if (passphrase_file.empty()) {
+    std::cerr << "rbd: must specify passphrase-file." << std::endl;
+    return -EINVAL;
+  }
+
+  std::ifstream file(passphrase_file.c_str());
+  if (file.fail()) {
+    std::cerr << "rbd: unable to open passphrase file " << passphrase_file
+              << ": " << cpp_strerror(errno) << std::endl;
+    return -errno;
+  }
+  std::string passphrase((std::istreambuf_iterator<char>(file)),
+                         (std::istreambuf_iterator<char>()));
+  auto sg = make_scope_guard([&] {
+      explicit_bzero(&passphrase[0], passphrase.size()); });
+  file.close();
+  if (!passphrase.empty() && passphrase[passphrase.length() - 1] == '\n') {
+    passphrase.erase(passphrase.length() - 1);
+  }
+
+  auto alg = RBD_ENCRYPTION_ALGORITHM_AES256;
+  if (vm.count("cipher-alg")) {
+    alg = vm["cipher-alg"].as<librbd::encryption_algorithm_t>();
+  }
+
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  librbd::Image image;
+  r = utils::init_and_open_image(pool_name, namespace_name, image_name, "", "",
+                                 false, &rados, &io_ctx, &image);
+  if (r < 0) {
+    return r;
+  }
+
+  if (format_str == "luks1") {
+    librbd::encryption_luks1_format_options_t opts = {};
+    opts.alg = alg;
+    opts.passphrase = passphrase;
+    r = image.encryption_format(
+            RBD_ENCRYPTION_FORMAT_LUKS1, &opts, sizeof(opts));
+  } else if (format_str == "luks2") {
+    librbd::encryption_luks2_format_options_t opts = {};
+    opts.alg = alg;
+    opts.passphrase = passphrase;
+    r = image.encryption_format(
+            RBD_ENCRYPTION_FORMAT_LUKS2, &opts, sizeof(opts));
+  } else {
+    std::cerr << "rbd: unsupported encryption format" << std::endl;
+    return -ENOTSUP;
+  }
+
+  if (r < 0) {
+    std::cerr << "rbd: encryption format error: " << cpp_strerror(r)
+              << std::endl;
+  }
+  return r;
+}
+
+Shell::Action action(
+  {"encryption", "format"}, {}, "Format image to an encrypted format.", "",
+  &get_arguments, &execute);
+
+} // namespace encryption
+} // namespace action
+} // namespace rbd
index d2e11405f8d1ff852c15eac09a71591077433062..c692e0ce0896410d202c36031bfab9ff5c418443 100644 (file)
@@ -18,6 +18,7 @@
 
 #include "acconfig.h"
 #include "include/int_types.h"
+#include "include/scope_guard.h"
 
 #include <libgen.h>
 #include <stdio.h>
@@ -114,6 +115,9 @@ struct Config {
   std::string format;
   bool pretty_format = false;
 
+  std::optional<librbd::encryption_format_t> encryption_format;
+  std::optional<std::string> encryption_passphrase_file;
+
   Command command = None;
   int pid = 0;
 
@@ -141,18 +145,21 @@ static void usage()
             << "               unmap <device|image-or-snap-spec>     Unmap nbd device\n"
             << "               [options] list-mapped                 List mapped nbd devices\n"
             << "Map and attach options:\n"
-            << "  --device <device path>   Specify nbd device path (/dev/nbd{num})\n"
-            << "  --exclusive              Forbid writes by other clients\n"
-            << "  --io-timeout <sec>       Set nbd IO timeout\n"
-            << "  --max_part <limit>       Override for module param max_part\n"
-            << "  --nbds_max <limit>       Override for module param nbds_max\n"
-            << "  --quiesce                Use quiesce callbacks\n"
-            << "  --quiesce-hook <path>    Specify quiesce hook path\n"
-            << "                           (default: " << Config().quiesce_hook << ")\n"
-            << "  --read-only              Map read-only\n"
-            << "  --reattach-timeout <sec> Set nbd re-attach timeout\n"
-            << "                           (default: " << Config().reattach_timeout << ")\n"
-            << "  --try-netlink            Use the nbd netlink interface\n"
+            << "  --device <device path>        Specify nbd device path (/dev/nbd{num})\n"
+            << "  --encryption-format           Image encryption format\n"
+            << "                                (possible values: luks1, luks2)\n"
+            << "  --encryption-passphrase-file  Path of file containing passphrase for unlocking image encryption\n"
+            << "  --exclusive                   Forbid writes by other clients\n"
+            << "  --io-timeout <sec>            Set nbd IO timeout\n"
+            << "  --max_part <limit>            Override for module param max_part\n"
+            << "  --nbds_max <limit>            Override for module param nbds_max\n"
+            << "  --quiesce                     Use quiesce callbacks\n"
+            << "  --quiesce-hook <path>         Specify quiesce hook path\n"
+            << "                                (default: " << Config().quiesce_hook << ")\n"
+            << "  --read-only                   Map read-only\n"
+            << "  --reattach-timeout <sec>      Set nbd re-attach timeout\n"
+            << "                                (default: " << Config().reattach_timeout << ")\n"
+            << "  --try-netlink                 Use the nbd netlink interface\n"
             << "\n"
             << "List options:\n"
             << "  --format plain|json|xml Output format (default: plain)\n"
@@ -1635,6 +1642,56 @@ 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([&] {
+      explicit_bzero(&passphrase[0], passphrase.size()); });
+    file.close();
+    if (!passphrase.empty() && passphrase[passphrase.length() - 1] == '\n') {
+      passphrase.erase(passphrase.length() - 1);
+    }
+
+    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));
+        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));
+        break;
+      }
+      default:
+        r = -ENOTSUP;
+        cerr << "rbd-nbd: unsupported encryption format" << std::endl;
+        goto close_fd;
+    }
+
+    if (r != 0) {
+      cerr << "rbd-nbd: failed to load encryption: " << cpp_strerror(r)
+           << std::endl;
+      goto close_fd;
+    }
+  }
+
   r = image.stat(info, sizeof(info));
   if (r < 0)
     goto close_fd;
@@ -1925,6 +1982,7 @@ static int parse_args(vector<const char*>& args, std::ostream *err_msg,
 
   std::vector<const char*>::iterator i;
   std::ostringstream err;
+  std::string arg_value;
 
   for (i = args.begin(); i != args.end(); ) {
     if (ceph_argparse_flag(args, i, "-h", "--help", (char*)NULL)) {
@@ -1996,6 +2054,22 @@ static int parse_args(vector<const char*>& args, std::ostream *err_msg,
       cfg->pretty_format = true;
     } else if (ceph_argparse_flag(args, i, "--try-netlink", (char *)NULL)) {
       cfg->try_netlink = true;
+    } 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_LUKS1);
+      } else if (arg_value == "luks2") {
+        cfg->encryption_format =
+                std::make_optional(RBD_ENCRYPTION_FORMAT_LUKS2);
+      } else {
+        *err_msg << "rbd-nbd: Invalid encryption format";
+        return -EINVAL;
+      }
+    } else if (ceph_argparse_witharg(args, i, &arg_value,
+                                     "--encryption-passphrase-file",
+                                     (char *)NULL)) {
+      cfg->encryption_passphrase_file = std::make_optional(arg_value);
     } else {
       ++i;
     }