OPTION(rgw_cors_rules_max_num, OPT_INT) // According to AWS S3(http://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html), An cors can have up to 100 rules.
OPTION(rgw_delete_multi_obj_max_num, OPT_INT) // According to AWS S3(https://docs.aws.amazon.com/AmazonS3/latest/dev/DeletingObjects.html), Amazon S3 also provides the Multi-Object Delete API that you can use to delete up to 1000 objects in a single HTTP request.
OPTION(rgw_website_routing_rules_max_num, OPT_INT) // According to AWS S3, An website routing config can have up to 50 rules.
+OPTION(rgw_sts_entry, OPT_STR)
"will be located in the path that is specified here. "),
Option("rgw_enable_apis", Option::TYPE_STR, Option::LEVEL_ADVANCED)
- .set_default("s3, s3website, swift, swift_auth, admin")
+ .set_default("s3, s3website, swift, swift_auth, admin, sts")
.set_description("A list of set of RESTful APIs that rgw handles."),
Option("rgw_cache_enabled", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
"increasing this value may cause some operations to "
"take longer in exceptional cases and thus may, "
"rarely, cause clients to time out."),
+ Option("rgw_sts_entry", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+ .set_default("sts")
+ .set_description("STS URL prefix")
+ .set_long_description("URL path prefix for internal STS requests.")
});
}
rgw_swift_auth.cc
rgw_usage.cc
rgw_opa.cc
- sts-assume-role.cc)
+ sts-assume-role.cc
+ rgw_rest_sts.cc)
gperf_generate(${CMAKE_SOURCE_DIR}/src/rgw/rgw_iam_policy_keywords.gperf
rgw_iam_policy_keywords.frag.cc)
{ ERR_ZERO_IN_URL, {412, "Invalid UTF8 or contains NULL"}},
});
+rgw_http_errors rgw_http_sts_errors({
+ { ERR_PACKED_POLICY_TOO_LARGE, {400, "PackedPolicyTooLarge" }},
+});
+
int rgw_perf_start(CephContext *cct)
{
PerfCountersBuilder plb(cct, "rgw", l_rgw_first, l_rgw_last);
return;
}
+ if (prot_flags & RGW_REST_STS) {
+ if (search_err(rgw_http_sts_errors, err_no, err.http_ret, err.err_code))
+ return;
+ }
+
//Default to searching in s3 errors
if (search_err(rgw_http_s3_errors, err_no, err.http_ret, err.err_code))
return;
#define RGW_REST_SWIFT_AUTH 0x2
#define RGW_REST_S3 0x4
#define RGW_REST_WEBSITE 0x8
+#define RGW_REST_STS 0x10
#define RGW_SUSPENDED_USER_AUID (uint64_t)-2
#define ERR_BUSY_RESHARDING 2300
#define ERR_NO_SUCH_ENTITY 2301
+
+// STS Errors
+#define ERR_PACKED_POLICY_TOO_LARGE 2400
+
#ifndef UINT32_MAX
#define UINT32_MAX (0xffffffffu)
#endif
RGW_OP_CONFIG_BUCKET_META_SEARCH,
RGW_OP_GET_BUCKET_META_SEARCH,
RGW_OP_DEL_BUCKET_META_SEARCH,
+ /* sts specific*/
+ RGW_STS_ASSUME_ROLE,
};
class RGWAccessControlPolicy;
extern rgw_http_errors rgw_http_swift_errors;
+extern rgw_http_errors rgw_http_sts_errors;
+
static inline int rgw_http_error_to_errno(int http_err)
{
if (http_err >= 200 && http_err <= 299)
#include "rgw_rest_log.h"
#include "rgw_rest_config.h"
#include "rgw_rest_realm.h"
+#include "rgw_rest_sts.h"
#include "rgw_swift_auth.h"
#include "rgw_log.h"
#include "rgw_tools.h"
rest.register_resource(g_conf()->rgw_admin_entry, admin_resource);
}
+ if (apis_map.count("sts") > 0) {
+ auto *sts = new RGWRESTMgr_STS;
+ rest.register_resource(g_conf()->rgw_sts_entry, set_logging(sts));
+ }
+
/* Initialize the registry of auth strategies which will coordinate
* the dynamic reconfiguration. */
auto auth_registry = \
--- /dev/null
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+#include <boost/optional.hpp>
+#include <boost/utility/in_place_factory.hpp>
+
+#include "include/assert.h"
+#include "ceph_ver.h"
+
+#include "common/Formatter.h"
+#include "common/utf8.h"
+#include "common/ceph_json.h"
+
+#include "rgw_rest.h"
+#include "rgw_auth.h"
+#include "rgw_auth_s3.h"
+#include "rgw_rest_sts.h"
+#include "rgw_formats.h"
+#include "rgw_client_io.h"
+
+#include "rgw_request.h"
+#include "rgw_process.h"
+
+#include "sts-assume-role.h"
+
+#include <array>
+#include <sstream>
+#include <memory>
+
+#include <boost/utility/string_ref.hpp>
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rgw
+
+int RGWREST_STS::verify_permission()
+{
+ return 0;
+}
+
+void RGWREST_STS::send_response()
+{
+ if (op_ret) {
+ set_req_state_err(s, op_ret);
+ }
+ dump_errno(s);
+ end_header(s);
+}
+
+int RGWSTSAssumeRole::get_params()
+{
+ duration = s->info.args.get("DurationSeconds");
+ externalId = s->info.args.get("ExternalId");
+ policy = s->info.args.get("Policy");
+ roleArn = s->info.args.get("RoleArn");
+ roleSessionName = s->info.args.get("RoleSessionName");
+ serialNumber = s->info.args.get("SerialNumber");
+ tokenCode = s->info.args.get("TokenCode");
+
+ if (roleArn.empty() || roleSessionName.empty()) {
+ ldout(s->cct, 20) << "ERROR: one of role arn or role session name is empty" << dendl;
+ return -EINVAL;
+ }
+
+ JSONParser p;
+ if (!p.parse(policy.c_str(), policy.length())) {
+ ldout(s->cct, 20) << "ERROR: failed to parse policy doc" << dendl;
+ return -ERR_MALFORMED_DOC;
+ }
+
+ return 0;
+}
+
+void RGWSTSAssumeRole::execute()
+{
+ STS::AssumeRoleRequest req(duration, externalId, policy, roleArn,
+ roleSessionName, serialNumber, tokenCode);
+ STS::STSService sts(s->cct, store);
+ const auto& [op_ret, assumedRoleUser, creds, packedPolicySize] = sts.assumeRole(req);
+
+ //Dump the output
+ if (op_ret == 0) {
+ s->formatter->open_object_section("AssumeRole");
+ assumedRoleUser.dump(s->formatter);
+ creds.dump(s->formatter);
+ encode_json("PackedPolicySize", packedPolicySize , s->formatter);
+ s->formatter->close_section();
+ }
+}
+
+RGWOp *RGWHandler_REST_STS::op_post()
+{
+ if (s->info.args.exists("Action")) {
+ if (string action = s->info.args.get("Action"); action == "AssumeRole") {
+ return new RGWSTSAssumeRole;
+ }
+ }
+ return nullptr;
+}
+
+int RGWHandler_REST_STS::init(RGWRados *store,
+ struct req_state *s,
+ rgw::io::BasicClient *cio)
+{
+ s->dialect = "sts";
+
+ if (int ret = RGWHandler_REST_STS::init_from_header(s, RGW_FORMAT_JSON, true); ret < 0) {
+ ldout(s->cct, 10) << "init_from_header returned err=" << ret << dendl;
+ return ret;
+ }
+
+ return RGWHandler_REST::init(store, s, cio);
+}
+
+int RGWHandler_REST_STS::init_from_header(struct req_state* s,
+ int default_formatter,
+ bool configurable_format)
+{
+ string req;
+ string first;
+
+ s->prot_flags |= RGW_REST_STS;
+
+ const char *p, *req_name;
+ if (req_name = s->relative_uri.c_str(); *req_name == '?') {
+ p = req_name;
+ } else {
+ p = s->info.request_params.c_str();
+ }
+
+ s->info.args.set(p);
+ s->info.args.parse();
+
+ /* must be called after the args parsing */
+ if (int ret = allocate_formatter(s, default_formatter, configurable_format); ret < 0)
+ return ret;
+
+ if (*req_name != '/')
+ return 0;
+
+ req_name++;
+
+ if (!*req_name)
+ return 0;
+
+ req = req_name;
+ int pos = req.find('/');
+ if (pos >= 0) {
+ first = req.substr(0, pos);
+ } else {
+ first = req;
+ }
+
+ return 0;
+}
+
+RGWHandler_REST*
+RGWRESTMgr_STS::get_handler(struct req_state* const s,
+ const rgw::auth::StrategyRegistry& auth_registry,
+ const std::string& frontend_prefix)
+{
+ return new RGWHandler_REST_STS(auth_registry);
+}
--- /dev/null
+#ifndef CEPH_RGW_REST_STS_H
+#define CEPH_RGW_REST_STS_H
+
+class RGWREST_STS : public RGWRESTOp {
+public:
+ int verify_permission() override;
+ void send_response() override;
+};
+
+class RGWSTSAssumeRole : public RGWREST_STS {
+protected:
+ string duration;
+ string externalId;
+ string policy;
+ string roleArn;
+ string roleSessionName;
+ string serialNumber;
+ string tokenCode;
+public:
+ RGWSTSAssumeRole() = default;
+ void execute() override;
+ int get_params();
+ const string name() override { return "assume_role"; }
+ RGWOpType get_type() override { return RGW_STS_ASSUME_ROLE; }
+};
+
+class RGWHandler_REST_STS : public RGWHandler_REST {
+ const rgw::auth::StrategyRegistry& auth_registry;
+ RGWOp *op_post() override;
+public:
+ static int init_from_header(struct req_state *s, int default_formatter, bool configurable_format);
+
+ RGWHandler_REST_STS(const rgw::auth::StrategyRegistry& auth_registry)
+ : RGWHandler_REST(),
+ auth_registry(auth_registry) {}
+ ~RGWHandler_REST_STS() override = default;
+
+ int init(RGWRados *store,
+ struct req_state *s,
+ rgw::io::BasicClient *cio) override;
+ int authorize() override {
+ return RGW_Auth_S3::authorize(store, auth_registry, s);
+ }
+ int postauth_init() override { return 0; }
+};
+
+class RGWRESTMgr_STS : public RGWRESTMgr {
+public:
+ RGWRESTMgr_STS() = default;
+ ~RGWRESTMgr_STS() override = default;
+
+ RGWRESTMgr *get_resource_mgr(struct req_state* const s,
+ const std::string& uri,
+ std::string* const out_uri) override {
+ return this;
+ }
+
+ RGWHandler_REST* get_handler(struct req_state*,
+ const rgw::auth::StrategyRegistry&,
+ const std::string&) override;
+};
+
+#endif /* CEPH_RGW_REST_STS_H */
+
#define dout_subsys ceph_subsys_rgw
namespace STS {
-
+
+void Credentials::dump(Formatter *f) const
+{
+ encode_json("AccessKeyId", accessKeyId , f);
+ encode_json("Expiration", expiration , f);
+ encode_json("SecretAccessKey", secretAccessKey , f);
+ encode_json("SessionToken", sessionToken , f);
+}
+
int Credentials::generateCredentials(CephContext* cct)
{
uuid_d accessKey, secretKey;
return ret;
}
+void AssumedRoleUser::dump(Formatter *f) const
+{
+ encode_json("Arn", arn , f);
+ encode_json("AssumeRoleId", assumeRoleId , f);
+}
+
int AssumedRoleUser::generateAssumedRoleUser(CephContext* cct,
RGWRados *store,
- const string& roleArn,
+ const string& roleId,
+ const boost::optional<rgw::IAM::ARN>& roleArn,
const string& roleSessionName)
{
- auto r_arn = rgw::IAM::ARN::parse(roleArn);
- string resource = r_arn->resource;
- boost::replace_first(resource, "role", "assumed-role");
- resource.append("/");
- resource.append(roleSessionName);
+ string resource, account;
+ rgw::IAM::ARN r_arn;
+ if (roleArn) {
+ r_arn = roleArn.get();
+ resource = r_arn.resource;
+ boost::replace_first(resource, "role", "assumed-role");
+ resource.append("/");
+ resource.append(roleSessionName);
+ account = r_arn.account;
+ }
rgw::IAM::ARN assumed_role_arn(rgw::IAM::Partition::aws,
rgw::IAM::Service::sts,
- "", r_arn->account, resource);
+ "", account, resource);
arn = assumed_role_arn.to_string();
//Assumeroleid = roleid:rolesessionname
- auto pos = r_arn->resource.find_last_of('/');
- string roleName = r_arn->resource.substr(pos + 1);
- RGWRole role(cct, store, roleName, r_arn->account);
- if (int ret = role.get(); ret < 0) {
- return ret;
+ assumeRoleId = roleId + ":" + roleSessionName;
+
+ return 0;
+}
+
+AssumeRoleRequest::AssumeRoleRequest(string _duration, string _externalId, string _iamPolicy,
+ string _roleArn, string _roleSessionName, string _serialNumber,
+ string _tokenCode)
+ : externalId(_externalId), iamPolicy(_iamPolicy),
+ roleArn(_roleArn), roleSessionName(_roleSessionName),
+ serialNumber(_serialNumber), tokenCode(_tokenCode)
+{
+ if (_duration.empty()) {
+ duration = DEFAULT_DURATION_IN_SECS;
+ } else {
+ duration = std::stoull(_duration);
+ }
+}
+
+int AssumeRoleRequest::validate_input() const
+{
+ if (duration < MIN_DURATION_IN_SECS) {
+ return -EINVAL;
+ }
+
+ if (! externalId.empty()) {
+ if (externalId.length() < MIN_EXTERNAL_ID_LEN ||
+ externalId.length() > MAX_EXTERNAL_ID_LEN) {
+ return -EINVAL;
+ }
+
+ std::regex regex_externalId("[A-Za-z0-9_+=,.@:/-");
+ if (! std::regex_match(externalId, regex_externalId)) {
+ return -EINVAL;
+ }
+ }
+ if (! iamPolicy.empty() &&
+ (iamPolicy.size() < MIN_POLICY_SIZE || iamPolicy.size() > MAX_POLICY_SIZE)) {
+ return -ERR_PACKED_POLICY_TOO_LARGE;
+ }
+
+ if (! roleArn.empty() &&
+ (roleArn.size() < MIN_ROLE_ARN_SIZE || roleArn.size() > MAX_ROLE_ARN_SIZE)) {
+ return -EINVAL;
+ }
+
+ if (! roleSessionName.empty()) {
+ if (roleSessionName.size() < MIN_ROLE_SESSION_SIZE || roleSessionName.size() > MAX_ROLE_SESSION_SIZE) {
+ return -EINVAL;
+ }
+
+ std::regex regex_roleSession("[A-Za-z0-9_+=,.@-");
+ if (! std::regex_match(roleSessionName, regex_roleSession)) {
+ return -EINVAL;
+ }
+ }
+ if (! serialNumber.empty()){
+ if (serialNumber.size() < MIN_SERIAL_NUMBER_SIZE || serialNumber.size() > MAX_SERIAL_NUMBER_SIZE) {
+ return -EINVAL;
+ }
+
+ std::regex regex_serialNumber("[A-Za-z0-9_=/:,.@-");
+ if (! std::regex_match(serialNumber, regex_serialNumber)) {
+ return -EINVAL;
+ }
+ }
+ if (! tokenCode.empty() && tokenCode.size() == TOKEN_CODE_SIZE) {
+ return -EINVAL;
}
- assumeRoleId = role.get_id() + ":" + roleSessionName;
return 0;
}
AssumeRoleResponse STSService::assumeRole(const AssumeRoleRequest& req)
{
+ int ret = 0;
+ uint64_t packedPolicySize = 0;
AssumedRoleUser user;
- user.generateAssumedRoleUser(cct, store, req.getRoleARN(), req.getRoleSessionName());
-
Credentials cred;
- cred.generateCredentials(cct);
+ string roleId;
+
+ //Get the role info which is being assumed
+ auto r_arn = rgw::IAM::ARN::parse(req.getRoleARN());
+ if (r_arn) {
+ auto pos = r_arn->resource.find_last_of('/');
+ string roleName = r_arn->resource.substr(pos + 1);
+ RGWRole role(cct, store, roleName, r_arn->account);
+ if (ret = role.get(); ret < 0) {
+ return make_tuple(ret, user, cred, packedPolicySize);
+ }
+ roleId = role.get_id();
+ } else {
+ return make_tuple(-EINVAL, user, cred, packedPolicySize);
+ }
+
+ //Validate input
+ if (ret = req.validate_input(); ret < 0) {
+ return make_tuple(ret, user, cred, packedPolicySize);
+ }
+ //Calculate PackedPolicySize
string policy = req.getPolicy();
- uint64_t packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
- return make_tuple(user, cred, packedPolicySize);
+ packedPolicySize = (policy.size() / req.getMaxPolicySize()) * 100;
+
+ //Generate Assumed Role User
+ if (ret = user.generateAssumedRoleUser(cct, store, roleId, r_arn, req.getRoleSessionName()); ret < 0) {
+ return make_tuple(ret, user, cred, packedPolicySize);
+ }
+
+ //Generate Credentials
+ if (ret = cred.generateCredentials(cct); ret < 0) {
+ return make_tuple(ret, user, cred, packedPolicySize);
+ }
+
+ return make_tuple(0, user, cred, packedPolicySize);
}
}
\ No newline at end of file
namespace STS {
class AssumeRoleRequest {
- static constexpr int MAX_POLICY_SIZE = 2048;
+ static constexpr uint64_t MIN_POLICY_SIZE = 1;
+ static constexpr uint64_t MAX_POLICY_SIZE = 2048;
+ static constexpr uint64_t DEFAULT_DURATION_IN_SECS = 3600;
+ static constexpr uint64_t MIN_DURATION_IN_SECS = 900;
+ static constexpr uint64_t MIN_EXTERNAL_ID_LEN = 2;
+ static constexpr uint64_t MAX_EXTERNAL_ID_LEN = 1224;
+ static constexpr uint64_t MIN_ROLE_ARN_SIZE = 2;
+ static constexpr uint64_t MAX_ROLE_ARN_SIZE = 2048;
+ static constexpr uint64_t MIN_ROLE_SESSION_SIZE = 2;
+ static constexpr uint64_t MAX_ROLE_SESSION_SIZE = 64;
+ static constexpr uint64_t MIN_SERIAL_NUMBER_SIZE = 9;
+ static constexpr uint64_t MAX_SERIAL_NUMBER_SIZE = 256;
+ static constexpr uint64_t TOKEN_CODE_SIZE = 6;
uint64_t duration;
string externalId;
string iamPolicy;
string serialNumber;
string tokenCode;
public:
- AssumeRoleRequest(uint64_t _duration, string _externalId, string _iamPolicy,
- string _roleArn, string _roleSessionName, string _serialNumber,
- string _tokenCode)
- : duration(_duration), externalId(_externalId), iamPolicy(_iamPolicy),
- roleArn(_roleArn), roleSessionName(_roleSessionName),
- serialNumber(_serialNumber), tokenCode(_tokenCode) {}
- string getRoleARN() const { return roleArn; }
- string getRoleSessionName() const { return roleSessionName; }
- string getPolicy() const {return iamPolicy; }
- int getMaxPolicySize() const { return MAX_POLICY_SIZE; }
+ AssumeRoleRequest( string _duration,
+ string _externalId,
+ string _iamPolicy,
+ string _roleArn,
+ string _roleSessionName,
+ string _serialNumber,
+ string _tokenCode);
+ const string& getRoleARN() const { return roleArn; }
+ const string& getRoleSessionName() const { return roleSessionName; }
+ const string& getPolicy() const {return iamPolicy; }
+ static uint64_t getMaxPolicySize() { return MAX_POLICY_SIZE; }
+ int validate_input() const;
};
public:
int generateAssumedRoleUser( CephContext* cct,
RGWRados *store,
- const string& roleArn,
+ const string& roleId,
+ const boost::optional<rgw::IAM::ARN>& roleArn,
const string& roleSessionName);
- string getARN() const { return arn; }
- string getAssumeRoleId() const { return assumeRoleId; }
+ const string& getARN() const { return arn; }
+ const string& getAssumeRoleId() const { return assumeRoleId; }
+ void dump(Formatter *f) const;
};
class Credentials {
string sessionToken;
public:
int generateCredentials(CephContext* cct);
- string getAccessKeyId() const { return accessKeyId; }
- string getExpiration() const { return expiration; }
- string getSecretAccessKey() const { return secretAccessKey; }
- string getSessionToken() const { return sessionToken; }
+ const string& getAccessKeyId() const { return accessKeyId; }
+ const string& getExpiration() const { return expiration; }
+ const string& getSecretAccessKey() const { return secretAccessKey; }
+ const string& getSessionToken() const { return sessionToken; }
+ void dump(Formatter *f) const;
};
-using AssumeRoleResponse = std::tuple<AssumedRoleUser, Credentials, uint64_t> ;
+//AssumedRoleUser, Credentials, PackedpolicySize
+using AssumeRoleResponse = std::tuple<int, AssumedRoleUser, Credentials, uint64_t> ;
class STSService {
CephContext* cct;