From b3a687a3966d28d15d9add0f46ae183408d00bca Mon Sep 17 00:00:00 2001 From: "S. Santos" Date: Tue, 3 Dec 2024 22:30:13 -0300 Subject: [PATCH] Add support to Hashicorp Vault Secrets --- lockbox/CMakeLists.txt | 1 + lockbox/README.md | 2 +- lockbox/Settings.toml | 9 ++- lockbox/include/hashicorp_key_manager.h | 11 +++ lockbox/include/server.h | 4 +- lockbox/src/hashicorp_key_manager.cpp | 98 +++++++++++++++++++++++++ lockbox/src/main.cpp | 14 +++- lockbox/src/server.cpp | 17 ++++- 8 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 lockbox/include/hashicorp_key_manager.h create mode 100644 lockbox/src/hashicorp_key_manager.cpp diff --git a/lockbox/CMakeLists.txt b/lockbox/CMakeLists.txt index b5e7cf69..1dbbc88e 100644 --- a/lockbox/CMakeLists.txt +++ b/lockbox/CMakeLists.txt @@ -30,6 +30,7 @@ add_library(LockboxLibrary src/server.cpp src/utils.cpp src/key_manager.cpp + src/hashicorp_key_manager.cpp src/enclave.cpp src/monocypher.c src/db_manager.cpp) diff --git a/lockbox/README.md b/lockbox/README.md index 20c4dfb8..ccc1da7d 100644 --- a/lockbox/README.md +++ b/lockbox/README.md @@ -12,4 +12,4 @@ $ cmake --preset=vcpkg .. $ cmake --build . ``` -4. Then, to run the server: `./MercuryLockbox` \ No newline at end of file +4. Then, to run the server: `./MercuryLockbox --key_provider=google_kms` or `./MercuryLockbox --key_provider=hashicorp`. \ No newline at end of file diff --git a/lockbox/Settings.toml b/lockbox/Settings.toml index befdf322..086dbba5 100644 --- a/lockbox/Settings.toml +++ b/lockbox/Settings.toml @@ -11,4 +11,11 @@ location_id = "global" key_name = "encrypted-key" [kms] ring = "enclave" -crypto_key = "sealing" \ No newline at end of file +crypto_key = "sealing" +[hashicorp] +hcp_client_id = "" +hcp_client_secret = "" +organization_id = "" +project_id = "" +app_name = "" +secret_name = "" diff --git a/lockbox/include/hashicorp_key_manager.h b/lockbox/include/hashicorp_key_manager.h new file mode 100644 index 00000000..914732ff --- /dev/null +++ b/lockbox/include/hashicorp_key_manager.h @@ -0,0 +1,11 @@ +#ifndef HASHICORP_KEY_MANAGER_H +#define HASHICORP_KEY_MANAGER_H + +#include +#include + +namespace hashicorp_key_manager { + std::vector get_seed(); +} // namespace key_manager + +#endif // HASHICORP_KEY_MANAGER_H \ No newline at end of file diff --git a/lockbox/include/server.h b/lockbox/include/server.h index 907a8c0e..59e9421f 100644 --- a/lockbox/include/server.h +++ b/lockbox/include/server.h @@ -1,8 +1,10 @@ #ifndef SERVER_H #define SERVER_H +#include + namespace lockbox { - void start_server(); + void start_server(const std::string& key_provider); } // namespace lockbox #endif // SERVER_H \ No newline at end of file diff --git a/lockbox/src/hashicorp_key_manager.cpp b/lockbox/src/hashicorp_key_manager.cpp new file mode 100644 index 00000000..ed58fa26 --- /dev/null +++ b/lockbox/src/hashicorp_key_manager.cpp @@ -0,0 +1,98 @@ +#include "hashicorp_key_manager.h" + +#include +#include +#include +#include "utils.h" +#include + +namespace hashicorp_key_manager { + + std::string get_access_token() { + + auto config = toml::parse_file("Settings.toml"); + + const std::string client_id = config["hashicorp"]["hcp_client_id"].as_string()->get(); + const std::string client_secret = config["hashicorp"]["hcp_client_secret"].as_string()->get(); + + // Make the HTTP POST request using cpr + cpr::Response response = cpr::Post( + cpr::Url{"https://auth.idp.hashicorp.com/oauth2/token"}, + cpr::Header{{"Content-Type", "application/x-www-form-urlencoded"}}, + cpr::Payload{ + {"client_id", client_id}, + {"client_secret", client_secret}, + {"grant_type", "client_credentials"}, + {"audience", "https://api.hashicorp.cloud"}} + ); + + // Check for HTTP request success + if (response.status_code != 200) { + throw std::runtime_error("HTTP request failed with status code: " + + std::to_string(response.status_code) + "\nResponse: " + response.text); + } + + // Parse the JSON response using CrowCPP + crow::json::rvalue json = crow::json::load(response.text); + if (!json) { + throw std::runtime_error("Failed to parse JSON response: " + response.text); + } + + // Extract the access token + if (!json.has("access_token")) { + throw std::runtime_error("Response JSON does not contain 'access_token'. Response: " + response.text); + } + + auto access_token = json["access_token"].s(); + + return access_token; + } + + std::string get_secret(const std::string& hcp_api_token) { + + auto config = toml::parse_file("Settings.toml"); + + const std::string organization_id = config["hashicorp"]["organization_id"].as_string()->get(); + const std::string project_id = config["hashicorp"]["project_id"].as_string()->get(); + const std::string app_name = config["hashicorp"]["app_name"].as_string()->get(); + const std::string secret_name = config["hashicorp"]["secret_name"].as_string()->get(); + + // Construct the URL + std::string url = "https://api.cloud.hashicorp.com/secrets/2023-11-28/organizations/" + organization_id + + "/projects/" + project_id + + "/apps/" + app_name + + "/secrets/" + secret_name + ":open"; + + // Make the HTTP GET request using CPR + cpr::Response response = cpr::Get( + cpr::Url{url}, + cpr::Header{{"Authorization", "Bearer " + hcp_api_token}} + ); + + // Check for HTTP request success + if (response.status_code != 200) { + throw std::runtime_error("HTTP request failed with status code: " + + std::to_string(response.status_code) + "\nResponse: " + response.text); + } + + // Parse the JSON response using CrowCPP + crow::json::rvalue json = crow::json::load(response.text); + if (!json) { + throw std::runtime_error("Failed to parse JSON response: " + response.text); + } + + return json["secret"]["static_version"]["value"].s(); + } + + std::vector get_seed() { + + auto access_token = get_access_token(); + + auto secret = get_secret(access_token); + + std::vector serialized_secret = utils::ParseHex(secret); + + return serialized_secret; + } + +} \ No newline at end of file diff --git a/lockbox/src/main.cpp b/lockbox/src/main.cpp index 8ad552a9..fd26e4eb 100644 --- a/lockbox/src/main.cpp +++ b/lockbox/src/main.cpp @@ -7,9 +7,19 @@ int main(int argc, char *argv[]) { CLI::App cli_app{"Lockbox Server"}; cli_app.set_version_flag("--version", std::string("0.0.1")); - CLI11_PARSE(cli_app, argc, argv); + // Add the mandatory key_provider option + std::string key_provider; + cli_app.add_option("--key_provider", key_provider, "Key provider (google_kms or hashicorp)") + ->required() // Mark it as mandatory + ->check(CLI::IsMember({"google_kms", "hashicorp"})); // Validate allowed values - lockbox::start_server(); + try { + CLI11_PARSE(cli_app, argc, argv); + } catch (const CLI::ParseError &e) { + return cli_app.exit(e); // Exit gracefully on parse errors + } + + lockbox::start_server(key_provider); return 0; } \ No newline at end of file diff --git a/lockbox/src/server.cpp b/lockbox/src/server.cpp index c956ea98..2336f38a 100644 --- a/lockbox/src/server.cpp +++ b/lockbox/src/server.cpp @@ -4,6 +4,7 @@ #include "utils.h" #include "enclave.h" #include "key_manager.h" +#include "hashicorp_key_manager.h" #include "db_manager.h" namespace lockbox { @@ -182,11 +183,21 @@ namespace lockbox { return crow::response{result}; } - void start_server() { + void start_server(const std::string& key_provider) { - std::vector seed = key_manager::get_seed(); + std::vector seed; - std::string seed_hex = utils::key_to_string(seed.data(), seed.size()); + if (key_provider == "google_kms") { + seed = key_manager::get_seed(); + } else if (key_provider == "hashicorp") { + seed = hashicorp_key_manager::get_seed(); + } else { + throw std::runtime_error("Invalid key provider: " + key_provider); + } + + /* std::string seed_hex = utils::key_to_string(seed.data(), seed.size()); + + std::cout << "Seed: " << seed_hex << std::endl; */ // Initialize Crow HTTP server crow::SimpleApp app;