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