1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
12 #include "common/errno.h"
13 #include "common/ceph_json.h"
14 #include "include/types.h"
15 #include "include/str_list.h"
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"
24 #include "common/ceph_crypto_cms.h"
25 #include "common/armor.h"
26 #include "common/Cond.h"
28 #define dout_subsys ceph_subsys_rgw
36 TokenEngine::is_applicable(const std::string
& token
) const noexcept
38 return ! token
.empty() && ! cct
->_conf
->rgw_keystone_url
.empty();
41 TokenEngine::token_envelope_t
42 TokenEngine::decode_pki_token(const std::string
& token
) const
44 ceph::buffer::list token_body_bl
;
45 int ret
= rgw_decode_b64_cms(cct
, token
, token_body_bl
);
47 ldout(cct
, 20) << "cannot decode pki token" << dendl
;
50 ldout(cct
, 20) << "successfully decoded pki token" << dendl
;
53 TokenEngine::token_envelope_t token_body
;
54 ret
= token_body
.parse(cct
, token
, token_body_bl
, config
.get_api_version());
62 boost::optional
<TokenEngine::token_envelope_t
>
63 TokenEngine::get_from_keystone(const std::string
& token
) const
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
;
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
);
75 std::string url
= config
.get_endpoint_url();
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
);
88 std::string admin_token
;
89 if (rgw::keystone::Service::get_admin_token(cct
, token_cache
, config
,
94 validate
.append_header("X-Auth-Token", admin_token
);
95 validate
.set_send_length(0);
97 int ret
= validate
.process(url
.c_str());
102 /* NULL terminate for debug output. */
103 token_body_bl
.append(static_cast<char>(0));
105 /* Detect Keystone rejection earlier than during the token parsing.
106 * Although failure at the parsing phase doesn't impose a threat,
107 * this allows to return proper error code (EACCESS instead of EINVAL
108 * or similar) and thus improves logging. */
109 if (validate
.get_http_status() ==
110 /* Most likely: wrong admin credentials or admin token. */
111 RGWValidateKeystoneToken::HTTP_STATUS_UNAUTHORIZED
||
112 validate
.get_http_status() ==
113 /* Most likely: non-existent token supplied by the client. */
114 RGWValidateKeystoneToken::HTTP_STATUS_NOTFOUND
) {
115 ldout(cct
, 5) << "Failed keystone auth from " << url
<< " with "
116 << validate
.get_http_status() << dendl
;
120 ldout(cct
, 20) << "received response status=" << validate
.get_http_status()
121 << ", body=" << token_body_bl
.c_str() << dendl
;
123 TokenEngine::token_envelope_t token_body
;
124 ret
= token_body
.parse(cct
, token
, token_body_bl
, config
.get_api_version());
132 TokenEngine::auth_info_t
133 TokenEngine::get_creds_info(const TokenEngine::token_envelope_t
& token
,
134 const std::vector
<std::string
>& admin_roles
137 using acct_privilege_t
= rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
139 /* Check whether the user has an admin status. */
140 acct_privilege_t level
= acct_privilege_t::IS_PLAIN_ACCT
;
141 for (const auto& admin_role
: admin_roles
) {
142 if (token
.has_role(admin_role
)) {
143 level
= acct_privilege_t::IS_ADMIN_ACCT
;
149 /* Suggested account name for the authenticated user. */
150 rgw_user(token
.get_project_id()),
151 /* User's display name (aka real name). */
152 token
.get_project_name(),
153 /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
154 * the access rights through the perm_mask. At least at this layer. */
155 RGW_PERM_FULL_CONTROL
,
161 static inline const std::string
162 make_spec_item(const std::string
& tenant
, const std::string
& id
)
164 return tenant
+ ":" + id
;
167 TokenEngine::acl_strategy_t
168 TokenEngine::get_acl_strategy(const TokenEngine::token_envelope_t
& token
) const
170 /* The primary identity is constructed upon UUIDs. */
171 const auto& tenant_uuid
= token
.get_project_id();
172 const auto& user_uuid
= token
.get_user_id();
174 /* For Keystone v2 an alias may be also used. */
175 const auto& tenant_name
= token
.get_project_name();
176 const auto& user_name
= token
.get_user_name();
178 /* Construct all possible combinations including Swift's wildcards. */
179 const std::array
<std::string
, 6> allowed_items
= {
180 make_spec_item(tenant_uuid
, user_uuid
),
181 make_spec_item(tenant_name
, user_name
),
184 make_spec_item(tenant_uuid
, "*"),
185 make_spec_item(tenant_name
, "*"),
186 make_spec_item("*", user_uuid
),
187 make_spec_item("*", user_name
),
190 /* Lambda will obtain a copy of (not a reference to!) allowed_items. */
191 return [allowed_items
](const rgw::auth::Identity::aclspec_t
& aclspec
) {
194 for (const auto& allowed_item
: allowed_items
) {
195 const auto iter
= aclspec
.find(allowed_item
);
197 if (std::end(aclspec
) != iter
) {
198 perm
|= iter
->second
;
206 TokenEngine::result_t
207 TokenEngine::authenticate(const std::string
& token
,
208 const req_state
* const s
) const
210 boost::optional
<TokenEngine::token_envelope_t
> t
;
212 /* This will be initialized on the first call to this method. In C++11 it's
213 * also thread-safe. */
214 static const struct RolesCacher
{
215 RolesCacher(CephContext
* const cct
) {
216 get_str_vec(cct
->_conf
->rgw_keystone_accepted_roles
, plain
);
217 get_str_vec(cct
->_conf
->rgw_keystone_accepted_admin_roles
, admin
);
219 /* Let's suppose that having an admin role implies also a regular one. */
220 plain
.insert(std::end(plain
), std::begin(admin
), std::end(admin
));
223 std::vector
<std::string
> plain
;
224 std::vector
<std::string
> admin
;
227 if (! is_applicable(token
)) {
228 return result_t::deny();
231 /* Token ID is a concept that makes dealing with PKI tokens more effective.
232 * Instead of storing several kilobytes, a short hash can be burried. */
233 const auto& token_id
= rgw_get_token_id(token
);
234 ldout(cct
, 20) << "token_id=" << token_id
<< dendl
;
236 /* Check cache first. */
237 t
= token_cache
.find(token_id
);
239 ldout(cct
, 20) << "cached token.project.id=" << t
->get_project_id()
241 auto apl
= apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(*t
),
242 get_creds_info(*t
, roles
.admin
));
243 return result_t::grant(std::move(apl
));
246 /* Retrieve token. */
247 if (rgw_is_pki_token(token
)) {
249 t
= decode_pki_token(token
);
252 t
= get_from_keystone(token
);
255 /* Can't decode, just go to the Keystone server for validation. */
256 t
= get_from_keystone(token
);
260 return result_t::deny(-EACCES
);
263 /* Verify expiration. */
265 ldout(cct
, 0) << "got expired token: " << t
->get_project_name()
266 << ":" << t
->get_user_name()
267 << " expired: " << t
->get_expires() << dendl
;
268 return result_t::deny(-EPERM
);
271 /* Check for necessary roles. */
272 for (const auto& role
: roles
.plain
) {
273 if (t
->has_role(role
) == true) {
274 ldout(cct
, 0) << "validated token: " << t
->get_project_name()
275 << ":" << t
->get_user_name()
276 << " expires: " << t
->get_expires() << dendl
;
277 token_cache
.add(token_id
, *t
);
278 auto apl
= apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(*t
),
279 get_creds_info(*t
, roles
.admin
));
280 return result_t::grant(std::move(apl
));
284 ldout(cct
, 0) << "user does not hold a matching role; required roles: "
285 << g_conf
->rgw_keystone_accepted_roles
<< dendl
;
287 return result_t::deny(-EPERM
);
292 * Try to validate S3 auth against keystone s3token interface
294 std::pair
<boost::optional
<rgw::keystone::TokenEnvelope
>, int>
295 EC2Engine::get_from_keystone(const boost::string_view
& access_key_id
,
296 const std::string
& string_to_sign
,
297 const boost::string_view
& signature
) const
299 /* prepare keystone url */
300 std::string keystone_url
= config
.get_endpoint_url();
301 if (keystone_url
.empty()) {
305 const auto api_version
= config
.get_api_version();
306 if (config
.get_api_version() == rgw::keystone::ApiVersion::VER_3
) {
307 keystone_url
.append("v3/s3tokens");
309 keystone_url
.append("v2.0/s3tokens");
312 /* get authentication token for Keystone. */
313 std::string admin_token
;
314 int ret
= rgw::keystone::Service::get_admin_token(cct
, token_cache
, config
,
317 ldout(cct
, 2) << "s3 keystone: cannot get token for keystone access"
322 using RGWValidateKeystoneToken
323 = rgw::keystone::Service::RGWValidateKeystoneToken
;
325 /* The container for plain response obtained from Keystone. It will be
326 * parsed token_envelope_t::parse method. */
327 ceph::bufferlist token_body_bl
;
328 RGWValidateKeystoneToken
validate(cct
, &token_body_bl
);
330 /* set required headers for keystone request */
331 validate
.append_header("X-Auth-Token", admin_token
);
332 validate
.append_header("Content-Type", "application/json");
334 /* check if we want to verify keystone's ssl certs */
335 validate
.set_verify_ssl(cct
->_conf
->rgw_keystone_verify_ssl
);
337 /* create json credentials request body */
338 JSONFormatter
credentials(false);
339 credentials
.open_object_section("");
340 credentials
.open_object_section("credentials");
341 credentials
.dump_string("access", sview2cstr(access_key_id
).data());
342 credentials
.dump_string("token", rgw::to_base64(string_to_sign
));
343 credentials
.dump_string("signature", sview2cstr(signature
).data());
344 credentials
.close_section();
345 credentials
.close_section();
347 std::stringstream os
;
348 credentials
.flush(os
);
349 validate
.set_post_data(os
.str());
350 validate
.set_send_length(os
.str().length());
353 ret
= validate
.process("POST", keystone_url
.c_str());
355 ldout(cct
, 2) << "s3 keystone: token validation ERROR: "
356 << token_body_bl
.c_str() << dendl
;
360 /* if the supplied signature is wrong, we will get 401 from Keystone */
361 if (validate
.get_http_status() ==
362 decltype(validate
)::HTTP_STATUS_UNAUTHORIZED
) {
363 return std::make_pair(boost::none
, -ERR_SIGNATURE_NO_MATCH
);
364 } else if (validate
.get_http_status() ==
365 decltype(validate
)::HTTP_STATUS_NOTFOUND
) {
366 return std::make_pair(boost::none
, -ERR_INVALID_ACCESS_KEY
);
369 /* now parse response */
370 rgw::keystone::TokenEnvelope token_envelope
;
371 ret
= token_envelope
.parse(cct
, std::string(), token_body_bl
, api_version
);
373 ldout(cct
, 2) << "s3 keystone: token parsing failed, ret=0" << ret
378 return std::make_pair(std::move(token_envelope
), 0);
381 EC2Engine::acl_strategy_t
382 EC2Engine::get_acl_strategy(const EC2Engine::token_envelope_t
&) const
384 /* This is based on the assumption that the default acl strategy in
385 * get_perms_from_aclspec, will take care. Extra acl spec is not required. */
389 EC2Engine::auth_info_t
390 EC2Engine::get_creds_info(const EC2Engine::token_envelope_t
& token
,
391 const std::vector
<std::string
>& admin_roles
394 using acct_privilege_t
= \
395 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
397 /* Check whether the user has an admin status. */
398 acct_privilege_t level
= acct_privilege_t::IS_PLAIN_ACCT
;
399 for (const auto& admin_role
: admin_roles
) {
400 if (token
.has_role(admin_role
)) {
401 level
= acct_privilege_t::IS_ADMIN_ACCT
;
407 /* Suggested account name for the authenticated user. */
408 rgw_user(token
.get_project_id()),
409 /* User's display name (aka real name). */
410 token
.get_project_name(),
411 /* Keystone doesn't support RGW's subuser concept, so we cannot cut down
412 * the access rights through the perm_mask. At least at this layer. */
413 RGW_PERM_FULL_CONTROL
,
419 rgw::auth::Engine::result_t
EC2Engine::authenticate(
420 const boost::string_view
& access_key_id
,
421 const boost::string_view
& signature
,
422 const string_to_sign_t
& string_to_sign
,
423 const signature_factory_t
&,
424 const completer_factory_t
& completer_factory
,
425 /* Passthorugh only! */
426 const req_state
* s
) const
428 /* This will be initialized on the first call to this method. In C++11 it's
429 * also thread-safe. */
430 static const struct RolesCacher
{
431 RolesCacher(CephContext
* const cct
) {
432 get_str_vec(cct
->_conf
->rgw_keystone_accepted_roles
, plain
);
433 get_str_vec(cct
->_conf
->rgw_keystone_accepted_admin_roles
, admin
);
435 /* Let's suppose that having an admin role implies also a regular one. */
436 plain
.insert(std::end(plain
), std::begin(admin
), std::end(admin
));
439 std::vector
<std::string
> plain
;
440 std::vector
<std::string
> admin
;
441 } accepted_roles(cct
);
443 boost::optional
<token_envelope_t
> t
;
445 std::tie(t
, failure_reason
) = \
446 get_from_keystone(access_key_id
, string_to_sign
, signature
);
448 return result_t::deny(failure_reason
);
451 /* Verify expiration. */
453 ldout(cct
, 0) << "got expired token: " << t
->get_project_name()
454 << ":" << t
->get_user_name()
455 << " expired: " << t
->get_expires() << dendl
;
456 return result_t::deny();
459 /* check if we have a valid role */
461 for (const auto& role
: accepted_roles
.plain
) {
462 if (t
->has_role(role
) == true) {
469 ldout(cct
, 5) << "s3 keystone: user does not hold a matching role;"
471 << cct
->_conf
->rgw_keystone_accepted_roles
<< dendl
;
472 return result_t::deny();
474 /* everything seems fine, continue with this user */
475 ldout(cct
, 5) << "s3 keystone: validated token: " << t
->get_project_name()
476 << ":" << t
->get_user_name()
477 << " expires: " << t
->get_expires() << dendl
;
479 auto apl
= apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(*t
),
480 get_creds_info(*t
, accepted_roles
.admin
));
481 return result_t::grant(std::move(apl
), completer_factory(boost::none
));
485 }; /* namespace keystone */
486 }; /* namespace auth */
487 }; /* namespace rgw */