From: Abhishek Lekshmanan Date: Wed, 29 Jan 2020 10:29:43 +0000 (+0100) Subject: rgw: implement get/put/delete public access block for buckets X-Git-Tag: v15.1.1~555^2~22 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=2ba4a0f6f53531ed944194c53f4ee4f91c256915;p=ceph.git rgw: implement get/put/delete public access block for buckets Signed-off-by: Abhishek Lekshmanan --- 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_access.h b/src/rgw/rgw_public_access.h new file mode 100644 index 00000000000..b9eb6d5ddee --- /dev/null +++ b/src/rgw/rgw_public_access.h @@ -0,0 +1,70 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2019 SUSE LLC + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once +#include + +class XMLObj; + +namespace rgw::IAM { + +class PublicAccessConfiguration { + bool BlockPublicAcls; + bool IgnorePublicAcls; + bool BlockPublicPolicy; + bool RestrictPublicBuckets; + public: + PublicAccessConfiguration(): + BlockPublicAcls(true), IgnorePublicAcls(false), + BlockPublicPolicy(true), RestrictPublicBuckets(false) + {} + + auto get_block_public_acls() const { + return BlockPublicAcls; + } + auto get_ignore_public_acls() const { + return IgnorePublicAcls; + } + auto get_block_public_policy() const { + return BlockPublicPolicy; + } + auto get_restrict_public_buckets() const { + return RestrictPublicBuckets; + } + + void encode(ceph::bufferlist& bl) const { + ENCODE_START(1,1, bl); + encode(BlockPublicAcls, bl); + encode(IgnorePublicAcls, bl); + encode(BlockPublicPolicy, bl); + encode(RestrictPublicBuckets, bl); + ENCODE_FINISH(bl); + } + + void decode(ceph::bufferlist::const_iterator& bl) { + DECODE_START(1,bl); + decode(BlockPublicAcls, bl); + decode(IgnorePublicAcls, bl); + decode(BlockPublicPolicy, bl); + decode(RestrictPublicBuckets, bl); + DECODE_FINISH(bl); + } + + void decode_xml(XMLObj *obj); + 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_public_acess.h b/src/rgw/rgw_public_acess.h deleted file mode 100644 index aee0360cdb9..00000000000 --- a/src/rgw/rgw_public_acess.h +++ /dev/null @@ -1,70 +0,0 @@ -// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- -// vim: ts=8 sw=2 smarttab ft=cpp - -/* - * Ceph - scalable distributed file system - * - * Copyright (C) 2019 SUSE LLC - * - * This is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License version 2.1, as published by the Free Software - * Foundation. See file COPYING. - * - */ - -#pragma once -#include - -class XMLObj; - -namespace rgw::IAM { - -class PublicAccessConfiguration { - bool BlockPublicAcls; - bool IgnorePublicAcls; - bool BlockPublicPolicy; - bool RestrictPublicBuckets; - public: - PublicAccessConfiguration(): - BlockPublicAcls(true), IgnorePublicAcls(false), - BlockPublicPolicy(true), RestrictPublicBuckets(false) - {} - - auto get_block_public_acls() const { - return BlockPublicAcls; - } - auto get_ignore_public_acls() const { - return IgnorePublicAcls; - } - auto get_block_public_policy() const { - return BlockPublicPolicy; - } - auto get_restrict_public_buckets() const { - return RestrictPublicBuckets; - } - - void encode(ceph::bufferlist& bl) const { - ENCODE_START(1,1, bl); - encode(BlockPublicAcls, bl); - encode(IgnorePublicAcls, bl); - encode(BlockPublicPolicy, bl); - encode(RestrictPublicBuckets, bl); - ENCODE_FINISH(bl); - } - - void decode(ceph::bufferlist::const_iterator& bl) { - DECODE_START(1,bl); - decode(BlockPublicAcls, bl); - decode(IgnorePublicAcls, bl); - decode(BlockPublicPolicy, bl); - decode(RestrictPublicBuckets, bl); - DECODE_FINISH(bl); - } - - void decode_xml(XMLObj *obj); - void dump_xml(Formatter *f) const; -}; -WRITE_CLASS_ENCODER(PublicAccessConfiguration) - -} // 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;