]> git.proxmox.com Git - ceph.git/blame_incremental - ceph/src/rgw/rgw_auth_keystone.cc
bump version to 19.2.0-pve1
[ceph.git] / ceph / src / rgw / rgw_auth_keystone.cc
... / ...
CommitLineData
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab ft=cpp
3
4#include <string>
5#include <vector>
6
7#include <errno.h>
8#include <fnmatch.h>
9
10#include "rgw_b64.h"
11
12#include "common/errno.h"
13#include "common/ceph_json.h"
14#include "include/types.h"
15#include "include/str_list.h"
16
17#include "rgw_common.h"
18#include "rgw_keystone.h"
19#include "rgw_auth_keystone.h"
20#include "rgw_rest_s3.h"
21#include "rgw_auth_s3.h"
22
23#include "common/ceph_crypto.h"
24#include "common/Cond.h"
25
26#define dout_subsys ceph_subsys_rgw
27
28using namespace std;
29
30namespace rgw {
31namespace auth {
32namespace keystone {
33
34bool
35TokenEngine::is_applicable(const std::string& token) const noexcept
36{
37 return ! token.empty() && ! cct->_conf->rgw_keystone_url.empty();
38}
39
40boost::optional<TokenEngine::token_envelope_t>
41TokenEngine::get_from_keystone(const DoutPrefixProvider* dpp,
42 const std::string& token,
43 bool allow_expired,
44 optional_yield y) const
45{
46 /* Unfortunately, we can't use the short form of "using" here. It's because
47 * we're aliasing a class' member, not namespace. */
48 using RGWValidateKeystoneToken = \
49 rgw::keystone::Service::RGWValidateKeystoneToken;
50
51 bool admin_token_retried = false;
52
53admin_token_retry:
54
55 /* The container for plain response obtained from Keystone. It will be
56 * parsed token_envelope_t::parse method. */
57 ceph::bufferlist token_body_bl;
58 RGWValidateKeystoneToken validate(cct, "GET", "", &token_body_bl);
59
60 std::string url = config.get_endpoint_url();
61 if (url.empty()) {
62 throw -EINVAL;
63 }
64
65 const auto keystone_version = config.get_api_version();
66 if (keystone_version == rgw::keystone::ApiVersion::VER_2) {
67 url.append("v2.0/tokens/" + token);
68 } else if (keystone_version == rgw::keystone::ApiVersion::VER_3) {
69 url.append("v3/auth/tokens");
70
71 if (allow_expired) {
72 url.append("?allow_expired=1");
73 }
74
75 validate.append_header("X-Subject-Token", token);
76 }
77
78 std::string admin_token;
79 bool admin_token_cached = false;
80 int ret = rgw::keystone::Service::get_admin_token(dpp, token_cache, config,
81 y, admin_token, admin_token_cached);
82 if (ret < 0) {
83 throw -EINVAL;
84 }
85
86 validate.append_header("X-Auth-Token", admin_token);
87 validate.set_send_length(0);
88
89 validate.set_url(url);
90
91 ret = validate.process(y);
92
93 /* NULL terminate for debug output. */
94 token_body_bl.append(static_cast<char>(0));
95
96 /* Detect Keystone rejection earlier than during the token parsing.
97 * Although failure at the parsing phase doesn't impose a threat,
98 * this allows to return proper error code (EACCESS instead of EINVAL
99 * or similar) and thus improves logging. */
100
101 /* If admin token is invalid we should expire it from the cache and
102 try one last time without the cache. */
103 bool admin_token_unauthorized = (validate.get_http_status() ==
104 RGWValidateKeystoneToken::HTTP_STATUS_UNAUTHORIZED);
105
106 if (admin_token_unauthorized && admin_token_cached) {
107 ldpp_dout(dpp, 20) << "invalidating admin_token cache due to 401" << dendl;
108 token_cache.invalidate_admin(dpp);
109
110 if (!admin_token_retried) {
111 ldpp_dout(dpp, 20) << "retrying with uncached admin_token" << dendl;
112 admin_token_retried = true;
113 goto admin_token_retry;
114 }
115 }
116
117 /* If admin token is invalid or token supplied by client is non-existent. */
118 if (admin_token_unauthorized || validate.get_http_status() ==
119 RGWValidateKeystoneToken::HTTP_STATUS_NOTFOUND) {
120 ldpp_dout(dpp, 5) << "Failed keystone auth from " << url << " with "
121 << validate.get_http_status() << dendl;
122 return boost::none;
123 }
124 // throw any other http or connection errors
125 if (ret < 0) {
126 throw ret;
127 }
128
129 ldpp_dout(dpp, 20) << "received response status=" << validate.get_http_status()
130 << ", body=" << token_body_bl.c_str() << dendl;
131
132 TokenEngine::token_envelope_t token_body;
133 ret = token_body.parse(dpp, token, token_body_bl, config.get_api_version());
134 if (ret < 0) {
135 throw ret;
136 }
137
138 return token_body;
139}
140
141TokenEngine::auth_info_t
142TokenEngine::get_creds_info(const TokenEngine::token_envelope_t& token
143 ) const noexcept
144{
145 using acct_privilege_t = rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t;
146
147 /* Check whether the user has an admin status. */
148 acct_privilege_t level = acct_privilege_t::IS_PLAIN_ACCT;
149 for (const auto& role : token.roles) {
150 if (role.is_admin && !role.is_reader) {
151 level = acct_privilege_t::IS_ADMIN_ACCT;
152 break;
153 }
154 }
155
156 return auth_info_t {
157 /* Suggested account name for the authenticated user. */
158 rgw_user(token.get_project_id()),
159 /* User's display name (aka real name). */
160 token.get_project_name(),
161 /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
162 * the access rights through the perm_mask. At least at this layer. */
163 RGW_PERM_FULL_CONTROL,
164 level,
165 rgw::auth::RemoteApplier::AuthInfo::NO_ACCESS_KEY,
166 rgw::auth::RemoteApplier::AuthInfo::NO_SUBUSER,
167 TYPE_KEYSTONE
168};
169}
170
171static inline const std::string
172make_spec_item(const std::string& tenant, const std::string& id)
173{
174 return tenant + ":" + id;
175}
176
177TokenEngine::acl_strategy_t
178TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t& token) const
179{
180 /* The primary identity is constructed upon UUIDs. */
181 const auto& tenant_uuid = token.get_project_id();
182 const auto& user_uuid = token.get_user_id();
183
184 /* For Keystone v2 an alias may be also used. */
185 const auto& tenant_name = token.get_project_name();
186 const auto& user_name = token.get_user_name();
187
188 /* Construct all possible combinations including Swift's wildcards. */
189 const std::array<std::string, 6> allowed_items = {
190 make_spec_item(tenant_uuid, user_uuid),
191 make_spec_item(tenant_name, user_name),
192
193 /* Wildcards. */
194 make_spec_item(tenant_uuid, "*"),
195 make_spec_item(tenant_name, "*"),
196 make_spec_item("*", user_uuid),
197 make_spec_item("*", user_name),
198 };
199
200 /* Lambda will obtain a copy of (not a reference to!) allowed_items. */
201 return [allowed_items, token_roles=token.roles](const rgw::auth::Identity::aclspec_t& aclspec) {
202 uint32_t perm = 0;
203
204 for (const auto& allowed_item : allowed_items) {
205 const auto iter = aclspec.find(allowed_item);
206
207 if (std::end(aclspec) != iter) {
208 perm |= iter->second;
209 }
210 }
211
212 for (const auto& r : token_roles) {
213 if (r.is_reader) {
214 if (r.is_admin) { /* system scope reader persona */
215 /*
216 * Because system reader defeats permissions,
217 * we don't even look at the aclspec.
218 */
219 perm |= RGW_OP_TYPE_READ;
220 }
221 }
222 }
223
224 return perm;
225 };
226}
227
228TokenEngine::result_t
229TokenEngine::authenticate(const DoutPrefixProvider* dpp,
230 const std::string& token,
231 const std::string& service_token,
232 const req_state* const s,
233 optional_yield y) const
234{
235 bool allow_expired = false;
236 boost::optional<TokenEngine::token_envelope_t> t;
237
238 /* This will be initialized on the first call to this method. In C++11 it's
239 * also thread-safe. */
240 static const struct RolesCacher {
241 explicit RolesCacher(CephContext* const cct) {
242 get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain);
243 get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin);
244 get_str_vec(cct->_conf->rgw_keystone_accepted_reader_roles, reader);
245
246 /* Let's suppose that having an admin role implies also a regular one. */
247 plain.insert(std::end(plain), std::begin(admin), std::end(admin));
248 }
249
250 std::vector<std::string> plain;
251 std::vector<std::string> admin;
252 std::vector<std::string> reader;
253 } roles(cct);
254
255 static const struct ServiceTokenRolesCacher {
256 explicit ServiceTokenRolesCacher(CephContext* const cct) {
257 get_str_vec(cct->_conf->rgw_keystone_service_token_accepted_roles, plain);
258 }
259
260 std::vector<std::string> plain;
261 } service_token_roles(cct);
262
263 if (! is_applicable(token)) {
264 return result_t::deny();
265 }
266
267 /* Token ID is a legacy of supporting the service-side validation
268 * of PKI/PKIz token type which are already-removed-in-OpenStack.
269 * The idea was to bury in cache only a short hash instead of few
270 * kilobytes. RadosGW doesn't do the local validation anymore. */
271 const auto& token_id = rgw_get_token_id(token);
272 ldpp_dout(dpp, 20) << "token_id=" << token_id << dendl;
273
274 /* Check cache first. */
275 t = token_cache.find(token_id);
276 if (t) {
277 ldpp_dout(dpp, 20) << "cached token.project.id=" << t->get_project_id()
278 << dendl;
279 auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
280 get_creds_info(*t));
281 return result_t::grant(std::move(apl));
282 }
283
284 /* We have a service token and a token so we verify the service
285 * token and if it's invalid the request is invalid. If it's valid
286 * we allow an expired token to be used when doing lookup in Keystone.
287 * We never get to this if the token is in the cache. */
288 if (g_conf()->rgw_keystone_service_token_enabled && ! service_token.empty()) {
289 boost::optional<TokenEngine::token_envelope_t> st;
290
291 const auto& service_token_id = rgw_get_token_id(service_token);
292 ldpp_dout(dpp, 20) << "service_token_id=" << service_token_id << dendl;
293
294 /* Check cache for service token first. */
295 st = token_cache.find_service(service_token_id);
296 if (st) {
297 ldpp_dout(dpp, 20) << "cached service_token.project.id=" << st->get_project_id()
298 << dendl;
299
300 /* We found the service token in the cache so we allow using an expired
301 * token for this request. */
302 allow_expired = true;
303 ldpp_dout(dpp, 20) << "allowing expired tokens because service_token_id="
304 << service_token_id
305 << " was found in cache" << dendl;
306 } else {
307 /* Service token was not found in cache. Go to Keystone for validating
308 * the token. The allow_expired here must always be false. */
309 ceph_assert(allow_expired == false);
310 st = get_from_keystone(dpp, service_token, allow_expired, y);
311
312 if (! st) {
313 return result_t::deny(-EACCES);
314 }
315
316 /* Verify expiration of service token. */
317 if (st->expired()) {
318 ldpp_dout(dpp, 0) << "got expired service token: " << st->get_project_name()
319 << ":" << st->get_user_name()
320 << " expired " << st->get_expires() << dendl;
321 return result_t::deny(-EPERM);
322 }
323
324 /* Check for necessary roles for service token. */
325 for (const auto& role : service_token_roles.plain) {
326 if (st->has_role(role) == true) {
327 /* Service token is valid so we allow using an expired token for
328 * this request. */
329 ldpp_dout(dpp, 20) << "allowing expired tokens because service_token_id="
330 << service_token_id
331 << " is valid, role: "
332 << role << dendl;
333 allow_expired = true;
334 token_cache.add_service(service_token_id, *st);
335 break;
336 }
337 }
338
339 if (!allow_expired) {
340 ldpp_dout(dpp, 0) << "service token user does not hold a matching role; required roles: "
341 << g_conf()->rgw_keystone_service_token_accepted_roles << dendl;
342 return result_t::deny(-EPERM);
343 }
344 }
345 }
346
347 /* Token not in cache. Go to the Keystone for validation. This happens even
348 * for the legacy PKI/PKIz token types. That's it, after the PKI/PKIz
349 * RadosGW-side validation has been removed, we always ask Keystone. */
350 t = get_from_keystone(dpp, token, allow_expired, y);
351 if (! t) {
352 return result_t::deny(-EACCES);
353 }
354 t->update_roles(roles.admin, roles.reader);
355
356 /* Verify expiration. */
357 if (t->expired()) {
358 if (allow_expired) {
359 ldpp_dout(dpp, 20) << "allowing expired token: " << t->get_project_name()
360 << ":" << t->get_user_name()
361 << " expired: " << t->get_expires()
362 << " because of valid service token" << dendl;
363 } else {
364 ldpp_dout(dpp, 0) << "got expired token: " << t->get_project_name()
365 << ":" << t->get_user_name()
366 << " expired: " << t->get_expires() << dendl;
367 return result_t::deny(-EPERM);
368 }
369 }
370
371 /* Check for necessary roles. */
372 for (const auto& role : roles.plain) {
373 if (t->has_role(role) == true) {
374 /* If this token was an allowed expired token because we got a
375 * service token we need to update the expiration before we cache it. */
376 if (allow_expired) {
377 time_t now = ceph_clock_now().sec();
378 time_t new_expires = now + g_conf()->rgw_keystone_expired_token_cache_expiration;
379 ldpp_dout(dpp, 20) << "updating expiration of allowed expired token"
380 << " from old " << t->get_expires() << " to now " << now << " + "
381 << g_conf()->rgw_keystone_expired_token_cache_expiration
382 << " secs = "
383 << new_expires << dendl;
384 t->set_expires(new_expires);
385 }
386 ldpp_dout(dpp, 0) << "validated token: " << t->get_project_name()
387 << ":" << t->get_user_name()
388 << " expires: " << t->get_expires() << dendl;
389 token_cache.add(token_id, *t);
390 auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
391 get_creds_info(*t));
392 return result_t::grant(std::move(apl));
393 }
394 }
395
396 ldpp_dout(dpp, 0) << "user does not hold a matching role; required roles: "
397 << g_conf()->rgw_keystone_accepted_roles << dendl;
398
399 return result_t::deny(-EPERM);
400}
401
402
403/*
404 * Try to validate S3 auth against keystone s3token interface
405 */
406std::pair<boost::optional<rgw::keystone::TokenEnvelope>, int>
407EC2Engine::get_from_keystone(const DoutPrefixProvider* dpp, const std::string_view& access_key_id,
408 const std::string& string_to_sign,
409 const std::string_view& signature,
410 optional_yield y) const
411{
412 /* prepare keystone url */
413 std::string keystone_url = config.get_endpoint_url();
414 if (keystone_url.empty()) {
415 throw -EINVAL;
416 }
417
418 const auto api_version = config.get_api_version();
419 if (api_version == rgw::keystone::ApiVersion::VER_3) {
420 keystone_url.append("v3/s3tokens");
421 } else {
422 keystone_url.append("v2.0/s3tokens");
423 }
424
425 /* get authentication token for Keystone. */
426 std::string admin_token;
427 bool admin_token_cached = false;
428 int ret = rgw::keystone::Service::get_admin_token(dpp, token_cache, config,
429 y, admin_token, admin_token_cached);
430 if (ret < 0) {
431 ldpp_dout(dpp, 2) << "s3 keystone: cannot get token for keystone access"
432 << dendl;
433 throw ret;
434 }
435
436 using RGWValidateKeystoneToken
437 = rgw::keystone::Service::RGWValidateKeystoneToken;
438
439 /* The container for plain response obtained from Keystone. It will be
440 * parsed token_envelope_t::parse method. */
441 ceph::bufferlist token_body_bl;
442 RGWValidateKeystoneToken validate(cct, "POST", keystone_url, &token_body_bl);
443
444 /* set required headers for keystone request */
445 validate.append_header("X-Auth-Token", admin_token);
446 validate.append_header("Content-Type", "application/json");
447
448 /* check if we want to verify keystone's ssl certs */
449 validate.set_verify_ssl(cct->_conf->rgw_keystone_verify_ssl);
450
451 /* create json credentials request body */
452 JSONFormatter credentials(false);
453 credentials.open_object_section("");
454 credentials.open_object_section("credentials");
455 credentials.dump_string("access", sview2cstr(access_key_id).data());
456 credentials.dump_string("token", rgw::to_base64(string_to_sign));
457 credentials.dump_string("signature", sview2cstr(signature).data());
458 credentials.close_section();
459 credentials.close_section();
460
461 std::stringstream os;
462 credentials.flush(os);
463 validate.set_post_data(os.str());
464 validate.set_send_length(os.str().length());
465
466 /* send request */
467 ret = validate.process(y);
468
469 /* if the supplied signature is wrong, we will get 401 from Keystone */
470 if (validate.get_http_status() ==
471 decltype(validate)::HTTP_STATUS_UNAUTHORIZED) {
472 return std::make_pair(boost::none, -ERR_SIGNATURE_NO_MATCH);
473 } else if (validate.get_http_status() ==
474 decltype(validate)::HTTP_STATUS_NOTFOUND) {
475 return std::make_pair(boost::none, -ERR_INVALID_ACCESS_KEY);
476 }
477 // throw any other http or connection errors
478 if (ret < 0) {
479 ldpp_dout(dpp, 2) << "s3 keystone: token validation ERROR: "
480 << token_body_bl.c_str() << dendl;
481 throw ret;
482 }
483
484 /* now parse response */
485 rgw::keystone::TokenEnvelope token_envelope;
486 ret = token_envelope.parse(dpp, std::string(), token_body_bl, api_version);
487 if (ret < 0) {
488 ldpp_dout(dpp, 2) << "s3 keystone: token parsing failed, ret=0" << ret
489 << dendl;
490 throw ret;
491 }
492
493 return std::make_pair(std::move(token_envelope), 0);
494}
495
496auto EC2Engine::get_secret_from_keystone(const DoutPrefixProvider* dpp,
497 const std::string& user_id,
498 const std::string_view& access_key_id,
499 optional_yield y) const
500 -> std::pair<boost::optional<std::string>, int>
501{
502 /* Fetch from /users/{USER_ID}/credentials/OS-EC2/{ACCESS_KEY_ID} */
503 /* Should return json with response key "credential" which contains entry "secret"*/
504
505 /* prepare keystone url */
506 std::string keystone_url = config.get_endpoint_url();
507 if (keystone_url.empty()) {
508 return make_pair(boost::none, -EINVAL);
509 }
510
511 const auto api_version = config.get_api_version();
512 if (api_version == rgw::keystone::ApiVersion::VER_3) {
513 keystone_url.append("v3/");
514 } else {
515 keystone_url.append("v2.0/");
516 }
517 keystone_url.append("users/");
518 keystone_url.append(user_id);
519 keystone_url.append("/credentials/OS-EC2/");
520 keystone_url.append(std::string(access_key_id));
521
522 /* get authentication token for Keystone. */
523 std::string admin_token;
524 bool admin_token_cached = false;
525 int ret = rgw::keystone::Service::get_admin_token(dpp, token_cache, config,
526 y, admin_token, admin_token_cached);
527 if (ret < 0) {
528 ldpp_dout(dpp, 2) << "s3 keystone: cannot get token for keystone access"
529 << dendl;
530 return make_pair(boost::none, ret);
531 }
532
533 using RGWGetAccessSecret
534 = rgw::keystone::Service::RGWKeystoneHTTPTransceiver;
535
536 /* The container for plain response obtained from Keystone.*/
537 ceph::bufferlist token_body_bl;
538 RGWGetAccessSecret secret(cct, "GET", keystone_url, &token_body_bl);
539
540 /* set required headers for keystone request */
541 secret.append_header("X-Auth-Token", admin_token);
542
543 /* check if we want to verify keystone's ssl certs */
544 secret.set_verify_ssl(cct->_conf->rgw_keystone_verify_ssl);
545
546 /* send request */
547 ret = secret.process(y);
548
549 /* if the supplied access key isn't found, we will get 404 from Keystone */
550 if (secret.get_http_status() ==
551 decltype(secret)::HTTP_STATUS_NOTFOUND) {
552 return make_pair(boost::none, -ERR_INVALID_ACCESS_KEY);
553 }
554 // return any other http or connection errors
555 if (ret < 0) {
556 ldpp_dout(dpp, 2) << "s3 keystone: secret fetching error: "
557 << token_body_bl.c_str() << dendl;
558 return make_pair(boost::none, ret);
559 }
560
561 /* now parse response */
562
563 JSONParser parser;
564 if (! parser.parse(token_body_bl.c_str(), token_body_bl.length())) {
565 ldpp_dout(dpp, 0) << "Keystone credential parse error: malformed json" << dendl;
566 return make_pair(boost::none, -EINVAL);
567 }
568
569 JSONObjIter credential_iter = parser.find_first("credential");
570 std::string secret_string;
571
572 try {
573 if (!credential_iter.end()) {
574 JSONDecoder::decode_json("secret", secret_string, *credential_iter, true);
575 } else {
576 ldpp_dout(dpp, 0) << "Keystone credential not present in return from server" << dendl;
577 return make_pair(boost::none, -EINVAL);
578 }
579 } catch (const JSONDecoder::err& err) {
580 ldpp_dout(dpp, 0) << "Keystone credential parse error: " << err.what() << dendl;
581 return make_pair(boost::none, -EINVAL);
582 }
583
584 return make_pair(secret_string, 0);
585}
586
587/*
588 * Try to get a token for S3 authentication, using a secret cache if available
589 */
590auto EC2Engine::get_access_token(const DoutPrefixProvider* dpp,
591 const std::string_view& access_key_id,
592 const std::string& string_to_sign,
593 const std::string_view& signature,
594 const signature_factory_t& signature_factory,
595 bool ignore_signature,
596 optional_yield y) const
597 -> access_token_result
598{
599 using server_signature_t = VersionAbstractor::server_signature_t;
600 boost::optional<rgw::keystone::TokenEnvelope> token;
601 boost::optional<std::string> secret;
602 int failure_reason;
603
604 /* Get a token from the cache if one has already been stored */
605 boost::optional<boost::tuple<rgw::keystone::TokenEnvelope, std::string>>
606 t = secret_cache.find(std::string(access_key_id));
607
608 /* Check that credentials can correctly be used to sign data */
609 if (t) {
610 /* We should ignore checking signature in cache if caller tells us to which
611 * means we're handling a HTTP OPTIONS call. */
612 if (ignore_signature) {
613 ldpp_dout(dpp, 20) << "ignore_signature set and found in cache" << dendl;
614 return {t->get<0>(), t->get<1>(), 0};
615 } else {
616 std::string sig(signature);
617 server_signature_t server_signature = signature_factory(cct, t->get<1>(), string_to_sign);
618 if (sig.compare(server_signature) == 0) {
619 return {t->get<0>(), t->get<1>(), 0};
620 } else {
621 ldpp_dout(dpp, 0) << "Secret string does not correctly sign payload, cache miss" << dendl;
622 }
623 }
624 } else {
625 ldpp_dout(dpp, 0) << "No stored secret string, cache miss" << dendl;
626 }
627
628 /* No cached token, token expired, or secret invalid: fall back to keystone */
629 std::tie(token, failure_reason) =
630 get_from_keystone(dpp, access_key_id, string_to_sign, signature, y);
631
632 if (token) {
633 /* Fetch secret from keystone for the access_key_id */
634 std::tie(secret, failure_reason) =
635 get_secret_from_keystone(dpp, token->get_user_id(), access_key_id, y);
636
637 if (secret) {
638 /* Add token, secret pair to cache, and set timeout */
639 secret_cache.add(std::string(access_key_id), *token, *secret);
640 }
641 }
642
643 return {token, secret, failure_reason};
644}
645
646EC2Engine::acl_strategy_t
647EC2Engine::get_acl_strategy(const EC2Engine::token_envelope_t&) const
648{
649 /* This is based on the assumption that the default acl strategy in
650 * get_perms_from_aclspec, will take care. Extra acl spec is not required. */
651 return nullptr;
652}
653
654EC2Engine::auth_info_t
655EC2Engine::get_creds_info(const EC2Engine::token_envelope_t& token,
656 const std::vector<std::string>& admin_roles,
657 const std::string& access_key_id
658 ) const noexcept
659{
660 using acct_privilege_t = \
661 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t;
662
663 /* Check whether the user has an admin status. */
664 acct_privilege_t level = acct_privilege_t::IS_PLAIN_ACCT;
665 for (const auto& admin_role : admin_roles) {
666 if (token.has_role(admin_role)) {
667 level = acct_privilege_t::IS_ADMIN_ACCT;
668 break;
669 }
670 }
671
672 return auth_info_t {
673 /* Suggested account name for the authenticated user. */
674 rgw_user(token.get_project_id()),
675 /* User's display name (aka real name). */
676 token.get_project_name(),
677 /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
678 * the access rights through the perm_mask. At least at this layer. */
679 RGW_PERM_FULL_CONTROL,
680 level,
681 access_key_id,
682 rgw::auth::RemoteApplier::AuthInfo::NO_SUBUSER,
683 TYPE_KEYSTONE
684 };
685}
686
687rgw::auth::Engine::result_t EC2Engine::authenticate(
688 const DoutPrefixProvider* dpp,
689 const std::string_view& access_key_id,
690 const std::string_view& signature,
691 const std::string_view& session_token,
692 const string_to_sign_t& string_to_sign,
693 const signature_factory_t& signature_factory,
694 const completer_factory_t& completer_factory,
695 const req_state* s,
696 optional_yield y) const
697{
698 /* This will be initialized on the first call to this method. In C++11 it's
699 * also thread-safe. */
700 static const struct RolesCacher {
701 explicit RolesCacher(CephContext* const cct) {
702 get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain);
703 get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin);
704
705 /* Let's suppose that having an admin role implies also a regular one. */
706 plain.insert(std::end(plain), std::begin(admin), std::end(admin));
707 }
708
709 std::vector<std::string> plain;
710 std::vector<std::string> admin;
711 } accepted_roles(cct);
712
713 /* When we handle a HTTP OPTIONS call we must ignore the signature */
714 bool ignore_signature = (s->op_type == RGW_OP_OPTIONS_CORS);
715
716 auto [t, secret_key, failure_reason] =
717 get_access_token(dpp, access_key_id, string_to_sign,
718 signature, signature_factory, ignore_signature, y);
719 if (! t) {
720 if (failure_reason == -ERR_SIGNATURE_NO_MATCH) {
721 // we looked up a secret but it didn't generate the same signature as
722 // the client. since we found this access key in keystone, we should
723 // reject the request instead of trying other engines
724 return result_t::reject(failure_reason);
725 }
726 return result_t::deny(failure_reason);
727 }
728
729 /* Verify expiration. */
730 if (t->expired()) {
731 ldpp_dout(dpp, 0) << "got expired token: " << t->get_project_name()
732 << ":" << t->get_user_name()
733 << " expired: " << t->get_expires() << dendl;
734 return result_t::deny();
735 }
736
737 /* check if we have a valid role */
738 bool found = false;
739 for (const auto& role : accepted_roles.plain) {
740 if (t->has_role(role) == true) {
741 found = true;
742 break;
743 }
744 }
745
746 if (! found) {
747 ldpp_dout(dpp, 5) << "s3 keystone: user does not hold a matching role;"
748 " required roles: "
749 << cct->_conf->rgw_keystone_accepted_roles << dendl;
750 return result_t::deny();
751 } else {
752 /* everything seems fine, continue with this user */
753 ldpp_dout(dpp, 5) << "s3 keystone: validated token: " << t->get_project_name()
754 << ":" << t->get_user_name()
755 << " expires: " << t->get_expires() << dendl;
756
757 auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
758 get_creds_info(*t, accepted_roles.admin, std::string(access_key_id)));
759 return result_t::grant(std::move(apl), completer_factory(secret_key));
760 }
761}
762
763bool SecretCache::find(const std::string& token_id,
764 SecretCache::token_envelope_t& token,
765 std::string &secret)
766{
767 std::lock_guard<std::mutex> l(lock);
768
769 map<std::string, secret_entry>::iterator iter = secrets.find(token_id);
770 if (iter == secrets.end()) {
771 return false;
772 }
773
774 secret_entry& entry = iter->second;
775 secrets_lru.erase(entry.lru_iter);
776
777 const utime_t now = ceph_clock_now();
778 if (entry.token.expired() || now > entry.expires) {
779 secrets.erase(iter);
780 return false;
781 }
782 token = entry.token;
783 secret = entry.secret;
784
785 secrets_lru.push_front(token_id);
786 entry.lru_iter = secrets_lru.begin();
787
788 return true;
789}
790
791void SecretCache::add(const std::string& token_id,
792 const SecretCache::token_envelope_t& token,
793 const std::string& secret)
794{
795 std::lock_guard<std::mutex> l(lock);
796
797 map<string, secret_entry>::iterator iter = secrets.find(token_id);
798 if (iter != secrets.end()) {
799 secret_entry& e = iter->second;
800 secrets_lru.erase(e.lru_iter);
801 }
802
803 const utime_t now = ceph_clock_now();
804 secrets_lru.push_front(token_id);
805 secret_entry& entry = secrets[token_id];
806 entry.token = token;
807 entry.secret = secret;
808 entry.expires = now + s3_token_expiry_length;
809 entry.lru_iter = secrets_lru.begin();
810
811 while (secrets_lru.size() > max) {
812 list<string>::reverse_iterator riter = secrets_lru.rbegin();
813 iter = secrets.find(*riter);
814 assert(iter != secrets.end());
815 secrets.erase(iter);
816 secrets_lru.pop_back();
817 }
818}
819
820}; /* namespace keystone */
821}; /* namespace auth */
822}; /* namespace rgw */