* refactor rgw_kms.cc to support extension to multiple secret engines.
* introduced support to Vault Namesapces
* added support for Vault Agent
Signed-off-by: Andrea Baglioni <andrea.baglioni@workday.com>
Signed-off-by: Sergio de Carvalho <sergio.carvalho@workday.com>
:Description: The number of entries in the Ceph Object Gateway cache.
:Type: Integer
:Default: ``10000``
-
+
``rgw socket path``
-:Description: The socket path for the domain socket. ``FastCgiExternalServer``
- uses this socket. If you do not specify a socket path, Ceph
- Object Gateway will not run as an external server. The path you
- specify here must be the same as the path specified in the
+:Description: The socket path for the domain socket. ``FastCgiExternalServer``
+ uses this socket. If you do not specify a socket path, Ceph
+ Object Gateway will not run as an external server. The path you
+ specify here must be the same as the path specified in the
``rgw.conf`` file.
:Type: String
``rgw host``
-:Description: The host for the Ceph Object Gateway instance. Can be an IP
+:Description: The host for the Ceph Object Gateway instance. Can be an IP
address or a hostname.
:Type: String
``rgw port``
-:Description: Port the instance listens for requests. If not specified,
+:Description: Port the instance listens for requests. If not specified,
Ceph Object Gateway runs external FastCGI.
-
+
:Type: String
:Default: None
``rgw dns name``
:Description: The DNS name of the served domain. See also the ``hostnames`` setting within regions.
-:Type: String
+:Type: String
:Default: None
-
+
``rgw script uri``
``rgw remote addr param``
-:Description: The remote address parameter. For example, the HTTP field
- containing the remote address, or the ``X-Forwarded-For``
+:Description: The remote address parameter. For example, the HTTP field
+ containing the remote address, or the ``X-Forwarded-For``
address if a reverse proxy is operational.
:Type: String
``rgw op thread timeout``
-
+
:Description: The timeout in seconds for open threads.
:Type: Integer
:Default: 600
-
+
``rgw op thread suicide timeout``
-
-:Description: The time ``timeout`` in seconds before a Ceph Object Gateway
+
+:Description: The time ``timeout`` in seconds before a Ceph Object Gateway
process dies. Disabled if set to ``0``.
-:Type: Integer
+:Type: Integer
:Default: ``0``
``rgw thread pool size``
:Description: The size of the thread pool.
-:Type: Integer
+:Type: Integer
:Default: 100 threads.
``rgw init timeout``
-:Description: The number of seconds before Ceph Object Gateway gives up on
+:Description: The number of seconds before Ceph Object Gateway gives up on
initialization.
:Type: Integer
``rgw mime types file``
-:Description: The path and location of the MIME types. Used for Swift
+:Description: The path and location of the MIME types. Used for Swift
auto-detection of object types.
:Type: String
``rgw gc max objs``
-:Description: The maximum number of objects that may be handled by
+:Description: The maximum number of objects that may be handled by
garbage collection in one garbage collection processing cycle.
:Type: Integer
``rgw gc obj min wait``
-:Description: The minimum wait time before the object may be removed
+:Description: The minimum wait time before the object may be removed
and handled by garbage collection processing.
-
+
:Type: Integer
:Default: ``2 * 3600``
``rgw gc processor max time``
-:Description: The maximum time between the beginning of two consecutive garbage
+:Description: The maximum time between the beginning of two consecutive garbage
collection processing cycles.
:Type: Integer
``rgw resolve cname``
-:Description: Whether ``rgw`` should use DNS CNAME record of the request
+:Description: Whether ``rgw`` should use DNS CNAME record of the request
hostname field (if hostname is not equal to ``rgw dns name``).
:Type: Boolean
``rgw exit timeout secs``
-:Description: Number of seconds to wait for a process before exiting
+:Description: Number of seconds to wait for a process before exiting
unconditionally.
:Type: Integer
:Type: Integer
:Default: ``4 << 20``
-
+
``rgw relaxed s3 bucket names``
:Description: Enables relaxed S3 bucket names rules for US region buckets.
``rgw curl wait timeout ms``
-:Description: The timeout in milliseconds for certain ``curl`` calls.
+:Description: The timeout in milliseconds for certain ``curl`` calls.
:Type: Integer
:Default: ``1000``
``rgw copy obj progress every bytes``
:Description: The minimum bytes between copy progress output.
-:Type: Integer
+:Type: Integer
:Default: ``1024 * 1024``
:Description: Enforces the Swift Access Control List (ACL) settings.
:Type: Boolean
:Default: ``true``
-
-
+
+
``rgw swift token expiration``
:Description: The time in seconds for expiring a Swift token.
:Description: The URL for the Ceph Object Gateway Swift API.
:Type: String
:Default: None
-
+
``rgw swift url prefix``
``rgw swift auth url``
-:Description: Default URL for verifying v1 auth tokens (if not using internal
+:Description: Default URL for verifying v1 auth tokens (if not using internal
Swift auth).
:Type: String
Those containers cannot be versioned by the S3 object versioning
mechanism.
- A slightly different attribute, ``X-History-Location``, which is also understood by
+ A slightly different attribute, ``X-History-Location``, which is also understood by
`OpenStack Swift <https://docs.openstack.org/swift/latest/api/object_versioning.html>`_
for handling ``DELETE`` operations, is currently not supported.
:Type: Boolean
``rgw log nonexistent bucket``
-:Description: Enables Ceph Object Gateway to log a request for a non-existent
+:Description: Enables Ceph Object Gateway to log a request for a non-existent
bucket.
:Type: Boolean
``rgw log object name``
-:Description: The logging format for an object name. See manpage
+:Description: The logging format for an object name. See manpage
:manpage:`date` for details about format specifiers.
:Type: Date
``rgw log object name utc``
-:Description: Whether a logged object name includes a UTC time.
+:Description: Whether a logged object name includes a UTC time.
If ``false``, it uses the local time.
:Type: Boolean
``rgw usage max user shards``
-:Description: The maximum number of shards used for a single user's
+:Description: The maximum number of shards used for a single user's
usage logging.
:Type: Integer
``rgw ops log rados``
-:Description: Whether the operations log should be written to the
+:Description: Whether the operations log should be written to the
Ceph Storage Cluster backend.
:Type: Boolean
``rgw usage log flush threshold``
-:Description: The number of dirty merged entries in the usage log before
+:Description: The number of dirty merged entries in the usage log before
flushing synchronously.
:Type: Integer
``rgw intent log object name``
-:Description: The logging format for the intent log object name. See manpage
+:Description: The logging format for the intent log object name. See manpage
:manpage:`date` for details about format specifiers.
:Type: Date
``rgw intent log object name utc``
-:Description: Whether the intent log object name includes a UTC time.
+:Description: Whether the intent log object name includes a UTC time.
If ``false``, it uses the local time.
:Type: Boolean
:Type: String
:Default: None
+``rgw crypt vault secret engine``
+
+:Description: Vault Secret Engine to be used to retrieve encryption keys: choose
+ between kv-v2, transit.
+:Type: String
+:Default: None
+
+``rgw crypt vault namespace``
+
+:Description: If set, Vault Namespace provides tenant isolation for teams and individuals
+ on the same Vault Enterprise instance, e.g. ``acme/tenant1``
+:Type: String
+:Default: None
+
+
QoS settings
------------
===============
Vault provides several Secret Engines, which can store, generate, and encrypt
-data. Currently, the Object Gateway supports the `KV Secrets engine`_ version 2
-only. To enable the KV engine version 2 in Vault, use the Vault command line
+data. Currently, the Object Gateway supports `KV Secrets engine`_ version 2
+an `KV Transit engine`_.
+
+Basic Vault Configuration
+-------------------------
+To enable the KV engine version 2 in Vault, use the Vault command line
tool::
vault secrets enable kv-v2
-Vault also provides several authentication mechanisms. Currently, the Object
-Gateway supports the `token authentication method`_ only. When authenticating
-using the token method, a token must be obtained for the Gateway and saved in a
-file as plain-text.
+Analogously for the Transit Engine::
+ vault secrets enable transit
+
+Vault also provides several authentication mechanisms.
+To simplify user's interaction with Vault, the Object Gateway supports
+two modes: `token authentication method`_ and `agent authentication method`_.
+
+When authenticating using the token method, a token must be obtained
+for the Gateway and saved in a file as plain-text.
For security reasons, the Object Gateway should be given a Vault token with a
restricted policy that allows it to fetch secrets only. Such a policy can be
Gateway configuration file (see section below). For security reasons, ensure
the file is readable by the Object Gateway only.
+
Configure the Ceph Object Gateway
=================================
OPTION(ms_max_accept_failures, OPT_INT)
OPTION(ms_dpdk_port_id, OPT_INT)
-SAFE_OPTION(ms_dpdk_coremask, OPT_STR) // it is modified in unittest so that use SAFE_OPTION to declare
+SAFE_OPTION(ms_dpdk_coremask, OPT_STR) // it is modified in unittest so that use SAFE_OPTION to declare
OPTION(ms_dpdk_memory_channel, OPT_STR)
OPTION(ms_dpdk_hugepages, OPT_STR)
OPTION(ms_dpdk_pmd, OPT_STR)
OPTION(osd_journal, OPT_STR)
OPTION(osd_journal_size, OPT_INT) // in mb
OPTION(osd_journal_flush_on_shutdown, OPT_BOOL) // Flush journal to data store on shutdown
-// flags for specific control purpose during osd mount() process.
+// flags for specific control purpose during osd mount() process.
// e.g., can be 1 to skip over replaying journal
// or 2 to skip over mounting omap or 3 to skip over both.
// This might be helpful in case the journal is totally corrupted
OPTION(osd_scrub_priority, OPT_U32)
// set default cost equal to 50MB io
-OPTION(osd_scrub_cost, OPT_U32)
+OPTION(osd_scrub_cost, OPT_U32)
// set requested scrub priority higher than scrub priority to make the
// requested scrubs jump the queue of scheduled scrubs
OPTION(osd_requested_scrub_priority, OPT_U32)
/*
* Specifies minimum expected amount of saved allocation units
* per single blob to enable compressed blobs garbage collection
- *
+ *
*/
-OPTION(bluestore_gc_enable_blob_threshold, OPT_INT)
+OPTION(bluestore_gc_enable_blob_threshold, OPT_INT)
/*
* Specifies minimum expected amount of saved allocation units
* per all blobsb to enable compressed blobs garbage collection
- *
+ *
*/
-OPTION(bluestore_gc_enable_total_threshold, OPT_INT)
+OPTION(bluestore_gc_enable_total_threshold, OPT_INT)
OPTION(bluestore_max_blob_size, OPT_U32)
OPTION(bluestore_max_blob_size_hdd, OPT_U32)
OPTION(rgw_crypt_vault_token_file, OPT_STR) // Path to the token file for Vault authentication
OPTION(rgw_crypt_vault_addr, OPT_STR) // Vault server base address
OPTION(rgw_crypt_vault_prefix, OPT_STR) // Optional URL prefix to Vault secret path
+OPTION(rgw_crypt_vault_secret_engine, OPT_STR) // kv, transit or other supported secret engines
+OPTION(rgw_crypt_vault_namespace, OPT_STR) // Vault Namespace (only availabe in Vault Enterprise Version)
OPTION(rgw_crypt_s3_kms_encryption_keys, OPT_STR) // extra keys that may be used for aws:kms
// defined as map "key1=YmluCmJvb3N0CmJvb3N0LQ== key2=b3V0CnNyYwpUZXN0aW5nCg=="
.set_description("Number of seconds the timeout on the reshard locks (bucket reshard lock and reshard log lock) are set to. As a reshard proceeds these locks can be renewed/extended. If too short, reshards cannot complete and will fail, causing a future reshard attempt. If too long a hung or crashed reshard attempt will keep the bucket locked for an extended period, not allowing RGW to detect the failed reshard attempt and recover.")
.add_tag("performance")
.add_service("rgw"),
-
+
Option("rgw_reshard_batch_size", Option::TYPE_UINT, Option::LEVEL_ADVANCED)
.set_default(64)
.set_min(8)
Option("rgw_crypt_vault_auth", Option::TYPE_STR, Option::LEVEL_ADVANCED)
.set_default("token")
- .set_enum_allowed({"token"})
+ .set_enum_allowed({"token", "agent"})
.set_description(
- "Type of authentication method to be used with Vault. "
- "The only currently supported method is 'token'.")
+ "Type of authentication method to be used with Vault. ")
.add_see_also({
"rgw_crypt_s3_kms_backend",
"rgw_crypt_vault_addr",
"rgw_crypt_vault_addr",
"rgw_crypt_vault_auth"}),
+
+ Option("rgw_crypt_vault_secret_engine", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+ .set_enum_allowed({"kv", "transit"})
+ .set_default("transit")
+ .set_description(
+ "Vault Secret Engine to be used to retrieve encryption keys.")
+ .add_see_also({
+ "rgw_crypt_s3_kms_backend",
+ "rgw_crypt_vault_auth",
+ "rgw_crypt_vault_addr"}),
+
+ Option("rgw_crypt_vault_namespace", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+ .set_default("")
+ .set_description("Vault Namespace to be used to select your tenant")
+ .add_see_also({
+ "rgw_crypt_s3_kms_backend",
+ "rgw_crypt_vault_auth",
+ "rgw_crypt_vault_addr"}),
+
Option("rgw_crypt_suppress_logs", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_default(true)
.set_description("Suppress logs that might print client key"),
using namespace rgw;
-static map<string,string> get_str_map(const string &str) {
- map<string,string> m;
- get_str_map(str, &m, ";, \t");
- return m;
-}
/**
* Construct a full URL string by concatenating a "base" URL with another path,
}
}
+
+class VaultSecretEngine: public SecretEngine {
+
+protected:
+ CephContext *cct;
+
+ int load_token_from_file(std::string *vault_token)
+ {
+
+ int res = 0;
+ std::string token_file = cct->_conf->rgw_crypt_vault_token_file;
+ if (token_file.empty()) {
+ ldout(cct, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl;
+ return -EINVAL;
+ }
+ ldout(cct, 20) << "Vault token file: " << token_file << dendl;
+
+ struct stat token_st;
+ if (stat(token_file.c_str(), &token_st) != 0) {
+ ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' not found " << dendl;
+ return -ENOENT;
+ }
+
+ if (token_st.st_mode & (S_IRWXG | S_IRWXO)) {
+ ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' permissions are "
+ << "too open, it must not be accessible by other users" << dendl;
+ return -EACCES;
+ }
+
+ char buf[2048];
+ res = safe_read_file("", token_file.c_str(), buf, sizeof(buf));
+ if (res < 0) {
+ if (-EACCES == res) {
+ ldout(cct, 0) << "ERROR: Permission denied reading Vault token file" << dendl;
+ } else {
+ ldout(cct, 0) << "ERROR: Failed to read Vault token file with error " << res << dendl;
+ }
+ return res;
+ }
+ // drop trailing newlines
+ while (res && isspace(buf[res-1])) {
+ --res;
+ }
+ vault_token->assign(std::string{buf, static_cast<size_t>(res)});
+ memset(buf, 0, sizeof(buf));
+ ::ceph::crypto::zeroize_for_security(buf, sizeof(buf));
+ return res;
+ }
+
+ int send_request(boost::string_view key_id, JSONParser* parser) override
+ {
+ bufferlist secret_bl;
+ int res;
+ string vault_token = "";
+ if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == cct->_conf->rgw_crypt_vault_auth){
+ ldout(cct, 0) << "Loading Vault Token from filesystem" << dendl;
+ res = load_token_from_file(&vault_token);
+ if (res < 0){
+ return res;
+ }
+ }
+
+ std::string secret_url = cct->_conf->rgw_crypt_vault_addr;
+ if (secret_url.empty()) {
+ ldout(cct, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl;
+ return -EINVAL;
+ }
+
+ concat_url(secret_url, cct->_conf->rgw_crypt_vault_prefix);
+ concat_url(secret_url, std::string(key_id));
+
+ RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
+
+ if (!vault_token.empty()){
+ secret_req.append_header("X-Vault-Token", vault_token);
+ vault_token.replace(0, vault_token.length(), vault_token.length(), '\000');
+ }
+
+ string vault_namespace = cct->_conf->rgw_crypt_vault_namespace;
+ if (!vault_namespace.empty()){
+ ldout(cct, 20) << "Vault Namespace: " << vault_namespace << dendl;
+ secret_req.append_header("X-Vault-Namespace", vault_namespace);
+ }
+
+ res = secret_req.process(null_yield);
+ if (res < 0) {
+ ldout(cct, 0) << "ERROR: Request to Vault failed with error " << res << dendl;
+ return res;
+ }
+
+ if (secret_req.get_http_status() ==
+ RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
+ ldout(cct, 0) << "ERROR: Vault request failed authorization" << dendl;
+ return -EACCES;
+ }
+
+ ldout(cct, 20) << "Request to Vault returned " << res << " and HTTP status "
+ << secret_req.get_http_status() << dendl;
+
+ ldout(cct, 20) << "Parse response into JSON Object" << dendl;
+
+ if (!parser->parse(secret_bl.c_str(), secret_bl.length())) {
+ ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault" << dendl;
+ return -EINVAL;
+ }
+ secret_bl.zero();
+
+ return res;
+ }
+
+ int decode_secret(JSONObj* json_obj, std::string& actual_key){
+ std::string secret;
+ try {
+ secret = from_base64(json_obj->get_data());
+ } catch (std::exception&) {
+ ldout(cct, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl;
+ return -EINVAL;
+ }
+
+ actual_key.assign(secret.c_str(), secret.length());
+ secret.replace(0, secret.length(), secret.length(), '\000');
+ return 0;
+ }
+
+public:
+
+ VaultSecretEngine(CephContext *cct) {
+ this->cct = cct;
+ }
+
+ virtual ~VaultSecretEngine(){}
+};
+
+
+class TransitSecretEngine: public VaultSecretEngine {
+
+private:
+ int get_key_version(boost::string_view key_id, string& version)
+ {
+ vector<boost::string_view> tokens;
+ size_t pos = 0;
+
+ pos = key_id.rfind("/", key_id.length());
+ if (pos != std::string::npos){
+ boost::string_view token = key_id.substr(pos, key_id.length());
+ if (tokens.size() >= 1 && token.find_first_not_of("0123456789") == std::string::npos){
+ version.assign(std::string(token));
+ return 0;
+ }
+ }
+ return -1;
+ }
+
+public:
+ TransitSecretEngine(CephContext *cct): VaultSecretEngine(cct){ }
+
+ int get_key(boost::string_view key_id, std::string& actual_key)
+ {
+ JSONParser* parser = new JSONParser();
+ string version;
+
+ if (get_key_version(key_id, version) < 0){
+ ldout(cct, 20) << "Missing or invalid key version" << dendl;
+ return -EINVAL;
+ }
+
+ int res = send_request(key_id, parser);
+ if (res < 0) {
+ return res;
+ }
+
+ JSONObj* json_obj = &(*parser);
+ std::array<std::string, 3> elements = {"data", "keys", version};
+ for(const auto& elem : elements) {
+ json_obj = json_obj->find_obj(elem);
+ if (!json_obj) {
+ ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl;
+ return -EINVAL;
+ }
+ }
+
+ return decode_secret(json_obj, actual_key);
+ }
+
+};
+
+class KvSecretEngine: public VaultSecretEngine {
+
+public:
+
+ KvSecretEngine(CephContext *cct): VaultSecretEngine(cct){ }
+
+ virtual ~KvSecretEngine(){}
+
+ int get_key(boost::string_view key_id, std::string& actual_key){
+ JSONParser parser;
+ int res = send_request(key_id, &parser);
+ if (res < 0) {
+ return res;
+ }
+
+ JSONObj *json_obj = &parser;
+ std::array<std::string, 3> elements = {"data", "data", "key"};
+ for(const auto& elem : elements) {
+ json_obj = json_obj->find_obj(elem);
+ if (!json_obj) {
+ ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using KV Engine" << dendl;
+ return -EINVAL;
+ }
+ }
+ return decode_secret(json_obj, actual_key);
+ }
+
+};
+
+
+class VaultKMS : KMS {
+
+protected:
+ CephContext *cct;
+ VaultSecretEngine *engine;
+
+public:
+ int get_key(boost::string_view key_id, std::string& actual_key) {
+ if (!engine){
+ ldout(cct, 5) << "ERROR: No supported Vault Secret Engine found" << dendl;
+ return -EINVAL;
+ }
+ return engine->get_key(key_id, actual_key);
+ }
+
+ VaultKMS(CephContext *cct) {
+ std::string secret_engine = cct->_conf->rgw_crypt_vault_secret_engine;
+
+ ldout(cct, 10) << "Selected " << secret_engine << " secret engine" << dendl;
+
+ if (RGW_SSE_KMS_VAULT_SE_KV == secret_engine)
+ engine = new KvSecretEngine(cct);
+ else if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine)
+ engine = new TransitSecretEngine(cct);
+ else
+ ldout(cct, 0) << "Missing or invalid secret engine" << dendl;
+
+ this->cct = cct;
+ }
+
+ ~VaultKMS(){
+ delete engine;
+ }
+
+};
+
+
+static map<string,string> get_str_map(const string &str) {
+ map<string,string> m;
+ get_str_map(str, &m, ";, \t");
+ return m;
+}
+
+
static int get_actual_key_from_conf(CephContext *cct,
boost::string_view key_id,
boost::string_view key_selector,
return res;
}
-static int request_key_from_vault_with_token(CephContext *cct,
- boost::string_view key_id,
- bufferlist *secret_bl)
-{
- std::string token_file, secret_url, vault_token;
- int res = 0;
-
- token_file = cct->_conf->rgw_crypt_vault_token_file;
- if (token_file.empty()) {
- ldout(cct, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl;
- return -EINVAL;
- }
- ldout(cct, 20) << "Vault token file: " << token_file << dendl;
-
- struct stat token_st;
- if (stat(token_file.c_str(), &token_st) != 0) {
- ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' not found " << dendl;
- return -ENOENT;
- }
-
- if (token_st.st_mode & (S_IRWXG | S_IRWXO)) {
- ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' permissions are "
- << "too open, it must not be accessible by other users" << dendl;
- return -EACCES;
- }
-
- char buf[2048];
- res = safe_read_file("", token_file.c_str(), buf, sizeof(buf));
- if (res < 0) {
- if (-EACCES == res) {
- ldout(cct, 0) << "ERROR: Permission denied reading Vault token file" << dendl;
- } else {
- ldout(cct, 0) << "ERROR: Failed to read Vault token file with error " << res << dendl;
- }
- return res;
- }
- // drop trailing newlines
- while (res && isspace(buf[res-1])) {
- --res;
- }
- vault_token = std::string{buf, static_cast<size_t>(res)};
- ::ceph::crypto::zeroize_for_security(buf, sizeof(buf));
-
- secret_url = cct->_conf->rgw_crypt_vault_addr;
- if (secret_url.empty()) {
- ldout(cct, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl;
- return -EINVAL;
- }
- concat_url(secret_url, cct->_conf->rgw_crypt_vault_prefix);
- concat_url(secret_url, std::string(key_id));
-
- RGWHTTPTransceiver secret_req(cct, "GET", secret_url, secret_bl);
- secret_req.append_header("X-Vault-Token", vault_token);
- vault_token.replace(0, vault_token.length(), vault_token.length(), '\000');
- res = secret_req.process(null_yield);
- if (res < 0) {
- ldout(cct, 0) << "ERROR: Request to Vault failed with error " << res << dendl;
- return res;
- }
-
- if (secret_req.get_http_status() ==
- RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
- ldout(cct, 0) << "ERROR: Vault request failed authorization" << dendl;
- return -EACCES;
- }
-
- ldout(cct, 20) << "Request to Vault returned " << res << " and HTTP status "
- << secret_req.get_http_status() << dendl;
- return res;
-}
static int get_actual_key_from_vault(CephContext *cct,
boost::string_view key_id,
std::string& actual_key)
{
- int res = 0;
- std::string auth;
- bufferlist secret_bl;
-
- auth = cct->_conf->rgw_crypt_vault_auth;
- ldout(cct, 20) << "Vault authentication method: " << auth << dendl;
-
- // Currently only token-based authentication is supported
- if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == auth) {
- res = request_key_from_vault_with_token(cct, key_id, &secret_bl);
- } else {
- ldout(cct, 0) << "ERROR: Invalid rgw_crypt_vault_auth: " << auth << dendl;
- return -EINVAL;
- }
-
- if (res < 0) {
- return res;
- }
-
- JSONParser parser;
- if (!parser.parse(secret_bl.c_str(), secret_bl.length())) {
- ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault" << dendl;
- return -EINVAL;
- }
- secret_bl.zero();
-
- JSONObj *json_obj = &parser;
- std::array<std::string, 3> elements = {"data", "data", "key"};
- for(const auto& elem : elements) {
- json_obj = json_obj->find_obj(elem);
- if (!json_obj) {
- ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault" << dendl;
- return -EINVAL;
- }
- }
-
- std::string secret;
- try {
- secret = from_base64(json_obj->get_data());
- } catch (std::exception&) {
- ldout(cct, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl;
- return -EINVAL;
- }
-
- actual_key.assign(secret.c_str(), secret.length());
- secret.replace(0, secret.length(), secret.length(), '\000');
-
+ ldout(cct, 20) << "Vault authentication method: " << cct->_conf->rgw_crypt_vault_auth << dendl;
+ ldout(cct, 20) << "Vault Secrets Engine: " << cct->_conf->rgw_crypt_vault_secret_engine << dendl;
+ VaultKMS *kms = new VaultKMS(cct);
+ int res = kms->get_key(key_id, actual_key);
+ delete kms;
return res;
}
static const std::string RGW_SSE_KMS_VAULT_AUTH_TOKEN = "token";
static const std::string RGW_SSE_KMS_VAULT_AUTH_AGENT = "agent";
+static const std::string RGW_SSE_KMS_VAULT_SE_TRANSIT = "transit";
+static const std::string RGW_SSE_KMS_VAULT_SE_KV = "kv";
+
/**
* Retrieves the actual server-side encryption key from a KMS system given a
* key ID. Currently supported KMS systems are OpenStack Barbican and HashiCorp
boost::string_view key_selector,
std::string& actual_key);
+/**
+ * SecretEngine Interface
+ * Defining interface here such that we can use both a real implementation
+ * of this interface, and a mock implementation in tests.
+**/
+class SecretEngine {
+
+public:
+ virtual int get_key(boost::string_view key_id, std::string& actual_key) = 0;
+ virtual ~SecretEngine(){};
+protected:
+ virtual int send_request(boost::string_view key_id, JSONParser* parser) = 0;
+ virtual int decode_secret(JSONObj* json_obj, std::string& actual_key) = 0;
+};
+
+class KMS {
+
+public:
+ virtual int get_key(boost::string_view key_id, std::string& actual_key) = 0;
+ virtual ~KMS(){};
+};
+
+
#endif
// vim: ts=8 sw=2 smarttab
#include <gtest/gtest.h>
+#include <gmock/gmock.h>
#include "common/ceph_context.h"
#include "rgw/rgw_common.h"
#include "rgw/rgw_kms.cc"
-TEST(TestSSEKMS, vault_token_file_unset)
+using ::testing::_;
+using ::testing::Action;
+using ::testing::ActionInterface;
+using ::testing::MakeAction;
+
+
+class MockTransitSecretEngine : public TransitSecretEngine {
+public:
+ MockTransitSecretEngine(CephContext *cct) : TransitSecretEngine(cct){}
+
+ MOCK_METHOD(int, send_request, (boost::string_view key_id, JSONParser* parser), (override));
+
+};
+
+
+class TestSSEKMS : public ::testing::Test {
+
+protected:
+ CephContext *cct;
+ MockTransitSecretEngine* transit_engine;
+
+ void SetUp() override {
+ cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
+ transit_engine = new MockTransitSecretEngine(cct);
+ }
+
+ void TearDown() {
+ delete transit_engine;
+ }
+
+};
+
+
+TEST_F(TestSSEKMS, vault_token_file_unset)
{
- CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
-
- std::string key_id, actual_key;
- bufferlist secret_bl;
- ASSERT_EQ(
- request_key_from_vault_with_token(cct, key_id, &secret_bl),
- -EINVAL
- );
+
+ boost::string_view key_id("my_key");
+ std::string actual_key;
+ TransitSecretEngine te(cct);
+ int res = te.get_key(key_id, actual_key);
+ ASSERT_EQ(res, -EINVAL);
}
-TEST(TestSSEKMS, non_existent_vault_token_file)
+
+TEST_F(TestSSEKMS, non_existent_vault_token_file)
{
- CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
cct->_conf.set_val("rgw_crypt_vault_token_file", "/nonexistent/file");
- std::string key_id, key_selector, actual_key;
- bufferlist secret_bl;
- ASSERT_EQ(
- request_key_from_vault_with_token(cct, key_id, &secret_bl),
- -ENOENT
- );
+ boost::string_view key_id("my_key/1");
+ std::string actual_key;
+ TransitSecretEngine te(cct);
+ int res = te.get_key(key_id, actual_key);
+
+ ASSERT_EQ(res, -ENOENT);
+}
+
+
+typedef int SendRequestMethod(boost::string_view, JSONParser*);
+
+class SetPointedValueAction : public ActionInterface<SendRequestMethod> {
+ public:
+ string json;
+
+ SetPointedValueAction(std::string json){
+ this->json = json;
+ }
+
+ int Perform(const ::std::tuple<boost::string_view, JSONParser*>& args) override {
+ JSONParser* p = ::std::get<1>(args);
+ JSONParser* parser = new JSONParser();
+
+ parser->parse(json.c_str(), json.length());
+ *p = *parser;
+ return 0;
+ }
+};
+
+Action<SendRequestMethod> SetPointedValue(std::string json) {
+ return MakeAction(new SetPointedValueAction(json));
}
-TEST(TestSSEKMS, concat_url)
+
+TEST_F(TestSSEKMS, test_transit_key_version_extraction){
+
+ boost::string_view key_id("my_key");
+ std::string version;
+ std::string actual_key;
+
+ string json = R"({"data": {"keys": {"6": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
+ EXPECT_CALL(*transit_engine, send_request(_, _)).WillOnce(SetPointedValue(json));
+
+ std::string tests[4] {"/", "my_key/", "my_key", ""};
+
+ int res;
+ for (const auto &test: tests) {
+ res = transit_engine->get_key(boost::string_view(test), actual_key);
+ ASSERT_EQ(res, -EINVAL);
+ }
+
+ res = transit_engine->get_key(boost::string_view("1/2/3/4/5/6"), actual_key);
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
+}
+
+
+TEST_F(TestSSEKMS, test_transit_backend){
+
+ boost::string_view my_key("my_key/1");
+ std::string actual_key;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ string json = R"({"data": {"keys": {"1": "8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="}}})";
+ EXPECT_CALL(*transit_engine, send_request(_, _)).WillOnce(SetPointedValue(json));
+
+ int res = transit_engine->get_key(my_key, actual_key);
+
+ ASSERT_EQ(res, 0);
+ ASSERT_EQ(actual_key, from_base64("8qgPWvdtf6zrriS5+nkOzDJ14IGVR6Bgkub5dJn6qeg="));
+}
+
+
+TEST_F(TestSSEKMS, concat_url)
{
// Each test has 3 strings:
// * the base URL
concat_url(url, path);
ASSERT_EQ(url, expected);
}
-}
\ No newline at end of file
+}
+
+
+TEST_F(TestSSEKMS, test_transit_backend_empty_response)
+{
+
+ boost::string_view my_key("/key/nonexistent/1");
+ std::string actual_key;
+
+ // Mocks the expected return Value from Vault Server using custom Argument Action
+ string json = R"({"errors": ["version does not exist or cannot be found"]})";
+ EXPECT_CALL(*transit_engine, send_request(_, _)).WillOnce(SetPointedValue(json));
+
+ int res = transit_engine->get_key(my_key, actual_key);
+
+ ASSERT_EQ(res, -EINVAL);
+ ASSERT_EQ(actual_key, from_base64(""));
+}
+
fi
fi
-# use CEPH_BUILD_ROOT to vstart from a 'make install'
+# use CEPH_BUILD_ROOT to vstart from a 'make install'
if [ -n "$CEPH_BUILD_ROOT" ]; then
[ -z "$CEPH_BIN" ] && CEPH_BIN=$CEPH_BUILD_ROOT/bin
[ -z "$CEPH_LIB" ] && CEPH_LIB=$CEPH_BUILD_ROOT/lib
export LD_LIBRARY_PATH=$CEPH_LIB:$LD_LIBRARY_PATH
export DYLD_LIBRARY_PATH=$CEPH_LIB:$DYLD_LIBRARY_PATH
# Suppress logging for regular use that indicated that we are using a
-# development version. vstart.sh is only used during testing and
+# development version. vstart.sh is only used during testing and
# development
export CEPH_DEV=1
; needed for passing lc time based s3-tests (can be verbose)
; rgw lc debug interval = 10
; The following settings are for SSE-KMS with Vault
- ; rgw crypt s3 kms backend = vault
- ; rgw crypt vault auth = token
- ; rgw crypt vault addr = http://127.0.0.1:8200
- ; rgw crypt vault prefix = /v1/secret/data
- ; rgw crypt vault token file = $CEPH_CONF_PATH/vault.token
+ ;rgw crypt s3 kms backend = vault
+ ;rgw crypt vault auth = token
+ ;rgw crypt vault addr = http://127.0.0.1:8200
+ ;rgw crypt vault prefix = /v1/transit/export/encryption-key/
+ ;rgw crypt vault token file = $CEPH_CONF_PATH/vault.token
+ ;rgw crypt vault secret engine = transit
$extra_conf
EOF