]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_rest_sts.cc
import 14.2.4 nautilus point release
[ceph.git] / ceph / src / rgw / rgw_rest_sts.cc
CommitLineData
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"
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
41namespace rgw {
42namespace auth {
43namespace sts {
44
45bool
46WebTokenEngine::is_applicable(const std::string& token) const noexcept
47{
48 return ! token.empty();
49}
50
51boost::optional<WebTokenEngine::token_t>
52WebTokenEngine::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
98WebTokenEngine::result_t
99WebTokenEngine::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
126int 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
161void 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
170int 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
184int 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
200void 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
223int 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
253void 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
283int 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
312void 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
338int 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
346void 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);
494da23a 365 if (key == "RoleArn" || key == "Policy") {
11fdf7f2
TL
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
378RGWOp *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
396int 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
410int 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
418int 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
460RGWHandler_REST*
461RGWRESTMgr_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}