From 50c5b18c5b6ef64a2608e8ee7a1ca1bccb945b92 Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Tue, 1 Jul 2025 00:49:20 -0400 Subject: [PATCH] rgw: add s3control apis for PublicAccessBlock Signed-off-by: Casey Bodley --- src/rgw/CMakeLists.txt | 1 + src/rgw/rgw_appmain.cc | 3 +- src/rgw/rgw_common.h | 1 + src/rgw/rgw_op_type.h | 3 + src/rgw/rgw_rest_s3.cc | 24 ++- src/rgw/rgw_rest_s3.h | 4 +- src/rgw/rgw_rest_s3control.cc | 339 ++++++++++++++++++++++++++++++++++ src/rgw/rgw_rest_s3control.h | 25 +++ 8 files changed, 397 insertions(+), 3 deletions(-) create mode 100644 src/rgw/rgw_rest_s3control.cc create mode 100644 src/rgw/rgw_rest_s3control.h diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index 0b6dabe0a71..87e58872942 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -102,6 +102,7 @@ set(librgw_common_srcs rgw_rest_iam_group.cc rgw_rest_iam_user.cc rgw_rest_s3.cc + rgw_rest_s3control.cc rgw_rest_pubsub.cc rgw_rest_zero.cc rgw_s3select.cc diff --git a/src/rgw/rgw_appmain.cc b/src/rgw/rgw_appmain.cc index 34806d9ae2d..7f1d22b765b 100644 --- a/src/rgw/rgw_appmain.cc +++ b/src/rgw/rgw_appmain.cc @@ -305,6 +305,7 @@ void rgw::AppMain::cond_init_apis() // S3 website mode is a specialization of S3 const bool s3website_enabled = apis_set.contains("s3website"); + const bool s3control_enabled = apis_set.contains("s3control"); const bool sts_enabled = apis_set.contains("sts"); const bool iam_enabled = apis_set.contains("iam"); const bool pubsub_enabled = @@ -315,7 +316,7 @@ void rgw::AppMain::cond_init_apis() if (!swift_at_root) { rest.register_default_mgr(set_logging( rest_filter(env.driver, RGW_REST_S3, - new RGWRESTMgr_S3(s3website_enabled, sts_enabled, + new RGWRESTMgr_S3(s3control_enabled, s3website_enabled, sts_enabled, iam_enabled, pubsub_enabled)))); } else { derr << "Cannot have the S3 or S3 Website enabled together with " diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index 7bbde0fd41b..94d04c2fa37 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -243,6 +243,7 @@ static inline const char* to_mime_type(const RGWFormat f) #define RGW_REST_STS 0x10 #define RGW_REST_IAM 0x20 #define RGW_REST_SNS 0x40 +#define RGW_REST_S3CONTROL 0x80 inline constexpr const char* RGW_REST_IAM_XMLNS = "https://iam.amazonaws.com/doc/2010-05-08/"; diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h index 0631bf2b393..96b4fc41b89 100644 --- a/src/rgw/rgw_op_type.h +++ b/src/rgw/rgw_op_type.h @@ -165,6 +165,9 @@ enum RGWOpType { RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK, RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK, RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK, + RGW_OP_PUT_PUBLIC_ACCESS_BLOCK, + RGW_OP_GET_PUBLIC_ACCESS_BLOCK, + RGW_OP_DELETE_PUBLIC_ACCESS_BLOCK, /*OIDC provider specific*/ RGW_OP_CREATE_OIDC_PROVIDER, RGW_OP_DELETE_OIDC_PROVIDER, diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 44db27c5ffc..aaf014cc670 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -37,6 +37,7 @@ #include "rgw_rest.h" #include "rgw_rest_s3.h" +#include "rgw_rest_s3control.h" #include "rgw_rest_s3website.h" #include "rgw_rest_pubsub.h" #include "rgw_auth_s3.h" @@ -5968,7 +5969,8 @@ void parse_post_action(const std::string& post_body, req_state* s) } } -RGWRESTMgr_S3::RGWRESTMgr_S3(bool _enable_s3website, +RGWRESTMgr_S3::RGWRESTMgr_S3(bool enable_s3control, + bool _enable_s3website, bool _enable_sts, bool _enable_iam, bool _enable_pubsub) @@ -5976,6 +5978,9 @@ RGWRESTMgr_S3::RGWRESTMgr_S3(bool _enable_s3website, enable_iam(_enable_iam), enable_pubsub(_enable_pubsub) { + if (enable_s3control) { + s3control = std::make_unique(); + } if (_enable_s3website) { s3website = std::make_unique(); } @@ -5986,6 +5991,22 @@ RGWRESTMgr* RGWRESTMgr_S3::get_resource_mgr_as_default(req_state* s, const std::string& uri, std::string* out_uri) { + // s3control apis all expect the request header x-amz-account-id, + // and s3 apis don't. use that to disambiguate between s3control + // and requests to s3 buckets named v20180820 + if (s3control && s->info.env->exists("HTTP_X_AMZ_ACCOUNT_ID")) { + ldpp_dout(s, 20) << "checking for s3control path v20180820 in " + "request_uri=" << uri << dendl; + // route matching requests RGWRESTMgr_S3Control + constexpr std::string_view s3control_root = "/v20180820"; + if (auto i = std::ranges::mismatch(s3control_root, uri); + i.in1 == s3control_root.end() && // matched full string + (i.in2 == uri.end() || *i.in2 == '/')) { // end or / + const auto suffix = std::string{i.in2, uri.end()}; // trim prefix + return s3control->get_resource_mgr(s, suffix, out_uri); + } + } + // check the Host header for virtual-host style requests, and // rewrite the request_uri with the subdomain as the bucket name. // this applies to s3 and s3website requests, but not s3control @@ -6703,6 +6724,7 @@ AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, case RGW_OP_POST_BUCKET_LOGGING: case RGW_OP_GET_BUCKET_LOGGING: case RGW_OP_PUT_BUCKET_OWNERSHIP_CONTROLS: + case RGW_OP_PUT_PUBLIC_ACCESS_BLOCK: break; default: ldpp_dout(s, 10) << "ERROR: AWS4 completion for operation: " << s->op_type << ", NOT IMPLEMENTED" << dendl; diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index 5d9a722e5b0..8e8805e906e 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -818,16 +818,18 @@ public: ~RGWHandler_REST_Obj_S3() override = default; }; +class RGWRESTMgr_S3Control; class RGWRESTMgr_S3Website; class RGWRESTMgr_S3 : public RGWRESTMgr { private: + std::unique_ptr s3control; std::unique_ptr s3website; const bool enable_sts; const bool enable_iam; const bool enable_pubsub; public: - explicit RGWRESTMgr_S3(bool _enable_s3website=false, bool _enable_sts=false, bool _enable_iam=false, bool _enable_pubsub=false); + RGWRESTMgr_S3(bool enable_s3control, bool _enable_s3website, bool _enable_sts, bool _enable_iam, bool _enable_pubsub); ~RGWRESTMgr_S3() override; RGWHandler_REST *get_handler(rgw::sal::Driver* driver, diff --git a/src/rgw/rgw_rest_s3control.cc b/src/rgw/rgw_rest_s3control.cc new file mode 100644 index 00000000000..d06923ee78e --- /dev/null +++ b/src/rgw/rgw_rest_s3control.cc @@ -0,0 +1,339 @@ +// -*- 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 contributors to the Ceph project + * + * 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. + * + */ + +#include "rgw_rest_s3control.h" + +#include + +#include "rgw_auth_s3.h" +#include "rgw_process_env.h" +#include "common/errno.h" + +template F> +int retry_raced_account_write(const DoutPrefixProvider* dpp, optional_yield y, + rgw::sal::Driver* driver, RGWAccountInfo& info, + RGWObjVersionTracker& objv, const F& f) +{ + int r = f(); + for (int i = 0; i < 10 && r == -ECANCELED; ++i) { + objv.clear(); + r = driver->load_account_by_id(dpp, y, info.id, info, objv); + if (r >= 0) { + r = f(); + } + } + return r; +} + +static int get_account_id(req_state* s, rgw_account_id& account_id) +{ + auto id = s->info.x_meta_map.find("x-amz-account-id"); + if (id == s->info.x_meta_map.end()) { + s->err.message = "Missing required header x-amz-account-id"; + return -EINVAL; + } + account_id = id->second; + if (account_id.empty()) { + s->err.message = "Missing required value for x-amz-account-id"; + return -EINVAL; + } + + const auto& account = s->auth.identity->get_account(); + if (!account) { + return -ERR_METHOD_NOT_ALLOWED; + } + if (account_id != account->id) { + s->err.message = "x-amz-account-id must match the requester"; + return -EINVAL; + } + return 0; +} + +// GetPublicAccessBlock +class RGWGetPublicAccessBlock_S3Control : public RGWOp { + rgw_account_id account_id; + PublicAccessBlockConfiguration public_access_block; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "get_public_access_block"; } + RGWOpType get_type() override { return RGW_OP_GET_PUBLIC_ACCESS_BLOCK; } +}; + +int RGWGetPublicAccessBlock_S3Control::init_processing(optional_yield y) +{ + return get_account_id(s, account_id); +} + +int RGWGetPublicAccessBlock_S3Control::verify_permission(optional_yield y) +{ + const rgw::ARN arn{"", "root", account_id, false}; // XXX + if (verify_user_permission(this, s, arn, rgw::IAM::s3GetAccountPublicAccessBlock, true)) { + return 0; + } + return -EACCES; +} + +void RGWGetPublicAccessBlock_S3Control::execute(optional_yield y) +{ + RGWAccountInfo account; + RGWObjVersionTracker objv; + op_ret = driver->load_account_by_id(this, y, account_id, account, objv); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to load account id " + << account_id << ": " << cpp_strerror(op_ret) << dendl; + return; + } + + auto i = account.attrs.find(RGW_ATTR_PUBLIC_ACCESS); + if (i == account.attrs.end()) { + op_ret = -ERR_NO_SUCH_PUBLIC_ACCESS_BLOCK_CONFIGURATION; + return; + } + + try { + auto p = i->second.cbegin(); + decode(public_access_block, p); + } catch (const buffer::error&) { + op_ret = -EIO; + return; + } +} + +void RGWGetPublicAccessBlock_S3Control::send_response() +{ + if (!op_ret) { + dump_start(s); // + public_access_block.dump_xml(s->formatter); + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + +// PutPublicAccessBlock +class RGWPutPublicAccessBlock_S3Control : public RGWOp { + rgw_account_id account_id; + bufferlist data; + PublicAccessBlockConfiguration public_access_block; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "put_public_access_block"; } + RGWOpType get_type() override { return RGW_OP_PUT_PUBLIC_ACCESS_BLOCK; } +}; + +int RGWPutPublicAccessBlock_S3Control::init_processing(optional_yield y) +{ + int ret = get_account_id(s, account_id); + if (ret < 0) { + return ret; + } + + const auto max_size = s->cct->_conf->rgw_max_put_param_size; + std::tie(ret, data) = read_all_input(s, max_size, false); + if (ret < 0) { + return ret; + } + + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + return -EINVAL; + } + if (!parser.parse(data.c_str(), data.length(), 1)) { + return -ERR_MALFORMED_XML; + } + try { + RGWXMLDecoder::decode_xml("PublicAccessBlockConfiguration", + public_access_block, &parser, true); + } catch (RGWXMLDecoder::err& err) { + ldpp_dout(this, 5) << "unexpected xml:" << err << dendl; + return -ERR_MALFORMED_XML; + } + return 0; +} + +int RGWPutPublicAccessBlock_S3Control::verify_permission(optional_yield y) +{ + const rgw::ARN arn{"", "root", account_id, false}; // XXX + if (verify_user_permission(this, s, arn, rgw::IAM::s3PutAccountPublicAccessBlock, true)) { + return 0; + } + return -EACCES; +} + +void RGWPutPublicAccessBlock_S3Control::execute(optional_yield y) +{ + RGWAccountInfo account; + RGWObjVersionTracker objv; + op_ret = driver->load_account_by_id(this, y, account_id, account, objv); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to load account id " + << account_id << ": " << cpp_strerror(op_ret) << dendl; + return; + } + + op_ret = rgw_forward_request_to_master(this, *s->penv.site, s->owner.id, + &data, nullptr, s->info, s->err, y); + if (op_ret < 0) { + ldpp_dout(this, 20) << "forward_request_to_master returned ret=" << op_ret << dendl; + return; + } + + bufferlist conf_bl; + encode(public_access_block, conf_bl); + + op_ret = retry_raced_account_write(this, y, driver, account, objv, + [this, y, &conf_bl, &account, &objv] { + const RGWAccountInfo old_info = account; + account.attrs[RGW_ATTR_PUBLIC_ACCESS] = conf_bl; + + constexpr bool exclusive = false; + return driver->store_account(this, y, exclusive, account, + &old_info, objv); + }); +} + +void RGWPutPublicAccessBlock_S3Control::send_response() +{ + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + +// DeletePublicAccessBlock +class RGWDeletePublicAccessBlock_S3Control : public RGWOp { + rgw_account_id account_id; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "delete_public_access_block"; } + RGWOpType get_type() override { return RGW_OP_DELETE_PUBLIC_ACCESS_BLOCK; } +}; + +int RGWDeletePublicAccessBlock_S3Control::init_processing(optional_yield y) +{ + return get_account_id(s, account_id); +} + +int RGWDeletePublicAccessBlock_S3Control::verify_permission(optional_yield y) +{ + const rgw::ARN arn{"", "root", account_id, false}; // XXX + if (verify_user_permission(this, s, arn, rgw::IAM::s3PutAccountPublicAccessBlock, true)) { + return 0; + } + return -EACCES; +} + +void RGWDeletePublicAccessBlock_S3Control::execute(optional_yield y) +{ + RGWAccountInfo account; + RGWObjVersionTracker objv; + op_ret = driver->load_account_by_id(this, y, account_id, account, objv); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to load account id " + << account_id << ": " << cpp_strerror(op_ret) << dendl; + return; + } + + op_ret = rgw_forward_request_to_master(this, *s->penv.site, s->owner.id, + nullptr, nullptr, s->info, s->err, y); + if (op_ret < 0) { + ldpp_dout(this, 20) << "forward_request_to_master returned ret=" << op_ret << dendl; + return; + } + + op_ret = retry_raced_account_write(this, y, driver, account, objv, + [this, y, &account, &objv] { + const RGWAccountInfo old_info = account; + + auto i = account.attrs.find(RGW_ATTR_PUBLIC_ACCESS); + if (i == account.attrs.end()) { + return 0; + } + account.attrs.erase(i); + + constexpr bool exclusive = false; + return driver->store_account(this, y, exclusive, account, + &old_info, objv); + }); +} + +void RGWDeletePublicAccessBlock_S3Control::send_response() +{ + if (!op_ret) { + op_ret = STATUS_NO_CONTENT; + } + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + +class RGWHandler_PublicAccessBlock : public RGWHandler_REST { + const rgw::auth::StrategyRegistry& auth_registry; + + RGWOp* op_delete() override { return new RGWDeletePublicAccessBlock_S3Control; } + RGWOp* op_get() override { return new RGWGetPublicAccessBlock_S3Control; } + RGWOp* op_put() override { return new RGWPutPublicAccessBlock_S3Control; } +public: + RGWHandler_PublicAccessBlock(const rgw::auth::StrategyRegistry& auth_registry) + : auth_registry(auth_registry) + {} + + int init(rgw::sal::Driver* driver, req_state *s, rgw::io::BasicClient *cio) { + s->dialect = "s3control"; + s->prot_flags = RGW_REST_S3CONTROL; + int r = RGWHandler_REST::allocate_formatter(s, RGWFormat::XML, true); + if (r < 0) { + return r; + } + return RGWHandler_REST::init(driver, s, cio); + } + int authorize(const DoutPrefixProvider* dpp, optional_yield y) override { + return RGW_Auth_S3::authorize(dpp, driver, auth_registry, s, y); + } + int postauth_init(optional_yield y) override { return 0; } +}; + +class RGWRESTMgr_S3Control_PublicAccessBlock : public RGWRESTMgr { + public: + RGWHandler_REST* get_handler(rgw::sal::Driver* driver, req_state* s, + const rgw::auth::StrategyRegistry& auth_registry, + const std::string& prefix) { + return new RGWHandler_PublicAccessBlock(auth_registry); + } +}; + +RGWRESTMgr_S3Control::RGWRESTMgr_S3Control() +{ + // register nested resources + auto cfg = std::make_unique(); + cfg->register_resource("publicAccessBlock", + new RGWRESTMgr_S3Control_PublicAccessBlock); + register_resource("configuration", cfg.release()); +} diff --git a/src/rgw/rgw_rest_s3control.h b/src/rgw/rgw_rest_s3control.h new file mode 100644 index 00000000000..1f744586ee3 --- /dev/null +++ b/src/rgw/rgw_rest_s3control.h @@ -0,0 +1,25 @@ +// -*- 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 contributors to the Ceph project + * + * 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 "rgw_rest.h" + +// Serves the s3control api under the path /v20180820 +class RGWRESTMgr_S3Control : public RGWRESTMgr { + friend class RGWRESTMgr_S3; // for protected get_resource_mgr() + public: + RGWRESTMgr_S3Control(); +}; -- 2.39.5