]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_rest_sts.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / rgw / rgw_rest_sts.cc
CommitLineData
11fdf7f2 1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
9f95a23c 2// vim: ts=8 sw=2 smarttab ft=cpp
f91f0fd5
TL
3#include <vector>
4#include <string>
5#include <array>
f67539c2 6#include <string_view>
f91f0fd5
TL
7#include <sstream>
8#include <memory>
11fdf7f2
TL
9
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>
15
11fdf7f2 16
f91f0fd5
TL
17
18#include "ceph_ver.h"
11fdf7f2
TL
19#include "common/Formatter.h"
20#include "common/utf8.h"
21#include "common/ceph_json.h"
22
23#include "rgw_rest.h"
24#include "rgw_auth.h"
25#include "rgw_auth_registry.h"
f91f0fd5 26#include "jwt-cpp/jwt.h"
11fdf7f2 27#include "rgw_rest_sts.h"
11fdf7f2
TL
28
29#include "rgw_formats.h"
30#include "rgw_client_io.h"
31
32#include "rgw_request.h"
33#include "rgw_process.h"
34#include "rgw_iam_policy.h"
35#include "rgw_iam_policy_keywords.h"
36
37#include "rgw_sts.h"
f91f0fd5 38#include "rgw_rest_oidc_provider.h"
f67539c2 39
11fdf7f2
TL
40
41#define dout_context g_ceph_context
42#define dout_subsys ceph_subsys_rgw
43
20effc67
TL
44using namespace std;
45
9f95a23c 46namespace rgw::auth::sts {
11fdf7f2
TL
47
48bool
49WebTokenEngine::is_applicable(const std::string& token) const noexcept
50{
51 return ! token.empty();
52}
53
f67539c2
TL
54std::string
55WebTokenEngine::get_role_tenant(const string& role_arn) const
f91f0fd5
TL
56{
57 string tenant;
58 auto r_arn = rgw::ARN::parse(role_arn);
59 if (r_arn) {
60 tenant = r_arn->account;
61 }
f67539c2
TL
62 return tenant;
63}
64
20effc67
TL
65std::string
66WebTokenEngine::get_role_name(const string& role_arn) const
67{
68 string role_name;
69 auto r_arn = rgw::ARN::parse(role_arn);
70 if (r_arn) {
71 role_name = r_arn->resource;
72 }
73 if (!role_name.empty()) {
74 auto pos = role_name.find_last_of('/');
75 if(pos != string::npos) {
76 role_name = role_name.substr(pos + 1);
77 }
78 }
79 return role_name;
80}
81
82std::unique_ptr<rgw::sal::RGWOIDCProvider>
b3b6e05e 83WebTokenEngine::get_provider(const DoutPrefixProvider *dpp, const string& role_arn, const string& iss) const
f67539c2
TL
84{
85 string tenant = get_role_tenant(role_arn);
86
f91f0fd5
TL
87 string idp_url = iss;
88 auto pos = idp_url.find("http://");
89 if (pos == std::string::npos) {
90 pos = idp_url.find("https://");
91 if (pos != std::string::npos) {
92 idp_url.erase(pos, 8);
93 } else {
94 pos = idp_url.find("www.");
95 if (pos != std::string::npos) {
96 idp_url.erase(pos, 4);
97 }
98 }
99 } else {
100 idp_url.erase(pos, 7);
101 }
102 auto provider_arn = rgw::ARN(idp_url, "oidc-provider", tenant);
103 string p_arn = provider_arn.to_string();
1e59de90 104 std::unique_ptr<rgw::sal::RGWOIDCProvider> provider = driver->get_oidc_provider();
20effc67
TL
105 provider->set_arn(p_arn);
106 provider->set_tenant(tenant);
107 auto ret = provider->get(dpp);
f91f0fd5 108 if (ret < 0) {
20effc67 109 return nullptr;
f91f0fd5
TL
110 }
111 return provider;
112}
113
114bool
115WebTokenEngine::is_client_id_valid(vector<string>& client_ids, const string& client_id) const
116{
117 for (auto it : client_ids) {
118 if (it == client_id) {
119 return true;
120 }
121 }
122 return false;
123}
124
125bool
126WebTokenEngine::is_cert_valid(const vector<string>& thumbprints, const string& cert) const
127{
128 //calculate thumbprint of cert
129 std::unique_ptr<BIO, decltype(&BIO_free_all)> certbio(BIO_new_mem_buf(cert.data(), cert.size()), BIO_free_all);
130 std::unique_ptr<BIO, decltype(&BIO_free_all)> keybio(BIO_new(BIO_s_mem()), BIO_free_all);
131 string pw="";
132 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);
133 const EVP_MD* fprint_type = EVP_sha1();
134 unsigned int fprint_size;
135 unsigned char fprint[EVP_MAX_MD_SIZE];
136
137 if (!X509_digest(x_509cert.get(), fprint_type, fprint, &fprint_size)) {
138 return false;
139 }
140 stringstream ss;
141 for (unsigned int i = 0; i < fprint_size; i++) {
142 ss << std::setfill('0') << std::setw(2) << std::hex << (0xFF & (unsigned int)fprint[i]);
143 }
144 std::string digest = ss.str();
145
146 for (auto& it : thumbprints) {
147 if (boost::iequals(it,digest)) {
148 return true;
149 }
150 }
151 return false;
152}
153
20effc67
TL
154template <typename T>
155void
156WebTokenEngine::recurse_and_insert(const string& key, const jwt::claim& c, T& t) const
157{
158 string s_val;
159 jwt::claim::type c_type = c.get_type();
160 switch(c_type) {
161 case jwt::claim::type::null:
162 break;
163 case jwt::claim::type::boolean:
164 case jwt::claim::type::number:
165 case jwt::claim::type::int64:
166 {
167 s_val = c.to_json().serialize();
168 t.emplace(std::make_pair(key, s_val));
169 break;
170 }
171 case jwt::claim::type::string:
172 {
173 s_val = c.to_json().to_str();
174 t.emplace(std::make_pair(key, s_val));
175 break;
176 }
177 case jwt::claim::type::array:
178 {
179 const picojson::array& arr = c.as_array();
180 for (auto& a : arr) {
181 recurse_and_insert(key, jwt::claim(a), t);
182 }
183 break;
184 }
185 case jwt::claim::type::object:
186 {
187 const picojson::object& obj = c.as_object();
188 for (auto& m : obj) {
189 recurse_and_insert(m.first, jwt::claim(m.second), t);
190 }
191 break;
192 }
193 }
194 return;
195}
196
197//Extract all token claims so that they can be later used in the Condition element of Role's trust policy
198WebTokenEngine::token_t
199WebTokenEngine::get_token_claims(const jwt::decoded_jwt& decoded) const
200{
201 WebTokenEngine::token_t token;
202 const auto& claims = decoded.get_payload_claims();
203
204 for (auto& c : claims) {
205 if (c.first == string(princTagsNamespace)) {
206 continue;
207 }
208 recurse_and_insert(c.first, c.second, token);
209 }
210 return token;
211}
212
f91f0fd5 213//Offline validation of incoming Web Token which is a signed JWT (JSON Web Token)
20effc67 214std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>>
f67539c2
TL
215WebTokenEngine::get_from_jwt(const DoutPrefixProvider* dpp, const std::string& token, const req_state* const s,
216 optional_yield y) const
11fdf7f2 217{
f91f0fd5 218 WebTokenEngine::token_t t;
20effc67 219 WebTokenEngine::principal_tags_t principal_tags;
f91f0fd5
TL
220 try {
221 const auto& decoded = jwt::decode(token);
f67539c2 222
f91f0fd5
TL
223 auto& payload = decoded.get_payload();
224 ldpp_dout(dpp, 20) << " payload = " << payload << dendl;
20effc67
TL
225
226 t = get_token_claims(decoded);
227
228 string iss;
f91f0fd5 229 if (decoded.has_issuer()) {
20effc67 230 iss = decoded.get_issuer();
f91f0fd5 231 }
20effc67
TL
232
233 set<string> aud;
f91f0fd5 234 if (decoded.has_audience()) {
20effc67 235 aud = decoded.get_audience();
f91f0fd5 236 }
20effc67
TL
237
238 string client_id;
f91f0fd5 239 if (decoded.has_payload_claim("client_id")) {
20effc67 240 client_id = decoded.get_payload_claim("client_id").as_string();
f91f0fd5 241 }
20effc67
TL
242 if (client_id.empty() && decoded.has_payload_claim("clientId")) {
243 client_id = decoded.get_payload_claim("clientId").as_string();
f91f0fd5 244 }
20effc67
TL
245 string azp;
246 if (decoded.has_payload_claim("azp")) {
247 azp = decoded.get_payload_claim("azp").as_string();
248 }
249
f91f0fd5 250 string role_arn = s->info.args.get("RoleArn");
20effc67 251 auto provider = get_provider(dpp, role_arn, iss);
f91f0fd5 252 if (! provider) {
20effc67 253 ldpp_dout(dpp, 0) << "Couldn't get oidc provider info using input iss" << iss << dendl;
f91f0fd5
TL
254 throw -EACCES;
255 }
20effc67
TL
256 if (decoded.has_payload_claim(string(princTagsNamespace))) {
257 auto& cl = decoded.get_payload_claim(string(princTagsNamespace));
258 if (cl.get_type() == jwt::claim::type::object || cl.get_type() == jwt::claim::type::array) {
259 recurse_and_insert("dummy", cl, principal_tags);
260 for (auto it : principal_tags) {
261 ldpp_dout(dpp, 5) << "Key: " << it.first << " Value: " << it.second << dendl;
262 }
263 } else {
264 ldpp_dout(dpp, 0) << "Malformed principal tags" << cl.as_string() << dendl;
265 throw -EINVAL;
266 }
267 }
f91f0fd5
TL
268 vector<string> client_ids = provider->get_client_ids();
269 vector<string> thumbprints = provider->get_thumbprints();
270 if (! client_ids.empty()) {
20effc67
TL
271 bool found = false;
272 for (auto& it : aud) {
273 if (is_client_id_valid(client_ids, it)) {
274 found = true;
275 break;
276 }
277 }
278 if (! found && ! is_client_id_valid(client_ids, client_id) && ! is_client_id_valid(client_ids, azp)) {
f67539c2 279 ldpp_dout(dpp, 0) << "Client id in token doesn't match with that registered with oidc provider" << dendl;
f91f0fd5
TL
280 throw -EACCES;
281 }
282 }
283 //Validate signature
284 if (decoded.has_algorithm()) {
285 auto& algorithm = decoded.get_algorithm();
286 try {
20effc67 287 validate_signature(dpp, decoded, algorithm, iss, thumbprints, y);
f91f0fd5
TL
288 } catch (...) {
289 throw -EACCES;
290 }
291 } else {
20effc67 292 return {boost::none, boost::none};
f91f0fd5
TL
293 }
294 } catch (int error) {
295 if (error == -EACCES) {
296 throw -EACCES;
297 }
298 ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl;
20effc67 299 return {boost::none, boost::none};
f91f0fd5
TL
300 }
301 catch (...) {
302 ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl;
20effc67
TL
303 return {boost::none, boost::none};
304 }
305 return {t, principal_tags};
306}
307
308std::string
309WebTokenEngine::get_cert_url(const string& iss, const DoutPrefixProvider *dpp, optional_yield y) const
310{
311 string cert_url;
1e59de90 312 string openidc_wellknown_url = iss;
20effc67 313 bufferlist openidc_resp;
1e59de90
TL
314
315 if (openidc_wellknown_url.back() == '/') {
316 openidc_wellknown_url.pop_back();
317 }
318 openidc_wellknown_url.append("/.well-known/openid-configuration");
319
20effc67
TL
320 RGWHTTPTransceiver openidc_req(cct, "GET", openidc_wellknown_url, &openidc_resp);
321
322 //Headers
323 openidc_req.append_header("Content-Type", "application/x-www-form-urlencoded");
324
325 int res = openidc_req.process(y);
326 if (res < 0) {
327 ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl;
328 throw -EINVAL;
329 }
330
331 //Debug only
332 ldpp_dout(dpp, 20) << "HTTP status: " << openidc_req.get_http_status() << dendl;
333 ldpp_dout(dpp, 20) << "JSON Response is: " << openidc_resp.c_str() << dendl;
334
335 JSONParser parser;
336 if (parser.parse(openidc_resp.c_str(), openidc_resp.length())) {
337 JSONObj::data_val val;
338 if (parser.get_data("jwks_uri", &val)) {
339 cert_url = val.str.c_str();
340 ldpp_dout(dpp, 20) << "Cert URL is: " << cert_url.c_str() << dendl;
341 } else {
342 ldpp_dout(dpp, 0) << "Malformed json returned while fetching openidc url" << dendl;
343 }
f91f0fd5 344 }
20effc67 345 return cert_url;
f91f0fd5
TL
346}
347
348void
f67539c2 349WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector<string>& thumbprints, optional_yield y) const
f91f0fd5
TL
350{
351 if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") {
20effc67
TL
352 string cert_url = get_cert_url(iss, dpp, y);
353 if (cert_url.empty()) {
354 throw -EINVAL;
355 }
356
f91f0fd5 357 // Get certificate
f91f0fd5
TL
358 bufferlist cert_resp;
359 RGWHTTPTransceiver cert_req(cct, "GET", cert_url, &cert_resp);
11fdf7f2 360 //Headers
f91f0fd5
TL
361 cert_req.append_header("Content-Type", "application/x-www-form-urlencoded");
362
f67539c2 363 int res = cert_req.process(y);
11fdf7f2
TL
364 if (res < 0) {
365 ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl;
366 throw -EINVAL;
367 }
368 //Debug only
f91f0fd5
TL
369 ldpp_dout(dpp, 20) << "HTTP status: " << cert_req.get_http_status() << dendl;
370 ldpp_dout(dpp, 20) << "JSON Response is: " << cert_resp.c_str() << dendl;
11fdf7f2
TL
371
372 JSONParser parser;
f91f0fd5
TL
373 if (parser.parse(cert_resp.c_str(), cert_resp.length())) {
374 JSONObj::data_val val;
375 if (parser.get_data("keys", &val)) {
376 if (val.str[0] == '[') {
377 val.str.erase(0, 1);
378 }
379 if (val.str[val.str.size() - 1] == ']') {
380 val.str = val.str.erase(val.str.size() - 1, 1);
381 }
382 if (parser.parse(val.str.c_str(), val.str.size())) {
383 vector<string> x5c;
384 if (JSONDecoder::decode_json("x5c", x5c, &parser)) {
385 string cert;
386 bool found_valid_cert = false;
387 for (auto& it : x5c) {
388 cert = "-----BEGIN CERTIFICATE-----\n" + it + "\n-----END CERTIFICATE-----";
389 ldpp_dout(dpp, 20) << "Certificate is: " << cert.c_str() << dendl;
390 if (is_cert_valid(thumbprints, cert)) {
391 found_valid_cert = true;
392 break;
393 }
394 found_valid_cert = true;
395 }
396 if (! found_valid_cert) {
f67539c2 397 ldpp_dout(dpp, 0) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl;
f91f0fd5
TL
398 throw -EINVAL;
399 }
400 try {
401 //verify method takes care of expired tokens also
402 if (algorithm == "RS256") {
403 auto verifier = jwt::verify()
404 .allow_algorithm(jwt::algorithm::rs256{cert});
405
406 verifier.verify(decoded);
407 } else if (algorithm == "RS384") {
408 auto verifier = jwt::verify()
409 .allow_algorithm(jwt::algorithm::rs384{cert});
410
411 verifier.verify(decoded);
412 } else if (algorithm == "RS512") {
413 auto verifier = jwt::verify()
414 .allow_algorithm(jwt::algorithm::rs512{cert});
415
416 verifier.verify(decoded);
417 } else if (algorithm == "ES256") {
418 auto verifier = jwt::verify()
419 .allow_algorithm(jwt::algorithm::es256{cert});
420
421 verifier.verify(decoded);
422 } else if (algorithm == "ES384") {
423 auto verifier = jwt::verify()
424 .allow_algorithm(jwt::algorithm::es384{cert});
425
426 verifier.verify(decoded);
427 } else if (algorithm == "ES512") {
428 auto verifier = jwt::verify()
429 .allow_algorithm(jwt::algorithm::es512{cert});
430
431 verifier.verify(decoded);
432 } else if (algorithm == "PS256") {
433 auto verifier = jwt::verify()
434 .allow_algorithm(jwt::algorithm::ps256{cert});
435
436 verifier.verify(decoded);
437 } else if (algorithm == "PS384") {
438 auto verifier = jwt::verify()
439 .allow_algorithm(jwt::algorithm::ps384{cert});
440
441 verifier.verify(decoded);
442 } else if (algorithm == "PS512") {
443 auto verifier = jwt::verify()
444 .allow_algorithm(jwt::algorithm::ps512{cert});
445
446 verifier.verify(decoded);
447 }
448 } catch (std::runtime_error& e) {
449 ldpp_dout(dpp, 0) << "Signature validation failed: " << e.what() << dendl;
450 throw;
451 }
452 catch (...) {
453 ldpp_dout(dpp, 0) << "Signature validation failed" << dendl;
454 throw;
455 }
456 } else {
457 ldpp_dout(dpp, 0) << "x5c not present" << dendl;
458 throw -EINVAL;
459 }
460 } else {
461 ldpp_dout(dpp, 0) << "Malformed JSON object for keys" << dendl;
462 throw -EINVAL;
463 }
464 } else {
465 ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl;
466 throw -EINVAL;
467 } //if-else get-data
11fdf7f2 468 } else {
f91f0fd5
TL
469 ldpp_dout(dpp, 0) << "Malformed json returned while fetching cert" << dendl;
470 throw -EINVAL;
471 } //if-else parser cert_resp
472 } else {
473 ldpp_dout(dpp, 0) << "JWT signed by HMAC algos are currently not supported" << dendl;
474 throw -EINVAL;
11fdf7f2 475 }
11fdf7f2
TL
476}
477
478WebTokenEngine::result_t
479WebTokenEngine::authenticate( const DoutPrefixProvider* dpp,
480 const std::string& token,
f67539c2
TL
481 const req_state* const s,
482 optional_yield y) const
11fdf7f2 483{
11fdf7f2
TL
484 if (! is_applicable(token)) {
485 return result_t::deny();
486 }
487
488 try {
20effc67
TL
489 auto [t, princ_tags] = get_from_jwt(dpp, token, s, y);
490 if (t) {
491 string role_session = s->info.args.get("RoleSessionName");
492 if (role_session.empty()) {
493 ldout(s->cct, 0) << "Role Session Name is empty " << dendl;
494 return result_t::deny(-EACCES);
495 }
496 string role_arn = s->info.args.get("RoleArn");
497 string role_tenant = get_role_tenant(role_arn);
498 string role_name = get_role_name(role_arn);
1e59de90 499 std::unique_ptr<rgw::sal::RGWRole> role = driver->get_role(role_name, role_tenant);
20effc67
TL
500 int ret = role->get(dpp, y);
501 if (ret < 0) {
502 ldpp_dout(dpp, 0) << "Role not found: name:" << role_name << " tenant: " << role_tenant << dendl;
503 return result_t::deny(-EACCES);
504 }
505 boost::optional<multimap<string,string>> role_tags = role->get_tags();
506 auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, role_tenant, *t, role_tags, princ_tags);
507 return result_t::grant(std::move(apl));
508 }
509 return result_t::deny(-EACCES);
f91f0fd5
TL
510 }
511 catch (...) {
11fdf7f2
TL
512 return result_t::deny(-EACCES);
513 }
11fdf7f2
TL
514}
515
f91f0fd5 516} // namespace rgw::auth::sts
11fdf7f2 517
f67539c2 518int RGWREST_STS::verify_permission(optional_yield y)
11fdf7f2 519{
1e59de90 520 STS::STSService _sts(s->cct, driver, s->user->get_id(), s->auth.identity.get());
11fdf7f2
TL
521 sts = std::move(_sts);
522
523 string rArn = s->info.args.get("RoleArn");
b3b6e05e 524 const auto& [ret, role] = sts.getRoleInfo(s, rArn, y);
11fdf7f2 525 if (ret < 0) {
b3b6e05e 526 ldpp_dout(this, 0) << "failed to get role info using role arn: " << rArn << dendl;
11fdf7f2
TL
527 return ret;
528 }
20effc67 529 string policy = role->get_assume_role_policy();
11fdf7f2
TL
530 buffer::list bl = buffer::list::static_from_string(policy);
531
532 //Parse the policy
533 //TODO - This step should be part of Role Creation
534 try {
1e59de90 535 const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl, false);
20effc67
TL
536 if (!s->principal_tags.empty()) {
537 auto res = p.eval(s->env, *s->auth.identity, rgw::IAM::stsTagSession, boost::none);
538 if (res != rgw::IAM::Effect::Allow) {
539 ldout(s->cct, 0) << "evaluating policy for stsTagSession returned deny/pass" << dendl;
540 return -EPERM;
541 }
542 }
543 uint64_t op;
544 if (get_type() == RGW_STS_ASSUME_ROLE_WEB_IDENTITY) {
545 op = rgw::IAM::stsAssumeRoleWithWebIdentity;
546 } else {
547 op = rgw::IAM::stsAssumeRole;
11fdf7f2 548 }
20effc67
TL
549
550 auto res = p.eval(s->env, *s->auth.identity, op, boost::none);
551 if (res != rgw::IAM::Effect::Allow) {
552 ldout(s->cct, 0) << "evaluating policy for op: " << op << " returned deny/pass" << dendl;
11fdf7f2
TL
553 return -EPERM;
554 }
555 } catch (rgw::IAM::PolicyParseException& e) {
b3b6e05e 556 ldpp_dout(this, 0) << "failed to parse policy: " << e.what() << dendl;
11fdf7f2
TL
557 return -EPERM;
558 }
559
560 return 0;
561}
562
563void RGWREST_STS::send_response()
564{
565 if (op_ret) {
566 set_req_state_err(s, op_ret);
567 }
568 dump_errno(s);
569 end_header(s);
570}
571
f67539c2 572int RGWSTSGetSessionToken::verify_permission(optional_yield y)
11fdf7f2 573{
eafe8130
TL
574 rgw::Partition partition = rgw::Partition::aws;
575 rgw::Service service = rgw::Service::s3;
11fdf7f2
TL
576 if (!verify_user_permission(this,
577 s,
9f95a23c 578 rgw::ARN(partition, service, "", s->user->get_tenant(), ""),
11fdf7f2 579 rgw::IAM::stsGetSessionToken)) {
b3b6e05e 580 ldpp_dout(this, 0) << "User does not have permssion to perform GetSessionToken" << dendl;
11fdf7f2
TL
581 return -EACCES;
582 }
583
584 return 0;
585}
586
587int RGWSTSGetSessionToken::get_params()
588{
589 duration = s->info.args.get("DurationSeconds");
590 serialNumber = s->info.args.get("SerialNumber");
591 tokenCode = s->info.args.get("TokenCode");
592
593 if (! duration.empty()) {
9f95a23c
TL
594 string err;
595 uint64_t duration_in_secs = strict_strtoll(duration.c_str(), 10, &err);
596 if (!err.empty()) {
b3b6e05e 597 ldpp_dout(this, 0) << "Invalid value of input duration: " << duration << dendl;
9f95a23c
TL
598 return -EINVAL;
599 }
600
11fdf7f2 601 if (duration_in_secs < STS::GetSessionTokenRequest::getMinDuration() ||
f67539c2 602 duration_in_secs > s->cct->_conf->rgw_sts_max_session_duration) {
b3b6e05e 603 ldpp_dout(this, 0) << "Invalid duration in secs: " << duration_in_secs << dendl;
11fdf7f2 604 return -EINVAL;
f67539c2 605 }
11fdf7f2
TL
606 }
607
608 return 0;
609}
610
f67539c2 611void RGWSTSGetSessionToken::execute(optional_yield y)
11fdf7f2
TL
612{
613 if (op_ret = get_params(); op_ret < 0) {
614 return;
615 }
616
1e59de90 617 STS::STSService sts(s->cct, driver, s->user->get_id(), s->auth.identity.get());
11fdf7f2
TL
618
619 STS::GetSessionTokenRequest req(duration, serialNumber, tokenCode);
20effc67 620 const auto& [ret, creds] = sts.getSessionToken(this, req);
11fdf7f2
TL
621 op_ret = std::move(ret);
622 //Dump the output
623 if (op_ret == 0) {
624 s->formatter->open_object_section("GetSessionTokenResponse");
625 s->formatter->open_object_section("GetSessionTokenResult");
626 s->formatter->open_object_section("Credentials");
627 creds.dump(s->formatter);
628 s->formatter->close_section();
629 s->formatter->close_section();
630 s->formatter->close_section();
631 }
632}
633
634int RGWSTSAssumeRoleWithWebIdentity::get_params()
635{
636 duration = s->info.args.get("DurationSeconds");
637 providerId = s->info.args.get("ProviderId");
638 policy = s->info.args.get("Policy");
639 roleArn = s->info.args.get("RoleArn");
640 roleSessionName = s->info.args.get("RoleSessionName");
641 iss = s->info.args.get("provider_id");
642 sub = s->info.args.get("sub");
643 aud = s->info.args.get("aud");
644
645 if (roleArn.empty() || roleSessionName.empty() || sub.empty() || aud.empty()) {
b3b6e05e 646 ldpp_dout(this, 0) << "ERROR: one of role arn or role session name or token is empty" << dendl;
11fdf7f2
TL
647 return -EINVAL;
648 }
649
650 if (! policy.empty()) {
651 bufferlist bl = bufferlist::static_from_string(policy);
652 try {
1e59de90
TL
653 const rgw::IAM::Policy p(
654 s->cct, s->user->get_tenant(), bl,
655 s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
11fdf7f2
TL
656 }
657 catch (rgw::IAM::PolicyParseException& e) {
1e59de90
TL
658 ldpp_dout(this, 5) << "failed to parse policy: " << e.what() << "policy" << policy << dendl;
659 s->err.message = e.what();
11fdf7f2
TL
660 return -ERR_MALFORMED_DOC;
661 }
662 }
663
664 return 0;
665}
666
f67539c2 667void RGWSTSAssumeRoleWithWebIdentity::execute(optional_yield y)
11fdf7f2
TL
668{
669 if (op_ret = get_params(); op_ret < 0) {
670 return;
671 }
672
f67539c2 673 STS::AssumeRoleWithWebIdentityRequest req(s->cct, duration, providerId, policy, roleArn,
20effc67
TL
674 roleSessionName, iss, sub, aud, s->principal_tags);
675 STS::AssumeRoleWithWebIdentityResponse response = sts.assumeRoleWithWebIdentity(this, req);
11fdf7f2
TL
676 op_ret = std::move(response.assumeRoleResp.retCode);
677
678 //Dump the output
679 if (op_ret == 0) {
680 s->formatter->open_object_section("AssumeRoleWithWebIdentityResponse");
681 s->formatter->open_object_section("AssumeRoleWithWebIdentityResult");
682 encode_json("SubjectFromWebIdentityToken", response.sub , s->formatter);
683 encode_json("Audience", response.aud , s->formatter);
684 s->formatter->open_object_section("AssumedRoleUser");
685 response.assumeRoleResp.user.dump(s->formatter);
686 s->formatter->close_section();
687 s->formatter->open_object_section("Credentials");
688 response.assumeRoleResp.creds.dump(s->formatter);
689 s->formatter->close_section();
690 encode_json("Provider", response.providerId , s->formatter);
691 encode_json("PackedPolicySize", response.assumeRoleResp.packedPolicySize , s->formatter);
692 s->formatter->close_section();
693 s->formatter->close_section();
694 }
695}
696
697int RGWSTSAssumeRole::get_params()
698{
699 duration = s->info.args.get("DurationSeconds");
700 externalId = s->info.args.get("ExternalId");
701 policy = s->info.args.get("Policy");
702 roleArn = s->info.args.get("RoleArn");
703 roleSessionName = s->info.args.get("RoleSessionName");
704 serialNumber = s->info.args.get("SerialNumber");
705 tokenCode = s->info.args.get("TokenCode");
706
707 if (roleArn.empty() || roleSessionName.empty()) {
b3b6e05e 708 ldpp_dout(this, 0) << "ERROR: one of role arn or role session name is empty" << dendl;
11fdf7f2
TL
709 return -EINVAL;
710 }
711
712 if (! policy.empty()) {
713 bufferlist bl = bufferlist::static_from_string(policy);
714 try {
1e59de90
TL
715 const rgw::IAM::Policy p(
716 s->cct, s->user->get_tenant(), bl,
717 s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
11fdf7f2
TL
718 }
719 catch (rgw::IAM::PolicyParseException& e) {
b3b6e05e 720 ldpp_dout(this, 0) << "failed to parse policy: " << e.what() << "policy" << policy << dendl;
1e59de90 721 s->err.message = e.what();
11fdf7f2
TL
722 return -ERR_MALFORMED_DOC;
723 }
724 }
725
726 return 0;
727}
728
f67539c2 729void RGWSTSAssumeRole::execute(optional_yield y)
11fdf7f2
TL
730{
731 if (op_ret = get_params(); op_ret < 0) {
732 return;
733 }
734
f67539c2 735 STS::AssumeRoleRequest req(s->cct, duration, externalId, policy, roleArn,
11fdf7f2 736 roleSessionName, serialNumber, tokenCode);
b3b6e05e 737 STS::AssumeRoleResponse response = sts.assumeRole(s, req, y);
11fdf7f2
TL
738 op_ret = std::move(response.retCode);
739 //Dump the output
740 if (op_ret == 0) {
741 s->formatter->open_object_section("AssumeRoleResponse");
742 s->formatter->open_object_section("AssumeRoleResult");
743 s->formatter->open_object_section("Credentials");
744 response.creds.dump(s->formatter);
745 s->formatter->close_section();
746 s->formatter->open_object_section("AssumedRoleUser");
747 response.user.dump(s->formatter);
748 s->formatter->close_section();
749 encode_json("PackedPolicySize", response.packedPolicySize , s->formatter);
750 s->formatter->close_section();
751 s->formatter->close_section();
752 }
753}
754
755int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp,
1e59de90 756 rgw::sal::Driver* driver,
11fdf7f2 757 const rgw::auth::StrategyRegistry& auth_registry,
1e59de90 758 req_state *s, optional_yield y)
11fdf7f2 759{
f67539c2 760 return rgw::auth::Strategy::apply(dpp, auth_registry.get_sts(), s, y);
11fdf7f2
TL
761}
762
1e59de90
TL
763using op_generator = RGWOp*(*)();
764static const std::unordered_map<std::string_view, op_generator> op_generators = {
765 {"AssumeRole", []() -> RGWOp* {return new RGWSTSAssumeRole;}},
766 {"GetSessionToken", []() -> RGWOp* {return new RGWSTSGetSessionToken;}},
767 {"AssumeRoleWithWebIdentity", []() -> RGWOp* {return new RGWSTSAssumeRoleWithWebIdentity;}}
768};
769
770bool RGWHandler_REST_STS::action_exists(const req_state* s)
11fdf7f2 771{
1e59de90
TL
772 if (s->info.args.exists("Action")) {
773 const std::string action_name = s->info.args.get("Action");
774 return op_generators.contains(action_name);
11fdf7f2 775 }
1e59de90 776 return false;
11fdf7f2
TL
777}
778
779RGWOp *RGWHandler_REST_STS::op_post()
780{
1e59de90
TL
781 if (s->info.args.exists("Action")) {
782 const std::string action_name = s->info.args.get("Action");
783 const auto action_it = op_generators.find(action_name);
784 if (action_it != op_generators.end()) {
785 return action_it->second();
11fdf7f2 786 }
1e59de90
TL
787 ldpp_dout(s, 10) << "unknown action '" << action_name << "' for STS handler" << dendl;
788 } else {
789 ldpp_dout(s, 10) << "missing action argument in STS handler" << dendl;
11fdf7f2 790 }
11fdf7f2
TL
791 return nullptr;
792}
793
1e59de90
TL
794int RGWHandler_REST_STS::init(rgw::sal::Driver* driver,
795 req_state *s,
11fdf7f2
TL
796 rgw::io::BasicClient *cio)
797{
798 s->dialect = "sts";
1e59de90 799 s->prot_flags = RGW_REST_STS;
11fdf7f2 800
1e59de90 801 return RGWHandler_REST::init(driver, s, cio);
11fdf7f2
TL
802}
803
f67539c2 804int RGWHandler_REST_STS::authorize(const DoutPrefixProvider* dpp, optional_yield y)
11fdf7f2
TL
805{
806 if (s->info.args.exists("Action") && s->info.args.get("Action") == "AssumeRoleWithWebIdentity") {
1e59de90 807 return RGW_Auth_STS::authorize(dpp, driver, auth_registry, s, y);
11fdf7f2 808 }
1e59de90 809 return RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y);
11fdf7f2
TL
810}
811
812RGWHandler_REST*
1e59de90
TL
813RGWRESTMgr_STS::get_handler(rgw::sal::Driver* driver,
814 req_state* const s,
f67539c2
TL
815 const rgw::auth::StrategyRegistry& auth_registry,
816 const std::string& frontend_prefix)
11fdf7f2
TL
817{
818 return new RGWHandler_REST_STS(auth_registry);
819}