]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_sts.cc
import ceph 15.2.10
[ceph.git] / ceph / src / rgw / rgw_sts.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
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"
14 #include "rgw_rados.h"
15 #include "auth/Crypto.h"
16 #include "include/ceph_fs.h"
17 #include "common/iso_8601.h"
18
19 #include "include/types.h"
20 #include "rgw_string.h"
21
22 #include "rgw_b64.h"
23 #include "rgw_common.h"
24 #include "rgw_tools.h"
25 #include "rgw_role.h"
26 #include "rgw_user.h"
27 #include "rgw_iam_policy.h"
28 #include "rgw_sts.h"
29 #include "rgw_sal.h"
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,
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)
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) {
71 return -EINVAL;
72 }
73 string secret_s = cct->_conf->rgw_sts_key;
74 buffer::ptr secret(secret_s.c_str(), secret_s.length());
75 int ret = 0;
76 if (ret = cryptohandler->validate_secret(secret); ret < 0) {
77 ldout(cct, 0) << "ERROR: Invalid secret key" << dendl;
78 return ret;
79 }
80 string error;
81 auto* keyhandler = cryptohandler->get_key_handler(secret, error);
82 if (! keyhandler) {
83 return -EINVAL;
84 }
85 error.clear();
86 //Storing policy and roleId as part of token, so that they can be extracted
87 // from the token itself for policy evaluation.
88 SessionToken token;
89 //authentication info
90 token.access_key_id = accessKeyId;
91 token.secret_access_key = secretAccessKey;
92 token.expiration = expiration;
93
94 //Authorization info
95 if (policy)
96 token.policy = *policy;
97 else
98 token.policy = {};
99
100 if (roleId)
101 token.roleId = *roleId;
102 else
103 token.roleId = {};
104
105 if (user)
106 token.user = *user;
107 else {
108 rgw_user u({}, {});
109 token.user = u;
110 }
111
112 if (token_claims) {
113 token.token_claims = std::move(*token_claims);
114 }
115
116 if (identity) {
117 token.acct_name = identity->get_acct_name();
118 token.perm_mask = identity->get_perm_mask();
119 token.is_admin = identity->is_admin_of(token.user);
120 token.acct_type = identity->get_identity_type();
121 } else {
122 token.acct_name = {};
123 token.perm_mask = 0;
124 token.is_admin = 0;
125 token.acct_type = TYPE_ROLE;
126 token.role_session = role_session.get();
127 }
128
129 buffer::list input, enc_output;
130 encode(token, input);
131
132 if (ret = keyhandler->encrypt(input, enc_output, &error); ret < 0) {
133 return ret;
134 }
135
136 bufferlist encoded_op;
137 enc_output.encode_base64(encoded_op);
138 encoded_op.append('\0');
139 sessionToken = encoded_op.c_str();
140
141 return ret;
142 }
143
144 void AssumedRoleUser::dump(Formatter *f) const
145 {
146 encode_json("Arn", arn , f);
147 encode_json("AssumeRoleId", assumeRoleId , f);
148 }
149
150 int AssumedRoleUser::generateAssumedRoleUser(CephContext* cct,
151 rgw::sal::RGWRadosStore *store,
152 const string& roleId,
153 const rgw::ARN& roleArn,
154 const string& roleSessionName)
155 {
156 string resource = std::move(roleArn.resource);
157 boost::replace_first(resource, "role", "assumed-role");
158 resource.append("/");
159 resource.append(roleSessionName);
160
161 rgw::ARN assumed_role_arn(rgw::Partition::aws,
162 rgw::Service::sts,
163 "", roleArn.account, resource);
164 arn = assumed_role_arn.to_string();
165
166 //Assumeroleid = roleid:rolesessionname
167 assumeRoleId = roleId + ":" + roleSessionName;
168
169 return 0;
170 }
171
172 AssumeRoleRequestBase::AssumeRoleRequestBase( const string& duration,
173 const string& iamPolicy,
174 const string& roleArn,
175 const string& roleSessionName)
176 : iamPolicy(iamPolicy), roleArn(roleArn), roleSessionName(roleSessionName)
177 {
178 if (duration.empty()) {
179 this->duration = DEFAULT_DURATION_IN_SECS;
180 } else {
181 this->duration = strict_strtoll(duration.c_str(), 10, &this->err_msg);
182 }
183 }
184
185 int AssumeRoleRequestBase::validate_input() const
186 {
187 if (!err_msg.empty()) {
188 return -EINVAL;
189 }
190
191 if (duration < MIN_DURATION_IN_SECS ||
192 duration > MAX_DURATION_IN_SECS) {
193 return -EINVAL;
194 }
195
196 if (! iamPolicy.empty() &&
197 (iamPolicy.size() < MIN_POLICY_SIZE || iamPolicy.size() > MAX_POLICY_SIZE)) {
198 return -ERR_PACKED_POLICY_TOO_LARGE;
199 }
200
201 if (! roleArn.empty() &&
202 (roleArn.size() < MIN_ROLE_ARN_SIZE || roleArn.size() > MAX_ROLE_ARN_SIZE)) {
203 return -EINVAL;
204 }
205
206 if (! roleSessionName.empty()) {
207 if (roleSessionName.size() < MIN_ROLE_SESSION_SIZE || roleSessionName.size() > MAX_ROLE_SESSION_SIZE) {
208 return -EINVAL;
209 }
210
211 std::regex regex_roleSession("[A-Za-z0-9_=,.@-]+");
212 if (! std::regex_match(roleSessionName, regex_roleSession)) {
213 return -EINVAL;
214 }
215 }
216
217 return 0;
218 }
219
220 int AssumeRoleWithWebIdentityRequest::validate_input() const
221 {
222 if (! providerId.empty()) {
223 if (providerId.length() < MIN_PROVIDER_ID_LEN ||
224 providerId.length() > MAX_PROVIDER_ID_LEN) {
225 return -EINVAL;
226 }
227 }
228 return AssumeRoleRequestBase::validate_input();
229 }
230
231 int AssumeRoleRequest::validate_input() const
232 {
233 if (! externalId.empty()) {
234 if (externalId.length() < MIN_EXTERNAL_ID_LEN ||
235 externalId.length() > MAX_EXTERNAL_ID_LEN) {
236 return -EINVAL;
237 }
238
239 std::regex regex_externalId("[A-Za-z0-9_=,.@:/-]+");
240 if (! std::regex_match(externalId, regex_externalId)) {
241 return -EINVAL;
242 }
243 }
244 if (! serialNumber.empty()){
245 if (serialNumber.size() < MIN_SERIAL_NUMBER_SIZE || serialNumber.size() > MAX_SERIAL_NUMBER_SIZE) {
246 return -EINVAL;
247 }
248
249 std::regex regex_serialNumber("[A-Za-z0-9_=/:,.@-]+");
250 if (! std::regex_match(serialNumber, regex_serialNumber)) {
251 return -EINVAL;
252 }
253 }
254 if (! tokenCode.empty() && tokenCode.size() == TOKEN_CODE_SIZE) {
255 return -EINVAL;
256 }
257
258 return AssumeRoleRequestBase::validate_input();
259 }
260
261 std::tuple<int, RGWRole> STSService::getRoleInfo(const string& arn)
262 {
263 if (auto r_arn = rgw::ARN::parse(arn); r_arn) {
264 auto pos = r_arn->resource.find_last_of('/');
265 string roleName = r_arn->resource.substr(pos + 1);
266 RGWRole role(cct, store->getRados()->pctl, roleName, r_arn->account);
267 if (int ret = role.get(); ret < 0) {
268 if (ret == -ENOENT) {
269 ret = -ERR_NO_ROLE_FOUND;
270 }
271 return make_tuple(ret, this->role);
272 } else {
273 this->role = std::move(role);
274 return make_tuple(0, this->role);
275 }
276 } else {
277 return make_tuple(-EINVAL, this->role);
278 }
279 }
280
281 int STSService::storeARN(string& arn)
282 {
283 int ret = 0;
284 RGWUserInfo info;
285 if (ret = rgw_get_user_info_by_uid(store->ctl()->user, user_id, info); ret < 0) {
286 return -ERR_NO_SUCH_ENTITY;
287 }
288
289 info.assumed_role_arn = arn;
290
291 RGWObjVersionTracker objv_tracker;
292 if (ret = rgw_store_user_info(store->ctl()->user, info, &info, &objv_tracker, real_time(),
293 false); ret < 0) {
294 return -ERR_INTERNAL_ERROR;
295 }
296 return ret;
297 }
298
299 AssumeRoleWithWebIdentityResponse STSService::assumeRoleWithWebIdentity(AssumeRoleWithWebIdentityRequest& req)
300 {
301 AssumeRoleWithWebIdentityResponse response;
302 response.assumeRoleResp.packedPolicySize = 0;
303 std::vector<string> token_claims;
304
305 if (req.getProviderId().empty()) {
306 response.providerId = req.getIss();
307 }
308 response.aud = req.getAud();
309 response.sub = req.getSub();
310
311 token_claims.emplace_back(string("iss") + ":" + req.getIss());
312 token_claims.emplace_back(string("aud") + ":" + req.getAud());
313 token_claims.emplace_back(string("sub") + ":" + req.getSub());
314
315 //Get the role info which is being assumed
316 boost::optional<rgw::ARN> r_arn = rgw::ARN::parse(req.getRoleARN());
317 if (r_arn == boost::none) {
318 response.assumeRoleResp.retCode = -EINVAL;
319 return response;
320 }
321
322 string roleId = role.get_id();
323 uint64_t roleMaxSessionDuration = role.get_max_session_duration();
324 req.setMaxDuration(roleMaxSessionDuration);
325
326 //Validate input
327 response.assumeRoleResp.retCode = req.validate_input();
328 if (response.assumeRoleResp.retCode < 0) {
329 return response;
330 }
331
332 //Calculate PackedPolicySize
333 string policy = req.getPolicy();
334 response.assumeRoleResp.packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
335
336 //Generate Assumed Role User
337 response.assumeRoleResp.retCode = response.assumeRoleResp.user.generateAssumedRoleUser(cct,
338 store,
339 roleId,
340 r_arn.get(),
341 req.getRoleSessionName());
342 if (response.assumeRoleResp.retCode < 0) {
343 return response;
344 }
345
346 //Generate Credentials
347 //Role and Policy provide the authorization info, user id and applier info are not needed
348 response.assumeRoleResp.retCode = response.assumeRoleResp.creds.generateCredentials(cct, req.getDuration(),
349 req.getPolicy(), roleId,
350 req.getRoleSessionName(),
351 token_claims,
352 user_id, nullptr);
353 if (response.assumeRoleResp.retCode < 0) {
354 return response;
355 }
356
357 response.assumeRoleResp.retCode = 0;
358 return response;
359 }
360
361 AssumeRoleResponse STSService::assumeRole(AssumeRoleRequest& req)
362 {
363 AssumeRoleResponse response;
364 response.packedPolicySize = 0;
365
366 //Get the role info which is being assumed
367 boost::optional<rgw::ARN> r_arn = rgw::ARN::parse(req.getRoleARN());
368 if (r_arn == boost::none) {
369 response.retCode = -EINVAL;
370 return response;
371 }
372
373 string roleId = role.get_id();
374 uint64_t roleMaxSessionDuration = role.get_max_session_duration();
375 req.setMaxDuration(roleMaxSessionDuration);
376
377 //Validate input
378 response.retCode = req.validate_input();
379 if (response.retCode < 0) {
380 return response;
381 }
382
383 //Calculate PackedPolicySize
384 string policy = req.getPolicy();
385 response.packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
386
387 //Generate Assumed Role User
388 response.retCode = response.user.generateAssumedRoleUser(cct, store, roleId, r_arn.get(), req.getRoleSessionName());
389 if (response.retCode < 0) {
390 return response;
391 }
392
393 //Generate Credentials
394 //Role and Policy provide the authorization info, user id and applier info are not needed
395 response.retCode = response.creds.generateCredentials(cct, req.getDuration(),
396 req.getPolicy(), roleId,
397 req.getRoleSessionName(),
398 boost::none,
399 user_id, nullptr);
400 if (response.retCode < 0) {
401 return response;
402 }
403
404 //Save ARN with the user
405 string arn = response.user.getARN();
406 response.retCode = storeARN(arn);
407 if (response.retCode < 0) {
408 return response;
409 }
410
411 response.retCode = 0;
412 return response;
413 }
414
415 GetSessionTokenRequest::GetSessionTokenRequest(const string& duration, const string& serialNumber, const string& tokenCode)
416 {
417 if (duration.empty()) {
418 this->duration = DEFAULT_DURATION_IN_SECS;
419 } else {
420 this->duration = stoull(duration);
421 }
422 this->serialNumber = serialNumber;
423 this->tokenCode = tokenCode;
424 }
425
426 GetSessionTokenResponse STSService::getSessionToken(GetSessionTokenRequest& req)
427 {
428 int ret;
429 Credentials cred;
430
431 //Generate Credentials
432 if (ret = cred.generateCredentials(cct,
433 req.getDuration(),
434 boost::none,
435 boost::none,
436 boost::none,
437 boost::none,
438 user_id,
439 identity); ret < 0) {
440 return make_tuple(ret, cred);
441 }
442
443 return make_tuple(0, cred);
444 }
445
446 }