Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / rgw / rgw_keystone.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include <errno.h>
5 #include <fnmatch.h>
6
7 #include <boost/algorithm/string/predicate.hpp>
8
9 #include "common/errno.h"
10 #include "common/ceph_json.h"
11 #include "include/types.h"
12 #include "include/str_list.h"
13
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"
19
20 #define dout_context g_ceph_context
21 #define dout_subsys ceph_subsys_rgw
22
23 int rgw_open_cms_envelope(CephContext * const cct,
24                           const std::string& src,
25                           std::string& dst)             /* out */
26 {
27 #define BEGIN_CMS "-----BEGIN CMS-----"
28 #define END_CMS "-----END CMS-----"
29
30   int start = src.find(BEGIN_CMS);
31   if (start < 0) {
32     ldout(cct, 0) << "failed to find " << BEGIN_CMS << " in response" << dendl;
33     return -EINVAL;
34   }
35   start += sizeof(BEGIN_CMS) - 1;
36
37   int end = src.find(END_CMS);
38   if (end < 0) {
39     ldout(cct, 0) << "failed to find " << END_CMS << " in response" << dendl;
40     return -EINVAL;
41   }
42
43   string s = src.substr(start, end - start);
44
45   int pos = 0;
46
47   do {
48     int next = s.find('\n', pos);
49     if (next < 0) {
50       dst.append(s.substr(pos));
51       break;
52     } else {
53       dst.append(s.substr(pos, next - pos));
54     }
55     pos = next + 1;
56   } while (pos < (int)s.size());
57
58   return 0;
59 }
60
61 int rgw_decode_b64_cms(CephContext * const cct,
62                        const string& signed_b64,
63                        bufferlist& bl)
64 {
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();
69   char buf[len + 1];
70   buf[len] = '\0';
71
72   for (size_t i = 0; i < len; i++, src++) {
73     if (*src != '-') {
74       buf[i] = *src;
75     } else {
76       buf[i] = '/';
77     }
78   }
79
80   int ret = ceph_unarmor(dest, dest + signed_ber.length(), buf,
81                          buf + signed_b64.size());
82   if (ret < 0) {
83     ldout(cct, 0) << "ceph_unarmor() failed, ret=" << ret << dendl;
84     return ret;
85   }
86
87   bufferlist signed_ber_bl;
88   signed_ber_bl.append(signed_ber);
89
90   ret = ceph_decode_cms(cct, signed_ber_bl, bl);
91   if (ret < 0) {
92     ldout(cct, 0) << "ceph_decode_cms returned " << ret << dendl;
93     return ret;
94   }
95
96   return 0;
97 }
98
99 #define PKI_ANS1_PREFIX "MII"
100
101 bool rgw_is_pki_token(const string& token)
102 {
103   return token.compare(0, sizeof(PKI_ANS1_PREFIX) - 1, PKI_ANS1_PREFIX) == 0;
104 }
105
106 void rgw_get_token_id(const string& token, string& token_id)
107 {
108   if (!rgw_is_pki_token(token)) {
109     token_id = token;
110     return;
111   }
112
113   unsigned char m[CEPH_CRYPTO_MD5_DIGESTSIZE];
114
115   MD5 hash;
116   hash.Update((const byte *)token.c_str(), token.size());
117   hash.Final(m);
118
119   char calc_md5[CEPH_CRYPTO_MD5_DIGESTSIZE * 2 + 1];
120   buf_to_hex(m, CEPH_CRYPTO_MD5_DIGESTSIZE, calc_md5);
121   token_id = calc_md5;
122 }
123
124 bool rgw_decode_pki_token(CephContext * const cct,
125                           const string& token,
126                           bufferlist& bl)
127 {
128   if (!rgw_is_pki_token(token)) {
129     return false;
130   }
131
132   int ret = rgw_decode_b64_cms(cct, token, bl);
133   if (ret < 0) {
134     return false;
135   }
136
137   ldout(cct, 20) << "successfully decoded pki token" << dendl;
138
139   return true;
140 }
141
142
143 namespace rgw {
144 namespace keystone {
145
146 ApiVersion CephCtxConfig::get_api_version() const noexcept
147 {
148   switch (g_ceph_context->_conf->rgw_keystone_api_version) {
149   case 3:
150     return ApiVersion::VER_3;
151   case 2:
152     return ApiVersion::VER_2;
153   default:
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;
158   }
159 }
160
161 std::string CephCtxConfig::get_endpoint_url() const noexcept
162 {
163   static const std::string url = g_ceph_context->_conf->rgw_keystone_url;
164
165   if (url.empty() || boost::algorithm::ends_with(url, "/")) {
166     return url;
167   } else {
168     static const std::string url_normalised = url + '/';
169     return url_normalised;
170   }
171 }
172
173 int Service::get_admin_token(CephContext* const cct,
174                              TokenCache& token_cache,
175                              const Config& config,
176                              std::string& token)
177 {
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());
183     return 0;
184   }
185
186   TokenEnvelope t;
187
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;
191     token = t.token.id;
192     return 0;
193   }
194
195   /* Call Keystone now. */
196   const auto ret = issue_admin_token_request(cct, config, t);
197   if (! ret) {
198     token_cache.add_admin(t);
199     token = t.token.id;
200   }
201
202   return ret;
203 }
204
205 int Service::issue_admin_token_request(CephContext* const cct,
206                                        const Config& config,
207                                        TokenEnvelope& t)
208 {
209   std::string token_url = config.get_endpoint_url();
210   if (token_url.empty()) {
211     return -EINVAL;
212   }
213
214   bufferlist token_bl;
215   RGWGetKeystoneAdminToken token_req(cct, &token_bl);
216   token_req.append_header("Content-Type", "application/json");
217   JSONFormatter jf;
218
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);
223
224     std::stringstream ss;
225     jf.flush(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");
229
230   } else if (keystone_version == ApiVersion::VER_3) {
231     AdminTokenRequestVer3 req_serializer(config);
232     req_serializer.dump(&jf);
233
234     std::stringstream ss;
235     jf.flush(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");
239   } else {
240     return -ENOTSUP;
241   }
242
243   const int ret = token_req.process("POST", token_url.c_str());
244   if (ret < 0) {
245     return ret;
246   }
247
248   /* Detect rejection earlier than during the token parsing step. */
249   if (token_req.get_http_status() ==
250           RGWGetKeystoneAdminToken::HTTP_STATUS_UNAUTHORIZED) {
251     return -EACCES;
252   }
253
254   if (t.parse(cct, token_req.get_subject_token(), token_bl,
255               keystone_version) != 0) {
256     return -EINVAL;
257   }
258
259   return 0;
260 }
261
262 int Service::get_keystone_barbican_token(CephContext * const cct,
263                                          std::string& token)
264 {
265   using keystone_config_t = rgw::keystone::CephCtxConfig;
266   using keystone_cache_t = rgw::keystone::TokenCache;
267
268   auto& config = keystone_config_t::get_instance();
269   auto& token_cache = keystone_cache_t::get_instance<keystone_config_t>();
270
271   std::string token_url = config.get_endpoint_url();
272   if (token_url.empty()) {
273     return -EINVAL;
274   }
275
276   rgw::keystone::TokenEnvelope t;
277
278   /* Try cache first. */
279   if (token_cache.find_barbican(t)) {
280     ldout(cct, 20) << "found cached barbican token" << dendl;
281     token = t.token.id;
282     return 0;
283   }
284
285   bufferlist token_bl;
286   RGWKeystoneHTTPTransceiver token_req(cct, &token_bl);
287   token_req.append_header("Content-Type", "application/json");
288   JSONFormatter jf;
289
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);
294
295     std::stringstream ss;
296     jf.flush(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");
300
301   } else if (keystone_version == ApiVersion::VER_3) {
302     BarbicanTokenRequestVer3 req_serializer(cct);
303     req_serializer.dump(&jf);
304
305     std::stringstream ss;
306     jf.flush(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");
310   } else {
311     return -ENOTSUP;
312   }
313
314   ldout(cct, 20) << "Requesting secret from barbican url=" << token_url << dendl;
315   const int ret = token_req.process("POST", token_url.c_str());
316   if (ret < 0) {
317     ldout(cct, 20) << "Barbican process error:" << token_bl.c_str() << dendl;
318     return ret;
319   }
320
321   /* Detect rejection earlier than during the token parsing step. */
322   if (token_req.get_http_status() ==
323       RGWKeystoneHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
324     return -EACCES;
325   }
326
327   if (t.parse(cct, token_req.get_subject_token(), token_bl,
328               keystone_version) != 0) {
329     return -EINVAL;
330   }
331
332   token_cache.add_barbican(t);
333   token = t.token.id;
334   return 0;
335 }
336
337
338 bool TokenEnvelope::has_role(const std::string& r) const
339 {
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) {
343         return true;
344       }
345   }
346   return false;
347 }
348
349 int TokenEnvelope::parse(CephContext* const cct,
350                          const std::string& token_str,
351                          ceph::bufferlist& bl,
352                          const ApiVersion version)
353 {
354   JSONParser parser;
355   if (! parser.parse(bl.c_str(), bl.length())) {
356     ldout(cct, 0) << "Keystone token parse error: malformed json" << dendl;
357     return -EINVAL;
358   }
359
360   JSONObjIter token_iter = parser.find_first("token");
361   JSONObjIter access_iter = parser.find_first("access");
362
363   try {
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);
373
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;
377       } else {
378         return -EINVAL;
379       }
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);
390       } else {
391         return -EINVAL;
392       }
393     } else {
394       return -ENOTSUP;
395     }
396   } catch (JSONDecoder::err& err) {
397     ldout(cct, 0) << "Keystone token parse error: " << err.message << dendl;
398     return -EINVAL;
399   }
400
401   return 0;
402 }
403
404 bool TokenCache::find(const std::string& token_id,
405                       rgw::keystone::TokenEnvelope& token)
406 {
407   Mutex::Locker l(lock);
408   return find_locked(token_id, token);
409 }
410
411 bool TokenCache::find_locked(const std::string& token_id,
412                              rgw::keystone::TokenEnvelope& token)
413 {
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);
418     return false;
419   }
420
421   token_entry& entry = iter->second;
422   tokens_lru.erase(entry.lru_iter);
423
424   if (entry.token.expired()) {
425     tokens.erase(iter);
426     if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
427     return false;
428   }
429   token = entry.token;
430
431   tokens_lru.push_front(token_id);
432   entry.lru_iter = tokens_lru.begin();
433
434   if (perfcounter) perfcounter->inc(l_rgw_keystone_token_cache_hit);
435
436   return true;
437 }
438
439 bool TokenCache::find_admin(rgw::keystone::TokenEnvelope& token)
440 {
441   Mutex::Locker l(lock);
442
443   return find_locked(admin_token_id, token);
444 }
445
446 bool TokenCache::find_barbican(rgw::keystone::TokenEnvelope& token)
447 {
448   Mutex::Locker l(lock);
449
450   return find_locked(barbican_token_id, token);
451 }
452
453 void TokenCache::add(const std::string& token_id,
454                      const rgw::keystone::TokenEnvelope& token)
455 {
456   Mutex::Locker l(lock);
457   add_locked(token_id, token);
458 }
459
460 void TokenCache::add_locked(const std::string& token_id,
461                             const rgw::keystone::TokenEnvelope& token)
462 {
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);
468   }
469
470   tokens_lru.push_front(token_id);
471   token_entry& entry = tokens[token_id];
472   entry.token = token;
473   entry.lru_iter = tokens_lru.begin();
474
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());
479     tokens.erase(iter);
480     tokens_lru.pop_back();
481   }
482 }
483
484 void TokenCache::add_admin(const rgw::keystone::TokenEnvelope& token)
485 {
486   Mutex::Locker l(lock);
487
488   rgw_get_token_id(token.token.id, admin_token_id);
489   add_locked(admin_token_id, token);
490 }
491
492 void TokenCache::add_barbican(const rgw::keystone::TokenEnvelope& token)
493 {
494   Mutex::Locker l(lock);
495
496   rgw_get_token_id(token.token.id, barbican_token_id);
497   add_locked(barbican_token_id, token);
498 }
499
500 void TokenCache::invalidate(const std::string& token_id)
501 {
502   Mutex::Locker l(lock);
503   map<string, token_entry>::iterator iter = tokens.find(token_id);
504   if (iter == tokens.end())
505     return;
506
507   ldout(cct, 20) << "invalidating revoked token id=" << token_id << dendl;
508   token_entry& e = iter->second;
509   tokens_lru.erase(e.lru_iter);
510   tokens.erase(iter);
511 }
512
513 int TokenCache::RevokeThread::check_revoked()
514 {
515   std::string url;
516   std::string token;
517
518   bufferlist bl;
519   RGWGetRevokedTokens req(cct, &bl);
520
521   if (rgw::keystone::Service::get_admin_token(cct, *cache, config, token) < 0) {
522     return -EINVAL;
523   }
524
525   url = config.get_endpoint_url();
526   if (url.empty()) {
527     return -EINVAL;
528   }
529
530   req.append_header("X-Auth-Token", token);
531
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");
537   }
538
539   req.set_send_length(0);
540   int ret = req.process(url.c_str());
541   if (ret < 0) {
542     return ret;
543   }
544
545   bl.append((char)0); // NULL terminate for debug output
546
547   ldout(cct, 10) << "request returned " << bl.c_str() << dendl;
548
549   JSONParser parser;
550
551   if (!parser.parse(bl.c_str(), bl.length())) {
552     ldout(cct, 0) << "malformed json" << dendl;
553     return -EINVAL;
554   }
555
556   JSONObjIter iter = parser.find_first("signed");
557   if (iter.end()) {
558     ldout(cct, 0) << "revoked tokens response is missing signed section" << dendl;
559     return -EINVAL;
560   }
561
562   JSONObj *signed_obj = *iter;
563   const std::string signed_str = signed_obj->get_data();
564
565   ldout(cct, 10) << "signed=" << signed_str << dendl;
566
567   std::string signed_b64;
568   ret = rgw_open_cms_envelope(cct, signed_str, signed_b64);
569   if (ret < 0) {
570     return ret;
571   }
572
573   ldout(cct, 10) << "content=" << signed_b64 << dendl;
574   
575   bufferlist json;
576   ret = rgw_decode_b64_cms(cct, signed_b64, json);
577   if (ret < 0) {
578     return ret;
579   }
580
581   ldout(cct, 10) << "ceph_decode_cms: decoded: " << json.c_str() << dendl;
582
583   JSONParser list_parser;
584   if (!list_parser.parse(json.c_str(), json.length())) {
585     ldout(cct, 0) << "malformed json" << dendl;
586     return -EINVAL;
587   }
588
589   JSONObjIter revoked_iter = list_parser.find_first("revoked");
590   if (revoked_iter.end()) {
591     ldout(cct, 0) << "no revoked section in json" << dendl;
592     return -EINVAL;
593   }
594
595   JSONObj *revoked_obj = *revoked_iter;
596
597   JSONObjIter tokens_iter = revoked_obj->find_first();
598   for (; !tokens_iter.end(); ++tokens_iter) {
599     JSONObj *o = *tokens_iter;
600
601     JSONObj *token = o->find_obj("id");
602     if (!token) {
603       ldout(cct, 0) << "bad token in array, missing id" << dendl;
604       continue;
605     }
606
607     const std::string token_id = token->get_data();
608     cache->invalidate(token_id);
609   }
610   
611   return 0;
612 }
613
614 bool TokenCache::going_down() const
615 {
616   return down_flag;
617 }
618
619 void* TokenCache::RevokeThread::entry()
620 {
621   do {
622     ldout(cct, 2) << "keystone revoke thread: start" << dendl;
623     int r = check_revoked();
624     if (r < 0) {
625       ldout(cct, 0) << "ERROR: keystone revocation processing returned error r="
626                     << r << dendl;
627     }
628
629     if (cache->going_down()) {
630       break;
631     }
632
633     lock.Lock();
634     cond.WaitInterval(lock,
635                       utime_t(cct->_conf->rgw_keystone_revocation_interval, 0));
636     lock.Unlock();
637   } while (!cache->going_down());
638
639   return nullptr;
640 }
641
642 void TokenCache::RevokeThread::stop()
643 {
644   Mutex::Locker l(lock);
645   cond.Signal();
646 }
647
648 }; /* namespace keystone */
649 }; /* namespace rgw */