1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
7 #include <boost/format.hpp>
8 #include <boost/algorithm/string/replace.hpp>
10 #include "common/errno.h"
11 #include "common/Formatter.h"
12 #include "common/ceph_json.h"
13 #include "common/ceph_time.h"
14 #include "auth/Crypto.h"
15 #include "include/ceph_fs.h"
16 #include "common/iso_8601.h"
18 #include "include/types.h"
19 #include "rgw_string.h"
22 #include "rgw_common.h"
23 #include "rgw_tools.h"
26 #include "rgw_iam_policy.h"
29 #include "rgw_sal_rados.h"
31 #define dout_subsys ceph_subsys_rgw
35 void Credentials::dump(Formatter
*f
) const
37 encode_json("AccessKeyId", accessKeyId
, f
);
38 encode_json("Expiration", expiration
, f
);
39 encode_json("SecretAccessKey", secretAccessKey
, f
);
40 encode_json("SessionToken", sessionToken
, f
);
43 int Credentials::generateCredentials(CephContext
* cct
,
44 const uint64_t& duration
,
45 const boost::optional
<string
>& policy
,
46 const boost::optional
<string
>& roleId
,
47 const boost::optional
<string
>& role_session
,
48 const boost::optional
<std::vector
<string
>> token_claims
,
49 boost::optional
<rgw_user
> user
,
50 rgw::auth::Identity
* identity
)
52 uuid_d accessKey
, secretKey
;
53 char accessKeyId_str
[MAX_ACCESS_KEY_LEN
], secretAccessKey_str
[MAX_SECRET_KEY_LEN
];
56 gen_rand_alphanumeric_plain(cct
, accessKeyId_str
, sizeof(accessKeyId_str
));
57 accessKeyId
= accessKeyId_str
;
60 gen_rand_alphanumeric_upper(cct
, secretAccessKey_str
, sizeof(secretAccessKey_str
));
61 secretAccessKey
= secretAccessKey_str
;
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
);
68 //Session Token - Encrypt using AES
69 auto* cryptohandler
= cct
->get_crypto_handler(CEPH_CRYPTO_AES
);
70 if (! cryptohandler
) {
71 ldout(cct
, 0) << "ERROR: No AES cryto handler found !" << dendl
;
74 string secret_s
= cct
->_conf
->rgw_sts_key
;
75 buffer::ptr
secret(secret_s
.c_str(), secret_s
.length());
77 if (ret
= cryptohandler
->validate_secret(secret
); ret
< 0) {
78 ldout(cct
, 0) << "ERROR: Invalid rgw sts key, please ensure its length is 16" << dendl
;
82 auto* keyhandler
= cryptohandler
->get_key_handler(secret
, error
);
84 ldout(cct
, 0) << "ERROR: No Key handler found !" << dendl
;
88 //Storing policy and roleId as part of token, so that they can be extracted
89 // from the token itself for policy evaluation.
92 token
.access_key_id
= accessKeyId
;
93 token
.secret_access_key
= secretAccessKey
;
94 token
.expiration
= expiration
;
95 token
.issued_at
= ceph::to_iso_8601(t
);
99 token
.policy
= *policy
;
104 token
.roleId
= *roleId
;
111 rgw_user
u({}, {}, {});
116 token
.token_claims
= std::move(*token_claims
);
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();
125 token
.acct_name
= {};
128 token
.acct_type
= TYPE_ROLE
;
129 token
.role_session
= role_session
.get();
132 buffer::list input
, enc_output
;
133 encode(token
, input
);
135 if (ret
= keyhandler
->encrypt(input
, enc_output
, &error
); ret
< 0) {
136 ldout(cct
, 0) << "ERROR: Encrypting session token returned an error !" << dendl
;
140 bufferlist encoded_op
;
141 enc_output
.encode_base64(encoded_op
);
142 encoded_op
.append('\0');
143 sessionToken
= encoded_op
.c_str();
148 void AssumedRoleUser::dump(Formatter
*f
) const
150 encode_json("Arn", arn
, f
);
151 encode_json("AssumeRoleId", assumeRoleId
, f
);
154 int AssumedRoleUser::generateAssumedRoleUser(CephContext
* cct
,
155 rgw::sal::RGWRadosStore
*store
,
156 const string
& roleId
,
157 const rgw::ARN
& roleArn
,
158 const string
& roleSessionName
)
160 string resource
= std::move(roleArn
.resource
);
161 boost::replace_first(resource
, "role", "assumed-role");
162 resource
.append("/");
163 resource
.append(roleSessionName
);
165 rgw::ARN
assumed_role_arn(rgw::Partition::aws
,
167 "", roleArn
.account
, resource
);
168 arn
= assumed_role_arn
.to_string();
170 //Assumeroleid = roleid:rolesessionname
171 assumeRoleId
= roleId
+ ":" + roleSessionName
;
176 AssumeRoleRequestBase::AssumeRoleRequestBase( CephContext
* cct
,
177 const string
& duration
,
178 const string
& iamPolicy
,
179 const string
& roleArn
,
180 const string
& roleSessionName
)
181 : cct(cct
), iamPolicy(iamPolicy
), roleArn(roleArn
), roleSessionName(roleSessionName
)
183 MIN_DURATION_IN_SECS
= cct
->_conf
->rgw_sts_min_session_duration
;
184 if (duration
.empty()) {
185 this->duration
= DEFAULT_DURATION_IN_SECS
;
187 this->duration
= strict_strtoll(duration
.c_str(), 10, &this->err_msg
);
191 int AssumeRoleRequestBase::validate_input() const
193 if (!err_msg
.empty()) {
194 ldout(cct
, 0) << "ERROR: error message is empty !" << dendl
;
198 if (duration
< MIN_DURATION_IN_SECS
||
199 duration
> MAX_DURATION_IN_SECS
) {
200 ldout(cct
, 0) << "ERROR: Incorrect value of duration: " << duration
<< dendl
;
204 if (! iamPolicy
.empty() &&
205 (iamPolicy
.size() < MIN_POLICY_SIZE
|| iamPolicy
.size() > MAX_POLICY_SIZE
)) {
206 ldout(cct
, 0) << "ERROR: Incorrect size of iamPolicy: " << iamPolicy
.size() << dendl
;
207 return -ERR_PACKED_POLICY_TOO_LARGE
;
210 if (! roleArn
.empty() &&
211 (roleArn
.size() < MIN_ROLE_ARN_SIZE
|| roleArn
.size() > MAX_ROLE_ARN_SIZE
)) {
212 ldout(cct
, 0) << "ERROR: Incorrect size of roleArn: " << roleArn
.size() << dendl
;
216 if (! roleSessionName
.empty()) {
217 if (roleSessionName
.size() < MIN_ROLE_SESSION_SIZE
|| roleSessionName
.size() > MAX_ROLE_SESSION_SIZE
) {
218 ldout(cct
, 0) << "ERROR: Either role session name is empty or role session size is incorrect: " << roleSessionName
.size() << dendl
;
222 std::regex
regex_roleSession("[A-Za-z0-9_=,.@-]+");
223 if (! std::regex_match(roleSessionName
, regex_roleSession
)) {
224 ldout(cct
, 0) << "ERROR: Role session name is incorrect: " << roleSessionName
<< dendl
;
232 int AssumeRoleWithWebIdentityRequest::validate_input() const
234 if (! providerId
.empty()) {
235 if (providerId
.length() < MIN_PROVIDER_ID_LEN
||
236 providerId
.length() > MAX_PROVIDER_ID_LEN
) {
237 ldout(cct
, 0) << "ERROR: Either provider id is empty or provider id length is incorrect: " << providerId
.length() << dendl
;
241 return AssumeRoleRequestBase::validate_input();
244 int AssumeRoleRequest::validate_input() const
246 if (! externalId
.empty()) {
247 if (externalId
.length() < MIN_EXTERNAL_ID_LEN
||
248 externalId
.length() > MAX_EXTERNAL_ID_LEN
) {
249 ldout(cct
, 0) << "ERROR: Either external id is empty or external id length is incorrect: " << externalId
.length() << dendl
;
253 std::regex
regex_externalId("[A-Za-z0-9_=,.@:/-]+");
254 if (! std::regex_match(externalId
, regex_externalId
)) {
255 ldout(cct
, 0) << "ERROR: Invalid external Id: " << externalId
<< dendl
;
259 if (! serialNumber
.empty()){
260 if (serialNumber
.size() < MIN_SERIAL_NUMBER_SIZE
|| serialNumber
.size() > MAX_SERIAL_NUMBER_SIZE
) {
261 ldout(cct
, 0) << "Either serial number is empty or serial number length is incorrect: " << serialNumber
.size() << dendl
;
265 std::regex
regex_serialNumber("[A-Za-z0-9_=/:,.@-]+");
266 if (! std::regex_match(serialNumber
, regex_serialNumber
)) {
267 ldout(cct
, 0) << "Incorrect serial number: " << serialNumber
<< dendl
;
271 if (! tokenCode
.empty() && tokenCode
.size() == TOKEN_CODE_SIZE
) {
272 ldout(cct
, 0) << "Either token code is empty or token code size is invalid: " << tokenCode
.size() << dendl
;
276 return AssumeRoleRequestBase::validate_input();
279 std::tuple
<int, RGWRole
> STSService::getRoleInfo(const string
& arn
,
282 if (auto r_arn
= rgw::ARN::parse(arn
); r_arn
) {
283 auto pos
= r_arn
->resource
.find_last_of('/');
284 string roleName
= r_arn
->resource
.substr(pos
+ 1);
285 RGWRole
role(cct
, store
->getRados()->pctl
, roleName
, r_arn
->account
);
286 if (int ret
= role
.get(y
); ret
< 0) {
287 if (ret
== -ENOENT
) {
288 ldout(cct
, 0) << "Role doesn't exist: " << roleName
<< dendl
;
289 ret
= -ERR_NO_ROLE_FOUND
;
291 return make_tuple(ret
, this->role
);
293 auto path_pos
= r_arn
->resource
.find('/');
295 if (path_pos
== pos
) {
298 path
= r_arn
->resource
.substr(path_pos
, ((pos
- path_pos
) + 1));
300 string r_path
= role
.get_path();
301 if (path
!= r_path
) {
302 ldout(cct
, 0) << "Invalid Role ARN: Path in ARN does not match with the role path: " << path
<< " " << r_path
<< dendl
;
303 return make_tuple(-EACCES
, this->role
);
305 this->role
= std::move(role
);
306 return make_tuple(0, this->role
);
309 ldout(cct
, 0) << "Invalid role arn: " << arn
<< dendl
;
310 return make_tuple(-EINVAL
, this->role
);
314 int STSService::storeARN(string
& arn
, optional_yield y
)
318 if (ret
= rgw_get_user_info_by_uid(store
->ctl()->user
, user_id
, info
, y
); ret
< 0) {
319 return -ERR_NO_SUCH_ENTITY
;
322 info
.assumed_role_arn
= arn
;
324 RGWObjVersionTracker objv_tracker
;
325 if (ret
= rgw_store_user_info(store
->ctl()->user
, info
, &info
, &objv_tracker
, real_time(),
326 false, y
); ret
< 0) {
327 return -ERR_INTERNAL_ERROR
;
332 AssumeRoleWithWebIdentityResponse
STSService::assumeRoleWithWebIdentity(AssumeRoleWithWebIdentityRequest
& req
)
334 AssumeRoleWithWebIdentityResponse response
;
335 response
.assumeRoleResp
.packedPolicySize
= 0;
336 std::vector
<string
> token_claims
;
338 if (req
.getProviderId().empty()) {
339 response
.providerId
= req
.getIss();
341 response
.aud
= req
.getAud();
342 response
.sub
= req
.getSub();
344 token_claims
.emplace_back(string("iss") + ":" + req
.getIss());
345 token_claims
.emplace_back(string("aud") + ":" + req
.getAud());
346 token_claims
.emplace_back(string("sub") + ":" + req
.getSub());
348 //Get the role info which is being assumed
349 boost::optional
<rgw::ARN
> r_arn
= rgw::ARN::parse(req
.getRoleARN());
350 if (r_arn
== boost::none
) {
351 ldout(cct
, 0) << "Error in parsing role arn: " << req
.getRoleARN() << dendl
;
352 response
.assumeRoleResp
.retCode
= -EINVAL
;
356 string roleId
= role
.get_id();
357 uint64_t roleMaxSessionDuration
= role
.get_max_session_duration();
358 req
.setMaxDuration(roleMaxSessionDuration
);
361 response
.assumeRoleResp
.retCode
= req
.validate_input();
362 if (response
.assumeRoleResp
.retCode
< 0) {
366 //Calculate PackedPolicySize
367 string policy
= req
.getPolicy();
368 response
.assumeRoleResp
.packedPolicySize
= (policy
.size() / req
.getMaxPolicySize()) * 100;
370 //Generate Assumed Role User
371 response
.assumeRoleResp
.retCode
= response
.assumeRoleResp
.user
.generateAssumedRoleUser(cct
,
375 req
.getRoleSessionName());
376 if (response
.assumeRoleResp
.retCode
< 0) {
380 //Generate Credentials
381 //Role and Policy provide the authorization info, user id and applier info are not needed
382 response
.assumeRoleResp
.retCode
= response
.assumeRoleResp
.creds
.generateCredentials(cct
, req
.getDuration(),
383 req
.getPolicy(), roleId
,
384 req
.getRoleSessionName(),
387 if (response
.assumeRoleResp
.retCode
< 0) {
391 response
.assumeRoleResp
.retCode
= 0;
395 AssumeRoleResponse
STSService::assumeRole(AssumeRoleRequest
& req
,
398 AssumeRoleResponse response
;
399 response
.packedPolicySize
= 0;
401 //Get the role info which is being assumed
402 boost::optional
<rgw::ARN
> r_arn
= rgw::ARN::parse(req
.getRoleARN());
403 if (r_arn
== boost::none
) {
404 ldout(cct
, 0) << "Error in parsing role arn: " << req
.getRoleARN() << dendl
;
405 response
.retCode
= -EINVAL
;
409 string roleId
= role
.get_id();
410 uint64_t roleMaxSessionDuration
= role
.get_max_session_duration();
411 req
.setMaxDuration(roleMaxSessionDuration
);
414 response
.retCode
= req
.validate_input();
415 if (response
.retCode
< 0) {
419 //Calculate PackedPolicySize
420 string policy
= req
.getPolicy();
421 response
.packedPolicySize
= (policy
.size() / req
.getMaxPolicySize()) * 100;
423 //Generate Assumed Role User
424 response
.retCode
= response
.user
.generateAssumedRoleUser(cct
, store
, roleId
, r_arn
.get(), req
.getRoleSessionName());
425 if (response
.retCode
< 0) {
429 //Generate Credentials
430 //Role and Policy provide the authorization info, user id and applier info are not needed
431 response
.retCode
= response
.creds
.generateCredentials(cct
, req
.getDuration(),
432 req
.getPolicy(), roleId
,
433 req
.getRoleSessionName(),
436 if (response
.retCode
< 0) {
440 //Save ARN with the user
441 string arn
= response
.user
.getARN();
442 response
.retCode
= storeARN(arn
, y
);
443 if (response
.retCode
< 0) {
447 response
.retCode
= 0;
451 GetSessionTokenRequest::GetSessionTokenRequest(const string
& duration
, const string
& serialNumber
, const string
& tokenCode
)
453 if (duration
.empty()) {
454 this->duration
= DEFAULT_DURATION_IN_SECS
;
456 this->duration
= stoull(duration
);
458 this->serialNumber
= serialNumber
;
459 this->tokenCode
= tokenCode
;
462 GetSessionTokenResponse
STSService::getSessionToken(GetSessionTokenRequest
& req
)
467 //Generate Credentials
468 if (ret
= cred
.generateCredentials(cct
,
475 identity
); ret
< 0) {
476 return make_tuple(ret
, cred
);
479 return make_tuple(0, cred
);