From 2ba4a0f6f53531ed944194c53f4ee4f91c256915 Mon Sep 17 00:00:00 2001 From: Abhishek Lekshmanan Date: Wed, 29 Jan 2020 11:29:43 +0100 Subject: [PATCH] rgw: implement get/put/delete public access block for buckets Signed-off-by: Abhishek Lekshmanan --- src/rgw/rgw_auth_s3.cc | 1 + src/rgw/rgw_common.cc | 3 +- src/rgw/rgw_common.h | 8 ++ src/rgw/rgw_iam_policy.cc | 2 + src/rgw/rgw_iam_policy.h | 10 +- src/rgw/rgw_op.cc | 123 ++++++++++++++++++ src/rgw/rgw_op.h | 42 ++++++ src/rgw/rgw_public_access.cc | 27 +++- ...rgw_public_acess.h => rgw_public_access.h} | 2 +- src/rgw/rgw_rest_s3.cc | 32 ++++- src/rgw/rgw_rest_s3.h | 13 ++ 11 files changed, 254 insertions(+), 9 deletions(-) rename src/rgw/{rgw_public_acess.h => rgw_public_access.h} (95%) diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 9a40fd86dbb..bdee372b10d 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -34,6 +34,7 @@ static const auto signed_subresources = { "partNumber", "policy", "policyStatus", + "publicAccessBlock", "requestPayment", "response-cache-control", "response-content-disposition", diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 93c625ab599..e44854bfa64 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -849,7 +849,8 @@ void RGWHTTPArgs::append(const string& name, const string& val) (name.compare("tagging") == 0) || (name.compare("append") == 0) || (name.compare("position") == 0) || - (name.compare("policyStatus") == 0)) { + (name.compare("policyStatus") == 0) || + (name.compare("publicAccessBlock") == 0)) { sub_resources[name] = val; } else if (name[0] == 'r') { // root of all evil if ((name.compare("response-content-type") == 0) || diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 30df2dcfcba..c8a02ed54a5 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -124,6 +124,7 @@ using ceph::crypto::MD5; /* IAM Policy */ #define RGW_ATTR_IAM_POLICY RGW_ATTR_PREFIX "iam-policy" #define RGW_ATTR_USER_POLICY RGW_ATTR_PREFIX "user-policy" +#define RGW_ATTR_PUBLIC_ACCESS RGW_ATTR_PREFIX "public-access" /* RGW File Attributes */ #define RGW_ATTR_UNIX_KEY1 RGW_ATTR_PREFIX "unix-key1" @@ -135,6 +136,7 @@ using ceph::crypto::MD5; #define RGW_ATTR_CRYPT_KEYID RGW_ATTR_CRYPT_PREFIX "keyid" #define RGW_ATTR_CRYPT_KEYSEL RGW_ATTR_CRYPT_PREFIX "keysel" + #define RGW_FORMAT_PLAIN 0 #define RGW_FORMAT_XML 1 #define RGW_FORMAT_JSON 2 @@ -536,6 +538,12 @@ enum RGWOpType { RGW_OP_DELETE_BUCKET_REPLICATION, RGW_OP_GET_BUCKET_POLICY_STATUS + + /* public access */ + RGW_OP_GET_BUCKET_POLICY_STATUS, + RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK, + RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK, + RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK, }; class RGWAccessControlPolicy; diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index 86bc8ac9be3..72c4f4ab6d5 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -81,6 +81,7 @@ static const actpair actpairs[] = { "s3:GetBucketLogging", s3GetBucketLogging }, { "s3:GetBucketNotification", s3GetBucketNotification }, { "s3:GetBucketPolicy", s3GetBucketPolicy }, + { "s3:GetBucketPublicAccessBlock", s3GetBucketPublicAccessBlock }, { "s3:GetBucketRequestPayment", s3GetBucketRequestPayment }, { "s3:GetBucketTagging", s3GetBucketTagging }, { "s3:GetBucketVersioning", s3GetBucketVersioning }, @@ -123,6 +124,7 @@ static const actpair actpairs[] = { "s3:PutObjectRetention", s3PutObjectRetention }, { "s3:PutObjectLegalHold", s3PutObjectLegalHold }, { "s3:BypassGovernanceRetention", s3BypassGovernanceRetention }, + { "s3:PutBucketPublicAccessBlock", s3PutBucketPublicAccessBlock }, { "s3:PutReplicationConfiguration", s3PutReplicationConfiguration }, { "s3:RestoreObject", s3RestoreObject }, { "iam:PutUserPolicy", iamPutUserPolicy }, diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index a667a0192ac..78f363a145b 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -101,7 +101,13 @@ static constexpr std::uint64_t s3PutObjectLegalHold = 58; static constexpr std::uint64_t s3GetObjectLegalHold = 59; static constexpr std::uint64_t s3BypassGovernanceRetention = 60; static constexpr std::uint64_t s3GetBucketPolicyStatus = 61; -static constexpr std::uint64_t s3All = 62; +static constexpr std::uint64_t s3PutPublicAccessBlock = 62; +static constexpr std::uint64_t s3GetPublicAccessBlock = 63; +static constexpr std::uint64_t s3DeletePublicAccessBlock = 64; +static constexpr std::uint64_t s3GetBucketPublicAccessBlock = 65; +static constexpr std::uint64_t s3PutBucketPublicAccessBlock = 66; +static constexpr std::uint64_t s3DeleteBucketPublicAccessBlock = 67; +static constexpr std::uint64_t s3All = 68; static constexpr std::uint64_t iamPutUserPolicy = s3All + 1; static constexpr std::uint64_t iamGetUserPolicy = s3All + 2; @@ -201,6 +207,7 @@ inline int op_to_perm(std::uint64_t op) { case s3GetObjectVersionAcl: case s3GetReplicationConfiguration: case s3GetBucketObjectLockConfiguration: + case s3GetBucketPublicAccessBlock: return RGW_PERM_READ_ACP; case s3DeleteBucketPolicy: @@ -221,6 +228,7 @@ inline int op_to_perm(std::uint64_t op) { case s3PutObjectVersionAcl: case s3PutReplicationConfiguration: case s3PutBucketObjectLockConfiguration: + case s3PutBucketPublicAccessBlock: return RGW_PERM_WRITE_ACP; case s3All: diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index a9fafb77f2d..9757a75ef96 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -8088,3 +8088,126 @@ void RGWGetBucketPolicyStatus::execute() if (s->iam_policy) isPublic |= rgw::IAM::IsPublic(*s->iam_policy); } + +int RGWPutBucketPublicAccessBlock::verify_permission() +{ + if (!verify_bucket_permission(this, s, rgw::IAM::s3PutBucketPublicAccessBlock)) { + return -EACCES; + } + + return 0; +} + +int RGWPutBucketPublicAccessBlock::get_params() +{ + const auto max_size = s->cct->_conf->rgw_max_put_param_size; + std::tie(op_ret, data) = rgw_rest_read_all_input(s, max_size, false); + return op_ret; +} + +void RGWPutBucketPublicAccessBlock::execute() +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl; + op_ret = -EINVAL; + return; + } + + op_ret = get_params(); + if (op_ret < 0) + return; + + if (!parser.parse(data.c_str(), data.length(), 1)) { + ldpp_dout(this,0) << "ERROR: malformed XML" << dendl; + ldpp_dout(this,20) << "xml: " << data.c_str() << dendl; + op_ret = -ERR_MALFORMED_XML; + return; + } + + try { + RGWXMLDecoder::decode_xml("PublicAccessBlockConfiguration", access_conf, &parser, true); + } catch (RGWXMLDecoder::err &err) { + ldout(s->cct, 5) << "unexpected xml:" << err << dendl; + op_ret = -ERR_MALFORMED_XML; + return; + } + + if (!store->svc()->zone->is_meta_master()) { + op_ret = forward_request_to_master(s, NULL, store, data, nullptr); + if (op_ret < 0) { + ldpp_dout(this, 20) << "forward_request_to_master returned ret=" << op_ret << dendl; + return; + } + } + + bufferlist bl; + access_conf.encode(bl); + op_ret = retry_raced_bucket_write(store->getRados(), s, [this, &bl] { + map attrs = s->bucket_attrs; + attrs[RGW_ATTR_PUBLIC_ACCESS] = bl; + return store->ctl()->bucket->set_bucket_instance_attrs(s->bucket_info, attrs, &s->bucket_info.objv_tracker, s->yield); + }); + +} + +int RGWGetBucketPublicAccessBlock::verify_permission() +{ + if (!verify_bucket_permission(this, s, rgw::IAM::s3GetBucketPolicy)) { + return -EACCES; + } + + return 0; +} + +void RGWGetBucketPublicAccessBlock::execute() +{ + auto attrs = s->bucket_attrs; + if (auto aiter = attrs.find(RGW_ATTR_PUBLIC_ACCESS); + aiter == attrs.end()) { + ldpp_dout(this, 0) << "can't find bucket IAM POLICY attr bucket_name = " + << s->bucket_name << dendl; + // return the default; + return; + } else { + bufferlist::const_iterator iter{&aiter->second}; + try { + access_conf.decode(iter); + } catch (const buffer::error& e) { + ldout(s->cct, 0) << __func__ << "decode object legal hold config failed" << dendl; + op_ret = -EIO; + return; + } + } +} + + +void RGWDeleteBucketPublicAccessBlock::send_response() +{ + if (op_ret) { + set_req_state_err(s, op_ret); + } + dump_errno(s); + end_header(s); +} + +int RGWDeleteBucketPublicAccessBlock::verify_permission() +{ + if (!verify_bucket_permission(this, s, rgw::IAM::s3PutBucketPublicAccessBlock)) { + return -EACCES; + } + + return 0; +} + +void RGWDeleteBucketPublicAccessBlock::execute() +{ + op_ret = retry_raced_bucket_write(store->getRados(), s, [this] { + auto attrs = s->bucket_attrs; + attrs.erase(RGW_ATTR_PUBLIC_ACCESS); + op_ret = store->ctl()->bucket->set_bucket_instance_attrs(s->bucket_info, attrs, + &s->bucket_info.objv_tracker, + s->yield); + return op_ret; + }); +} diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 131db7c91a8..882bd949579 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -50,6 +50,7 @@ #include "rgw_object_lock.h" #include "cls/lock/cls_lock_client.h" #include "cls/rgw/cls_rgw_client.h" +#include "rgw_public_access.h" #include "services/svc_sys_obj.h" #include "services/svc_tier_rados.h" @@ -2396,6 +2397,47 @@ public: dmc::client_id dmclock_client() override { return dmc::client_id::metadata; } }; +class RGWPutBucketPublicAccessBlock : public RGWOp { +protected: + bufferlist data; + rgw::IAM::PublicAccessConfiguration access_conf; +public: + int verify_permission() override; + const char* name() const override { return "put_bucket_public_access_block";} + virtual RGWOpType get_type() override { return RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK; } + virtual uint32_t op_mask() override { return RGW_OP_TYPE_WRITE; } + int get_params(); + void execute() override; + dmc::client_id dmclock_client() override { return dmc::client_id::metadata; } +}; + +class RGWGetBucketPublicAccessBlock : public RGWOp { +protected: + rgw::IAM::PublicAccessConfiguration access_conf; +public: + int verify_permission() override; + const char* name() const override { return "get_bucket_public_access_block";} + virtual RGWOpType get_type() override { return RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK; } + virtual uint32_t op_mask() override { return RGW_OP_TYPE_READ; } + int get_params(); + void execute() override; + dmc::client_id dmclock_client() override { return dmc::client_id::metadata; } +}; + +class RGWDeleteBucketPublicAccessBlock : public RGWOp { +protected: + rgw::IAM::PublicAccessConfiguration access_conf; +public: + int verify_permission() override; + const char* name() const override { return "delete_bucket_public_access_block";} + virtual RGWOpType get_type() override { return RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK; } + virtual uint32_t op_mask() override { return RGW_OP_TYPE_WRITE; } + int get_params(); + void execute() override; + void send_response() override; + dmc::client_id dmclock_client() override { return dmc::client_id::metadata; } +}; + static inline int parse_value_and_bound( const string &input, int &output, diff --git a/src/rgw/rgw_public_access.cc b/src/rgw/rgw_public_access.cc index 67a108d540c..e5131a0f06d 100644 --- a/src/rgw/rgw_public_access.cc +++ b/src/rgw/rgw_public_access.cc @@ -1,4 +1,4 @@ -#include "rgw_public_acess.h" +#include "rgw_public_access.h" #include "rgw_xml.h" namespace rgw::IAM { @@ -13,10 +13,27 @@ void PublicAccessConfiguration::decode_xml(XMLObj *obj) { void PublicAccessConfiguration::dump_xml(Formatter *f) const { Formatter::ObjectSection os(*f, "BlockPublicAccessConfiguration"); - encode_xml("BlockPublicAcls", BlockPublicAcls, f); - encode_xml("IgnorePublicAcls", IgnorePublicAcls, f); - encode_xml("BlockPublicPolicy", BlockPublicPolicy, f); - encode_xml("RestrictPublicBuckets", RestrictPublicBuckets, f); + // AWS spec mentions the values to be ALL CAPs, but clients will not + // understand this or a mixed case like it is supposed to, hence the need to + // manually encode here + auto bool_val = [](bool b) -> auto { return b ? "true": "false"; }; + + f->dump_string("BlockPublicAcls", bool_val(BlockPublicAcls)); + f->dump_string("IgnorePublicAcls", bool_val(IgnorePublicAcls)); + f->dump_string("BlockPublicPolicy", bool_val(BlockPublicPolicy)); + f->dump_string("RestrictPublicBuckets", bool_val(RestrictPublicBuckets)); +} + + +ostream& operator<< (ostream& os, const PublicAccessConfiguration& access_conf) +{ + os << std::boolalpha + << "BlockPublicAcls: " << access_conf.get_block_public_acls() << std::endl + << "IgnorePublicAcls: " << access_conf.get_ignore_public_acls() << std::endl + << "BlockPublicPolicy" << access_conf.get_block_public_policy() << std::endl + << "RestrictPublicBuckets" << access_conf.get_restrict_public_buckets() << std::endl; + + return os; } } // namespace rgw::IAM diff --git a/src/rgw/rgw_public_acess.h b/src/rgw/rgw_public_access.h similarity index 95% rename from src/rgw/rgw_public_acess.h rename to src/rgw/rgw_public_access.h index aee0360cdb9..b9eb6d5ddee 100644 --- a/src/rgw/rgw_public_acess.h +++ b/src/rgw/rgw_public_access.h @@ -66,5 +66,5 @@ class PublicAccessConfiguration { void dump_xml(Formatter *f) const; }; WRITE_CLASS_ENCODER(PublicAccessConfiguration) - +ostream& operator<< (ostream& os, const PublicAccessConfiguration& access_conf); } // namespace rgw::IAM diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 95df630396a..18680992618 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -4183,6 +4183,28 @@ void RGWGetBucketPolicyStatus_ObjStore_S3::send_response() } +void RGWPutBucketPublicAccessBlock_ObjStore_S3::send_response() +{ + if (op_ret) { + set_req_state_err(s, op_ret); + } + dump_errno(s); + end_header(s); +} + +void RGWGetBucketPublicAccessBlock_ObjStore_S3::send_response() +{ + if (op_ret) { + set_req_state_err(s, op_ret); + } + dump_errno(s); + end_header(s, this, "application/xml"); + dump_start(s); + + access_conf.dump_xml(s->formatter); + rgw_flush_formatter_and_reset(s, s->formatter); +} + RGWOp *RGWHandler_REST_Service_S3::op_get() { if (is_usage_op()) { @@ -4304,6 +4326,8 @@ RGWOp *RGWHandler_REST_Bucket_S3::op_get() return new RGWGetBucketReplication_ObjStore_S3; } else if (is_policy_status_op()) { return new RGWGetBucketPolicyStatus_ObjStore_S3; + } else if (is_block_public_access_op()) { + return new RGWGetBucketPublicAccessBlock_ObjStore_S3; } return get_obj_op(true); } @@ -4330,7 +4354,6 @@ RGWOp *RGWHandler_REST_Bucket_S3::op_put() } return new RGWSetBucketWebsite_ObjStore_S3; } - if (is_tagging_op()) { return new RGWPutBucketTags_ObjStore_S3; } else if (is_acl_op()) { @@ -4355,6 +4378,8 @@ RGWOp *RGWHandler_REST_Bucket_S3::op_put() } return new RGWPutBucketReplication_ObjStore_S3; + } else if (is_block_public_access_op()) { + return new RGWPutBucketPublicAccessBlock_ObjStore_S3; } return new RGWCreateBucket_ObjStore_S3; } @@ -4373,6 +4398,8 @@ RGWOp *RGWHandler_REST_Bucket_S3::op_delete() return RGWHandler_REST_PSNotifs_S3::create_delete_op(); } else if (is_replication_op()) { return new RGWDeleteBucketReplication_ObjStore_S3; + } else if (is_block_public_access_op()) { + return new RGWDeleteBucketPublicAccessBlock; } if (s->info.args.sub_resource_exists("website")) { @@ -5242,6 +5269,9 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, case RGW_OP_PUT_OBJ_LEGAL_HOLD: case RGW_STS_GET_SESSION_TOKEN: case RGW_STS_ASSUME_ROLE: + case RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK: + case RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK: + case RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK: break; default: dout(10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED" << dendl; diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index b0e080b2550..7bb2f9d260f 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -575,6 +575,16 @@ public: void send_response() override; }; +class RGWPutBucketPublicAccessBlock_ObjStore_S3 : public RGWPutBucketPublicAccessBlock { +public: + void send_response() override; +}; + +class RGWGetBucketPublicAccessBlock_ObjStore_S3 : public RGWGetBucketPublicAccessBlock { +public: + void send_response() override; +}; + class RGW_Auth_S3 { public: static int authorize(const DoutPrefixProvider *dpp, @@ -684,6 +694,9 @@ protected: bool is_policy_status_op() { return s->info.args.exists("policyStatus"); } + bool is_block_public_access_op() { + return s->info.args.exists("publicAccessBlock"); + } RGWOp *get_obj_op(bool get_data) const; RGWOp *op_get() override; -- 2.39.5