From: Ali Masarwa Date: Wed, 3 Dec 2025 17:19:09 +0000 (+0200) Subject: RGW: add support global CORS rule X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=3d6f0fe0f81c0e45dea8bf0f912e34a7525c96e4;p=ceph.git RGW: add support global CORS rule Signed-off-by: Ali Masarwa --- diff --git a/doc/radosgw/config-ref.rst b/doc/radosgw/config-ref.rst index 246bd43b604..37cb646f20c 100644 --- a/doc/radosgw/config-ref.rst +++ b/doc/radosgw/config-ref.rst @@ -365,3 +365,16 @@ Cloud Restore feature currently enables the restoration of objects transitioned .. confval:: rgw_restore_processor_period These values can be tuned based upon your specific workload to further increase the aggressiveness of restore processing. + +Global CORS +=========== + +Configuration options that allows administrators to define a global CORS policy applied at the gateway level, affecting all buckets served by RGW. +Previously, CORS (Cross-Origin Resource Sharing) support in RGW is limited to per-bucket configuration using the Get/PutBucketCors API, following the standard AWS S3 behavior. +While this suffices for most use cases, it becomes a blocker when using browser-based tools that require interaction with gateway-wide resources, such as listing all buckets accessible to a user. +These configuration options are necessary to enable CORS-based access for tools like S3 Browser, which need to list and create buckets across the gateway from a browser client. + +.. confval:: rgw_gcors_allow_origins +.. confval:: rgw_gcors_allow_headers +.. confval:: rgw_gcors_allow_methods +.. confval:: rgw_gcors_expose_headers diff --git a/qa/workunits/rgw/test_rgw_global_cors.py b/qa/workunits/rgw/test_rgw_global_cors.py new file mode 100644 index 00000000000..5ae9800d7b5 --- /dev/null +++ b/qa/workunits/rgw/test_rgw_global_cors.py @@ -0,0 +1,80 @@ +import requests +from requests_aws4auth import AWS4Auth +import json +try: + import xmltodict +except ModuleNotFoundError: + print("Module 'xmltodict' is not installed.") + + +ACCESS_KEY = '0555b35654ad1656d804' +SECRET_KEY = 'h7GhxuBLTrlhVUyxSPUKUV8r/2EI4ngqJxD7iBdBYLhwluN30JaT3Q==' +REGION = 'us-east-1' # Replace with your bucket's region + +# 1. Initialize AWS4Auth for Signature Version 4 +# This generates the signature and handles the Authorization header automatically +auth = AWS4Auth(ACCESS_KEY, SECRET_KEY, REGION, 's3') + +url = 'http://localhost:8000/' +origin = "https://www.your-website.com" +custom_headers = 'Content-Type,Authorization' +methods = 'GET,PUT, DELETE' + +# 1. Step: Send the Preflight OPTIONS Request +preflight_headers = { + "Host": "8.8.8.8", + 'Origin': origin, + 'Access-Control-Request-Method': methods, + 'Access-Control-Request-Headers': custom_headers +} + +# 2. Step: Send the Actual GET Request +actual_headers = { + "Host": "8.8.8.8", + 'Origin': origin, +} + +def list_buckets(): + print("Listing all buckets...") + url = 'http://localhost:8000/' + response = requests.get(url, headers=actual_headers, auth=auth) + print(f"Actual Response Status: {response.status_code}") + data_dict = xmltodict.parse(response.text, process_namespaces=False) + json_data = json.dumps(data_dict["ListAllMyBucketsResult"]["Buckets"], indent=4) + print(f"Buckets: {json_data}") + +def create_bucket(bucket_name): + print(f"Creating a bucket named {bucket_name}...") + url = f'http://localhost:8000/{bucket_name}' + response = requests.put(url, headers=actual_headers, auth=auth) + print(f"Actual Response Status: {response.status_code}") + +def delete_bucket(bucket_name): + print(f"Deleting a bucket named {bucket_name}...") + url = f'http://localhost:8000/{bucket_name}' + response = requests.delete(url, headers=actual_headers, auth=auth) + print(f"Actual Response Status: {response.status_code}") + +preflight_resp = requests.options(url, headers=preflight_headers) + +print(f"Preflight Status: {preflight_resp.status_code}") + +# Check if the server allows the custom headers +allowed_headers = preflight_resp.headers.get('Access-Control-Allow-Headers', '') + +if preflight_resp.status_code in [200, 204] and custom_headers.lower() in allowed_headers.lower(): + print(f"Preflight successful! Proceeding with {methods} request...") + + list_buckets() + + create_bucket('bkt') + list_buckets() + + create_bucket('bkt2') + list_buckets() + + delete_bucket('bkt') + list_buckets() + + delete_bucket('bkt2') + list_buckets() diff --git a/src/common/options/rgw.yaml.in b/src/common/options/rgw.yaml.in index 6a85b076ca5..162b6188785 100644 --- a/src/common/options/rgw.yaml.in +++ b/src/common/options/rgw.yaml.in @@ -4642,3 +4642,27 @@ options: see_also: - rgw_enable_usage_log with_legacy: true +- name: rgw_gcors_allow_origins + type: str + level: advanced + desc: When not empty, this value is returned as a response header Access-Control-Allow-Origins. + services: + - rgw +- name: rgw_gcors_allow_headers + type: str + level: advanced + desc: When not empty, this value is returned as a response header Access-Control-Allow-Headers to preflight requests. + services: + - rgw +- name: rgw_gcors_allow_methods + type: str + level: advanced + desc: When not empty, this value is returned as a response header Access-Control-Allow-Methods. + services: + - rgw +- name: rgw_gcors_expose_headers + type: str + level: advanced + desc: When not empty, this value is returned as a response header Access-Control-Expose-Headers. + services: + - rgw diff --git a/src/rgw/radosgw-admin/radosgw-admin.cc b/src/rgw/radosgw-admin/radosgw-admin.cc index a8d22d99e02..ba431cb1d4f 100644 --- a/src/rgw/radosgw-admin/radosgw-admin.cc +++ b/src/rgw/radosgw-admin/radosgw-admin.cc @@ -14,6 +14,8 @@ #include #include +#include "rgw_cors_s3.h" + extern "C" { #include } @@ -937,6 +939,7 @@ enum class OPT { ACCOUNT_LIST, RESTORE_STATUS, RESTORE_LIST, + GLOBAL_CORS_GET, }; } @@ -1205,6 +1208,7 @@ static SimpleCmd::Commands all_cmds = { { "account list", OPT::ACCOUNT_LIST }, { "restore status", OPT::RESTORE_STATUS }, { "restore list", OPT::RESTORE_LIST }, + { "global-cors get", OPT::GLOBAL_CORS_GET}, }; static SimpleCmd::Aliases cmd_aliases = { @@ -3867,6 +3871,12 @@ int main(int argc, const char **argv) std::optional restore_status_filter; int show_restore_stats = false; + // global CORS settings + std::optional gcors_allow_origins; + std::optional gcors_allow_methods; + std::optional gcors_allow_headers; + std::optional gcors_expose_headers; + init_realm_param(cct.get(), realm_id, opt_realm_id, "rgw_realm_id"); init_realm_param(cct.get(), zonegroup_id, opt_zonegroup_id, "rgw_zonegroup_id"); init_realm_param(cct.get(), zone_id, opt_zone_id, "rgw_zone_id"); @@ -4467,6 +4477,14 @@ int main(int argc, const char **argv) restore_status_filter = val; } else if (ceph_argparse_binary_flag(args, i, &show_restore_stats, NULL, "--show-restore-stats", (char*)NULL)){ // do nothing + } else if (ceph_argparse_witharg(args, i, &val, "--allow-origin", (char*)NULL)) { + gcors_allow_origins = val; + } else if (ceph_argparse_witharg(args, i, &val, "--allow-methods", (char*)NULL)) { + gcors_allow_methods = val; + } else if (ceph_argparse_witharg(args, i, &val, "--allow-headers", (char*)NULL)) { + gcors_allow_headers = val; + } else if (ceph_argparse_witharg(args, i, &val, "--expose-headers", (char*)NULL)) { + gcors_expose_headers = val; } else if (strncmp(*i, "-", 1) == 0) { cerr << "ERROR: invalid flag " << *i << std::endl; return EINVAL; @@ -12410,5 +12428,35 @@ next: err_msg, stream_flusher, null_yield); } } + if (opt_cmd == OPT::GLOBAL_CORS_GET) { + string allow_origins, allow_headers, allow_methods, expose_headers; + ret = g_conf().get_val("rgw_gcors_allow_origins", &allow_origins); + if (ret < 0 || allow_origins.empty()) { + cerr << "ERROR in OPT::GLOBAL_CORS_GET, no rgw_gcors_allow_origins config found or empty, ret=" << ret << std::endl; + return -EINVAL; + } + ret = g_conf().get_val("rgw_gcors_allow_headers", &allow_headers); + if (ret < 0 || allow_headers.empty()) { + cerr << "ERROR in OPT::GLOBAL_CORS_GET, no rgw_gcors_allow_headers config found or empty, ret=" << ret << std::endl; + return -EINVAL; + } + ret = g_conf().get_val("rgw_gcors_allow_methods", &allow_methods); + if (ret < 0 || allow_methods.empty()) { + cerr << "ERROR in OPT::GLOBAL_CORS_GET, no rgw_gcors_allow_methods config found or empty, ret=" << ret << std::endl; + return -EINVAL; + } + ret = g_conf().get_val("rgw_gcors_expose_headers", &expose_headers); + std::optional optional_global_cors; + if (RGWCORSRule::create_rule(allow_origins.c_str(), allow_headers.c_str(), + expose_headers.c_str(), allow_methods.c_str(), optional_global_cors) < 0) { + cerr << "ERROR: couldn't create RGWCORSRule from rgw_gcors_allow_origins=" << allow_origins << + ", rgw_gcors_allow_headers=" << allow_headers << ", rgw_gcors_allow_methods=" << allow_methods << + ", rgw_gcors_expose_headers=" << expose_headers << std::endl; + return -EINVAL; + } + + optional_global_cors->dump(formatter.get()); + formatter->flush(cout); + } return 0; } diff --git a/src/rgw/rgw_cors.cc b/src/rgw/rgw_cors.cc index 049e0ab0eac..6ca6fad42c7 100644 --- a/src/rgw/rgw_cors.cc +++ b/src/rgw/rgw_cors.cc @@ -50,6 +50,7 @@ void RGWCORSRule::dump(Formatter *f) const encode_json("AllowedOrigin", allowed_origins, f); encode_json("AllowedHeader", allowed_hdrs, f); encode_json("ExposeHeader", exposable_hdrs, f); + f->close_section();//CORSRule } void RGWCORSRule::erase_origin_if_present(string& origin, bool *rule_empty) { @@ -81,6 +82,59 @@ list RGWCORSRule::generate_test_instances() return o; } +int RGWCORSRule::create_rule(const char *allow_origins, const char *allow_headers, + const char *expose_headers, const char* allowed_methods, std::optional& rule, const char *max_age) +{ + std::set o, h; + std::list e; + unsigned long a = CORS_MAX_AGE_INVALID; + const uint8_t flags = ("*"s == allowed_methods)? RGW_CORS_ALL:get_multi_cors_method_flags(allowed_methods); + + int nr_invalid_names = 0; + auto add_host = [&nr_invalid_names, &o] (auto host) { + if (validate_name_string(host) == 0) { + o.emplace(std::string{host}); + } else { + nr_invalid_names++; + } + }; + for_each_substr(allow_origins, ";,= \t", add_host); + if (o.empty() || nr_invalid_names > 0) { + return -EINVAL; + } + + if (allow_headers) { + int nr_invalid_headers = 0; + auto add_header = [&nr_invalid_headers, &h] (auto allow_header) { + if (validate_name_string(allow_header) == 0) { + h.emplace(std::string{allow_header}); + } else { + nr_invalid_headers++; + } + }; + for_each_substr(allow_headers, ";,= \t", add_header); + if (h.empty() || nr_invalid_headers > 0) { + return -EINVAL; + } + } + + if (expose_headers) { + for_each_substr(expose_headers, ";,= \t", + [&e] (auto expose_header) { + e.emplace_back(std::string(expose_header)); + }); + } + if (max_age) { + char *end = NULL; + a = strtoul(max_age, &end, 10); + if (a == ULONG_MAX) + a = CORS_MAX_AGE_INVALID; + } + + rule = RGWCORSRule(o, h, e, flags, a); + return 0; +} + /* * make attrs look-like-this * does not convert underscores or dashes diff --git a/src/rgw/rgw_cors.h b/src/rgw/rgw_cors.h index 64f85275b5a..fe4f5ecdbd9 100644 --- a/src/rgw/rgw_cors.h +++ b/src/rgw/rgw_cors.h @@ -59,6 +59,7 @@ public: std::string& get_id() { return id; } uint32_t get_max_age() { return max_age; } uint8_t get_allowed_methods() { return allowed_methods; } + bool has_origin(const std::string& s) const { return allowed_origins.contains(s); } void encode(bufferlist& bl) const { ENCODE_START(1, 1, bl); @@ -81,11 +82,13 @@ public: DECODE_FINISH(bl); } static std::list generate_test_instances(); + static int create_rule(const char *allow_origins, const char *allow_headers, + const char *expose_headers, const char* allowed_methods, std::optional& rule, const char *max_age=""); bool has_wildcard_origin(); bool is_origin_present(const char *o); void format_exp_headers(std::string& s); void erase_origin_if_present(std::string& origin, bool *rule_empty); - void dump_origins(); + void dump_origins(); void dump(Formatter *f) const; bool is_header_allowed(const char *hdr, size_t len); }; @@ -142,6 +145,23 @@ static inline uint8_t get_cors_method_flags(const char *req_meth) { else if (strcmp(req_meth, "PUT") == 0) flags = RGW_CORS_PUT; else if (strcmp(req_meth, "DELETE") == 0) flags = RGW_CORS_DELETE; else if (strcmp(req_meth, "HEAD") == 0) flags = RGW_CORS_HEAD; + else if (strcmp(req_meth, "COPY") == 0) flags = RGW_CORS_COPY; + + return flags; +} + +static inline uint8_t get_multi_cors_method_flags(const char *req_meth) { + uint8_t flags = 0; + const std::string allowed_methods(req_meth); + auto apply_flag = [&flags] (std::string_view method) { + if (method == "GET") flags |= RGW_CORS_GET; + else if (method == "POST") flags |= RGW_CORS_POST; + else if (method == "PUT") flags |= RGW_CORS_PUT; + else if (method == "DELETE") flags |= RGW_CORS_DELETE; + else if (method == "HEAD") flags |= RGW_CORS_HEAD; + else if (method == "COPY") flags |= RGW_CORS_COPY; + }; + for_each_substr(allowed_methods, ";,= \t", apply_flag); return flags; } diff --git a/src/rgw/rgw_cors_swift.h b/src/rgw/rgw_cors_swift.h index 8a998ede900..50c21b5138c 100644 --- a/src/rgw/rgw_cors_swift.h +++ b/src/rgw/rgw_cors_swift.h @@ -30,54 +30,11 @@ class RGWCORSConfiguration_SWIFT : public RGWCORSConfiguration ~RGWCORSConfiguration_SWIFT() {} int create_update(const char *allow_origins, const char *allow_headers, const char *expose_headers, const char *max_age) { - std::set o, h; - std::list e; - unsigned long a = CORS_MAX_AGE_INVALID; - uint8_t flags = RGW_CORS_ALL; - - int nr_invalid_names = 0; - auto add_host = [&nr_invalid_names, &o] (auto host) { - if (validate_name_string(host) == 0) { - o.emplace(std::string{host}); - } else { - nr_invalid_names++; - } - }; - for_each_substr(allow_origins, ";,= \t", add_host); - if (o.empty() || nr_invalid_names > 0) { + std::optional optional_rule; + if (RGWCORSRule::create_rule(allow_origins, allow_headers, expose_headers, "*", optional_rule, max_age) < 0) { return -EINVAL; } - - if (allow_headers) { - int nr_invalid_headers = 0; - auto add_header = [&nr_invalid_headers, &h] (auto allow_header) { - if (validate_name_string(allow_header) == 0) { - h.emplace(std::string{allow_header}); - } else { - nr_invalid_headers++; - } - }; - for_each_substr(allow_headers, ";,= \t", add_header); - if (h.empty() || nr_invalid_headers > 0) { - return -EINVAL; - } - } - - if (expose_headers) { - for_each_substr(expose_headers, ";,= \t", - [&e] (auto expose_header) { - e.emplace_back(std::string(expose_header)); - }); - } - if (max_age) { - char *end = NULL; - a = strtoul(max_age, &end, 10); - if (a == ULONG_MAX) - a = CORS_MAX_AGE_INVALID; - } - - RGWCORSRule rule(o, h, e, flags, a); - stack_rule(rule); + stack_rule(optional_rule.value()); return 0; } }; diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index bd3cfe2a4e2..acc0e734d05 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -575,6 +575,10 @@ int rgw_build_bucket_policies(const DoutPrefixProvider *dpp, rgw::sal::Driver* d return ret; } s->bucket_exists = false; + if (s->op_type == RGW_OP_OPTIONS_CORS) { + ldpp_dout(dpp, 0) << "NOTICE: RGW_OP_OPTIONS_CORS shouldn't return -ERR_NO_SUCH_BUCKET in case we have a global CORS!" << dendl; + return 0; + } return -ERR_NO_SUCH_BUCKET; } if (!rgw::sal::Object::empty(s->object.get())) { @@ -1818,7 +1822,7 @@ static bool validate_cors_rule_method(const DoutPrefixProvider *dpp, RGWCORSRule return false; } - uint8_t flags = get_cors_method_flags(req_meth); + uint8_t flags = get_multi_cors_method_flags(req_meth); if (rule->get_allowed_methods() & flags) { ldpp_dout(dpp, 10) << "Method " << req_meth << " is supported" << dendl; @@ -1851,7 +1855,6 @@ int RGWOp::read_bucket_cors() map::iterator aiter = s->bucket_attrs.find(RGW_ATTR_CORS); if (aiter == s->bucket_attrs.end()) { ldpp_dout(this, 20) << "no CORS configuration attr found" << dendl; - cors_exist = false; return 0; /* no CORS configuration found */ } @@ -1875,6 +1878,40 @@ int RGWOp::read_bucket_cors() return 0; } +int RGWOp::read_global_cors() +{ + string allow_origins, allow_headers, allow_methods, expose_headers; + int ret = g_conf().get_val("rgw_gcors_allow_origins", &allow_origins); + if (ret < 0 || allow_origins.empty()) { + return -EINVAL; + } + ret = g_conf().get_val("rgw_gcors_allow_headers", &allow_headers); + if (ret < 0 || allow_headers.empty()) { + return -EINVAL; + } + ret = g_conf().get_val("rgw_gcors_allow_methods", &allow_methods); + if (ret < 0 || allow_methods.empty()) { + return -EINVAL; + } + g_conf().get_val("rgw_gcors_expose_headers", &expose_headers); + if (RGWCORSRule::create_rule(allow_origins.c_str(), allow_headers.c_str(), expose_headers.c_str(), allow_methods.c_str(), + optional_global_cors) < 0) { + return -EINVAL; + } + + cors_exist = true; + + if (s->cct->_conf->subsys.should_gather()) { + XMLFormatter f; + RGWCORSRule_S3 *s3cors = static_cast(&(*optional_global_cors)); + ldpp_dout(this, 15) << "Read global RGWCORSRule"; + s3cors->to_xml(f); + f.flush(*_dout); + *_dout << dendl; + } + return 0; +} + /** CORS 6.2.6. * If any of the header field-names is not a ASCII case-insensitive match for * any of the values in list of headers do not set any additional headers and @@ -1888,6 +1925,7 @@ static void get_cors_response_headers(const DoutPrefixProvider *dpp, RGWCORSRule if (!rule->is_header_allowed((*it).c_str(), (*it).length())) { ldpp_dout(dpp, 5) << "Header " << (*it) << " is not registered in this rule" << dendl; } else { + ldpp_dout(dpp, 20) << "Header " << (*it) << " is registered in this rule" << dendl; if (hdrs.length() > 0) hdrs.append(","); hdrs.append((*it)); } @@ -1911,56 +1949,67 @@ bool RGWOp::generate_cors_headers(string& origin, string& method, string& header } /* Custom: */ + cors_exist = false; origin = orig; - int temp_op_ret = read_bucket_cors(); - if (temp_op_ret < 0) { - op_ret = temp_op_ret; - return false; - } + const int read_global_cors_ret = read_global_cors(); + const int temp_op_ret = read_bucket_cors(); if (!cors_exist) { - ldpp_dout(this, 2) << "No CORS configuration set yet for this bucket" << dendl; + ldpp_dout(this, 2) << "No global CORS or bucket CORS configuration set yet" << dendl; + op_ret = std::min(temp_op_ret, read_global_cors_ret); return false; } /* CORS 6.2.2. */ RGWCORSRule *rule = bucket_cors.host_name_rule(orig); - if (!rule) - return false; + auto is_allowed_to_generate_rule_cors_header = [this, &origin, &method, &headers, &exp_headers, &max_age] (RGWCORSRule *rule) { + if (!rule) + return false; - /* - * Set the Allowed-Origin header to a asterisk if this is allowed in the rule - * and no Authorization was send by the client - * - * The origin parameter specifies a URI that may access the resource. The browser must enforce this. - * For requests without credentials, the server may specify "*" as a wildcard, - * thereby allowing any origin to access the resource. - */ - const char *authorization = s->info.env->get("HTTP_AUTHORIZATION"); - if (!authorization && rule->has_wildcard_origin()) - origin = "*"; - - /* CORS 6.2.3. */ - const char *req_meth = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_METHOD"); - if (!req_meth) { - req_meth = s->info.method; - } + /* + * Set the Allowed-Origin header to a asterisk if this is allowed in the rule + * and no Authorization was send by the client + * + * The origin parameter specifies a URI that may access the resource. The browser must enforce this. + * For requests without credentials, the server may specify "*" as a wildcard, + * thereby allowing any origin to access the resource. + */ + const char *authorization = s->info.env->get("HTTP_AUTHORIZATION"); + if (!authorization && rule->has_wildcard_origin()) + origin = "*"; - if (req_meth) { - method = req_meth; - /* CORS 6.2.5. */ - if (!validate_cors_rule_method(this, rule, req_meth)) { - return false; + /* CORS 6.2.3. */ + const char *req_meth = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_METHOD"); + if (!req_meth) { + req_meth = s->info.method; + } + + if (req_meth) { + method = req_meth; + /* CORS 6.2.5. */ + if (!validate_cors_rule_method(this, rule, req_meth)) { + return false; + } } - } - /* CORS 6.2.4. */ - const char *req_hdrs = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_HEADERS"); + /* CORS 6.2.4. */ + const char *req_hdrs = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_HEADERS"); - /* CORS 6.2.6. */ - get_cors_response_headers(this, rule, req_hdrs, headers, exp_headers, max_age); + /* CORS 6.2.6. */ + get_cors_response_headers(this, rule, req_hdrs, headers, exp_headers, max_age); - return true; + return true; + }; + + if (is_allowed_to_generate_rule_cors_header(rule)) { + return true; + } + + if (optional_global_cors.has_value() && is_allowed_to_generate_rule_cors_header(&(*optional_global_cors))) { + return true; + } + + return false; } int rgw_policy_from_attrset(const DoutPrefixProvider *dpp, CephContext *cct, map& attrset, RGWAccessControlPolicy *policy) @@ -7030,11 +7079,38 @@ int RGWOptionsCORS::validate_cors_request(RGWCORSConfiguration *cc) { return 0; } +int RGWOptionsCORS::validate_global_cors_request(RGWCORSRule *global_cors_rule) { + ldpp_dout(this, 20) << "Validating request with global CORS" << dendl; + rule = global_cors_rule; + if (!rule) { + ldpp_dout(this, 10) << "There is no global cors rule present" << dendl; + return -ENOENT; + } + + if (!rule->is_origin_present(origin)) { + ldpp_dout(this, 10) << "There is no cors rule present for " << origin << dendl; + return -ENOENT; + } + + if (!validate_cors_rule_method(this, rule, req_meth)) { + return -ENOENT; + } + + if (!validate_cors_rule_header(this, rule, req_hdrs)) { + return -ENOENT; + } + + return 0; +} + void RGWOptionsCORS::execute(optional_yield y) { op_ret = read_bucket_cors(); - if (op_ret < 0) - return; + int ret = read_global_cors(); + if (ret < 0 && op_ret < 0) { + ldpp_dout(this, 2) << "No CORS configuration set yet for this bucket nor globally" << dendl; + return; + } origin = s->info.env->get("HTTP_ORIGIN"); if (!origin) { @@ -7055,11 +7131,13 @@ void RGWOptionsCORS::execute(optional_yield y) } req_hdrs = s->info.env->get("HTTP_ACCESS_CONTROL_REQUEST_HEADERS"); op_ret = validate_cors_request(&bucket_cors); - if (!rule) { + if ((op_ret < 0 || !rule) && optional_global_cors.has_value()) { + op_ret = validate_global_cors_request(&(*optional_global_cors)) ; + } + if (op_ret < 0 || !rule) { origin = req_meth = NULL; return; } - return; } int RGWGetRequestPayment::verify_permission(optional_yield y) @@ -9138,7 +9216,7 @@ void RGWPutBucketPolicy::send_response() set_req_state_err(s, op_ret); } dump_errno(s); - end_header(s); + end_header(s, this); } int RGWPutBucketPolicy::verify_permission(optional_yield y) diff --git a/src/rgw/rgw_op.h b/src/rgw/rgw_op.h index 4408bbb2e86..843432b644c 100644 --- a/src/rgw/rgw_op.h +++ b/src/rgw/rgw_op.h @@ -214,6 +214,7 @@ protected: req_state *s; RGWHandler *dialect_handler; rgw::sal::Driver* driver; + std::optional optional_global_cors; RGWCORSConfiguration bucket_cors; bool cors_exist; RGWQuota quota; @@ -278,6 +279,7 @@ public: this->dialect_handler = dialect_handler; } int read_bucket_cors(); + int read_global_cors(); bool generate_cors_headers(std::string& origin, std::string& method, std::string& headers, std::string& exp_headers, unsigned *max_age); virtual int verify_params() { return 0; } @@ -1929,6 +1931,7 @@ public: int verify_permission(optional_yield y) override {return 0;} int validate_cors_request(RGWCORSConfiguration *cc); + int validate_global_cors_request(RGWCORSRule *cc); void execute(optional_yield y) override; void get_response_params(std::string& allowed_hdrs, std::string& exp_hdrs, unsigned *max_age); void send_response() override = 0; diff --git a/src/rgw/rgw_process.cc b/src/rgw/rgw_process.cc index 507d4e3ff7b..9163bc1fdfe 100644 --- a/src/rgw/rgw_process.cc +++ b/src/rgw/rgw_process.cc @@ -210,6 +210,10 @@ int rgw_process_authenticated(RGWHandler_REST * const handler, ldpp_dout(op, 2) << "init op" << dendl; ret = op->init_processing(y); + if (op->get_type() == RGW_OP_OPTIONS_CORS && ret == -EINVAL) { + ldpp_dout(op, 0) << "NOTICE: RGW_OP_OPTIONS_CORS shouldn't return -EINVAL in case we have a global CORS!" << dendl; + ret = 0; + } if (ret < 0) { return ret; } @@ -351,7 +355,6 @@ int process_request(const RGWProcessEnv& penv, req->op = op; ldpp_dout(op, 10) << "op=" << typeid(*op).name() << " " << dendl; s->op_type = op->get_type(); - try { ldpp_dout(op, 2) << "verifying requester" << dendl; ret = op->verify_requester(*penv.auth_registry, yield); diff --git a/src/rgw/rgw_rest_s3.cc b/src/rgw/rgw_rest_s3.cc index 909246c221f..948d026567c 100644 --- a/src/rgw/rgw_rest_s3.cc +++ b/src/rgw/rgw_rest_s3.cc @@ -1661,7 +1661,7 @@ void RGWListBuckets_ObjStore_S3::send_response_begin(bool has_buckets) dump_start(s); // Explicitly use chunked transfer encoding so that we can stream the result // to the user without having to wait for the full length of it. - end_header(s, NULL, to_mime_type(s->format), CHUNKED_TRANSFER_ENCODING); + end_header(s, this, to_mime_type(s->format), CHUNKED_TRANSFER_ENCODING); if (! op_ret) { list_all_buckets_start(s); @@ -2764,7 +2764,7 @@ void RGWCreateBucket_ObjStore_S3::send_response() set_req_state_err(s, op_ret); } dump_errno(s); - end_header(s); + end_header(s, this); if (op_ret < 0) return; @@ -4388,7 +4388,7 @@ void RGWPutBucketEncryption_ObjStore_S3::send_response() set_req_state_err(s, op_ret); } dump_errno(s); - end_header(s); + end_header(s, this); } void RGWGetBucketEncryption_ObjStore_S3::send_response() @@ -4419,7 +4419,7 @@ void RGWDeleteBucketEncryption_ObjStore_S3::send_response() set_req_state_err(s, op_ret); dump_errno(s); - end_header(s); + end_header(s,this); } int RGWPutBucketOwnershipControls_ObjStore_S3::get_params(optional_yield y) @@ -5131,7 +5131,7 @@ void RGWPutBucketObjectLock_ObjStore_S3::send_response() set_req_state_err(s, op_ret); } dump_errno(s); - end_header(s); + end_header(s, this); } void RGWGetBucketObjectLock_ObjStore_S3::send_response() @@ -5265,6 +5265,11 @@ RGWOp *RGWHandler_REST_Service_S3::op_get() } } +RGWOp *RGWHandler_REST_Service_S3::op_options() +{ + return new RGWOptionsCORS_ObjStore_S3; +} + RGWOp *RGWHandler_REST_Service_S3::op_head() { return new RGWListBuckets_ObjStore_S3; diff --git a/src/rgw/rgw_rest_s3.h b/src/rgw/rgw_rest_s3.h index bd46763fd98..37f113ed675 100644 --- a/src/rgw/rgw_rest_s3.h +++ b/src/rgw/rgw_rest_s3.h @@ -710,6 +710,7 @@ protected: return s->info.args.exists("usage"); } RGWOp *op_get() override; + RGWOp *op_options() override; RGWOp *op_head() override; public: RGWHandler_REST_Service_S3(const rgw::auth::StrategyRegistry& auth_registry) : diff --git a/src/rgw/rgw_rest_swift.cc b/src/rgw/rgw_rest_swift.cc index bc442840fa0..d95558b7c49 100644 --- a/src/rgw/rgw_rest_swift.cc +++ b/src/rgw/rgw_rest_swift.cc @@ -294,7 +294,7 @@ void RGWListBuckets_ObjStore_SWIFT::send_response_begin(bool has_buckets) s->user_acl); dump_errno(s); dump_header(s, "Accept-Ranges", "bytes"); - end_header(s, NULL, NULL, NO_CONTENT_LENGTH, true); + end_header(s, this, NULL, NO_CONTENT_LENGTH, true); } if (! op_ret) { @@ -393,7 +393,7 @@ void RGWListBuckets_ObjStore_SWIFT::send_response_end() s->user->get_max_buckets(), s->user_acl); dump_errno(s); - end_header(s, nullptr, nullptr, s->formatter->get_len(), true); + end_header(s, this, nullptr, s->formatter->get_len(), true); } if (sent_data || s->cct->_conf->rgw_swift_enforce_content_length) {