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