]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_kms.cc
import ceph 15.2.10
[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"
15
16#define dout_context g_ceph_context
17#define dout_subsys ceph_subsys_rgw
18
19using namespace rgw;
20
21
22/**
23 * Construct a full URL string by concatenating a "base" URL with another path,
24 * ensuring there is one and only one forward slash between them. If path is
25 * empty, the URL is not changed.
26 */
27static void concat_url(std::string &url, std::string path) {
28 bool url_has_slash = !url.empty() && url.back() == '/';
29 if (!path.empty()) {
30 if (url_has_slash && path.front() == '/') {
31 url.pop_back();
32 } else if (!url_has_slash && path.front() != '/') {
33 url.push_back('/');
34 }
35 url.append(path);
36 }
37}
38
39
40class VaultSecretEngine: public SecretEngine {
41
42protected:
43 CephContext *cct;
44
45 int load_token_from_file(std::string *vault_token)
46 {
47
48 int res = 0;
49 std::string token_file = cct->_conf->rgw_crypt_vault_token_file;
50 if (token_file.empty()) {
51 ldout(cct, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl;
52 return -EINVAL;
53 }
54 ldout(cct, 20) << "Vault token file: " << token_file << dendl;
55
56 struct stat token_st;
57 if (stat(token_file.c_str(), &token_st) != 0) {
58 ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' not found " << dendl;
59 return -ENOENT;
60 }
61
62 if (token_st.st_mode & (S_IRWXG | S_IRWXO)) {
63 ldout(cct, 0) << "ERROR: Vault token file '" << token_file << "' permissions are "
64 << "too open, it must not be accessible by other users" << dendl;
65 return -EACCES;
66 }
67
68 char buf[2048];
69 res = safe_read_file("", token_file.c_str(), buf, sizeof(buf));
70 if (res < 0) {
71 if (-EACCES == res) {
72 ldout(cct, 0) << "ERROR: Permission denied reading Vault token file" << dendl;
73 } else {
74 ldout(cct, 0) << "ERROR: Failed to read Vault token file with error " << res << dendl;
75 }
76 return res;
77 }
78 // drop trailing newlines
79 while (res && isspace(buf[res-1])) {
80 --res;
81 }
82 vault_token->assign(std::string{buf, static_cast<size_t>(res)});
83 memset(buf, 0, sizeof(buf));
84 ::ceph::crypto::zeroize_for_security(buf, sizeof(buf));
85 return res;
86 }
87
88 int send_request(boost::string_view key_id, JSONParser* parser) override
89 {
90 bufferlist secret_bl;
91 int res;
92 string vault_token = "";
93 if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == cct->_conf->rgw_crypt_vault_auth){
94 ldout(cct, 0) << "Loading Vault Token from filesystem" << dendl;
95 res = load_token_from_file(&vault_token);
96 if (res < 0){
97 return res;
98 }
99 }
100
101 std::string secret_url = cct->_conf->rgw_crypt_vault_addr;
102 if (secret_url.empty()) {
103 ldout(cct, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl;
104 return -EINVAL;
105 }
106
107 concat_url(secret_url, cct->_conf->rgw_crypt_vault_prefix);
108 concat_url(secret_url, std::string(key_id));
109
110 RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
111
112 if (!vault_token.empty()){
113 secret_req.append_header("X-Vault-Token", vault_token);
114 vault_token.replace(0, vault_token.length(), vault_token.length(), '\000');
115 }
116
117 string vault_namespace = cct->_conf->rgw_crypt_vault_namespace;
118 if (!vault_namespace.empty()){
119 ldout(cct, 20) << "Vault Namespace: " << vault_namespace << dendl;
120 secret_req.append_header("X-Vault-Namespace", vault_namespace);
121 }
122
123 res = secret_req.process(null_yield);
124 if (res < 0) {
125 ldout(cct, 0) << "ERROR: Request to Vault failed with error " << res << dendl;
126 return res;
127 }
128
129 if (secret_req.get_http_status() ==
130 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
131 ldout(cct, 0) << "ERROR: Vault request failed authorization" << dendl;
132 return -EACCES;
133 }
134
135 ldout(cct, 20) << "Request to Vault returned " << res << " and HTTP status "
136 << secret_req.get_http_status() << dendl;
137
138 ldout(cct, 20) << "Parse response into JSON Object" << dendl;
139
140 if (!parser->parse(secret_bl.c_str(), secret_bl.length())) {
141 ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault" << dendl;
142 return -EINVAL;
143 }
144 secret_bl.zero();
145
146 return res;
147 }
148
149 int decode_secret(JSONObj* json_obj, std::string& actual_key){
150 std::string secret;
151 try {
152 secret = from_base64(json_obj->get_data());
153 } catch (std::exception&) {
154 ldout(cct, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl;
155 return -EINVAL;
156 }
157
158 actual_key.assign(secret.c_str(), secret.length());
159 secret.replace(0, secret.length(), secret.length(), '\000');
160 return 0;
161 }
162
163public:
164
165 VaultSecretEngine(CephContext *cct) {
166 this->cct = cct;
167 }
168
169 virtual ~VaultSecretEngine(){}
170};
171
172
173class TransitSecretEngine: public VaultSecretEngine {
174
175private:
176 int get_key_version(boost::string_view key_id, string& version)
177 {
178 size_t pos = 0;
179
180 pos = key_id.rfind("/");
181 if (pos != boost::string_view::npos){
182 boost::string_view token = key_id.substr(pos+1, key_id.length()-pos);
183 if (!token.empty() && token.find_first_not_of("0123456789") == boost::string_view::npos){
184 version.assign(std::string(token));
185 return 0;
186 }
187 }
188 return -1;
189 }
190
191public:
192 TransitSecretEngine(CephContext *cct): VaultSecretEngine(cct){ }
193
194 int get_key(boost::string_view key_id, std::string& actual_key)
195 {
196 JSONParser parser;
197 string version;
198
199 if (get_key_version(key_id, version) < 0){
200 ldout(cct, 20) << "Missing or invalid key version" << dendl;
201 return -EINVAL;
202 }
203
204 int res = send_request(key_id, &parser);
205 if (res < 0) {
206 return res;
207 }
208
209 JSONObj* json_obj = &parser;
210 std::array<std::string, 3> elements = {"data", "keys", version};
211 for(const auto& elem : elements) {
212 json_obj = json_obj->find_obj(elem);
213 if (!json_obj) {
214 ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using Transit Engine" << dendl;
215 return -EINVAL;
216 }
217 }
218
219 return decode_secret(json_obj, actual_key);
220 }
221
222};
223
224class KvSecretEngine: public VaultSecretEngine {
225
226public:
227
228 KvSecretEngine(CephContext *cct): VaultSecretEngine(cct){ }
229
230 virtual ~KvSecretEngine(){}
231
232 int get_key(boost::string_view key_id, std::string& actual_key){
233 JSONParser parser;
234 int res = send_request(key_id, &parser);
235 if (res < 0) {
236 return res;
237 }
238
239 JSONObj *json_obj = &parser;
240 std::array<std::string, 3> elements = {"data", "data", "key"};
241 for(const auto& elem : elements) {
242 json_obj = json_obj->find_obj(elem);
243 if (!json_obj) {
244 ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault using KV Engine" << dendl;
245 return -EINVAL;
246 }
247 }
248 return decode_secret(json_obj, actual_key);
249 }
250
251};
252
253
254static map<string,string> get_str_map(const string &str) {
255 map<string,string> m;
256 get_str_map(str, &m, ";, \t");
257 return m;
258}
259
260
261static int get_actual_key_from_conf(CephContext *cct,
262 boost::string_view key_id,
263 boost::string_view key_selector,
264 std::string& actual_key)
265{
266 int res = 0;
267
268 static map<string,string> str_map = get_str_map(
269 cct->_conf->rgw_crypt_s3_kms_encryption_keys);
270
271 map<string, string>::iterator it = str_map.find(std::string(key_id));
272 if (it == str_map.end())
273 return -ERR_INVALID_ACCESS_KEY;
274
275 std::string master_key;
276 try {
277 master_key = from_base64((*it).second);
278 } catch (std::exception&) {
279 ldout(cct, 5) << "ERROR: get_actual_key_from_conf invalid encryption key id "
280 << "which contains character that is not base64 encoded."
281 << dendl;
282 return -EINVAL;
283 }
284
285 if (master_key.length() == AES_256_KEYSIZE) {
286 uint8_t _actual_key[AES_256_KEYSIZE];
287 if (AES_256_ECB_encrypt(cct,
288 reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE,
289 reinterpret_cast<const uint8_t*>(key_selector.data()),
290 _actual_key, AES_256_KEYSIZE)) {
291 actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE);
292 } else {
293 res = -EIO;
294 }
295 ::ceph::crypto::zeroize_for_security(_actual_key, sizeof(_actual_key));
296 } else {
297 ldout(cct, 20) << "Wrong size for key=" << key_id << dendl;
298 res = -EIO;
299 }
300
301 return res;
302}
303
304static int request_key_from_barbican(CephContext *cct,
305 boost::string_view key_id,
306 const std::string& barbican_token,
307 std::string& actual_key) {
308 int res;
309
310 std::string secret_url = cct->_conf->rgw_barbican_url;
311 if (secret_url.empty()) {
312 ldout(cct, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl;
313 return -EINVAL;
314 }
315 concat_url(secret_url, "/v1/secrets/");
316 concat_url(secret_url, std::string(key_id));
317
318 bufferlist secret_bl;
319 RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
320 secret_req.append_header("Accept", "application/octet-stream");
321 secret_req.append_header("X-Auth-Token", barbican_token);
322
323 res = secret_req.process(null_yield);
324 if (res < 0) {
325 return res;
326 }
327 if (secret_req.get_http_status() ==
328 RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
329 return -EACCES;
330 }
331
332 if (secret_req.get_http_status() >=200 &&
333 secret_req.get_http_status() < 300 &&
334 secret_bl.length() == AES_256_KEYSIZE) {
335 actual_key.assign(secret_bl.c_str(), secret_bl.length());
336 secret_bl.zero();
337 } else {
338 res = -EACCES;
339 }
340 return res;
341}
342
343static int get_actual_key_from_barbican(CephContext *cct,
344 boost::string_view key_id,
345 std::string& actual_key)
346{
347 int res = 0;
348 std::string token;
349
350 if (rgw::keystone::Service::get_keystone_barbican_token(cct, token) < 0) {
351 ldout(cct, 5) << "Failed to retrieve token for Barbican" << dendl;
352 return -EINVAL;
353 }
354
355 res = request_key_from_barbican(cct, key_id, token, actual_key);
356 if (res != 0) {
357 ldout(cct, 5) << "Failed to retrieve secret from Barbican:" << key_id << dendl;
358 }
359 return res;
360}
361
362
363static int get_actual_key_from_vault(CephContext *cct,
364 boost::string_view key_id,
365 std::string& actual_key)
366{
367 std::string secret_engine = cct->_conf->rgw_crypt_vault_secret_engine;
368 ldout(cct, 20) << "Vault authentication method: " << cct->_conf->rgw_crypt_vault_auth << dendl;
369 ldout(cct, 20) << "Vault Secrets Engine: " << secret_engine << dendl;
370
371 if (RGW_SSE_KMS_VAULT_SE_KV == secret_engine){
372 KvSecretEngine engine(cct);
373 return engine.get_key(key_id, actual_key);
374 }
375 else if (RGW_SSE_KMS_VAULT_SE_TRANSIT == secret_engine){
376 TransitSecretEngine engine(cct);
377 return engine.get_key(key_id, actual_key);
378 }
379 else{
380 ldout(cct, 0) << "Missing or invalid secret engine" << dendl;
381 return -EINVAL;
382 }
383}
384
385
386int get_actual_key_from_kms(CephContext *cct,
387 boost::string_view key_id,
388 boost::string_view key_selector,
389 std::string& actual_key)
390{
391 std::string kms_backend;
392
393 kms_backend = cct->_conf->rgw_crypt_s3_kms_backend;
394 ldout(cct, 20) << "Getting KMS encryption key for key " << key_id << dendl;
395 ldout(cct, 20) << "SSE-KMS backend is " << kms_backend << dendl;
396
397 if (RGW_SSE_KMS_BACKEND_BARBICAN == kms_backend)
398 return get_actual_key_from_barbican(cct, key_id, actual_key);
399
400 if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend)
401 return get_actual_key_from_vault(cct, key_id, actual_key);
402
403 if (RGW_SSE_KMS_BACKEND_TESTING == kms_backend)
404 return get_actual_key_from_conf(cct, key_id, key_selector, actual_key);
405
406 ldout(cct, 0) << "ERROR: Invalid rgw_crypt_s3_kms_backend: " << kms_backend << dendl;
407 return -EINVAL;
408}