From b2c3d5d4b2f89e2e21645dd476869657b1421d24 Mon Sep 17 00:00:00 2001 From: Or Ozeri Date: Sun, 10 Jan 2021 20:57:32 +0200 Subject: [PATCH] librbd: add encryption cli This commit adds rbd encryption cli (encryption format and nbd map) Signed-off-by: Or Ozeri --- doc/man/8/rbd-nbd.rst | 11 ++- doc/man/8/rbd.rst | 7 ++ src/test/cli/rbd/help.t | 22 ++++++ src/tools/rbd/ArgumentTypes.cc | 13 ++++ src/tools/rbd/ArgumentTypes.h | 4 + src/tools/rbd/CMakeLists.txt | 1 + src/tools/rbd/action/Encryption.cc | 119 +++++++++++++++++++++++++++++ src/tools/rbd_nbd/rbd-nbd.cc | 98 +++++++++++++++++++++--- 8 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 src/tools/rbd/action/Encryption.cc diff --git a/doc/man/8/rbd-nbd.rst b/doc/man/8/rbd-nbd.rst index dce7f032cc0..8fcc5799de8 100644 --- a/doc/man/8/rbd-nbd.rst +++ b/doc/man/8/rbd-nbd.rst @@ -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. diff --git a/doc/man/8/rbd.rst b/doc/man/8/rbd.rst index 89ce32bac9c..94cdd0bfdde 100644 --- a/doc/man/8/rbd.rst +++ b/doc/man/8/rbd.rst @@ -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 diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index fb9f1b4737c..8b9390713b6 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -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. @@ -674,6 +675,27 @@ --exact compute exact disk usage (slow) --merge-snapshots merge snapshot sizes with its image + rbd help encryption format + usage: rbd encryption format [--pool ] [--namespace ] + [--image ] [--cipher-alg ] + + + Format image to an encrypted format. + + Positional arguments + image specification + (example: [/[/]]) + encryption format [possible values: luks1, luks2] + 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 ] [--namespace ] [--image ] [--snap ] [--path ] [--no-progress] diff --git a/src/tools/rbd/ArgumentTypes.cc b/src/tools/rbd/ArgumentTypes.cc index b9eb6f5340f..7b111b811a3 100644 --- a/src/tools/rbd/ArgumentTypes.cc +++ b/src/tools/rbd/ArgumentTypes.cc @@ -508,6 +508,19 @@ void validate(boost::any& v, const std::vector& values, throw po::validation_error(po::validation_error::invalid_option_value); } +void validate(boost::any& v, const std::vector& 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& values, ExportFormat *target_type, int) { po::validators::check_first_occurrence(v); diff --git a/src/tools/rbd/ArgumentTypes.h b/src/tools/rbd/ArgumentTypes.h index 5f500e52072..57473f31c65 100644 --- a/src/tools/rbd/ArgumentTypes.h +++ b/src/tools/rbd/ArgumentTypes.h @@ -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& values, Format *target_type, int); void validate(boost::any& v, const std::vector& values, JournalObjectSize *target_type, int); +void validate(boost::any& v, const std::vector& values, + EncryptionAlgorithm *target_type, int); void validate(boost::any& v, const std::vector& values, Secret *target_type, int); diff --git a/src/tools/rbd/CMakeLists.txt b/src/tools/rbd/CMakeLists.txt index 8ab1db3d2a8..d3052cff113 100644 --- a/src/tools/rbd/CMakeLists.txt +++ b/src/tools/rbd/CMakeLists.txt @@ -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 index 00000000000..7796eee267d --- /dev/null +++ b/src/tools/rbd/action/Encryption.cc @@ -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 +#include +#include + +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(), + "encryption algorithm [possible values: aes-128, aes-256 (default)]"); +} + +int execute(const po::variables_map &vm, + const std::vector &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(file)), + (std::istreambuf_iterator())); + 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(); + } + + 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 diff --git a/src/tools/rbd_nbd/rbd-nbd.cc b/src/tools/rbd_nbd/rbd-nbd.cc index d2e11405f8d..c692e0ce089 100644 --- a/src/tools/rbd_nbd/rbd-nbd.cc +++ b/src/tools/rbd_nbd/rbd-nbd.cc @@ -18,6 +18,7 @@ #include "acconfig.h" #include "include/int_types.h" +#include "include/scope_guard.h" #include #include @@ -114,6 +115,9 @@ struct Config { std::string format; bool pretty_format = false; + std::optional encryption_format; + std::optional encryption_passphrase_file; + Command command = None; int pid = 0; @@ -141,18 +145,21 @@ static void usage() << " unmap Unmap nbd device\n" << " [options] list-mapped List mapped nbd devices\n" << "Map and attach options:\n" - << " --device Specify nbd device path (/dev/nbd{num})\n" - << " --exclusive Forbid writes by other clients\n" - << " --io-timeout Set nbd IO timeout\n" - << " --max_part Override for module param max_part\n" - << " --nbds_max Override for module param nbds_max\n" - << " --quiesce Use quiesce callbacks\n" - << " --quiesce-hook Specify quiesce hook path\n" - << " (default: " << Config().quiesce_hook << ")\n" - << " --read-only Map read-only\n" - << " --reattach-timeout Set nbd re-attach timeout\n" - << " (default: " << Config().reattach_timeout << ")\n" - << " --try-netlink Use the nbd netlink interface\n" + << " --device 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 Set nbd IO timeout\n" + << " --max_part Override for module param max_part\n" + << " --nbds_max Override for module param nbds_max\n" + << " --quiesce Use quiesce callbacks\n" + << " --quiesce-hook Specify quiesce hook path\n" + << " (default: " << Config().quiesce_hook << ")\n" + << " --read-only Map read-only\n" + << " --reattach-timeout 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(file)), + (std::istreambuf_iterator())); + 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& args, std::ostream *err_msg, std::vector::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& 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; } -- 2.39.5