1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
7 #include <boost/algorithm/string/predicate.hpp>
9 #include "common/errno.h"
10 #include "common/ceph_json.h"
11 #include "include/types.h"
12 #include "include/str_list.h"
14 #include "rgw_common.h"
15 #include "rgw_keystone.h"
16 #include "common/ceph_crypto_cms.h"
17 #include "common/armor.h"
18 #include "common/Cond.h"
20 #define dout_context g_ceph_context
21 #define dout_subsys ceph_subsys_rgw
23 int rgw_open_cms_envelope(CephContext
* const cct
,
24 const std::string
& src
,
25 std::string
& dst
) /* out */
27 #define BEGIN_CMS "-----BEGIN CMS-----"
28 #define END_CMS "-----END CMS-----"
30 int start
= src
.find(BEGIN_CMS
);
32 ldout(cct
, 0) << "failed to find " << BEGIN_CMS
<< " in response" << dendl
;
35 start
+= sizeof(BEGIN_CMS
) - 1;
37 int end
= src
.find(END_CMS
);
39 ldout(cct
, 0) << "failed to find " << END_CMS
<< " in response" << dendl
;
43 string s
= src
.substr(start
, end
- start
);
48 int next
= s
.find('\n', pos
);
50 dst
.append(s
.substr(pos
));
53 dst
.append(s
.substr(pos
, next
- pos
));
56 } while (pos
< (int)s
.size());
61 int rgw_decode_b64_cms(CephContext
* const cct
,
62 const string
& signed_b64
,
65 bufferptr
signed_ber(signed_b64
.size() * 2);
66 char *dest
= signed_ber
.c_str();
67 const char *src
= signed_b64
.c_str();
68 size_t len
= signed_b64
.size();
72 for (size_t i
= 0; i
< len
; i
++, src
++) {
80 int ret
= ceph_unarmor(dest
, dest
+ signed_ber
.length(), buf
,
81 buf
+ signed_b64
.size());
83 ldout(cct
, 0) << "ceph_unarmor() failed, ret=" << ret
<< dendl
;
87 bufferlist signed_ber_bl
;
88 signed_ber_bl
.append(signed_ber
);
90 ret
= ceph_decode_cms(cct
, signed_ber_bl
, bl
);
92 ldout(cct
, 0) << "ceph_decode_cms returned " << ret
<< dendl
;
99 #define PKI_ANS1_PREFIX "MII"
101 bool rgw_is_pki_token(const string
& token
)
103 return token
.compare(0, sizeof(PKI_ANS1_PREFIX
) - 1, PKI_ANS1_PREFIX
) == 0;
106 void rgw_get_token_id(const string
& token
, string
& token_id
)
108 if (!rgw_is_pki_token(token
)) {
113 unsigned char m
[CEPH_CRYPTO_MD5_DIGESTSIZE
];
116 hash
.Update((const byte
*)token
.c_str(), token
.size());
119 char calc_md5
[CEPH_CRYPTO_MD5_DIGESTSIZE
* 2 + 1];
120 buf_to_hex(m
, CEPH_CRYPTO_MD5_DIGESTSIZE
, calc_md5
);
124 bool rgw_decode_pki_token(CephContext
* const cct
,
128 if (!rgw_is_pki_token(token
)) {
132 int ret
= rgw_decode_b64_cms(cct
, token
, bl
);
137 ldout(cct
, 20) << "successfully decoded pki token" << dendl
;
146 ApiVersion
CephCtxConfig::get_api_version() const noexcept
148 switch (g_ceph_context
->_conf
->rgw_keystone_api_version
) {
150 return ApiVersion::VER_3
;
152 return ApiVersion::VER_2
;
154 dout(0) << "ERROR: wrong Keystone API version: "
155 << g_ceph_context
->_conf
->rgw_keystone_api_version
156 << "; falling back to v2" << dendl
;
157 return ApiVersion::VER_2
;
161 std::string
CephCtxConfig::get_endpoint_url() const noexcept
163 static const std::string url
= g_ceph_context
->_conf
->rgw_keystone_url
;
165 if (url
.empty() || boost::algorithm::ends_with(url
, "/")) {
168 static const std::string url_normalised
= url
+ '/';
169 return url_normalised
;
173 int Service::get_admin_token(CephContext
* const cct
,
174 TokenCache
& token_cache
,
175 const Config
& config
,
178 /* Let's check whether someone uses the deprecated "admin token" feauture
179 * based on a shared secret from keystone.conf file. */
180 const auto& admin_token
= config
.get_admin_token();
181 if (! admin_token
.empty()) {
182 token
= std::string(admin_token
.data(), admin_token
.length());
188 /* Try cache first before calling Keystone for a new admin token. */
189 if (token_cache
.find_admin(t
)) {
190 ldout(cct
, 20) << "found cached admin token" << dendl
;
195 /* Call Keystone now. */
196 const auto ret
= issue_admin_token_request(cct
, config
, t
);
198 token_cache
.add_admin(t
);
205 int Service::issue_admin_token_request(CephContext
* const cct
,
206 const Config
& config
,
209 std::string token_url
= config
.get_endpoint_url();
210 if (token_url
.empty()) {
215 RGWGetKeystoneAdminToken
token_req(cct
, &token_bl
);
216 token_req
.append_header("Content-Type", "application/json");
219 const auto keystone_version
= config
.get_api_version();
220 if (keystone_version
== ApiVersion::VER_2
) {
221 AdminTokenRequestVer2
req_serializer(config
);
222 req_serializer
.dump(&jf
);
224 std::stringstream ss
;
226 token_req
.set_post_data(ss
.str());
227 token_req
.set_send_length(ss
.str().length());
228 token_url
.append("v2.0/tokens");
230 } else if (keystone_version
== ApiVersion::VER_3
) {
231 AdminTokenRequestVer3
req_serializer(config
);
232 req_serializer
.dump(&jf
);
234 std::stringstream ss
;
236 token_req
.set_post_data(ss
.str());
237 token_req
.set_send_length(ss
.str().length());
238 token_url
.append("v3/auth/tokens");
243 const int ret
= token_req
.process("POST", token_url
.c_str());
248 /* Detect rejection earlier than during the token parsing step. */
249 if (token_req
.get_http_status() ==
250 RGWGetKeystoneAdminToken::HTTP_STATUS_UNAUTHORIZED
) {
254 if (t
.parse(cct
, token_req
.get_subject_token(), token_bl
,
255 keystone_version
) != 0) {
262 int Service::get_keystone_barbican_token(CephContext
* const cct
,
265 using keystone_config_t
= rgw::keystone::CephCtxConfig
;
266 using keystone_cache_t
= rgw::keystone::TokenCache
;
268 auto& config
= keystone_config_t::get_instance();
269 auto& token_cache
= keystone_cache_t::get_instance
<keystone_config_t
>();
271 std::string token_url
= config
.get_endpoint_url();
272 if (token_url
.empty()) {
276 rgw::keystone::TokenEnvelope t
;
278 /* Try cache first. */
279 if (token_cache
.find_barbican(t
)) {
280 ldout(cct
, 20) << "found cached barbican token" << dendl
;
286 RGWKeystoneHTTPTransceiver
token_req(cct
, &token_bl
);
287 token_req
.append_header("Content-Type", "application/json");
290 const auto keystone_version
= config
.get_api_version();
291 if (keystone_version
== ApiVersion::VER_2
) {
292 rgw::keystone::BarbicanTokenRequestVer2
req_serializer(cct
);
293 req_serializer
.dump(&jf
);
295 std::stringstream ss
;
297 token_req
.set_post_data(ss
.str());
298 token_req
.set_send_length(ss
.str().length());
299 token_url
.append("v2.0/tokens");
301 } else if (keystone_version
== ApiVersion::VER_3
) {
302 BarbicanTokenRequestVer3
req_serializer(cct
);
303 req_serializer
.dump(&jf
);
305 std::stringstream ss
;
307 token_req
.set_post_data(ss
.str());
308 token_req
.set_send_length(ss
.str().length());
309 token_url
.append("v3/auth/tokens");
314 ldout(cct
, 20) << "Requesting secret from barbican url=" << token_url
<< dendl
;
315 const int ret
= token_req
.process("POST", token_url
.c_str());
317 ldout(cct
, 20) << "Barbican process error:" << token_bl
.c_str() << dendl
;
321 /* Detect rejection earlier than during the token parsing step. */
322 if (token_req
.get_http_status() ==
323 RGWKeystoneHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED
) {
327 if (t
.parse(cct
, token_req
.get_subject_token(), token_bl
,
328 keystone_version
) != 0) {
332 token_cache
.add_barbican(t
);
338 bool TokenEnvelope::has_role(const std::string
& r
) const
340 list
<Role
>::const_iterator iter
;
341 for (iter
= roles
.cbegin(); iter
!= roles
.cend(); ++iter
) {
342 if (fnmatch(r
.c_str(), ((*iter
).name
.c_str()), 0) == 0) {
349 int TokenEnvelope::parse(CephContext
* const cct
,
350 const std::string
& token_str
,
351 ceph::bufferlist
& bl
,
352 const ApiVersion version
)
355 if (! parser
.parse(bl
.c_str(), bl
.length())) {
356 ldout(cct
, 0) << "Keystone token parse error: malformed json" << dendl
;
360 JSONObjIter token_iter
= parser
.find_first("token");
361 JSONObjIter access_iter
= parser
.find_first("access");
364 if (version
== rgw::keystone::ApiVersion::VER_2
) {
365 if (! access_iter
.end()) {
366 decode_v2(*access_iter
);
367 } else if (! token_iter
.end()) {
368 /* TokenEnvelope structure doesn't follow Identity API v2, so let's
369 * fallback to v3. Otherwise we can assume it's wrongly formatted.
370 * The whole mechanism is a workaround for s3_token middleware that
371 * speaks in v2 disregarding the promise to go with v3. */
372 decode_v3(*token_iter
);
374 /* Identity v3 conveys the token inforamtion not as a part of JSON but
375 * in the X-Subject-Token HTTP header we're getting from caller. */
376 token
.id
= token_str
;
380 } else if (version
== rgw::keystone::ApiVersion::VER_3
) {
381 if (! token_iter
.end()) {
382 decode_v3(*token_iter
);
383 /* v3 suceeded. We have to fill token.id from external input as it
384 * isn't a part of the JSON response anymore. It has been moved
385 * to X-Subject-Token HTTP header instead. */
386 token
.id
= token_str
;
387 } else if (! access_iter
.end()) {
388 /* If the token cannot be parsed according to V3, try V2. */
389 decode_v2(*access_iter
);
396 } catch (JSONDecoder::err
& err
) {
397 ldout(cct
, 0) << "Keystone token parse error: " << err
.message
<< dendl
;
404 bool TokenCache::find(const std::string
& token_id
,
405 rgw::keystone::TokenEnvelope
& token
)
407 Mutex::Locker
l(lock
);
408 return find_locked(token_id
, token
);
411 bool TokenCache::find_locked(const std::string
& token_id
,
412 rgw::keystone::TokenEnvelope
& token
)
414 assert(lock
.is_locked_by_me());
415 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
416 if (iter
== tokens
.end()) {
417 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_miss
);
421 token_entry
& entry
= iter
->second
;
422 tokens_lru
.erase(entry
.lru_iter
);
424 if (entry
.token
.expired()) {
426 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_hit
);
431 tokens_lru
.push_front(token_id
);
432 entry
.lru_iter
= tokens_lru
.begin();
434 if (perfcounter
) perfcounter
->inc(l_rgw_keystone_token_cache_hit
);
439 bool TokenCache::find_admin(rgw::keystone::TokenEnvelope
& token
)
441 Mutex::Locker
l(lock
);
443 return find_locked(admin_token_id
, token
);
446 bool TokenCache::find_barbican(rgw::keystone::TokenEnvelope
& token
)
448 Mutex::Locker
l(lock
);
450 return find_locked(barbican_token_id
, token
);
453 void TokenCache::add(const std::string
& token_id
,
454 const rgw::keystone::TokenEnvelope
& token
)
456 Mutex::Locker
l(lock
);
457 add_locked(token_id
, token
);
460 void TokenCache::add_locked(const std::string
& token_id
,
461 const rgw::keystone::TokenEnvelope
& token
)
463 assert(lock
.is_locked_by_me());
464 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
465 if (iter
!= tokens
.end()) {
466 token_entry
& e
= iter
->second
;
467 tokens_lru
.erase(e
.lru_iter
);
470 tokens_lru
.push_front(token_id
);
471 token_entry
& entry
= tokens
[token_id
];
473 entry
.lru_iter
= tokens_lru
.begin();
475 while (tokens_lru
.size() > max
) {
476 list
<string
>::reverse_iterator riter
= tokens_lru
.rbegin();
477 iter
= tokens
.find(*riter
);
478 assert(iter
!= tokens
.end());
480 tokens_lru
.pop_back();
484 void TokenCache::add_admin(const rgw::keystone::TokenEnvelope
& token
)
486 Mutex::Locker
l(lock
);
488 rgw_get_token_id(token
.token
.id
, admin_token_id
);
489 add_locked(admin_token_id
, token
);
492 void TokenCache::add_barbican(const rgw::keystone::TokenEnvelope
& token
)
494 Mutex::Locker
l(lock
);
496 rgw_get_token_id(token
.token
.id
, barbican_token_id
);
497 add_locked(barbican_token_id
, token
);
500 void TokenCache::invalidate(const std::string
& token_id
)
502 Mutex::Locker
l(lock
);
503 map
<string
, token_entry
>::iterator iter
= tokens
.find(token_id
);
504 if (iter
== tokens
.end())
507 ldout(cct
, 20) << "invalidating revoked token id=" << token_id
<< dendl
;
508 token_entry
& e
= iter
->second
;
509 tokens_lru
.erase(e
.lru_iter
);
513 int TokenCache::RevokeThread::check_revoked()
519 RGWGetRevokedTokens
req(cct
, &bl
);
521 if (rgw::keystone::Service::get_admin_token(cct
, *cache
, config
, token
) < 0) {
525 url
= config
.get_endpoint_url();
530 req
.append_header("X-Auth-Token", token
);
532 const auto keystone_version
= config
.get_api_version();
533 if (keystone_version
== rgw::keystone::ApiVersion::VER_2
) {
534 url
.append("v2.0/tokens/revoked");
535 } else if (keystone_version
== rgw::keystone::ApiVersion::VER_3
) {
536 url
.append("v3/auth/tokens/OS-PKI/revoked");
539 req
.set_send_length(0);
540 int ret
= req
.process(url
.c_str());
545 bl
.append((char)0); // NULL terminate for debug output
547 ldout(cct
, 10) << "request returned " << bl
.c_str() << dendl
;
551 if (!parser
.parse(bl
.c_str(), bl
.length())) {
552 ldout(cct
, 0) << "malformed json" << dendl
;
556 JSONObjIter iter
= parser
.find_first("signed");
558 ldout(cct
, 0) << "revoked tokens response is missing signed section" << dendl
;
562 JSONObj
*signed_obj
= *iter
;
563 const std::string signed_str
= signed_obj
->get_data();
565 ldout(cct
, 10) << "signed=" << signed_str
<< dendl
;
567 std::string signed_b64
;
568 ret
= rgw_open_cms_envelope(cct
, signed_str
, signed_b64
);
573 ldout(cct
, 10) << "content=" << signed_b64
<< dendl
;
576 ret
= rgw_decode_b64_cms(cct
, signed_b64
, json
);
581 ldout(cct
, 10) << "ceph_decode_cms: decoded: " << json
.c_str() << dendl
;
583 JSONParser list_parser
;
584 if (!list_parser
.parse(json
.c_str(), json
.length())) {
585 ldout(cct
, 0) << "malformed json" << dendl
;
589 JSONObjIter revoked_iter
= list_parser
.find_first("revoked");
590 if (revoked_iter
.end()) {
591 ldout(cct
, 0) << "no revoked section in json" << dendl
;
595 JSONObj
*revoked_obj
= *revoked_iter
;
597 JSONObjIter tokens_iter
= revoked_obj
->find_first();
598 for (; !tokens_iter
.end(); ++tokens_iter
) {
599 JSONObj
*o
= *tokens_iter
;
601 JSONObj
*token
= o
->find_obj("id");
603 ldout(cct
, 0) << "bad token in array, missing id" << dendl
;
607 const std::string token_id
= token
->get_data();
608 cache
->invalidate(token_id
);
614 bool TokenCache::going_down() const
619 void* TokenCache::RevokeThread::entry()
622 ldout(cct
, 2) << "keystone revoke thread: start" << dendl
;
623 int r
= check_revoked();
625 ldout(cct
, 0) << "ERROR: keystone revocation processing returned error r="
629 if (cache
->going_down()) {
634 cond
.WaitInterval(lock
,
635 utime_t(cct
->_conf
->rgw_keystone_revocation_interval
, 0));
637 } while (!cache
->going_down());
642 void TokenCache::RevokeThread::stop()
644 Mutex::Locker
l(lock
);
648 }; /* namespace keystone */
649 }; /* namespace rgw */