1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
10 #include <boost/algorithm/string/predicate.hpp>
11 #include <boost/format.hpp>
12 #include <boost/optional.hpp>
13 #include <boost/utility/in_place_factory.hpp>
14 #include <boost/tokenizer.hpp>
19 #include "common/Formatter.h"
20 #include "common/utf8.h"
21 #include "common/ceph_json.h"
25 #include "rgw_auth_registry.h"
26 #include "jwt-cpp/jwt.h"
27 #include "rgw_rest_sts.h"
29 #include "rgw_formats.h"
30 #include "rgw_client_io.h"
32 #include "rgw_request.h"
33 #include "rgw_process.h"
34 #include "rgw_iam_policy.h"
35 #include "rgw_iam_policy_keywords.h"
38 #include "rgw_rest_oidc_provider.h"
41 #define dout_context g_ceph_context
42 #define dout_subsys ceph_subsys_rgw
44 namespace rgw::auth::sts
{
47 WebTokenEngine::is_applicable(const std::string
& token
) const noexcept
49 return ! token
.empty();
53 WebTokenEngine::get_role_tenant(const string
& role_arn
) const
56 auto r_arn
= rgw::ARN::parse(role_arn
);
58 tenant
= r_arn
->account
;
63 boost::optional
<RGWOIDCProvider
>
64 WebTokenEngine::get_provider(const DoutPrefixProvider
*dpp
, const string
& role_arn
, const string
& iss
) const
66 string tenant
= get_role_tenant(role_arn
);
69 auto pos
= idp_url
.find("http://");
70 if (pos
== std::string::npos
) {
71 pos
= idp_url
.find("https://");
72 if (pos
!= std::string::npos
) {
73 idp_url
.erase(pos
, 8);
75 pos
= idp_url
.find("www.");
76 if (pos
!= std::string::npos
) {
77 idp_url
.erase(pos
, 4);
81 idp_url
.erase(pos
, 7);
83 auto provider_arn
= rgw::ARN(idp_url
, "oidc-provider", tenant
);
84 string p_arn
= provider_arn
.to_string();
85 RGWOIDCProvider
provider(cct
, ctl
, p_arn
, tenant
);
86 auto ret
= provider
.get(dpp
);
94 WebTokenEngine::is_client_id_valid(vector
<string
>& client_ids
, const string
& client_id
) const
96 for (auto it
: client_ids
) {
97 if (it
== client_id
) {
105 WebTokenEngine::is_cert_valid(const vector
<string
>& thumbprints
, const string
& cert
) const
107 //calculate thumbprint of cert
108 std::unique_ptr
<BIO
, decltype(&BIO_free_all
)> certbio(BIO_new_mem_buf(cert
.data(), cert
.size()), BIO_free_all
);
109 std::unique_ptr
<BIO
, decltype(&BIO_free_all
)> keybio(BIO_new(BIO_s_mem()), BIO_free_all
);
111 std::unique_ptr
<X509
, decltype(&X509_free
)> x_509cert(PEM_read_bio_X509(certbio
.get(), nullptr, nullptr, const_cast<char*>(pw
.c_str())), X509_free
);
112 const EVP_MD
* fprint_type
= EVP_sha1();
113 unsigned int fprint_size
;
114 unsigned char fprint
[EVP_MAX_MD_SIZE
];
116 if (!X509_digest(x_509cert
.get(), fprint_type
, fprint
, &fprint_size
)) {
120 for (unsigned int i
= 0; i
< fprint_size
; i
++) {
121 ss
<< std::setfill('0') << std::setw(2) << std::hex
<< (0xFF & (unsigned int)fprint
[i
]);
123 std::string digest
= ss
.str();
125 for (auto& it
: thumbprints
) {
126 if (boost::iequals(it
,digest
)) {
133 //Offline validation of incoming Web Token which is a signed JWT (JSON Web Token)
134 boost::optional
<WebTokenEngine::token_t
>
135 WebTokenEngine::get_from_jwt(const DoutPrefixProvider
* dpp
, const std::string
& token
, const req_state
* const s
,
136 optional_yield y
) const
138 WebTokenEngine::token_t t
;
140 const auto& decoded
= jwt::decode(token
);
142 auto& payload
= decoded
.get_payload();
143 ldpp_dout(dpp
, 20) << " payload = " << payload
<< dendl
;
144 if (decoded
.has_issuer()) {
145 t
.iss
= decoded
.get_issuer();
147 if (decoded
.has_audience()) {
148 auto aud
= decoded
.get_audience();
149 t
.aud
= *(aud
.begin());
151 if (decoded
.has_subject()) {
152 t
.sub
= decoded
.get_subject();
154 if (decoded
.has_payload_claim("client_id")) {
155 t
.client_id
= decoded
.get_payload_claim("client_id").as_string();
157 if (t
.client_id
.empty() && decoded
.has_payload_claim("clientId")) {
158 t
.client_id
= decoded
.get_payload_claim("clientId").as_string();
160 string role_arn
= s
->info
.args
.get("RoleArn");
161 auto provider
= get_provider(dpp
, role_arn
, t
.iss
);
163 ldpp_dout(dpp
, 0) << "Couldn't get oidc provider info using input iss" << t
.iss
<< dendl
;
166 vector
<string
> client_ids
= provider
->get_client_ids();
167 vector
<string
> thumbprints
= provider
->get_thumbprints();
168 if (! client_ids
.empty()) {
169 if (! is_client_id_valid(client_ids
, t
.client_id
) && ! is_client_id_valid(client_ids
, t
.aud
)) {
170 ldpp_dout(dpp
, 0) << "Client id in token doesn't match with that registered with oidc provider" << dendl
;
175 if (decoded
.has_algorithm()) {
176 auto& algorithm
= decoded
.get_algorithm();
178 validate_signature(dpp
, decoded
, algorithm
, t
.iss
, thumbprints
, y
);
185 } catch (int error
) {
186 if (error
== -EACCES
) {
189 ldpp_dout(dpp
, 5) << "Invalid JWT token" << dendl
;
193 ldpp_dout(dpp
, 5) << "Invalid JWT token" << dendl
;
200 WebTokenEngine::validate_signature(const DoutPrefixProvider
* dpp
, const jwt::decoded_jwt
& decoded
, const string
& algorithm
, const string
& iss
, const vector
<string
>& thumbprints
, optional_yield y
) const
202 if (algorithm
!= "HS256" && algorithm
!= "HS384" && algorithm
!= "HS512") {
204 string cert_url
= iss
+ "/protocol/openid-connect/certs";
205 bufferlist cert_resp
;
206 RGWHTTPTransceiver
cert_req(cct
, "GET", cert_url
, &cert_resp
);
208 cert_req
.append_header("Content-Type", "application/x-www-form-urlencoded");
210 int res
= cert_req
.process(y
);
212 ldpp_dout(dpp
, 10) << "HTTP request res: " << res
<< dendl
;
216 ldpp_dout(dpp
, 20) << "HTTP status: " << cert_req
.get_http_status() << dendl
;
217 ldpp_dout(dpp
, 20) << "JSON Response is: " << cert_resp
.c_str() << dendl
;
220 if (parser
.parse(cert_resp
.c_str(), cert_resp
.length())) {
221 JSONObj::data_val val
;
222 if (parser
.get_data("keys", &val
)) {
223 if (val
.str
[0] == '[') {
226 if (val
.str
[val
.str
.size() - 1] == ']') {
227 val
.str
= val
.str
.erase(val
.str
.size() - 1, 1);
229 if (parser
.parse(val
.str
.c_str(), val
.str
.size())) {
231 if (JSONDecoder::decode_json("x5c", x5c
, &parser
)) {
233 bool found_valid_cert
= false;
234 for (auto& it
: x5c
) {
235 cert
= "-----BEGIN CERTIFICATE-----\n" + it
+ "\n-----END CERTIFICATE-----";
236 ldpp_dout(dpp
, 20) << "Certificate is: " << cert
.c_str() << dendl
;
237 if (is_cert_valid(thumbprints
, cert
)) {
238 found_valid_cert
= true;
241 found_valid_cert
= true;
243 if (! found_valid_cert
) {
244 ldpp_dout(dpp
, 0) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert
.c_str() << dendl
;
248 //verify method takes care of expired tokens also
249 if (algorithm
== "RS256") {
250 auto verifier
= jwt::verify()
251 .allow_algorithm(jwt::algorithm::rs256
{cert
});
253 verifier
.verify(decoded
);
254 } else if (algorithm
== "RS384") {
255 auto verifier
= jwt::verify()
256 .allow_algorithm(jwt::algorithm::rs384
{cert
});
258 verifier
.verify(decoded
);
259 } else if (algorithm
== "RS512") {
260 auto verifier
= jwt::verify()
261 .allow_algorithm(jwt::algorithm::rs512
{cert
});
263 verifier
.verify(decoded
);
264 } else if (algorithm
== "ES256") {
265 auto verifier
= jwt::verify()
266 .allow_algorithm(jwt::algorithm::es256
{cert
});
268 verifier
.verify(decoded
);
269 } else if (algorithm
== "ES384") {
270 auto verifier
= jwt::verify()
271 .allow_algorithm(jwt::algorithm::es384
{cert
});
273 verifier
.verify(decoded
);
274 } else if (algorithm
== "ES512") {
275 auto verifier
= jwt::verify()
276 .allow_algorithm(jwt::algorithm::es512
{cert
});
278 verifier
.verify(decoded
);
279 } else if (algorithm
== "PS256") {
280 auto verifier
= jwt::verify()
281 .allow_algorithm(jwt::algorithm::ps256
{cert
});
283 verifier
.verify(decoded
);
284 } else if (algorithm
== "PS384") {
285 auto verifier
= jwt::verify()
286 .allow_algorithm(jwt::algorithm::ps384
{cert
});
288 verifier
.verify(decoded
);
289 } else if (algorithm
== "PS512") {
290 auto verifier
= jwt::verify()
291 .allow_algorithm(jwt::algorithm::ps512
{cert
});
293 verifier
.verify(decoded
);
295 } catch (std::runtime_error
& e
) {
296 ldpp_dout(dpp
, 0) << "Signature validation failed: " << e
.what() << dendl
;
300 ldpp_dout(dpp
, 0) << "Signature validation failed" << dendl
;
304 ldpp_dout(dpp
, 0) << "x5c not present" << dendl
;
308 ldpp_dout(dpp
, 0) << "Malformed JSON object for keys" << dendl
;
312 ldpp_dout(dpp
, 0) << "keys not present in JSON" << dendl
;
316 ldpp_dout(dpp
, 0) << "Malformed json returned while fetching cert" << dendl
;
318 } //if-else parser cert_resp
320 ldpp_dout(dpp
, 0) << "JWT signed by HMAC algos are currently not supported" << dendl
;
325 WebTokenEngine::result_t
326 WebTokenEngine::authenticate( const DoutPrefixProvider
* dpp
,
327 const std::string
& token
,
328 const req_state
* const s
,
329 optional_yield y
) const
331 boost::optional
<WebTokenEngine::token_t
> t
;
333 if (! is_applicable(token
)) {
334 return result_t::deny();
338 t
= get_from_jwt(dpp
, token
, s
, y
);
341 return result_t::deny(-EACCES
);
345 string role_session
= s
->info
.args
.get("RoleSessionName");
346 if (role_session
.empty()) {
347 ldpp_dout(dpp
, 0) << "Role Session Name is empty " << dendl
;
348 return result_t::deny(-EACCES
);
350 string role_arn
= s
->info
.args
.get("RoleArn");
351 string role_tenant
= get_role_tenant(role_arn
);
352 auto apl
= apl_factory
->create_apl_web_identity(cct
, s
, role_session
, role_tenant
, *t
);
353 return result_t::grant(std::move(apl
));
355 return result_t::deny(-EACCES
);
358 } // namespace rgw::auth::sts
360 int RGWREST_STS::verify_permission(optional_yield y
)
362 STS::STSService
_sts(s
->cct
, store
, s
->user
->get_id(), s
->auth
.identity
.get());
363 sts
= std::move(_sts
);
365 string rArn
= s
->info
.args
.get("RoleArn");
366 const auto& [ret
, role
] = sts
.getRoleInfo(s
, rArn
, y
);
368 ldpp_dout(this, 0) << "failed to get role info using role arn: " << rArn
<< dendl
;
371 string policy
= role
.get_assume_role_policy();
372 buffer::list bl
= buffer::list::static_from_string(policy
);
375 //TODO - This step should be part of Role Creation
377 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
378 //Check if the input role arn is there as one of the Principals in the policy,
379 // If yes, then return 0, else -EPERM
380 auto p_res
= p
.eval_principal(s
->env
, *s
->auth
.identity
);
381 if (p_res
== rgw::IAM::Effect::Deny
) {
382 ldpp_dout(this, 0) << "evaluating principal returned deny" << dendl
;
385 auto c_res
= p
.eval_conditions(s
->env
);
386 if (c_res
== rgw::IAM::Effect::Deny
) {
387 ldpp_dout(this, 0) << "evaluating condition returned deny" << dendl
;
390 } catch (rgw::IAM::PolicyParseException
& e
) {
391 ldpp_dout(this, 0) << "failed to parse policy: " << e
.what() << dendl
;
398 void RGWREST_STS::send_response()
401 set_req_state_err(s
, op_ret
);
407 int RGWSTSGetSessionToken::verify_permission(optional_yield y
)
409 rgw::Partition partition
= rgw::Partition::aws
;
410 rgw::Service service
= rgw::Service::s3
;
411 if (!verify_user_permission(this,
413 rgw::ARN(partition
, service
, "", s
->user
->get_tenant(), ""),
414 rgw::IAM::stsGetSessionToken
)) {
415 ldpp_dout(this, 0) << "User does not have permssion to perform GetSessionToken" << dendl
;
422 int RGWSTSGetSessionToken::get_params()
424 duration
= s
->info
.args
.get("DurationSeconds");
425 serialNumber
= s
->info
.args
.get("SerialNumber");
426 tokenCode
= s
->info
.args
.get("TokenCode");
428 if (! duration
.empty()) {
430 uint64_t duration_in_secs
= strict_strtoll(duration
.c_str(), 10, &err
);
432 ldpp_dout(this, 0) << "Invalid value of input duration: " << duration
<< dendl
;
436 if (duration_in_secs
< STS::GetSessionTokenRequest::getMinDuration() ||
437 duration_in_secs
> s
->cct
->_conf
->rgw_sts_max_session_duration
) {
438 ldpp_dout(this, 0) << "Invalid duration in secs: " << duration_in_secs
<< dendl
;
446 void RGWSTSGetSessionToken::execute(optional_yield y
)
448 if (op_ret
= get_params(); op_ret
< 0) {
452 STS::STSService
sts(s
->cct
, store
, s
->user
->get_id(), s
->auth
.identity
.get());
454 STS::GetSessionTokenRequest
req(duration
, serialNumber
, tokenCode
);
455 const auto& [ret
, creds
] = sts
.getSessionToken(req
);
456 op_ret
= std::move(ret
);
459 s
->formatter
->open_object_section("GetSessionTokenResponse");
460 s
->formatter
->open_object_section("GetSessionTokenResult");
461 s
->formatter
->open_object_section("Credentials");
462 creds
.dump(s
->formatter
);
463 s
->formatter
->close_section();
464 s
->formatter
->close_section();
465 s
->formatter
->close_section();
469 int RGWSTSAssumeRoleWithWebIdentity::get_params()
471 duration
= s
->info
.args
.get("DurationSeconds");
472 providerId
= s
->info
.args
.get("ProviderId");
473 policy
= s
->info
.args
.get("Policy");
474 roleArn
= s
->info
.args
.get("RoleArn");
475 roleSessionName
= s
->info
.args
.get("RoleSessionName");
476 iss
= s
->info
.args
.get("provider_id");
477 sub
= s
->info
.args
.get("sub");
478 aud
= s
->info
.args
.get("aud");
480 if (roleArn
.empty() || roleSessionName
.empty() || sub
.empty() || aud
.empty()) {
481 ldpp_dout(this, 0) << "ERROR: one of role arn or role session name or token is empty" << dendl
;
485 if (! policy
.empty()) {
486 bufferlist bl
= bufferlist::static_from_string(policy
);
488 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
490 catch (rgw::IAM::PolicyParseException
& e
) {
491 ldpp_dout(this, 20) << "failed to parse policy: " << e
.what() << "policy" << policy
<< dendl
;
492 return -ERR_MALFORMED_DOC
;
499 void RGWSTSAssumeRoleWithWebIdentity::execute(optional_yield y
)
501 if (op_ret
= get_params(); op_ret
< 0) {
505 STS::AssumeRoleWithWebIdentityRequest
req(s
->cct
, duration
, providerId
, policy
, roleArn
,
506 roleSessionName
, iss
, sub
, aud
);
507 STS::AssumeRoleWithWebIdentityResponse response
= sts
.assumeRoleWithWebIdentity(req
);
508 op_ret
= std::move(response
.assumeRoleResp
.retCode
);
512 s
->formatter
->open_object_section("AssumeRoleWithWebIdentityResponse");
513 s
->formatter
->open_object_section("AssumeRoleWithWebIdentityResult");
514 encode_json("SubjectFromWebIdentityToken", response
.sub
, s
->formatter
);
515 encode_json("Audience", response
.aud
, s
->formatter
);
516 s
->formatter
->open_object_section("AssumedRoleUser");
517 response
.assumeRoleResp
.user
.dump(s
->formatter
);
518 s
->formatter
->close_section();
519 s
->formatter
->open_object_section("Credentials");
520 response
.assumeRoleResp
.creds
.dump(s
->formatter
);
521 s
->formatter
->close_section();
522 encode_json("Provider", response
.providerId
, s
->formatter
);
523 encode_json("PackedPolicySize", response
.assumeRoleResp
.packedPolicySize
, s
->formatter
);
524 s
->formatter
->close_section();
525 s
->formatter
->close_section();
529 int RGWSTSAssumeRole::get_params()
531 duration
= s
->info
.args
.get("DurationSeconds");
532 externalId
= s
->info
.args
.get("ExternalId");
533 policy
= s
->info
.args
.get("Policy");
534 roleArn
= s
->info
.args
.get("RoleArn");
535 roleSessionName
= s
->info
.args
.get("RoleSessionName");
536 serialNumber
= s
->info
.args
.get("SerialNumber");
537 tokenCode
= s
->info
.args
.get("TokenCode");
539 if (roleArn
.empty() || roleSessionName
.empty()) {
540 ldpp_dout(this, 0) << "ERROR: one of role arn or role session name is empty" << dendl
;
544 if (! policy
.empty()) {
545 bufferlist bl
= bufferlist::static_from_string(policy
);
547 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
549 catch (rgw::IAM::PolicyParseException
& e
) {
550 ldpp_dout(this, 0) << "failed to parse policy: " << e
.what() << "policy" << policy
<< dendl
;
551 return -ERR_MALFORMED_DOC
;
558 void RGWSTSAssumeRole::execute(optional_yield y
)
560 if (op_ret
= get_params(); op_ret
< 0) {
564 STS::AssumeRoleRequest
req(s
->cct
, duration
, externalId
, policy
, roleArn
,
565 roleSessionName
, serialNumber
, tokenCode
);
566 STS::AssumeRoleResponse response
= sts
.assumeRole(s
, req
, y
);
567 op_ret
= std::move(response
.retCode
);
570 s
->formatter
->open_object_section("AssumeRoleResponse");
571 s
->formatter
->open_object_section("AssumeRoleResult");
572 s
->formatter
->open_object_section("Credentials");
573 response
.creds
.dump(s
->formatter
);
574 s
->formatter
->close_section();
575 s
->formatter
->open_object_section("AssumedRoleUser");
576 response
.user
.dump(s
->formatter
);
577 s
->formatter
->close_section();
578 encode_json("PackedPolicySize", response
.packedPolicySize
, s
->formatter
);
579 s
->formatter
->close_section();
580 s
->formatter
->close_section();
584 int RGW_Auth_STS::authorize(const DoutPrefixProvider
*dpp
,
585 rgw::sal::RGWRadosStore
*store
,
586 const rgw::auth::StrategyRegistry
& auth_registry
,
587 struct req_state
*s
, optional_yield y
)
589 return rgw::auth::Strategy::apply(dpp
, auth_registry
.get_sts(), s
, y
);
592 void RGWHandler_REST_STS::rgw_sts_parse_input()
594 if (post_body
.size() > 0) {
595 ldpp_dout(s
, 10) << "Content of POST: " << post_body
<< dendl
;
597 if (post_body
.find("Action") != string::npos
) {
598 boost::char_separator
<char> sep("&");
599 boost::tokenizer
<boost::char_separator
<char>> tokens(post_body
, sep
);
600 for (const auto& t
: tokens
) {
601 auto pos
= t
.find("=");
602 if (pos
!= string::npos
) {
603 s
->info
.args
.append(t
.substr(0,pos
),
604 url_decode(t
.substr(pos
+1, t
.size() -1)));
609 auto payload_hash
= rgw::auth::s3::calc_v4_payload_hash(post_body
);
610 s
->info
.args
.append("PayloadHash", payload_hash
);
613 RGWOp
*RGWHandler_REST_STS::op_post()
615 rgw_sts_parse_input();
617 if (s
->info
.args
.exists("Action")) {
618 string action
= s
->info
.args
.get("Action");
619 if (action
== "AssumeRole") {
620 return new RGWSTSAssumeRole
;
621 } else if (action
== "GetSessionToken") {
622 return new RGWSTSGetSessionToken
;
623 } else if (action
== "AssumeRoleWithWebIdentity") {
624 return new RGWSTSAssumeRoleWithWebIdentity
;
631 int RGWHandler_REST_STS::init(rgw::sal::RGWRadosStore
*store
,
633 rgw::io::BasicClient
*cio
)
637 if (int ret
= RGWHandler_REST_STS::init_from_header(s
, RGW_FORMAT_XML
, true); ret
< 0) {
638 ldpp_dout(s
, 10) << "init_from_header returned err=" << ret
<< dendl
;
642 return RGWHandler_REST::init(store
, s
, cio
);
645 int RGWHandler_REST_STS::authorize(const DoutPrefixProvider
* dpp
, optional_yield y
)
647 if (s
->info
.args
.exists("Action") && s
->info
.args
.get("Action") == "AssumeRoleWithWebIdentity") {
648 return RGW_Auth_STS::authorize(dpp
, store
, auth_registry
, s
, y
);
650 return RGW_Auth_S3::authorize(dpp
, store
, auth_registry
, s
, y
);
653 int RGWHandler_REST_STS::init_from_header(struct req_state
* s
,
654 int default_formatter
,
655 bool configurable_format
)
660 s
->prot_flags
= RGW_REST_STS
;
662 const char *p
, *req_name
;
663 if (req_name
= s
->relative_uri
.c_str(); *req_name
== '?') {
666 p
= s
->info
.request_params
.c_str();
670 s
->info
.args
.parse(s
);
672 /* must be called after the args parsing */
673 if (int ret
= allocate_formatter(s
, default_formatter
, configurable_format
); ret
< 0)
676 if (*req_name
!= '/')
685 int pos
= req
.find('/');
687 first
= req
.substr(0, pos
);
696 RGWRESTMgr_STS::get_handler(rgw::sal::RGWRadosStore
*store
,
697 struct req_state
* const s
,
698 const rgw::auth::StrategyRegistry
& auth_registry
,
699 const std::string
& frontend_prefix
)
701 return new RGWHandler_REST_STS(auth_registry
);