]> git.proxmox.com Git - ceph.git/blame - ceph/src/rgw/rgw_sts.cc
import ceph pacific 16.2.5
[ceph.git] / ceph / src / rgw / rgw_sts.cc
CommitLineData
11fdf7f2 1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
9f95a23c 2// vim: ts=8 sw=2 smarttab ft=cpp
11fdf7f2
TL
3
4#include <errno.h>
5#include <ctime>
6#include <regex>
7#include <boost/format.hpp>
8#include <boost/algorithm/string/replace.hpp>
9
10#include "common/errno.h"
11#include "common/Formatter.h"
12#include "common/ceph_json.h"
13#include "common/ceph_time.h"
11fdf7f2
TL
14#include "auth/Crypto.h"
15#include "include/ceph_fs.h"
16#include "common/iso_8601.h"
17
18#include "include/types.h"
19#include "rgw_string.h"
20
21#include "rgw_b64.h"
22#include "rgw_common.h"
23#include "rgw_tools.h"
24#include "rgw_role.h"
25#include "rgw_user.h"
26#include "rgw_iam_policy.h"
27#include "rgw_sts.h"
9f95a23c 28#include "rgw_sal.h"
f67539c2 29#include "rgw_sal_rados.h"
11fdf7f2
TL
30
31#define dout_subsys ceph_subsys_rgw
32
33namespace STS {
34
35void Credentials::dump(Formatter *f) const
36{
37 encode_json("AccessKeyId", accessKeyId , f);
38 encode_json("Expiration", expiration , f);
39 encode_json("SecretAccessKey", secretAccessKey , f);
40 encode_json("SessionToken", sessionToken , f);
41}
42
43int Credentials::generateCredentials(CephContext* cct,
44 const uint64_t& duration,
45 const boost::optional<string>& policy,
46 const boost::optional<string>& roleId,
f91f0fd5 47 const boost::optional<string>& role_session,
adb31ebb 48 const boost::optional<std::vector<string>> token_claims,
11fdf7f2
TL
49 boost::optional<rgw_user> user,
50 rgw::auth::Identity* identity)
51{
52 uuid_d accessKey, secretKey;
53 char accessKeyId_str[MAX_ACCESS_KEY_LEN], secretAccessKey_str[MAX_SECRET_KEY_LEN];
54
55 //AccessKeyId
56 gen_rand_alphanumeric_plain(cct, accessKeyId_str, sizeof(accessKeyId_str));
57 accessKeyId = accessKeyId_str;
58
59 //SecretAccessKey
60 gen_rand_alphanumeric_upper(cct, secretAccessKey_str, sizeof(secretAccessKey_str));
61 secretAccessKey = secretAccessKey_str;
62
63 //Expiration
64 real_clock::time_point t = real_clock::now();
65 real_clock::time_point exp = t + std::chrono::seconds(duration);
66 expiration = ceph::to_iso_8601(exp);
67
68 //Session Token - Encrypt using AES
69 auto* cryptohandler = cct->get_crypto_handler(CEPH_CRYPTO_AES);
70 if (! cryptohandler) {
f67539c2 71 ldout(cct, 0) << "ERROR: No AES cryto handler found !" << dendl;
11fdf7f2
TL
72 return -EINVAL;
73 }
74 string secret_s = cct->_conf->rgw_sts_key;
75 buffer::ptr secret(secret_s.c_str(), secret_s.length());
76 int ret = 0;
77 if (ret = cryptohandler->validate_secret(secret); ret < 0) {
f67539c2 78 ldout(cct, 0) << "ERROR: Invalid rgw sts key, please ensure its length is 16" << dendl;
11fdf7f2
TL
79 return ret;
80 }
81 string error;
82 auto* keyhandler = cryptohandler->get_key_handler(secret, error);
83 if (! keyhandler) {
f67539c2 84 ldout(cct, 0) << "ERROR: No Key handler found !" << dendl;
11fdf7f2
TL
85 return -EINVAL;
86 }
87 error.clear();
88 //Storing policy and roleId as part of token, so that they can be extracted
89 // from the token itself for policy evaluation.
90 SessionToken token;
91 //authentication info
92 token.access_key_id = accessKeyId;
93 token.secret_access_key = secretAccessKey;
94 token.expiration = expiration;
f67539c2 95 token.issued_at = ceph::to_iso_8601(t);
11fdf7f2
TL
96
97 //Authorization info
98 if (policy)
99 token.policy = *policy;
100 else
101 token.policy = {};
102
103 if (roleId)
104 token.roleId = *roleId;
105 else
106 token.roleId = {};
107
108 if (user)
109 token.user = *user;
110 else {
f67539c2 111 rgw_user u({}, {}, {});
11fdf7f2
TL
112 token.user = u;
113 }
114
adb31ebb
TL
115 if (token_claims) {
116 token.token_claims = std::move(*token_claims);
117 }
118
11fdf7f2
TL
119 if (identity) {
120 token.acct_name = identity->get_acct_name();
121 token.perm_mask = identity->get_perm_mask();
122 token.is_admin = identity->is_admin_of(token.user);
123 token.acct_type = identity->get_identity_type();
124 } else {
125 token.acct_name = {};
126 token.perm_mask = 0;
127 token.is_admin = 0;
128 token.acct_type = TYPE_ROLE;
f91f0fd5 129 token.role_session = role_session.get();
11fdf7f2
TL
130 }
131
132 buffer::list input, enc_output;
133 encode(token, input);
134
135 if (ret = keyhandler->encrypt(input, enc_output, &error); ret < 0) {
f67539c2 136 ldout(cct, 0) << "ERROR: Encrypting session token returned an error !" << dendl;
11fdf7f2
TL
137 return ret;
138 }
139
140 bufferlist encoded_op;
141 enc_output.encode_base64(encoded_op);
142 encoded_op.append('\0');
143 sessionToken = encoded_op.c_str();
144
145 return ret;
146}
147
148void AssumedRoleUser::dump(Formatter *f) const
149{
150 encode_json("Arn", arn , f);
151 encode_json("AssumeRoleId", assumeRoleId , f);
152}
153
154int AssumedRoleUser::generateAssumedRoleUser(CephContext* cct,
9f95a23c 155 rgw::sal::RGWRadosStore *store,
11fdf7f2 156 const string& roleId,
eafe8130 157 const rgw::ARN& roleArn,
11fdf7f2
TL
158 const string& roleSessionName)
159{
160 string resource = std::move(roleArn.resource);
161 boost::replace_first(resource, "role", "assumed-role");
162 resource.append("/");
163 resource.append(roleSessionName);
164
eafe8130
TL
165 rgw::ARN assumed_role_arn(rgw::Partition::aws,
166 rgw::Service::sts,
11fdf7f2
TL
167 "", roleArn.account, resource);
168 arn = assumed_role_arn.to_string();
169
170 //Assumeroleid = roleid:rolesessionname
171 assumeRoleId = roleId + ":" + roleSessionName;
172
173 return 0;
174}
175
f67539c2
TL
176AssumeRoleRequestBase::AssumeRoleRequestBase( CephContext* cct,
177 const string& duration,
11fdf7f2
TL
178 const string& iamPolicy,
179 const string& roleArn,
180 const string& roleSessionName)
f67539c2 181 : cct(cct), iamPolicy(iamPolicy), roleArn(roleArn), roleSessionName(roleSessionName)
11fdf7f2 182{
f67539c2 183 MIN_DURATION_IN_SECS = cct->_conf->rgw_sts_min_session_duration;
11fdf7f2
TL
184 if (duration.empty()) {
185 this->duration = DEFAULT_DURATION_IN_SECS;
186 } else {
9f95a23c 187 this->duration = strict_strtoll(duration.c_str(), 10, &this->err_msg);
11fdf7f2
TL
188 }
189}
190
191int AssumeRoleRequestBase::validate_input() const
192{
9f95a23c 193 if (!err_msg.empty()) {
f67539c2 194 ldout(cct, 0) << "ERROR: error message is empty !" << dendl;
9f95a23c
TL
195 return -EINVAL;
196 }
197
11fdf7f2
TL
198 if (duration < MIN_DURATION_IN_SECS ||
199 duration > MAX_DURATION_IN_SECS) {
f67539c2 200 ldout(cct, 0) << "ERROR: Incorrect value of duration: " << duration << dendl;
11fdf7f2
TL
201 return -EINVAL;
202 }
203
204 if (! iamPolicy.empty() &&
205 (iamPolicy.size() < MIN_POLICY_SIZE || iamPolicy.size() > MAX_POLICY_SIZE)) {
f67539c2 206 ldout(cct, 0) << "ERROR: Incorrect size of iamPolicy: " << iamPolicy.size() << dendl;
11fdf7f2
TL
207 return -ERR_PACKED_POLICY_TOO_LARGE;
208 }
209
210 if (! roleArn.empty() &&
211 (roleArn.size() < MIN_ROLE_ARN_SIZE || roleArn.size() > MAX_ROLE_ARN_SIZE)) {
f67539c2 212 ldout(cct, 0) << "ERROR: Incorrect size of roleArn: " << roleArn.size() << dendl;
11fdf7f2
TL
213 return -EINVAL;
214 }
215
216 if (! roleSessionName.empty()) {
217 if (roleSessionName.size() < MIN_ROLE_SESSION_SIZE || roleSessionName.size() > MAX_ROLE_SESSION_SIZE) {
f67539c2 218 ldout(cct, 0) << "ERROR: Either role session name is empty or role session size is incorrect: " << roleSessionName.size() << dendl;
11fdf7f2
TL
219 return -EINVAL;
220 }
221
222 std::regex regex_roleSession("[A-Za-z0-9_=,.@-]+");
223 if (! std::regex_match(roleSessionName, regex_roleSession)) {
f67539c2 224 ldout(cct, 0) << "ERROR: Role session name is incorrect: " << roleSessionName << dendl;
11fdf7f2
TL
225 return -EINVAL;
226 }
227 }
228
229 return 0;
230}
231
232int AssumeRoleWithWebIdentityRequest::validate_input() const
233{
234 if (! providerId.empty()) {
235 if (providerId.length() < MIN_PROVIDER_ID_LEN ||
236 providerId.length() > MAX_PROVIDER_ID_LEN) {
f67539c2 237 ldout(cct, 0) << "ERROR: Either provider id is empty or provider id length is incorrect: " << providerId.length() << dendl;
11fdf7f2
TL
238 return -EINVAL;
239 }
240 }
241 return AssumeRoleRequestBase::validate_input();
242}
243
244int AssumeRoleRequest::validate_input() const
245{
246 if (! externalId.empty()) {
247 if (externalId.length() < MIN_EXTERNAL_ID_LEN ||
248 externalId.length() > MAX_EXTERNAL_ID_LEN) {
f67539c2 249 ldout(cct, 0) << "ERROR: Either external id is empty or external id length is incorrect: " << externalId.length() << dendl;
11fdf7f2
TL
250 return -EINVAL;
251 }
252
253 std::regex regex_externalId("[A-Za-z0-9_=,.@:/-]+");
254 if (! std::regex_match(externalId, regex_externalId)) {
f67539c2 255 ldout(cct, 0) << "ERROR: Invalid external Id: " << externalId << dendl;
11fdf7f2
TL
256 return -EINVAL;
257 }
258 }
259 if (! serialNumber.empty()){
260 if (serialNumber.size() < MIN_SERIAL_NUMBER_SIZE || serialNumber.size() > MAX_SERIAL_NUMBER_SIZE) {
f67539c2 261 ldout(cct, 0) << "Either serial number is empty or serial number length is incorrect: " << serialNumber.size() << dendl;
11fdf7f2
TL
262 return -EINVAL;
263 }
264
265 std::regex regex_serialNumber("[A-Za-z0-9_=/:,.@-]+");
266 if (! std::regex_match(serialNumber, regex_serialNumber)) {
f67539c2 267 ldout(cct, 0) << "Incorrect serial number: " << serialNumber << dendl;
11fdf7f2
TL
268 return -EINVAL;
269 }
270 }
271 if (! tokenCode.empty() && tokenCode.size() == TOKEN_CODE_SIZE) {
f67539c2 272 ldout(cct, 0) << "Either token code is empty or token code size is invalid: " << tokenCode.size() << dendl;
11fdf7f2
TL
273 return -EINVAL;
274 }
275
276 return AssumeRoleRequestBase::validate_input();
277}
278
b3b6e05e
TL
279std::tuple<int, RGWRole> STSService::getRoleInfo(const DoutPrefixProvider *dpp,
280 const string& arn,
f67539c2 281 optional_yield y)
11fdf7f2 282{
eafe8130 283 if (auto r_arn = rgw::ARN::parse(arn); r_arn) {
11fdf7f2
TL
284 auto pos = r_arn->resource.find_last_of('/');
285 string roleName = r_arn->resource.substr(pos + 1);
9f95a23c 286 RGWRole role(cct, store->getRados()->pctl, roleName, r_arn->account);
b3b6e05e 287 if (int ret = role.get(dpp, y); ret < 0) {
11fdf7f2 288 if (ret == -ENOENT) {
b3b6e05e 289 ldpp_dout(dpp, 0) << "Role doesn't exist: " << roleName << dendl;
11fdf7f2
TL
290 ret = -ERR_NO_ROLE_FOUND;
291 }
292 return make_tuple(ret, this->role);
293 } else {
f67539c2
TL
294 auto path_pos = r_arn->resource.find('/');
295 string path;
296 if (path_pos == pos) {
297 path = "/";
298 } else {
299 path = r_arn->resource.substr(path_pos, ((pos - path_pos) + 1));
300 }
301 string r_path = role.get_path();
302 if (path != r_path) {
b3b6e05e 303 ldpp_dout(dpp, 0) << "Invalid Role ARN: Path in ARN does not match with the role path: " << path << " " << r_path << dendl;
f67539c2
TL
304 return make_tuple(-EACCES, this->role);
305 }
11fdf7f2
TL
306 this->role = std::move(role);
307 return make_tuple(0, this->role);
308 }
309 } else {
b3b6e05e 310 ldpp_dout(dpp, 0) << "Invalid role arn: " << arn << dendl;
11fdf7f2
TL
311 return make_tuple(-EINVAL, this->role);
312 }
313}
314
b3b6e05e 315int STSService::storeARN(const DoutPrefixProvider *dpp, string& arn, optional_yield y)
11fdf7f2
TL
316{
317 int ret = 0;
318 RGWUserInfo info;
b3b6e05e 319 if (ret = rgw_get_user_info_by_uid(dpp, store->ctl()->user, user_id, info, y); ret < 0) {
11fdf7f2
TL
320 return -ERR_NO_SUCH_ENTITY;
321 }
322
323 info.assumed_role_arn = arn;
324
325 RGWObjVersionTracker objv_tracker;
b3b6e05e 326 if (ret = rgw_store_user_info(dpp, store->ctl()->user, info, &info, &objv_tracker, real_time(),
f67539c2 327 false, y); ret < 0) {
11fdf7f2
TL
328 return -ERR_INTERNAL_ERROR;
329 }
330 return ret;
331}
332
333AssumeRoleWithWebIdentityResponse STSService::assumeRoleWithWebIdentity(AssumeRoleWithWebIdentityRequest& req)
334{
335 AssumeRoleWithWebIdentityResponse response;
336 response.assumeRoleResp.packedPolicySize = 0;
adb31ebb 337 std::vector<string> token_claims;
11fdf7f2
TL
338
339 if (req.getProviderId().empty()) {
340 response.providerId = req.getIss();
341 }
342 response.aud = req.getAud();
343 response.sub = req.getSub();
344
adb31ebb
TL
345 token_claims.emplace_back(string("iss") + ":" + req.getIss());
346 token_claims.emplace_back(string("aud") + ":" + req.getAud());
347 token_claims.emplace_back(string("sub") + ":" + req.getSub());
348
11fdf7f2 349 //Get the role info which is being assumed
eafe8130 350 boost::optional<rgw::ARN> r_arn = rgw::ARN::parse(req.getRoleARN());
11fdf7f2 351 if (r_arn == boost::none) {
f67539c2 352 ldout(cct, 0) << "Error in parsing role arn: " << req.getRoleARN() << dendl;
11fdf7f2
TL
353 response.assumeRoleResp.retCode = -EINVAL;
354 return response;
355 }
356
357 string roleId = role.get_id();
358 uint64_t roleMaxSessionDuration = role.get_max_session_duration();
359 req.setMaxDuration(roleMaxSessionDuration);
360
361 //Validate input
362 response.assumeRoleResp.retCode = req.validate_input();
363 if (response.assumeRoleResp.retCode < 0) {
364 return response;
365 }
366
367 //Calculate PackedPolicySize
368 string policy = req.getPolicy();
369 response.assumeRoleResp.packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
370
371 //Generate Assumed Role User
372 response.assumeRoleResp.retCode = response.assumeRoleResp.user.generateAssumedRoleUser(cct,
373 store,
374 roleId,
375 r_arn.get(),
376 req.getRoleSessionName());
377 if (response.assumeRoleResp.retCode < 0) {
378 return response;
379 }
380
381 //Generate Credentials
382 //Role and Policy provide the authorization info, user id and applier info are not needed
383 response.assumeRoleResp.retCode = response.assumeRoleResp.creds.generateCredentials(cct, req.getDuration(),
384 req.getPolicy(), roleId,
f91f0fd5 385 req.getRoleSessionName(),
adb31ebb 386 token_claims,
11fdf7f2
TL
387 user_id, nullptr);
388 if (response.assumeRoleResp.retCode < 0) {
389 return response;
390 }
391
392 response.assumeRoleResp.retCode = 0;
393 return response;
394}
395
b3b6e05e
TL
396AssumeRoleResponse STSService::assumeRole(const DoutPrefixProvider *dpp,
397 AssumeRoleRequest& req,
f67539c2 398 optional_yield y)
11fdf7f2
TL
399{
400 AssumeRoleResponse response;
401 response.packedPolicySize = 0;
402
403 //Get the role info which is being assumed
eafe8130 404 boost::optional<rgw::ARN> r_arn = rgw::ARN::parse(req.getRoleARN());
11fdf7f2 405 if (r_arn == boost::none) {
b3b6e05e 406 ldpp_dout(dpp, 0) << "Error in parsing role arn: " << req.getRoleARN() << dendl;
11fdf7f2
TL
407 response.retCode = -EINVAL;
408 return response;
409 }
410
411 string roleId = role.get_id();
412 uint64_t roleMaxSessionDuration = role.get_max_session_duration();
413 req.setMaxDuration(roleMaxSessionDuration);
414
415 //Validate input
416 response.retCode = req.validate_input();
417 if (response.retCode < 0) {
418 return response;
419 }
420
421 //Calculate PackedPolicySize
422 string policy = req.getPolicy();
423 response.packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
424
425 //Generate Assumed Role User
426 response.retCode = response.user.generateAssumedRoleUser(cct, store, roleId, r_arn.get(), req.getRoleSessionName());
427 if (response.retCode < 0) {
428 return response;
429 }
430
431 //Generate Credentials
432 //Role and Policy provide the authorization info, user id and applier info are not needed
433 response.retCode = response.creds.generateCredentials(cct, req.getDuration(),
434 req.getPolicy(), roleId,
f91f0fd5 435 req.getRoleSessionName(),
adb31ebb 436 boost::none,
11fdf7f2
TL
437 user_id, nullptr);
438 if (response.retCode < 0) {
439 return response;
440 }
441
442 //Save ARN with the user
443 string arn = response.user.getARN();
b3b6e05e 444 response.retCode = storeARN(dpp, arn, y);
11fdf7f2
TL
445 if (response.retCode < 0) {
446 return response;
447 }
448
449 response.retCode = 0;
450 return response;
451}
452
453GetSessionTokenRequest::GetSessionTokenRequest(const string& duration, const string& serialNumber, const string& tokenCode)
454{
455 if (duration.empty()) {
456 this->duration = DEFAULT_DURATION_IN_SECS;
457 } else {
458 this->duration = stoull(duration);
459 }
460 this->serialNumber = serialNumber;
461 this->tokenCode = tokenCode;
462}
463
464GetSessionTokenResponse STSService::getSessionToken(GetSessionTokenRequest& req)
465{
466 int ret;
467 Credentials cred;
468
469 //Generate Credentials
470 if (ret = cred.generateCredentials(cct,
471 req.getDuration(),
472 boost::none,
473 boost::none,
f91f0fd5 474 boost::none,
adb31ebb 475 boost::none,
11fdf7f2
TL
476 user_id,
477 identity); ret < 0) {
478 return make_tuple(ret, cred);
479 }
480
481 return make_tuple(0, cred);
482}
483
484}