1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
5 * Server-side encryption integrations with Key Management Systems (SSE-KMS)
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"
22 #define dout_context g_ceph_context
23 #define dout_subsys ceph_subsys_rgw
28 #ifndef FORTEST_VIRTUAL
29 #define FORTEST_VIRTUAL /**/
33 * Memory pool for use with rapidjson. This version
34 * carefully zeros out all memory before returning it to
37 #define ALIGNTYPE double
38 #define MINCHUNKSIZE 4096
39 class ZeroPoolAllocator
{
48 static const bool kNeedFree
{ false };
57 memset(p
->data
, 0, p
->size
);
61 void * Malloc(size_t size
) {
64 size
= (size
+ sizeof(ALIGNTYPE
)-1)&(-sizeof(ALIGNTYPE
));
67 if (ns
< MINCHUNKSIZE
) ns
= MINCHUNKSIZE
;
68 element
*nw
{ (element
*) malloc(sizeof *b
+ ns
) };
70 // std::cerr << "out of memory" << std::endl;
73 left
= ns
- sizeof *b
;
79 r
= static_cast<void*>(b
->data
+ left
);
82 void* Realloc(void* p
, size_t old
, size_t nw
) {
84 if (nw
) r
= malloc(nw
);
85 if (nw
> old
) nw
= old
;
86 if (r
&& old
) memcpy(r
, p
, nw
);
89 static void Free(void *p
) {
90 ceph_assert(0 == "Free should not be called");
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 */;
99 typedef rapidjson::GenericDocument
<rapidjson::UTF8
<>,
101 rapidjson::CrtAllocator
103 typedef rapidjson::GenericValue
<rapidjson::UTF8
<>, ZeroPoolAllocator
> ZeroPoolValue
;
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.
110 static void concat_url(std::string
&url
, std::string path
) {
111 bool url_has_slash
= !url
.empty() && url
.back() == '/';
113 if (url_has_slash
&& path
.front() == '/') {
115 } else if (!url_has_slash
&& path
.front() != '/') {
123 * Determine if a string (url) ends with a given suffix.
124 * Must deal with (ignore) trailing slashes.
126 static bool string_ends_maybe_slash(std::string_view hay
,
127 std::string_view needle
)
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] == '/') {
137 std::string_view hay_suffix
{ hay_suffix_start
, needle_len
};
138 return hay_suffix
== needle
;
141 template<typename E
, typename A
= ZeroPoolAllocator
>
143 add_name_val_to_obj(std::string
&n
, std::string
&v
, rapidjson::GenericValue
<E
,A
> &d
,
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
);
152 template<typename E
, typename A
= ZeroPoolAllocator
>
154 add_name_val_to_obj(std::string
&n
, bool v
, rapidjson::GenericValue
<E
,A
> &d
,
157 rapidjson::GenericValue
<E
,A
> name
, val
;
158 name
.SetString(n
.c_str(), n
.length(), allocator
);
160 d
.AddMember(name
, val
, allocator
);
163 template<typename E
, typename A
= ZeroPoolAllocator
>
165 add_name_val_to_obj(const char *n
, std::string
&v
, rapidjson::GenericValue
<E
,A
> &d
,
168 std::string ns
{n
, strlen(n
) };
169 add_name_val_to_obj(ns
, v
, d
, allocator
);
172 template<typename E
, typename A
= ZeroPoolAllocator
>
174 add_name_val_to_obj(const char *n
, bool v
, rapidjson::GenericValue
<E
,A
> &d
,
177 std::string ns
{n
, strlen(n
) };
178 add_name_val_to_obj(ns
, v
, d
, allocator
);
181 typedef std::map
<std::string
, std::string
> EngineParmMap
;
186 virtual ~SSEContext(){};
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;
201 class VaultSecretEngine
: public SecretEngine
{
207 int load_token_from_file(const DoutPrefixProvider
*dpp
, std::string
*vault_token
)
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
;
216 ldpp_dout(dpp
, 20) << "Vault token file: " << token_file
<< dendl
;
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
;
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
;
231 res
= safe_read_file("", token_file
.c_str(), buf
, sizeof(buf
));
233 if (-EACCES
== res
) {
234 ldpp_dout(dpp
, 0) << "ERROR: Permission denied reading Vault token file" << dendl
;
236 ldpp_dout(dpp
, 0) << "ERROR: Failed to read Vault token file with error " << res
<< dendl
;
240 // drop trailing newlines
241 while (res
&& isspace(buf
[res
-1])) {
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
));
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
)
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
);
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
;
272 concat_url(secret_url
, kctx
.prefix());
273 concat_url(secret_url
, std::string(infix
));
274 concat_url(secret_url
, std::string(key_id
));
276 RGWHTTPTransceiver
secret_req(cct
, method
, secret_url
, &secret_bl
);
278 if (postdata
.length()) {
279 secret_req
.set_post_data(postdata
);
280 secret_req
.set_send_length(postdata
.length());
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');
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
);
295 secret_req
.set_verify_ssl(kctx
.verify_ssl());
297 if (!kctx
.ssl_cacert().empty()) {
298 secret_req
.set_ca_path(kctx
.ssl_cacert());
301 if (!kctx
.ssl_clientcert().empty()) {
302 secret_req
.set_client_cert(kctx
.ssl_clientcert());
304 if (!kctx
.ssl_clientkey().empty()) {
305 secret_req
.set_client_key(kctx
.ssl_clientkey());
308 res
= secret_req
.process(null_yield
);
310 ldpp_dout(dpp
, 0) << "ERROR: Request to Vault failed with error " << res
<< dendl
;
314 if (secret_req
.get_http_status() ==
315 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED
) {
316 ldpp_dout(dpp
, 0) << "ERROR: Vault request failed authorization" << dendl
;
320 ldpp_dout(dpp
, 20) << "Request to Vault returned " << res
<< " and HTTP status "
321 << secret_req
.get_http_status() << dendl
;
326 int send_request(const DoutPrefixProvider
*dpp
, std::string_view key_id
, bufferlist
&secret_bl
)
328 return send_request(dpp
, "GET", "", key_id
, string
{}, secret_bl
);
331 int decode_secret(const DoutPrefixProvider
*dpp
, std::string encoded
, std::string
& actual_key
){
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
;
338 memset(encoded
.data(), 0, encoded
.length());
344 VaultSecretEngine(CephContext
*_c
, SSEContext
& _k
) : cct(_c
), kctx(_k
) {
348 class TransitSecretEngine
: public VaultSecretEngine
{
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;
359 int get_key_version(std::string_view key_id
, string
& version
)
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
));
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
;
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
;
392 lderr(cct
) << "ERROR: vault transit secrets engine : parameter "
393 << e
.first
<< "=" << e
.second
<< " ignored" << dendl
;
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
;
400 compat
= COMPAT_NEW_ONLY
;
405 int get_key(const DoutPrefixProvider
*dpp
, std::string_view key_id
, std::string
& actual_key
)
410 bufferlist secret_bl
;
412 if (get_key_version(key_id
, version
) < 0){
413 ldpp_dout(dpp
, 20) << "Missing or invalid key version" << dendl
;
417 int res
= send_request(dpp
, "GET", compat
== COMPAT_ONLY_OLD
? "" : "/export/encryption-key",
418 key_id
, string
{}, secret_bl
);
423 ldpp_dout(dpp
, 20) << "Parse response into JSON Object" << dendl
;
425 secret_bl
.append('\0');
426 rapidjson::StringStream
isw(secret_bl
.c_str());
427 d
.ParseStream
<>(isw
);
429 if (d
.HasParseError()) {
430 ldpp_dout(dpp
, 0) << "ERROR: Failed to parse JSON response from Vault: "
431 << rapidjson::GetParseError_En(d
.GetParseError()) << dendl
;
436 const char *elements
[] = {"data", "keys", version
.c_str()};
438 for (auto &elem
: elements
) {
439 if (!v
->IsObject()) {
443 auto endr
{ v
->MemberEnd() };
444 auto itr
{ v
->FindMember(elem
) };
451 if (!v
|| !v
->IsString()) {
452 ldpp_dout(dpp
, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl
;
455 return decode_secret(dpp
, v
->GetString(), actual_key
);
458 int make_actual_key(const DoutPrefixProvider
*dpp
, map
<string
, bufferlist
>& attrs
, std::string
& actual_key
)
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
;
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)
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
;
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
;
485 std::string post_data
{ buf
.GetString() };
487 int res
= send_request(dpp
, "POST", "/datakey/plaintext/", key_id
,
488 post_data
, secret_bl
);
493 ldpp_dout(dpp
, 20) << "Parse response into JSON Object" << dendl
;
495 secret_bl
.append('\0');
496 rapidjson::StringStream
isw(secret_bl
.c_str());
498 d
.ParseStream
<>(isw
);
500 if (d
.HasParseError()) {
501 ldpp_dout(dpp
, 0) << "ERROR: Failed to parse JSON response from Vault: "
502 << rapidjson::GetParseError_En(d
.GetParseError()) << dendl
;
508 ldpp_dout(dpp
, 0) << "ERROR: response from Vault is not an object" << dendl
;
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
;
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
;
523 if (plaintext_itr
== data_itr
->value
.MemberEnd()) {
524 ldpp_dout(dpp
, 0) << "ERROR: no .data.plaintext in response from Vault" << dendl
;
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
;
533 if (!plaintext_v
.IsString()) {
534 ldpp_dout(dpp
, 0) << "ERROR: .data.plaintext not a string in response from Vault" << dendl
;
537 set_attr(attrs
, RGW_ATTR_CRYPT_DATAKEY
, ciphertext_v
.GetString());
538 return decode_secret(dpp
, plaintext_v
.GetString(), actual_key
);
542 int reconstitute_actual_key(const DoutPrefixProvider
*dpp
, map
<string
, bufferlist
>& attrs
, std::string
& actual_key
)
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
);
550 .data.ciphertext <- (to-be) named attribute
551 data: {context ciphertext}
552 post to prefix + /decrypt/ + key_id
554 return decode_secret(json_obj, actual_key)
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
;
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
;
569 std::string post_data
{ buf
.GetString() };
571 int res
= send_request(dpp
, "POST", "/decrypt/", key_id
,
572 post_data
, secret_bl
);
577 ldpp_dout(dpp
, 20) << "Parse response into JSON Object" << dendl
;
579 secret_bl
.append('\0');
580 rapidjson::StringStream
isw(secret_bl
.c_str());
582 d
.ParseStream
<>(isw
);
584 if (d
.HasParseError()) {
585 ldpp_dout(dpp
, 0) << "ERROR: Failed to parse JSON response from Vault: "
586 << rapidjson::GetParseError_En(d
.GetParseError()) << dendl
;
592 ldpp_dout(dpp
, 0) << "ERROR: response from Vault is not an object" << dendl
;
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
;
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
;
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
;
611 return decode_secret(dpp
, plaintext_v
.GetString(), actual_key
);
615 int create_bucket_key(const DoutPrefixProvider
*dpp
, const std::string
& key_name
)
618 .data.ciphertext <- (to-be) named attribute
619 data: {"type": "chacha20-poly1305", "derived": true}
620 post to prefix + key_name
623 ZeroPoolDocument d
{ rapidjson::kObjectType
};
624 auto &allocator
{ d
.GetAllocator() };
626 std::string chacha20_poly1305
{ "chacha20-poly1305" };
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
;
636 std::string post_data
{ buf
.GetString() };
638 int res
= send_request(dpp
, "POST", "/keys/", key_name
,
639 post_data
, dummy_bl
);
643 if (dummy_bl
.length() != 0) {
644 ldpp_dout(dpp
, 0) << "ERROR: unexpected response from Vault making a key: "
651 int delete_bucket_key(const DoutPrefixProvider
*dpp
, const std::string
& key_name
)
654 /keys/<keyname>/config
655 data: {"deletion_allowed": true}
656 post to prefix + key_name
659 ZeroPoolDocument d
{ rapidjson::kObjectType
};
660 auto &allocator
{ d
.GetAllocator() };
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() };
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
;
676 std::string post_data
{ buf
.GetString() };
678 int res
= send_request(dpp
, "POST", "", config_path
,
679 post_data
, dummy_bl
);
683 if (dummy_bl
.length() != 0) {
684 ldpp_dout(dpp
, 0) << "ERROR: unexpected response from Vault marking key to delete: "
690 res
= send_request(dpp
, "DELETE", "", delete_path
,
695 if (dummy_bl
.length() != 0) {
696 ldpp_dout(dpp
, 0) << "ERROR: unexpected response from Vault deleting key: "
705 class KvSecretEngine
: public VaultSecretEngine
{
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
;
715 virtual ~KvSecretEngine(){}
717 int get_key(const DoutPrefixProvider
*dpp
, std::string_view key_id
, std::string
& actual_key
){
720 bufferlist secret_bl
;
722 int res
= send_request(dpp
, key_id
, secret_bl
);
727 ldpp_dout(dpp
, 20) << "Parse response into JSON Object" << dendl
;
729 secret_bl
.append('\0');
730 rapidjson::StringStream
isw(secret_bl
.c_str());
731 d
.ParseStream
<>(isw
);
733 if (d
.HasParseError()) {
734 ldpp_dout(dpp
, 0) << "ERROR: Failed to parse JSON response from Vault: "
735 << rapidjson::GetParseError_En(d
.GetParseError()) << dendl
;
740 static const char *elements
[] = {"data", "data", "key"};
742 for (auto &elem
: elements
) {
743 if (!v
->IsObject()) {
747 auto endr
{ v
->MemberEnd() };
748 auto itr
{ v
->FindMember(elem
) };
755 if (!v
|| !v
->IsString()) {
756 ldpp_dout(dpp
, 0) << "ERROR: Key not found in JSON response from Vault using KV Engine" << dendl
;
759 return decode_secret(dpp
, v
->GetString(), actual_key
);
764 class KmipSecretEngine
;
765 class KmipGetTheKey
{
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
;
780 KmipGetTheKey::keyid_to_keyname(std::string_view key_id
)
782 work
= cct
->_conf
->rgw_crypt_kmip_kms_key_template
;
783 std::string keyword
= "$keyid";
784 std::string replacement
= std::string(key_id
);
786 if (work
.length() == 0) {
787 work
= std::move(replacement
);
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();
800 KmipGetTheKey::get_uniqueid_for_keyname()
802 RGWKMIPTransceiver
secret_req(cct
, RGWKMIPTransceiver::LOCATE
);
804 secret_req
.name
= work
.data();
805 ret
= secret_req
.process(null_yield
);
808 } else if (!secret_req
.outlist
->string_count
) {
810 lderr(cct
) << "error: locate returned no results for "
811 << secret_req
.name
<< dendl
;
813 } else if (secret_req
.outlist
->string_count
!= 1) {
815 lderr(cct
) << "error: locate found "
816 << secret_req
.outlist
->string_count
817 << " results for " << secret_req
.name
<< dendl
;
820 work
= std::string(secret_req
.outlist
->strings
[0]);
826 KmipGetTheKey::get_key_for_uniqueid(std::string
& actual_key
)
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
);
835 actual_key
= std::string((char*)(secret_req
.outkey
->data
),
836 secret_req
.outkey
->keylen
);
841 class KmipSecretEngine
: public SecretEngine
{
848 KmipSecretEngine(CephContext
*cct
) {
852 int get_key(const DoutPrefixProvider
*dpp
, std::string_view key_id
, std::string
& actual_key
)
855 r
= KmipGetTheKey
{cct
}
856 .keyid_to_keyname(key_id
)
857 .get_uniqueid_for_keyname()
858 .get_key_for_uniqueid(actual_key
);
863 static int get_actual_key_from_conf(const DoutPrefixProvider
* dpp
,
865 std::string_view key_id
,
866 std::string_view key_selector
,
867 std::string
& actual_key
)
871 static map
<string
,string
> str_map
= get_str_map(
872 cct
->_conf
->rgw_crypt_s3_kms_encryption_keys
);
874 map
<string
, string
>::iterator it
= str_map
.find(std::string(key_id
));
875 if (it
== str_map
.end())
878 std::string master_key
;
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."
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
);
898 ::ceph::crypto::zeroize_for_security(_actual_key
, sizeof(_actual_key
));
900 ldpp_dout(dpp
, 20) << "Wrong size for key=" << key_id
<< dendl
;
907 static int request_key_from_barbican(const DoutPrefixProvider
*dpp
,
909 std::string_view key_id
,
910 const std::string
& barbican_token
,
911 std::string
& actual_key
) {
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
;
919 concat_url(secret_url
, "/v1/secrets/");
920 concat_url(secret_url
, std::string(key_id
));
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
);
927 res
= secret_req
.process(null_yield
);
931 if (secret_req
.get_http_status() ==
932 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED
) {
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());
947 static int get_actual_key_from_barbican(const DoutPrefixProvider
*dpp
,
949 std::string_view key_id
,
950 std::string
& actual_key
)
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
;
960 res
= request_key_from_barbican(dpp
, cct
, key_id
, token
, actual_key
);
962 ldpp_dout(dpp
, 5) << "Failed to retrieve secret from Barbican:" << key_id
<< dendl
;
968 std::string
config_to_engine_and_parms(CephContext
*cct
,
970 std::string
& secret_engine_str
,
971 EngineParmMap
& secret_engine_parms
)
973 std::ostringstream oss
;
974 std::vector
<std::string
> secret_engine_v
;
975 std::string secret_engine
;
977 get_str_vec(secret_engine_str
, " ", secret_engine_v
);
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
;
986 for (auto& e
: secret_engine_v
) {
987 if (!secret_engine
.length()) {
988 secret_engine
= std::move(e
);
991 auto p
{ e
.find('=') };
992 if (p
== std::string::npos
) {
993 secret_engine_parms
.emplace(std::move(e
), "");
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
));
1000 return secret_engine
;
1004 static int get_actual_key_from_vault(const DoutPrefixProvider
*dpp
,
1007 map
<string
, bufferlist
>& attrs
,
1008 std::string
& actual_key
, bool make_it
)
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
;
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
);
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
);
1027 ? engine
.make_actual_key(dpp
, attrs
, actual_key
)
1028 : engine
.reconstitute_actual_key(dpp
, attrs
, actual_key
);
1031 ldpp_dout(dpp
, 0) << "Missing or invalid secret engine" << dendl
;
1037 static int make_actual_key_from_vault(const DoutPrefixProvider
*dpp
,
1040 map
<string
, bufferlist
>& attrs
,
1041 std::string
& actual_key
)
1043 return get_actual_key_from_vault(dpp
, cct
, kctx
, attrs
, actual_key
, true);
1047 static int reconstitute_actual_key_from_vault(const DoutPrefixProvider
*dpp
,
1050 map
<string
, bufferlist
>& attrs
,
1051 std::string
& actual_key
)
1053 return get_actual_key_from_vault(dpp
, cct
, kctx
, attrs
, actual_key
, false);
1057 static int get_actual_key_from_kmip(const DoutPrefixProvider
*dpp
,
1059 std::string_view key_id
,
1060 std::string
& actual_key
)
1062 std::string secret_engine
= RGW_SSE_KMS_KMIP_SE_KV
;
1064 if (RGW_SSE_KMS_KMIP_SE_KV
== secret_engine
){
1065 KmipSecretEngine
engine(cct
);
1066 return engine
.get_key(dpp
, key_id
, actual_key
);
1069 ldpp_dout(dpp
, 0) << "Missing or invalid secret engine" << dendl
;
1073 class KMSContext
: public SSEContext
{
1076 KMSContext(CephContext
*_cct
) : cct
{_cct
} {};
1077 ~KMSContext() override
{};
1078 const std::string
& backend() override
{
1079 return cct
->_conf
->rgw_crypt_s3_kms_backend
;
1081 const std::string
& addr() override
{
1082 return cct
->_conf
->rgw_crypt_vault_addr
;
1084 const std::string
& auth() override
{
1085 return cct
->_conf
->rgw_crypt_vault_auth
;
1087 const std::string
& k_namespace() override
{
1088 return cct
->_conf
->rgw_crypt_vault_namespace
;
1090 const std::string
& prefix() override
{
1091 return cct
->_conf
->rgw_crypt_vault_prefix
;
1093 const std::string
& secret_engine() override
{
1094 return cct
->_conf
->rgw_crypt_vault_secret_engine
;
1096 const std::string
& ssl_cacert() override
{
1097 return cct
->_conf
->rgw_crypt_vault_ssl_cacert
;
1099 const std::string
& ssl_clientcert() override
{
1100 return cct
->_conf
->rgw_crypt_vault_ssl_clientcert
;
1102 const std::string
& ssl_clientkey() override
{
1103 return cct
->_conf
->rgw_crypt_vault_ssl_clientkey
;
1105 const std::string
& token_file() override
{
1106 return cct
->_conf
->rgw_crypt_vault_token_file
;
1108 const bool verify_ssl() override
{
1109 return cct
->_conf
->rgw_crypt_vault_verify_ssl
;
1113 class SseS3Context
: public SSEContext
{
1116 static const std::string sse_s3_secret_engine
;
1117 SseS3Context(CephContext
*_cct
) : cct
{_cct
} {};
1119 const std::string
& backend() override
{
1120 return cct
->_conf
->rgw_crypt_sse_s3_backend
;
1122 const std::string
& addr() override
{
1123 return cct
->_conf
->rgw_crypt_sse_s3_vault_addr
;
1125 const std::string
& auth() override
{
1126 return cct
->_conf
->rgw_crypt_sse_s3_vault_auth
;
1128 const std::string
& k_namespace() override
{
1129 return cct
->_conf
->rgw_crypt_sse_s3_vault_namespace
;
1131 const std::string
& prefix() override
{
1132 return cct
->_conf
->rgw_crypt_sse_s3_vault_prefix
;
1134 const std::string
& secret_engine() override
{
1135 return cct
->_conf
->rgw_crypt_sse_s3_vault_secret_engine
;
1137 const std::string
& ssl_cacert() override
{
1138 return cct
->_conf
->rgw_crypt_sse_s3_vault_ssl_cacert
;
1140 const std::string
& ssl_clientcert() override
{
1141 return cct
->_conf
->rgw_crypt_sse_s3_vault_ssl_clientcert
;
1143 const std::string
& ssl_clientkey() override
{
1144 return cct
->_conf
->rgw_crypt_sse_s3_vault_ssl_clientkey
;
1146 const std::string
& token_file() override
{
1147 return cct
->_conf
->rgw_crypt_sse_s3_vault_token_file
;
1149 const bool verify_ssl() override
{
1150 return cct
->_conf
->rgw_crypt_sse_s3_vault_verify_ssl
;
1154 int reconstitute_actual_key_from_kms(const DoutPrefixProvider
*dpp
, CephContext
*cct
,
1155 map
<string
, bufferlist
>& attrs
,
1156 std::string
& actual_key
)
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() };
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
;
1165 if (RGW_SSE_KMS_BACKEND_BARBICAN
== kms_backend
) {
1166 return get_actual_key_from_barbican(dpp
, cct
, key_id
, actual_key
);
1169 if (RGW_SSE_KMS_BACKEND_VAULT
== kms_backend
) {
1170 return reconstitute_actual_key_from_vault(dpp
, cct
, kctx
, attrs
, actual_key
);
1173 if (RGW_SSE_KMS_BACKEND_KMIP
== kms_backend
) {
1174 return get_actual_key_from_kmip(dpp
, cct
, key_id
, actual_key
);
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
);
1182 ldpp_dout(dpp
, 0) << "ERROR: Invalid rgw_crypt_s3_kms_backend: " << kms_backend
<< dendl
;
1186 int make_actual_key_from_kms(const DoutPrefixProvider
*dpp
, CephContext
*cct
,
1187 map
<string
, bufferlist
>& attrs
,
1188 std::string
& actual_key
)
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
);
1197 int reconstitute_actual_key_from_sse_s3(const DoutPrefixProvider
*dpp
,
1199 map
<string
, bufferlist
>& attrs
,
1200 std::string
& actual_key
)
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() };
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
;
1209 if (RGW_SSE_KMS_BACKEND_VAULT
== kms_backend
) {
1210 return reconstitute_actual_key_from_vault(dpp
, cct
, kctx
, attrs
, actual_key
);
1213 ldpp_dout(dpp
, 0) << "ERROR: Invalid rgw_crypt_sse_s3_backend: " << kms_backend
<< dendl
;
1217 int make_actual_key_from_sse_s3(const DoutPrefixProvider
*dpp
,
1219 map
<string
, bufferlist
>& attrs
,
1220 std::string
& actual_key
)
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
;
1228 return make_actual_key_from_vault(dpp
, cct
, kctx
, attrs
, actual_key
);
1232 int create_sse_s3_bucket_key(const DoutPrefixProvider
*dpp
,
1234 const std::string
& bucket_key
)
1236 SseS3Context kctx
{ cct
};
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
;
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
);
1254 ldpp_dout(dpp
, 0) << "Missing or invalid secret engine" << dendl
;
1259 int remove_sse_s3_bucket_key(const DoutPrefixProvider
*dpp
,
1261 const std::string
& bucket_key
)
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
);
1274 ldpp_dout(dpp
, 0) << "Missing or invalid secret engine" << dendl
;