]>
Commit | Line | Data |
---|---|---|
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- | |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include <boost/algorithm/string/predicate.hpp> | |
5 | #include <boost/format.hpp> | |
6 | #include <boost/optional.hpp> | |
7 | #include <boost/utility/in_place_factory.hpp> | |
8 | #include <boost/tokenizer.hpp> | |
9 | ||
10 | #include "ceph_ver.h" | |
11 | ||
12 | #include "common/Formatter.h" | |
13 | #include "common/utf8.h" | |
14 | #include "common/ceph_json.h" | |
15 | ||
16 | #include "rgw_rest.h" | |
17 | #include "rgw_auth.h" | |
18 | #include "rgw_auth_registry.h" | |
19 | #include "rgw_rest_sts.h" | |
20 | #include "rgw_auth_s3.h" | |
21 | ||
22 | #include "rgw_formats.h" | |
23 | #include "rgw_client_io.h" | |
24 | ||
25 | #include "rgw_request.h" | |
26 | #include "rgw_process.h" | |
27 | #include "rgw_iam_policy.h" | |
28 | #include "rgw_iam_policy_keywords.h" | |
29 | ||
30 | #include "rgw_sts.h" | |
31 | ||
32 | #include <array> | |
33 | #include <sstream> | |
34 | #include <memory> | |
35 | ||
36 | #include <boost/utility/string_ref.hpp> | |
37 | ||
38 | #define dout_context g_ceph_context | |
39 | #define dout_subsys ceph_subsys_rgw | |
40 | ||
41 | namespace rgw { | |
42 | namespace auth { | |
43 | namespace sts { | |
44 | ||
45 | bool | |
46 | WebTokenEngine::is_applicable(const std::string& token) const noexcept | |
47 | { | |
48 | return ! token.empty(); | |
49 | } | |
50 | ||
51 | boost::optional<WebTokenEngine::token_t> | |
52 | WebTokenEngine::get_from_idp(const DoutPrefixProvider* dpp, const std::string& token) const | |
53 | { | |
54 | //Access token conforming to OAuth2.0 | |
55 | if (! cct->_conf->rgw_sts_token_introspection_url.empty()) { | |
56 | bufferlist introspect_resp; | |
57 | RGWHTTPTransceiver introspect_req(cct, "POST", cct->_conf->rgw_sts_token_introspection_url, &introspect_resp); | |
58 | //Headers | |
59 | introspect_req.append_header("Content-Type", "application/x-www-form-urlencoded"); | |
60 | string base64_creds = "Basic " + rgw::to_base64(cct->_conf->rgw_sts_client_id + ":" + cct->_conf->rgw_sts_client_secret); | |
61 | introspect_req.append_header("Authorization", base64_creds); | |
62 | // POST data | |
63 | string post_data = "token=" + token; | |
64 | introspect_req.set_post_data(post_data); | |
65 | introspect_req.set_send_length(post_data.length()); | |
66 | ||
67 | int res = introspect_req.process(); | |
68 | if (res < 0) { | |
69 | ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl; | |
70 | throw -EINVAL; | |
71 | } | |
72 | //Debug only | |
73 | ldpp_dout(dpp, 20) << "HTTP status: " << introspect_req.get_http_status() << dendl; | |
74 | ldpp_dout(dpp, 20) << "JSON Response is: " << introspect_resp.c_str() << dendl; | |
75 | ||
76 | JSONParser parser; | |
77 | WebTokenEngine::token_t token; | |
78 | if (!parser.parse(introspect_resp.c_str(), introspect_resp.length())) { | |
79 | ldpp_dout(dpp, 2) << "Malformed json" << dendl; | |
80 | throw -EINVAL; | |
81 | } else { | |
82 | bool is_active; | |
83 | JSONDecoder::decode_json("active", is_active, &parser); | |
84 | if (! is_active) { | |
85 | ldpp_dout(dpp, 0) << "Active state is false" << dendl; | |
86 | throw -ERR_INVALID_IDENTITY_TOKEN; | |
87 | } | |
88 | JSONDecoder::decode_json("iss", token.iss, &parser); | |
89 | JSONDecoder::decode_json("aud", token.aud, &parser); | |
90 | JSONDecoder::decode_json("sub", token.sub, &parser); | |
91 | JSONDecoder::decode_json("user_name", token.user_name, &parser); | |
92 | } | |
93 | return token; | |
94 | } | |
95 | return boost::none; | |
96 | } | |
97 | ||
98 | WebTokenEngine::result_t | |
99 | WebTokenEngine::authenticate( const DoutPrefixProvider* dpp, | |
100 | const std::string& token, | |
101 | const req_state* const s) const | |
102 | { | |
103 | boost::optional<WebTokenEngine::token_t> t; | |
104 | ||
105 | if (! is_applicable(token)) { | |
106 | return result_t::deny(); | |
107 | } | |
108 | ||
109 | try { | |
110 | t = get_from_idp(dpp, token); | |
111 | } catch(...) { | |
112 | return result_t::deny(-EACCES); | |
113 | } | |
114 | ||
115 | if (t) { | |
116 | auto apl = apl_factory->create_apl_web_identity(cct, s, *t); | |
117 | return result_t::grant(std::move(apl)); | |
118 | } | |
119 | return result_t::deny(-EACCES); | |
120 | } | |
121 | ||
122 | }; /* namespace sts */ | |
123 | }; /* namespace auth */ | |
124 | }; /* namespace rgw */ | |
125 | ||
126 | int RGWREST_STS::verify_permission() | |
127 | { | |
128 | STS::STSService _sts(s->cct, store, s->user->user_id, s->auth.identity.get()); | |
129 | sts = std::move(_sts); | |
130 | ||
131 | string rArn = s->info.args.get("RoleArn"); | |
132 | const auto& [ret, role] = sts.getRoleInfo(rArn); | |
133 | if (ret < 0) { | |
134 | return ret; | |
135 | } | |
136 | string policy = role.get_assume_role_policy(); | |
137 | buffer::list bl = buffer::list::static_from_string(policy); | |
138 | ||
139 | //Parse the policy | |
140 | //TODO - This step should be part of Role Creation | |
141 | try { | |
142 | const rgw::IAM::Policy p(s->cct, s->user->user_id.tenant, bl); | |
143 | //Check if the input role arn is there as one of the Principals in the policy, | |
144 | // If yes, then return 0, else -EPERM | |
145 | auto p_res = p.eval_principal(s->env, *s->auth.identity); | |
146 | if (p_res == rgw::IAM::Effect::Deny) { | |
147 | return -EPERM; | |
148 | } | |
149 | auto c_res = p.eval_conditions(s->env); | |
150 | if (c_res == rgw::IAM::Effect::Deny) { | |
151 | return -EPERM; | |
152 | } | |
153 | } catch (rgw::IAM::PolicyParseException& e) { | |
154 | ldout(s->cct, 20) << "failed to parse policy: " << e.what() << dendl; | |
155 | return -EPERM; | |
156 | } | |
157 | ||
158 | return 0; | |
159 | } | |
160 | ||
161 | void RGWREST_STS::send_response() | |
162 | { | |
163 | if (op_ret) { | |
164 | set_req_state_err(s, op_ret); | |
165 | } | |
166 | dump_errno(s); | |
167 | end_header(s); | |
168 | } | |
169 | ||
170 | int RGWSTSGetSessionToken::verify_permission() | |
171 | { | |
172 | rgw::IAM::Partition partition = rgw::IAM::Partition::aws; | |
173 | rgw::IAM::Service service = rgw::IAM::Service::s3; | |
174 | if (!verify_user_permission(this, | |
175 | s, | |
176 | rgw::IAM::ARN(partition, service, "", s->user->user_id.tenant, ""), | |
177 | rgw::IAM::stsGetSessionToken)) { | |
178 | return -EACCES; | |
179 | } | |
180 | ||
181 | return 0; | |
182 | } | |
183 | ||
184 | int RGWSTSGetSessionToken::get_params() | |
185 | { | |
186 | duration = s->info.args.get("DurationSeconds"); | |
187 | serialNumber = s->info.args.get("SerialNumber"); | |
188 | tokenCode = s->info.args.get("TokenCode"); | |
189 | ||
190 | if (! duration.empty()) { | |
191 | uint64_t duration_in_secs = stoull(duration); | |
192 | if (duration_in_secs < STS::GetSessionTokenRequest::getMinDuration() || | |
193 | duration_in_secs > s->cct->_conf->rgw_sts_max_session_duration) | |
194 | return -EINVAL; | |
195 | } | |
196 | ||
197 | return 0; | |
198 | } | |
199 | ||
200 | void RGWSTSGetSessionToken::execute() | |
201 | { | |
202 | if (op_ret = get_params(); op_ret < 0) { | |
203 | return; | |
204 | } | |
205 | ||
206 | STS::STSService sts(s->cct, store, s->user->user_id, s->auth.identity.get()); | |
207 | ||
208 | STS::GetSessionTokenRequest req(duration, serialNumber, tokenCode); | |
209 | const auto& [ret, creds] = sts.getSessionToken(req); | |
210 | op_ret = std::move(ret); | |
211 | //Dump the output | |
212 | if (op_ret == 0) { | |
213 | s->formatter->open_object_section("GetSessionTokenResponse"); | |
214 | s->formatter->open_object_section("GetSessionTokenResult"); | |
215 | s->formatter->open_object_section("Credentials"); | |
216 | creds.dump(s->formatter); | |
217 | s->formatter->close_section(); | |
218 | s->formatter->close_section(); | |
219 | s->formatter->close_section(); | |
220 | } | |
221 | } | |
222 | ||
223 | int RGWSTSAssumeRoleWithWebIdentity::get_params() | |
224 | { | |
225 | duration = s->info.args.get("DurationSeconds"); | |
226 | providerId = s->info.args.get("ProviderId"); | |
227 | policy = s->info.args.get("Policy"); | |
228 | roleArn = s->info.args.get("RoleArn"); | |
229 | roleSessionName = s->info.args.get("RoleSessionName"); | |
230 | iss = s->info.args.get("provider_id"); | |
231 | sub = s->info.args.get("sub"); | |
232 | aud = s->info.args.get("aud"); | |
233 | ||
234 | if (roleArn.empty() || roleSessionName.empty() || sub.empty() || aud.empty()) { | |
235 | ldout(s->cct, 20) << "ERROR: one of role arn or role session name or token is empty" << dendl; | |
236 | return -EINVAL; | |
237 | } | |
238 | ||
239 | if (! policy.empty()) { | |
240 | bufferlist bl = bufferlist::static_from_string(policy); | |
241 | try { | |
242 | const rgw::IAM::Policy p(s->cct, s->user->user_id.tenant, bl); | |
243 | } | |
244 | catch (rgw::IAM::PolicyParseException& e) { | |
245 | ldout(s->cct, 20) << "failed to parse policy: " << e.what() << "policy" << policy << dendl; | |
246 | return -ERR_MALFORMED_DOC; | |
247 | } | |
248 | } | |
249 | ||
250 | return 0; | |
251 | } | |
252 | ||
253 | void RGWSTSAssumeRoleWithWebIdentity::execute() | |
254 | { | |
255 | if (op_ret = get_params(); op_ret < 0) { | |
256 | return; | |
257 | } | |
258 | ||
259 | STS::AssumeRoleWithWebIdentityRequest req(duration, providerId, policy, roleArn, | |
260 | roleSessionName, iss, sub, aud); | |
261 | STS::AssumeRoleWithWebIdentityResponse response = sts.assumeRoleWithWebIdentity(req); | |
262 | op_ret = std::move(response.assumeRoleResp.retCode); | |
263 | ||
264 | //Dump the output | |
265 | if (op_ret == 0) { | |
266 | s->formatter->open_object_section("AssumeRoleWithWebIdentityResponse"); | |
267 | s->formatter->open_object_section("AssumeRoleWithWebIdentityResult"); | |
268 | encode_json("SubjectFromWebIdentityToken", response.sub , s->formatter); | |
269 | encode_json("Audience", response.aud , s->formatter); | |
270 | s->formatter->open_object_section("AssumedRoleUser"); | |
271 | response.assumeRoleResp.user.dump(s->formatter); | |
272 | s->formatter->close_section(); | |
273 | s->formatter->open_object_section("Credentials"); | |
274 | response.assumeRoleResp.creds.dump(s->formatter); | |
275 | s->formatter->close_section(); | |
276 | encode_json("Provider", response.providerId , s->formatter); | |
277 | encode_json("PackedPolicySize", response.assumeRoleResp.packedPolicySize , s->formatter); | |
278 | s->formatter->close_section(); | |
279 | s->formatter->close_section(); | |
280 | } | |
281 | } | |
282 | ||
283 | int RGWSTSAssumeRole::get_params() | |
284 | { | |
285 | duration = s->info.args.get("DurationSeconds"); | |
286 | externalId = s->info.args.get("ExternalId"); | |
287 | policy = s->info.args.get("Policy"); | |
288 | roleArn = s->info.args.get("RoleArn"); | |
289 | roleSessionName = s->info.args.get("RoleSessionName"); | |
290 | serialNumber = s->info.args.get("SerialNumber"); | |
291 | tokenCode = s->info.args.get("TokenCode"); | |
292 | ||
293 | if (roleArn.empty() || roleSessionName.empty()) { | |
294 | ldout(s->cct, 20) << "ERROR: one of role arn or role session name is empty" << dendl; | |
295 | return -EINVAL; | |
296 | } | |
297 | ||
298 | if (! policy.empty()) { | |
299 | bufferlist bl = bufferlist::static_from_string(policy); | |
300 | try { | |
301 | const rgw::IAM::Policy p(s->cct, s->user->user_id.tenant, bl); | |
302 | } | |
303 | catch (rgw::IAM::PolicyParseException& e) { | |
304 | ldout(s->cct, 20) << "failed to parse policy: " << e.what() << "policy" << policy << dendl; | |
305 | return -ERR_MALFORMED_DOC; | |
306 | } | |
307 | } | |
308 | ||
309 | return 0; | |
310 | } | |
311 | ||
312 | void RGWSTSAssumeRole::execute() | |
313 | { | |
314 | if (op_ret = get_params(); op_ret < 0) { | |
315 | return; | |
316 | } | |
317 | ||
318 | STS::AssumeRoleRequest req(duration, externalId, policy, roleArn, | |
319 | roleSessionName, serialNumber, tokenCode); | |
320 | STS::AssumeRoleResponse response = sts.assumeRole(req); | |
321 | op_ret = std::move(response.retCode); | |
322 | //Dump the output | |
323 | if (op_ret == 0) { | |
324 | s->formatter->open_object_section("AssumeRoleResponse"); | |
325 | s->formatter->open_object_section("AssumeRoleResult"); | |
326 | s->formatter->open_object_section("Credentials"); | |
327 | response.creds.dump(s->formatter); | |
328 | s->formatter->close_section(); | |
329 | s->formatter->open_object_section("AssumedRoleUser"); | |
330 | response.user.dump(s->formatter); | |
331 | s->formatter->close_section(); | |
332 | encode_json("PackedPolicySize", response.packedPolicySize , s->formatter); | |
333 | s->formatter->close_section(); | |
334 | s->formatter->close_section(); | |
335 | } | |
336 | } | |
337 | ||
338 | int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp, | |
339 | RGWRados *store, | |
340 | const rgw::auth::StrategyRegistry& auth_registry, | |
341 | struct req_state *s) | |
342 | { | |
343 | return rgw::auth::Strategy::apply(dpp, auth_registry.get_sts(), s); | |
344 | } | |
345 | ||
346 | void RGWHandler_REST_STS::rgw_sts_parse_input() | |
347 | { | |
348 | const auto max_size = s->cct->_conf->rgw_max_put_param_size; | |
349 | ||
350 | int ret = 0; | |
351 | bufferlist data; | |
352 | std::tie(ret, data) = rgw_rest_read_all_input(s, max_size, false); | |
353 | string post_body = data.to_str(); | |
354 | if (data.length() > 0) { | |
355 | ldout(s->cct, 10) << "Content of POST: " << post_body << dendl; | |
356 | ||
357 | if (post_body.find("Action") != string::npos) { | |
358 | boost::char_separator<char> sep("&"); | |
359 | boost::tokenizer<boost::char_separator<char>> tokens(post_body, sep); | |
360 | for (const auto& t : tokens) { | |
361 | auto pos = t.find("="); | |
362 | if (pos != string::npos) { | |
363 | std::string key = t.substr(0, pos); | |
364 | std::string value = t.substr(pos + 1, t.size() - 1); | |
365 | if (key == "RoleArn" || key == "Policy") { | |
366 | value = url_decode(value); | |
367 | } | |
368 | ldout(s->cct, 10) << "Key: " << key << "Value: " << value << dendl; | |
369 | s->info.args.append(key, value); | |
370 | } | |
371 | } | |
372 | } | |
373 | } | |
374 | auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); | |
375 | s->info.args.append("PayloadHash", payload_hash); | |
376 | } | |
377 | ||
378 | RGWOp *RGWHandler_REST_STS::op_post() | |
379 | { | |
380 | rgw_sts_parse_input(); | |
381 | ||
382 | if (s->info.args.exists("Action")) { | |
383 | string action = s->info.args.get("Action"); | |
384 | if (action == "AssumeRole") { | |
385 | return new RGWSTSAssumeRole; | |
386 | } else if (action == "GetSessionToken") { | |
387 | return new RGWSTSGetSessionToken; | |
388 | } else if (action == "AssumeRoleWithWebIdentity") { | |
389 | return new RGWSTSAssumeRoleWithWebIdentity; | |
390 | } | |
391 | } | |
392 | ||
393 | return nullptr; | |
394 | } | |
395 | ||
396 | int RGWHandler_REST_STS::init(RGWRados *store, | |
397 | struct req_state *s, | |
398 | rgw::io::BasicClient *cio) | |
399 | { | |
400 | s->dialect = "sts"; | |
401 | ||
402 | if (int ret = RGWHandler_REST_STS::init_from_header(s, RGW_FORMAT_XML, true); ret < 0) { | |
403 | ldout(s->cct, 10) << "init_from_header returned err=" << ret << dendl; | |
404 | return ret; | |
405 | } | |
406 | ||
407 | return RGWHandler_REST::init(store, s, cio); | |
408 | } | |
409 | ||
410 | int RGWHandler_REST_STS::authorize(const DoutPrefixProvider* dpp) | |
411 | { | |
412 | if (s->info.args.exists("Action") && s->info.args.get("Action") == "AssumeRoleWithWebIdentity") { | |
413 | return RGW_Auth_STS::authorize(dpp, store, auth_registry, s); | |
414 | } | |
415 | return RGW_Auth_S3::authorize(dpp, store, auth_registry, s); | |
416 | } | |
417 | ||
418 | int RGWHandler_REST_STS::init_from_header(struct req_state* s, | |
419 | int default_formatter, | |
420 | bool configurable_format) | |
421 | { | |
422 | string req; | |
423 | string first; | |
424 | ||
425 | s->prot_flags |= RGW_REST_STS; | |
426 | ||
427 | const char *p, *req_name; | |
428 | if (req_name = s->relative_uri.c_str(); *req_name == '?') { | |
429 | p = req_name; | |
430 | } else { | |
431 | p = s->info.request_params.c_str(); | |
432 | } | |
433 | ||
434 | s->info.args.set(p); | |
435 | s->info.args.parse(); | |
436 | ||
437 | /* must be called after the args parsing */ | |
438 | if (int ret = allocate_formatter(s, default_formatter, configurable_format); ret < 0) | |
439 | return ret; | |
440 | ||
441 | if (*req_name != '/') | |
442 | return 0; | |
443 | ||
444 | req_name++; | |
445 | ||
446 | if (!*req_name) | |
447 | return 0; | |
448 | ||
449 | req = req_name; | |
450 | int pos = req.find('/'); | |
451 | if (pos >= 0) { | |
452 | first = req.substr(0, pos); | |
453 | } else { | |
454 | first = req; | |
455 | } | |
456 | ||
457 | return 0; | |
458 | } | |
459 | ||
460 | RGWHandler_REST* | |
461 | RGWRESTMgr_STS::get_handler(struct req_state* const s, | |
462 | const rgw::auth::StrategyRegistry& auth_registry, | |
463 | const std::string& frontend_prefix) | |
464 | { | |
465 | return new RGWHandler_REST_STS(auth_registry); | |
466 | } |