1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
9 #include <boost/algorithm/string/predicate.hpp>
10 #include <boost/format.hpp>
11 #include <boost/optional.hpp>
12 #include <boost/utility/in_place_factory.hpp>
13 #include <boost/tokenizer.hpp>
18 #include "common/Formatter.h"
19 #include "common/utf8.h"
20 #include "common/ceph_json.h"
24 #include "rgw_auth_registry.h"
25 #include "jwt-cpp/jwt.h"
26 #include "rgw_rest_sts.h"
28 #include "rgw_formats.h"
29 #include "rgw_client_io.h"
31 #include "rgw_request.h"
32 #include "rgw_process.h"
33 #include "rgw_iam_policy.h"
34 #include "rgw_iam_policy_keywords.h"
37 #include "rgw_rest_oidc_provider.h"
38 #include <boost/utility/string_ref.hpp>
40 #define dout_context g_ceph_context
41 #define dout_subsys ceph_subsys_rgw
43 namespace rgw::auth::sts
{
46 WebTokenEngine::is_applicable(const std::string
& token
) const noexcept
48 return ! token
.empty();
51 boost::optional
<RGWOIDCProvider
>
52 WebTokenEngine::get_provider(const string
& role_arn
, const string
& iss
) const
55 auto r_arn
= rgw::ARN::parse(role_arn
);
57 tenant
= r_arn
->account
;
60 auto pos
= idp_url
.find("http://");
61 if (pos
== std::string::npos
) {
62 pos
= idp_url
.find("https://");
63 if (pos
!= std::string::npos
) {
64 idp_url
.erase(pos
, 8);
66 pos
= idp_url
.find("www.");
67 if (pos
!= std::string::npos
) {
68 idp_url
.erase(pos
, 4);
72 idp_url
.erase(pos
, 7);
74 auto provider_arn
= rgw::ARN(idp_url
, "oidc-provider", tenant
);
75 string p_arn
= provider_arn
.to_string();
76 RGWOIDCProvider
provider(cct
, ctl
, p_arn
, tenant
);
77 auto ret
= provider
.get();
85 WebTokenEngine::is_client_id_valid(vector
<string
>& client_ids
, const string
& client_id
) const
87 for (auto it
: client_ids
) {
88 if (it
== client_id
) {
96 WebTokenEngine::is_cert_valid(const vector
<string
>& thumbprints
, const string
& cert
) const
98 //calculate thumbprint of cert
99 std::unique_ptr
<BIO
, decltype(&BIO_free_all
)> certbio(BIO_new_mem_buf(cert
.data(), cert
.size()), BIO_free_all
);
100 std::unique_ptr
<BIO
, decltype(&BIO_free_all
)> keybio(BIO_new(BIO_s_mem()), BIO_free_all
);
102 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
);
103 const EVP_MD
* fprint_type
= EVP_sha1();
104 unsigned int fprint_size
;
105 unsigned char fprint
[EVP_MAX_MD_SIZE
];
107 if (!X509_digest(x_509cert
.get(), fprint_type
, fprint
, &fprint_size
)) {
111 for (unsigned int i
= 0; i
< fprint_size
; i
++) {
112 ss
<< std::setfill('0') << std::setw(2) << std::hex
<< (0xFF & (unsigned int)fprint
[i
]);
114 std::string digest
= ss
.str();
116 for (auto& it
: thumbprints
) {
117 if (boost::iequals(it
,digest
)) {
124 //Offline validation of incoming Web Token which is a signed JWT (JSON Web Token)
125 boost::optional
<WebTokenEngine::token_t
>
126 WebTokenEngine::get_from_jwt(const DoutPrefixProvider
* dpp
, const std::string
& token
, const req_state
* const s
) const
128 WebTokenEngine::token_t t
;
130 const auto& decoded
= jwt::decode(token
);
132 auto& payload
= decoded
.get_payload();
133 ldpp_dout(dpp
, 20) << " payload = " << payload
<< dendl
;
134 if (decoded
.has_issuer()) {
135 t
.iss
= decoded
.get_issuer();
137 if (decoded
.has_audience()) {
138 auto aud
= decoded
.get_audience();
139 t
.aud
= *(aud
.begin());
141 if (decoded
.has_subject()) {
142 t
.sub
= decoded
.get_subject();
144 if (decoded
.has_payload_claim("client_id")) {
145 t
.client_id
= decoded
.get_payload_claim("client_id").as_string();
147 if (t
.client_id
.empty() && decoded
.has_payload_claim("clientId")) {
148 t
.client_id
= decoded
.get_payload_claim("clientId").as_string();
150 string role_arn
= s
->info
.args
.get("RoleArn");
151 auto provider
= get_provider(role_arn
, t
.iss
);
155 vector
<string
> client_ids
= provider
->get_client_ids();
156 vector
<string
> thumbprints
= provider
->get_thumbprints();
157 if (! client_ids
.empty()) {
158 if (! is_client_id_valid(client_ids
, t
.client_id
) && ! is_client_id_valid(client_ids
, t
.aud
)) {
163 if (decoded
.has_algorithm()) {
164 auto& algorithm
= decoded
.get_algorithm();
166 validate_signature(dpp
, decoded
, algorithm
, t
.iss
, thumbprints
);
173 } catch (int error
) {
174 if (error
== -EACCES
) {
177 ldpp_dout(dpp
, 5) << "Invalid JWT token" << dendl
;
181 ldpp_dout(dpp
, 5) << "Invalid JWT token" << dendl
;
188 WebTokenEngine::validate_signature(const DoutPrefixProvider
* dpp
, const jwt::decoded_jwt
& decoded
, const string
& algorithm
, const string
& iss
, const vector
<string
>& thumbprints
) const
190 if (algorithm
!= "HS256" && algorithm
!= "HS384" && algorithm
!= "HS512") {
192 string cert_url
= iss
+ "/protocol/openid-connect/certs";
193 bufferlist cert_resp
;
194 RGWHTTPTransceiver
cert_req(cct
, "GET", cert_url
, &cert_resp
);
196 cert_req
.append_header("Content-Type", "application/x-www-form-urlencoded");
198 int res
= cert_req
.process(null_yield
);
200 ldpp_dout(dpp
, 10) << "HTTP request res: " << res
<< dendl
;
204 ldpp_dout(dpp
, 20) << "HTTP status: " << cert_req
.get_http_status() << dendl
;
205 ldpp_dout(dpp
, 20) << "JSON Response is: " << cert_resp
.c_str() << dendl
;
208 if (parser
.parse(cert_resp
.c_str(), cert_resp
.length())) {
209 JSONObj::data_val val
;
210 if (parser
.get_data("keys", &val
)) {
211 if (val
.str
[0] == '[') {
214 if (val
.str
[val
.str
.size() - 1] == ']') {
215 val
.str
= val
.str
.erase(val
.str
.size() - 1, 1);
217 if (parser
.parse(val
.str
.c_str(), val
.str
.size())) {
219 if (JSONDecoder::decode_json("x5c", x5c
, &parser
)) {
221 bool found_valid_cert
= false;
222 for (auto& it
: x5c
) {
223 cert
= "-----BEGIN CERTIFICATE-----\n" + it
+ "\n-----END CERTIFICATE-----";
224 ldpp_dout(dpp
, 20) << "Certificate is: " << cert
.c_str() << dendl
;
225 if (is_cert_valid(thumbprints
, cert
)) {
226 found_valid_cert
= true;
229 found_valid_cert
= true;
231 if (! found_valid_cert
) {
235 //verify method takes care of expired tokens also
236 if (algorithm
== "RS256") {
237 auto verifier
= jwt::verify()
238 .allow_algorithm(jwt::algorithm::rs256
{cert
});
240 verifier
.verify(decoded
);
241 } else if (algorithm
== "RS384") {
242 auto verifier
= jwt::verify()
243 .allow_algorithm(jwt::algorithm::rs384
{cert
});
245 verifier
.verify(decoded
);
246 } else if (algorithm
== "RS512") {
247 auto verifier
= jwt::verify()
248 .allow_algorithm(jwt::algorithm::rs512
{cert
});
250 verifier
.verify(decoded
);
251 } else if (algorithm
== "ES256") {
252 auto verifier
= jwt::verify()
253 .allow_algorithm(jwt::algorithm::es256
{cert
});
255 verifier
.verify(decoded
);
256 } else if (algorithm
== "ES384") {
257 auto verifier
= jwt::verify()
258 .allow_algorithm(jwt::algorithm::es384
{cert
});
260 verifier
.verify(decoded
);
261 } else if (algorithm
== "ES512") {
262 auto verifier
= jwt::verify()
263 .allow_algorithm(jwt::algorithm::es512
{cert
});
265 verifier
.verify(decoded
);
266 } else if (algorithm
== "PS256") {
267 auto verifier
= jwt::verify()
268 .allow_algorithm(jwt::algorithm::ps256
{cert
});
270 verifier
.verify(decoded
);
271 } else if (algorithm
== "PS384") {
272 auto verifier
= jwt::verify()
273 .allow_algorithm(jwt::algorithm::ps384
{cert
});
275 verifier
.verify(decoded
);
276 } else if (algorithm
== "PS512") {
277 auto verifier
= jwt::verify()
278 .allow_algorithm(jwt::algorithm::ps512
{cert
});
280 verifier
.verify(decoded
);
282 } catch (std::runtime_error
& e
) {
283 ldpp_dout(dpp
, 0) << "Signature validation failed: " << e
.what() << dendl
;
287 ldpp_dout(dpp
, 0) << "Signature validation failed" << dendl
;
291 ldpp_dout(dpp
, 0) << "x5c not present" << dendl
;
295 ldpp_dout(dpp
, 0) << "Malformed JSON object for keys" << dendl
;
299 ldpp_dout(dpp
, 0) << "keys not present in JSON" << dendl
;
303 ldpp_dout(dpp
, 0) << "Malformed json returned while fetching cert" << dendl
;
305 } //if-else parser cert_resp
307 ldpp_dout(dpp
, 0) << "JWT signed by HMAC algos are currently not supported" << dendl
;
312 WebTokenEngine::result_t
313 WebTokenEngine::authenticate( const DoutPrefixProvider
* dpp
,
314 const std::string
& token
,
315 const req_state
* const s
) const
317 boost::optional
<WebTokenEngine::token_t
> t
;
319 if (! is_applicable(token
)) {
320 return result_t::deny();
324 t
= get_from_jwt(dpp
, token
, s
);
327 return result_t::deny(-EACCES
);
331 string role_session
= s
->info
.args
.get("RoleSessionName");
332 if (role_session
.empty()) {
333 return result_t::deny(-EACCES
);
335 auto apl
= apl_factory
->create_apl_web_identity(cct
, s
, role_session
, *t
);
336 return result_t::grant(std::move(apl
));
338 return result_t::deny(-EACCES
);
341 } // namespace rgw::auth::sts
343 int RGWREST_STS::verify_permission()
345 STS::STSService
_sts(s
->cct
, store
, s
->user
->get_id(), s
->auth
.identity
.get());
346 sts
= std::move(_sts
);
348 string rArn
= s
->info
.args
.get("RoleArn");
349 const auto& [ret
, role
] = sts
.getRoleInfo(rArn
);
353 string policy
= role
.get_assume_role_policy();
354 buffer::list bl
= buffer::list::static_from_string(policy
);
357 //TODO - This step should be part of Role Creation
359 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
360 //Check if the input role arn is there as one of the Principals in the policy,
361 // If yes, then return 0, else -EPERM
362 auto p_res
= p
.eval_principal(s
->env
, *s
->auth
.identity
);
363 if (p_res
== rgw::IAM::Effect::Deny
) {
366 auto c_res
= p
.eval_conditions(s
->env
);
367 if (c_res
== rgw::IAM::Effect::Deny
) {
370 } catch (rgw::IAM::PolicyParseException
& e
) {
371 ldout(s
->cct
, 20) << "failed to parse policy: " << e
.what() << dendl
;
378 void RGWREST_STS::send_response()
381 set_req_state_err(s
, op_ret
);
387 int RGWSTSGetSessionToken::verify_permission()
389 rgw::Partition partition
= rgw::Partition::aws
;
390 rgw::Service service
= rgw::Service::s3
;
391 if (!verify_user_permission(this,
393 rgw::ARN(partition
, service
, "", s
->user
->get_tenant(), ""),
394 rgw::IAM::stsGetSessionToken
)) {
401 int RGWSTSGetSessionToken::get_params()
403 duration
= s
->info
.args
.get("DurationSeconds");
404 serialNumber
= s
->info
.args
.get("SerialNumber");
405 tokenCode
= s
->info
.args
.get("TokenCode");
407 if (! duration
.empty()) {
409 uint64_t duration_in_secs
= strict_strtoll(duration
.c_str(), 10, &err
);
414 if (duration_in_secs
< STS::GetSessionTokenRequest::getMinDuration() ||
415 duration_in_secs
> s
->cct
->_conf
->rgw_sts_max_session_duration
)
422 void RGWSTSGetSessionToken::execute()
424 if (op_ret
= get_params(); op_ret
< 0) {
428 STS::STSService
sts(s
->cct
, store
, s
->user
->get_id(), s
->auth
.identity
.get());
430 STS::GetSessionTokenRequest
req(duration
, serialNumber
, tokenCode
);
431 const auto& [ret
, creds
] = sts
.getSessionToken(req
);
432 op_ret
= std::move(ret
);
435 s
->formatter
->open_object_section("GetSessionTokenResponse");
436 s
->formatter
->open_object_section("GetSessionTokenResult");
437 s
->formatter
->open_object_section("Credentials");
438 creds
.dump(s
->formatter
);
439 s
->formatter
->close_section();
440 s
->formatter
->close_section();
441 s
->formatter
->close_section();
445 int RGWSTSAssumeRoleWithWebIdentity::get_params()
447 duration
= s
->info
.args
.get("DurationSeconds");
448 providerId
= s
->info
.args
.get("ProviderId");
449 policy
= s
->info
.args
.get("Policy");
450 roleArn
= s
->info
.args
.get("RoleArn");
451 roleSessionName
= s
->info
.args
.get("RoleSessionName");
452 iss
= s
->info
.args
.get("provider_id");
453 sub
= s
->info
.args
.get("sub");
454 aud
= s
->info
.args
.get("aud");
456 if (roleArn
.empty() || roleSessionName
.empty() || sub
.empty() || aud
.empty()) {
457 ldout(s
->cct
, 20) << "ERROR: one of role arn or role session name or token is empty" << dendl
;
461 if (! policy
.empty()) {
462 bufferlist bl
= bufferlist::static_from_string(policy
);
464 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
466 catch (rgw::IAM::PolicyParseException
& e
) {
467 ldout(s
->cct
, 20) << "failed to parse policy: " << e
.what() << "policy" << policy
<< dendl
;
468 return -ERR_MALFORMED_DOC
;
475 void RGWSTSAssumeRoleWithWebIdentity::execute()
477 if (op_ret
= get_params(); op_ret
< 0) {
481 STS::AssumeRoleWithWebIdentityRequest
req(duration
, providerId
, policy
, roleArn
,
482 roleSessionName
, iss
, sub
, aud
);
483 STS::AssumeRoleWithWebIdentityResponse response
= sts
.assumeRoleWithWebIdentity(req
);
484 op_ret
= std::move(response
.assumeRoleResp
.retCode
);
488 s
->formatter
->open_object_section("AssumeRoleWithWebIdentityResponse");
489 s
->formatter
->open_object_section("AssumeRoleWithWebIdentityResult");
490 encode_json("SubjectFromWebIdentityToken", response
.sub
, s
->formatter
);
491 encode_json("Audience", response
.aud
, s
->formatter
);
492 s
->formatter
->open_object_section("AssumedRoleUser");
493 response
.assumeRoleResp
.user
.dump(s
->formatter
);
494 s
->formatter
->close_section();
495 s
->formatter
->open_object_section("Credentials");
496 response
.assumeRoleResp
.creds
.dump(s
->formatter
);
497 s
->formatter
->close_section();
498 encode_json("Provider", response
.providerId
, s
->formatter
);
499 encode_json("PackedPolicySize", response
.assumeRoleResp
.packedPolicySize
, s
->formatter
);
500 s
->formatter
->close_section();
501 s
->formatter
->close_section();
505 int RGWSTSAssumeRole::get_params()
507 duration
= s
->info
.args
.get("DurationSeconds");
508 externalId
= s
->info
.args
.get("ExternalId");
509 policy
= s
->info
.args
.get("Policy");
510 roleArn
= s
->info
.args
.get("RoleArn");
511 roleSessionName
= s
->info
.args
.get("RoleSessionName");
512 serialNumber
= s
->info
.args
.get("SerialNumber");
513 tokenCode
= s
->info
.args
.get("TokenCode");
515 if (roleArn
.empty() || roleSessionName
.empty()) {
516 ldout(s
->cct
, 20) << "ERROR: one of role arn or role session name is empty" << dendl
;
520 if (! policy
.empty()) {
521 bufferlist bl
= bufferlist::static_from_string(policy
);
523 const rgw::IAM::Policy
p(s
->cct
, s
->user
->get_tenant(), bl
);
525 catch (rgw::IAM::PolicyParseException
& e
) {
526 ldout(s
->cct
, 20) << "failed to parse policy: " << e
.what() << "policy" << policy
<< dendl
;
527 return -ERR_MALFORMED_DOC
;
534 void RGWSTSAssumeRole::execute()
536 if (op_ret
= get_params(); op_ret
< 0) {
540 STS::AssumeRoleRequest
req(duration
, externalId
, policy
, roleArn
,
541 roleSessionName
, serialNumber
, tokenCode
);
542 STS::AssumeRoleResponse response
= sts
.assumeRole(req
);
543 op_ret
= std::move(response
.retCode
);
546 s
->formatter
->open_object_section("AssumeRoleResponse");
547 s
->formatter
->open_object_section("AssumeRoleResult");
548 s
->formatter
->open_object_section("Credentials");
549 response
.creds
.dump(s
->formatter
);
550 s
->formatter
->close_section();
551 s
->formatter
->open_object_section("AssumedRoleUser");
552 response
.user
.dump(s
->formatter
);
553 s
->formatter
->close_section();
554 encode_json("PackedPolicySize", response
.packedPolicySize
, s
->formatter
);
555 s
->formatter
->close_section();
556 s
->formatter
->close_section();
560 int RGW_Auth_STS::authorize(const DoutPrefixProvider
*dpp
,
561 rgw::sal::RGWRadosStore
*store
,
562 const rgw::auth::StrategyRegistry
& auth_registry
,
565 return rgw::auth::Strategy::apply(dpp
, auth_registry
.get_sts(), s
);
568 void RGWHandler_REST_STS::rgw_sts_parse_input()
570 if (post_body
.size() > 0) {
571 ldout(s
->cct
, 10) << "Content of POST: " << post_body
<< dendl
;
573 if (post_body
.find("Action") != string::npos
) {
574 boost::char_separator
<char> sep("&");
575 boost::tokenizer
<boost::char_separator
<char>> tokens(post_body
, sep
);
576 for (const auto& t
: tokens
) {
577 auto pos
= t
.find("=");
578 if (pos
!= string::npos
) {
579 s
->info
.args
.append(t
.substr(0,pos
),
580 url_decode(t
.substr(pos
+1, t
.size() -1)));
585 auto payload_hash
= rgw::auth::s3::calc_v4_payload_hash(post_body
);
586 s
->info
.args
.append("PayloadHash", payload_hash
);
589 RGWOp
*RGWHandler_REST_STS::op_post()
591 rgw_sts_parse_input();
593 if (s
->info
.args
.exists("Action")) {
594 string action
= s
->info
.args
.get("Action");
595 if (action
== "AssumeRole") {
596 return new RGWSTSAssumeRole
;
597 } else if (action
== "GetSessionToken") {
598 return new RGWSTSGetSessionToken
;
599 } else if (action
== "AssumeRoleWithWebIdentity") {
600 return new RGWSTSAssumeRoleWithWebIdentity
;
607 int RGWHandler_REST_STS::init(rgw::sal::RGWRadosStore
*store
,
609 rgw::io::BasicClient
*cio
)
613 if (int ret
= RGWHandler_REST_STS::init_from_header(s
, RGW_FORMAT_XML
, true); ret
< 0) {
614 ldout(s
->cct
, 10) << "init_from_header returned err=" << ret
<< dendl
;
618 return RGWHandler_REST::init(store
, s
, cio
);
621 int RGWHandler_REST_STS::authorize(const DoutPrefixProvider
* dpp
)
623 if (s
->info
.args
.exists("Action") && s
->info
.args
.get("Action") == "AssumeRoleWithWebIdentity") {
624 return RGW_Auth_STS::authorize(dpp
, store
, auth_registry
, s
);
626 return RGW_Auth_S3::authorize(dpp
, store
, auth_registry
, s
);
629 int RGWHandler_REST_STS::init_from_header(struct req_state
* s
,
630 int default_formatter
,
631 bool configurable_format
)
636 s
->prot_flags
= RGW_REST_STS
;
638 const char *p
, *req_name
;
639 if (req_name
= s
->relative_uri
.c_str(); *req_name
== '?') {
642 p
= s
->info
.request_params
.c_str();
646 s
->info
.args
.parse();
648 /* must be called after the args parsing */
649 if (int ret
= allocate_formatter(s
, default_formatter
, configurable_format
); ret
< 0)
652 if (*req_name
!= '/')
661 int pos
= req
.find('/');
663 first
= req
.substr(0, pos
);
672 RGWRESTMgr_STS::get_handler(struct req_state
* const s
,
673 const rgw::auth::StrategyRegistry
& auth_registry
,
674 const std::string
& frontend_prefix
)
676 return new RGWHandler_REST_STS(auth_registry
);