1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
7 #include <boost/algorithm/string/predicate.hpp>
8 #include <boost/algorithm/string.hpp>
11 #include "common/errno.h"
12 #include "common/ceph_json.h"
13 #include "include/types.h"
14 #include "include/str_list.h"
16 #include "rgw_common.h"
17 #include "rgw_keystone.h"
18 #include "common/armor.h"
19 #include "common/Cond.h"
20 #include "rgw_perf_counters.h"
22 #define dout_context g_ceph_context
23 #define dout_subsys ceph_subsys_rgw
24 #define PKI_ANS1_PREFIX "MII"
28 bool rgw_is_pki_token(const string
& token
)
30 return token
.compare(0, sizeof(PKI_ANS1_PREFIX
) - 1, PKI_ANS1_PREFIX
) == 0;
33 void rgw_get_token_id(const string
& token
, string
& token_id
)
35 if (!rgw_is_pki_token(token
)) {
40 unsigned char m
[CEPH_CRYPTO_MD5_DIGESTSIZE
];
43 // Allow use of MD5 digest in FIPS mode for non-cryptographic purposes
44 hash
.SetFlags(EVP_MD_CTX_FLAG_NON_FIPS_ALLOW
);
45 hash
.Update((const unsigned char *)token
.c_str(), token
.size());
48 char calc_md5
[CEPH_CRYPTO_MD5_DIGESTSIZE
* 2 + 1];
49 buf_to_hex(m
, CEPH_CRYPTO_MD5_DIGESTSIZE
, calc_md5
);
57 ApiVersion
CephCtxConfig::get_api_version() const noexcept
59 switch (g_ceph_context
->_conf
->rgw_keystone_api_version
) {
61 return ApiVersion::VER_3
;
63 return ApiVersion::VER_2
;
65 dout(0) << "ERROR: wrong Keystone API version: "
66 << g_ceph_context
->_conf
->rgw_keystone_api_version
67 << "; falling back to v2" << dendl
;
68 return ApiVersion::VER_2
;
72 std::string
CephCtxConfig::get_endpoint_url() const noexcept
74 static const std::string url
= g_ceph_context
->_conf
->rgw_keystone_url
;
76 if (url
.empty() || boost::algorithm::ends_with(url
, "/")) {
79 static const std::string url_normalised
= url
+ '/';
80 return url_normalised
;
85 const std::string
CephCtxConfig::empty
{""};
87 static inline std::string
read_secret(const std::string
& file_path
)
91 constexpr int16_t size
{1024};
96 ifstream
ifs(file_path
, ios::in
| ios::binary
);
99 auto sbuf
= ifs
.rdbuf();
100 auto len
= sbuf
->sgetn(buf
, size
);
105 boost::algorithm::trim(s
);
106 if (s
.back() == '\n')
112 std::string
CephCtxConfig::get_admin_token() const noexcept
114 auto& atv
= g_ceph_context
->_conf
->rgw_keystone_admin_token_path
;
116 return read_secret(atv
);
118 auto& atv
= g_ceph_context
->_conf
->rgw_keystone_admin_token
;
126 std::string
CephCtxConfig::get_admin_password() const noexcept
{
127 auto& apv
= g_ceph_context
->_conf
->rgw_keystone_admin_password_path
;
129 return read_secret(apv
);
131 auto& apv
= g_ceph_context
->_conf
->rgw_keystone_admin_password
;
139 int Service::get_admin_token(const DoutPrefixProvider
*dpp
,
140 CephContext
* const cct
,
141 TokenCache
& token_cache
,
142 const Config
& config
,
145 /* Let's check whether someone uses the deprecated "admin token" feauture
146 * based on a shared secret from keystone.conf file. */
147 const auto& admin_token
= config
.get_admin_token();
148 if (! admin_token
.empty()) {
149 token
= std::string(admin_token
.data(), admin_token
.length());
155 /* Try cache first before calling Keystone for a new admin token. */
156 if (token_cache
.find_admin(t
)) {
157 ldpp_dout(dpp
, 20) << "found cached admin token" << dendl
;
162 /* Call Keystone now. */
163 const auto ret
= issue_admin_token_request(dpp
, cct
, config
, t
);
165 token_cache
.add_admin(t
);
172 int Service::issue_admin_token_request(const DoutPrefixProvider
*dpp
,
173 CephContext
* const cct
,
174 const Config
& config
,
177 std::string token_url
= config
.get_endpoint_url();
178 if (token_url
.empty()) {
183 RGWGetKeystoneAdminToken
token_req(cct
, "POST", "", &token_bl
);
184 token_req
.append_header("Content-Type", "application/json");
187 const auto keystone_version
= config
.get_api_version();
188 if (keystone_version
== ApiVersion::VER_2
) {
189 AdminTokenRequestVer2
req_serializer(config
);
190 req_serializer
.dump(&jf
);
192 std::stringstream ss
;
194 token_req
.set_post_data(ss
.str());
195 token_req
.set_send_length(ss
.str().length());
196 token_url
.append("v2.0/tokens");
198 } else if (keystone_version
== ApiVersion::VER_3
) {
199 AdminTokenRequestVer3
req_serializer(config
);
200 req_serializer
.dump(&jf
);
202 std::stringstream ss
;
204 token_req
.set_post_data(ss
.str());
205 token_req
.set_send_length(ss
.str().length());
206 token_url
.append("v3/auth/tokens");
211 token_req
.set_url(token_url
);
213 const int ret
= token_req
.process(null_yield
);
218 /* Detect rejection earlier than during the token parsing step. */
219 if (token_req
.get_http_status() ==
220 RGWGetKeystoneAdminToken::HTTP_STATUS_UNAUTHORIZED
) {
224 if (t
.parse(dpp
, cct
, token_req
.get_subject_token(), token_bl
,
225 keystone_version
) != 0) {
232 int Service::get_keystone_barbican_token(const DoutPrefixProvider
*dpp
,
233 CephContext
* const cct
,
236 using keystone_config_t
= rgw::keystone::CephCtxConfig
;
237 using keystone_cache_t
= rgw::keystone::TokenCache
;
239 auto& config
= keystone_config_t::get_instance();
240 auto& token_cache
= keystone_cache_t::get_instance
<keystone_config_t
>();
242 std::string token_url
= config
.get_endpoint_url();
243 if (token_url
.empty()) {
247 rgw::keystone::TokenEnvelope t
;
249 /* Try cache first. */
250 if (token_cache
.find_barbican(t
)) {
251 ldpp_dout(dpp
, 20) << "found cached barbican token" << dendl
;
257 RGWKeystoneHTTPTransceiver
token_req(cct
, "POST", "", &token_bl
);
258 token_req
.append_header("Content-Type", "application/json");
261 const auto keystone_version
= config
.get_api_version();
262 if (keystone_version
== ApiVersion::VER_2
) {
263 rgw::keystone::BarbicanTokenRequestVer2
req_serializer(cct
);
264 req_serializer
.dump(&jf
);
266 std::stringstream ss
;
268 token_req
.set_post_data(ss
.str());
269 token_req
.set_send_length(ss
.str().length());
270 token_url
.append("v2.0/tokens");
272 } else if (keystone_version
== ApiVersion::VER_3
) {
273 BarbicanTokenRequestVer3
req_serializer(cct
);
274 req_serializer
.dump(&jf
);
276 std::stringstream ss
;
278 token_req
.set_post_data(ss
.str());
279 token_req
.set_send_length(ss
.str().length());
280 token_url
.append("v3/auth/tokens");
285 token_req
.set_url(token_url
);
287 ldpp_dout(dpp
, 20) << "Requesting secret from barbican url=" << token_url
<< dendl
;
288 const int ret
= token_req
.process(null_yield
);
290 ldpp_dout(dpp
, 20) << "Barbican process error:" << token_bl
.c_str() << dendl
;
294 /* Detect rejection earlier than during the token parsing step. */
295 if (token_req
.get_http_status() ==
296 RGWKeystoneHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED
) {
300 if (t
.parse(dpp
, cct
, token_req
.get_subject_token(), token_bl
,
301 keystone_version
) != 0) {
305 token_cache
.add_barbican(t
);
311 bool TokenEnvelope::has_role(const std::string
& r
) const
313 list
<Role
>::const_iterator iter
;
314 for (iter
= roles
.cbegin(); iter
!= roles
.cend(); ++iter
) {
315 if (fnmatch(r
.c_str(), ((*iter
).name
.c_str()), 0) == 0) {
322 int TokenEnvelope::parse(const DoutPrefixProvider
*dpp
,
323 CephContext
* const cct
,
324 const std::string
& token_str
,
325 ceph::bufferlist
& bl
,
326 const ApiVersion version
)
329 if (! parser
.parse(bl
.c_str(), bl
.length())) {
330 ldpp_dout(dpp
, 0) << "Keystone token parse error: malformed json" << dendl
;
334 JSONObjIter token_iter
= parser
.find_first("token");
335 JSONObjIter access_iter
= parser
.find_first("access");
338 if (version
== rgw::keystone::ApiVersion::VER_2
) {
339 if (! access_iter
.end()) {
340 decode_v2(*access_iter
);
341 } else if (! token_iter
.end()) {
342 /* TokenEnvelope structure doesn't follow Identity API v2, so let's
343 * fallback to v3. Otherwise we can assume it's wrongly formatted.
344 * The whole mechanism is a workaround for s3_token middleware that
345 * speaks in v2 disregarding the promise to go with v3. */
346 decode_v3(*token_iter
);
348 /* Identity v3 conveys the token inforamtion not as a part of JSON but
349 * in the X-Subject-Token HTTP header we're getting from caller. */
350 token
.id
= token_str
;
354 } else if (version
== rgw::keystone::ApiVersion::VER_3
) {
355 if (! token_iter
.end()) {
356 decode_v3(*token_iter
);
357 /* v3 suceeded. We have to fill token.id from external input as it
358 * isn't a part of the JSON response anymore. It has been moved
359 * to X-Subject-Token HTTP header instead. */
360 token
.id
= token_str
;
361 } else if (! access_iter
.end()) {
362 /* If the token cannot be parsed according to V3, try V2. */
363 decode_v2(*access_iter
);
370 } catch (const JSONDecoder::err
& err
) {
371 ldpp_dout(dpp
, 0) << "Keystone token parse error: " << err
.what() << dendl
;
378 bool TokenCache::find(const std::string
& token_id
,
379 rgw::keystone::TokenEnvelope
& token
)
381 std::lock_guard l
{lock
};
382 return find_locked(token_id
, token
);
385 bool TokenCache::find_locked(const std::string
& token_id
,
386 rgw::keystone::TokenEnvelope
& token
)
388 ceph_assert(ceph_mutex_is_locked_by_me(lock
));
389 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
390 if (iter
== tokens
.end()) {
391 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_miss
);
395 token_entry
& entry
= iter
->second
;
396 tokens_lru
.erase(entry
.lru_iter
);
398 if (entry
.token
.expired()) {
400 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_hit
);
405 tokens_lru
.push_front(token_id
);
406 entry
.lru_iter
= tokens_lru
.begin();
408 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_hit
);
413 bool TokenCache::find_admin(rgw::keystone::TokenEnvelope
& token
)
415 std::lock_guard l
{lock
};
417 return find_locked(admin_token_id
, token
);
420 bool TokenCache::find_barbican(rgw::keystone::TokenEnvelope
& token
)
422 std::lock_guard l
{lock
};
424 return find_locked(barbican_token_id
, token
);
427 void TokenCache::add(const std::string
& token_id
,
428 const rgw::keystone::TokenEnvelope
& token
)
430 std::lock_guard l
{lock
};
431 add_locked(token_id
, token
);
434 void TokenCache::add_locked(const std::string
& token_id
,
435 const rgw::keystone::TokenEnvelope
& token
)
437 ceph_assert(ceph_mutex_is_locked_by_me(lock
));
438 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
439 if (iter
!= tokens
.end()) {
440 token_entry
& e
= iter
->second
;
441 tokens_lru
.erase(e
.lru_iter
);
444 tokens_lru
.push_front(token_id
);
445 token_entry
& entry
= tokens
[token_id
];
447 entry
.lru_iter
= tokens_lru
.begin();
449 while (tokens_lru
.size() > max
) {
450 list
<string
>::reverse_iterator riter
= tokens_lru
.rbegin();
451 iter
= tokens
.find(*riter
);
452 ceph_assert(iter
!= tokens
.end());
454 tokens_lru
.pop_back();
458 void TokenCache::add_admin(const rgw::keystone::TokenEnvelope
& token
)
460 std::lock_guard l
{lock
};
462 rgw_get_token_id(token
.token
.id
, admin_token_id
);
463 add_locked(admin_token_id
, token
);
466 void TokenCache::add_barbican(const rgw::keystone::TokenEnvelope
& token
)
468 std::lock_guard l
{lock
};
470 rgw_get_token_id(token
.token
.id
, barbican_token_id
);
471 add_locked(barbican_token_id
, token
);
474 void TokenCache::invalidate(const DoutPrefixProvider
*dpp
, const std::string
& token_id
)
476 std::lock_guard l
{lock
};
477 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
478 if (iter
== tokens
.end())
481 ldpp_dout(dpp
, 20) << "invalidating revoked token id=" << token_id
<< dendl
;
482 token_entry
& e
= iter
->second
;
483 tokens_lru
.erase(e
.lru_iter
);
487 bool TokenCache::going_down() const
492 }; /* namespace keystone */
493 }; /* namespace rgw */
495 void rgw::keystone::TokenEnvelope::Token::decode_json(JSONObj
*obj
)
497 string expires_iso8601
;
500 JSONDecoder::decode_json("id", id
, obj
, true);
501 JSONDecoder::decode_json("tenant", tenant_v2
, obj
, true);
502 JSONDecoder::decode_json("expires", expires_iso8601
, obj
, true);
504 if (parse_iso8601(expires_iso8601
.c_str(), &t
)) {
505 expires
= internal_timegm(&t
);
508 throw JSONDecoder::err("Failed to parse ISO8601 expiration date from Keystone response.");
512 void rgw::keystone::TokenEnvelope::Role::decode_json(JSONObj
*obj
)
514 JSONDecoder::decode_json("id", id
, obj
);
515 JSONDecoder::decode_json("name", name
, obj
, true);
518 void rgw::keystone::TokenEnvelope::Domain::decode_json(JSONObj
*obj
)
520 JSONDecoder::decode_json("id", id
, obj
, true);
521 JSONDecoder::decode_json("name", name
, obj
, true);
524 void rgw::keystone::TokenEnvelope::Project::decode_json(JSONObj
*obj
)
526 JSONDecoder::decode_json("id", id
, obj
, true);
527 JSONDecoder::decode_json("name", name
, obj
, true);
528 JSONDecoder::decode_json("domain", domain
, obj
);
531 void rgw::keystone::TokenEnvelope::User::decode_json(JSONObj
*obj
)
533 JSONDecoder::decode_json("id", id
, obj
, true);
534 JSONDecoder::decode_json("name", name
, obj
, true);
535 JSONDecoder::decode_json("domain", domain
, obj
);
536 JSONDecoder::decode_json("roles", roles_v2
, obj
);
539 void rgw::keystone::TokenEnvelope::decode_v3(JSONObj
* const root_obj
)
541 std::string expires_iso8601
;
543 JSONDecoder::decode_json("user", user
, root_obj
, true);
544 JSONDecoder::decode_json("expires_at", expires_iso8601
, root_obj
, true);
545 JSONDecoder::decode_json("roles", roles
, root_obj
, true);
546 JSONDecoder::decode_json("project", project
, root_obj
, true);
549 if (parse_iso8601(expires_iso8601
.c_str(), &t
)) {
550 token
.expires
= internal_timegm(&t
);
553 throw JSONDecoder::err("Failed to parse ISO8601 expiration date"
554 "from Keystone response.");
558 void rgw::keystone::TokenEnvelope::decode_v2(JSONObj
* const root_obj
)
560 JSONDecoder::decode_json("user", user
, root_obj
, true);
561 JSONDecoder::decode_json("token", token
, root_obj
, true);
563 roles
= user
.roles_v2
;
564 project
= token
.tenant_v2
;
567 /* This utility function shouldn't conflict with the overload of std::to_string
568 * provided by string_ref since Boost 1.54 as it's defined outside of the std
569 * namespace. I hope we'll remove it soon - just after merging the Matt's PR
570 * for bundled Boost. It would allow us to forget that CentOS 7 has Boost 1.53. */
571 static inline std::string
to_string(const std::string_view
& s
)
573 return std::string(s
.data(), s
.length());
576 void rgw::keystone::AdminTokenRequestVer2::dump(Formatter
* const f
) const
578 f
->open_object_section("token_request");
579 f
->open_object_section("auth");
580 f
->open_object_section("passwordCredentials");
581 encode_json("username", ::to_string(conf
.get_admin_user()), f
);
582 encode_json("password", ::to_string(conf
.get_admin_password()), f
);
584 encode_json("tenantName", ::to_string(conf
.get_admin_tenant()), f
);
589 void rgw::keystone::AdminTokenRequestVer3::dump(Formatter
* const f
) const
591 f
->open_object_section("token_request");
592 f
->open_object_section("auth");
593 f
->open_object_section("identity");
594 f
->open_array_section("methods");
595 f
->dump_string("", "password");
597 f
->open_object_section("password");
598 f
->open_object_section("user");
599 f
->open_object_section("domain");
600 encode_json("name", ::to_string(conf
.get_admin_domain()), f
);
602 encode_json("name", ::to_string(conf
.get_admin_user()), f
);
603 encode_json("password", ::to_string(conf
.get_admin_password()), f
);
607 f
->open_object_section("scope");
608 f
->open_object_section("project");
609 if (! conf
.get_admin_project().empty()) {
610 encode_json("name", ::to_string(conf
.get_admin_project()), f
);
612 encode_json("name", ::to_string(conf
.get_admin_tenant()), f
);
614 f
->open_object_section("domain");
615 encode_json("name", ::to_string(conf
.get_admin_domain()), f
);
623 void rgw::keystone::BarbicanTokenRequestVer2::dump(Formatter
* const f
) const
625 f
->open_object_section("token_request");
626 f
->open_object_section("auth");
627 f
->open_object_section("passwordCredentials");
628 encode_json("username", cct
->_conf
->rgw_keystone_barbican_user
, f
);
629 encode_json("password", cct
->_conf
->rgw_keystone_barbican_password
, f
);
631 encode_json("tenantName", cct
->_conf
->rgw_keystone_barbican_tenant
, f
);
636 void rgw::keystone::BarbicanTokenRequestVer3::dump(Formatter
* const f
) const
638 f
->open_object_section("token_request");
639 f
->open_object_section("auth");
640 f
->open_object_section("identity");
641 f
->open_array_section("methods");
642 f
->dump_string("", "password");
644 f
->open_object_section("password");
645 f
->open_object_section("user");
646 f
->open_object_section("domain");
647 encode_json("name", cct
->_conf
->rgw_keystone_barbican_domain
, f
);
649 encode_json("name", cct
->_conf
->rgw_keystone_barbican_user
, f
);
650 encode_json("password", cct
->_conf
->rgw_keystone_barbican_password
, f
);
654 f
->open_object_section("scope");
655 f
->open_object_section("project");
656 if (!cct
->_conf
->rgw_keystone_barbican_project
.empty()) {
657 encode_json("name", cct
->_conf
->rgw_keystone_barbican_project
, f
);
659 encode_json("name", cct
->_conf
->rgw_keystone_barbican_tenant
, f
);
661 f
->open_object_section("domain");
662 encode_json("name", cct
->_conf
->rgw_keystone_barbican_domain
, f
);