Files
scylla/tools/scylla-local-file-key-generator.cc
Calle Wilund f901beec87 tools: Add local-file-key-generator tool
For generating key files for local provider
2025-01-09 10:40:47 +00:00

167 lines
5.2 KiB
C++

/*
* Copyright (C) 2020-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#include <seastar/core/coroutine.hh>
#include <filesystem>
#include <iostream>
#include <ranges>
#include <fmt/ranges.h>
#include "compound.hh"
#include "db/marshal/type_parser.hh"
#include "schema/schema_builder.hh"
#include "tools/utils.hh"
#include "dht/i_partitioner.hh"
#include "utils/managed_bytes.hh"
#include "ent/encryption/symmetric_key.hh"
#include "ent/encryption/local_file_provider.hh"
using namespace seastar;
using namespace tools::utils;
namespace bpo = boost::program_options;
namespace std {
// required by boost::lexical_cast<std::string>(vector<string>), which is in turn used
// by boost::program_option for printing out the default value of an option
static std::ostream& operator<<(std::ostream& os, const std::vector<std::string>& v) {
return os << fmt::format("{}", v);
}
}
namespace {
const auto app_name = "local-file-key-generator";
const std::vector<operation_option> global_options{
typed_option<std::string>("alg,a", "AES", "Key algorithm (i.e. AES, 3DES)"),
typed_option<std::string>("block-mode,b", "CBC", "Algorithm block mode (i.e. CBC, EBC)"),
typed_option<std::string>("padding,p", "PKCS5", "Algorithm padding method (i.e. PKCS5)"),
typed_option<unsigned>("length,l", 128, "Key length in bits (i.e. 128, 256)"),
};
const std::vector<operation_option> global_positional_options{
typed_option<std::vector<std::string>>("files", "key path|key name", -1),
};
const std::vector<operation> operations = {
{"generate", "creates a new key and stores to a new file",
R"(
Generate a key suitable for a given algorithm and key length
and store to a file readable by scylla encryption at rest
local file key provider.
)"},
{"append", "same as generate, but appends key to existing file",
R"(
Generate a key suitable for a given algorithm and key length
and append to an existing file readable by scylla encryption at rest
local file key provider.
)"},
};
}
namespace tools {
using namespace encryption;
using namespace std::string_literals;
namespace fs = std::filesystem;
int scylla_local_file_key_generator_main(int argc, char** argv) {
constexpr auto description_template =
R"(scylla-{} - a command-line tool to generate file-based encryption keys.
Usage: scylla {} <op> [--option1] [--option2] ... [key path|key name]
Allows creating symmetric keys for use with scylla encryption at rest
local key file provider.
Where <op> can be one of:
{}
)";
auto op_str = std::ranges::to<std::string>(operations | std::views::transform([] (const operation& op) {
return fmt::format("* {} - {}\n{}", op.name(), op.summary(), op.description());
}) | std::views::join_with('\n'));
tool_app_template::config app_cfg{
.name = app_name,
.description = seastar::format(description_template, app_name, app_name, op_str),
.operations = std::move(operations),
.global_options = &global_options,
.global_positional_options = &global_positional_options,
};
tool_app_template app(std::move(app_cfg));
return app.run_async(argc, argv, [] (const operation& op, const boost::program_options::variables_map& app_config) {
std::vector<std::string> files;
if (app_config.contains("files")) {
files = app_config["files"].as<std::vector<std::string>>();
}
if (files.size() > 1) {
throw std::invalid_argument("Too many arguments");
}
auto alg = app_config["alg"].as<std::string>();
auto mode = app_config["block-mode"].as<std::string>();
auto padd = app_config["padding"].as<std::string>();
auto len = app_config["length"].as<unsigned>();
if (!padd.ends_with("Padding")) {
padd = padd + "Padding";
}
auto java_sig = fmt::format("{}/{}/{}", alg, mode, padd);
key_info info {
.alg = java_sig, .len = len
};
symmetric_key k(info);
auto key = k.key();
auto hex = base64_encode(key);
auto line = fmt::format("{}:{}:{}", java_sig, len, hex);
auto key_name = "system_key"s;
if (!files.empty()) {
fs::path f(files.front());
if (fs::is_directory(f)) {
f = f / key_name;
}
if (!fs::exists(f)) {
auto p = f.parent_path();
if (!p.empty()) {
fs::create_directories(p);
}
}
std::ios_base::openmode mode = std::ios_base::out;
if (op.name() == "append") {
mode |= std::ios_base::ate|std::ios_base::app;
} else {
mode |= std::ios_base::trunc;
}
if (!fs::exists(f) || op.name() != "append") {
// create once so we can enforce proper
// permissions. (neither seastar or c++ io is great here)
std::ofstream os(f, mode);
}
fs::permissions(f, fs::perms::owner_read|fs::perms::owner_write);
std::ofstream os(f, mode);
os << line << std::endl;
} else {
std::cout << line << std::endl;
}
return 0;
});
}
} // namespace tools