]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_rest_sts.cc
Import ceph 15.2.8
[ceph.git] / ceph / src / rgw / rgw_rest_sts.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
3 #include <vector>
4 #include <string>
5 #include <array>
6 #include <sstream>
7 #include <memory>
8
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>
14
15
16
17 #include "ceph_ver.h"
18 #include "common/Formatter.h"
19 #include "common/utf8.h"
20 #include "common/ceph_json.h"
21
22 #include "rgw_rest.h"
23 #include "rgw_auth.h"
24 #include "rgw_auth_registry.h"
25 #include "jwt-cpp/jwt.h"
26 #include "rgw_rest_sts.h"
27
28 #include "rgw_formats.h"
29 #include "rgw_client_io.h"
30
31 #include "rgw_request.h"
32 #include "rgw_process.h"
33 #include "rgw_iam_policy.h"
34 #include "rgw_iam_policy_keywords.h"
35
36 #include "rgw_sts.h"
37 #include "rgw_rest_oidc_provider.h"
38 #include <boost/utility/string_ref.hpp>
39
40 #define dout_context g_ceph_context
41 #define dout_subsys ceph_subsys_rgw
42
43 namespace rgw::auth::sts {
44
45 bool
46 WebTokenEngine::is_applicable(const std::string& token) const noexcept
47 {
48 return ! token.empty();
49 }
50
51 boost::optional<RGWOIDCProvider>
52 WebTokenEngine::get_provider(const string& role_arn, const string& iss) const
53 {
54 string tenant;
55 auto r_arn = rgw::ARN::parse(role_arn);
56 if (r_arn) {
57 tenant = r_arn->account;
58 }
59 string idp_url = iss;
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);
65 } else {
66 pos = idp_url.find("www.");
67 if (pos != std::string::npos) {
68 idp_url.erase(pos, 4);
69 }
70 }
71 } else {
72 idp_url.erase(pos, 7);
73 }
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();
78 if (ret < 0) {
79 return boost::none;
80 }
81 return provider;
82 }
83
84 bool
85 WebTokenEngine::is_client_id_valid(vector<string>& client_ids, const string& client_id) const
86 {
87 for (auto it : client_ids) {
88 if (it == client_id) {
89 return true;
90 }
91 }
92 return false;
93 }
94
95 bool
96 WebTokenEngine::is_cert_valid(const vector<string>& thumbprints, const string& cert) const
97 {
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);
101 string pw="";
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];
106
107 if (!X509_digest(x_509cert.get(), fprint_type, fprint, &fprint_size)) {
108 return false;
109 }
110 stringstream ss;
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]);
113 }
114 std::string digest = ss.str();
115
116 for (auto& it : thumbprints) {
117 if (boost::iequals(it,digest)) {
118 return true;
119 }
120 }
121 return false;
122 }
123
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
127 {
128 WebTokenEngine::token_t t;
129 try {
130 const auto& decoded = jwt::decode(token);
131
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();
136 }
137 if (decoded.has_audience()) {
138 auto aud = decoded.get_audience();
139 t.aud = *(aud.begin());
140 }
141 if (decoded.has_subject()) {
142 t.sub = decoded.get_subject();
143 }
144 if (decoded.has_payload_claim("client_id")) {
145 t.client_id = decoded.get_payload_claim("client_id").as_string();
146 }
147 if (t.client_id.empty() && decoded.has_payload_claim("clientId")) {
148 t.client_id = decoded.get_payload_claim("clientId").as_string();
149 }
150 string role_arn = s->info.args.get("RoleArn");
151 auto provider = get_provider(role_arn, t.iss);
152 if (! provider) {
153 throw -EACCES;
154 }
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)) {
159 throw -EACCES;
160 }
161 }
162 //Validate signature
163 if (decoded.has_algorithm()) {
164 auto& algorithm = decoded.get_algorithm();
165 try {
166 validate_signature(dpp, decoded, algorithm, t.iss, thumbprints);
167 } catch (...) {
168 throw -EACCES;
169 }
170 } else {
171 return boost::none;
172 }
173 } catch (int error) {
174 if (error == -EACCES) {
175 throw -EACCES;
176 }
177 ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl;
178 return boost::none;
179 }
180 catch (...) {
181 ldpp_dout(dpp, 5) << "Invalid JWT token" << dendl;
182 return boost::none;
183 }
184 return t;
185 }
186
187 void
188 WebTokenEngine::validate_signature(const DoutPrefixProvider* dpp, const jwt::decoded_jwt& decoded, const string& algorithm, const string& iss, const vector<string>& thumbprints) const
189 {
190 if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") {
191 // Get certificate
192 string cert_url = iss + "/protocol/openid-connect/certs";
193 bufferlist cert_resp;
194 RGWHTTPTransceiver cert_req(cct, "GET", cert_url, &cert_resp);
195 //Headers
196 cert_req.append_header("Content-Type", "application/x-www-form-urlencoded");
197
198 int res = cert_req.process(null_yield);
199 if (res < 0) {
200 ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl;
201 throw -EINVAL;
202 }
203 //Debug only
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;
206
207 JSONParser parser;
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] == '[') {
212 val.str.erase(0, 1);
213 }
214 if (val.str[val.str.size() - 1] == ']') {
215 val.str = val.str.erase(val.str.size() - 1, 1);
216 }
217 if (parser.parse(val.str.c_str(), val.str.size())) {
218 vector<string> x5c;
219 if (JSONDecoder::decode_json("x5c", x5c, &parser)) {
220 string cert;
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;
227 break;
228 }
229 found_valid_cert = true;
230 }
231 if (! found_valid_cert) {
232 throw -EINVAL;
233 }
234 try {
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});
239
240 verifier.verify(decoded);
241 } else if (algorithm == "RS384") {
242 auto verifier = jwt::verify()
243 .allow_algorithm(jwt::algorithm::rs384{cert});
244
245 verifier.verify(decoded);
246 } else if (algorithm == "RS512") {
247 auto verifier = jwt::verify()
248 .allow_algorithm(jwt::algorithm::rs512{cert});
249
250 verifier.verify(decoded);
251 } else if (algorithm == "ES256") {
252 auto verifier = jwt::verify()
253 .allow_algorithm(jwt::algorithm::es256{cert});
254
255 verifier.verify(decoded);
256 } else if (algorithm == "ES384") {
257 auto verifier = jwt::verify()
258 .allow_algorithm(jwt::algorithm::es384{cert});
259
260 verifier.verify(decoded);
261 } else if (algorithm == "ES512") {
262 auto verifier = jwt::verify()
263 .allow_algorithm(jwt::algorithm::es512{cert});
264
265 verifier.verify(decoded);
266 } else if (algorithm == "PS256") {
267 auto verifier = jwt::verify()
268 .allow_algorithm(jwt::algorithm::ps256{cert});
269
270 verifier.verify(decoded);
271 } else if (algorithm == "PS384") {
272 auto verifier = jwt::verify()
273 .allow_algorithm(jwt::algorithm::ps384{cert});
274
275 verifier.verify(decoded);
276 } else if (algorithm == "PS512") {
277 auto verifier = jwt::verify()
278 .allow_algorithm(jwt::algorithm::ps512{cert});
279
280 verifier.verify(decoded);
281 }
282 } catch (std::runtime_error& e) {
283 ldpp_dout(dpp, 0) << "Signature validation failed: " << e.what() << dendl;
284 throw;
285 }
286 catch (...) {
287 ldpp_dout(dpp, 0) << "Signature validation failed" << dendl;
288 throw;
289 }
290 } else {
291 ldpp_dout(dpp, 0) << "x5c not present" << dendl;
292 throw -EINVAL;
293 }
294 } else {
295 ldpp_dout(dpp, 0) << "Malformed JSON object for keys" << dendl;
296 throw -EINVAL;
297 }
298 } else {
299 ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl;
300 throw -EINVAL;
301 } //if-else get-data
302 } else {
303 ldpp_dout(dpp, 0) << "Malformed json returned while fetching cert" << dendl;
304 throw -EINVAL;
305 } //if-else parser cert_resp
306 } else {
307 ldpp_dout(dpp, 0) << "JWT signed by HMAC algos are currently not supported" << dendl;
308 throw -EINVAL;
309 }
310 }
311
312 WebTokenEngine::result_t
313 WebTokenEngine::authenticate( const DoutPrefixProvider* dpp,
314 const std::string& token,
315 const req_state* const s) const
316 {
317 boost::optional<WebTokenEngine::token_t> t;
318
319 if (! is_applicable(token)) {
320 return result_t::deny();
321 }
322
323 try {
324 t = get_from_jwt(dpp, token, s);
325 }
326 catch (...) {
327 return result_t::deny(-EACCES);
328 }
329
330 if (t) {
331 string role_session = s->info.args.get("RoleSessionName");
332 if (role_session.empty()) {
333 return result_t::deny(-EACCES);
334 }
335 auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, *t);
336 return result_t::grant(std::move(apl));
337 }
338 return result_t::deny(-EACCES);
339 }
340
341 } // namespace rgw::auth::sts
342
343 int RGWREST_STS::verify_permission()
344 {
345 STS::STSService _sts(s->cct, store, s->user->get_id(), s->auth.identity.get());
346 sts = std::move(_sts);
347
348 string rArn = s->info.args.get("RoleArn");
349 const auto& [ret, role] = sts.getRoleInfo(rArn);
350 if (ret < 0) {
351 return ret;
352 }
353 string policy = role.get_assume_role_policy();
354 buffer::list bl = buffer::list::static_from_string(policy);
355
356 //Parse the policy
357 //TODO - This step should be part of Role Creation
358 try {
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) {
364 return -EPERM;
365 }
366 auto c_res = p.eval_conditions(s->env);
367 if (c_res == rgw::IAM::Effect::Deny) {
368 return -EPERM;
369 }
370 } catch (rgw::IAM::PolicyParseException& e) {
371 ldout(s->cct, 20) << "failed to parse policy: " << e.what() << dendl;
372 return -EPERM;
373 }
374
375 return 0;
376 }
377
378 void RGWREST_STS::send_response()
379 {
380 if (op_ret) {
381 set_req_state_err(s, op_ret);
382 }
383 dump_errno(s);
384 end_header(s);
385 }
386
387 int RGWSTSGetSessionToken::verify_permission()
388 {
389 rgw::Partition partition = rgw::Partition::aws;
390 rgw::Service service = rgw::Service::s3;
391 if (!verify_user_permission(this,
392 s,
393 rgw::ARN(partition, service, "", s->user->get_tenant(), ""),
394 rgw::IAM::stsGetSessionToken)) {
395 return -EACCES;
396 }
397
398 return 0;
399 }
400
401 int RGWSTSGetSessionToken::get_params()
402 {
403 duration = s->info.args.get("DurationSeconds");
404 serialNumber = s->info.args.get("SerialNumber");
405 tokenCode = s->info.args.get("TokenCode");
406
407 if (! duration.empty()) {
408 string err;
409 uint64_t duration_in_secs = strict_strtoll(duration.c_str(), 10, &err);
410 if (!err.empty()) {
411 return -EINVAL;
412 }
413
414 if (duration_in_secs < STS::GetSessionTokenRequest::getMinDuration() ||
415 duration_in_secs > s->cct->_conf->rgw_sts_max_session_duration)
416 return -EINVAL;
417 }
418
419 return 0;
420 }
421
422 void RGWSTSGetSessionToken::execute()
423 {
424 if (op_ret = get_params(); op_ret < 0) {
425 return;
426 }
427
428 STS::STSService sts(s->cct, store, s->user->get_id(), s->auth.identity.get());
429
430 STS::GetSessionTokenRequest req(duration, serialNumber, tokenCode);
431 const auto& [ret, creds] = sts.getSessionToken(req);
432 op_ret = std::move(ret);
433 //Dump the output
434 if (op_ret == 0) {
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();
442 }
443 }
444
445 int RGWSTSAssumeRoleWithWebIdentity::get_params()
446 {
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");
455
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;
458 return -EINVAL;
459 }
460
461 if (! policy.empty()) {
462 bufferlist bl = bufferlist::static_from_string(policy);
463 try {
464 const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
465 }
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;
469 }
470 }
471
472 return 0;
473 }
474
475 void RGWSTSAssumeRoleWithWebIdentity::execute()
476 {
477 if (op_ret = get_params(); op_ret < 0) {
478 return;
479 }
480
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);
485
486 //Dump the output
487 if (op_ret == 0) {
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();
502 }
503 }
504
505 int RGWSTSAssumeRole::get_params()
506 {
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");
514
515 if (roleArn.empty() || roleSessionName.empty()) {
516 ldout(s->cct, 20) << "ERROR: one of role arn or role session name is empty" << dendl;
517 return -EINVAL;
518 }
519
520 if (! policy.empty()) {
521 bufferlist bl = bufferlist::static_from_string(policy);
522 try {
523 const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
524 }
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;
528 }
529 }
530
531 return 0;
532 }
533
534 void RGWSTSAssumeRole::execute()
535 {
536 if (op_ret = get_params(); op_ret < 0) {
537 return;
538 }
539
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);
544 //Dump the output
545 if (op_ret == 0) {
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();
557 }
558 }
559
560 int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp,
561 rgw::sal::RGWRadosStore *store,
562 const rgw::auth::StrategyRegistry& auth_registry,
563 struct req_state *s)
564 {
565 return rgw::auth::Strategy::apply(dpp, auth_registry.get_sts(), s);
566 }
567
568 void RGWHandler_REST_STS::rgw_sts_parse_input()
569 {
570 if (post_body.size() > 0) {
571 ldout(s->cct, 10) << "Content of POST: " << post_body << dendl;
572
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)));
581 }
582 }
583 }
584 }
585 auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body);
586 s->info.args.append("PayloadHash", payload_hash);
587 }
588
589 RGWOp *RGWHandler_REST_STS::op_post()
590 {
591 rgw_sts_parse_input();
592
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;
601 }
602 }
603
604 return nullptr;
605 }
606
607 int RGWHandler_REST_STS::init(rgw::sal::RGWRadosStore *store,
608 struct req_state *s,
609 rgw::io::BasicClient *cio)
610 {
611 s->dialect = "sts";
612
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;
615 return ret;
616 }
617
618 return RGWHandler_REST::init(store, s, cio);
619 }
620
621 int RGWHandler_REST_STS::authorize(const DoutPrefixProvider* dpp)
622 {
623 if (s->info.args.exists("Action") && s->info.args.get("Action") == "AssumeRoleWithWebIdentity") {
624 return RGW_Auth_STS::authorize(dpp, store, auth_registry, s);
625 }
626 return RGW_Auth_S3::authorize(dpp, store, auth_registry, s);
627 }
628
629 int RGWHandler_REST_STS::init_from_header(struct req_state* s,
630 int default_formatter,
631 bool configurable_format)
632 {
633 string req;
634 string first;
635
636 s->prot_flags = RGW_REST_STS;
637
638 const char *p, *req_name;
639 if (req_name = s->relative_uri.c_str(); *req_name == '?') {
640 p = req_name;
641 } else {
642 p = s->info.request_params.c_str();
643 }
644
645 s->info.args.set(p);
646 s->info.args.parse();
647
648 /* must be called after the args parsing */
649 if (int ret = allocate_formatter(s, default_formatter, configurable_format); ret < 0)
650 return ret;
651
652 if (*req_name != '/')
653 return 0;
654
655 req_name++;
656
657 if (!*req_name)
658 return 0;
659
660 req = req_name;
661 int pos = req.find('/');
662 if (pos >= 0) {
663 first = req.substr(0, pos);
664 } else {
665 first = req;
666 }
667
668 return 0;
669 }
670
671 RGWHandler_REST*
672 RGWRESTMgr_STS::get_handler(struct req_state* const s,
673 const rgw::auth::StrategyRegistry& auth_registry,
674 const std::string& frontend_prefix)
675 {
676 return new RGWHandler_REST_STS(auth_registry);
677 }