Skip to content

Commit

Permalink
Merge pull request ceph#49335 from yuvalif/wip-yuval-fix-58167
Browse files Browse the repository at this point in the history
prevent anonymous topic operations

Reviewed-By: pritha-srivastava, cbodley
  • Loading branch information
yuvalif authored Feb 20, 2023
2 parents 75fcae6 + 5bb78ab commit b619fda
Show file tree
Hide file tree
Showing 13 changed files with 284 additions and 404 deletions.
156 changes: 34 additions & 122 deletions src/rgw/driver/rados/rgw_rest_pubsub.cc
Original file line number Diff line number Diff line change
Expand Up @@ -468,140 +468,52 @@ 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<std::string, op_generator> 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<unsigned, Attribute>;

// 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<char> 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<char> sep("&");
const boost::tokenizer<boost::char_separator<char>> 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;
}

int RGWHandler_REST_PSTopic_AWS::authorize(const DoutPrefixProvider* dpp, optional_yield y) {
return RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y);
const auto rc = RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y);
if (rc < 0) {
return rc;
}
if (s->auth.identity->is_anonymous()) {
ldpp_dout(dpp, 1) << "anonymous user not allowed in topic operations" << dendl;
return -ERR_INVALID_REQUEST;
}
return 0;
}

namespace {
Expand Down
9 changes: 4 additions & 5 deletions src/rgw/driver/rados/rgw_rest_pubsub.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@ class RGWHandler_REST_PSNotifs_S3 : public RGWHandler_REST_S3 {
// 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);
};

20 changes: 12 additions & 8 deletions src/rgw/rgw_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,8 @@ bool verify_user_permission(const DoutPrefixProvider* dpp,
const vector<rgw::IAM::Policy>& user_policies,
const vector<rgw::IAM::Policy>& 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) {
Expand All @@ -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,
Expand All @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions src/rgw/rgw_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,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

Expand Down Expand Up @@ -1584,15 +1585,17 @@ bool verify_user_permission(const DoutPrefixProvider* dpp,
const std::vector<rgw::IAM::Policy>& user_policies,
const std::vector<rgw::IAM::Policy>& 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,
const int perm);
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);
Expand Down
4 changes: 2 additions & 2 deletions src/rgw/rgw_op.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2400,7 +2400,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;
}

Expand Down Expand Up @@ -3019,7 +3019,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;
}

Expand Down
3 changes: 3 additions & 0 deletions src/rgw/rgw_op.h
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class RGWOp : public DoutPrefixProvider {
RGWQuota quota;
int op_ret;
int do_aws4_auth_completion();
bool init_called = false;

virtual int init_quota();

Expand Down Expand Up @@ -233,7 +234,9 @@ class RGWOp : public DoutPrefixProvider {
}

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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/rgw/rgw_rest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading

0 comments on commit b619fda

Please sign in to comment.