]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_rest_sts.cc
import new upstream nautilus stable release 14.2.8
[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"
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
40namespace rgw {
41namespace auth {
42namespace sts {
43
44bool
45WebTokenEngine::is_applicable(const std::string& token) const noexcept
46{
47 return ! token.empty();
48}
49
50boost::optional<WebTokenEngine::token_t>
51WebTokenEngine::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
97WebTokenEngine::result_t
98WebTokenEngine::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
125int 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
160void 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
169int 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
183int 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
199void 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
222int 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
252void 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
282int 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
311void 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
337int 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
345void 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
370RGWOp *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
388int 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
402int 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
410int 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
452RGWHandler_REST*
453RGWRESTMgr_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}