]>
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(); | |
20effc67 TL |
104 | std::unique_ptr<rgw::sal::RGWOIDCProvider> provider = store->get_oidc_provider(); |
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; | |
312 | string openidc_wellknown_url = iss + "/.well-known/openid-configuration"; | |
313 | bufferlist openidc_resp; | |
314 | RGWHTTPTransceiver openidc_req(cct, "GET", openidc_wellknown_url, &openidc_resp); | |
315 | ||
316 | //Headers | |
317 | openidc_req.append_header("Content-Type", "application/x-www-form-urlencoded"); | |
318 | ||
319 | int res = openidc_req.process(y); | |
320 | if (res < 0) { | |
321 | ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl; | |
322 | throw -EINVAL; | |
323 | } | |
324 | ||
325 | //Debug only | |
326 | ldpp_dout(dpp, 20) << "HTTP status: " << openidc_req.get_http_status() << dendl; | |
327 | ldpp_dout(dpp, 20) << "JSON Response is: " << openidc_resp.c_str() << dendl; | |
328 | ||
329 | JSONParser parser; | |
330 | if (parser.parse(openidc_resp.c_str(), openidc_resp.length())) { | |
331 | JSONObj::data_val val; | |
332 | if (parser.get_data("jwks_uri", &val)) { | |
333 | cert_url = val.str.c_str(); | |
334 | ldpp_dout(dpp, 20) << "Cert URL is: " << cert_url.c_str() << dendl; | |
335 | } else { | |
336 | ldpp_dout(dpp, 0) << "Malformed json returned while fetching openidc url" << dendl; | |
337 | } | |
f91f0fd5 | 338 | } |
20effc67 | 339 | return cert_url; |
f91f0fd5 TL |
340 | } |
341 | ||
342 | void | |
f67539c2 | 343 | 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 |
344 | { |
345 | if (algorithm != "HS256" && algorithm != "HS384" && algorithm != "HS512") { | |
20effc67 TL |
346 | string cert_url = get_cert_url(iss, dpp, y); |
347 | if (cert_url.empty()) { | |
348 | throw -EINVAL; | |
349 | } | |
350 | ||
f91f0fd5 | 351 | // Get certificate |
f91f0fd5 TL |
352 | bufferlist cert_resp; |
353 | RGWHTTPTransceiver cert_req(cct, "GET", cert_url, &cert_resp); | |
11fdf7f2 | 354 | //Headers |
f91f0fd5 TL |
355 | cert_req.append_header("Content-Type", "application/x-www-form-urlencoded"); |
356 | ||
f67539c2 | 357 | int res = cert_req.process(y); |
11fdf7f2 TL |
358 | if (res < 0) { |
359 | ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl; | |
360 | throw -EINVAL; | |
361 | } | |
362 | //Debug only | |
f91f0fd5 TL |
363 | ldpp_dout(dpp, 20) << "HTTP status: " << cert_req.get_http_status() << dendl; |
364 | ldpp_dout(dpp, 20) << "JSON Response is: " << cert_resp.c_str() << dendl; | |
11fdf7f2 TL |
365 | |
366 | JSONParser parser; | |
f91f0fd5 TL |
367 | if (parser.parse(cert_resp.c_str(), cert_resp.length())) { |
368 | JSONObj::data_val val; | |
369 | if (parser.get_data("keys", &val)) { | |
370 | if (val.str[0] == '[') { | |
371 | val.str.erase(0, 1); | |
372 | } | |
373 | if (val.str[val.str.size() - 1] == ']') { | |
374 | val.str = val.str.erase(val.str.size() - 1, 1); | |
375 | } | |
376 | if (parser.parse(val.str.c_str(), val.str.size())) { | |
377 | vector<string> x5c; | |
378 | if (JSONDecoder::decode_json("x5c", x5c, &parser)) { | |
379 | string cert; | |
380 | bool found_valid_cert = false; | |
381 | for (auto& it : x5c) { | |
382 | cert = "-----BEGIN CERTIFICATE-----\n" + it + "\n-----END CERTIFICATE-----"; | |
383 | ldpp_dout(dpp, 20) << "Certificate is: " << cert.c_str() << dendl; | |
384 | if (is_cert_valid(thumbprints, cert)) { | |
385 | found_valid_cert = true; | |
386 | break; | |
387 | } | |
388 | found_valid_cert = true; | |
389 | } | |
390 | if (! found_valid_cert) { | |
f67539c2 | 391 | ldpp_dout(dpp, 0) << "Cert doesn't match that with the thumbprints registered with oidc provider: " << cert.c_str() << dendl; |
f91f0fd5 TL |
392 | throw -EINVAL; |
393 | } | |
394 | try { | |
395 | //verify method takes care of expired tokens also | |
396 | if (algorithm == "RS256") { | |
397 | auto verifier = jwt::verify() | |
398 | .allow_algorithm(jwt::algorithm::rs256{cert}); | |
399 | ||
400 | verifier.verify(decoded); | |
401 | } else if (algorithm == "RS384") { | |
402 | auto verifier = jwt::verify() | |
403 | .allow_algorithm(jwt::algorithm::rs384{cert}); | |
404 | ||
405 | verifier.verify(decoded); | |
406 | } else if (algorithm == "RS512") { | |
407 | auto verifier = jwt::verify() | |
408 | .allow_algorithm(jwt::algorithm::rs512{cert}); | |
409 | ||
410 | verifier.verify(decoded); | |
411 | } else if (algorithm == "ES256") { | |
412 | auto verifier = jwt::verify() | |
413 | .allow_algorithm(jwt::algorithm::es256{cert}); | |
414 | ||
415 | verifier.verify(decoded); | |
416 | } else if (algorithm == "ES384") { | |
417 | auto verifier = jwt::verify() | |
418 | .allow_algorithm(jwt::algorithm::es384{cert}); | |
419 | ||
420 | verifier.verify(decoded); | |
421 | } else if (algorithm == "ES512") { | |
422 | auto verifier = jwt::verify() | |
423 | .allow_algorithm(jwt::algorithm::es512{cert}); | |
424 | ||
425 | verifier.verify(decoded); | |
426 | } else if (algorithm == "PS256") { | |
427 | auto verifier = jwt::verify() | |
428 | .allow_algorithm(jwt::algorithm::ps256{cert}); | |
429 | ||
430 | verifier.verify(decoded); | |
431 | } else if (algorithm == "PS384") { | |
432 | auto verifier = jwt::verify() | |
433 | .allow_algorithm(jwt::algorithm::ps384{cert}); | |
434 | ||
435 | verifier.verify(decoded); | |
436 | } else if (algorithm == "PS512") { | |
437 | auto verifier = jwt::verify() | |
438 | .allow_algorithm(jwt::algorithm::ps512{cert}); | |
439 | ||
440 | verifier.verify(decoded); | |
441 | } | |
442 | } catch (std::runtime_error& e) { | |
443 | ldpp_dout(dpp, 0) << "Signature validation failed: " << e.what() << dendl; | |
444 | throw; | |
445 | } | |
446 | catch (...) { | |
447 | ldpp_dout(dpp, 0) << "Signature validation failed" << dendl; | |
448 | throw; | |
449 | } | |
450 | } else { | |
451 | ldpp_dout(dpp, 0) << "x5c not present" << dendl; | |
452 | throw -EINVAL; | |
453 | } | |
454 | } else { | |
455 | ldpp_dout(dpp, 0) << "Malformed JSON object for keys" << dendl; | |
456 | throw -EINVAL; | |
457 | } | |
458 | } else { | |
459 | ldpp_dout(dpp, 0) << "keys not present in JSON" << dendl; | |
460 | throw -EINVAL; | |
461 | } //if-else get-data | |
11fdf7f2 | 462 | } else { |
f91f0fd5 TL |
463 | ldpp_dout(dpp, 0) << "Malformed json returned while fetching cert" << dendl; |
464 | throw -EINVAL; | |
465 | } //if-else parser cert_resp | |
466 | } else { | |
467 | ldpp_dout(dpp, 0) << "JWT signed by HMAC algos are currently not supported" << dendl; | |
468 | throw -EINVAL; | |
11fdf7f2 | 469 | } |
11fdf7f2 TL |
470 | } |
471 | ||
472 | WebTokenEngine::result_t | |
473 | WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, | |
474 | const std::string& token, | |
f67539c2 TL |
475 | const req_state* const s, |
476 | optional_yield y) const | |
11fdf7f2 | 477 | { |
11fdf7f2 TL |
478 | if (! is_applicable(token)) { |
479 | return result_t::deny(); | |
480 | } | |
481 | ||
482 | try { | |
20effc67 TL |
483 | auto [t, princ_tags] = get_from_jwt(dpp, token, s, y); |
484 | if (t) { | |
485 | string role_session = s->info.args.get("RoleSessionName"); | |
486 | if (role_session.empty()) { | |
487 | ldout(s->cct, 0) << "Role Session Name is empty " << dendl; | |
488 | return result_t::deny(-EACCES); | |
489 | } | |
490 | string role_arn = s->info.args.get("RoleArn"); | |
491 | string role_tenant = get_role_tenant(role_arn); | |
492 | string role_name = get_role_name(role_arn); | |
493 | std::unique_ptr<rgw::sal::RGWRole> role = store->get_role(role_name, role_tenant); | |
494 | int ret = role->get(dpp, y); | |
495 | if (ret < 0) { | |
496 | ldpp_dout(dpp, 0) << "Role not found: name:" << role_name << " tenant: " << role_tenant << dendl; | |
497 | return result_t::deny(-EACCES); | |
498 | } | |
499 | boost::optional<multimap<string,string>> role_tags = role->get_tags(); | |
500 | auto apl = apl_factory->create_apl_web_identity(cct, s, role_session, role_tenant, *t, role_tags, princ_tags); | |
501 | return result_t::grant(std::move(apl)); | |
502 | } | |
503 | return result_t::deny(-EACCES); | |
f91f0fd5 TL |
504 | } |
505 | catch (...) { | |
11fdf7f2 TL |
506 | return result_t::deny(-EACCES); |
507 | } | |
11fdf7f2 TL |
508 | } |
509 | ||
f91f0fd5 | 510 | } // namespace rgw::auth::sts |
11fdf7f2 | 511 | |
f67539c2 | 512 | int RGWREST_STS::verify_permission(optional_yield y) |
11fdf7f2 | 513 | { |
9f95a23c | 514 | STS::STSService _sts(s->cct, store, s->user->get_id(), s->auth.identity.get()); |
11fdf7f2 TL |
515 | sts = std::move(_sts); |
516 | ||
517 | string rArn = s->info.args.get("RoleArn"); | |
b3b6e05e | 518 | const auto& [ret, role] = sts.getRoleInfo(s, rArn, y); |
11fdf7f2 | 519 | if (ret < 0) { |
b3b6e05e | 520 | ldpp_dout(this, 0) << "failed to get role info using role arn: " << rArn << dendl; |
11fdf7f2 TL |
521 | return ret; |
522 | } | |
20effc67 | 523 | string policy = role->get_assume_role_policy(); |
11fdf7f2 TL |
524 | buffer::list bl = buffer::list::static_from_string(policy); |
525 | ||
526 | //Parse the policy | |
527 | //TODO - This step should be part of Role Creation | |
528 | try { | |
9f95a23c | 529 | const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl); |
20effc67 TL |
530 | if (!s->principal_tags.empty()) { |
531 | auto res = p.eval(s->env, *s->auth.identity, rgw::IAM::stsTagSession, boost::none); | |
532 | if (res != rgw::IAM::Effect::Allow) { | |
533 | ldout(s->cct, 0) << "evaluating policy for stsTagSession returned deny/pass" << dendl; | |
534 | return -EPERM; | |
535 | } | |
536 | } | |
537 | uint64_t op; | |
538 | if (get_type() == RGW_STS_ASSUME_ROLE_WEB_IDENTITY) { | |
539 | op = rgw::IAM::stsAssumeRoleWithWebIdentity; | |
540 | } else { | |
541 | op = rgw::IAM::stsAssumeRole; | |
11fdf7f2 | 542 | } |
20effc67 TL |
543 | |
544 | auto res = p.eval(s->env, *s->auth.identity, op, boost::none); | |
545 | if (res != rgw::IAM::Effect::Allow) { | |
546 | ldout(s->cct, 0) << "evaluating policy for op: " << op << " returned deny/pass" << dendl; | |
11fdf7f2 TL |
547 | return -EPERM; |
548 | } | |
549 | } catch (rgw::IAM::PolicyParseException& e) { | |
b3b6e05e | 550 | ldpp_dout(this, 0) << "failed to parse policy: " << e.what() << dendl; |
11fdf7f2 TL |
551 | return -EPERM; |
552 | } | |
553 | ||
554 | return 0; | |
555 | } | |
556 | ||
557 | void RGWREST_STS::send_response() | |
558 | { | |
559 | if (op_ret) { | |
560 | set_req_state_err(s, op_ret); | |
561 | } | |
562 | dump_errno(s); | |
563 | end_header(s); | |
564 | } | |
565 | ||
f67539c2 | 566 | int RGWSTSGetSessionToken::verify_permission(optional_yield y) |
11fdf7f2 | 567 | { |
eafe8130 TL |
568 | rgw::Partition partition = rgw::Partition::aws; |
569 | rgw::Service service = rgw::Service::s3; | |
11fdf7f2 TL |
570 | if (!verify_user_permission(this, |
571 | s, | |
9f95a23c | 572 | rgw::ARN(partition, service, "", s->user->get_tenant(), ""), |
11fdf7f2 | 573 | rgw::IAM::stsGetSessionToken)) { |
b3b6e05e | 574 | ldpp_dout(this, 0) << "User does not have permssion to perform GetSessionToken" << dendl; |
11fdf7f2 TL |
575 | return -EACCES; |
576 | } | |
577 | ||
578 | return 0; | |
579 | } | |
580 | ||
581 | int RGWSTSGetSessionToken::get_params() | |
582 | { | |
583 | duration = s->info.args.get("DurationSeconds"); | |
584 | serialNumber = s->info.args.get("SerialNumber"); | |
585 | tokenCode = s->info.args.get("TokenCode"); | |
586 | ||
587 | if (! duration.empty()) { | |
9f95a23c TL |
588 | string err; |
589 | uint64_t duration_in_secs = strict_strtoll(duration.c_str(), 10, &err); | |
590 | if (!err.empty()) { | |
b3b6e05e | 591 | ldpp_dout(this, 0) << "Invalid value of input duration: " << duration << dendl; |
9f95a23c TL |
592 | return -EINVAL; |
593 | } | |
594 | ||
11fdf7f2 | 595 | if (duration_in_secs < STS::GetSessionTokenRequest::getMinDuration() || |
f67539c2 | 596 | duration_in_secs > s->cct->_conf->rgw_sts_max_session_duration) { |
b3b6e05e | 597 | ldpp_dout(this, 0) << "Invalid duration in secs: " << duration_in_secs << dendl; |
11fdf7f2 | 598 | return -EINVAL; |
f67539c2 | 599 | } |
11fdf7f2 TL |
600 | } |
601 | ||
602 | return 0; | |
603 | } | |
604 | ||
f67539c2 | 605 | void RGWSTSGetSessionToken::execute(optional_yield y) |
11fdf7f2 TL |
606 | { |
607 | if (op_ret = get_params(); op_ret < 0) { | |
608 | return; | |
609 | } | |
610 | ||
9f95a23c | 611 | STS::STSService sts(s->cct, store, s->user->get_id(), s->auth.identity.get()); |
11fdf7f2 TL |
612 | |
613 | STS::GetSessionTokenRequest req(duration, serialNumber, tokenCode); | |
20effc67 | 614 | const auto& [ret, creds] = sts.getSessionToken(this, req); |
11fdf7f2 TL |
615 | op_ret = std::move(ret); |
616 | //Dump the output | |
617 | if (op_ret == 0) { | |
618 | s->formatter->open_object_section("GetSessionTokenResponse"); | |
619 | s->formatter->open_object_section("GetSessionTokenResult"); | |
620 | s->formatter->open_object_section("Credentials"); | |
621 | creds.dump(s->formatter); | |
622 | s->formatter->close_section(); | |
623 | s->formatter->close_section(); | |
624 | s->formatter->close_section(); | |
625 | } | |
626 | } | |
627 | ||
628 | int RGWSTSAssumeRoleWithWebIdentity::get_params() | |
629 | { | |
630 | duration = s->info.args.get("DurationSeconds"); | |
631 | providerId = s->info.args.get("ProviderId"); | |
632 | policy = s->info.args.get("Policy"); | |
633 | roleArn = s->info.args.get("RoleArn"); | |
634 | roleSessionName = s->info.args.get("RoleSessionName"); | |
635 | iss = s->info.args.get("provider_id"); | |
636 | sub = s->info.args.get("sub"); | |
637 | aud = s->info.args.get("aud"); | |
638 | ||
639 | if (roleArn.empty() || roleSessionName.empty() || sub.empty() || aud.empty()) { | |
b3b6e05e | 640 | ldpp_dout(this, 0) << "ERROR: one of role arn or role session name or token is empty" << dendl; |
11fdf7f2 TL |
641 | return -EINVAL; |
642 | } | |
643 | ||
644 | if (! policy.empty()) { | |
645 | bufferlist bl = bufferlist::static_from_string(policy); | |
646 | try { | |
9f95a23c | 647 | const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl); |
11fdf7f2 TL |
648 | } |
649 | catch (rgw::IAM::PolicyParseException& e) { | |
b3b6e05e | 650 | ldpp_dout(this, 20) << "failed to parse policy: " << e.what() << "policy" << policy << dendl; |
11fdf7f2 TL |
651 | return -ERR_MALFORMED_DOC; |
652 | } | |
653 | } | |
654 | ||
655 | return 0; | |
656 | } | |
657 | ||
f67539c2 | 658 | void RGWSTSAssumeRoleWithWebIdentity::execute(optional_yield y) |
11fdf7f2 TL |
659 | { |
660 | if (op_ret = get_params(); op_ret < 0) { | |
661 | return; | |
662 | } | |
663 | ||
f67539c2 | 664 | STS::AssumeRoleWithWebIdentityRequest req(s->cct, duration, providerId, policy, roleArn, |
20effc67 TL |
665 | roleSessionName, iss, sub, aud, s->principal_tags); |
666 | STS::AssumeRoleWithWebIdentityResponse response = sts.assumeRoleWithWebIdentity(this, req); | |
11fdf7f2 TL |
667 | op_ret = std::move(response.assumeRoleResp.retCode); |
668 | ||
669 | //Dump the output | |
670 | if (op_ret == 0) { | |
671 | s->formatter->open_object_section("AssumeRoleWithWebIdentityResponse"); | |
672 | s->formatter->open_object_section("AssumeRoleWithWebIdentityResult"); | |
673 | encode_json("SubjectFromWebIdentityToken", response.sub , s->formatter); | |
674 | encode_json("Audience", response.aud , s->formatter); | |
675 | s->formatter->open_object_section("AssumedRoleUser"); | |
676 | response.assumeRoleResp.user.dump(s->formatter); | |
677 | s->formatter->close_section(); | |
678 | s->formatter->open_object_section("Credentials"); | |
679 | response.assumeRoleResp.creds.dump(s->formatter); | |
680 | s->formatter->close_section(); | |
681 | encode_json("Provider", response.providerId , s->formatter); | |
682 | encode_json("PackedPolicySize", response.assumeRoleResp.packedPolicySize , s->formatter); | |
683 | s->formatter->close_section(); | |
684 | s->formatter->close_section(); | |
685 | } | |
686 | } | |
687 | ||
688 | int RGWSTSAssumeRole::get_params() | |
689 | { | |
690 | duration = s->info.args.get("DurationSeconds"); | |
691 | externalId = s->info.args.get("ExternalId"); | |
692 | policy = s->info.args.get("Policy"); | |
693 | roleArn = s->info.args.get("RoleArn"); | |
694 | roleSessionName = s->info.args.get("RoleSessionName"); | |
695 | serialNumber = s->info.args.get("SerialNumber"); | |
696 | tokenCode = s->info.args.get("TokenCode"); | |
697 | ||
698 | if (roleArn.empty() || roleSessionName.empty()) { | |
b3b6e05e | 699 | ldpp_dout(this, 0) << "ERROR: one of role arn or role session name is empty" << dendl; |
11fdf7f2 TL |
700 | return -EINVAL; |
701 | } | |
702 | ||
703 | if (! policy.empty()) { | |
704 | bufferlist bl = bufferlist::static_from_string(policy); | |
705 | try { | |
9f95a23c | 706 | const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl); |
11fdf7f2 TL |
707 | } |
708 | catch (rgw::IAM::PolicyParseException& e) { | |
b3b6e05e | 709 | ldpp_dout(this, 0) << "failed to parse policy: " << e.what() << "policy" << policy << dendl; |
11fdf7f2 TL |
710 | return -ERR_MALFORMED_DOC; |
711 | } | |
712 | } | |
713 | ||
714 | return 0; | |
715 | } | |
716 | ||
f67539c2 | 717 | void RGWSTSAssumeRole::execute(optional_yield y) |
11fdf7f2 TL |
718 | { |
719 | if (op_ret = get_params(); op_ret < 0) { | |
720 | return; | |
721 | } | |
722 | ||
f67539c2 | 723 | STS::AssumeRoleRequest req(s->cct, duration, externalId, policy, roleArn, |
11fdf7f2 | 724 | roleSessionName, serialNumber, tokenCode); |
b3b6e05e | 725 | STS::AssumeRoleResponse response = sts.assumeRole(s, req, y); |
11fdf7f2 TL |
726 | op_ret = std::move(response.retCode); |
727 | //Dump the output | |
728 | if (op_ret == 0) { | |
729 | s->formatter->open_object_section("AssumeRoleResponse"); | |
730 | s->formatter->open_object_section("AssumeRoleResult"); | |
731 | s->formatter->open_object_section("Credentials"); | |
732 | response.creds.dump(s->formatter); | |
733 | s->formatter->close_section(); | |
734 | s->formatter->open_object_section("AssumedRoleUser"); | |
735 | response.user.dump(s->formatter); | |
736 | s->formatter->close_section(); | |
737 | encode_json("PackedPolicySize", response.packedPolicySize , s->formatter); | |
738 | s->formatter->close_section(); | |
739 | s->formatter->close_section(); | |
740 | } | |
741 | } | |
742 | ||
743 | int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp, | |
20effc67 | 744 | rgw::sal::Store* store, |
11fdf7f2 | 745 | const rgw::auth::StrategyRegistry& auth_registry, |
f67539c2 | 746 | struct req_state *s, optional_yield y) |
11fdf7f2 | 747 | { |
f67539c2 | 748 | return rgw::auth::Strategy::apply(dpp, auth_registry.get_sts(), s, y); |
11fdf7f2 TL |
749 | } |
750 | ||
751 | void RGWHandler_REST_STS::rgw_sts_parse_input() | |
752 | { | |
eafe8130 | 753 | if (post_body.size() > 0) { |
b3b6e05e | 754 | ldpp_dout(s, 10) << "Content of POST: " << post_body << dendl; |
11fdf7f2 TL |
755 | |
756 | if (post_body.find("Action") != string::npos) { | |
757 | boost::char_separator<char> sep("&"); | |
758 | boost::tokenizer<boost::char_separator<char>> tokens(post_body, sep); | |
759 | for (const auto& t : tokens) { | |
760 | auto pos = t.find("="); | |
761 | if (pos != string::npos) { | |
9f95a23c TL |
762 | s->info.args.append(t.substr(0,pos), |
763 | url_decode(t.substr(pos+1, t.size() -1))); | |
764 | } | |
765 | } | |
f67539c2 | 766 | } |
11fdf7f2 TL |
767 | } |
768 | auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); | |
769 | s->info.args.append("PayloadHash", payload_hash); | |
770 | } | |
771 | ||
772 | RGWOp *RGWHandler_REST_STS::op_post() | |
773 | { | |
774 | rgw_sts_parse_input(); | |
775 | ||
776 | if (s->info.args.exists("Action")) { | |
777 | string action = s->info.args.get("Action"); | |
778 | if (action == "AssumeRole") { | |
779 | return new RGWSTSAssumeRole; | |
780 | } else if (action == "GetSessionToken") { | |
781 | return new RGWSTSGetSessionToken; | |
782 | } else if (action == "AssumeRoleWithWebIdentity") { | |
783 | return new RGWSTSAssumeRoleWithWebIdentity; | |
784 | } | |
785 | } | |
786 | ||
787 | return nullptr; | |
788 | } | |
789 | ||
20effc67 | 790 | int RGWHandler_REST_STS::init(rgw::sal::Store* store, |
11fdf7f2 TL |
791 | struct req_state *s, |
792 | rgw::io::BasicClient *cio) | |
793 | { | |
794 | s->dialect = "sts"; | |
795 | ||
796 | if (int ret = RGWHandler_REST_STS::init_from_header(s, RGW_FORMAT_XML, true); ret < 0) { | |
b3b6e05e | 797 | ldpp_dout(s, 10) << "init_from_header returned err=" << ret << dendl; |
11fdf7f2 TL |
798 | return ret; |
799 | } | |
800 | ||
801 | return RGWHandler_REST::init(store, s, cio); | |
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") { | |
f67539c2 | 807 | return RGW_Auth_STS::authorize(dpp, store, auth_registry, s, y); |
11fdf7f2 | 808 | } |
f67539c2 | 809 | return RGW_Auth_S3::authorize(dpp, store, auth_registry, s, y); |
11fdf7f2 TL |
810 | } |
811 | ||
812 | int RGWHandler_REST_STS::init_from_header(struct req_state* s, | |
813 | int default_formatter, | |
814 | bool configurable_format) | |
815 | { | |
816 | string req; | |
817 | string first; | |
818 | ||
92f5a8d4 | 819 | s->prot_flags = RGW_REST_STS; |
11fdf7f2 TL |
820 | |
821 | const char *p, *req_name; | |
822 | if (req_name = s->relative_uri.c_str(); *req_name == '?') { | |
823 | p = req_name; | |
824 | } else { | |
825 | p = s->info.request_params.c_str(); | |
826 | } | |
827 | ||
828 | s->info.args.set(p); | |
b3b6e05e | 829 | s->info.args.parse(s); |
11fdf7f2 TL |
830 | |
831 | /* must be called after the args parsing */ | |
832 | if (int ret = allocate_formatter(s, default_formatter, configurable_format); ret < 0) | |
833 | return ret; | |
834 | ||
835 | if (*req_name != '/') | |
836 | return 0; | |
837 | ||
838 | req_name++; | |
839 | ||
840 | if (!*req_name) | |
841 | return 0; | |
842 | ||
843 | req = req_name; | |
844 | int pos = req.find('/'); | |
845 | if (pos >= 0) { | |
846 | first = req.substr(0, pos); | |
847 | } else { | |
848 | first = req; | |
849 | } | |
850 | ||
851 | return 0; | |
852 | } | |
853 | ||
854 | RGWHandler_REST* | |
20effc67 | 855 | RGWRESTMgr_STS::get_handler(rgw::sal::Store* store, |
f67539c2 TL |
856 | struct req_state* const s, |
857 | const rgw::auth::StrategyRegistry& auth_registry, | |
858 | const std::string& frontend_prefix) | |
11fdf7f2 TL |
859 | { |
860 | return new RGWHandler_REST_STS(auth_registry); | |
861 | } |