Fix some bugs when testing opensds ansible
[stor4nfv.git] / src / ceph / src / rgw / rgw_auth_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 <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_keystone.h"
21 #include "rgw_rest_s3.h"
22 #include "rgw_auth_s3.h"
23
24 #include "common/ceph_crypto_cms.h"
25 #include "common/armor.h"
26 #include "common/Cond.h"
27
28 #define dout_subsys ceph_subsys_rgw
29
30
31 namespace rgw {
32 namespace auth {
33 namespace keystone {
34
35 bool
36 TokenEngine::is_applicable(const std::string& token) const noexcept
37 {
38   return ! token.empty() && ! cct->_conf->rgw_keystone_url.empty();
39 }
40
41 TokenEngine::token_envelope_t
42 TokenEngine::decode_pki_token(const std::string& token) const
43 {
44   ceph::buffer::list token_body_bl;
45   int ret = rgw_decode_b64_cms(cct, token, token_body_bl);
46   if (ret < 0) {
47     ldout(cct, 20) << "cannot decode pki token" << dendl;
48     throw ret;
49   } else {
50     ldout(cct, 20) << "successfully decoded pki token" << dendl;
51   }
52
53   TokenEngine::token_envelope_t token_body;
54   ret = token_body.parse(cct, token, token_body_bl, config.get_api_version());
55   if (ret < 0) {
56     throw ret;
57   }
58
59   return token_body;
60 }
61
62 boost::optional<TokenEngine::token_envelope_t>
63 TokenEngine::get_from_keystone(const std::string& token) const
64 {
65   /* Unfortunately, we can't use the short form of "using" here. It's because
66    * we're aliasing a class' member, not namespace. */
67   using RGWValidateKeystoneToken = \
68     rgw::keystone::Service::RGWValidateKeystoneToken;
69
70   /* The container for plain response obtained from Keystone. It will be
71    * parsed token_envelope_t::parse method. */
72   ceph::bufferlist token_body_bl;
73   RGWValidateKeystoneToken validate(cct, &token_body_bl);
74
75   std::string url = config.get_endpoint_url();
76   if (url.empty()) {
77     throw -EINVAL;
78   }
79
80   const auto keystone_version = config.get_api_version();
81   if (keystone_version == rgw::keystone::ApiVersion::VER_2) {
82     url.append("v2.0/tokens/" + token);
83   } else if (keystone_version == rgw::keystone::ApiVersion::VER_3) {
84     url.append("v3/auth/tokens");
85     validate.append_header("X-Subject-Token", token);
86   }
87
88   std::string admin_token;
89   if (rgw::keystone::Service::get_admin_token(cct, token_cache, config,
90                                               admin_token) < 0) {
91     throw -EINVAL;
92   }
93
94   validate.append_header("X-Auth-Token", admin_token);
95   validate.set_send_length(0);
96
97   int ret = validate.process(url.c_str());
98   if (ret < 0) {
99     throw ret;
100   }
101
102   /* NULL terminate for debug output. */
103   token_body_bl.append(static_cast<char>(0));
104   ldout(cct, 20) << "received response status=" << validate.get_http_status()
105                  << ", body=" << token_body_bl.c_str() << dendl;
106
107   /* Detect Keystone rejection earlier than during the token parsing.
108    * Although failure at the parsing phase doesn't impose a threat,
109    * this allows to return proper error code (EACCESS instead of EINVAL
110    * or similar) and thus improves logging. */
111   if (validate.get_http_status() ==
112           /* Most likely: wrong admin credentials or admin token. */
113           RGWValidateKeystoneToken::HTTP_STATUS_UNAUTHORIZED ||
114       validate.get_http_status() ==
115           /* Most likely: non-existent token supplied by the client. */
116           RGWValidateKeystoneToken::HTTP_STATUS_NOTFOUND) {
117     return boost::none;
118   }
119
120   TokenEngine::token_envelope_t token_body;
121   ret = token_body.parse(cct, token, token_body_bl, config.get_api_version());
122   if (ret < 0) {
123     throw ret;
124   }
125
126   return token_body;
127 }
128
129 TokenEngine::auth_info_t
130 TokenEngine::get_creds_info(const TokenEngine::token_envelope_t& token,
131                             const std::vector<std::string>& admin_roles
132                            ) const noexcept
133 {
134   using acct_privilege_t = rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t;
135
136   /* Check whether the user has an admin status. */
137   acct_privilege_t level = acct_privilege_t::IS_PLAIN_ACCT;
138   for (const auto& admin_role : admin_roles) {
139     if (token.has_role(admin_role)) {
140       level = acct_privilege_t::IS_ADMIN_ACCT;
141       break;
142     }
143   }
144
145   return auth_info_t {
146     /* Suggested account name for the authenticated user. */
147     rgw_user(token.get_project_id()),
148     /* User's display name (aka real name). */
149     token.get_project_name(),
150     /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
151      * the access rights through the perm_mask. At least at this layer. */
152     RGW_PERM_FULL_CONTROL,
153     level,
154     TYPE_KEYSTONE,
155   };
156 }
157
158 static inline const std::string
159 make_spec_item(const std::string& tenant, const std::string& id)
160 {
161   return tenant + ":" + id;
162 }
163
164 TokenEngine::acl_strategy_t
165 TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t& token) const
166 {
167   /* The primary identity is constructed upon UUIDs. */
168   const auto& tenant_uuid = token.get_project_id();
169   const auto& user_uuid = token.get_user_id();
170
171   /* For Keystone v2 an alias may be also used. */
172   const auto& tenant_name = token.get_project_name();
173   const auto& user_name = token.get_user_name();
174
175   /* Construct all possible combinations including Swift's wildcards. */
176   const std::array<std::string, 6> allowed_items = {
177     make_spec_item(tenant_uuid, user_uuid),
178     make_spec_item(tenant_name, user_name),
179
180     /* Wildcards. */
181     make_spec_item(tenant_uuid, "*"),
182     make_spec_item(tenant_name, "*"),
183     make_spec_item("*", user_uuid),
184     make_spec_item("*", user_name),
185   };
186
187   /* Lambda will obtain a copy of (not a reference to!) allowed_items. */
188   return [allowed_items](const rgw::auth::Identity::aclspec_t& aclspec) {
189     uint32_t perm = 0;
190
191     for (const auto& allowed_item : allowed_items) {
192       const auto iter = aclspec.find(allowed_item);
193
194       if (std::end(aclspec) != iter) {
195         perm |= iter->second;
196       }
197     }
198
199     return perm;
200   };
201 }
202
203 TokenEngine::result_t
204 TokenEngine::authenticate(const std::string& token,
205                           const req_state* const s) const
206 {
207   boost::optional<TokenEngine::token_envelope_t> t;
208
209   /* This will be initialized on the first call to this method. In C++11 it's
210    * also thread-safe. */
211   static const struct RolesCacher {
212     RolesCacher(CephContext* const cct) {
213       get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain);
214       get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin);
215
216       /* Let's suppose that having an admin role implies also a regular one. */
217       plain.insert(std::end(plain), std::begin(admin), std::end(admin));
218     }
219
220     std::vector<std::string> plain;
221     std::vector<std::string> admin;
222   } roles(cct);
223
224   if (! is_applicable(token)) {
225     return result_t::deny();
226   }
227
228   /* Token ID is a concept that makes dealing with PKI tokens more effective.
229    * Instead of storing several kilobytes, a short hash can be burried. */
230   const auto& token_id = rgw_get_token_id(token);
231   ldout(cct, 20) << "token_id=" << token_id << dendl;
232
233   /* Check cache first. */
234   t = token_cache.find(token_id);
235   if (t) {
236     ldout(cct, 20) << "cached token.project.id=" << t->get_project_id()
237                    << dendl;
238     auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
239                                               get_creds_info(*t, roles.admin));
240     return result_t::grant(std::move(apl));
241   }
242
243   /* Retrieve token. */
244   if (rgw_is_pki_token(token)) {
245     try {
246       t = decode_pki_token(token);
247     } catch (...) {
248       /* Last resort. */
249       t = get_from_keystone(token);
250     }
251   } else {
252     /* Can't decode, just go to the Keystone server for validation. */
253     t = get_from_keystone(token);
254   }
255
256   if (! t) {
257     return result_t::deny(-EACCES);
258   }
259
260   /* Verify expiration. */
261   if (t->expired()) {
262     ldout(cct, 0) << "got expired token: " << t->get_project_name()
263                   << ":" << t->get_user_name()
264                   << " expired: " << t->get_expires() << dendl;
265     return result_t::deny(-EPERM);
266   }
267
268   /* Check for necessary roles. */
269   for (const auto& role : roles.plain) {
270     if (t->has_role(role) == true) {
271       ldout(cct, 0) << "validated token: " << t->get_project_name()
272                     << ":" << t->get_user_name()
273                     << " expires: " << t->get_expires() << dendl;
274       token_cache.add(token_id, *t);
275       auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
276                                             get_creds_info(*t, roles.admin));
277       return result_t::grant(std::move(apl));
278     }
279   }
280
281   ldout(cct, 0) << "user does not hold a matching role; required roles: "
282                 << g_conf->rgw_keystone_accepted_roles << dendl;
283
284   return result_t::deny(-EPERM);
285 }
286
287
288 /*
289  * Try to validate S3 auth against keystone s3token interface
290  */
291 std::pair<boost::optional<rgw::keystone::TokenEnvelope>, int>
292 EC2Engine::get_from_keystone(const boost::string_view& access_key_id,
293                              const std::string& string_to_sign,
294                              const boost::string_view& signature) const
295 {
296   /* prepare keystone url */
297   std::string keystone_url = config.get_endpoint_url();
298   if (keystone_url.empty()) {
299     throw -EINVAL;
300   }
301
302   const auto api_version = config.get_api_version();
303   if (config.get_api_version() == rgw::keystone::ApiVersion::VER_3) {
304     keystone_url.append("v3/s3tokens");
305   } else {
306     keystone_url.append("v2.0/s3tokens");
307   }
308
309   /* get authentication token for Keystone. */
310   std::string admin_token;
311   int ret = rgw::keystone::Service::get_admin_token(cct, token_cache, config,
312                                                     admin_token);
313   if (ret < 0) {
314     ldout(cct, 2) << "s3 keystone: cannot get token for keystone access"
315                   << dendl;
316     throw ret;
317   }
318
319   using RGWValidateKeystoneToken
320     = rgw::keystone::Service::RGWValidateKeystoneToken;
321
322   /* The container for plain response obtained from Keystone. It will be
323    * parsed token_envelope_t::parse method. */
324   ceph::bufferlist token_body_bl;
325   RGWValidateKeystoneToken validate(cct, &token_body_bl);
326
327   /* set required headers for keystone request */
328   validate.append_header("X-Auth-Token", admin_token);
329   validate.append_header("Content-Type", "application/json");
330
331   /* check if we want to verify keystone's ssl certs */
332   validate.set_verify_ssl(cct->_conf->rgw_keystone_verify_ssl);
333
334   /* create json credentials request body */
335   JSONFormatter credentials(false);
336   credentials.open_object_section("");
337   credentials.open_object_section("credentials");
338   credentials.dump_string("access", sview2cstr(access_key_id).data());
339   credentials.dump_string("token", rgw::to_base64(string_to_sign));
340   credentials.dump_string("signature", sview2cstr(signature).data());
341   credentials.close_section();
342   credentials.close_section();
343
344   std::stringstream os;
345   credentials.flush(os);
346   validate.set_post_data(os.str());
347   validate.set_send_length(os.str().length());
348
349   /* send request */
350   ret = validate.process("POST", keystone_url.c_str());
351   if (ret < 0) {
352     ldout(cct, 2) << "s3 keystone: token validation ERROR: "
353                   << token_body_bl.c_str() << dendl;
354     throw ret;
355   }
356
357   /* if the supplied signature is wrong, we will get 401 from Keystone */
358   if (validate.get_http_status() ==
359           decltype(validate)::HTTP_STATUS_UNAUTHORIZED) {
360     return std::make_pair(boost::none, -ERR_SIGNATURE_NO_MATCH);
361   } else if (validate.get_http_status() ==
362           decltype(validate)::HTTP_STATUS_NOTFOUND) {
363     return std::make_pair(boost::none, -ERR_INVALID_ACCESS_KEY);
364   }
365
366   /* now parse response */
367   rgw::keystone::TokenEnvelope token_envelope;
368   ret = token_envelope.parse(cct, std::string(), token_body_bl, api_version);
369   if (ret < 0) {
370     ldout(cct, 2) << "s3 keystone: token parsing failed, ret=0" << ret
371                   << dendl;
372     throw ret;
373   }
374
375   return std::make_pair(std::move(token_envelope), 0);
376 }
377
378 EC2Engine::acl_strategy_t
379 EC2Engine::get_acl_strategy(const EC2Engine::token_envelope_t&) const
380 {
381   /* This is based on the assumption that the default acl strategy in
382    * get_perms_from_aclspec, will take care. Extra acl spec is not required. */
383   return nullptr;
384 }
385
386 EC2Engine::auth_info_t
387 EC2Engine::get_creds_info(const EC2Engine::token_envelope_t& token,
388                           const std::vector<std::string>& admin_roles
389                          ) const noexcept
390 {
391   using acct_privilege_t = \
392     rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t;
393
394   /* Check whether the user has an admin status. */
395   acct_privilege_t level = acct_privilege_t::IS_PLAIN_ACCT;
396   for (const auto& admin_role : admin_roles) {
397     if (token.has_role(admin_role)) {
398       level = acct_privilege_t::IS_ADMIN_ACCT;
399       break;
400     }
401   }
402
403   return auth_info_t {
404     /* Suggested account name for the authenticated user. */
405     rgw_user(token.get_project_id()),
406     /* User's display name (aka real name). */
407     token.get_project_name(),
408     /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
409      * the access rights through the perm_mask. At least at this layer. */
410     RGW_PERM_FULL_CONTROL,
411     level,
412     TYPE_KEYSTONE,
413   };
414 }
415
416 rgw::auth::Engine::result_t EC2Engine::authenticate(
417   const boost::string_view& access_key_id,
418   const boost::string_view& signature,
419   const string_to_sign_t& string_to_sign,
420   const signature_factory_t&,
421   const completer_factory_t& completer_factory,
422   /* Passthorugh only! */
423   const req_state* s) const
424 {
425   /* This will be initialized on the first call to this method. In C++11 it's
426    * also thread-safe. */
427   static const struct RolesCacher {
428     RolesCacher(CephContext* const cct) {
429       get_str_vec(cct->_conf->rgw_keystone_accepted_roles, plain);
430       get_str_vec(cct->_conf->rgw_keystone_accepted_admin_roles, admin);
431
432       /* Let's suppose that having an admin role implies also a regular one. */
433       plain.insert(std::end(plain), std::begin(admin), std::end(admin));
434     }
435
436     std::vector<std::string> plain;
437     std::vector<std::string> admin;
438   } accepted_roles(cct);
439
440   boost::optional<token_envelope_t> t;
441   int failure_reason;
442   std::tie(t, failure_reason) = \
443     get_from_keystone(access_key_id, string_to_sign, signature);
444   if (! t) {
445     return result_t::deny(failure_reason);
446   }
447
448   /* Verify expiration. */
449   if (t->expired()) {
450     ldout(cct, 0) << "got expired token: " << t->get_project_name()
451                   << ":" << t->get_user_name()
452                   << " expired: " << t->get_expires() << dendl;
453     return result_t::deny();
454   }
455
456   /* check if we have a valid role */
457   bool found = false;
458   for (const auto& role : accepted_roles.plain) {
459     if (t->has_role(role) == true) {
460       found = true;
461       break;
462     }
463   }
464
465   if (! found) {
466     ldout(cct, 5) << "s3 keystone: user does not hold a matching role;"
467                      " required roles: "
468                   << cct->_conf->rgw_keystone_accepted_roles << dendl;
469     return result_t::deny();
470   } else {
471     /* everything seems fine, continue with this user */
472     ldout(cct, 5) << "s3 keystone: validated token: " << t->get_project_name()
473                   << ":" << t->get_user_name()
474                   << " expires: " << t->get_expires() << dendl;
475
476     auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(*t),
477                                               get_creds_info(*t, accepted_roles.admin));
478     return result_t::grant(std::move(apl), completer_factory(boost::none));
479   }
480 }
481
482 }; /* namespace keystone */
483 }; /* namespace auth */
484 }; /* namespace rgw */