]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_kms.cc
eec5d80daab136f1b140afeecfa13ead2cdd6714
[ceph.git] / ceph / src / rgw / rgw_kms.cc
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"
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"
21
22 #define dout_context g_ceph_context
23 #define dout_subsys ceph_subsys_rgw
24
25 using namespace std;
26 using namespace rgw;
27
28 #ifndef FORTEST_VIRTUAL
29 #define FORTEST_VIRTUAL /**/
30 #endif
31
32 /**
33 * Memory pool for use with rapidjson. This version
34 * carefully zeros out all memory before returning it to
35 * the system.
36 */
37 #define ALIGNTYPE double
38 #define MINCHUNKSIZE 4096
39 class ZeroPoolAllocator {
40 private:
41 struct element {
42 struct element *next;
43 int size;
44 char data[4];
45 } *b;
46 size_t left;
47 public:
48 static const bool kNeedFree { false };
49 ZeroPoolAllocator(){
50 b = 0;
51 left = 0;
52 }
53 ~ZeroPoolAllocator(){
54 element *p;
55 while ((p = b)) {
56 b = p->next;
57 memset(p->data, 0, p->size);
58 free(p);
59 }
60 }
61 void * Malloc(size_t size) {
62 void *r;
63 if (!size) return 0;
64 size = (size + sizeof(ALIGNTYPE)-1)&(-sizeof(ALIGNTYPE));
65 if (size > left) {
66 size_t ns { size };
67 if (ns < MINCHUNKSIZE) ns = MINCHUNKSIZE;
68 element *nw { (element *) malloc(sizeof *b + ns) };
69 if (!nw) {
70 // std::cerr << "out of memory" << std::endl;
71 return 0;
72 }
73 left = ns - sizeof *b;
74 nw->size = ns;
75 nw->next = b;
76 b = nw;
77 }
78 left -= size;
79 r = static_cast<void*>(b->data + left);
80 return r;
81 }
82 void* Realloc(void* p, size_t old, size_t nw) {
83 void *r = nullptr;
84 if (nw) r = malloc(nw);
85 if (nw > old) nw = old;
86 if (r && old) memcpy(r, p, nw);
87 return r;
88 }
89 static void Free(void *p) {
90 ceph_assert(0 == "Free should not be called");
91 }
92 private:
93 //! Copy constructor is not permitted.
94 ZeroPoolAllocator(const ZeroPoolAllocator& rhs) /* = delete */;
95 //! Copy assignment operator is not permitted.
96 ZeroPoolAllocator& operator=(const ZeroPoolAllocator& rhs) /* = delete */;
97 };
98
99 typedef rapidjson::GenericDocument<rapidjson::UTF8<>,
100 ZeroPoolAllocator,
101 rapidjson::CrtAllocator
102 > ZeroPoolDocument;
103 typedef rapidjson::GenericValue<rapidjson::UTF8<>, ZeroPoolAllocator> ZeroPoolValue;
104
105 /**
106 * Construct a full URL string by concatenating a "base" URL with another path,
107 * ensuring there is one and only one forward slash between them. If path is
108 * empty, the URL is not changed.
109 */
110 static void concat_url(std::string &url, std::string path) {
111 bool url_has_slash = !url.empty() && url.back() == '/';
112 if (!path.empty()) {
113 if (url_has_slash && path.front() == '/') {
114 url.pop_back();
115 } else if (!url_has_slash && path.front() != '/') {
116 url.push_back('/');
117 }
118 url.append(path);
119 }
120 }
121
122 /**
123 * Determine if a string (url) ends with a given suffix.
124 * Must deal with (ignore) trailing slashes.
125 */
126 static bool string_ends_maybe_slash(std::string_view hay,
127 std::string_view needle)
128 {
129 auto hay_len { hay.size() };
130 auto needle_len { needle.size() };
131 if (hay_len < needle_len) return false;
132 auto hay_suffix_start { hay.data() + (hay_len - needle_len) };
133 while (hay_len > needle_len && hay[hay_len-1] == '/') {
134 --hay_len;
135 --hay_suffix_start;
136 }
137 std::string_view hay_suffix { hay_suffix_start, needle_len };
138 return hay_suffix == needle;
139 }
140
141 template<typename E, typename A = ZeroPoolAllocator>
142 static inline void
143 add_name_val_to_obj(std::string &n, std::string &v, rapidjson::GenericValue<E,A> &d,
144 A &allocator)
145 {
146 rapidjson::GenericValue<E,A> name, val;
147 name.SetString(n.c_str(), n.length(), allocator);
148 val.SetString(v.c_str(), v.length(), allocator);
149 d.AddMember(name, val, allocator);
150 }
151
152 template<typename E, typename A = ZeroPoolAllocator>
153 static inline void
154 add_name_val_to_obj(std::string &n, bool v, rapidjson::GenericValue<E,A> &d,
155 A &allocator)
156 {
157 rapidjson::GenericValue<E,A> name, val;
158 name.SetString(n.c_str(), n.length(), allocator);
159 val.SetBool(v);
160 d.AddMember(name, val, allocator);
161 }
162
163 template<typename E, typename A = ZeroPoolAllocator>
164 static inline void
165 add_name_val_to_obj(const char *n, std::string &v, rapidjson::GenericValue<E,A> &d,
166 A &allocator)
167 {
168 std::string ns{n, strlen(n) };
169 add_name_val_to_obj(ns, v, d, allocator);
170 }
171
172 template<typename E, typename A = ZeroPoolAllocator>
173 static inline void
174 add_name_val_to_obj(const char *n, bool v, rapidjson::GenericValue<E,A> &d,
175 A &allocator)
176 {
177 std::string ns{n, strlen(n) };
178 add_name_val_to_obj(ns, v, d, allocator);
179 }
180
181 typedef std::map<std::string, std::string> EngineParmMap;
182
183
184 class SSEContext {
185 protected:
186 virtual ~SSEContext(){};
187 public:
188 virtual const std::string & backend() = 0;
189 virtual const std::string & addr() = 0;
190 virtual const std::string & auth() = 0;
191 virtual const std::string & k_namespace() = 0;
192 virtual const std::string & prefix() = 0;
193 virtual const std::string & secret_engine() = 0;
194 virtual const std::string & ssl_cacert() = 0;
195 virtual const std::string & ssl_clientcert() = 0;
196 virtual const std::string & ssl_clientkey() = 0;
197 virtual const std::string & token_file() = 0;
198 virtual const bool verify_ssl() = 0;
199 };
200
201 class VaultSecretEngine: public SecretEngine {
202
203 protected:
204 CephContext *cct;
205 SSEContext & kctx;
206
207 int load_token_from_file(const DoutPrefixProvider *dpp, std::string *vault_token)
208 {
209
210 int res = 0;
211 std::string token_file = kctx.token_file();
212 if (token_file.empty()) {
213 ldpp_dout(dpp, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl;
214 return -EINVAL;
215 }
216 ldpp_dout(dpp, 20) << "Vault token file: " << token_file << dendl;
217
218 struct stat token_st;
219 if (stat(token_file.c_str(), &token_st) != 0) {
220 ldpp_dout(dpp, 0) << "ERROR: Vault token file '" << token_file << "' not found " << dendl;
221 return -ENOENT;
222 }
223
224 if (token_st.st_mode & (S_IRWXG | S_IRWXO)) {
225 ldpp_dout(dpp, 0) << "ERROR: Vault token file '" << token_file << "' permissions are "
226 << "too open, it must not be accessible by other users" << dendl;
227 return -EACCES;
228 }
229
230 char buf[2048];
231 res = safe_read_file("", token_file.c_str(), buf, sizeof(buf));
232 if (res < 0) {
233 if (-EACCES == res) {
234 ldpp_dout(dpp, 0) << "ERROR: Permission denied reading Vault token file" << dendl;
235 } else {
236 ldpp_dout(dpp, 0) << "ERROR: Failed to read Vault token file with error " << res << dendl;
237 }
238 return res;
239 }
240 // drop trailing newlines
241 while (res && isspace(buf[res-1])) {
242 --res;
243 }
244 vault_token->assign(std::string{buf, static_cast<size_t>(res)});
245 memset(buf, 0, sizeof(buf));
246 ::ceph::crypto::zeroize_for_security(buf, sizeof(buf));
247 return res;
248 }
249
250 FORTEST_VIRTUAL
251 int send_request(const DoutPrefixProvider *dpp, const char *method, std::string_view infix,
252 std::string_view key_id,
253 const std::string& postdata,
254 bufferlist &secret_bl)
255 {
256 int res;
257 string vault_token = "";
258 if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == kctx.auth()){
259 ldpp_dout(dpp, 0) << "Loading Vault Token from filesystem" << dendl;
260 res = load_token_from_file(dpp, &vault_token);
261 if (res < 0){
262 return res;
263 }
264 }
265
266 std::string secret_url = kctx.addr();
267 if (secret_url.empty()) {
268 ldpp_dout(dpp, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl;
269 return -EINVAL;
270 }
271
272 concat_url(secret_url, kctx.prefix());
273 concat_url(secret_url, std::string(infix));
274 concat_url(secret_url, std::string(key_id));
275
276 RGWHTTPTransceiver secret_req(cct, method, secret_url, &secret_bl);
277
278 if (postdata.length()) {
279 secret_req.set_post_data(postdata);
280 secret_req.set_send_length(postdata.length());
281 }
282
283 secret_req.append_header("X-Vault-Token", vault_token);
284 if (!vault_token.empty()){
285 secret_req.append_header("X-Vault-Token", vault_token);
286 vault_token.replace(0, vault_token.length(), vault_token.length(), '\000');
287 }
288
289 string vault_namespace = kctx.k_namespace();
290 if (!vault_namespace.empty()){
291 ldpp_dout(dpp, 20) << "Vault Namespace: " << vault_namespace << dendl;
292 secret_req.append_header("X-Vault-Namespace", vault_namespace);
293 }
294
295 secret_req.set_verify_ssl(kctx.verify_ssl());
296
297 if (!kctx.ssl_cacert().empty()) {
298 secret_req.set_ca_path(kctx.ssl_cacert());
299 }
300
301 if (!kctx.ssl_clientcert().empty()) {
302 secret_req.set_client_cert(kctx.ssl_clientcert());
303 }
304 if (!kctx.ssl_clientkey().empty()) {
305 secret_req.set_client_key(kctx.ssl_clientkey());
306 }
307
308 res = secret_req.process(null_yield);
309 if (res < 0) {
310 ldpp_dout(dpp, 0) << "ERROR: Request to Vault failed with error " << res << dendl;
311 return res;
312 }
313
314 if (secret_req.get_http_status() ==
315 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
316 ldpp_dout(dpp, 0) << "ERROR: Vault request failed authorization" << dendl;
317 return -EACCES;
318 }
319
320 ldpp_dout(dpp, 20) << "Request to Vault returned " << res << " and HTTP status "
321 << secret_req.get_http_status() << dendl;
322
323 return res;
324 }
325
326 int send_request(const DoutPrefixProvider *dpp, std::string_view key_id, bufferlist &secret_bl)
327 {
328 return send_request(dpp, "GET", "", key_id, string{}, secret_bl);
329 }
330
331 int decode_secret(const DoutPrefixProvider *dpp, std::string encoded, std::string& actual_key){
332 try {
333 actual_key = from_base64(encoded);
334 } catch (std::exception&) {
335 ldpp_dout(dpp, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl;
336 return -EINVAL;
337 }
338 memset(encoded.data(), 0, encoded.length());
339 return 0;
340 }
341
342 public:
343
344 VaultSecretEngine(CephContext *_c, SSEContext & _k) : cct(_c), kctx(_k) {
345 }
346 };
347
348 class TransitSecretEngine: public VaultSecretEngine {
349 public:
350 int compat;
351 static const int COMPAT_NEW_ONLY = 0;
352 static const int COMPAT_OLD_AND_NEW = 1;
353 static const int COMPAT_ONLY_OLD = 2;
354 static const int COMPAT_UNSET = -1;
355
356 private:
357 EngineParmMap parms;
358
359 int get_key_version(std::string_view key_id, string& version)
360 {
361 size_t pos = 0;
362
363 pos = key_id.rfind("/");
364 if (pos != std::string_view::npos){
365 std::string_view token = key_id.substr(pos+1, key_id.length()-pos);
366 if (!token.empty() && token.find_first_not_of("0123456789") == std::string_view::npos){
367 version.assign(std::string(token));
368 return 0;
369 }
370 }
371 return -1;
372 }
373
374 public:
375 TransitSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms): VaultSecretEngine(cct, kctx), parms(parms) {
376 compat = COMPAT_UNSET;
377 for (auto& e: parms) {
378 if (e.first == "compat") {
379 if (e.second.empty()) {
380 compat = COMPAT_OLD_AND_NEW;
381 } else {
382 size_t ep;
383
384 compat = std::stoi(e.second, &ep);
385 if (ep != e.second.length()) {
386 lderr(cct) << "warning: vault transit secrets engine : compat="
387 << e.second << " trailing junk? (ignored)" << dendl;
388 }
389 }
390 continue;
391 }
392 lderr(cct) << "ERROR: vault transit secrets engine : parameter "
393 << e.first << "=" << e.second << " ignored" << dendl;
394 }
395 if (compat == COMPAT_UNSET) {
396 std::string_view v { kctx.prefix() };
397 if (string_ends_maybe_slash(v,"/export/encryption-key")) {
398 compat = COMPAT_ONLY_OLD;
399 } else {
400 compat = COMPAT_NEW_ONLY;
401 }
402 }
403 }
404
405 int get_key(const DoutPrefixProvider *dpp, std::string_view key_id, std::string& actual_key)
406 {
407 ZeroPoolDocument d;
408 ZeroPoolValue *v;
409 string version;
410 bufferlist secret_bl;
411
412 if (get_key_version(key_id, version) < 0){
413 ldpp_dout(dpp, 20) << "Missing or invalid key version" << dendl;
414 return -EINVAL;
415 }
416
417 int res = send_request(dpp, "GET", compat == COMPAT_ONLY_OLD ? "" : "/export/encryption-key",
418 key_id, string{}, secret_bl);
419 if (res < 0) {
420 return res;
421 }
422
423 ldpp_dout(dpp, 20) << "Parse response into JSON Object" << dendl;
424
425 secret_bl.append('\0');
426 rapidjson::StringStream isw(secret_bl.c_str());
427 d.ParseStream<>(isw);
428
429 if (d.HasParseError()) {
430 ldpp_dout(dpp, 0) << "ERROR: Failed to parse JSON response from Vault: "
431 << rapidjson::GetParseError_En(d.GetParseError()) << dendl;
432 return -EINVAL;
433 }
434 secret_bl.zero();
435
436 const char *elements[] = {"data", "keys", version.c_str()};
437 v = &d;
438 for (auto &elem: elements) {
439 if (!v->IsObject()) {
440 v = nullptr;
441 break;
442 }
443 auto endr { v->MemberEnd() };
444 auto itr { v->FindMember(elem) };
445 if (itr == endr) {
446 v = nullptr;
447 break;
448 }
449 v = &itr->value;
450 }
451 if (!v || !v->IsString()) {
452 ldpp_dout(dpp, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl;
453 return -EINVAL;
454 }
455 return decode_secret(dpp, v->GetString(), actual_key);
456 }
457
458 int make_actual_key(const DoutPrefixProvider *dpp, map<string, bufferlist>& attrs, std::string& actual_key)
459 {
460 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
461 if (compat == COMPAT_ONLY_OLD) return get_key(dpp, key_id, actual_key);
462 if (key_id.find("/") != std::string::npos) {
463 ldpp_dout(dpp, 0) << "sorry, can't allow / in keyid" << dendl;
464 return -EINVAL;
465 }
466 /*
467 data: {context }
468 post to prefix + /datakey/plaintext/ + key_id
469 jq: .data.plaintext -> key
470 jq: .data.ciphertext -> (to-be) named attribute
471 return decode_secret(json_obj, actual_key)
472 */
473 std::string context = get_str_attribute(attrs, RGW_ATTR_CRYPT_CONTEXT);
474 ZeroPoolDocument d { rapidjson::kObjectType };
475 auto &allocator { d.GetAllocator() };
476 bufferlist secret_bl;
477
478 add_name_val_to_obj("context", context, d, allocator);
479 rapidjson::StringBuffer buf;
480 rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
481 if (!d.Accept(writer)) {
482 ldpp_dout(dpp, 0) << "ERROR: can't make json for vault" << dendl;
483 return -EINVAL;
484 }
485 std::string post_data { buf.GetString() };
486
487 int res = send_request(dpp, "POST", "/datakey/plaintext/", key_id,
488 post_data, secret_bl);
489 if (res < 0) {
490 return res;
491 }
492
493 ldpp_dout(dpp, 20) << "Parse response into JSON Object" << dendl;
494
495 secret_bl.append('\0');
496 rapidjson::StringStream isw(secret_bl.c_str());
497 d.SetNull();
498 d.ParseStream<>(isw);
499
500 if (d.HasParseError()) {
501 ldpp_dout(dpp, 0) << "ERROR: Failed to parse JSON response from Vault: "
502 << rapidjson::GetParseError_En(d.GetParseError()) << dendl;
503 return -EINVAL;
504 }
505 secret_bl.zero();
506
507 if (!d.IsObject()) {
508 ldpp_dout(dpp, 0) << "ERROR: response from Vault is not an object" << dendl;
509 return -EINVAL;
510 }
511 {
512 auto data_itr { d.FindMember("data") };
513 if (data_itr == d.MemberEnd()) {
514 ldpp_dout(dpp, 0) << "ERROR: no .data in response from Vault" << dendl;
515 return -EINVAL;
516 }
517 auto ciphertext_itr { data_itr->value.FindMember("ciphertext") };
518 auto plaintext_itr { data_itr->value.FindMember("plaintext") };
519 if (ciphertext_itr == data_itr->value.MemberEnd()) {
520 ldpp_dout(dpp, 0) << "ERROR: no .data.ciphertext in response from Vault" << dendl;
521 return -EINVAL;
522 }
523 if (plaintext_itr == data_itr->value.MemberEnd()) {
524 ldpp_dout(dpp, 0) << "ERROR: no .data.plaintext in response from Vault" << dendl;
525 return -EINVAL;
526 }
527 auto &ciphertext_v { ciphertext_itr->value };
528 auto &plaintext_v { plaintext_itr->value };
529 if (!ciphertext_v.IsString()) {
530 ldpp_dout(dpp, 0) << "ERROR: .data.ciphertext not a string in response from Vault" << dendl;
531 return -EINVAL;
532 }
533 if (!plaintext_v.IsString()) {
534 ldpp_dout(dpp, 0) << "ERROR: .data.plaintext not a string in response from Vault" << dendl;
535 return -EINVAL;
536 }
537 set_attr(attrs, RGW_ATTR_CRYPT_DATAKEY, ciphertext_v.GetString());
538 return decode_secret(dpp, plaintext_v.GetString(), actual_key);
539 }
540 }
541
542 int reconstitute_actual_key(const DoutPrefixProvider *dpp, map<string, bufferlist>& attrs, std::string& actual_key)
543 {
544 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
545 std::string wrapped_key = get_str_attribute(attrs, RGW_ATTR_CRYPT_DATAKEY);
546 if (compat == COMPAT_ONLY_OLD || key_id.rfind("/") != std::string::npos) {
547 return get_key(dpp, key_id, actual_key);
548 }
549 /*
550 .data.ciphertext <- (to-be) named attribute
551 data: {context ciphertext}
552 post to prefix + /decrypt/ + key_id
553 jq: .data.plaintext
554 return decode_secret(json_obj, actual_key)
555 */
556 std::string context = get_str_attribute(attrs, RGW_ATTR_CRYPT_CONTEXT);
557 ZeroPoolDocument d { rapidjson::kObjectType };
558 auto &allocator { d.GetAllocator() };
559 bufferlist secret_bl;
560
561 add_name_val_to_obj("context", context, d, allocator);
562 add_name_val_to_obj("ciphertext", wrapped_key, d, allocator);
563 rapidjson::StringBuffer buf;
564 rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
565 if (!d.Accept(writer)) {
566 ldpp_dout(dpp, 0) << "ERROR: can't make json for vault" << dendl;
567 return -EINVAL;
568 }
569 std::string post_data { buf.GetString() };
570
571 int res = send_request(dpp, "POST", "/decrypt/", key_id,
572 post_data, secret_bl);
573 if (res < 0) {
574 return res;
575 }
576
577 ldpp_dout(dpp, 20) << "Parse response into JSON Object" << dendl;
578
579 secret_bl.append('\0');
580 rapidjson::StringStream isw(secret_bl.c_str());
581 d.SetNull();
582 d.ParseStream<>(isw);
583
584 if (d.HasParseError()) {
585 ldpp_dout(dpp, 0) << "ERROR: Failed to parse JSON response from Vault: "
586 << rapidjson::GetParseError_En(d.GetParseError()) << dendl;
587 return -EINVAL;
588 }
589 secret_bl.zero();
590
591 if (!d.IsObject()) {
592 ldpp_dout(dpp, 0) << "ERROR: response from Vault is not an object" << dendl;
593 return -EINVAL;
594 }
595 {
596 auto data_itr { d.FindMember("data") };
597 if (data_itr == d.MemberEnd()) {
598 ldpp_dout(dpp, 0) << "ERROR: no .data in response from Vault" << dendl;
599 return -EINVAL;
600 }
601 auto plaintext_itr { data_itr->value.FindMember("plaintext") };
602 if (plaintext_itr == data_itr->value.MemberEnd()) {
603 ldpp_dout(dpp, 0) << "ERROR: no .data.plaintext in response from Vault" << dendl;
604 return -EINVAL;
605 }
606 auto &plaintext_v { plaintext_itr->value };
607 if (!plaintext_v.IsString()) {
608 ldpp_dout(dpp, 0) << "ERROR: .data.plaintext not a string in response from Vault" << dendl;
609 return -EINVAL;
610 }
611 return decode_secret(dpp, plaintext_v.GetString(), actual_key);
612 }
613 }
614
615 int create_bucket_key(const DoutPrefixProvider *dpp, const std::string& key_name)
616 {
617 /*
618 .data.ciphertext <- (to-be) named attribute
619 data: {"type": "chacha20-poly1305", "derived": true}
620 post to prefix + key_name
621 empty output.
622 */
623 ZeroPoolDocument d { rapidjson::kObjectType };
624 auto &allocator { d.GetAllocator() };
625 bufferlist dummy_bl;
626 std::string chacha20_poly1305 { "chacha20-poly1305" };
627
628 add_name_val_to_obj("type", chacha20_poly1305, d, allocator);
629 add_name_val_to_obj("derived", true, d, allocator);
630 rapidjson::StringBuffer buf;
631 rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
632 if (!d.Accept(writer)) {
633 ldpp_dout(dpp, 0) << "ERROR: can't make json for vault" << dendl;
634 return -EINVAL;
635 }
636 std::string post_data { buf.GetString() };
637
638 int res = send_request(dpp, "POST", "/keys/", key_name,
639 post_data, dummy_bl);
640 if (res < 0) {
641 return res;
642 }
643 if (dummy_bl.length() != 0) {
644 ldpp_dout(dpp, 0) << "ERROR: unexpected response from Vault making a key: "
645 << dummy_bl
646 << dendl;
647 }
648 return 0;
649 }
650
651 int delete_bucket_key(const DoutPrefixProvider *dpp, const std::string& key_name)
652 {
653 /*
654 /keys/<keyname>/config
655 data: {"deletion_allowed": true}
656 post to prefix + key_name
657 empty output.
658 */
659 ZeroPoolDocument d { rapidjson::kObjectType };
660 auto &allocator { d.GetAllocator() };
661 bufferlist dummy_bl;
662 std::ostringstream path_temp;
663 path_temp << "/keys/";
664 path_temp << key_name;
665 std::string delete_path { path_temp.str() };
666 path_temp << "/config";
667 std::string config_path { path_temp.str() };
668
669 add_name_val_to_obj("deletion_allowed", true, d, allocator);
670 rapidjson::StringBuffer buf;
671 rapidjson::Writer<rapidjson::StringBuffer> writer(buf);
672 if (!d.Accept(writer)) {
673 ldpp_dout(dpp, 0) << "ERROR: can't make json for vault" << dendl;
674 return -EINVAL;
675 }
676 std::string post_data { buf.GetString() };
677
678 int res = send_request(dpp, "POST", "", config_path,
679 post_data, dummy_bl);
680 if (res < 0) {
681 return res;
682 }
683 if (dummy_bl.length() != 0) {
684 ldpp_dout(dpp, 0) << "ERROR: unexpected response from Vault marking key to delete: "
685 << dummy_bl
686 << dendl;
687 return -EINVAL;
688 }
689
690 res = send_request(dpp, "DELETE", "", delete_path,
691 string{}, dummy_bl);
692 if (res < 0) {
693 return res;
694 }
695 if (dummy_bl.length() != 0) {
696 ldpp_dout(dpp, 0) << "ERROR: unexpected response from Vault deleting key: "
697 << dummy_bl
698 << dendl;
699 return -EINVAL;
700 }
701 return 0;
702 }
703 };
704
705 class KvSecretEngine: public VaultSecretEngine {
706
707 public:
708
709 KvSecretEngine(CephContext *cct, SSEContext & kctx, EngineParmMap parms): VaultSecretEngine(cct, kctx){
710 if (!parms.empty()) {
711 lderr(cct) << "ERROR: vault kv secrets engine takes no parameters (ignoring them)" << dendl;
712 }
713 }
714
715 virtual ~KvSecretEngine(){}
716
717 int get_key(const DoutPrefixProvider *dpp, std::string_view key_id, std::string& actual_key){
718 ZeroPoolDocument d;
719 ZeroPoolValue *v;
720 bufferlist secret_bl;
721
722 int res = send_request(dpp, key_id, secret_bl);
723 if (res < 0) {
724 return res;
725 }
726
727 ldpp_dout(dpp, 20) << "Parse response into JSON Object" << dendl;
728
729 secret_bl.append('\0');
730 rapidjson::StringStream isw(secret_bl.c_str());
731 d.ParseStream<>(isw);
732
733 if (d.HasParseError()) {
734 ldpp_dout(dpp, 0) << "ERROR: Failed to parse JSON response from Vault: "
735 << rapidjson::GetParseError_En(d.GetParseError()) << dendl;
736 return -EINVAL;
737 }
738 secret_bl.zero();
739
740 static const char *elements[] = {"data", "data", "key"};
741 v = &d;
742 for (auto &elem: elements) {
743 if (!v->IsObject()) {
744 v = nullptr;
745 break;
746 }
747 auto endr { v->MemberEnd() };
748 auto itr { v->FindMember(elem) };
749 if (itr == endr) {
750 v = nullptr;
751 break;
752 }
753 v = &itr->value;
754 }
755 if (!v || !v->IsString()) {
756 ldpp_dout(dpp, 0) << "ERROR: Key not found in JSON response from Vault using KV Engine" << dendl;
757 return -EINVAL;
758 }
759 return decode_secret(dpp, v->GetString(), actual_key);
760 }
761
762 };
763
764 class KmipSecretEngine;
765 class KmipGetTheKey {
766 private:
767 CephContext *cct;
768 std::string work;
769 bool failed = false;
770 int ret;
771 protected:
772 KmipGetTheKey(CephContext *cct) : cct(cct) {}
773 KmipGetTheKey& keyid_to_keyname(std::string_view key_id);
774 KmipGetTheKey& get_uniqueid_for_keyname();
775 int get_key_for_uniqueid(std::string &);
776 friend KmipSecretEngine;
777 };
778
779 KmipGetTheKey&
780 KmipGetTheKey::keyid_to_keyname(std::string_view key_id)
781 {
782 work = cct->_conf->rgw_crypt_kmip_kms_key_template;
783 std::string keyword = "$keyid";
784 std::string replacement = std::string(key_id);
785 size_t pos = 0;
786 if (work.length() == 0) {
787 work = std::move(replacement);
788 } else {
789 while (pos < work.length()) {
790 pos = work.find(keyword, pos);
791 if (pos == std::string::npos) break;
792 work.replace(pos, keyword.length(), replacement);
793 pos += key_id.length();
794 }
795 }
796 return *this;
797 }
798
799 KmipGetTheKey&
800 KmipGetTheKey::get_uniqueid_for_keyname()
801 {
802 RGWKMIPTransceiver secret_req(cct, RGWKMIPTransceiver::LOCATE);
803
804 secret_req.name = work.data();
805 ret = secret_req.process(null_yield);
806 if (ret < 0) {
807 failed = true;
808 } else if (!secret_req.outlist->string_count) {
809 ret = -ENOENT;
810 lderr(cct) << "error: locate returned no results for "
811 << secret_req.name << dendl;
812 failed = true;
813 } else if (secret_req.outlist->string_count != 1) {
814 ret = -EINVAL;
815 lderr(cct) << "error: locate found "
816 << secret_req.outlist->string_count
817 << " results for " << secret_req.name << dendl;
818 failed = true;
819 } else {
820 work = std::string(secret_req.outlist->strings[0]);
821 }
822 return *this;
823 }
824
825 int
826 KmipGetTheKey::get_key_for_uniqueid(std::string& actual_key)
827 {
828 if (failed) return ret;
829 RGWKMIPTransceiver secret_req(cct, RGWKMIPTransceiver::GET);
830 secret_req.unique_id = work.data();
831 ret = secret_req.process(null_yield);
832 if (ret < 0) {
833 failed = true;
834 } else {
835 actual_key = std::string((char*)(secret_req.outkey->data),
836 secret_req.outkey->keylen);
837 }
838 return ret;
839 }
840
841 class KmipSecretEngine: public SecretEngine {
842
843 protected:
844 CephContext *cct;
845
846 public:
847
848 KmipSecretEngine(CephContext *cct) {
849 this->cct = cct;
850 }
851
852 int get_key(const DoutPrefixProvider *dpp, std::string_view key_id, std::string& actual_key)
853 {
854 int r;
855 r = KmipGetTheKey{cct}
856 .keyid_to_keyname(key_id)
857 .get_uniqueid_for_keyname()
858 .get_key_for_uniqueid(actual_key);
859 return r;
860 }
861 };
862
863 static int get_actual_key_from_conf(const DoutPrefixProvider* dpp,
864 CephContext *cct,
865 std::string_view key_id,
866 std::string_view key_selector,
867 std::string& actual_key)
868 {
869 int res = 0;
870
871 static map<string,string> str_map = get_str_map(
872 cct->_conf->rgw_crypt_s3_kms_encryption_keys);
873
874 map<string, string>::iterator it = str_map.find(std::string(key_id));
875 if (it == str_map.end())
876 return -EINVAL;
877
878 std::string master_key;
879 try {
880 master_key = from_base64((*it).second);
881 } catch (std::exception&) {
882 ldpp_dout(dpp, 5) << "ERROR: get_actual_key_from_conf invalid encryption key id "
883 << "which contains character that is not base64 encoded."
884 << dendl;
885 return -EINVAL;
886 }
887
888 if (master_key.length() == AES_256_KEYSIZE) {
889 uint8_t _actual_key[AES_256_KEYSIZE];
890 if (AES_256_ECB_encrypt(dpp, cct,
891 reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE,
892 reinterpret_cast<const uint8_t*>(key_selector.data()),
893 _actual_key, AES_256_KEYSIZE)) {
894 actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE);
895 } else {
896 res = -EIO;
897 }
898 ::ceph::crypto::zeroize_for_security(_actual_key, sizeof(_actual_key));
899 } else {
900 ldpp_dout(dpp, 20) << "Wrong size for key=" << key_id << dendl;
901 res = -EIO;
902 }
903
904 return res;
905 }
906
907 static int request_key_from_barbican(const DoutPrefixProvider *dpp,
908 CephContext *cct,
909 std::string_view key_id,
910 const std::string& barbican_token,
911 std::string& actual_key) {
912 int res;
913
914 std::string secret_url = cct->_conf->rgw_barbican_url;
915 if (secret_url.empty()) {
916 ldpp_dout(dpp, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl;
917 return -EINVAL;
918 }
919 concat_url(secret_url, "/v1/secrets/");
920 concat_url(secret_url, std::string(key_id));
921
922 bufferlist secret_bl;
923 RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
924 secret_req.append_header("Accept", "application/octet-stream");
925 secret_req.append_header("X-Auth-Token", barbican_token);
926
927 res = secret_req.process(null_yield);
928 if (res < 0) {
929 return res;
930 }
931 if (secret_req.get_http_status() ==
932 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
933 return -EACCES;
934 }
935
936 if (secret_req.get_http_status() >=200 &&
937 secret_req.get_http_status() < 300 &&
938 secret_bl.length() == AES_256_KEYSIZE) {
939 actual_key.assign(secret_bl.c_str(), secret_bl.length());
940 secret_bl.zero();
941 } else {
942 res = -EACCES;
943 }
944 return res;
945 }
946
947 static int get_actual_key_from_barbican(const DoutPrefixProvider *dpp,
948 CephContext *cct,
949 std::string_view key_id,
950 std::string& actual_key)
951 {
952 int res = 0;
953 std::string token;
954
955 if (rgw::keystone::Service::get_keystone_barbican_token(dpp, cct, token) < 0) {
956 ldpp_dout(dpp, 5) << "Failed to retrieve token for Barbican" << dendl;
957 return -EINVAL;
958 }
959
960 res = request_key_from_barbican(dpp, cct, key_id, token, actual_key);
961 if (res != 0) {
962 ldpp_dout(dpp, 5) << "Failed to retrieve secret from Barbican:" << key_id << dendl;
963 }
964 return res;
965 }
966
967
968 std::string config_to_engine_and_parms(CephContext *cct,
969 const char* which,
970 std::string& secret_engine_str,
971 EngineParmMap& secret_engine_parms)
972 {
973 std::ostringstream oss;
974 std::vector<std::string> secret_engine_v;
975 std::string secret_engine;
976
977 get_str_vec(secret_engine_str, " ", secret_engine_v);
978
979 cct->_conf.early_expand_meta(secret_engine_str, &oss);
980 auto meta_errors {oss.str()};
981 if (meta_errors.length()) {
982 meta_errors.erase(meta_errors.find_last_not_of("\n")+1);
983 lderr(cct) << "ERROR: while expanding " << which << ": "
984 << meta_errors << dendl;
985 }
986 for (auto& e: secret_engine_v) {
987 if (!secret_engine.length()) {
988 secret_engine = std::move(e);
989 continue;
990 }
991 auto p { e.find('=') };
992 if (p == std::string::npos) {
993 secret_engine_parms.emplace(std::move(e), "");
994 continue;
995 }
996 std::string key{ e.substr(0,p) };
997 std::string val{ e.substr(p+1) };
998 secret_engine_parms.emplace(std::move(key), std::move(val));
999 }
1000 return secret_engine;
1001 }
1002
1003
1004 static int get_actual_key_from_vault(const DoutPrefixProvider *dpp,
1005 CephContext *cct,
1006 SSEContext & kctx,
1007 map<string, bufferlist>& attrs,
1008 std::string& actual_key, bool make_it)
1009 {
1010 std::string secret_engine_str = kctx.secret_engine();
1011 EngineParmMap secret_engine_parms;
1012 auto secret_engine { config_to_engine_and_parms(
1013 cct, "rgw_crypt_vault_secret_engine",
1014 secret_engine_str, secret_engine_parms) };
1015 ldpp_dout(dpp, 20) << "Vault authentication method: " << kctx.auth() << dendl;
1016 ldpp_dout(dpp, 20) << "Vault Secrets Engine: " << secret_engine << dendl;
1017
1018 if (RGW_SSE_KMS_VAULT_SE_KV == secret_engine){
1019 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
1020 KvSecretEngine engine(cct, kctx, std::move(secret_engine_parms));
1021 return engine.get_key(dpp, key_id, actual_key);
1022 }
1023 else if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){
1024 TransitSecretEngine engine(cct, kctx, std::move(secret_engine_parms));
1025 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
1026 return make_it
1027 ? engine.make_actual_key(dpp, attrs, actual_key)
1028 : engine.reconstitute_actual_key(dpp, attrs, actual_key);
1029 }
1030 else {
1031 ldpp_dout(dpp, 0) << "Missing or invalid secret engine" << dendl;
1032 return -EINVAL;
1033 }
1034 }
1035
1036
1037 static int make_actual_key_from_vault(const DoutPrefixProvider *dpp,
1038 CephContext *cct,
1039 SSEContext & kctx,
1040 map<string, bufferlist>& attrs,
1041 std::string& actual_key)
1042 {
1043 return get_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key, true);
1044 }
1045
1046
1047 static int reconstitute_actual_key_from_vault(const DoutPrefixProvider *dpp,
1048 CephContext *cct,
1049 SSEContext & kctx,
1050 map<string, bufferlist>& attrs,
1051 std::string& actual_key)
1052 {
1053 return get_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key, false);
1054 }
1055
1056
1057 static int get_actual_key_from_kmip(const DoutPrefixProvider *dpp,
1058 CephContext *cct,
1059 std::string_view key_id,
1060 std::string& actual_key)
1061 {
1062 std::string secret_engine = RGW_SSE_KMS_KMIP_SE_KV;
1063
1064 if (RGW_SSE_KMS_KMIP_SE_KV == secret_engine){
1065 KmipSecretEngine engine(cct);
1066 return engine.get_key(dpp, key_id, actual_key);
1067 }
1068 else{
1069 ldpp_dout(dpp, 0) << "Missing or invalid secret engine" << dendl;
1070 return -EINVAL;
1071 }
1072 }
1073 class KMSContext : public SSEContext {
1074 CephContext *cct;
1075 public:
1076 KMSContext(CephContext*_cct) : cct{_cct} {};
1077 ~KMSContext() override {};
1078 const std::string & backend() override {
1079 return cct->_conf->rgw_crypt_s3_kms_backend;
1080 };
1081 const std::string & addr() override {
1082 return cct->_conf->rgw_crypt_vault_addr;
1083 };
1084 const std::string & auth() override {
1085 return cct->_conf->rgw_crypt_vault_auth;
1086 };
1087 const std::string & k_namespace() override {
1088 return cct->_conf->rgw_crypt_vault_namespace;
1089 };
1090 const std::string & prefix() override {
1091 return cct->_conf->rgw_crypt_vault_prefix;
1092 };
1093 const std::string & secret_engine() override {
1094 return cct->_conf->rgw_crypt_vault_secret_engine;
1095 };
1096 const std::string & ssl_cacert() override {
1097 return cct->_conf->rgw_crypt_vault_ssl_cacert;
1098 };
1099 const std::string & ssl_clientcert() override {
1100 return cct->_conf->rgw_crypt_vault_ssl_clientcert;
1101 };
1102 const std::string & ssl_clientkey() override {
1103 return cct->_conf->rgw_crypt_vault_ssl_clientkey;
1104 };
1105 const std::string & token_file() override {
1106 return cct->_conf->rgw_crypt_vault_token_file;
1107 };
1108 const bool verify_ssl() override {
1109 return cct->_conf->rgw_crypt_vault_verify_ssl;
1110 };
1111 };
1112
1113 class SseS3Context : public SSEContext {
1114 CephContext *cct;
1115 public:
1116 static const std::string sse_s3_secret_engine;
1117 SseS3Context(CephContext*_cct) : cct{_cct} {};
1118 ~SseS3Context(){};
1119 const std::string & backend() override {
1120 return cct->_conf->rgw_crypt_sse_s3_backend;
1121 };
1122 const std::string & addr() override {
1123 return cct->_conf->rgw_crypt_sse_s3_vault_addr;
1124 };
1125 const std::string & auth() override {
1126 return cct->_conf->rgw_crypt_sse_s3_vault_auth;
1127 };
1128 const std::string & k_namespace() override {
1129 return cct->_conf->rgw_crypt_sse_s3_vault_namespace;
1130 };
1131 const std::string & prefix() override {
1132 return cct->_conf->rgw_crypt_sse_s3_vault_prefix;
1133 };
1134 const std::string & secret_engine() override {
1135 return cct->_conf->rgw_crypt_sse_s3_vault_secret_engine;
1136 };
1137 const std::string & ssl_cacert() override {
1138 return cct->_conf->rgw_crypt_sse_s3_vault_ssl_cacert;
1139 };
1140 const std::string & ssl_clientcert() override {
1141 return cct->_conf->rgw_crypt_sse_s3_vault_ssl_clientcert;
1142 };
1143 const std::string & ssl_clientkey() override {
1144 return cct->_conf->rgw_crypt_sse_s3_vault_ssl_clientkey;
1145 };
1146 const std::string & token_file() override {
1147 return cct->_conf->rgw_crypt_sse_s3_vault_token_file;
1148 };
1149 const bool verify_ssl() override {
1150 return cct->_conf->rgw_crypt_sse_s3_vault_verify_ssl;
1151 };
1152 };
1153
1154 int reconstitute_actual_key_from_kms(const DoutPrefixProvider *dpp, CephContext *cct,
1155 map<string, bufferlist>& attrs,
1156 std::string& actual_key)
1157 {
1158 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
1159 KMSContext kctx { cct };
1160 const std::string &kms_backend { kctx.backend() };
1161
1162 ldpp_dout(dpp, 20) << "Getting KMS encryption key for key " << key_id << dendl;
1163 ldpp_dout(dpp, 20) << "SSE-KMS backend is " << kms_backend << dendl;
1164
1165 if (RGW_SSE_KMS_BACKEND_BARBICAN == kms_backend) {
1166 return get_actual_key_from_barbican(dpp, cct, key_id, actual_key);
1167 }
1168
1169 if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend) {
1170 return reconstitute_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key);
1171 }
1172
1173 if (RGW_SSE_KMS_BACKEND_KMIP == kms_backend) {
1174 return get_actual_key_from_kmip(dpp, cct, key_id, actual_key);
1175 }
1176
1177 if (RGW_SSE_KMS_BACKEND_TESTING == kms_backend) {
1178 std::string key_selector = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYSEL);
1179 return get_actual_key_from_conf(dpp, cct, key_id, key_selector, actual_key);
1180 }
1181
1182 ldpp_dout(dpp, 0) << "ERROR: Invalid rgw_crypt_s3_kms_backend: " << kms_backend << dendl;
1183 return -EINVAL;
1184 }
1185
1186 int make_actual_key_from_kms(const DoutPrefixProvider *dpp, CephContext *cct,
1187 map<string, bufferlist>& attrs,
1188 std::string& actual_key)
1189 {
1190 KMSContext kctx { cct };
1191 const std::string &kms_backend { kctx.backend() };
1192 if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend)
1193 return make_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key);
1194 return reconstitute_actual_key_from_kms(dpp, cct, attrs, actual_key);
1195 }
1196
1197 int reconstitute_actual_key_from_sse_s3(const DoutPrefixProvider *dpp,
1198 CephContext *cct,
1199 map<string, bufferlist>& attrs,
1200 std::string& actual_key)
1201 {
1202 std::string key_id = get_str_attribute(attrs, RGW_ATTR_CRYPT_KEYID);
1203 SseS3Context kctx { cct };
1204 const std::string &kms_backend { kctx.backend() };
1205
1206 ldpp_dout(dpp, 20) << "Getting SSE-S3 encryption key for key " << key_id << dendl;
1207 ldpp_dout(dpp, 20) << "SSE-KMS backend is " << kms_backend << dendl;
1208
1209 if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend) {
1210 return reconstitute_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key);
1211 }
1212
1213 ldpp_dout(dpp, 0) << "ERROR: Invalid rgw_crypt_sse_s3_backend: " << kms_backend << dendl;
1214 return -EINVAL;
1215 }
1216
1217 int make_actual_key_from_sse_s3(const DoutPrefixProvider *dpp,
1218 CephContext *cct,
1219 map<string, bufferlist>& attrs,
1220 std::string& actual_key)
1221 {
1222 SseS3Context kctx { cct };
1223 const std::string kms_backend { kctx.backend() };
1224 if (RGW_SSE_KMS_BACKEND_VAULT != kms_backend) {
1225 ldpp_dout(dpp, 0) << "ERROR: Unsupported rgw_crypt_sse_s3_backend: " << kms_backend << dendl;
1226 return -EINVAL;
1227 }
1228 return make_actual_key_from_vault(dpp, cct, kctx, attrs, actual_key);
1229 }
1230
1231
1232 int create_sse_s3_bucket_key(const DoutPrefixProvider *dpp,
1233 CephContext *cct,
1234 const std::string& bucket_key)
1235 {
1236 SseS3Context kctx { cct };
1237
1238 const std::string kms_backend { kctx.backend() };
1239 if (RGW_SSE_KMS_BACKEND_VAULT != kms_backend) {
1240 ldpp_dout(dpp, 0) << "ERROR: Unsupported rgw_crypt_sse_s3_backend: " << kms_backend << dendl;
1241 return -EINVAL;
1242 }
1243
1244 std::string secret_engine_str = kctx.secret_engine();
1245 EngineParmMap secret_engine_parms;
1246 auto secret_engine { config_to_engine_and_parms(
1247 cct, "rgw_crypt_sse_s3_vault_secret_engine",
1248 secret_engine_str, secret_engine_parms) };
1249 if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){
1250 TransitSecretEngine engine(cct, kctx, std::move(secret_engine_parms));
1251 return engine.create_bucket_key(dpp, bucket_key);
1252 }
1253 else {
1254 ldpp_dout(dpp, 0) << "Missing or invalid secret engine" << dendl;
1255 return -EINVAL;
1256 }
1257 }
1258
1259 int remove_sse_s3_bucket_key(const DoutPrefixProvider *dpp,
1260 CephContext *cct,
1261 const std::string& bucket_key)
1262 {
1263 SseS3Context kctx { cct };
1264 std::string secret_engine_str = kctx.secret_engine();
1265 EngineParmMap secret_engine_parms;
1266 auto secret_engine { config_to_engine_and_parms(
1267 cct, "rgw_crypt_sse_s3_vault_secret_engine",
1268 secret_engine_str, secret_engine_parms) };
1269 if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){
1270 TransitSecretEngine engine(cct, kctx, std::move(secret_engine_parms));
1271 return engine.delete_bucket_key(dpp, bucket_key);
1272 }
1273 else {
1274 ldpp_dout(dpp, 0) << "Missing or invalid secret engine" << dendl;
1275 return -EINVAL;
1276 }
1277 }