]>
Commit | Line | Data |
---|---|---|
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 | ||
33 | namespace STS { | |
34 | ||
35 | void 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 | ||
43 | int 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 | ||
148 | void AssumedRoleUser::dump(Formatter *f) const | |
149 | { | |
150 | encode_json("Arn", arn , f); | |
151 | encode_json("AssumeRoleId", assumeRoleId , f); | |
152 | } | |
153 | ||
154 | int 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 |
176 | AssumeRoleRequestBase::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 | ||
191 | int 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 | ||
232 | int 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 | ||
244 | int 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 |
279 | std::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 | 315 | int 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 | ||
333 | AssumeRoleWithWebIdentityResponse 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 |
396 | AssumeRoleResponse 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 | ||
453 | GetSessionTokenRequest::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 | ||
464 | GetSessionTokenResponse 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 | } |