]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_kms.cc
import ceph 16.2.6
[ceph.git] / ceph / src / rgw / rgw_kms.cc
CommitLineData
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
25using 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
38class ZeroPoolAllocator {
39private:
40 struct element {
41 struct element *next;
42 int size;
43 char data[4];
44 } *b;
45 size_t left;
46public:
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 }
91private:
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
98typedef rapidjson::GenericDocument<rapidjson::UTF8<>,
99 ZeroPoolAllocator,
100 rapidjson::CrtAllocator
101 > ZeroPoolDocument;
102typedef 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 */
109static 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 */
125static 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
140template<typename E, typename A = ZeroPoolAllocator>
141static inline void
142add_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
151template<typename E, typename A = ZeroPoolAllocator>
152static inline void
153add_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
160typedef std::map<std::string, std::string> EngineParmMap;
9f95a23c
TL
161
162class VaultSecretEngine: public SecretEngine {
163
164protected:
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
302public:
303
304 VaultSecretEngine(CephContext *cct) {
305 this->cct = cct;
306 }
9f95a23c
TL
307};
308
9f95a23c 309class TransitSecretEngine: public VaultSecretEngine {
f67539c2
TL
310public:
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
317private:
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
335public:
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
577class KvSecretEngine: public VaultSecretEngine {
578
579public:
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
636class KmipSecretEngine;
637class KmipGetTheKey {
638private:
639 CephContext *cct;
640 std::string work;
641 bool failed = false;
642 int ret;
643protected:
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
651KmipGetTheKey&
652KmipGetTheKey::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
671KmipGetTheKey&
672KmipGetTheKey::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
697int
698KmipGetTheKey::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
713class KmipSecretEngine: public SecretEngine {
714
715protected:
716 CephContext *cct;
717
718public:
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
736static 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
743static 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
786static 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
825static 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
845std::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 881static 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
912static 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
920static 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
928static 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
945int 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
976int 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}