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 DoutPrefixProvider
*dpp
,
283 if (auto r_arn
= rgw::ARN::parse(arn
); r_arn
) {
284 auto pos
= r_arn
->resource
.find_last_of('/');
285 string roleName
= r_arn
->resource
.substr(pos
+ 1);
286 RGWRole
role(cct
, store
->getRados()->pctl
, roleName
, r_arn
->account
);
287 if (int ret
= role
.get(dpp
, y
); ret
< 0) {
288 if (ret
== -ENOENT
) {
289 ldpp_dout(dpp
, 0) << "Role doesn't exist: " << roleName
<< dendl
;
290 ret
= -ERR_NO_ROLE_FOUND
;
292 return make_tuple(ret
, this->role
);
294 auto path_pos
= r_arn
->resource
.find('/');
296 if (path_pos
== pos
) {
299 path
= r_arn
->resource
.substr(path_pos
, ((pos
- path_pos
) + 1));
301 string r_path
= role
.get_path();
302 if (path
!= r_path
) {
303 ldpp_dout(dpp
, 0) << "Invalid Role ARN: Path in ARN does not match with the role path: " << path
<< " " << r_path
<< dendl
;
304 return make_tuple(-EACCES
, this->role
);
306 this->role
= std::move(role
);
307 return make_tuple(0, this->role
);
310 ldpp_dout(dpp
, 0) << "Invalid role arn: " << arn
<< dendl
;
311 return make_tuple(-EINVAL
, this->role
);
315 int STSService::storeARN(const DoutPrefixProvider
*dpp
, string
& arn
, optional_yield y
)
319 if (ret
= rgw_get_user_info_by_uid(dpp
, store
->ctl()->user
, user_id
, info
, y
); ret
< 0) {
320 return -ERR_NO_SUCH_ENTITY
;
323 info
.assumed_role_arn
= arn
;
325 RGWObjVersionTracker objv_tracker
;
326 if (ret
= rgw_store_user_info(dpp
, store
->ctl()->user
, info
, &info
, &objv_tracker
, real_time(),
327 false, y
); ret
< 0) {
328 return -ERR_INTERNAL_ERROR
;
333 AssumeRoleWithWebIdentityResponse
STSService::assumeRoleWithWebIdentity(AssumeRoleWithWebIdentityRequest
& req
)
335 AssumeRoleWithWebIdentityResponse response
;
336 response
.assumeRoleResp
.packedPolicySize
= 0;
337 std::vector
<string
> token_claims
;
339 if (req
.getProviderId().empty()) {
340 response
.providerId
= req
.getIss();
342 response
.aud
= req
.getAud();
343 response
.sub
= req
.getSub();
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());
349 //Get the role info which is being assumed
350 boost::optional
<rgw::ARN
> r_arn
= rgw::ARN::parse(req
.getRoleARN());
351 if (r_arn
== boost::none
) {
352 ldout(cct
, 0) << "Error in parsing role arn: " << req
.getRoleARN() << dendl
;
353 response
.assumeRoleResp
.retCode
= -EINVAL
;
357 string roleId
= role
.get_id();
358 uint64_t roleMaxSessionDuration
= role
.get_max_session_duration();
359 req
.setMaxDuration(roleMaxSessionDuration
);
362 response
.assumeRoleResp
.retCode
= req
.validate_input();
363 if (response
.assumeRoleResp
.retCode
< 0) {
367 //Calculate PackedPolicySize
368 string policy
= req
.getPolicy();
369 response
.assumeRoleResp
.packedPolicySize
= (policy
.size() / req
.getMaxPolicySize()) * 100;
371 //Generate Assumed Role User
372 response
.assumeRoleResp
.retCode
= response
.assumeRoleResp
.user
.generateAssumedRoleUser(cct
,
376 req
.getRoleSessionName());
377 if (response
.assumeRoleResp
.retCode
< 0) {
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
,
385 req
.getRoleSessionName(),
388 if (response
.assumeRoleResp
.retCode
< 0) {
392 response
.assumeRoleResp
.retCode
= 0;
396 AssumeRoleResponse
STSService::assumeRole(const DoutPrefixProvider
*dpp
,
397 AssumeRoleRequest
& req
,
400 AssumeRoleResponse response
;
401 response
.packedPolicySize
= 0;
403 //Get the role info which is being assumed
404 boost::optional
<rgw::ARN
> r_arn
= rgw::ARN::parse(req
.getRoleARN());
405 if (r_arn
== boost::none
) {
406 ldpp_dout(dpp
, 0) << "Error in parsing role arn: " << req
.getRoleARN() << dendl
;
407 response
.retCode
= -EINVAL
;
411 string roleId
= role
.get_id();
412 uint64_t roleMaxSessionDuration
= role
.get_max_session_duration();
413 req
.setMaxDuration(roleMaxSessionDuration
);
416 response
.retCode
= req
.validate_input();
417 if (response
.retCode
< 0) {
421 //Calculate PackedPolicySize
422 string policy
= req
.getPolicy();
423 response
.packedPolicySize
= (policy
.size() / req
.getMaxPolicySize()) * 100;
425 //Generate Assumed Role User
426 response
.retCode
= response
.user
.generateAssumedRoleUser(cct
, store
, roleId
, r_arn
.get(), req
.getRoleSessionName());
427 if (response
.retCode
< 0) {
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
,
435 req
.getRoleSessionName(),
438 if (response
.retCode
< 0) {
442 //Save ARN with the user
443 string arn
= response
.user
.getARN();
444 response
.retCode
= storeARN(dpp
, arn
, y
);
445 if (response
.retCode
< 0) {
449 response
.retCode
= 0;
453 GetSessionTokenRequest::GetSessionTokenRequest(const string
& duration
, const string
& serialNumber
, const string
& tokenCode
)
455 if (duration
.empty()) {
456 this->duration
= DEFAULT_DURATION_IN_SECS
;
458 this->duration
= stoull(duration
);
460 this->serialNumber
= serialNumber
;
461 this->tokenCode
= tokenCode
;
464 GetSessionTokenResponse
STSService::getSessionToken(GetSessionTokenRequest
& req
)
469 //Generate Credentials
470 if (ret
= cred
.generateCredentials(cct
,
477 identity
); ret
< 0) {
478 return make_tuple(ret
, cred
);
481 return make_tuple(0, cred
);