]>
Commit | Line | Data |
---|---|---|
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 |
44 | using namespace std; |
45 | ||
9f95a23c | 46 | namespace rgw::auth::sts { |
11fdf7f2 TL |
47 | |
48 | bool | |
49 | WebTokenEngine::is_applicable(const std::string& token) const noexcept | |
50 | { | |
51 | return ! token.empty(); | |
52 | } | |
53 | ||
f67539c2 TL |
54 | std::string |
55 | WebTokenEngine::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 |
65 | std::string |
66 | WebTokenEngine::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 | ||
82 | std::unique_ptr<rgw::sal::RGWOIDCProvider> | |
b3b6e05e | 83 | WebTokenEngine::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 | ||
114 | bool | |
115 | WebTokenEngine::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 | ||
125 | bool | |
126 | WebTokenEngine::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 |
154 | template <typename T> |
155 | void | |
156 | WebTokenEngine::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 | |
198 | WebTokenEngine::token_t | |
199 | WebTokenEngine::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 | 214 | std::tuple<boost::optional<WebTokenEngine::token_t>, boost::optional<WebTokenEngine::principal_tags_t>> |
f67539c2 TL |
215 | WebTokenEngine::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 | ||
308 | std::string | |
309 | WebTokenEngine::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 | ||
348 | void | |
f67539c2 | 349 | 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 |
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 | ||
478 | WebTokenEngine::result_t | |
479 | WebTokenEngine::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 | 518 | int 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 | ||
563 | void 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 | 572 | int 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 | ||
587 | int 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 | 611 | void 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 | ||
634 | int 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 | 667 | void 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 | ||
697 | int 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 | 729 | void 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 | ||
755 | int 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 |
763 | using op_generator = RGWOp*(*)(); |
764 | static 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 | ||
770 | bool 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 | ||
779 | RGWOp *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 |
794 | int 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 | 804 | int 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 | ||
812 | RGWHandler_REST* | |
1e59de90 TL |
813 | RGWRESTMgr_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 | } |