From: Yuval Lifshitz Date: Thu, 8 Dec 2022 18:22:59 +0000 (+0000) Subject: rgw: refactor service handlers X-Git-Tag: v18.1.0~334^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=fbbc52aecf3548706b8d5ff49a57200aebc03d54;p=ceph.git rgw: refactor service handlers seperate between the different non-bucket handler operations: iam, sts, sns (topic) and non bucket s3 ops Signed-off-by: Yuval Lifshitz --- diff --git a/src/rgw/driver/rados/rgw_rest_pubsub.cc b/src/rgw/driver/rados/rgw_rest_pubsub.cc index 23d56615ac95..86c18bf74397 100644 --- a/src/rgw/driver/rados/rgw_rest_pubsub.cc +++ b/src/rgw/driver/rados/rgw_rest_pubsub.cc @@ -473,135 +473,39 @@ void RGWPSDeleteTopicOp::execute(optional_yield y) { ldpp_dout(this, 1) << "successfully removed topic '" << topic_name << "'" << dendl; } -namespace { -// utility classes and functions for handling parameters with the following format: -// Attributes.entry.{N}.{key|value}={VALUE} -// N - any unsigned number -// VALUE - url encoded string - -// and Attribute is holding key and value -// ctor and set are done according to the "type" argument -// if type is not "key" or "value" its a no-op -class Attribute { - std::string key; - std::string value; -public: - Attribute(const std::string& type, const std::string& key_or_value) { - set(type, key_or_value); - } - void set(const std::string& type, const std::string& key_or_value) { - if (type == "key") { - key = key_or_value; - } else if (type == "value") { - value = key_or_value; - } - } - const std::string& get_key() const { return key; } - const std::string& get_value() const { return value; } +using op_generator = RGWOp*(*)(); +static const std::unordered_map op_generators = { + {"CreateTopic", []() -> RGWOp* {return new RGWPSCreateTopicOp;}}, + {"DeleteTopic", []() -> RGWOp* {return new RGWPSDeleteTopicOp;}}, + {"ListTopics", []() -> RGWOp* {return new RGWPSListTopicsOp;}}, + {"GetTopic", []() -> RGWOp* {return new RGWPSGetTopicOp;}}, + {"GetTopicAttributes", []() -> RGWOp* {return new RGWPSGetTopicAttributesOp;}} }; -using AttributeMap = std::map; - -// aggregate the attributes into a map -// the key and value are associated by the index (N) -// no assumptions are made on the order in which these parameters are added -void update_attribute_map(const std::string& input, AttributeMap& map) { - const boost::char_separator sep("."); - const boost::tokenizer tokens(input, sep); - auto token = tokens.begin(); - if (*token != "Attributes") { - return; - } - ++token; - - if (*token != "entry") { - return; - } - ++token; - - unsigned idx; - try { - idx = std::stoul(*token); - } catch (const std::invalid_argument&) { - return; - } - ++token; - - std::string key_or_value = ""; - // get the rest of the string regardless of dots - // this is to allow dots in the value - while (token != tokens.end()) { - key_or_value.append(*token+"."); - ++token; - } - // remove last separator - key_or_value.pop_back(); - - auto pos = key_or_value.find("="); - if (pos != std::string::npos) { - const auto key_or_value_lhs = key_or_value.substr(0, pos); - const auto key_or_value_rhs = url_decode(key_or_value.substr(pos + 1, key_or_value.size() - 1)); - const auto map_it = map.find(idx); - if (map_it == map.end()) { - // new entry - map.emplace(std::make_pair(idx, Attribute(key_or_value_lhs, key_or_value_rhs))); - } else { - // existing entry - map_it->second.set(key_or_value_lhs, key_or_value_rhs); - } - } -} -} - -void RGWHandler_REST_PSTopic_AWS::rgw_topic_parse_input() { - if (post_body.size() > 0) { - ldpp_dout(s, 10) << "Content of POST: " << post_body << dendl; - - if (post_body.find("Action") != std::string::npos) { - const boost::char_separator sep("&"); - const boost::tokenizer> tokens(post_body, sep); - AttributeMap map; - for (const auto& t : tokens) { - auto pos = t.find("="); - if (pos != std::string::npos) { - const auto key = t.substr(0, pos); - if (key == "Action") { - s->info.args.append(key, t.substr(pos + 1, t.size() - 1)); - } else if (key == "Name" || key == "TopicArn") { - const auto value = url_decode(t.substr(pos + 1, t.size() - 1)); - s->info.args.append(key, value); - } else { - update_attribute_map(t, map); - } - } - } - // update the regular args with the content of the attribute map - for (const auto& attr : map) { - s->info.args.append(attr.second.get_key(), attr.second.get_value()); - } - } - const auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); - s->info.args.append("PayloadHash", payload_hash); +bool RGWHandler_REST_PSTopic_AWS::action_exists(const req_state* s) +{ + if (s->info.args.exists("Action")) { + const std::string action_name = s->info.args.get("Action"); + return op_generators.contains(action_name); } + return false; } -RGWOp* RGWHandler_REST_PSTopic_AWS::op_post() { - rgw_topic_parse_input(); +RGWOp *RGWHandler_REST_PSTopic_AWS::op_post() +{ + s->dialect = "sns"; + s->prot_flags = RGW_REST_STS; if (s->info.args.exists("Action")) { - const auto action = s->info.args.get("Action"); - if (action.compare("CreateTopic") == 0) - return new RGWPSCreateTopicOp(); - if (action.compare("DeleteTopic") == 0) - return new RGWPSDeleteTopicOp; - if (action.compare("ListTopics") == 0) - return new RGWPSListTopicsOp(); - if (action.compare("GetTopic") == 0) - return new RGWPSGetTopicOp(); - if (action.compare("GetTopicAttributes") == 0) - return new RGWPSGetTopicAttributesOp(); + const std::string action_name = s->info.args.get("Action"); + const auto action_it = op_generators.find(action_name); + if (action_it != op_generators.end()) { + return action_it->second(); + } + ldpp_dout(s, 10) << "unknown action '" << action_name << "' for Topic handler" << dendl; + } else { + ldpp_dout(s, 10) << "missing action argument in Topic handler" << dendl; } - return nullptr; } diff --git a/src/rgw/driver/rados/rgw_rest_pubsub.h b/src/rgw/driver/rados/rgw_rest_pubsub.h index 3b1a1bc9670b..27bde7a95d5a 100644 --- a/src/rgw/driver/rados/rgw_rest_pubsub.h +++ b/src/rgw/driver/rados/rgw_rest_pubsub.h @@ -25,15 +25,14 @@ public: // AWS compliant topics handler factory class RGWHandler_REST_PSTopic_AWS : public RGWHandler_REST { const rgw::auth::StrategyRegistry& auth_registry; - const std::string& post_body; - void rgw_topic_parse_input(); protected: RGWOp* op_post() override; public: - RGWHandler_REST_PSTopic_AWS(const rgw::auth::StrategyRegistry& _auth_registry, const std::string& _post_body) : - auth_registry(_auth_registry), - post_body(_post_body) {} + RGWHandler_REST_PSTopic_AWS(const rgw::auth::StrategyRegistry& _auth_registry) : + auth_registry(_auth_registry) {} virtual ~RGWHandler_REST_PSTopic_AWS() = default; int postauth_init(optional_yield) override { return 0; } int authorize(const DoutPrefixProvider* dpp, optional_yield y) override; + static bool action_exists(const req_state* s); }; + diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 31aa6f92a188..c9a1c0fcdc78 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -1144,7 +1144,8 @@ bool verify_user_permission(const DoutPrefixProvider* dpp, const vector& user_policies, const vector& session_policies, const rgw::ARN& res, - const uint64_t op) + const uint64_t op, + bool mandatory_policy) { auto identity_policy_res = eval_identity_or_session_policies(dpp, user_policies, s->env, op, res); if (identity_policy_res == Effect::Deny) { @@ -1167,13 +1168,15 @@ bool verify_user_permission(const DoutPrefixProvider* dpp, return true; } - if (op == rgw::IAM::s3CreateBucket || op == rgw::IAM::s3ListAllMyBuckets) { - auto perm = op_to_perm(op); - - return verify_user_permission_no_policy(dpp, s, user_acl, perm); + if (mandatory_policy) { + // no policies, and policy is mandatory + ldpp_dout(dpp, 20) << "no policies for a policy mandatory op " << op << dendl; + return false; } - return false; + auto perm = op_to_perm(op); + + return verify_user_permission_no_policy(dpp, s, user_acl, perm); } bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, @@ -1197,10 +1200,11 @@ bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, bool verify_user_permission(const DoutPrefixProvider* dpp, req_state * const s, const rgw::ARN& res, - const uint64_t op) + const uint64_t op, + bool mandatory_policy) { perm_state_from_req_state ps(s); - return verify_user_permission(dpp, &ps, s->user_acl.get(), s->iam_user_policies, s->session_policies, res, op); + return verify_user_permission(dpp, &ps, s->user_acl.get(), s->iam_user_policies, s->session_policies, res, op, mandatory_policy); } bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 3a5f2cd8824d..a8851f2ebb70 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -198,6 +198,7 @@ static inline const char* to_mime_type(const RGWFormat f) #define RGW_REST_WEBSITE 0x8 #define RGW_REST_STS 0x10 #define RGW_REST_IAM 0x20 +#define RGW_REST_SNS 0x30 #define RGW_SUSPENDED_USER_AUID (uint64_t)-2 @@ -1582,7 +1583,8 @@ bool verify_user_permission(const DoutPrefixProvider* dpp, const std::vector& user_policies, const std::vector& session_policies, const rgw::ARN& res, - const uint64_t op); + const uint64_t op, + bool mandatory_policy=true); bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, req_state * const s, RGWAccessControlPolicy * const user_acl, @@ -1590,7 +1592,8 @@ bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, bool verify_user_permission(const DoutPrefixProvider* dpp, req_state * const s, const rgw::ARN& res, - const uint64_t op); + const uint64_t op, + bool mandatory_policy=true); bool verify_user_permission_no_policy(const DoutPrefixProvider* dpp, req_state * const s, int perm); diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index 5bb0d3988225..5527c1c371a7 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -2396,7 +2396,7 @@ int RGWListBuckets::verify_permission(optional_yield y) tenant = s->user->get_tenant(); } - if (!verify_user_permission(this, s, ARN(partition, service, "", tenant, "*"), rgw::IAM::s3ListAllMyBuckets)) { + if (!verify_user_permission(this, s, ARN(partition, service, "", tenant, "*"), rgw::IAM::s3ListAllMyBuckets, false)) { return -EACCES; } @@ -3015,7 +3015,7 @@ int RGWCreateBucket::verify_permission(optional_yield y) bucket.name = s->bucket_name; bucket.tenant = s->bucket_tenant; ARN arn = ARN(bucket); - if (!verify_user_permission(this, s, arn, rgw::IAM::s3CreateBucket)) { + if (!verify_user_permission(this, s, arn, rgw::IAM::s3CreateBucket, false)) { return -EACCES; } diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index d0ff70b3132d..941f176234b1 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -183,6 +183,7 @@ protected: RGWQuota quota; int op_ret; int do_aws4_auth_completion(); + bool init_called = false; virtual int init_quota(); @@ -234,7 +235,9 @@ public: } virtual void init(rgw::sal::Driver* driver, req_state *s, RGWHandler *dialect_handler) { + if (init_called) return; this->driver = driver; + init_called = true; this->s = s; this->dialect_handler = dialect_handler; } diff --git a/src/rgw/rgw_rest.cc b/src/rgw/rgw_rest.cc index af5a40cc4718..54a39a9a549a 100644 --- a/src/rgw/rgw_rest.cc +++ b/src/rgw/rgw_rest.cc @@ -2324,6 +2324,9 @@ RGWHandler_REST* RGWREST::get_handler( *init_error = -ERR_METHOD_NOT_ALLOWED; return NULL; } + + ldpp_dout(s, 20) << __func__ << " handler=" << typeid(*handler).name() << dendl; + *init_error = handler->init(driver, s, rio); if (*init_error < 0) { m->put_handler(handler); diff --git a/src/rgw/rgw_rest_iam.cc b/src/rgw/rgw_rest_iam.cc index 9560e04c82bb..b9e8779c10a4 100644 --- a/src/rgw/rgw_rest_iam.cc +++ b/src/rgw/rgw_rest_iam.cc @@ -15,78 +15,52 @@ using namespace std; -void RGWHandler_REST_IAM::rgw_iam_parse_input() +using op_generator = RGWOp*(*)(const bufferlist&); +static const std::unordered_map op_generators = { + {"CreateRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWCreateRole(bl_post_body);}}, + {"DeleteRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWDeleteRole(bl_post_body);}}, + {"GetRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetRole;}}, + {"UpdateAssumeRolePolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWModifyRoleTrustPolicy(bl_post_body);}}, + {"ListRoles", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListRoles;}}, + {"PutRolePolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWPutRolePolicy(bl_post_body);}}, + {"GetRolePolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetRolePolicy;}}, + {"ListRolePolicies", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListRolePolicies;}}, + {"DeleteRolePolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWDeleteRolePolicy(bl_post_body);}}, + {"PutUserPolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWPutUserPolicy;}}, + {"GetUserPolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetUserPolicy;}}, + {"ListUserPolicies", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListUserPolicies;}}, + {"DeleteUserPolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWDeleteUserPolicy;}}, + {"CreateOpenIDConnectProvider", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWCreateOIDCProvider;}}, + {"ListOpenIDConnectProviders", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListOIDCProviders;}}, + {"GetOpenIDConnectProvider", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetOIDCProvider;}}, + {"DeleteOpenIDConnectProvider", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWDeleteOIDCProvider;}}, + {"TagRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWTagRole(bl_post_body);}}, + {"ListRoleTags", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListRoleTags;}}, + {"UntagRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWUntagRole(bl_post_body);}}, + {"UpdateRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWUpdateRole(bl_post_body);}} +}; + +bool RGWHandler_REST_IAM::action_exists(const req_state* s) { - std::string post_body = bl_post_body.to_str(); - if (post_body.size() > 0) { - ldpp_dout(s, 10) << "Content of POST: " << post_body << dendl; - - if (post_body.find("Action") != string::npos) { - boost::char_separator sep("&"); - boost::tokenizer> tokens(post_body, sep); - for (const auto& t : tokens) { - auto pos = t.find("="); - if (pos != string::npos) { - s->info.args.append(t.substr(0,pos), - url_decode(t.substr(pos+1, t.size() -1))); - } - } - } + if (s->info.args.exists("Action")) { + const std::string action_name = s->info.args.get("Action"); + return op_generators.contains(action_name); } - auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); - s->info.args.append("PayloadHash", payload_hash); + return false; } RGWOp *RGWHandler_REST_IAM::op_post() { - rgw_iam_parse_input(); - if (s->info.args.exists("Action")) { - string action = s->info.args.get("Action"); - if (action.compare("CreateRole") == 0) - return new RGWCreateRole(this->bl_post_body); - if (action.compare("DeleteRole") == 0) - return new RGWDeleteRole(this->bl_post_body); - if (action.compare("GetRole") == 0) - return new RGWGetRole; - if (action.compare("UpdateAssumeRolePolicy") == 0) - return new RGWModifyRoleTrustPolicy(this->bl_post_body); - if (action.compare("ListRoles") == 0) - return new RGWListRoles; - if (action.compare("PutRolePolicy") == 0) - return new RGWPutRolePolicy(this->bl_post_body); - if (action.compare("GetRolePolicy") == 0) - return new RGWGetRolePolicy; - if (action.compare("ListRolePolicies") == 0) - return new RGWListRolePolicies; - if (action.compare("DeleteRolePolicy") == 0) - return new RGWDeleteRolePolicy(this->bl_post_body); - if (action.compare("PutUserPolicy") == 0) - return new RGWPutUserPolicy; - if (action.compare("GetUserPolicy") == 0) - return new RGWGetUserPolicy; - if (action.compare("ListUserPolicies") == 0) - return new RGWListUserPolicies; - if (action.compare("DeleteUserPolicy") == 0) - return new RGWDeleteUserPolicy; - if (action.compare("CreateOpenIDConnectProvider") == 0) - return new RGWCreateOIDCProvider; - if (action.compare("ListOpenIDConnectProviders") == 0) - return new RGWListOIDCProviders; - if (action.compare("GetOpenIDConnectProvider") == 0) - return new RGWGetOIDCProvider; - if (action.compare("DeleteOpenIDConnectProvider") == 0) - return new RGWDeleteOIDCProvider; - if (action.compare("TagRole") == 0) - return new RGWTagRole(this->bl_post_body); - if (action.compare("ListRoleTags") == 0) - return new RGWListRoleTags; - if (action.compare("UntagRole") == 0) - return new RGWUntagRole(this->bl_post_body); - if (action.compare("UpdateRole") == 0) - return new RGWUpdateRole(this->bl_post_body); + const std::string action_name = s->info.args.get("Action"); + const auto action_it = op_generators.find(action_name); + if (action_it != op_generators.end()) { + return action_it->second(bl_post_body); + } + ldpp_dout(s, 10) << "unknown action '" << action_name << "' for IAM handler" << dendl; + } else { + ldpp_dout(s, 10) << "missing action argument in IAM handler" << dendl; } - return nullptr; } @@ -95,11 +69,7 @@ int RGWHandler_REST_IAM::init(rgw::sal::Driver* driver, rgw::io::BasicClient *cio) { s->dialect = "iam"; - - if (int ret = RGWHandler_REST_IAM::init_from_header(s, RGWFormat::XML, true); ret < 0) { - ldpp_dout(s, 10) << "init_from_header returned err=" << ret << dendl; - return ret; - } + s->prot_flags = RGW_REST_IAM; return RGWHandler_REST::init(driver, s, cio); } @@ -109,48 +79,6 @@ int RGWHandler_REST_IAM::authorize(const DoutPrefixProvider* dpp, optional_yield return RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y); } -int RGWHandler_REST_IAM::init_from_header(req_state* s, - RGWFormat default_formatter, - bool configurable_format) -{ - string req; - string first; - - s->prot_flags = RGW_REST_IAM; - - 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(s); - - /* 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_IAM::get_handler(rgw::sal::Driver* driver, req_state* const s, diff --git a/src/rgw/rgw_rest_iam.h b/src/rgw/rgw_rest_iam.h index 1a25362f4ae2..3e579ab35ce7 100644 --- a/src/rgw/rgw_rest_iam.h +++ b/src/rgw/rgw_rest_iam.h @@ -11,10 +11,10 @@ class RGWHandler_REST_IAM : public RGWHandler_REST { const rgw::auth::StrategyRegistry& auth_registry; bufferlist bl_post_body; RGWOp *op_post() override; - void rgw_iam_parse_input(); + public: - static int init_from_header(req_state *s, RGWFormat default_formatter, bool configurable_format); + static bool action_exists(const req_state* s); RGWHandler_REST_IAM(const rgw::auth::StrategyRegistry& auth_registry, bufferlist& bl_post_body) diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 932ab0226b96..fe14ca4269fa 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -16,6 +16,7 @@ #include "auth/Crypto.h" #include #include +#include #include #define BOOST_BIND_GLOBAL_PLACEHOLDERS #ifdef HAVE_WARN_IMPLICIT_CONST_INT_FLOAT_CONVERSION @@ -4479,49 +4480,6 @@ RGWOp *RGWHandler_REST_Service_S3::op_head() return new RGWListBuckets_ObjStore_S3; } -RGWOp *RGWHandler_REST_Service_S3::op_post() -{ - const auto max_size = s->cct->_conf->rgw_max_put_param_size; - - int ret; - bufferlist data; - std::tie(ret, data) = rgw_rest_read_all_input(s, max_size, false); - if (ret < 0) { - return nullptr; - } - - const auto post_body = data.to_str(); - - if (isSTSEnabled) { - RGWHandler_REST_STS sts_handler(auth_registry, post_body); - sts_handler.init(driver, s, s->cio); - auto op = sts_handler.get_op(); - if (op) { - return op; - } - } - - if (isIAMEnabled) { - RGWHandler_REST_IAM iam_handler(auth_registry, data); - iam_handler.init(driver, s, s->cio); - auto op = iam_handler.get_op(); - if (op) { - return op; - } - } - - if (isPSEnabled) { - RGWHandler_REST_PSTopic_AWS topic_handler(auth_registry, post_body); - topic_handler.init(driver, s, s->cio); - auto op = topic_handler.get_op(); - if (op) { - return op; - } - } - - return nullptr; -} - RGWOp *RGWHandler_REST_Bucket_S3::get_obj_op(bool get_data) const { // Non-website mode @@ -5065,6 +5023,117 @@ int RGWHandler_Auth_S3::init(rgw::sal::Driver* driver, req_state *state, return RGWHandler_REST::init(driver, state, cio); } +namespace { +// utility classes and functions for handling parameters with the following format: +// Attributes.entry.{N}.{key|value}={VALUE} +// N - any unsigned number +// VALUE - url encoded string + +// and Attribute is holding key and value +// ctor and set are done according to the "type" argument +// if type is not "key" or "value" its a no-op +class Attribute { + std::string key; + std::string value; +public: + Attribute(const std::string& type, const std::string& key_or_value) { + set(type, key_or_value); + } + void set(const std::string& type, const std::string& key_or_value) { + if (type == "key") { + key = key_or_value; + } else if (type == "value") { + value = key_or_value; + } + } + const std::string& get_key() const { return key; } + const std::string& get_value() const { return value; } +}; + +using AttributeMap = std::map; + +// aggregate the attributes into a map +// the key and value are associated by the index (N) +// no assumptions are made on the order in which these parameters are added +void update_attribute_map(const std::string& input, AttributeMap& map) { + const boost::char_separator sep("."); + const boost::tokenizer tokens(input, sep); + auto token = tokens.begin(); + if (*token != "Attributes") { + return; + } + ++token; + + if (*token != "entry") { + return; + } + ++token; + + unsigned idx; + try { + idx = std::stoul(*token); + } catch (const std::invalid_argument&) { + return; + } + ++token; + + std::string key_or_value = ""; + // get the rest of the string regardless of dots + // this is to allow dots in the value + while (token != tokens.end()) { + key_or_value.append(*token+"."); + ++token; + } + // remove last separator + key_or_value.pop_back(); + + auto pos = key_or_value.find("="); + if (pos != std::string::npos) { + const auto key_or_value_lhs = key_or_value.substr(0, pos); + const auto key_or_value_rhs = url_decode(key_or_value.substr(pos + 1, key_or_value.size() - 1)); + const auto map_it = map.find(idx); + if (map_it == map.end()) { + // new entry + map.emplace(std::make_pair(idx, Attribute(key_or_value_lhs, key_or_value_rhs))); + } else { + // existing entry + map_it->second.set(key_or_value_lhs, key_or_value_rhs); + } + } +} +} + +void parse_post_action(const std::string& post_body, req_state* s) +{ + if (post_body.size() > 0) { + ldpp_dout(s, 10) << "Content of POST: " << post_body << dendl; + + if (post_body.find("Action") != string::npos) { + const boost::char_separator sep("&"); + const boost::tokenizer> tokens(post_body, sep); + AttributeMap map; + for (const auto& t : tokens) { + const auto pos = t.find("="); + if (pos != string::npos) { + const auto key = t.substr(0, pos); + if (boost::starts_with(key, "Attributes.")) { + update_attribute_map(t, map); + } else { + s->info.args.append(t.substr(0, pos), + url_decode(t.substr(pos+1, t.size() -1))); + } + } + } + // update the regular args with the content of the attribute map + for (const auto& attr : map) { + s->info.args.append(attr.second.get_key(), attr.second.get_value()); + } + } + } + const auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); + s->info.args.append("PayloadHash", payload_hash); +} + RGWHandler_REST* RGWRESTMgr_S3::get_handler(rgw::sal::Driver* driver, req_state* const s, const rgw::auth::StrategyRegistry& auth_registry, @@ -5075,34 +5144,55 @@ RGWHandler_REST* RGWRESTMgr_S3::get_handler(rgw::sal::Driver* driver, RGWHandler_REST_S3::init_from_header(driver, s, is_s3website ? RGWFormat::HTML : RGWFormat::XML, true); - if (ret < 0) - return NULL; + if (ret < 0) { + return nullptr; + } - RGWHandler_REST* handler; - // TODO: Make this more readable if (is_s3website) { if (s->init_state.url_bucket.empty()) { - handler = new RGWHandler_REST_Service_S3Website(auth_registry); - } else if (rgw::sal::Object::empty(s->object.get())) { - handler = new RGWHandler_REST_Bucket_S3Website(auth_registry); - } else { - handler = new RGWHandler_REST_Obj_S3Website(auth_registry); + return new RGWHandler_REST_Service_S3Website(auth_registry); } - } else { - if (s->init_state.url_bucket.empty()) { - handler = new RGWHandler_REST_Service_S3(auth_registry, enable_sts, enable_iam, enable_pubsub); - } else if (!rgw::sal::Object::empty(s->object.get())) { - handler = new RGWHandler_REST_Obj_S3(auth_registry); - } else if (s->info.args.exist_obj_excl_sub_resource()) { - return NULL; - } else { - handler = new RGWHandler_REST_Bucket_S3(auth_registry, enable_pubsub); + if (rgw::sal::Object::empty(s->object.get())) { + return new RGWHandler_REST_Bucket_S3Website(auth_registry); } + return new RGWHandler_REST_Obj_S3Website(auth_registry); } - ldpp_dout(s, 20) << __func__ << " handler=" << typeid(*handler).name() - << dendl; - return handler; + if (s->init_state.url_bucket.empty()) { + // no bucket + if (s->op == OP_POST) { + // POST will be one of: IAM, STS or topic service + const auto max_size = s->cct->_conf->rgw_max_put_param_size; + int ret; + bufferlist data; + std::tie(ret, data) = rgw_rest_read_all_input(s, max_size, false); + if (ret < 0) { + return nullptr; + } + parse_post_action(data.to_str(), s); + if (enable_sts && RGWHandler_REST_STS::action_exists(s)) { + return new RGWHandler_REST_STS(auth_registry); + } + if (enable_iam && RGWHandler_REST_IAM::action_exists(s)) { + return new RGWHandler_REST_IAM(auth_registry, data); + } + if (enable_pubsub && RGWHandler_REST_PSTopic_AWS::action_exists(s)) { + return new RGWHandler_REST_PSTopic_AWS(auth_registry); + } + return nullptr; + } + // non-POST S3 service without a bucket + return new RGWHandler_REST_Service_S3(auth_registry); + } + if (!rgw::sal::Object::empty(s->object.get())) { + // has object + return new RGWHandler_REST_Obj_S3(auth_registry); + } + if (s->info.args.exist_obj_excl_sub_resource()) { + return nullptr; + } + // has bucket + return new RGWHandler_REST_Bucket_S3(auth_registry, enable_pubsub); } bool RGWHandler_REST_S3Website::web_dir() const { diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 7c1829aa7eb7..f1b95c01e20a 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -668,19 +668,14 @@ public: class RGWHandler_REST_Service_S3 : public RGWHandler_REST_S3 { protected: - const bool isSTSEnabled; - const bool isIAMEnabled; - const bool isPSEnabled; bool is_usage_op() const { return s->info.args.exists("usage"); } RGWOp *op_get() override; RGWOp *op_head() override; - RGWOp *op_post() override; public: - RGWHandler_REST_Service_S3(const rgw::auth::StrategyRegistry& auth_registry, - bool _isSTSEnabled, bool _isIAMEnabled, bool _isPSEnabled) : - RGWHandler_REST_S3(auth_registry), isSTSEnabled(_isSTSEnabled), isIAMEnabled(_isIAMEnabled), isPSEnabled(_isPSEnabled) {} + RGWHandler_REST_Service_S3(const rgw::auth::StrategyRegistry& auth_registry) : + RGWHandler_REST_S3(auth_registry) {} ~RGWHandler_REST_Service_S3() override = default; }; diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index b77b78c91019..8e70faa99f4e 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -754,42 +754,34 @@ int RGW_Auth_STS::authorize(const DoutPrefixProvider *dpp, return rgw::auth::Strategy::apply(dpp, auth_registry.get_sts(), s, y); } -void RGWHandler_REST_STS::rgw_sts_parse_input() +using op_generator = RGWOp*(*)(); +static const std::unordered_map op_generators = { + {"AssumeRole", []() -> RGWOp* {return new RGWSTSAssumeRole;}}, + {"GetSessionToken", []() -> RGWOp* {return new RGWSTSGetSessionToken;}}, + {"AssumeRoleWithWebIdentity", []() -> RGWOp* {return new RGWSTSAssumeRoleWithWebIdentity;}} +}; + +bool RGWHandler_REST_STS::action_exists(const req_state* s) { - if (post_body.size() > 0) { - ldpp_dout(s, 10) << "Content of POST: " << post_body << dendl; - - if (post_body.find("Action") != string::npos) { - boost::char_separator sep("&"); - boost::tokenizer> tokens(post_body, sep); - for (const auto& t : tokens) { - auto pos = t.find("="); - if (pos != string::npos) { - s->info.args.append(t.substr(0,pos), - url_decode(t.substr(pos+1, t.size() -1))); - } - } - } + if (s->info.args.exists("Action")) { + const std::string action_name = s->info.args.get("Action"); + return op_generators.contains(action_name); } - auto payload_hash = rgw::auth::s3::calc_v4_payload_hash(post_body); - s->info.args.append("PayloadHash", payload_hash); + return false; } RGWOp *RGWHandler_REST_STS::op_post() { - rgw_sts_parse_input(); - - if (s->info.args.exists("Action")) { - string action = s->info.args.get("Action"); - if (action == "AssumeRole") { - return new RGWSTSAssumeRole; - } else if (action == "GetSessionToken") { - return new RGWSTSGetSessionToken; - } else if (action == "AssumeRoleWithWebIdentity") { - return new RGWSTSAssumeRoleWithWebIdentity; + if (s->info.args.exists("Action")) { + const std::string action_name = s->info.args.get("Action"); + const auto action_it = op_generators.find(action_name); + if (action_it != op_generators.end()) { + return action_it->second(); } + ldpp_dout(s, 10) << "unknown action '" << action_name << "' for STS handler" << dendl; + } else { + ldpp_dout(s, 10) << "missing action argument in STS handler" << dendl; } - return nullptr; } @@ -798,11 +790,7 @@ int RGWHandler_REST_STS::init(rgw::sal::Driver* driver, rgw::io::BasicClient *cio) { s->dialect = "sts"; - - if (int ret = RGWHandler_REST_STS::init_from_header(s, RGWFormat::XML, true); ret < 0) { - ldpp_dout(s, 10) << "init_from_header returned err=" << ret << dendl; - return ret; - } + s->prot_flags = RGW_REST_STS; return RGWHandler_REST::init(driver, s, cio); } @@ -815,48 +803,6 @@ int RGWHandler_REST_STS::authorize(const DoutPrefixProvider* dpp, optional_yield return RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y); } -int RGWHandler_REST_STS::init_from_header(req_state* s, - RGWFormat 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(s); - - /* 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(rgw::sal::Driver* driver, req_state* const s, diff --git a/src/rgw/rgw_rest_sts.h b/src/rgw/rgw_rest_sts.h index 9c9488c6c171..db17281fc740 100644 --- a/src/rgw/rgw_rest_sts.h +++ b/src/rgw/rgw_rest_sts.h @@ -199,17 +199,14 @@ public: class RGWHandler_REST_STS : public RGWHandler_REST { const rgw::auth::StrategyRegistry& auth_registry; - const std::string& post_body; RGWOp *op_post() override; - void rgw_sts_parse_input(); public: - static int init_from_header(req_state *s, RGWFormat default_formatter, bool configurable_format); + static bool action_exists(const req_state* s); - RGWHandler_REST_STS(const rgw::auth::StrategyRegistry& auth_registry, const std::string& post_body="") + RGWHandler_REST_STS(const rgw::auth::StrategyRegistry& auth_registry) : RGWHandler_REST(), - auth_registry(auth_registry), - post_body(post_body) {} + auth_registry(auth_registry) {} ~RGWHandler_REST_STS() override = default; int init(rgw::sal::Driver* driver,