From: Marcus Watts Date: Tue, 8 Dec 2020 23:09:04 +0000 (-0500) Subject: rgw/kms/vault - "compat" option X-Git-Tag: v16.2.0~45^2~7 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=918804cfc3af63000a95d9f36943e77aa73bcc84;p=ceph.git rgw/kms/vault - "compat" option For transit engine: "compat" option: 0=new only, 1=old & new, 2=old only. This is just the option parsing itself: not the actual logic for make_key | reconstitute_key. Fixes: http://tracker.ceph.com/issues/48746 Signed-off-by: Marcus Watts (cherry picked from commit c190b741f88dc5e2e864c6a43689a444cc929123) --- diff --git a/src/rgw/rgw_kms.cc b/src/rgw/rgw_kms.cc index 260ff091b92..b5375134b2e 100644 --- a/src/rgw/rgw_kms.cc +++ b/src/rgw/rgw_kms.cc @@ -13,6 +13,11 @@ #include "rgw/rgw_b64.h" #include "rgw/rgw_kms.h" #include "rgw/rgw_kmip_client.h" +#include +#include +#include +#include "rapidjson/error/error.h" +#include "rapidjson/error/en.h" #define dout_context g_ceph_context #define dout_subsys ceph_subsys_rgw @@ -20,6 +25,79 @@ using namespace rgw; +/** + * Memory pool for use with rapidjson. This version + * carefully zeros out all memory before returning it to + * the system. + */ +#define ALIGNTYPE double +#define MINCHUNKSIZE 4096 +class ZeroPoolAllocator { +private: + struct element { + struct element *next; + int size; + char data[4]; + } *b; + size_t left; +public: + static const bool kNeedFree { false }; + ZeroPoolAllocator(){ + b = 0; + left = 0; + } + ~ZeroPoolAllocator(){ + element *p; + while ((p = b)) { + b = p->next; + memset(p->data, 0, p->size); + free(p); + } + } + void * Malloc(size_t size) { + void *r; + if (!size) return 0; + size = (size + sizeof(ALIGNTYPE)-1)&(-sizeof(ALIGNTYPE)); + if (size > left) { + size_t ns { size }; + if (ns < MINCHUNKSIZE) ns = MINCHUNKSIZE; + element *nw { (element *) malloc(sizeof *b + ns) }; + if (!nw) { +// std::cerr << "out of memory" << std::endl; + return 0; + } + left = ns - sizeof *b; + nw->size = ns; + nw->next = b; + b = nw; + } + left -= size; + r = static_cast(b->data + left); + return r; + } + void* Realloc(void* p, size_t old, size_t nw) { + void *r; + if (nw) r = malloc(nw); + if (nw > old) nw = old; + if (r && old) memcpy(r, p, nw); + return r; + } + static void Free(void *p) { + ceph_assert(0 == "Free should not be called"); + } +private: + //! Copy constructor is not permitted. + ZeroPoolAllocator(const ZeroPoolAllocator& rhs) /* = delete */; + //! Copy assignment operator is not permitted. + ZeroPoolAllocator& operator=(const ZeroPoolAllocator& rhs) /* = delete */; +}; + +typedef rapidjson::GenericDocument, + ZeroPoolAllocator, + rapidjson::CrtAllocator + > ZeroPoolDocument; +typedef rapidjson::GenericValue, ZeroPoolAllocator> ZeroPoolValue; + /** * Construct a full URL string by concatenating a "base" URL with another path, * ensuring there is one and only one forward slash between them. If path is @@ -87,9 +165,8 @@ protected: return res; } - int send_request(std::string_view key_id, JSONParser* parser) + int send_request(std::string_view key_id, bufferlist &secret_bl) { - bufferlist secret_bl; int res; string vault_token = ""; if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == cct->_conf->rgw_crypt_vault_auth){ @@ -137,28 +214,17 @@ protected: 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; + int decode_secret(std::string encoded, std::string& actual_key){ try { - secret = from_base64(json_obj->get_data()); + actual_key = from_base64(encoded); } 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'); + memset(encoded.data(), 0, encoded.length()); return 0; } @@ -167,12 +233,16 @@ public: VaultSecretEngine(CephContext *cct) { this->cct = cct; } - -// virtual ~VaultSecretEngine(){} }; class TransitSecretEngine: public VaultSecretEngine { +public: + int compat; + static const int COMPAT_NEW_ONLY = 0; + static const int COMPAT_OLD_AND_NEW = 1; + static const int COMPAT_ONLY_OLD = 2; + static const int COMPAT_UNSET = -1; private: EngineParmMap parms; @@ -194,38 +264,116 @@ private: public: TransitSecretEngine(CephContext *cct, EngineParmMap parms): VaultSecretEngine(cct), parms(parms) { + compat = COMPAT_UNSET; for (auto& e: parms) { + if (e.first == "compat") { + if (e.second.empty()) { + compat = COMPAT_OLD_AND_NEW; + } else { + size_t ep; + + compat = std::stoi(e.second, &ep); + if (ep != e.second.length()) { + lderr(cct) << "warning: vault transit secrets engine : compat=" + << e.second << " trailing junk? (ignored)" << dendl; + } + } + continue; + } lderr(cct) << "ERROR: vault transit secrets engine : parameter " << e.first << "=" << e.second << " ignored" << dendl; } + if (compat == COMPAT_UNSET) { + std::string_view v { cct->_conf->rgw_crypt_vault_prefix }; + if (v.rfind("export/encryption-key") == v.length() - 21) { + compat = COMPAT_ONLY_OLD; + } else { + compat = COMPAT_NEW_ONLY; + } + } } int get_key(std::string_view key_id, std::string& actual_key) { - JSONParser parser; + ZeroPoolDocument d; + ZeroPoolValue *v; string version; + bufferlist secret_bl; 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); + int res = send_request(key_id, secret_bl); if (res < 0) { return res; } - JSONObj* json_obj = &parser; - std::array 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; + ldout(cct, 20) << "Parse response into JSON Object" << dendl; + + rapidjson::StringStream isw(secret_bl.c_str()); + d.ParseStream<>(isw); + + if (d.HasParseError()) { + ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " + << rapidjson::GetParseError_En(d.GetParseError()) << dendl; + return -EINVAL; + } + secret_bl.zero(); + + const char *elements[] = {"data", "keys", version.c_str()}; + v = &d; + for (auto &elem: elements) { + if (!v->IsObject()) { + v = nullptr; + break; + } + auto endr { v->MemberEnd() }; + auto itr { v->FindMember(elem) }; + if (itr == endr) { + v = nullptr; + break; } + v = &itr->value; } + if (!v || !v->IsString()) { + ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl; + return -EINVAL; + } + return decode_secret(v->GetString(), actual_key); + } - return decode_secret(json_obj, actual_key); + int make_actual_key(map& attrs, std::string& actual_key) + { + std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); + if (compat == COMPAT_ONLY_OLD) return get_key(key_id, actual_key); +/* + data: {context } + post to prefix + /datakey/plaintext/ + key_id + jq: .data.plaintext -> key + jq: .data.ciphertext -> (to-be) named attribute + return decode_secret(json_obj, actual_key) +*/ +lderr(cct) << "make_key: not yet" << dendl; + return -EINVAL; + } + + int reconstitute_actual_key(map& attrs, std::string& actual_key) + { + std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); + if (compat == COMPAT_ONLY_OLD || key_id.rfind("/") != std::string::npos) { + return get_key(key_id, actual_key); + } +/* + .data.ciphertext <- (to-be) named attribute + data: {context ciphertext} + post to prefix + /decrypt/ + key_id + jq: .data.plaintext + return decode_secret(json_obj, actual_key) +*/ +lderr(cct) << "reconstitute_key: not yet" << dendl; + return -EINVAL; } }; @@ -243,22 +391,47 @@ public: virtual ~KvSecretEngine(){} int get_key(std::string_view key_id, std::string& actual_key){ - JSONParser parser; - int res = send_request(key_id, &parser); + ZeroPoolDocument d; + ZeroPoolValue *v; + bufferlist secret_bl; + + int res = send_request(key_id, secret_bl); if (res < 0) { return res; } - JSONObj *json_obj = &parser; - std::array 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; + ldout(cct, 20) << "Parse response into JSON Object" << dendl; + + rapidjson::StringStream isw(secret_bl.c_str()); + d.ParseStream<>(isw); + + if (d.HasParseError()) { + ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " + << rapidjson::GetParseError_En(d.GetParseError()) << dendl; + return -EINVAL; + } + secret_bl.zero(); + + static const char *elements[] = {"data", "data", "key"}; + v = &d; + for (auto &elem: elements) { + if (!v->IsObject()) { + v = nullptr; + break; } + auto endr { v->MemberEnd() }; + auto itr { v->FindMember(elem) }; + if (itr == endr) { + v = nullptr; + break; + } + v = &itr->value; + } + if (!v || !v->IsString()) { + 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); + return decode_secret(v->GetString(), actual_key); } }; @@ -345,15 +518,6 @@ class KmipSecretEngine: public SecretEngine { protected: CephContext *cct; -// int send_request(std::string_view key_id, JSONParser* parser) override -// { -// return -EINVAL; -// } -// -// int decode_secret(JSONObj* json_obj, std::string& actual_key){ -// return -EINVAL; -// } - public: KmipSecretEngine(CephContext *cct) { @@ -519,17 +683,15 @@ std::string config_to_engine_and_parms(CephContext *cct, static int get_actual_key_from_vault(CephContext *cct, map& attrs, - std::string& actual_key) + std::string& actual_key, bool make_it) { std::string secret_engine_str = cct->_conf->rgw_crypt_vault_secret_engine; - std::string context = get_str_attribute(attrs, RGW_ATTR_CRYPT_CONTEXT); EngineParmMap secret_engine_parms; auto secret_engine { config_to_engine_and_parms( cct, "rgw_crypt_vault_secret_engine", secret_engine_str, secret_engine_parms) }; ldout(cct, 20) << "Vault authentication method: " << cct->_conf->rgw_crypt_vault_auth << dendl; ldout(cct, 20) << "Vault Secrets Engine: " << secret_engine << dendl; -lderr(cct) << "TEMP cooked_context<" << context << ">" << dendl; if (RGW_SSE_KMS_VAULT_SE_KV == secret_engine){ std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); @@ -537,11 +699,13 @@ lderr(cct) << "TEMP cooked_context<" << context << ">" << dendl; return engine.get_key(key_id, actual_key); } else if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){ - std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); TransitSecretEngine engine(cct, std::move(secret_engine_parms)); - return engine.get_key(key_id, actual_key); + std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); + return make_it + ? engine.make_actual_key(attrs, actual_key) + : engine.reconstitute_actual_key(attrs, actual_key); } - else{ + else { ldout(cct, 0) << "Missing or invalid secret engine" << dendl; return -EINVAL; } @@ -552,7 +716,7 @@ static int make_actual_key_from_vault(CephContext *cct, map& attrs, std::string& actual_key) { - return get_actual_key_from_vault(cct, attrs, actual_key); + return get_actual_key_from_vault(cct, attrs, actual_key, true); } @@ -560,7 +724,7 @@ static int reconstitute_actual_key_from_vault(CephContext *cct, map& attrs, std::string& actual_key) { - return get_actual_key_from_vault(cct, attrs, actual_key); + return get_actual_key_from_vault(cct, attrs, actual_key, false); }