diff --git a/docs/usage/cli.en.md b/docs/usage/cli.en.md index 8a9e293b..843d7eed 100644 --- a/docs/usage/cli.en.md +++ b/docs/usage/cli.en.md @@ -125,18 +125,30 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0 ## create -`vitastor-cli create -s|--size [-p|--pool ] [--parent [@]] ` +`vitastor-cli create -s|--size SIZE [OPTIONS] ` -Create an image. You may use K/M/G/T suffixes for ``. If `--parent` is specified, -a copy-on-write image clone is created. Parent must be a snapshot (readonly image). -Pool must be specified if there is more than one pool. +Create an image. Options: + +* `-s|--size SIZE` - New image size in bytes or with a K/M/G/T unit suffix. +* `-p|--pool POOL` - Specify pool for the new image (may be omitted if there is only 1 pool). +* `--parent PARENT` - Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT). + If parent is not a snapshot, it must be a read-only image. +* `--enc-key random` - Generate a new random AES-256-XTS encryption key for the new image. +* `--enc-key HEX` - Set a specified AES-256-XTS key (64 bytes in hex) for the new image. ``` -vitastor-cli create --snapshot [-p|--pool ] -vitastor-cli snap-create [-p|--pool ] @ +vitastor-cli create --snapshot [OPTIONS] +vitastor-cli snap-create [OPTIONS] @ ``` -Create a snapshot of image `` (either form can be used). May be used live if only a single writer is active. +Create a snapshot of image ``. May be used live if only a single writer is active. + +Options: + +* `-p|--pool POOL` - Move image to pool POOL, leaving the snapshot in the old pool. +* `--enc-key random` - Change image encryption key to a new random AES-256-XTS key. +* `--enc-key HEX` - Change image encryption key to a specified key or to an empty key. + By default, the image retains its old encryption key when taking a snapshot. See also about [how to export snapshots](qemu.en.md#exporting-snapshots). @@ -151,6 +163,7 @@ You should resize file system in the image, if present, before shrinking it. * `--deleted 1|0` - Set/clear 'deleted image' flag (set automatically during unfinished deletes). * `-f|--force` - Proceed with shrinking or setting readwrite flag even if the image has children. * `--down-ok` - Proceed with shrinking even if some data will be left on unavailable OSDs. +* `--enc-key HEX` - Change image encryption key (allowed only with `--force`). ## dd diff --git a/docs/usage/cli.ru.md b/docs/usage/cli.ru.md index 6614f85a..f32b4857 100644 --- a/docs/usage/cli.ru.md +++ b/docs/usage/cli.ru.md @@ -127,19 +127,31 @@ bench-kaveri kaveri 10 G 10 G 0 B/s 0 0 0 us 0 B/s 0 ## create -`vitastor-cli create -s|--size [-p|--pool ] [--parent [@]] ` +`vitastor-cli create -s|--size SIZE [ОПЦИИ] ` -Создать образ. Для размера `` можно использовать суффиксы K/M/G/T (килобайт-мегабайт-гигабайт-терабайт). -Если указана опция `--parent`, создаётся клон образа. Родитель `[@]` должен быть -снимком (или просто немодифицируемым образом). Пул обязательно указывать, если в кластере больше одного пула. +Создать образ. Опции: + +* `-s|--size SIZE` - Размер нового образа в байтах или с суффиксом K/M/G/T (кило/мега/гига/терабайт). +* `-p|--pool POOL` - Создать образ в заданном пуле (можно не указывать, если пул всего один). +* `--parent PARENT` - Создать легковесный клон на основе образа `PARENT` или снимка `PARENT@SNAP`. + Если `PARENT` - не снимок, он должен быть помечен как образ только для чтения. +* `--enc-key random` - Сгенерировать случайный ключ шифрования AES-256-XTS для нового образа. +* `--enc-key HEX` - Установить заданный ключ AES-256-XTS (64 байта в hex) для нового образа. ``` -vitastor-cli create --snapshot [-p|--pool ] -vitastor-cli snap-create [-p|--pool ] @ +vitastor-cli create --snapshot [ОПЦИИ] +vitastor-cli snap-create [ОПЦИИ] @ ``` -Создать снимок образа `` (можно использовать любую форму команды). Снимок можно создавать без остановки -клиентов, если пишущий клиент максимум 1. +Создать снимок образа `` (можно использовать любую форму команды). +Снимок можно создавать без остановки клиентов, если пишущих клиентов не больше одного. + +Опции: + +* `-p|--pool POOL` - Переместить образ в пул POOL, оставив снимок в старом пуле. +* `--enc-key random` - Изменить ключ шифрования образа на новый случайный ключ AES-256-XTS. +* `--enc-key HEX` - Изменить ключ шифрования образа на заданный или пустой ключ. + По умолчанию шифрованные образы сохраняют старый ключ при снятии снимка. Смотрите также информацию о том, [как экспортировать снимки](qemu.ru.md#экспорт-снимков). @@ -156,6 +168,7 @@ vitastor-cli snap-create [-p|--pool ] @ * `--deleted 1|0` - Установить/снять флаг "образ удалён" (устанавливается при незавершённом удалении). * `-f|--force` - Разрешить уменьшение или перевод в чтение-запись образа, у которого есть клоны. * `--down-ok` - Разрешить уменьшение, даже если часть данных останется неудалённой на недоступных OSD. +* `--enc-key HEX` - Изменить ключ шифрования образа (разрешено только с `--force`). ## dd diff --git a/src/cmd/cli.cpp b/src/cmd/cli.cpp index cc6c6b05..69f02226 100644 --- a/src/cmd/cli.cpp +++ b/src/cmd/cli.cpp @@ -40,14 +40,23 @@ static const char* help_text = " --ids ID1,ID2 Only list images with specified full IDs\n" " --tree Show image snapshot/clone tree\n" "\n" - "vitastor-cli create -s|--size [-p|--pool ] [--parent [@]] \n" - " Create an image. You may use K/M/G/T suffixes for . If --parent is specified,\n" - " a copy-on-write image clone is created. Parent must be a snapshot (readonly image).\n" - " Pool must be specified if there is more than one pool.\n" + "vitastor-cli create -s|--size SIZE [OPTIONS] \n" + " Create an image. Options:\n" + " -s|--size SIZE New image size in bytes or with a K/M/G/T unit suffix.\n" + " -p|--pool POOL Specify pool for the new image (may be omitted if there is only 1 pool).\n" + " --parent PARENT Create a copy-on-write image clone based on PARENT (or PARENT@SNAPSHOT).\n" + " If parent is not a snapshot, it must be a read-only image.\n" + " --enc-key random Generate a new random AES-256-XTS encryption key for the new image.\n" + " --enc-key HEX Set a specified AES-256-XTS key (64 bytes in hex) for the new image.\n" "\n" - "vitastor-cli create --snapshot [-p|--pool ] \n" - "vitastor-cli snap-create [-p|--pool ] @\n" - " Create a snapshot of image . May be used live if only a single writer is active.\n" + "vitastor-cli create --snapshot [OPTIONS] \n" + "vitastor-cli snap-create [OPTIONS] @\n" + " Create a snapshot of image . May be used live if only a single writer is active.\n" + " Options:\n" + " -p|--pool POOL Move image to pool POOL, leaving the snapshot in the old pool.\n" + " --enc-key random Change image encryption key to a new random AES-256-XTS key.\n" + " --enc-key HEX Change image encryption key to a specified key or to an empty key.\n" + " By default, the image retains its old key when taking a snapshot.\n" "\n" "vitastor-cli modify [--rename ] [--resize ] [--readonly | --readwrite] [-f|--force] [--down-ok]\n" " Rename, resize image or change its readonly status. Images with children can't be made read-write.\n" @@ -56,6 +65,7 @@ static const char* help_text = " --deleted 1|0 Set/clear 'deleted image' flag (set automatically during unfinished deletes).\n" " -f|--force Proceed with shrinking or setting readwrite flag even if the image has children.\n" " --down-ok Proceed with shrinking even if some data will be left on unavailable OSDs.\n" + " --enc-key HEX Change image encryption key (allowed only with --force).\n" "\n" "vitastor-cli dd [iimg= | if=] [oimg= | of=] [bs=1M]\n" " [count=N] [seek/oseek=N] [skip/iseek=M] [iodepth=N] [status=progress]\n" diff --git a/src/cmd/cli_create.cpp b/src/cmd/cli_create.cpp index 302fd4c9..6672078b 100644 --- a/src/cmd/cli_create.cpp +++ b/src/cmd/cli_create.cpp @@ -1,6 +1,10 @@ // Copyright (c) Vitaliy Filippov, 2019+ // License: VNPL-1.1 (see README.md for details) +#ifdef WITH_OPENSSL +#include +#endif + #include #include "cli.h" #include "cluster_client.h" @@ -29,6 +33,8 @@ struct image_creator_t uint64_t size = 0; bool force = false; bool force_size = false; + std::string enc_key; + bool set_key = false; pool_id_t old_pool_id = 0; inode_t new_parent_id = 0; @@ -448,6 +454,13 @@ resume_3: .readonly = false, .meta = new_meta, }; + if (set_key) + { + new_cfg.enc_key.resize(enc_key.size()/2); + fromhexstr(enc_key, new_cfg.enc_key.size(), new_cfg.enc_key.data()); + } + else if (new_snap != "") + new_cfg.enc_key = cur_cfg.enc_key; json11::Json::array checks = json11::Json::array { json11::Json::object { { "target", "VERSION" }, @@ -584,6 +597,31 @@ std::function cli_tool_t::start_create(json11::Json cfg) { image_creator->new_snap = cfg["snapshot"].string_value(); } + if (!cfg["enc_key"].is_null()) + { + image_creator->set_key = true; + image_creator->enc_key = cfg["enc_key"].string_value(); + if (image_creator->enc_key != "" && +#ifdef WITH_OPENSSL + image_creator->enc_key != "random" && +#endif + (!ishexstr(image_creator->enc_key) || image_creator->enc_key.size() != 128)) + { + return [](cli_result_t & result) + { + result = (cli_result_t){ .err = EINVAL, .text = "Encryption key is not a 512-bit hex string, not \"\" and not \"random\"" }; + return true; + }; + } +#ifdef WITH_OPENSSL + if (image_creator->enc_key == "random") + { + uint8_t newkey[64]; + RAND_bytes(newkey, 64); + image_creator->enc_key = tohexstr(newkey, 64); + } +#endif + } image_creator->new_parent = cfg["parent"].string_value(); if (!cfg["size"].is_null()) { diff --git a/src/cmd/cli_modify.cpp b/src/cmd/cli_modify.cpp index 3402e43f..5967078b 100644 --- a/src/cmd/cli_modify.cpp +++ b/src/cmd/cli_modify.cpp @@ -17,6 +17,8 @@ struct image_changer_t bool force_size = false, inc_size = false; bool set_readonly = false, set_readwrite = false, force = false; bool set_deleted = false, new_deleted = false; + bool set_key = false; + std::string enc_key; bool down_ok = false; // interval between fsyncs int fsync_interval = 128; @@ -149,6 +151,23 @@ resume_1: { cfg.name = new_name; } + if (set_key) + { + if (!force) + { + result = (cli_result_t){ .err = EINVAL, .text = "Changing image encryption key is only allowed with --force" }; + state = 100; + return; + } + if (enc_key != "" && (!ishexstr(enc_key) || enc_key.size() != 128)) + { + result = (cli_result_t){ .err = EINVAL, .text = "Encryption key is not a 512-bit hex string and not \"\"" }; + state = 100; + return; + } + cfg.enc_key.resize(enc_key.size()/2); + fromhexstr(enc_key, cfg.enc_key.size(), cfg.enc_key.data()); + } { std::string cur_cfg_key = base64_encode(parent->cli->st_cli.etcd_prefix+ "/config/inode/"+std::to_string(INODE_POOL(inode_num))+ @@ -285,6 +304,8 @@ std::function cli_tool_t::start_modify(json11::Json cfg) changer->set_deleted = !cfg["deleted"].is_null(); changer->new_deleted = json_is_true(cfg["deleted"]); changer->fsync_interval = cfg["fsync_interval"].uint64_value(); + changer->enc_key = cfg["enc_key"].string_value(); + changer->set_key = cfg["enc_key"].is_string(); if (!changer->fsync_interval) changer->fsync_interval = 128; changer->down_ok = cfg["down_ok"].bool_value(); diff --git a/src/cmd/openapi.json b/src/cmd/openapi.json index fb679540..f710c48b 100644 --- a/src/cmd/openapi.json +++ b/src/cmd/openapi.json @@ -101,7 +101,14 @@ }, "pool_id": { "type": "integer", "format": "uint64", "description": "Pool ID for the new image/snapshot" }, "pool_name": { "type": "string", "description": "Pool name for the new image/snapshot" }, - "parent": { "type": "string", "description": "Create a clone with this parent image name" } + "parent": { "type": "string", "description": "Create a clone with this parent image name" }, + "enc_key": { + "description": "Encryption key for the new/cloned image", + "oneOf": [ + { "type": "string", "enum": [ "", "random" ] }, + { "type": "string", "pattern": "^[0-9a-fA-F]{128}$" } + ] + } } } } } }, "responses": { @@ -132,6 +139,13 @@ "readonly": { "type": "boolean", "description": "Make the image read-only" }, "readwrite": { "type": "boolean", "description": "Make the image read-write" }, "deleted": { "type": "boolean", "description": "Set or clear the 'deleted' flag" }, + "enc_key": { + "description": "Change encryption key for the image", + "oneOf": [ + { "type": "string", "enum": [ "" ] }, + { "type": "string", "pattern": "^[0-9a-fA-F]{128}$" } + ] + }, "force": { "type": "boolean", "description": "Proceed with shrinking or setting readwrite flag even if the image has children" }, "down_ok": { "type": "boolean", "description": "Proceed with shrinking even if some data will be left on unavailable OSDs" } } diff --git a/src/util/str_util.cpp b/src/util/str_util.cpp index b2ec85db..41a9587d 100644 --- a/src/util/str_util.cpp +++ b/src/util/str_util.cpp @@ -565,3 +565,17 @@ std::string tohexstr(const uint8_t *from, size_t bytes) } return res; } + +bool ishexstr(const std::string & str) +{ + if (str.size() % 2) + return false; + for (auto & c: str) + { + if ((c < '0' || c > '9') && + (c < 'a' || c > 'f') && + (c < 'A' || c > 'F')) + return false; + } + return true; +} diff --git a/src/util/str_util.h b/src/util/str_util.h index ac8e9f25..5b558c01 100644 --- a/src/util/str_util.h +++ b/src/util/str_util.h @@ -37,5 +37,6 @@ bool is_zero(void *buf, size_t size); std::string urldecode(const std::string & orig); size_t fromhexstr(const std::string & from, size_t bytes, uint8_t *to); std::string tohexstr(const uint8_t *from, size_t bytes); +bool ishexstr(const std::string & str); #pragma GCC visibility pop