]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_rest_sts.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / rgw / rgw_rest_sts.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
3
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
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::auth::sts {
41
42 bool
43 WebTokenEngine::is_applicable(const std::string& token) const noexcept
44 {
45 return ! token.empty();
46 }
47
48 boost::optional<WebTokenEngine::token_t>
49 WebTokenEngine::get_from_idp(const DoutPrefixProvider* dpp, const std::string& token) const
50 {
51 //Access token conforming to OAuth2.0
52 if (! cct->_conf->rgw_sts_token_introspection_url.empty()) {
53 bufferlist introspect_resp;
54 RGWHTTPTransceiver introspect_req(cct, "POST", cct->_conf->rgw_sts_token_introspection_url, &introspect_resp);
55 //Headers
56 introspect_req.append_header("Content-Type", "application/x-www-form-urlencoded");
57 string base64_creds = "Basic " + rgw::to_base64(cct->_conf->rgw_sts_client_id + ":" + cct->_conf->rgw_sts_client_secret);
58 introspect_req.append_header("Authorization", base64_creds);
59 // POST data
60 string post_data = "token=" + token;
61 introspect_req.set_post_data(post_data);
62 introspect_req.set_send_length(post_data.length());
63
64 int res = introspect_req.process(null_yield);
65 if (res < 0) {
66 ldpp_dout(dpp, 10) << "HTTP request res: " << res << dendl;
67 throw -EINVAL;
68 }
69 //Debug only
70 ldpp_dout(dpp, 20) << "HTTP status: " << introspect_req.get_http_status() << dendl;
71 ldpp_dout(dpp, 20) << "JSON Response is: " << introspect_resp.c_str() << dendl;
72
73 JSONParser parser;
74 WebTokenEngine::token_t token;
75 if (!parser.parse(introspect_resp.c_str(), introspect_resp.length())) {
76 ldpp_dout(dpp, 2) << "Malformed json" << dendl;
77 throw -EINVAL;
78 } else {
79 bool is_active;
80 JSONDecoder::decode_json("active", is_active, &parser);
81 if (! is_active) {
82 ldpp_dout(dpp, 0) << "Active state is false" << dendl;
83 throw -ERR_INVALID_IDENTITY_TOKEN;
84 }
85 JSONDecoder::decode_json("iss", token.iss, &parser);
86 JSONDecoder::decode_json("aud", token.aud, &parser);
87 JSONDecoder::decode_json("sub", token.sub, &parser);
88 JSONDecoder::decode_json("user_name", token.user_name, &parser);
89 }
90 return token;
91 }
92 return boost::none;
93 }
94
95 WebTokenEngine::result_t
96 WebTokenEngine::authenticate( const DoutPrefixProvider* dpp,
97 const std::string& token,
98 const req_state* const s) const
99 {
100 boost::optional<WebTokenEngine::token_t> t;
101
102 if (! is_applicable(token)) {
103 return result_t::deny();
104 }
105
106 try {
107 t = get_from_idp(dpp, token);
108 } catch(...) {
109 return result_t::deny(-EACCES);
110 }
111
112 if (t) {
113 auto apl = apl_factory->create_apl_web_identity(cct, s, *t);
114 return result_t::grant(std::move(apl));
115 }
116 return result_t::deny(-EACCES);
117 }
118
119 } // namespace rgw::auth::s3
120
121 int RGWREST_STS::verify_permission()
122 {
123 STS::STSService _sts(s->cct, store, s->user->get_id(), s->auth.identity.get());
124 sts = std::move(_sts);
125
126 string rArn = s->info.args.get("RoleArn");
127 const auto& [ret, role] = sts.getRoleInfo(rArn);
128 if (ret < 0) {
129 return ret;
130 }
131 string policy = role.get_assume_role_policy();
132 buffer::list bl = buffer::list::static_from_string(policy);
133
134 //Parse the policy
135 //TODO - This step should be part of Role Creation
136 try {
137 const rgw::IAM::Policy p(s->cct, s->user->get_tenant(), bl);
138 //Check if the input role arn is there as one of the Principals in the policy,
139 // If yes, then return 0, else -EPERM
140 auto p_res = p.eval_principal(s->env, *s->auth.identity);
141 if (p_res == rgw::IAM::Effect::Deny) {
142 return -EPERM;
143 }
144 auto c_res = p.eval_conditions(s->env);
145 if (c_res == rgw::IAM::Effect::Deny) {
146 return -EPERM;
147 }
148 } catch (rgw::IAM::PolicyParseException& e) {
149 ldout(s->cct, 20) << "failed to parse policy: " << e.what() << dendl;
150 return -EPERM;
151 }
152
153 return 0;
154 }
155
156 void RGWREST_STS::send_response()
157 {
158 if (op_ret) {
159 set_req_state_err(s, op_ret);
160 }
161 dump_errno(s);
162 end_header(s);
163 }
164
165 int RGWSTSGetSessionToken::verify_permission()
166 {
167 rgw::Partition partition = rgw::Partition::aws;
168 rgw::Service service = rgw::Service::s3;
169 if (!verify_user_permission(this,
170 s,
171 rgw::ARN(partition, service, "", s->user->get_tenant(), ""),
172 rgw::IAM::stsGetSessionToken)) {
173 return -EACCES;
174 }
175
176 return 0;
177 }
178
179 int RGWSTSGetSessionToken::get_params()
180 {
181 duration = s->info.args.get("DurationSeconds");
182 serialNumber = s->info.args.get("SerialNumber");
183 tokenCode = s->info.args.get("TokenCode");
184
185 if (! duration.empty()) {
186 string err;
187 uint64_t duration_in_secs = strict_strtoll(duration.c_str(), 10, &err);
188 if (!err.empty()) {
189 return -EINVAL;
190 }
191
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->get_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->get_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->get_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 rgw::sal::RGWRadosStore *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 if (post_body.size() > 0) {
349 ldout(s->cct, 10) << "Content of POST: " << post_body << dendl;
350
351 if (post_body.find("Action") != string::npos) {
352 boost::char_separator<char> sep("&");
353 boost::tokenizer<boost::char_separator<char>> tokens(post_body, sep);
354 for (const auto& t : tokens) {
355 auto pos = t.find("=");
356 if (pos != string::npos) {
357 s->info.args.append(t.substr(0,pos),
358 url_decode(t.substr(pos+1, t.size() -1)));
359 }
360 }
361 }
362 }
363 auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body);
364 s->info.args.append("PayloadHash", payload_hash);
365 }
366
367 RGWOp *RGWHandler_REST_STS::op_post()
368 {
369 rgw_sts_parse_input();
370
371 if (s->info.args.exists("Action")) {
372 string action = s->info.args.get("Action");
373 if (action == "AssumeRole") {
374 return new RGWSTSAssumeRole;
375 } else if (action == "GetSessionToken") {
376 return new RGWSTSGetSessionToken;
377 } else if (action == "AssumeRoleWithWebIdentity") {
378 return new RGWSTSAssumeRoleWithWebIdentity;
379 }
380 }
381
382 return nullptr;
383 }
384
385 int RGWHandler_REST_STS::init(rgw::sal::RGWRadosStore *store,
386 struct req_state *s,
387 rgw::io::BasicClient *cio)
388 {
389 s->dialect = "sts";
390
391 if (int ret = RGWHandler_REST_STS::init_from_header(s, RGW_FORMAT_XML, true); ret < 0) {
392 ldout(s->cct, 10) << "init_from_header returned err=" << ret << dendl;
393 return ret;
394 }
395
396 return RGWHandler_REST::init(store, s, cio);
397 }
398
399 int RGWHandler_REST_STS::authorize(const DoutPrefixProvider* dpp)
400 {
401 if (s->info.args.exists("Action") && s->info.args.get("Action") == "AssumeRoleWithWebIdentity") {
402 return RGW_Auth_STS::authorize(dpp, store, auth_registry, s);
403 }
404 return RGW_Auth_S3::authorize(dpp, store, auth_registry, s);
405 }
406
407 int RGWHandler_REST_STS::init_from_header(struct req_state* s,
408 int default_formatter,
409 bool configurable_format)
410 {
411 string req;
412 string first;
413
414 s->prot_flags = RGW_REST_STS;
415
416 const char *p, *req_name;
417 if (req_name = s->relative_uri.c_str(); *req_name == '?') {
418 p = req_name;
419 } else {
420 p = s->info.request_params.c_str();
421 }
422
423 s->info.args.set(p);
424 s->info.args.parse();
425
426 /* must be called after the args parsing */
427 if (int ret = allocate_formatter(s, default_formatter, configurable_format); ret < 0)
428 return ret;
429
430 if (*req_name != '/')
431 return 0;
432
433 req_name++;
434
435 if (!*req_name)
436 return 0;
437
438 req = req_name;
439 int pos = req.find('/');
440 if (pos >= 0) {
441 first = req.substr(0, pos);
442 } else {
443 first = req;
444 }
445
446 return 0;
447 }
448
449 RGWHandler_REST*
450 RGWRESTMgr_STS::get_handler(struct req_state* const s,
451 const rgw::auth::StrategyRegistry& auth_registry,
452 const std::string& frontend_prefix)
453 {
454 return new RGWHandler_REST_STS(auth_registry);
455 }