]>
Commit | Line | Data |
---|---|---|
9f95a23c TL |
1 | // -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | /** | |
5 | * Server-side encryption integrations with Key Management Systems (SSE-KMS) | |
6 | */ | |
7 | ||
8 | #include <sys/stat.h> | |
9 | #include "include/str_map.h" | |
10 | #include "common/safe_io.h" | |
11 | #include "rgw/rgw_crypt.h" | |
12 | #include "rgw/rgw_keystone.h" | |
13 | #include "rgw/rgw_b64.h" | |
14 | #include "rgw/rgw_kms.h" | |
f67539c2 TL |
15 | #include "rgw/rgw_kmip_client.h" |
16 | #include <rapidjson/allocators.h> | |
17 | #include <rapidjson/document.h> | |
18 | #include <rapidjson/writer.h> | |
19 | #include "rapidjson/error/error.h" | |
20 | #include "rapidjson/error/en.h" | |
9f95a23c TL |
21 | |
22 | #define dout_context g_ceph_context | |
23 | #define dout_subsys ceph_subsys_rgw | |
24 | ||
25 | using namespace rgw; | |
26 | ||
f67539c2 TL |
27 | #ifndef FORTEST_VIRTUAL |
28 | #define FORTEST_VIRTUAL /**/ | |
29 | #endif | |
30 | ||
31 | /** | |
32 | * Memory pool for use with rapidjson. This version | |
33 | * carefully zeros out all memory before returning it to | |
34 | * the system. | |
35 | */ | |
36 | #define ALIGNTYPE double | |
37 | #define MINCHUNKSIZE 4096 | |
38 | class ZeroPoolAllocator { | |
39 | private: | |
40 | struct element { | |
41 | struct element *next; | |
42 | int size; | |
43 | char data[4]; | |
44 | } *b; | |
45 | size_t left; | |
46 | public: | |
47 | static const bool kNeedFree { false }; | |
48 | ZeroPoolAllocator(){ | |
49 | b = 0; | |
50 | left = 0; | |
51 | } | |
52 | ~ZeroPoolAllocator(){ | |
53 | element *p; | |
54 | while ((p = b)) { | |
55 | b = p->next; | |
56 | memset(p->data, 0, p->size); | |
57 | free(p); | |
58 | } | |
59 | } | |
60 | void * Malloc(size_t size) { | |
61 | void *r; | |
62 | if (!size) return 0; | |
63 | size = (size + sizeof(ALIGNTYPE)-1)&(-sizeof(ALIGNTYPE)); | |
64 | if (size > left) { | |
65 | size_t ns { size }; | |
66 | if (ns < MINCHUNKSIZE) ns = MINCHUNKSIZE; | |
67 | element *nw { (element *) malloc(sizeof *b + ns) }; | |
68 | if (!nw) { | |
69 | // std::cerr << "out of memory" << std::endl; | |
70 | return 0; | |
71 | } | |
72 | left = ns - sizeof *b; | |
73 | nw->size = ns; | |
74 | nw->next = b; | |
75 | b = nw; | |
76 | } | |
77 | left -= size; | |
78 | r = static_cast<void*>(b->data + left); | |
79 | return r; | |
80 | } | |
81 | void* Realloc(void* p, size_t old, size_t nw) { | |
82 | void *r; | |
83 | if (nw) r = malloc(nw); | |
84 | if (nw > old) nw = old; | |
85 | if (r && old) memcpy(r, p, nw); | |
86 | return r; | |
87 | } | |
88 | static void Free(void *p) { | |
89 | ceph_assert(0 == "Free should not be called"); | |
90 | } | |
91 | private: | |
92 | //! Copy constructor is not permitted. | |
93 | ZeroPoolAllocator(const ZeroPoolAllocator& rhs) /* = delete */; | |
94 | //! Copy assignment operator is not permitted. | |
95 | ZeroPoolAllocator& operator=(const ZeroPoolAllocator& rhs) /* = delete */; | |
96 | }; | |
97 | ||
98 | typedef rapidjson::GenericDocument<rapidjson::UTF8<>, | |
99 | ZeroPoolAllocator, | |
100 | rapidjson::CrtAllocator | |
101 | > ZeroPoolDocument; | |
102 | typedef rapidjson::GenericValue<rapidjson::UTF8<>, ZeroPoolAllocator> ZeroPoolValue; | |
9f95a23c TL |
103 | |
104 | /** | |
105 | * Construct a full URL string by concatenating a "base" URL with another path, | |
106 | * ensuring there is one and only one forward slash between them. If path is | |
107 | * empty, the URL is not changed. | |
108 | */ | |
109 | static void concat_url(std::string &url, std::string path) { | |
110 | bool url_has_slash = !url.empty() && url.back() == '/'; | |
111 | if (!path.empty()) { | |
112 | if (url_has_slash && path.front() == '/') { | |
113 | url.pop_back(); | |
114 | } else if (!url_has_slash && path.front() != '/') { | |
115 | url.push_back('/'); | |
116 | } | |
117 | url.append(path); | |
118 | } | |
119 | } | |
120 | ||
f67539c2 TL |
121 | /** |
122 | * Determine if a string (url) ends with a given suffix. | |
123 | * Must deal with (ignore) trailing slashes. | |
124 | */ | |
125 | static bool string_ends_maybe_slash(std::string_view hay, | |
126 | std::string_view needle) | |
127 | { | |
128 | auto hay_len { hay.size() }; | |
129 | auto needle_len { needle.size() }; | |
130 | if (hay_len < needle_len) return false; | |
131 | auto hay_suffix_start { hay.data() + (hay_len - needle_len) }; | |
132 | while (hay_len > needle_len && hay[hay_len-1] == '/') { | |
133 | --hay_len; | |
134 | --hay_suffix_start; | |
135 | } | |
136 | std::string_view hay_suffix { hay_suffix_start, needle_len }; | |
137 | return hay_suffix == needle; | |
138 | } | |
139 | ||
140 | template<typename E, typename A = ZeroPoolAllocator> | |
141 | static inline void | |
142 | add_name_val_to_obj(std::string &n, std::string &v, rapidjson::GenericValue<E,A> &d, | |
143 | A &allocator) | |
144 | { | |
145 | rapidjson::GenericValue<E,A> name, val; | |
146 | name.SetString(n.c_str(), n.length(), allocator); | |
147 | val.SetString(v.c_str(), v.length(), allocator); | |
148 | d.AddMember(name, val, allocator); | |
149 | } | |
150 | ||
151 | template<typename E, typename A = ZeroPoolAllocator> | |
152 | static inline void | |
153 | add_name_val_to_obj(const char *n, std::string &v, rapidjson::GenericValue<E,A> &d, | |
154 | A &allocator) | |
155 | { | |
156 | std::string ns{n, strlen(n) }; | |
157 | add_name_val_to_obj(ns, v, d, allocator); | |
158 | } | |
159 | ||
160 | typedef std::map<std::string, std::string> EngineParmMap; | |
9f95a23c TL |
161 | |
162 | class VaultSecretEngine: public SecretEngine { | |
163 | ||
164 | protected: | |
165 | CephContext *cct; | |
166 | ||
167 | int load_token_from_file(std::string *vault_token) | |
168 | { | |
169 | ||
170 | int res = 0; | |
171 | std::string token_file = cct->_conf->rgw_crypt_vault_token_file; | |
172 | if (token_file.empty()) { | |
173 | ldout(cct, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl; | |
174 | return -EINVAL; | |
175 | } | |
176 | ldout(cct, 20) << "Vault token file: " << token_file << dendl; | |
177 | ||
178 | struct stat token_st; | |
179 | if (stat(token_file.c_str(), &token_st) != 0) { | |
180 | ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' not found " << dendl; | |
181 | return -ENOENT; | |
182 | } | |
183 | ||
184 | if (token_st.st_mode & (S_IRWXG | S_IRWXO)) { | |
185 | ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' permissions are " | |
186 | << "too open, it must not be accessible by other users" << dendl; | |
187 | return -EACCES; | |
188 | } | |
189 | ||
190 | char buf[2048]; | |
191 | res = safe_read_file("", token_file.c_str(), buf, sizeof(buf)); | |
192 | if (res < 0) { | |
193 | if (-EACCES == res) { | |
194 | ldout(cct, 0) << "ERROR: Permission denied reading Vault token file" << dendl; | |
195 | } else { | |
196 | ldout(cct, 0) << "ERROR: Failed to read Vault token file with error " << res << dendl; | |
197 | } | |
198 | return res; | |
199 | } | |
200 | // drop trailing newlines | |
201 | while (res && isspace(buf[res-1])) { | |
202 | --res; | |
203 | } | |
204 | vault_token->assign(std::string{buf, static_cast<size_t>(res)}); | |
205 | memset(buf, 0, sizeof(buf)); | |
206 | ::ceph::crypto::zeroize_for_security(buf, sizeof(buf)); | |
207 | return res; | |
208 | } | |
209 | ||
f67539c2 TL |
210 | FORTEST_VIRTUAL |
211 | int send_request(const char *method, std::string_view infix, | |
212 | std::string_view key_id, | |
213 | const std::string& postdata, | |
214 | bufferlist &secret_bl) | |
9f95a23c | 215 | { |
9f95a23c TL |
216 | int res; |
217 | string vault_token = ""; | |
218 | if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == cct->_conf->rgw_crypt_vault_auth){ | |
219 | ldout(cct, 0) << "Loading Vault Token from filesystem" << dendl; | |
220 | res = load_token_from_file(&vault_token); | |
221 | if (res < 0){ | |
222 | return res; | |
223 | } | |
224 | } | |
225 | ||
226 | std::string secret_url = cct->_conf->rgw_crypt_vault_addr; | |
227 | if (secret_url.empty()) { | |
228 | ldout(cct, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl; | |
229 | return -EINVAL; | |
230 | } | |
231 | ||
232 | concat_url(secret_url, cct->_conf->rgw_crypt_vault_prefix); | |
f67539c2 | 233 | concat_url(secret_url, std::string(infix)); |
9f95a23c TL |
234 | concat_url(secret_url, std::string(key_id)); |
235 | ||
f67539c2 TL |
236 | RGWHTTPTransceiver secret_req(cct, method, secret_url, &secret_bl); |
237 | ||
238 | if (postdata.length()) { | |
239 | secret_req.set_post_data(postdata); | |
240 | secret_req.set_send_length(postdata.length()); | |
241 | } | |
9f95a23c | 242 | |
f67539c2 | 243 | secret_req.append_header("X-Vault-Token", vault_token); |
9f95a23c TL |
244 | if (!vault_token.empty()){ |
245 | secret_req.append_header("X-Vault-Token", vault_token); | |
246 | vault_token.replace(0, vault_token.length(), vault_token.length(), '\000'); | |
247 | } | |
248 | ||
249 | string vault_namespace = cct->_conf->rgw_crypt_vault_namespace; | |
250 | if (!vault_namespace.empty()){ | |
251 | ldout(cct, 20) << "Vault Namespace: " << vault_namespace << dendl; | |
252 | secret_req.append_header("X-Vault-Namespace", vault_namespace); | |
253 | } | |
254 | ||
522d829b TL |
255 | secret_req.set_verify_ssl(cct->_conf->rgw_crypt_vault_verify_ssl); |
256 | ||
257 | if (!cct->_conf->rgw_crypt_vault_ssl_cacert.empty()) { | |
258 | secret_req.set_ca_path(cct->_conf->rgw_crypt_vault_ssl_cacert); | |
259 | } | |
260 | ||
261 | if (!cct->_conf->rgw_crypt_vault_ssl_clientcert.empty()) { | |
262 | secret_req.set_client_cert(cct->_conf->rgw_crypt_vault_ssl_clientcert); | |
263 | } | |
264 | if (!cct->_conf->rgw_crypt_vault_ssl_clientkey.empty()) { | |
265 | secret_req.set_client_key(cct->_conf->rgw_crypt_vault_ssl_clientkey); | |
266 | } | |
267 | ||
9f95a23c TL |
268 | res = secret_req.process(null_yield); |
269 | if (res < 0) { | |
270 | ldout(cct, 0) << "ERROR: Request to Vault failed with error " << res << dendl; | |
271 | return res; | |
272 | } | |
273 | ||
274 | if (secret_req.get_http_status() == | |
275 | RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) { | |
276 | ldout(cct, 0) << "ERROR: Vault request failed authorization" << dendl; | |
277 | return -EACCES; | |
278 | } | |
279 | ||
280 | ldout(cct, 20) << "Request to Vault returned " << res << " and HTTP status " | |
281 | << secret_req.get_http_status() << dendl; | |
282 | ||
9f95a23c TL |
283 | return res; |
284 | } | |
285 | ||
f67539c2 TL |
286 | int send_request(std::string_view key_id, bufferlist &secret_bl) |
287 | { | |
288 | return send_request("GET", "", key_id, string{}, secret_bl); | |
289 | } | |
290 | ||
291 | int decode_secret(std::string encoded, std::string& actual_key){ | |
9f95a23c | 292 | try { |
f67539c2 | 293 | actual_key = from_base64(encoded); |
9f95a23c TL |
294 | } catch (std::exception&) { |
295 | ldout(cct, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl; | |
296 | return -EINVAL; | |
297 | } | |
f67539c2 | 298 | memset(encoded.data(), 0, encoded.length()); |
9f95a23c TL |
299 | return 0; |
300 | } | |
301 | ||
302 | public: | |
303 | ||
304 | VaultSecretEngine(CephContext *cct) { | |
305 | this->cct = cct; | |
306 | } | |
9f95a23c TL |
307 | }; |
308 | ||
9f95a23c | 309 | class TransitSecretEngine: public VaultSecretEngine { |
f67539c2 TL |
310 | public: |
311 | int compat; | |
312 | static const int COMPAT_NEW_ONLY = 0; | |
313 | static const int COMPAT_OLD_AND_NEW = 1; | |
314 | static const int COMPAT_ONLY_OLD = 2; | |
315 | static const int COMPAT_UNSET = -1; | |
9f95a23c TL |
316 | |
317 | private: | |
f67539c2 TL |
318 | EngineParmMap parms; |
319 | ||
320 | int get_key_version(std::string_view key_id, string& version) | |
9f95a23c TL |
321 | { |
322 | size_t pos = 0; | |
323 | ||
324 | pos = key_id.rfind("/"); | |
f67539c2 TL |
325 | if (pos != std::string_view::npos){ |
326 | std::string_view token = key_id.substr(pos+1, key_id.length()-pos); | |
327 | if (!token.empty() && token.find_first_not_of("0123456789") == std::string_view::npos){ | |
9f95a23c TL |
328 | version.assign(std::string(token)); |
329 | return 0; | |
330 | } | |
331 | } | |
332 | return -1; | |
333 | } | |
334 | ||
335 | public: | |
f67539c2 TL |
336 | TransitSecretEngine(CephContext *cct, EngineParmMap parms): VaultSecretEngine(cct), parms(parms) { |
337 | compat = COMPAT_UNSET; | |
338 | for (auto& e: parms) { | |
339 | if (e.first == "compat") { | |
340 | if (e.second.empty()) { | |
341 | compat = COMPAT_OLD_AND_NEW; | |
342 | } else { | |
343 | size_t ep; | |
344 | ||
345 | compat = std::stoi(e.second, &ep); | |
346 | if (ep != e.second.length()) { | |
347 | lderr(cct) << "warning: vault transit secrets engine : compat=" | |
348 | << e.second << " trailing junk? (ignored)" << dendl; | |
349 | } | |
350 | } | |
351 | continue; | |
352 | } | |
353 | lderr(cct) << "ERROR: vault transit secrets engine : parameter " | |
354 | << e.first << "=" << e.second << " ignored" << dendl; | |
355 | } | |
356 | if (compat == COMPAT_UNSET) { | |
357 | std::string_view v { cct->_conf->rgw_crypt_vault_prefix }; | |
358 | if (string_ends_maybe_slash(v,"/export/encryption-key")) { | |
359 | compat = COMPAT_ONLY_OLD; | |
360 | } else { | |
361 | compat = COMPAT_NEW_ONLY; | |
362 | } | |
363 | } | |
364 | } | |
9f95a23c | 365 | |
f67539c2 | 366 | int get_key(std::string_view key_id, std::string& actual_key) |
9f95a23c | 367 | { |
f67539c2 TL |
368 | ZeroPoolDocument d; |
369 | ZeroPoolValue *v; | |
9f95a23c | 370 | string version; |
f67539c2 | 371 | bufferlist secret_bl; |
9f95a23c TL |
372 | |
373 | if (get_key_version(key_id, version) < 0){ | |
374 | ldout(cct, 20) << "Missing or invalid key version" << dendl; | |
375 | return -EINVAL; | |
376 | } | |
377 | ||
f67539c2 TL |
378 | int res = send_request("GET", compat == COMPAT_ONLY_OLD ? "" : "/export/encryption-key", |
379 | key_id, string{}, secret_bl); | |
9f95a23c TL |
380 | if (res < 0) { |
381 | return res; | |
382 | } | |
383 | ||
f67539c2 TL |
384 | ldout(cct, 20) << "Parse response into JSON Object" << dendl; |
385 | ||
386 | secret_bl.append('\0'); | |
387 | rapidjson::StringStream isw(secret_bl.c_str()); | |
388 | d.ParseStream<>(isw); | |
389 | ||
390 | if (d.HasParseError()) { | |
391 | ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " | |
392 | << rapidjson::GetParseError_En(d.GetParseError()) << dendl; | |
393 | return -EINVAL; | |
394 | } | |
395 | secret_bl.zero(); | |
396 | ||
397 | const char *elements[] = {"data", "keys", version.c_str()}; | |
398 | v = &d; | |
399 | for (auto &elem: elements) { | |
400 | if (!v->IsObject()) { | |
401 | v = nullptr; | |
402 | break; | |
403 | } | |
404 | auto endr { v->MemberEnd() }; | |
405 | auto itr { v->FindMember(elem) }; | |
406 | if (itr == endr) { | |
407 | v = nullptr; | |
408 | break; | |
9f95a23c | 409 | } |
f67539c2 TL |
410 | v = &itr->value; |
411 | } | |
412 | if (!v || !v->IsString()) { | |
413 | ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl; | |
414 | return -EINVAL; | |
9f95a23c | 415 | } |
f67539c2 TL |
416 | return decode_secret(v->GetString(), actual_key); |
417 | } | |
9f95a23c | 418 | |
f67539c2 TL |
419 | int make_actual_key(map<string, bufferlist>& attrs, std::string& actual_key) |
420 | { | |
421 | std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); | |
422 | if (compat == COMPAT_ONLY_OLD) return get_key(key_id, actual_key); | |
423 | if (key_id.find("/") != std::string::npos) { | |
424 | ldout(cct, 0) << "sorry, can't allow / in keyid" << dendl; | |
425 | return -EINVAL; | |
426 | } | |
427 | /* | |
428 | data: {context } | |
429 | post to prefix + /datakey/plaintext/ + key_id | |
430 | jq: .data.plaintext -> key | |
431 | jq: .data.ciphertext -> (to-be) named attribute | |
432 | return decode_secret(json_obj, actual_key) | |
433 | */ | |
434 | std::string context = get_str_attribute(attrs, RGW_ATTR_CRYPT_CONTEXT); | |
435 | ZeroPoolDocument d { rapidjson::kObjectType }; | |
436 | auto &allocator { d.GetAllocator() }; | |
437 | bufferlist secret_bl; | |
438 | ||
439 | add_name_val_to_obj("context", context, d, allocator); | |
440 | rapidjson::StringBuffer buf; | |
441 | rapidjson::Writer<rapidjson::StringBuffer> writer(buf); | |
442 | if (!d.Accept(writer)) { | |
443 | ldout(cct, 0) << "ERROR: can't make json for vault" << dendl; | |
444 | return -EINVAL; | |
445 | } | |
446 | std::string post_data { buf.GetString() }; | |
447 | ||
448 | int res = send_request("POST", "/datakey/plaintext/", key_id, | |
449 | post_data, secret_bl); | |
450 | if (res < 0) { | |
451 | return res; | |
452 | } | |
453 | ||
454 | ldout(cct, 20) << "Parse response into JSON Object" << dendl; | |
455 | ||
456 | secret_bl.append('\0'); | |
457 | rapidjson::StringStream isw(secret_bl.c_str()); | |
458 | d.SetNull(); | |
459 | d.ParseStream<>(isw); | |
460 | ||
461 | if (d.HasParseError()) { | |
462 | ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " | |
463 | << rapidjson::GetParseError_En(d.GetParseError()) << dendl; | |
464 | return -EINVAL; | |
465 | } | |
466 | secret_bl.zero(); | |
467 | ||
468 | if (!d.IsObject()) { | |
469 | ldout(cct, 0) << "ERROR: response from Vault is not an object" << dendl; | |
470 | return -EINVAL; | |
471 | } | |
472 | { | |
473 | auto data_itr { d.FindMember("data") }; | |
474 | if (data_itr == d.MemberEnd()) { | |
475 | ldout(cct, 0) << "ERROR: no .data in response from Vault" << dendl; | |
476 | return -EINVAL; | |
477 | } | |
478 | auto ciphertext_itr { data_itr->value.FindMember("ciphertext") }; | |
479 | auto plaintext_itr { data_itr->value.FindMember("plaintext") }; | |
480 | if (ciphertext_itr == data_itr->value.MemberEnd()) { | |
481 | ldout(cct, 0) << "ERROR: no .data.ciphertext in response from Vault" << dendl; | |
482 | return -EINVAL; | |
483 | } | |
484 | if (plaintext_itr == data_itr->value.MemberEnd()) { | |
485 | ldout(cct, 0) << "ERROR: no .data.plaintext in response from Vault" << dendl; | |
486 | return -EINVAL; | |
487 | } | |
488 | auto &ciphertext_v { ciphertext_itr->value }; | |
489 | auto &plaintext_v { plaintext_itr->value }; | |
490 | if (!ciphertext_v.IsString()) { | |
491 | ldout(cct, 0) << "ERROR: .data.ciphertext not a string in response from Vault" << dendl; | |
492 | return -EINVAL; | |
493 | } | |
494 | if (!plaintext_v.IsString()) { | |
495 | ldout(cct, 0) << "ERROR: .data.plaintext not a string in response from Vault" << dendl; | |
496 | return -EINVAL; | |
497 | } | |
498 | set_attr(attrs, RGW_ATTR_CRYPT_DATAKEY, ciphertext_v.GetString()); | |
499 | return decode_secret(plaintext_v.GetString(), actual_key); | |
500 | } | |
9f95a23c TL |
501 | } |
502 | ||
f67539c2 TL |
503 | int reconstitute_actual_key(map<string, bufferlist>& attrs, std::string& actual_key) |
504 | { | |
505 | std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); | |
506 | std::string wrapped_key = get_str_attribute(attrs, RGW_ATTR_CRYPT_DATAKEY); | |
507 | if (compat == COMPAT_ONLY_OLD || key_id.rfind("/") != std::string::npos) { | |
508 | return get_key(key_id, actual_key); | |
509 | } | |
510 | /* | |
511 | .data.ciphertext <- (to-be) named attribute | |
512 | data: {context ciphertext} | |
513 | post to prefix + /decrypt/ + key_id | |
514 | jq: .data.plaintext | |
515 | return decode_secret(json_obj, actual_key) | |
516 | */ | |
517 | std::string context = get_str_attribute(attrs, RGW_ATTR_CRYPT_CONTEXT); | |
518 | ZeroPoolDocument d { rapidjson::kObjectType }; | |
519 | auto &allocator { d.GetAllocator() }; | |
520 | bufferlist secret_bl; | |
521 | ||
522 | add_name_val_to_obj("context", context, d, allocator); | |
523 | add_name_val_to_obj("ciphertext", wrapped_key, d, allocator); | |
524 | rapidjson::StringBuffer buf; | |
525 | rapidjson::Writer<rapidjson::StringBuffer> writer(buf); | |
526 | if (!d.Accept(writer)) { | |
527 | ldout(cct, 0) << "ERROR: can't make json for vault" << dendl; | |
528 | return -EINVAL; | |
529 | } | |
530 | std::string post_data { buf.GetString() }; | |
531 | ||
532 | int res = send_request("POST", "/decrypt/", key_id, | |
533 | post_data, secret_bl); | |
534 | if (res < 0) { | |
535 | return res; | |
536 | } | |
537 | ||
538 | ldout(cct, 20) << "Parse response into JSON Object" << dendl; | |
539 | ||
540 | secret_bl.append('\0'); | |
541 | rapidjson::StringStream isw(secret_bl.c_str()); | |
542 | d.SetNull(); | |
543 | d.ParseStream<>(isw); | |
544 | ||
545 | if (d.HasParseError()) { | |
546 | ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " | |
547 | << rapidjson::GetParseError_En(d.GetParseError()) << dendl; | |
548 | return -EINVAL; | |
549 | } | |
550 | secret_bl.zero(); | |
551 | ||
552 | if (!d.IsObject()) { | |
553 | ldout(cct, 0) << "ERROR: response from Vault is not an object" << dendl; | |
554 | return -EINVAL; | |
555 | } | |
556 | { | |
557 | auto data_itr { d.FindMember("data") }; | |
558 | if (data_itr == d.MemberEnd()) { | |
559 | ldout(cct, 0) << "ERROR: no .data in response from Vault" << dendl; | |
560 | return -EINVAL; | |
561 | } | |
562 | auto plaintext_itr { data_itr->value.FindMember("plaintext") }; | |
563 | if (plaintext_itr == data_itr->value.MemberEnd()) { | |
564 | ldout(cct, 0) << "ERROR: no .data.plaintext in response from Vault" << dendl; | |
565 | return -EINVAL; | |
566 | } | |
567 | auto &plaintext_v { plaintext_itr->value }; | |
568 | if (!plaintext_v.IsString()) { | |
569 | ldout(cct, 0) << "ERROR: .data.plaintext not a string in response from Vault" << dendl; | |
570 | return -EINVAL; | |
571 | } | |
572 | return decode_secret(plaintext_v.GetString(), actual_key); | |
573 | } | |
574 | } | |
9f95a23c TL |
575 | }; |
576 | ||
577 | class KvSecretEngine: public VaultSecretEngine { | |
578 | ||
579 | public: | |
580 | ||
f67539c2 TL |
581 | KvSecretEngine(CephContext *cct, EngineParmMap parms): VaultSecretEngine(cct){ |
582 | if (!parms.empty()) { | |
583 | lderr(cct) << "ERROR: vault kv secrets engine takes no parameters (ignoring them)" << dendl; | |
584 | } | |
585 | } | |
9f95a23c TL |
586 | |
587 | virtual ~KvSecretEngine(){} | |
588 | ||
f67539c2 TL |
589 | int get_key(std::string_view key_id, std::string& actual_key){ |
590 | ZeroPoolDocument d; | |
591 | ZeroPoolValue *v; | |
592 | bufferlist secret_bl; | |
593 | ||
594 | int res = send_request(key_id, secret_bl); | |
9f95a23c TL |
595 | if (res < 0) { |
596 | return res; | |
597 | } | |
598 | ||
f67539c2 TL |
599 | ldout(cct, 20) << "Parse response into JSON Object" << dendl; |
600 | ||
601 | secret_bl.append('\0'); | |
602 | rapidjson::StringStream isw(secret_bl.c_str()); | |
603 | d.ParseStream<>(isw); | |
604 | ||
605 | if (d.HasParseError()) { | |
606 | ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault: " | |
607 | << rapidjson::GetParseError_En(d.GetParseError()) << dendl; | |
608 | return -EINVAL; | |
609 | } | |
610 | secret_bl.zero(); | |
611 | ||
612 | static const char *elements[] = {"data", "data", "key"}; | |
613 | v = &d; | |
614 | for (auto &elem: elements) { | |
615 | if (!v->IsObject()) { | |
616 | v = nullptr; | |
617 | break; | |
618 | } | |
619 | auto endr { v->MemberEnd() }; | |
620 | auto itr { v->FindMember(elem) }; | |
621 | if (itr == endr) { | |
622 | v = nullptr; | |
623 | break; | |
9f95a23c | 624 | } |
f67539c2 TL |
625 | v = &itr->value; |
626 | } | |
627 | if (!v || !v->IsString()) { | |
628 | ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using KV Engine" << dendl; | |
629 | return -EINVAL; | |
9f95a23c | 630 | } |
f67539c2 TL |
631 | return decode_secret(v->GetString(), actual_key); |
632 | } | |
633 | ||
634 | }; | |
635 | ||
636 | class KmipSecretEngine; | |
637 | class KmipGetTheKey { | |
638 | private: | |
639 | CephContext *cct; | |
640 | std::string work; | |
641 | bool failed = false; | |
642 | int ret; | |
643 | protected: | |
644 | KmipGetTheKey(CephContext *cct) : cct(cct) {} | |
645 | KmipGetTheKey& keyid_to_keyname(std::string_view key_id); | |
646 | KmipGetTheKey& get_uniqueid_for_keyname(); | |
647 | int get_key_for_uniqueid(std::string &); | |
648 | friend KmipSecretEngine; | |
649 | }; | |
650 | ||
651 | KmipGetTheKey& | |
652 | KmipGetTheKey::keyid_to_keyname(std::string_view key_id) | |
653 | { | |
654 | work = cct->_conf->rgw_crypt_kmip_kms_key_template; | |
655 | std::string keyword = "$keyid"; | |
656 | std::string replacement = std::string(key_id); | |
657 | size_t pos = 0; | |
658 | if (work.length() == 0) { | |
659 | work = std::move(replacement); | |
660 | } else { | |
661 | while (pos < work.length()) { | |
662 | pos = work.find(keyword, pos); | |
663 | if (pos == std::string::npos) break; | |
664 | work.replace(pos, keyword.length(), replacement); | |
665 | pos += key_id.length(); | |
666 | } | |
667 | } | |
668 | return *this; | |
669 | } | |
670 | ||
671 | KmipGetTheKey& | |
672 | KmipGetTheKey::get_uniqueid_for_keyname() | |
673 | { | |
674 | RGWKMIPTransceiver secret_req(cct, RGWKMIPTransceiver::LOCATE); | |
675 | ||
676 | secret_req.name = work.data(); | |
677 | ret = secret_req.process(null_yield); | |
678 | if (ret < 0) { | |
679 | failed = true; | |
680 | } else if (!secret_req.outlist->string_count) { | |
681 | ret = -ENOENT; | |
682 | lderr(cct) << "error: locate returned no results for " | |
683 | << secret_req.name << dendl; | |
684 | failed = true; | |
685 | } else if (secret_req.outlist->string_count != 1) { | |
686 | ret = -EINVAL; | |
687 | lderr(cct) << "error: locate found " | |
688 | << secret_req.outlist->string_count | |
689 | << " results for " << secret_req.name << dendl; | |
690 | failed = true; | |
691 | } else { | |
692 | work = std::string(secret_req.outlist->strings[0]); | |
693 | } | |
694 | return *this; | |
695 | } | |
696 | ||
697 | int | |
698 | KmipGetTheKey::get_key_for_uniqueid(std::string& actual_key) | |
699 | { | |
700 | if (failed) return ret; | |
701 | RGWKMIPTransceiver secret_req(cct, RGWKMIPTransceiver::GET); | |
702 | secret_req.unique_id = work.data(); | |
703 | ret = secret_req.process(null_yield); | |
704 | if (ret < 0) { | |
705 | failed = true; | |
706 | } else { | |
707 | actual_key = std::string((char*)(secret_req.outkey->data), | |
708 | secret_req.outkey->keylen); | |
709 | } | |
710 | return ret; | |
711 | } | |
712 | ||
713 | class KmipSecretEngine: public SecretEngine { | |
714 | ||
715 | protected: | |
716 | CephContext *cct; | |
717 | ||
718 | public: | |
719 | ||
720 | KmipSecretEngine(CephContext *cct) { | |
721 | this->cct = cct; | |
9f95a23c TL |
722 | } |
723 | ||
f67539c2 TL |
724 | int get_key(std::string_view key_id, std::string& actual_key) |
725 | { | |
726 | int r; | |
727 | r = KmipGetTheKey{cct} | |
728 | .keyid_to_keyname(key_id) | |
729 | .get_uniqueid_for_keyname() | |
730 | .get_key_for_uniqueid(actual_key); | |
731 | return r; | |
732 | } | |
9f95a23c TL |
733 | }; |
734 | ||
735 | ||
736 | static map<string,string> get_str_map(const string &str) { | |
737 | map<string,string> m; | |
738 | get_str_map(str, &m, ";, \t"); | |
739 | return m; | |
740 | } | |
741 | ||
742 | ||
743 | static int get_actual_key_from_conf(CephContext *cct, | |
f67539c2 TL |
744 | std::string_view key_id, |
745 | std::string_view key_selector, | |
9f95a23c TL |
746 | std::string& actual_key) |
747 | { | |
748 | int res = 0; | |
749 | ||
750 | static map<string,string> str_map = get_str_map( | |
751 | cct->_conf->rgw_crypt_s3_kms_encryption_keys); | |
752 | ||
753 | map<string, string>::iterator it = str_map.find(std::string(key_id)); | |
754 | if (it == str_map.end()) | |
755 | return -ERR_INVALID_ACCESS_KEY; | |
756 | ||
757 | std::string master_key; | |
758 | try { | |
759 | master_key = from_base64((*it).second); | |
760 | } catch (std::exception&) { | |
761 | ldout(cct, 5) << "ERROR: get_actual_key_from_conf invalid encryption key id " | |
762 | << "which contains character that is not base64 encoded." | |
763 | << dendl; | |
764 | return -EINVAL; | |
765 | } | |
766 | ||
767 | if (master_key.length() == AES_256_KEYSIZE) { | |
768 | uint8_t _actual_key[AES_256_KEYSIZE]; | |
769 | if (AES_256_ECB_encrypt(cct, | |
770 | reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE, | |
771 | reinterpret_cast<const uint8_t*>(key_selector.data()), | |
772 | _actual_key, AES_256_KEYSIZE)) { | |
773 | actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE); | |
774 | } else { | |
775 | res = -EIO; | |
776 | } | |
777 | ::ceph::crypto::zeroize_for_security(_actual_key, sizeof(_actual_key)); | |
778 | } else { | |
779 | ldout(cct, 20) << "Wrong size for key=" << key_id << dendl; | |
780 | res = -EIO; | |
781 | } | |
782 | ||
783 | return res; | |
784 | } | |
785 | ||
786 | static int request_key_from_barbican(CephContext *cct, | |
f67539c2 | 787 | std::string_view key_id, |
9f95a23c TL |
788 | const std::string& barbican_token, |
789 | std::string& actual_key) { | |
790 | int res; | |
791 | ||
792 | std::string secret_url = cct->_conf->rgw_barbican_url; | |
793 | if (secret_url.empty()) { | |
794 | ldout(cct, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl; | |
795 | return -EINVAL; | |
796 | } | |
797 | concat_url(secret_url, "/v1/secrets/"); | |
798 | concat_url(secret_url, std::string(key_id)); | |
799 | ||
800 | bufferlist secret_bl; | |
801 | RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl); | |
802 | secret_req.append_header("Accept", "application/octet-stream"); | |
803 | secret_req.append_header("X-Auth-Token", barbican_token); | |
804 | ||
805 | res = secret_req.process(null_yield); | |
806 | if (res < 0) { | |
807 | return res; | |
808 | } | |
809 | if (secret_req.get_http_status() == | |
810 | RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) { | |
811 | return -EACCES; | |
812 | } | |
813 | ||
814 | if (secret_req.get_http_status() >=200 && | |
815 | secret_req.get_http_status() < 300 && | |
816 | secret_bl.length() == AES_256_KEYSIZE) { | |
817 | actual_key.assign(secret_bl.c_str(), secret_bl.length()); | |
818 | secret_bl.zero(); | |
819 | } else { | |
820 | res = -EACCES; | |
821 | } | |
822 | return res; | |
823 | } | |
824 | ||
825 | static int get_actual_key_from_barbican(CephContext *cct, | |
f67539c2 | 826 | std::string_view key_id, |
9f95a23c TL |
827 | std::string& actual_key) |
828 | { | |
829 | int res = 0; | |
830 | std::string token; | |
831 | ||
832 | if (rgw::keystone::Service::get_keystone_barbican_token(cct, token) < 0) { | |
833 | ldout(cct, 5) << "Failed to retrieve token for Barbican" << dendl; | |
834 | return -EINVAL; | |
835 | } | |
836 | ||
837 | res = request_key_from_barbican(cct, key_id, token, actual_key); | |
838 | if (res != 0) { | |
839 | ldout(cct, 5) << "Failed to retrieve secret from Barbican:" << key_id << dendl; | |
840 | } | |
841 | return res; | |
842 | } | |
843 | ||
844 | ||
f67539c2 TL |
845 | std::string config_to_engine_and_parms(CephContext *cct, |
846 | const char* which, | |
847 | std::string& secret_engine_str, | |
848 | EngineParmMap& secret_engine_parms) | |
849 | { | |
850 | std::ostringstream oss; | |
851 | std::vector<std::string> secret_engine_v; | |
852 | std::string secret_engine; | |
853 | ||
854 | get_str_vec(secret_engine_str, " ", secret_engine_v); | |
855 | ||
856 | cct->_conf.early_expand_meta(secret_engine_str, &oss); | |
857 | auto meta_errors {oss.str()}; | |
858 | if (meta_errors.length()) { | |
859 | meta_errors.erase(meta_errors.find_last_not_of("\n")+1); | |
860 | lderr(cct) << "ERROR: while expanding " << which << ": " | |
861 | << meta_errors << dendl; | |
862 | } | |
863 | for (auto& e: secret_engine_v) { | |
864 | if (!secret_engine.length()) { | |
865 | secret_engine = std::move(e); | |
866 | continue; | |
867 | } | |
868 | auto p { e.find('=') }; | |
869 | if (p == std::string::npos) { | |
870 | secret_engine_parms.emplace(std::move(e), ""); | |
871 | continue; | |
872 | } | |
873 | std::string key{ e.substr(0,p) }; | |
874 | std::string val{ e.substr(p+1) }; | |
875 | secret_engine_parms.emplace(std::move(key), std::move(val)); | |
876 | } | |
877 | return secret_engine; | |
878 | } | |
879 | ||
880 | ||
9f95a23c | 881 | static int get_actual_key_from_vault(CephContext *cct, |
f67539c2 TL |
882 | map<string, bufferlist>& attrs, |
883 | std::string& actual_key, bool make_it) | |
9f95a23c | 884 | { |
f67539c2 TL |
885 | std::string secret_engine_str = cct->_conf->rgw_crypt_vault_secret_engine; |
886 | EngineParmMap secret_engine_parms; | |
887 | auto secret_engine { config_to_engine_and_parms( | |
888 | cct, "rgw_crypt_vault_secret_engine", | |
889 | secret_engine_str, secret_engine_parms) }; | |
9f95a23c TL |
890 | ldout(cct, 20) << "Vault authentication method: " << cct->_conf->rgw_crypt_vault_auth << dendl; |
891 | ldout(cct, 20) << "Vault Secrets Engine: " << secret_engine << dendl; | |
892 | ||
893 | if (RGW_SSE_KMS_VAULT_SE_KV == secret_engine){ | |
f67539c2 TL |
894 | std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); |
895 | KvSecretEngine engine(cct, std::move(secret_engine_parms)); | |
9f95a23c TL |
896 | return engine.get_key(key_id, actual_key); |
897 | } | |
898 | else if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){ | |
f67539c2 TL |
899 | TransitSecretEngine engine(cct, std::move(secret_engine_parms)); |
900 | std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); | |
901 | return make_it | |
902 | ? engine.make_actual_key(attrs, actual_key) | |
903 | : engine.reconstitute_actual_key(attrs, actual_key); | |
904 | } | |
905 | else { | |
906 | ldout(cct, 0) << "Missing or invalid secret engine" << dendl; | |
907 | return -EINVAL; | |
908 | } | |
909 | } | |
910 | ||
911 | ||
912 | static int make_actual_key_from_vault(CephContext *cct, | |
913 | map<string, bufferlist>& attrs, | |
914 | std::string& actual_key) | |
915 | { | |
916 | return get_actual_key_from_vault(cct, attrs, actual_key, true); | |
917 | } | |
918 | ||
919 | ||
920 | static int reconstitute_actual_key_from_vault(CephContext *cct, | |
921 | map<string, bufferlist>& attrs, | |
922 | std::string& actual_key) | |
923 | { | |
924 | return get_actual_key_from_vault(cct, attrs, actual_key, false); | |
925 | } | |
926 | ||
927 | ||
928 | static int get_actual_key_from_kmip(CephContext *cct, | |
929 | std::string_view key_id, | |
930 | std::string& actual_key) | |
931 | { | |
932 | std::string secret_engine = RGW_SSE_KMS_KMIP_SE_KV; | |
933 | ||
934 | if (RGW_SSE_KMS_KMIP_SE_KV == secret_engine){ | |
935 | KmipSecretEngine engine(cct); | |
9f95a23c TL |
936 | return engine.get_key(key_id, actual_key); |
937 | } | |
938 | else{ | |
939 | ldout(cct, 0) << "Missing or invalid secret engine" << dendl; | |
940 | return -EINVAL; | |
941 | } | |
942 | } | |
943 | ||
944 | ||
f67539c2 TL |
945 | int reconstitute_actual_key_from_kms(CephContext *cct, |
946 | map<string, bufferlist>& attrs, | |
9f95a23c TL |
947 | std::string& actual_key) |
948 | { | |
f67539c2 TL |
949 | std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID); |
950 | std::string kms_backend { cct->_conf->rgw_crypt_s3_kms_backend }; | |
9f95a23c | 951 | |
9f95a23c TL |
952 | ldout(cct, 20) << "Getting KMS encryption key for key " << key_id << dendl; |
953 | ldout(cct, 20) << "SSE-KMS backend is " << kms_backend << dendl; | |
954 | ||
f67539c2 | 955 | if (RGW_SSE_KMS_BACKEND_BARBICAN == kms_backend) { |
9f95a23c | 956 | return get_actual_key_from_barbican(cct, key_id, actual_key); |
f67539c2 | 957 | } |
9f95a23c | 958 | |
f67539c2 TL |
959 | if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend) { |
960 | return reconstitute_actual_key_from_vault(cct, attrs, actual_key); | |
961 | } | |
962 | ||
963 | if (RGW_SSE_KMS_BACKEND_KMIP == kms_backend) { | |
964 | return get_actual_key_from_kmip(cct, key_id, actual_key); | |
965 | } | |
9f95a23c | 966 | |
f67539c2 TL |
967 | if (RGW_SSE_KMS_BACKEND_TESTING == kms_backend) { |
968 | std::string key_selector = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYSEL); | |
9f95a23c | 969 | return get_actual_key_from_conf(cct, key_id, key_selector, actual_key); |
f67539c2 | 970 | } |
9f95a23c TL |
971 | |
972 | ldout(cct, 0) << "ERROR: Invalid rgw_crypt_s3_kms_backend: " << kms_backend << dendl; | |
973 | return -EINVAL; | |
974 | } | |
f67539c2 TL |
975 | |
976 | int make_actual_key_from_kms(CephContext *cct, | |
977 | map<string, bufferlist>& attrs, | |
978 | std::string& actual_key) | |
979 | { | |
980 | std::string kms_backend { cct->_conf->rgw_crypt_s3_kms_backend }; | |
981 | if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend) | |
982 | return make_actual_key_from_vault(cct, attrs, actual_key); | |
983 | return reconstitute_actual_key_from_kms(cct, attrs, actual_key); | |
984 | } |