1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab ft=cpp
9 #include "common/ceph_crypto.h"
10 #include "common/split.h"
11 #include "common/Formatter.h"
12 #include "common/utf8.h"
13 #include "common/ceph_json.h"
14 #include "common/safe_io.h"
15 #include "common/errno.h"
16 #include "auth/Crypto.h"
17 #include <boost/algorithm/string.hpp>
18 #include <boost/algorithm/string/replace.hpp>
19 #include <boost/tokenizer.hpp>
20 #define BOOST_BIND_GLOBAL_PLACEHOLDERS
21 #ifdef HAVE_WARN_IMPLICIT_CONST_INT_FLOAT_CONVERSION
22 #pragma clang diagnostic push
23 #pragma clang diagnostic ignored "-Wimplicit-const-int-float-conversion"
25 #ifdef HAVE_WARN_IMPLICIT_CONST_INT_FLOAT_CONVERSION
26 #pragma clang diagnostic pop
28 #undef BOOST_BIND_GLOBAL_PLACEHOLDERS
30 #include <liboath/oath.h>
33 #include "rgw_rest_s3.h"
34 #include "rgw_rest_s3website.h"
35 #include "rgw_rest_pubsub.h"
36 #include "rgw_auth_s3.h"
38 #include "rgw_policy_s3.h"
41 #include "rgw_cors_s3.h"
42 #include "rgw_tag_s3.h"
44 #include "rgw_client_io.h"
46 #include "rgw_keystone.h"
47 #include "rgw_auth_keystone.h"
48 #include "rgw_auth_registry.h"
50 #include "rgw_es_query.h"
52 #include <typeinfo> // for 'typeid'
55 #include "rgw_token.h"
56 #include "rgw_rest_role.h"
57 #include "rgw_crypt.h"
58 #include "rgw_crypt_sanitize.h"
59 #include "rgw_rest_user_policy.h"
61 #include "rgw_bucket_sync.h"
63 #include "services/svc_zone.h"
64 #include "services/svc_cls.h"
66 #include "include/ceph_assert.h"
68 #include "rgw_rest_sts.h"
69 #include "rgw_rest_iam.h"
71 #include "rgw_sal_rados.h"
73 #include "rgw_s3select.h"
75 #define dout_context g_ceph_context
76 #define dout_subsys ceph_subsys_rgw
80 using namespace ceph::crypto
;
82 void list_all_buckets_start(struct req_state
*s
)
84 s
->formatter
->open_array_section_in_ns("ListAllMyBucketsResult", XMLNS_AWS_S3
);
87 void list_all_buckets_end(struct req_state
*s
)
89 s
->formatter
->close_section();
92 void dump_bucket(struct req_state
*s
, rgw::sal::Bucket
& obj
)
94 s
->formatter
->open_object_section("Bucket");
95 s
->formatter
->dump_string("Name", obj
.get_name());
96 dump_time(s
, "CreationDate", obj
.get_creation_time());
97 s
->formatter
->close_section();
100 void rgw_get_errno_s3(rgw_http_error
*e
, int err_no
)
102 rgw_http_errors::const_iterator r
= rgw_http_s3_errors
.find(err_no
);
104 if (r
!= rgw_http_s3_errors
.end()) {
105 e
->http_ret
= r
->second
.first
;
106 e
->s3_code
= r
->second
.second
;
109 e
->s3_code
= "UnknownError";
113 static inline std::string
get_s3_expiration_header(
115 const ceph::real_time
& mtime
)
117 return rgw::lc::s3_expiration_header(
118 s
, s
->object
->get_key(), s
->tagset
, mtime
, s
->bucket_attrs
);
121 static inline bool get_s3_multipart_abort_header(
122 struct req_state
* s
, const ceph::real_time
& mtime
,
123 ceph::real_time
& date
, std::string
& rule_id
)
125 return rgw::lc::s3_multipart_abort_header(
126 s
, s
->object
->get_key(), mtime
, s
->bucket_attrs
, date
, rule_id
);
129 struct response_attr_param
{
131 const char *http_attr
;
134 static struct response_attr_param resp_attr_params
[] = {
135 {"response-content-type", "Content-Type"},
136 {"response-content-language", "Content-Language"},
137 {"response-expires", "Expires"},
138 {"response-cache-control", "Cache-Control"},
139 {"response-content-disposition", "Content-Disposition"},
140 {"response-content-encoding", "Content-Encoding"},
144 #define SSE_C_GROUP 1
147 int get_encryption_defaults(req_state
*s
)
149 int meta_sse_group
= 0;
150 constexpr auto sse_c_prefix
= "x-amz-server-side-encryption-customer-";
151 constexpr auto encrypt_attr
= "x-amz-server-side-encryption";
152 constexpr auto context_attr
= "x-amz-server-side-encryption-context";
153 constexpr auto kms_attr
= "x-amz-server-side-encryption-aws-kms-key-id";
154 constexpr auto bucket_key_attr
= "x-amz-server-side-encryption-bucket-key-enabled";
155 bool bucket_configuration_found
{ false };
156 bool rest_only
{ false };
158 for (auto& kv
: s
->info
.crypt_attribute_map
) {
159 if (kv
.first
.find(sse_c_prefix
) == 0)
160 meta_sse_group
|= SSE_C_GROUP
;
161 else if (kv
.first
.find(encrypt_attr
) == 0)
162 meta_sse_group
|= KMS_GROUP
;
164 if (meta_sse_group
== (SSE_C_GROUP
|KMS_GROUP
)) {
165 s
->err
.message
= "Server side error - can't do sse-c & sse-kms|sse-s3";
169 const auto& buck_attrs
= s
->bucket_attrs
;
170 auto aiter
= buck_attrs
.find(RGW_ATTR_BUCKET_ENCRYPTION_POLICY
);
171 RGWBucketEncryptionConfig bucket_encryption_conf
;
172 if (aiter
!= buck_attrs
.end()) {
173 ldpp_dout(s
, 5) << "Found RGW_ATTR_BUCKET_ENCRYPTION_POLICY on "
174 << s
->bucket_name
<< dendl
;
176 bufferlist::const_iterator iter
{&aiter
->second
};
179 bucket_encryption_conf
.decode(iter
);
180 bucket_configuration_found
= true;
181 } catch (const buffer::error
& e
) {
182 s
->err
.message
= "Server side error - can't decode bucket_encryption_conf";
183 ldpp_dout(s
, 5) << __func__
<< "decode bucket_encryption_conf failed" << dendl
;
187 if (meta_sse_group
& SSE_C_GROUP
) {
188 ldpp_dout(s
, 20) << "get_encryption_defaults: no defaults cause sse-c forced"
190 return 0; // sse-c: no defaults here
192 std::string sse_algorithm
{ bucket_encryption_conf
.sse_algorithm() };
193 auto kms_master_key_id
{ bucket_encryption_conf
.kms_master_key_id() };
194 bool bucket_key_enabled
{ bucket_encryption_conf
.bucket_key_enabled() };
195 bool kms_attr_seen
= false;
196 if (bucket_configuration_found
) {
197 ldpp_dout(s
, 5) << "RGW_ATTR_BUCKET_ENCRYPTION ALGO: "
198 << sse_algorithm
<< dendl
;
201 auto iter
= s
->info
.crypt_attribute_map
.find(encrypt_attr
);
202 if (iter
!= s
->info
.crypt_attribute_map
.end()) {
203 ldpp_dout(s
, 20) << "get_encryption_defaults: found encrypt_attr " << encrypt_attr
<< " = " << iter
->second
<< ", setting sse_algorithm to that" << dendl
;
205 sse_algorithm
= iter
->second
;
206 } else if (sse_algorithm
!= "") {
207 rgw_set_amz_meta_header(s
->info
.crypt_attribute_map
, encrypt_attr
, sse_algorithm
, OVERWRITE
);
210 iter
= s
->info
.crypt_attribute_map
.find(kms_attr
);
211 if (iter
!= s
->info
.crypt_attribute_map
.end()) {
212 ldpp_dout(s
, 20) << "get_encryption_defaults: found kms_attr " << kms_attr
<< " = " << iter
->second
<< ", setting kms_attr_seen" << dendl
;
214 s
->err
.message
= std::string("incomplete rest sse parms: ") + kms_attr
+ " not valid without kms";
215 ldpp_dout(s
, 5) << __func__
<< "argument problem: " << s
->err
.message
<< dendl
;
218 kms_attr_seen
= true;
219 } else if (!rest_only
&& kms_master_key_id
!= "") {
220 ldpp_dout(s
, 20) << "get_encryption_defaults: no kms_attr, but kms_master_key_id = " << kms_master_key_id
<< ", settig kms_attr_seen" << dendl
;
221 kms_attr_seen
= true;
222 rgw_set_amz_meta_header(s
->info
.crypt_attribute_map
, kms_attr
, kms_master_key_id
, OVERWRITE
);
225 iter
= s
->info
.crypt_attribute_map
.find(bucket_key_attr
);
226 if (iter
!= s
->info
.crypt_attribute_map
.end()) {
227 ldpp_dout(s
, 20) << "get_encryption_defaults: found bucket_key_attr " << bucket_key_attr
<< " = " << iter
->second
<< ", setting kms_attr_seen" << dendl
;
229 s
->err
.message
= std::string("incomplete rest sse parms: ") + bucket_key_attr
+ " not valid without kms";
230 ldpp_dout(s
, 5) << __func__
<< "argument problem: " << s
->err
.message
<< dendl
;
233 kms_attr_seen
= true;
234 } else if (!rest_only
&& bucket_key_enabled
) {
235 ldpp_dout(s
, 20) << "get_encryption_defaults: no bucket_key_attr, but bucket_key_enabled, setting kms_attr_seen" << dendl
;
236 kms_attr_seen
= true;
237 rgw_set_amz_meta_header(s
->info
.crypt_attribute_map
, bucket_key_attr
, "true", OVERWRITE
);
240 iter
= s
->info
.crypt_attribute_map
.find(context_attr
);
241 if (iter
!= s
->info
.crypt_attribute_map
.end()) {
242 ldpp_dout(s
, 20) << "get_encryption_defaults: found context_attr " << context_attr
<< " = " << iter
->second
<< ", setting kms_attr_seen" << dendl
;
244 s
->err
.message
= std::string("incomplete rest sse parms: ") + context_attr
+ " not valid without kms";
245 ldpp_dout(s
, 5) << __func__
<< "argument problem: " << s
->err
.message
<< dendl
;
248 kms_attr_seen
= true;
251 if (kms_attr_seen
&& sse_algorithm
== "") {
252 ldpp_dout(s
, 20) << "get_encryption_defaults: kms_attr but no algorithm, defaulting to aws_kms" << dendl
;
253 sse_algorithm
= "aws:kms";
255 for (const auto& kv
: s
->info
.crypt_attribute_map
) {
256 ldpp_dout(s
, 20) << "get_encryption_defaults: final map: " << kv
.first
<< " = " << kv
.second
<< dendl
;
258 ldpp_dout(s
, 20) << "get_encryption_defaults: kms_attr_seen is " << kms_attr_seen
<< " and sse_algorithm is " << sse_algorithm
<< dendl
;
259 if (kms_attr_seen
&& sse_algorithm
!= "aws:kms") {
260 s
->err
.message
= "algorithm <" + sse_algorithm
+ "> but got sse-kms attributes";
267 int RGWGetObj_ObjStore_S3Website::send_response_data(bufferlist
& bl
, off_t bl_ofs
, off_t bl_len
) {
268 map
<string
, bufferlist
>::iterator iter
;
269 iter
= attrs
.find(RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION
);
270 if (iter
!= attrs
.end()) {
271 bufferlist
&bl
= iter
->second
;
272 s
->redirect
= bl
.c_str();
273 s
->err
.http_ret
= 301;
274 ldpp_dout(this, 20) << __CEPH_ASSERT_FUNCTION
<< " redirecting per x-amz-website-redirect-location=" << s
->redirect
<< dendl
;
275 op_ret
= -ERR_WEBSITE_REDIRECT
;
276 set_req_state_err(s
, op_ret
);
278 dump_content_length(s
, 0);
279 dump_redirect(s
, s
->redirect
);
283 return RGWGetObj_ObjStore_S3::send_response_data(bl
, bl_ofs
, bl_len
);
287 int RGWGetObj_ObjStore_S3Website::send_response_data_error(optional_yield y
)
289 return RGWGetObj_ObjStore_S3::send_response_data_error(y
);
292 int RGWGetObj_ObjStore_S3::get_params(optional_yield y
)
294 // for multisite sync requests, only read the slo manifest itself, rather than
295 // all of the data from its parts. the parts will sync as separate objects
296 skip_manifest
= s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"sync-manifest");
298 // multisite sync requests should fetch encrypted data, along with the
299 // attributes needed to support decryption on the other zone
300 if (s
->system_request
) {
301 skip_decrypt
= s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"skip-decrypt");
304 return RGWGetObj_ObjStore::get_params(y
);
307 int RGWGetObj_ObjStore_S3::send_response_data_error(optional_yield y
)
310 return send_response_data(bl
, 0 , 0);
314 int decode_attr_bl_single_value(map
<string
, bufferlist
>& attrs
, const char *attr_name
, T
*result
, T def_val
)
316 map
<string
, bufferlist
>::iterator iter
= attrs
.find(attr_name
);
317 if (iter
== attrs
.end()) {
321 bufferlist
& bl
= iter
->second
;
322 if (bl
.length() == 0) {
326 auto bliter
= bl
.cbegin();
328 decode(*result
, bliter
);
329 } catch (buffer::error
& err
) {
335 inline bool str_has_cntrl(const std::string s
) {
336 return std::any_of(s
.begin(), s
.end(), ::iscntrl
);
339 inline bool str_has_cntrl(const char* s
) {
341 return str_has_cntrl(_s
);
344 int RGWGetObj_ObjStore_S3::send_response_data(bufferlist
& bl
, off_t bl_ofs
,
347 const char *content_type
= NULL
;
348 string content_type_str
;
349 map
<string
, string
> response_attrs
;
350 map
<string
, string
>::iterator riter
;
351 bufferlist metadata_bl
;
353 string expires
= get_s3_expiration_header(s
, lastmod
);
358 if (custom_http_ret
) {
359 set_req_state_err(s
, 0);
360 dump_errno(s
, custom_http_ret
);
362 set_req_state_err(s
, (partial_content
&& !op_ret
) ? STATUS_PARTIAL_CONTENT
371 dump_range(s
, start
, end
, s
->obj_size
);
373 if (s
->system_request
&&
374 s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"prepend-metadata")) {
376 dump_header(s
, "Rgwx-Object-Size", (long long)total_len
);
380 * in this case, we're not returning the object's content, only the prepended
386 /* JSON encode object metadata */
388 jf
.open_object_section("obj_metadata");
389 encode_json("attrs", attrs
, &jf
);
391 encode_json("mtime", ut
, &jf
);
395 metadata_bl
.append(ss
.str());
396 dump_header(s
, "Rgwx-Embedded-Metadata-Len", metadata_bl
.length());
397 total_len
+= metadata_bl
.length();
400 if (s
->system_request
&& !real_clock::is_zero(lastmod
)) {
401 /* we end up dumping mtime in two different methods, a bit redundant */
402 dump_epoch_header(s
, "Rgwx-Mtime", lastmod
);
404 int r
= decode_attr_bl_single_value(attrs
, RGW_ATTR_PG_VER
, &pg_ver
, (uint64_t)0);
406 ldpp_dout(this, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl
;
408 dump_header(s
, "Rgwx-Obj-PG-Ver", pg_ver
);
410 uint32_t source_zone_short_id
= 0;
411 r
= decode_attr_bl_single_value(attrs
, RGW_ATTR_SOURCE_ZONE
, &source_zone_short_id
, (uint32_t)0);
413 ldpp_dout(this, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl
;
415 if (source_zone_short_id
!= 0) {
416 dump_header(s
, "Rgwx-Source-Zone-Short-Id", source_zone_short_id
);
420 for (auto &it
: crypt_http_responses
)
421 dump_header(s
, it
.first
, it
.second
);
423 dump_content_length(s
, total_len
);
424 dump_last_modified(s
, lastmod
);
425 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
426 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
428 if (attrs
.find(RGW_ATTR_APPEND_PART_NUM
) != attrs
.end()) {
429 dump_header(s
, "x-rgw-object-type", "Appendable");
430 dump_header(s
, "x-rgw-next-append-position", s
->obj_size
);
432 dump_header(s
, "x-rgw-object-type", "Normal");
436 if (! lo_etag
.empty()) {
437 /* Handle etag of Swift API's large objects (DLO/SLO). It's entirerly
438 * legit to perform GET on them through S3 API. In such situation,
439 * a client should receive the composited content with corresponding
441 dump_etag(s
, lo_etag
);
443 auto iter
= attrs
.find(RGW_ATTR_ETAG
);
444 if (iter
!= attrs
.end()) {
445 dump_etag(s
, iter
->second
.to_str());
449 for (struct response_attr_param
*p
= resp_attr_params
; p
->param
; p
++) {
451 string val
= s
->info
.args
.get(p
->param
, &exists
);
453 /* reject unauthenticated response header manipulation, see
454 * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html */
455 if (s
->auth
.identity
->is_anonymous()) {
456 return -ERR_INVALID_REQUEST
;
458 /* HTTP specification says no control characters should be present in
459 * header values: https://tools.ietf.org/html/rfc7230#section-3.2
460 * field-vchar = VCHAR / obs-text
462 * Failure to validate this permits a CRLF injection in HTTP headers,
463 * whereas S3 GetObject only permits specific headers.
465 if(str_has_cntrl(val
)) {
466 /* TODO: return a more distinct error in future;
467 * stating what the problem is */
468 return -ERR_INVALID_REQUEST
;
471 if (strcmp(p
->param
, "response-content-type") != 0) {
472 response_attrs
[p
->http_attr
] = val
;
474 content_type_str
= val
;
475 content_type
= content_type_str
.c_str();
480 for (auto iter
= attrs
.begin(); iter
!= attrs
.end(); ++iter
) {
481 const char *name
= iter
->first
.c_str();
482 map
<string
, string
>::iterator aiter
= rgw_to_http_attrs
.find(name
);
483 if (aiter
!= rgw_to_http_attrs
.end()) {
484 if (response_attrs
.count(aiter
->second
) == 0) {
485 /* Was not already overridden by a response param. */
487 size_t len
= iter
->second
.length();
488 string
s(iter
->second
.c_str(), len
);
489 while (len
&& !s
[len
- 1]) {
493 response_attrs
[aiter
->second
] = s
;
495 } else if (iter
->first
.compare(RGW_ATTR_CONTENT_TYPE
) == 0) {
496 /* Special handling for content_type. */
498 content_type_str
= rgw_bl_str(iter
->second
);
499 content_type
= content_type_str
.c_str();
501 } else if (strcmp(name
, RGW_ATTR_SLO_UINDICATOR
) == 0) {
502 // this attr has an extra length prefix from encode() in prior versions
503 dump_header(s
, "X-Object-Meta-Static-Large-Object", "True");
504 } else if (strncmp(name
, RGW_ATTR_META_PREFIX
,
505 sizeof(RGW_ATTR_META_PREFIX
)-1) == 0) {
506 /* User custom metadata. */
507 name
+= sizeof(RGW_ATTR_PREFIX
) - 1;
508 dump_header(s
, name
, iter
->second
);
509 } else if (iter
->first
.compare(RGW_ATTR_TAGS
) == 0) {
512 auto it
= iter
->second
.cbegin();
514 } catch (buffer::error
&err
) {
515 ldpp_dout(this,0) << "Error caught buffer::error couldn't decode TagSet " << dendl
;
517 dump_header(s
, RGW_AMZ_TAG_COUNT
, obj_tags
.count());
518 } else if (iter
->first
.compare(RGW_ATTR_OBJECT_RETENTION
) == 0 && get_retention
){
519 RGWObjectRetention retention
;
521 decode(retention
, iter
->second
);
522 dump_header(s
, "x-amz-object-lock-mode", retention
.get_mode());
523 string date
= ceph::to_iso_8601(retention
.get_retain_until_date());
524 dump_header(s
, "x-amz-object-lock-retain-until-date", date
.c_str());
525 } catch (buffer::error
& err
) {
526 ldpp_dout(this, 0) << "ERROR: failed to decode RGWObjectRetention" << dendl
;
528 } else if (iter
->first
.compare(RGW_ATTR_OBJECT_LEGAL_HOLD
) == 0 && get_legal_hold
) {
529 RGWObjectLegalHold legal_hold
;
531 decode(legal_hold
, iter
->second
);
532 dump_header(s
, "x-amz-object-lock-legal-hold",legal_hold
.get_status());
533 } catch (buffer::error
& err
) {
534 ldpp_dout(this, 0) << "ERROR: failed to decode RGWObjectLegalHold" << dendl
;
541 for (riter
= response_attrs
.begin(); riter
!= response_attrs
.end();
543 dump_header(s
, riter
->first
, riter
->second
);
546 if (op_ret
== -ERR_NOT_MODIFIED
) {
550 content_type
= "binary/octet-stream";
552 end_header(s
, this, content_type
);
555 if (metadata_bl
.length()) {
556 dump_body(s
, metadata_bl
);
561 if (get_data
&& !op_ret
) {
562 int r
= dump_body(s
, bl
.c_str() + bl_ofs
, bl_len
);
570 int RGWGetObj_ObjStore_S3::get_decrypt_filter(std::unique_ptr
<RGWGetObj_Filter
> *filter
, RGWGetObj_Filter
* cb
, bufferlist
* manifest_bl
)
572 if (skip_decrypt
) { // bypass decryption for multisite sync requests
577 std::unique_ptr
<BlockCrypt
> block_crypt
;
578 res
= rgw_s3_prepare_decrypt(s
, attrs
, &block_crypt
, crypt_http_responses
);
580 if (block_crypt
!= nullptr) {
581 auto f
= std::make_unique
<RGWGetObj_BlockDecrypt
>(s
, s
->cct
, cb
, std::move(block_crypt
));
582 if (manifest_bl
!= nullptr) {
583 res
= f
->read_manifest(this, *manifest_bl
);
585 *filter
= std::move(f
);
592 int RGWGetObj_ObjStore_S3::verify_requester(const rgw::auth::StrategyRegistry
& auth_registry
, optional_yield y
)
595 ret
= RGWOp::verify_requester(auth_registry
, y
);
596 if(!s
->user
->get_caps().check_cap("amz-cache", RGW_CAP_READ
) && !ret
&& s
->info
.env
->exists("HTTP_X_AMZ_CACHE"))
597 ret
= override_range_hdr(auth_registry
, y
);
601 int RGWGetObj_ObjStore_S3::override_range_hdr(const rgw::auth::StrategyRegistry
& auth_registry
, optional_yield y
)
604 ldpp_dout(this, 10) << "cache override headers" << dendl
;
605 RGWEnv
* rgw_env
= const_cast<RGWEnv
*>(s
->info
.env
);
606 const char* backup_range
= rgw_env
->get("HTTP_RANGE");
607 const char hdrs_split
[2] = {(char)178,'\0'};
608 const char kv_split
[2] = {(char)177,'\0'};
609 const char* cache_hdr
= rgw_env
->get("HTTP_X_AMZ_CACHE");
610 for (std::string_view hdr
: ceph::split(cache_hdr
, hdrs_split
)) {
611 auto kv
= ceph::split(hdr
, kv_split
);
613 if (std::distance(k
, kv
.end()) != 2) {
616 auto v
= std::next(k
);
617 std::string key
= "HTTP_";
619 boost::replace_all(key
, "-", "_");
620 rgw_env
->set(std::move(key
), std::string(*v
));
621 ldpp_dout(this, 10) << "after splitting cache kv key: " << key
<< " " << rgw_env
->get(key
.c_str()) << dendl
;
623 ret
= RGWOp::verify_requester(auth_registry
, y
);
624 if(!ret
&& backup_range
) {
625 rgw_env
->set("HTTP_RANGE",backup_range
);
627 rgw_env
->remove("HTTP_RANGE");
633 void RGWGetObjTags_ObjStore_S3::send_response_data(bufferlist
& bl
)
636 end_header(s
, this, "application/xml");
639 s
->formatter
->open_object_section_in_ns("Tagging", XMLNS_AWS_S3
);
640 s
->formatter
->open_object_section("TagSet");
642 RGWObjTagSet_S3 tagset
;
643 auto iter
= bl
.cbegin();
646 } catch (buffer::error
& err
) {
647 ldpp_dout(this,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl
;
651 tagset
.dump_xml(s
->formatter
);
653 s
->formatter
->close_section();
654 s
->formatter
->close_section();
655 rgw_flush_formatter_and_reset(s
, s
->formatter
);
659 int RGWPutObjTags_ObjStore_S3::get_params(optional_yield y
)
667 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
671 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
676 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
677 return -ERR_MALFORMED_XML
;
680 RGWObjTagging_S3 tagging
;
683 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
684 } catch (RGWXMLDecoder::err
& err
) {
685 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
686 return -ERR_MALFORMED_XML
;
690 r
= tagging
.rebuild(obj_tags
);
694 obj_tags
.encode(tags_bl
);
695 ldpp_dout(this, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
700 void RGWPutObjTags_ObjStore_S3::send_response()
703 set_req_state_err(s
, op_ret
);
705 end_header(s
, this, "application/xml");
710 void RGWDeleteObjTags_ObjStore_S3::send_response()
716 r
= STATUS_NO_CONTENT
;
718 set_req_state_err(s
, r
);
723 void RGWGetBucketTags_ObjStore_S3::send_response_data(bufferlist
& bl
)
726 set_req_state_err(s
, op_ret
);
728 end_header(s
, this, "application/xml");
732 s
->formatter
->open_object_section_in_ns("Tagging", XMLNS_AWS_S3
);
733 s
->formatter
->open_object_section("TagSet");
735 RGWObjTagSet_S3 tagset
;
736 auto iter
= bl
.cbegin();
739 } catch (buffer::error
& err
) {
740 ldpp_dout(this,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl
;
744 tagset
.dump_xml(s
->formatter
);
746 s
->formatter
->close_section();
747 s
->formatter
->close_section();
748 rgw_flush_formatter_and_reset(s
, s
->formatter
);
752 int RGWPutBucketTags_ObjStore_S3::get_params(const DoutPrefixProvider
*dpp
, optional_yield y
)
760 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
764 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
769 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
770 return -ERR_MALFORMED_XML
;
773 RGWObjTagging_S3 tagging
;
775 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
776 } catch (RGWXMLDecoder::err
& err
) {
778 ldpp_dout(dpp
, 5) << "Malformed tagging request: " << err
<< dendl
;
779 return -ERR_MALFORMED_XML
;
782 RGWObjTags
obj_tags(50); // A tag set can contain as many as 50 tags, or it can be empty.
783 r
= tagging
.rebuild(obj_tags
);
787 obj_tags
.encode(tags_bl
);
788 ldpp_dout(dpp
, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
790 // forward bucket tags requests to meta master zone
791 if (!store
->is_meta_master()) {
792 /* only need to keep this data around if we're not meta master */
793 in_data
= std::move(data
);
799 void RGWPutBucketTags_ObjStore_S3::send_response()
802 set_req_state_err(s
, op_ret
);
804 end_header(s
, this, "application/xml");
808 void RGWDeleteBucketTags_ObjStore_S3::send_response()
811 set_req_state_err(s
, op_ret
);
813 end_header(s
, this, "application/xml");
819 bool is_valid_status(const string
& s
) {
820 return (s
== "Enabled" ||
824 static string enabled_group_id
= "s3-bucket-replication:enabled";
825 static string disabled_group_id
= "s3-bucket-replication:disabled";
827 struct ReplicationConfiguration
{
831 struct DeleteMarkerReplication
{
834 void decode_xml(XMLObj
*obj
) {
835 RGWXMLDecoder::decode_xml("Status", status
, obj
);
838 void dump_xml(Formatter
*f
) const {
839 encode_xml("Status", status
, f
);
842 bool is_valid(CephContext
*cct
) const {
843 bool result
= is_valid_status(status
);
845 ldout(cct
, 5) << "NOTICE: bad status provided in DeleteMarkerReplication element (status=" << status
<< ")" << dendl
;
851 struct Source
{ /* rgw extension */
852 std::vector
<string
> zone_names
;
854 void decode_xml(XMLObj
*obj
) {
855 RGWXMLDecoder::decode_xml("Zone", zone_names
, obj
);
858 void dump_xml(Formatter
*f
) const {
859 encode_xml("Zone", zone_names
, f
);
864 struct AccessControlTranslation
{
867 void decode_xml(XMLObj
*obj
) {
868 RGWXMLDecoder::decode_xml("Owner", owner
, obj
);
870 void dump_xml(Formatter
*f
) const {
871 encode_xml("Owner", owner
, f
);
875 std::optional
<AccessControlTranslation
> acl_translation
;
876 std::optional
<string
> account
;
878 std::optional
<string
> storage_class
;
879 std::vector
<string
> zone_names
;
881 void decode_xml(XMLObj
*obj
) {
882 RGWXMLDecoder::decode_xml("AccessControlTranslation", acl_translation
, obj
);
883 RGWXMLDecoder::decode_xml("Account", account
, obj
);
884 if (account
&& account
->empty()) {
887 RGWXMLDecoder::decode_xml("Bucket", bucket
, obj
);
888 RGWXMLDecoder::decode_xml("StorageClass", storage_class
, obj
);
889 if (storage_class
&& storage_class
->empty()) {
890 storage_class
.reset();
892 RGWXMLDecoder::decode_xml("Zone", zone_names
, obj
); /* rgw extension */
895 void dump_xml(Formatter
*f
) const {
896 encode_xml("AccessControlTranslation", acl_translation
, f
);
897 encode_xml("Account", account
, f
);
898 encode_xml("Bucket", bucket
, f
);
899 encode_xml("StorageClass", storage_class
, f
);
900 encode_xml("Zone", zone_names
, f
);
910 return key
.empty() && value
.empty();
913 void decode_xml(XMLObj
*obj
) {
914 RGWXMLDecoder::decode_xml("Key", key
, obj
);
915 RGWXMLDecoder::decode_xml("Value", value
, obj
);
918 void dump_xml(Formatter
*f
) const {
919 encode_xml("Key", key
, f
);
920 encode_xml("Value", value
, f
);
925 std::optional
<string
> prefix
;
926 std::vector
<Tag
> tags
;
933 void decode_xml(XMLObj
*obj
) {
934 std::vector
<Tag
> _tags
;
935 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
936 if (prefix
&& prefix
->empty()) {
939 RGWXMLDecoder::decode_xml("Tag", _tags
, obj
);
940 for (auto& t
: _tags
) {
942 tags
.push_back(std::move(t
));
947 void dump_xml(Formatter
*f
) const {
948 encode_xml("Prefix", prefix
, f
);
949 encode_xml("Tag", tags
, f
);
953 std::optional
<string
> prefix
;
954 std::optional
<Tag
> tag
;
955 std::optional
<AndElements
> and_elements
;
958 return (!prefix
&& !tag
&& !and_elements
);
961 void decode_xml(XMLObj
*obj
) {
962 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
963 if (prefix
&& prefix
->empty()) {
966 RGWXMLDecoder::decode_xml("Tag", tag
, obj
);
967 if (tag
&& tag
->empty()) {
970 RGWXMLDecoder::decode_xml("And", and_elements
, obj
);
971 if (and_elements
&& and_elements
->empty()) {
972 and_elements
.reset();
976 void dump_xml(Formatter
*f
) const {
977 encode_xml("Prefix", prefix
, f
);
978 encode_xml("Tag", tag
, f
);
979 encode_xml("And", and_elements
, f
);
982 bool is_valid(CephContext
*cct
) const {
984 ldout(cct
, 5) << "NOTICE: both tag and prefix were provided in replication filter rule" << dendl
;
989 if (prefix
&& and_elements
->prefix
) {
990 ldout(cct
, 5) << "NOTICE: too many prefixes were provided in re" << dendl
;
997 int to_sync_pipe_filter(CephContext
*cct
,
998 rgw_sync_pipe_filter
*f
) const {
999 if (!is_valid(cct
)) {
1003 f
->prefix
= *prefix
;
1006 f
->tags
.insert(rgw_sync_pipe_filter_tag(tag
->key
, tag
->value
));
1010 if (and_elements
->prefix
) {
1011 f
->prefix
= *and_elements
->prefix
;
1013 for (auto& t
: and_elements
->tags
) {
1014 f
->tags
.insert(rgw_sync_pipe_filter_tag(t
.key
, t
.value
));
1020 void from_sync_pipe_filter(const rgw_sync_pipe_filter
& f
) {
1021 if (f
.prefix
&& f
.tags
.empty()) {
1026 and_elements
.emplace();
1027 and_elements
->prefix
= f
.prefix
;
1028 } else if (f
.tags
.size() == 1) {
1029 auto iter
= f
.tags
.begin();
1030 if (iter
== f
.tags
.end()) {
1031 /* should never happen */
1037 tag
->value
= t
.value
;
1041 if (f
.tags
.empty()) {
1045 if (!and_elements
) {
1046 and_elements
.emplace();
1049 for (auto& t
: f
.tags
) {
1050 auto& tag
= and_elements
->tags
.emplace_back();
1052 tag
.value
= t
.value
;
1057 set
<rgw_zone_id
> get_zone_ids_from_names(rgw::sal::Store
* store
,
1058 const vector
<string
>& zone_names
) const {
1059 set
<rgw_zone_id
> ids
;
1061 for (auto& name
: zone_names
) {
1063 if (static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->find_zone_id_by_name(name
, &id
)) {
1064 ids
.insert(std::move(id
));
1071 vector
<string
> get_zone_names_from_ids(rgw::sal::Store
* store
,
1072 const set
<rgw_zone_id
>& zone_ids
) const {
1073 vector
<string
> names
;
1075 for (auto& id
: zone_ids
) {
1077 if (static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->find_zone(id
, &zone
)) {
1078 names
.emplace_back(zone
->name
);
1085 std::optional
<DeleteMarkerReplication
> delete_marker_replication
;
1086 std::optional
<Source
> source
;
1087 Destination destination
;
1088 std::optional
<Filter
> filter
;
1093 void decode_xml(XMLObj
*obj
) {
1094 RGWXMLDecoder::decode_xml("DeleteMarkerReplication", delete_marker_replication
, obj
);
1095 RGWXMLDecoder::decode_xml("Source", source
, obj
);
1096 RGWXMLDecoder::decode_xml("Destination", destination
, obj
);
1097 RGWXMLDecoder::decode_xml("ID", id
, obj
);
1099 std::optional
<string
> prefix
;
1100 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
1103 filter
->prefix
= prefix
;
1107 RGWXMLDecoder::decode_xml("Filter", filter
, obj
);
1109 /* don't want to have filter reset because it might have been initialized
1110 * when decoding prefix
1112 RGWXMLDecoder::decode_xml("Filter", *filter
, obj
);
1115 RGWXMLDecoder::decode_xml("Priority", priority
, obj
);
1116 RGWXMLDecoder::decode_xml("Status", status
, obj
);
1119 void dump_xml(Formatter
*f
) const {
1120 encode_xml("DeleteMarkerReplication", delete_marker_replication
, f
);
1121 encode_xml("Source", source
, f
);
1122 encode_xml("Destination", destination
, f
);
1123 encode_xml("Filter", filter
, f
);
1124 encode_xml("ID", id
, f
);
1125 encode_xml("Priority", priority
, f
);
1126 encode_xml("Status", status
, f
);
1129 bool is_valid(CephContext
*cct
) const {
1130 if (!is_valid_status(status
)) {
1131 ldout(cct
, 5) << "NOTICE: bad status provided in rule (status=" << status
<< ")" << dendl
;
1134 if ((filter
&& !filter
->is_valid(cct
)) ||
1135 (delete_marker_replication
&& !delete_marker_replication
->is_valid(cct
))) {
1141 int to_sync_policy_pipe(req_state
*s
, rgw::sal::Store
* store
,
1142 rgw_sync_bucket_pipes
*pipe
,
1143 bool *enabled
) const {
1144 if (!is_valid(s
->cct
)) {
1149 pipe
->params
.priority
= priority
;
1151 const auto& user_id
= s
->user
->get_id();
1153 rgw_bucket_key
dest_bk(user_id
.tenant
,
1154 destination
.bucket
);
1156 if (source
&& !source
->zone_names
.empty()) {
1157 pipe
->source
.zones
= get_zone_ids_from_names(store
, source
->zone_names
);
1159 pipe
->source
.set_all_zones(true);
1161 if (!destination
.zone_names
.empty()) {
1162 pipe
->dest
.zones
= get_zone_ids_from_names(store
, destination
.zone_names
);
1164 pipe
->dest
.set_all_zones(true);
1166 pipe
->dest
.bucket
.emplace(dest_bk
);
1169 int r
= filter
->to_sync_pipe_filter(s
->cct
, &pipe
->params
.source
.filter
);
1174 if (destination
.acl_translation
) {
1176 u
.tenant
= user_id
.tenant
;
1177 u
.from_str(destination
.acl_translation
->owner
); /* explicit tenant will override tenant,
1178 otherwise will inherit it from s->user */
1179 pipe
->params
.dest
.acl_translation
.emplace();
1180 pipe
->params
.dest
.acl_translation
->owner
= u
;
1182 pipe
->params
.dest
.storage_class
= destination
.storage_class
;
1184 *enabled
= (status
== "Enabled");
1186 pipe
->params
.mode
= rgw_sync_pipe_params::Mode::MODE_USER
;
1187 pipe
->params
.user
= user_id
.to_str();
1192 void from_sync_policy_pipe(rgw::sal::Store
* store
,
1193 const rgw_sync_bucket_pipes
& pipe
,
1196 status
= (enabled
? "Enabled" : "Disabled");
1197 priority
= pipe
.params
.priority
;
1199 if (pipe
.source
.all_zones
) {
1201 } else if (pipe
.source
.zones
) {
1203 source
->zone_names
= get_zone_names_from_ids(store
, *pipe
.source
.zones
);
1206 if (!pipe
.dest
.all_zones
&&
1208 destination
.zone_names
= get_zone_names_from_ids(store
, *pipe
.dest
.zones
);
1211 if (pipe
.params
.dest
.acl_translation
) {
1212 destination
.acl_translation
.emplace();
1213 destination
.acl_translation
->owner
= pipe
.params
.dest
.acl_translation
->owner
.to_str();
1216 if (pipe
.params
.dest
.storage_class
) {
1217 destination
.storage_class
= *pipe
.params
.dest
.storage_class
;
1220 if (pipe
.dest
.bucket
) {
1221 destination
.bucket
= pipe
.dest
.bucket
->get_key();
1225 filter
->from_sync_pipe_filter(pipe
.params
.source
.filter
);
1227 if (filter
->empty()) {
1233 std::vector
<Rule
> rules
;
1235 void decode_xml(XMLObj
*obj
) {
1236 RGWXMLDecoder::decode_xml("Role", role
, obj
);
1237 RGWXMLDecoder::decode_xml("Rule", rules
, obj
);
1240 void dump_xml(Formatter
*f
) const {
1241 encode_xml("Role", role
, f
);
1242 encode_xml("Rule", rules
, f
);
1245 int to_sync_policy_groups(req_state
*s
, rgw::sal::Store
* store
,
1246 vector
<rgw_sync_policy_group
> *result
) const {
1249 rgw_sync_policy_group
& enabled_group
= (*result
)[0];
1250 rgw_sync_policy_group
& disabled_group
= (*result
)[1];
1252 enabled_group
.id
= enabled_group_id
;
1253 enabled_group
.status
= rgw_sync_policy_group::Status::ENABLED
;
1254 disabled_group
.id
= disabled_group_id
;
1255 disabled_group
.status
= rgw_sync_policy_group::Status::ALLOWED
; /* not enabled, not forbidden */
1257 for (auto& rule
: rules
) {
1258 rgw_sync_bucket_pipes pipe
;
1260 int r
= rule
.to_sync_policy_pipe(s
, store
, &pipe
, &enabled
);
1262 ldpp_dout(s
, 5) << "NOTICE: failed to convert replication configuration into sync policy pipe (rule.id=" << rule
.id
<< "): " << cpp_strerror(-r
) << dendl
;
1267 enabled_group
.pipes
.emplace_back(std::move(pipe
));
1269 disabled_group
.pipes
.emplace_back(std::move(pipe
));
1275 void from_sync_policy_group(rgw::sal::Store
* store
,
1276 const rgw_sync_policy_group
& group
) {
1278 bool enabled
= (group
.status
== rgw_sync_policy_group::Status::ENABLED
);
1280 for (auto& pipe
: group
.pipes
) {
1281 auto& rule
= rules
.emplace_back();
1282 rule
.from_sync_policy_pipe(store
, pipe
, enabled
);
1289 void RGWGetBucketReplication_ObjStore_S3::send_response_data()
1292 set_req_state_err(s
, op_ret
);
1294 end_header(s
, this, "application/xml");
1297 ReplicationConfiguration conf
;
1299 if (s
->bucket
->get_info().sync_policy
) {
1300 auto policy
= s
->bucket
->get_info().sync_policy
;
1302 auto iter
= policy
->groups
.find(enabled_group_id
);
1303 if (iter
!= policy
->groups
.end()) {
1304 conf
.from_sync_policy_group(store
, iter
->second
);
1306 iter
= policy
->groups
.find(disabled_group_id
);
1307 if (iter
!= policy
->groups
.end()) {
1308 conf
.from_sync_policy_group(store
, iter
->second
);
1313 s
->formatter
->open_object_section_in_ns("ReplicationConfiguration", XMLNS_AWS_S3
);
1314 conf
.dump_xml(s
->formatter
);
1315 s
->formatter
->close_section();
1316 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1320 int RGWPutBucketReplication_ObjStore_S3::get_params(optional_yield y
)
1322 RGWXMLParser parser
;
1324 if (!parser
.init()){
1328 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
1332 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
1337 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
1338 return -ERR_MALFORMED_XML
;
1341 ReplicationConfiguration conf
;
1343 RGWXMLDecoder::decode_xml("ReplicationConfiguration", conf
, &parser
);
1344 } catch (RGWXMLDecoder::err
& err
) {
1346 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
1347 return -ERR_MALFORMED_XML
;
1350 r
= conf
.to_sync_policy_groups(s
, store
, &sync_policy_groups
);
1355 // forward requests to meta master zone
1356 if (!store
->is_meta_master()) {
1357 /* only need to keep this data around if we're not meta master */
1358 in_data
= std::move(data
);
1364 void RGWPutBucketReplication_ObjStore_S3::send_response()
1367 set_req_state_err(s
, op_ret
);
1369 end_header(s
, this, "application/xml");
1373 void RGWDeleteBucketReplication_ObjStore_S3::update_sync_policy(rgw_sync_policy_info
*policy
)
1375 policy
->groups
.erase(enabled_group_id
);
1376 policy
->groups
.erase(disabled_group_id
);
1379 void RGWDeleteBucketReplication_ObjStore_S3::send_response()
1382 set_req_state_err(s
, op_ret
);
1384 end_header(s
, this, "application/xml");
1388 void RGWListBuckets_ObjStore_S3::send_response_begin(bool has_buckets
)
1391 set_req_state_err(s
, op_ret
);
1394 // Explicitly use chunked transfer encoding so that we can stream the result
1395 // to the user without having to wait for the full length of it.
1396 end_header(s
, NULL
, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1399 list_all_buckets_start(s
);
1400 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1401 s
->formatter
->open_array_section("Buckets");
1406 void RGWListBuckets_ObjStore_S3::send_response_data(rgw::sal::BucketList
& buckets
)
1411 auto& m
= buckets
.get_buckets();
1413 for (auto iter
= m
.begin(); iter
!= m
.end(); ++iter
) {
1414 auto& bucket
= iter
->second
;
1415 dump_bucket(s
, *bucket
);
1417 rgw_flush_formatter(s
, s
->formatter
);
1420 void RGWListBuckets_ObjStore_S3::send_response_end()
1423 s
->formatter
->close_section();
1424 list_all_buckets_end(s
);
1425 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1429 int RGWGetUsage_ObjStore_S3::get_params(optional_yield y
)
1431 start_date
= s
->info
.args
.get("start-date");
1432 end_date
= s
->info
.args
.get("end-date");
1436 static void dump_usage_categories_info(Formatter
*formatter
, const rgw_usage_log_entry
& entry
, map
<string
, bool> *categories
)
1438 formatter
->open_array_section("categories");
1439 map
<string
, rgw_usage_data
>::const_iterator uiter
;
1440 for (uiter
= entry
.usage_map
.begin(); uiter
!= entry
.usage_map
.end(); ++uiter
) {
1441 if (categories
&& !categories
->empty() && !categories
->count(uiter
->first
))
1443 const rgw_usage_data
& usage
= uiter
->second
;
1444 formatter
->open_object_section("Entry");
1445 encode_json("Category", uiter
->first
, formatter
);
1446 encode_json("BytesSent", usage
.bytes_sent
, formatter
);
1447 encode_json("BytesReceived", usage
.bytes_received
, formatter
);
1448 encode_json("Ops", usage
.ops
, formatter
);
1449 encode_json("SuccessfulOps", usage
.successful_ops
, formatter
);
1450 formatter
->close_section(); // Entry
1452 formatter
->close_section(); // Category
1455 static void dump_usage_bucket_info(Formatter
*formatter
, const std::string
& name
, const bucket_meta_entry
& entry
)
1457 formatter
->open_object_section("Entry");
1458 encode_json("Bucket", name
, formatter
);
1459 encode_json("Bytes", entry
.size
, formatter
);
1460 encode_json("Bytes_Rounded", entry
.size_rounded
, formatter
);
1461 formatter
->close_section(); // entry
1464 void RGWGetUsage_ObjStore_S3::send_response()
1467 set_req_state_err(s
, op_ret
);
1470 // Explicitly use chunked transfer encoding so that we can stream the result
1471 // to the user without having to wait for the full length of it.
1472 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1477 Formatter
*formatter
= s
->formatter
;
1479 bool user_section_open
= false;
1481 formatter
->open_object_section("Usage");
1482 if (show_log_entries
) {
1483 formatter
->open_array_section("Entries");
1485 map
<rgw_user_bucket
, rgw_usage_log_entry
>::iterator iter
;
1486 for (iter
= usage
.begin(); iter
!= usage
.end(); ++iter
) {
1487 const rgw_user_bucket
& ub
= iter
->first
;
1488 const rgw_usage_log_entry
& entry
= iter
->second
;
1490 if (show_log_entries
) {
1491 if (ub
.user
.compare(last_owner
) != 0) {
1492 if (user_section_open
) {
1493 formatter
->close_section();
1494 formatter
->close_section();
1496 formatter
->open_object_section("User");
1497 formatter
->dump_string("Owner", ub
.user
);
1498 formatter
->open_array_section("Buckets");
1499 user_section_open
= true;
1500 last_owner
= ub
.user
;
1502 formatter
->open_object_section("Bucket");
1503 formatter
->dump_string("Bucket", ub
.bucket
);
1504 utime_t
ut(entry
.epoch
, 0);
1505 ut
.gmtime(formatter
->dump_stream("Time"));
1506 formatter
->dump_int("Epoch", entry
.epoch
);
1507 dump_usage_categories_info(formatter
, entry
, &categories
);
1508 formatter
->close_section(); // bucket
1511 summary_map
[ub
.user
].aggregate(entry
, &categories
);
1514 if (show_log_entries
) {
1515 if (user_section_open
) {
1516 formatter
->close_section(); // buckets
1517 formatter
->close_section(); //user
1519 formatter
->close_section(); // entries
1523 formatter
->open_array_section("Summary");
1524 map
<string
, rgw_usage_log_entry
>::iterator siter
;
1525 for (siter
= summary_map
.begin(); siter
!= summary_map
.end(); ++siter
) {
1526 const rgw_usage_log_entry
& entry
= siter
->second
;
1527 formatter
->open_object_section("User");
1528 formatter
->dump_string("User", siter
->first
);
1529 dump_usage_categories_info(formatter
, entry
, &categories
);
1530 rgw_usage_data total_usage
;
1531 entry
.sum(total_usage
, categories
);
1532 formatter
->open_object_section("Total");
1533 encode_json("BytesSent", total_usage
.bytes_sent
, formatter
);
1534 encode_json("BytesReceived", total_usage
.bytes_received
, formatter
);
1535 encode_json("Ops", total_usage
.ops
, formatter
);
1536 encode_json("SuccessfulOps", total_usage
.successful_ops
, formatter
);
1537 formatter
->close_section(); // total
1538 formatter
->close_section(); // user
1541 if (s
->cct
->_conf
->rgw_rest_getusage_op_compat
) {
1542 formatter
->open_object_section("Stats");
1545 // send info about quota config
1546 auto user_info
= s
->user
->get_info();
1547 encode_json("QuotaMaxBytes", user_info
.user_quota
.max_size
, formatter
);
1548 encode_json("QuotaMaxBuckets", user_info
.max_buckets
, formatter
);
1549 encode_json("QuotaMaxObjCount", user_info
.user_quota
.max_objects
, formatter
);
1550 encode_json("QuotaMaxBytesPerBucket", user_info
.bucket_quota
.max_objects
, formatter
);
1551 encode_json("QuotaMaxObjCountPerBucket", user_info
.bucket_quota
.max_size
, formatter
);
1552 // send info about user's capacity utilization
1553 encode_json("TotalBytes", stats
.size
, formatter
);
1554 encode_json("TotalBytesRounded", stats
.size_rounded
, formatter
);
1555 encode_json("TotalEntries", stats
.num_objects
, formatter
);
1557 if (s
->cct
->_conf
->rgw_rest_getusage_op_compat
) {
1558 formatter
->close_section(); //Stats
1561 formatter
->close_section(); // summary
1564 formatter
->open_array_section("CapacityUsed");
1565 formatter
->open_object_section("User");
1566 formatter
->open_array_section("Buckets");
1567 for (const auto& biter
: buckets_usage
) {
1568 const bucket_meta_entry
& entry
= biter
.second
;
1569 dump_usage_bucket_info(formatter
, biter
.first
, entry
);
1571 formatter
->close_section(); // Buckets
1572 formatter
->close_section(); // User
1573 formatter
->close_section(); // CapacityUsed
1575 formatter
->close_section(); // usage
1576 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1579 int RGWListBucket_ObjStore_S3::get_common_params()
1581 list_versions
= s
->info
.args
.exists("versions");
1582 prefix
= s
->info
.args
.get("prefix");
1585 s
->info
.args
.get_bool("allow-unordered", &allow_unordered
, false);
1586 delimiter
= s
->info
.args
.get("delimiter");
1587 max_keys
= s
->info
.args
.get("max-keys");
1588 op_ret
= parse_max_keys();
1592 encoding_type
= s
->info
.args
.get("encoding-type");
1593 if (s
->system_request
) {
1594 s
->info
.args
.get_bool("objs-container", &objs_container
, false);
1595 const char *shard_id_str
= s
->info
.env
->get("HTTP_RGWX_SHARD_ID");
1598 shard_id
= strict_strtol(shard_id_str
, 10, &err
);
1600 ldpp_dout(this, 5) << "bad shard id specified: " << shard_id_str
<< dendl
;
1604 shard_id
= s
->bucket_instance_shard_id
;
1610 int RGWListBucket_ObjStore_S3::get_params(optional_yield y
)
1612 int ret
= get_common_params();
1616 if (!list_versions
) {
1617 marker
= s
->info
.args
.get("marker");
1619 marker
.name
= s
->info
.args
.get("key-marker");
1620 marker
.instance
= s
->info
.args
.get("version-id-marker");
1625 int RGWListBucket_ObjStore_S3v2::get_params(optional_yield y
)
1627 int ret
= get_common_params();
1631 s
->info
.args
.get_bool("fetch-owner", &fetchOwner
, false);
1632 startAfter
= s
->info
.args
.get("start-after", &start_after_exist
);
1633 continuation_token
= s
->info
.args
.get("continuation-token", &continuation_token_exist
);
1634 if(!continuation_token_exist
) {
1635 marker
= startAfter
;
1637 marker
= continuation_token
;
1642 void RGWListBucket_ObjStore_S3::send_common_versioned_response()
1644 if (!s
->bucket_tenant
.empty()) {
1645 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
1647 s
->formatter
->dump_string("Name", s
->bucket_name
);
1648 s
->formatter
->dump_string("Prefix", prefix
);
1649 s
->formatter
->dump_int("MaxKeys", max
);
1650 if (!delimiter
.empty()) {
1651 s
->formatter
->dump_string("Delimiter", delimiter
);
1653 s
->formatter
->dump_string("IsTruncated", (max
&& is_truncated
? "true"
1656 if (!common_prefixes
.empty()) {
1657 map
<string
, bool>::iterator pref_iter
;
1658 for (pref_iter
= common_prefixes
.begin();
1659 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1660 s
->formatter
->open_array_section("CommonPrefixes");
1662 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1664 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1667 s
->formatter
->close_section();
1672 void RGWListBucket_ObjStore_S3::send_versioned_response()
1674 s
->formatter
->open_object_section_in_ns("ListVersionsResult", XMLNS_AWS_S3
);
1675 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1676 s
->formatter
->dump_string("EncodingType", "url");
1679 RGWListBucket_ObjStore_S3::send_common_versioned_response();
1680 s
->formatter
->dump_string("KeyMarker", marker
.name
);
1681 s
->formatter
->dump_string("VersionIdMarker", marker
.instance
);
1682 if (is_truncated
&& !next_marker
.empty()) {
1683 s
->formatter
->dump_string("NextKeyMarker", next_marker
.name
);
1684 if (next_marker
.instance
.empty()) {
1685 s
->formatter
->dump_string("NextVersionIdMarker", "null");
1688 s
->formatter
->dump_string("NextVersionIdMarker", next_marker
.instance
);
1693 if (objs_container
) {
1694 s
->formatter
->open_array_section("Entries");
1697 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1698 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1699 const char *section_name
= (iter
->is_delete_marker() ? "DeleteMarker"
1701 s
->formatter
->open_object_section(section_name
);
1702 if (objs_container
) {
1703 s
->formatter
->dump_bool("IsDeleteMarker", iter
->is_delete_marker());
1705 rgw_obj_key
key(iter
->key
);
1708 url_encode(key
.name
, key_name
);
1709 s
->formatter
->dump_string("Key", key_name
);
1712 s
->formatter
->dump_string("Key", key
.name
);
1714 string version_id
= key
.instance
;
1715 if (version_id
.empty()) {
1716 version_id
= "null";
1718 if (s
->system_request
) {
1719 if (iter
->versioned_epoch
> 0) {
1720 s
->formatter
->dump_int("VersionedEpoch", iter
->versioned_epoch
);
1722 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1723 utime_t
ut(iter
->meta
.mtime
);
1724 ut
.gmtime_nsec(s
->formatter
->dump_stream("RgwxMtime"));
1726 s
->formatter
->dump_string("VersionId", version_id
);
1727 s
->formatter
->dump_bool("IsLatest", iter
->is_current());
1728 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1729 if (!iter
->is_delete_marker()) {
1730 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1731 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1732 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1733 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1735 dump_owner(s
, rgw_user(iter
->meta
.owner
), iter
->meta
.owner_display_name
);
1736 if (iter
->meta
.appendable
) {
1737 s
->formatter
->dump_string("Type", "Appendable");
1739 s
->formatter
->dump_string("Type", "Normal");
1741 s
->formatter
->close_section(); // Version/DeleteMarker
1743 if (objs_container
) {
1744 s
->formatter
->close_section(); // Entries
1746 s
->formatter
->close_section(); // ListVersionsResult
1748 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1752 void RGWListBucket_ObjStore_S3::send_common_response()
1754 if (!s
->bucket_tenant
.empty()) {
1755 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
1757 s
->formatter
->dump_string("Name", s
->bucket_name
);
1758 s
->formatter
->dump_string("Prefix", prefix
);
1759 s
->formatter
->dump_int("MaxKeys", max
);
1760 if (!delimiter
.empty()) {
1761 s
->formatter
->dump_string("Delimiter", delimiter
);
1763 s
->formatter
->dump_string("IsTruncated", (max
&& is_truncated
? "true"
1766 if (!common_prefixes
.empty()) {
1767 map
<string
, bool>::iterator pref_iter
;
1768 for (pref_iter
= common_prefixes
.begin();
1769 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1770 s
->formatter
->open_array_section("CommonPrefixes");
1772 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1774 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1776 s
->formatter
->close_section();
1781 void RGWListBucket_ObjStore_S3::send_response()
1784 set_req_state_err(s
, op_ret
);
1788 // Explicitly use chunked transfer encoding so that we can stream the result
1789 // to the user without having to wait for the full length of it.
1790 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1795 if (list_versions
) {
1796 send_versioned_response();
1800 s
->formatter
->open_object_section_in_ns("ListBucketResult", XMLNS_AWS_S3
);
1801 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1802 s
->formatter
->dump_string("EncodingType", "url");
1805 RGWListBucket_ObjStore_S3::send_common_response();
1807 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1808 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1809 rgw_obj_key
key(iter
->key
);
1810 s
->formatter
->open_array_section("Contents");
1813 url_encode(key
.name
, key_name
);
1814 s
->formatter
->dump_string("Key", key_name
);
1816 s
->formatter
->dump_string("Key", key
.name
);
1818 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1819 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1820 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1821 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1822 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1823 dump_owner(s
, rgw_user(iter
->meta
.owner
), iter
->meta
.owner_display_name
);
1824 if (s
->system_request
) {
1825 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1827 if (iter
->meta
.appendable
) {
1828 s
->formatter
->dump_string("Type", "Appendable");
1830 s
->formatter
->dump_string("Type", "Normal");
1832 s
->formatter
->close_section();
1835 s
->formatter
->dump_string("Marker", marker
.name
);
1836 if (is_truncated
&& !next_marker
.empty()) {
1837 s
->formatter
->dump_string("NextMarker", next_marker
.name
);
1839 s
->formatter
->close_section();
1840 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1843 void RGWListBucket_ObjStore_S3v2::send_versioned_response()
1845 s
->formatter
->open_object_section_in_ns("ListVersionsResult", XMLNS_AWS_S3
);
1846 RGWListBucket_ObjStore_S3v2::send_common_versioned_response();
1847 s
->formatter
->dump_string("KeyContinuationToken", marker
.name
);
1848 s
->formatter
->dump_string("VersionIdContinuationToken", marker
.instance
);
1849 if (is_truncated
&& !next_marker
.empty()) {
1850 s
->formatter
->dump_string("NextKeyContinuationToken", next_marker
.name
);
1851 s
->formatter
->dump_string("NextVersionIdContinuationToken", next_marker
.instance
);
1854 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1855 s
->formatter
->dump_string("EncodingType", "url");
1860 if (objs_container
) {
1861 s
->formatter
->open_array_section("Entries");
1864 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1865 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1866 const char *section_name
= (iter
->is_delete_marker() ? "DeleteContinuationToken"
1868 s
->formatter
->open_object_section(section_name
);
1869 if (objs_container
) {
1870 s
->formatter
->dump_bool("IsDeleteContinuationToken", iter
->is_delete_marker());
1872 rgw_obj_key
key(iter
->key
);
1875 url_encode(key
.name
, key_name
);
1876 s
->formatter
->dump_string("Key", key_name
);
1879 s
->formatter
->dump_string("Key", key
.name
);
1881 string version_id
= key
.instance
;
1882 if (version_id
.empty()) {
1883 version_id
= "null";
1885 if (s
->system_request
) {
1886 if (iter
->versioned_epoch
> 0) {
1887 s
->formatter
->dump_int("VersionedEpoch", iter
->versioned_epoch
);
1889 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1890 utime_t
ut(iter
->meta
.mtime
);
1891 ut
.gmtime_nsec(s
->formatter
->dump_stream("RgwxMtime"));
1893 s
->formatter
->dump_string("VersionId", version_id
);
1894 s
->formatter
->dump_bool("IsLatest", iter
->is_current());
1895 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1896 if (!iter
->is_delete_marker()) {
1897 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1898 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1899 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1900 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1902 if (fetchOwner
== true) {
1903 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1905 s
->formatter
->close_section();
1909 if (objs_container
) {
1910 s
->formatter
->close_section();
1913 if (!common_prefixes
.empty()) {
1914 map
<string
, bool>::iterator pref_iter
;
1915 for (pref_iter
= common_prefixes
.begin();
1916 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1917 s
->formatter
->open_array_section("CommonPrefixes");
1919 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1921 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1924 s
->formatter
->dump_int("KeyCount",objs
.size());
1925 if (start_after_exist
) {
1926 s
->formatter
->dump_string("StartAfter", startAfter
);
1928 s
->formatter
->close_section();
1932 s
->formatter
->close_section();
1933 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1937 void RGWListBucket_ObjStore_S3v2::send_response()
1940 set_req_state_err(s
, op_ret
);
1944 // Explicitly use chunked transfer encoding so that we can stream the result
1945 // to the user without having to wait for the full length of it.
1946 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1951 if (list_versions
) {
1952 send_versioned_response();
1956 s
->formatter
->open_object_section_in_ns("ListBucketResult", XMLNS_AWS_S3
);
1957 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1958 s
->formatter
->dump_string("EncodingType", "url");
1962 RGWListBucket_ObjStore_S3::send_common_response();
1964 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1965 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1966 rgw_obj_key
key(iter
->key
);
1967 s
->formatter
->open_array_section("Contents");
1970 url_encode(key
.name
, key_name
);
1971 s
->formatter
->dump_string("Key", key_name
);
1974 s
->formatter
->dump_string("Key", key
.name
);
1976 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1977 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1978 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1979 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1980 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1981 if (fetchOwner
== true) {
1982 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1984 if (s
->system_request
) {
1985 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1987 if (iter
->meta
.appendable
) {
1988 s
->formatter
->dump_string("Type", "Appendable");
1990 s
->formatter
->dump_string("Type", "Normal");
1992 s
->formatter
->close_section();
1995 if (continuation_token_exist
) {
1996 s
->formatter
->dump_string("ContinuationToken", continuation_token
);
1998 if (is_truncated
&& !next_marker
.empty()) {
1999 s
->formatter
->dump_string("NextContinuationToken", next_marker
.name
);
2001 s
->formatter
->dump_int("KeyCount", objs
.size() + common_prefixes
.size());
2002 if (start_after_exist
) {
2003 s
->formatter
->dump_string("StartAfter", startAfter
);
2005 s
->formatter
->close_section();
2006 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2009 void RGWGetBucketLogging_ObjStore_S3::send_response()
2012 end_header(s
, this, "application/xml");
2015 s
->formatter
->open_object_section_in_ns("BucketLoggingStatus", XMLNS_AWS_S3
);
2016 s
->formatter
->close_section();
2017 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2020 void RGWGetBucketLocation_ObjStore_S3::send_response()
2023 end_header(s
, this);
2026 RGWZoneGroup zonegroup
;
2029 int ret
= store
->get_zone()->get_zonegroup(s
->bucket
->get_info().zonegroup
, zonegroup
);
2031 api_name
= zonegroup
.api_name
;
2033 if (s
->bucket
->get_info().zonegroup
!= "default") {
2034 api_name
= s
->bucket
->get_info().zonegroup
;
2038 s
->formatter
->dump_format_ns("LocationConstraint", XMLNS_AWS_S3
,
2039 "%s", api_name
.c_str());
2040 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2043 void RGWGetBucketVersioning_ObjStore_S3::send_response()
2046 set_req_state_err(s
, op_ret
);
2048 end_header(s
, this, "application/xml");
2051 s
->formatter
->open_object_section_in_ns("VersioningConfiguration", XMLNS_AWS_S3
);
2053 const char *status
= (versioning_enabled
? "Enabled" : "Suspended");
2054 s
->formatter
->dump_string("Status", status
);
2055 const char *mfa_status
= (mfa_enabled
? "Enabled" : "Disabled");
2056 s
->formatter
->dump_string("MfaDelete", mfa_status
);
2058 s
->formatter
->close_section();
2059 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2062 struct ver_config_status
{
2063 int status
{VersioningSuspended
};
2069 } mfa_status
{MFA_UNKNOWN
};
2072 void decode_xml(XMLObj
*obj
) {
2075 RGWXMLDecoder::decode_xml("Status", status_str
, obj
);
2076 if (status_str
== "Enabled") {
2077 status
= VersioningEnabled
;
2078 } else if (status_str
!= "Suspended") {
2079 status
= VersioningStatusInvalid
;
2083 if (RGWXMLDecoder::decode_xml("MfaDelete", mfa_str
, obj
)) {
2084 if (mfa_str
== "Enabled") {
2085 mfa_status
= MFA_ENABLED
;
2086 } else if (mfa_str
== "Disabled") {
2087 mfa_status
= MFA_DISABLED
;
2095 int RGWSetBucketVersioning_ObjStore_S3::get_params(optional_yield y
)
2100 read_all_input(s
, s
->cct
->_conf
->rgw_max_put_param_size
, false);
2105 RGWXMLDecoder::XMLParser parser
;
2106 if (!parser
.init()) {
2107 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
2111 char* buf
= data
.c_str();
2112 if (!parser
.parse(buf
, data
.length(), 1)) {
2113 ldpp_dout(this, 10) << "NOTICE: failed to parse data: " << buf
<< dendl
;
2118 ver_config_status status_conf
;
2120 if (!RGWXMLDecoder::decode_xml("VersioningConfiguration", status_conf
, &parser
)) {
2121 ldpp_dout(this, 10) << "NOTICE: bad versioning config input" << dendl
;
2125 if (!store
->is_meta_master()) {
2126 /* only need to keep this data around if we're not meta master */
2127 in_data
.append(data
);
2130 versioning_status
= status_conf
.status
;
2131 if (versioning_status
== VersioningStatusInvalid
) {
2135 if (status_conf
.mfa_status
!= ver_config_status::MFA_UNKNOWN
) {
2136 mfa_set_status
= true;
2137 switch (status_conf
.mfa_status
) {
2138 case ver_config_status::MFA_DISABLED
:
2141 case ver_config_status::MFA_ENABLED
:
2145 ldpp_dout(this, 0) << "ERROR: RGWSetBucketVersioning_ObjStore_S3::get_params(optional_yield y): unexpected switch case mfa_status=" << status_conf
.mfa_status
<< dendl
;
2148 } else if (status_conf
.retcode
< 0) {
2149 r
= status_conf
.retcode
;
2154 void RGWSetBucketVersioning_ObjStore_S3::send_response()
2157 set_req_state_err(s
, op_ret
);
2159 end_header(s
, this, "application/xml");
2162 int RGWSetBucketWebsite_ObjStore_S3::get_params(optional_yield y
)
2164 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
2168 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
2174 in_data
.append(data
);
2176 RGWXMLDecoder::XMLParser parser
;
2177 if (!parser
.init()) {
2178 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
2182 char* buf
= data
.c_str();
2183 if (!parser
.parse(buf
, data
.length(), 1)) {
2184 ldpp_dout(this, 5) << "failed to parse xml: " << buf
<< dendl
;
2189 RGWXMLDecoder::decode_xml("WebsiteConfiguration", website_conf
, &parser
, true);
2190 } catch (RGWXMLDecoder::err
& err
) {
2191 ldpp_dout(this, 5) << "unexpected xml: " << buf
<< dendl
;
2195 if (website_conf
.is_redirect_all
&& website_conf
.redirect_all
.hostname
.empty()) {
2196 s
->err
.message
= "A host name must be provided to redirect all requests (e.g. \"example.com\").";
2197 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2199 } else if (!website_conf
.is_redirect_all
&& !website_conf
.is_set_index_doc
) {
2200 s
->err
.message
= "A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty";
2201 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2203 } else if (!website_conf
.is_redirect_all
&& website_conf
.is_set_index_doc
&&
2204 website_conf
.index_doc_suffix
.empty()) {
2205 s
->err
.message
= "The IndexDocument Suffix is not well formed";
2206 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2210 #define WEBSITE_ROUTING_RULES_MAX_NUM 50
2211 int max_num
= s
->cct
->_conf
->rgw_website_routing_rules_max_num
;
2213 max_num
= WEBSITE_ROUTING_RULES_MAX_NUM
;
2215 int routing_rules_num
= website_conf
.routing_rules
.rules
.size();
2216 if (routing_rules_num
> max_num
) {
2217 ldpp_dout(this, 4) << "An website routing config can have up to "
2219 << " rules, request website routing rules num: "
2220 << routing_rules_num
<< dendl
;
2221 op_ret
= -ERR_INVALID_WEBSITE_ROUTING_RULES_ERROR
;
2222 s
->err
.message
= std::to_string(routing_rules_num
) +" routing rules provided, the number of routing rules in a website configuration is limited to "
2223 + std::to_string(max_num
)
2225 return -ERR_INVALID_REQUEST
;
2231 void RGWSetBucketWebsite_ObjStore_S3::send_response()
2234 set_req_state_err(s
, op_ret
);
2236 end_header(s
, this, "application/xml");
2239 void RGWDeleteBucketWebsite_ObjStore_S3::send_response()
2242 op_ret
= STATUS_NO_CONTENT
;
2244 set_req_state_err(s
, op_ret
);
2246 end_header(s
, this, "application/xml");
2249 void RGWGetBucketWebsite_ObjStore_S3::send_response()
2252 set_req_state_err(s
, op_ret
);
2254 end_header(s
, this, "application/xml");
2261 RGWBucketWebsiteConf
& conf
= s
->bucket
->get_info().website_conf
;
2263 s
->formatter
->open_object_section_in_ns("WebsiteConfiguration", XMLNS_AWS_S3
);
2264 conf
.dump_xml(s
->formatter
);
2265 s
->formatter
->close_section(); // WebsiteConfiguration
2266 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2269 static void dump_bucket_metadata(struct req_state
*s
, rgw::sal::Bucket
* bucket
)
2271 dump_header(s
, "X-RGW-Object-Count", static_cast<long long>(bucket
->get_count()));
2272 dump_header(s
, "X-RGW-Bytes-Used", static_cast<long long>(bucket
->get_size()));
2273 // only bucket's owner is allowed to get the quota settings of the account
2274 if (bucket
->is_owner(s
->user
.get())) {
2275 auto user_info
= s
->user
->get_info();
2276 dump_header(s
, "X-RGW-Quota-User-Size", static_cast<long long>(user_info
.user_quota
.max_size
));
2277 dump_header(s
, "X-RGW-Quota-User-Objects", static_cast<long long>(user_info
.user_quota
.max_objects
));
2278 dump_header(s
, "X-RGW-Quota-Max-Buckets", static_cast<long long>(user_info
.max_buckets
));
2279 dump_header(s
, "X-RGW-Quota-Bucket-Size", static_cast<long long>(user_info
.bucket_quota
.max_size
));
2280 dump_header(s
, "X-RGW-Quota-Bucket-Objects", static_cast<long long>(user_info
.bucket_quota
.max_objects
));
2284 void RGWStatBucket_ObjStore_S3::send_response()
2287 dump_bucket_metadata(s
, bucket
.get());
2290 set_req_state_err(s
, op_ret
);
2293 end_header(s
, this);
2297 static int create_s3_policy(struct req_state
*s
, rgw::sal::Store
* store
,
2298 RGWAccessControlPolicy_S3
& s3policy
,
2301 if (s
->has_acl_header
) {
2302 if (!s
->canned_acl
.empty())
2303 return -ERR_INVALID_REQUEST
;
2305 return s3policy
.create_from_headers(s
, store
, s
->info
.env
, owner
);
2308 return s3policy
.create_canned(owner
, s
->bucket_owner
, s
->canned_acl
);
2311 class RGWLocationConstraint
: public XMLObj
2314 RGWLocationConstraint() {}
2315 ~RGWLocationConstraint() override
{}
2316 bool xml_end(const char *el
) override
{
2320 location_constraint
= get_data();
2325 string location_constraint
;
2328 class RGWCreateBucketConfig
: public XMLObj
2331 RGWCreateBucketConfig() {}
2332 ~RGWCreateBucketConfig() override
{}
2335 class RGWCreateBucketParser
: public RGWXMLParser
2337 XMLObj
*alloc_obj(const char *el
) override
{
2342 RGWCreateBucketParser() {}
2343 ~RGWCreateBucketParser() override
{}
2345 bool get_location_constraint(string
& zone_group
) {
2346 XMLObj
*config
= find_first("CreateBucketConfiguration");
2350 XMLObj
*constraint
= config
->find_first("LocationConstraint");
2354 zone_group
= constraint
->get_data();
2360 int RGWCreateBucket_ObjStore_S3::get_params(optional_yield y
)
2362 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
2363 bool relaxed_names
= s
->cct
->_conf
->rgw_relaxed_s3_bucket_names
;
2366 if (!s
->system_request
) {
2367 r
= valid_s3_bucket_name(s
->bucket_name
, relaxed_names
);
2371 r
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
2377 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
2381 std::tie(op_ret
, data
) = read_all_input(s
, max_size
, false);
2383 if ((op_ret
< 0) && (op_ret
!= -ERR_LENGTH_REQUIRED
))
2386 in_data
.append(data
);
2388 if (data
.length()) {
2389 RGWCreateBucketParser parser
;
2391 if (!parser
.init()) {
2392 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
2396 char* buf
= data
.c_str();
2397 bool success
= parser
.parse(buf
, data
.length(), 1);
2398 ldpp_dout(this, 20) << "create bucket input data=" << buf
<< dendl
;
2401 ldpp_dout(this, 0) << "failed to parse input: " << buf
<< dendl
;
2405 if (!parser
.get_location_constraint(location_constraint
)) {
2406 ldpp_dout(this, 0) << "provided input did not specify location constraint correctly" << dendl
;
2410 ldpp_dout(this, 10) << "create bucket location constraint: "
2411 << location_constraint
<< dendl
;
2414 size_t pos
= location_constraint
.find(':');
2415 if (pos
!= string::npos
) {
2416 placement_rule
.init(location_constraint
.substr(pos
+ 1), s
->info
.storage_class
);
2417 location_constraint
= location_constraint
.substr(0, pos
);
2419 placement_rule
.storage_class
= s
->info
.storage_class
;
2421 auto iter
= s
->info
.x_meta_map
.find("x-amz-bucket-object-lock-enabled");
2422 if (iter
!= s
->info
.x_meta_map
.end()) {
2423 if (!boost::algorithm::iequals(iter
->second
, "true") && !boost::algorithm::iequals(iter
->second
, "false")) {
2426 obj_lock_enabled
= boost::algorithm::iequals(iter
->second
, "true");
2431 void RGWCreateBucket_ObjStore_S3::send_response()
2433 if (op_ret
== -ERR_BUCKET_EXISTS
)
2436 set_req_state_err(s
, op_ret
);
2443 if (s
->system_request
) {
2444 JSONFormatter f
; /* use json formatter for system requests output */
2446 f
.open_object_section("info");
2447 encode_json("entry_point_object_ver", ep_objv
, &f
);
2448 encode_json("object_ver", info
.objv_tracker
.read_version
, &f
);
2449 encode_json("bucket_info", info
, &f
);
2451 rgw_flush_formatter_and_reset(s
, &f
);
2455 void RGWDeleteBucket_ObjStore_S3::send_response()
2459 r
= STATUS_NO_CONTENT
;
2461 set_req_state_err(s
, r
);
2463 end_header(s
, this);
2466 static inline void map_qs_metadata(struct req_state
* s
, bool crypto_too
)
2468 /* merge S3 valid user metadata from the query-string into
2469 * x_meta_map, which maps them to attributes */
2470 const auto& params
= const_cast<RGWHTTPArgs
&>(s
->info
.args
).get_params();
2471 for (const auto& elt
: params
) {
2472 std::string k
= boost::algorithm::to_lower_copy(elt
.first
);
2473 if (k
.find("x-amz-meta-") == /* offset */ 0) {
2474 rgw_add_amz_meta_header(s
->info
.x_meta_map
, k
, elt
.second
);
2476 if (crypto_too
&& k
.find("x-amz-server-side-encryption") == /* offset */ 0) {
2477 rgw_set_amz_meta_header(s
->info
.crypt_attribute_map
, k
, elt
.second
, OVERWRITE
);
2482 int RGWPutObj_ObjStore_S3::get_params(optional_yield y
)
2485 return -ERR_LENGTH_REQUIRED
;
2489 map_qs_metadata(s
, true);
2490 ret
= get_encryption_defaults(s
);
2492 ldpp_dout(this, 5) << __func__
<< "(): get_encryption_defaults() returned ret=" << ret
<< dendl
;
2496 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
2497 ret
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
2503 if_match
= s
->info
.env
->get("HTTP_IF_MATCH");
2504 if_nomatch
= s
->info
.env
->get("HTTP_IF_NONE_MATCH");
2506 /* handle object tagging */
2507 auto tag_str
= s
->info
.env
->get("HTTP_X_AMZ_TAGGING");
2509 obj_tags
= std::make_unique
<RGWObjTags
>();
2510 ret
= obj_tags
->set_from_string(tag_str
);
2512 ldpp_dout(this,0) << "setting obj tags failed with " << ret
<< dendl
;
2513 if (ret
== -ERR_INVALID_TAG
){
2514 ret
= -EINVAL
; //s3 returns only -EINVAL for PUT requests
2521 //handle object lock
2522 auto obj_lock_mode_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_MODE");
2523 auto obj_lock_date_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE");
2524 auto obj_legal_hold_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_LEGAL_HOLD");
2525 if (obj_lock_mode_str
&& obj_lock_date_str
) {
2526 boost::optional
<ceph::real_time
> date
= ceph::from_iso_8601(obj_lock_date_str
);
2527 if (boost::none
== date
|| ceph::real_clock::to_time_t(*date
) <= ceph_clock_now()) {
2529 ldpp_dout(this,0) << "invalid x-amz-object-lock-retain-until-date value" << dendl
;
2532 if (strcmp(obj_lock_mode_str
, "GOVERNANCE") != 0 && strcmp(obj_lock_mode_str
, "COMPLIANCE") != 0) {
2534 ldpp_dout(this,0) << "invalid x-amz-object-lock-mode value" << dendl
;
2537 obj_retention
= new RGWObjectRetention(obj_lock_mode_str
, *date
);
2538 } else if ((obj_lock_mode_str
&& !obj_lock_date_str
) || (!obj_lock_mode_str
&& obj_lock_date_str
)) {
2540 ldpp_dout(this,0) << "need both x-amz-object-lock-mode and x-amz-object-lock-retain-until-date " << dendl
;
2543 if (obj_legal_hold_str
) {
2544 if (strcmp(obj_legal_hold_str
, "ON") != 0 && strcmp(obj_legal_hold_str
, "OFF") != 0) {
2546 ldpp_dout(this,0) << "invalid x-amz-object-lock-legal-hold value" << dendl
;
2549 obj_legal_hold
= new RGWObjectLegalHold(obj_legal_hold_str
);
2551 if (!s
->bucket
->get_info().obj_lock_enabled() && (obj_retention
|| obj_legal_hold
)) {
2552 ldpp_dout(this, 0) << "ERROR: object retention or legal hold can't be set if bucket object lock not configured" << dendl
;
2553 ret
= -ERR_INVALID_REQUEST
;
2556 multipart_upload_id
= s
->info
.args
.get("uploadId");
2557 multipart_part_str
= s
->info
.args
.get("partNumber");
2558 if (!multipart_part_str
.empty()) {
2560 multipart_part_num
= strict_strtol(multipart_part_str
.c_str(), 10, &err
);
2562 ldpp_dout(s
, 10) << "bad part number: " << multipart_part_str
<< ": " << err
<< dendl
;
2565 } else if (!multipart_upload_id
.empty()) {
2566 ldpp_dout(s
, 10) << "part number with no multipart upload id" << dendl
;
2570 append
= s
->info
.args
.exists("append");
2572 string pos_str
= s
->info
.args
.get("position");
2574 long long pos_tmp
= strict_strtoll(pos_str
.c_str(), 10, &err
);
2576 ldpp_dout(s
, 10) << "bad position: " << pos_str
<< ": " << err
<< dendl
;
2578 } else if (pos_tmp
< 0) {
2579 ldpp_dout(s
, 10) << "bad position: " << pos_str
<< ": " << "position shouldn't be negative" << dendl
;
2582 position
= uint64_t(pos_tmp
);
2585 return RGWPutObj_ObjStore::get_params(y
);
2588 int RGWPutObj_ObjStore_S3::get_data(bufferlist
& bl
)
2590 const int ret
= RGWPutObj_ObjStore::get_data(bl
);
2592 const int ret_auth
= do_aws4_auth_completion();
2601 static int get_success_retcode(int code
)
2605 return STATUS_CREATED
;
2607 return STATUS_NO_CONTENT
;
2612 void RGWPutObj_ObjStore_S3::send_response()
2615 set_req_state_err(s
, op_ret
);
2618 if (s
->cct
->_conf
->rgw_s3_success_create_obj_status
) {
2619 op_ret
= get_success_retcode(
2620 s
->cct
->_conf
->rgw_s3_success_create_obj_status
);
2621 set_req_state_err(s
, op_ret
);
2624 string expires
= get_s3_expiration_header(s
, mtime
);
2626 if (copy_source
.empty()) {
2629 dump_content_length(s
, 0);
2630 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
2631 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
2632 for (auto &it
: crypt_http_responses
)
2633 dump_header(s
, it
.first
, it
.second
);
2636 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
2637 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
2638 end_header(s
, this, "application/xml");
2642 time_t secs
= (time_t)ut
.sec();
2643 gmtime_r(&secs
, &tmp
);
2644 char buf
[TIME_BUF_SIZE
];
2645 s
->formatter
->open_object_section_in_ns("CopyPartResult",
2646 "http://s3.amazonaws.com/doc/2006-03-01/");
2647 if (strftime(buf
, sizeof(buf
), "%Y-%m-%dT%T.000Z", &tmp
) > 0) {
2648 s
->formatter
->dump_string("LastModified", buf
);
2650 s
->formatter
->dump_string("ETag", etag
);
2651 s
->formatter
->close_section();
2652 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2657 if (op_ret
== 0 || op_ret
== -ERR_POSITION_NOT_EQUAL_TO_LENGTH
) {
2658 dump_header(s
, "x-rgw-next-append-position", cur_accounted_size
);
2661 if (s
->system_request
&& !real_clock::is_zero(mtime
)) {
2662 dump_epoch_header(s
, "Rgwx-Mtime", mtime
);
2664 end_header(s
, this);
2667 static inline void set_attr(map
<string
, bufferlist
>& attrs
, const char* key
, const std::string
& value
)
2671 attrs
.emplace(key
, std::move(bl
));
2674 static inline void set_attr(map
<string
, bufferlist
>& attrs
, const char* key
, const char* value
)
2678 attrs
.emplace(key
, std::move(bl
));
2681 int RGWPutObj_ObjStore_S3::get_decrypt_filter(
2682 std::unique_ptr
<RGWGetObj_Filter
>* filter
,
2683 RGWGetObj_Filter
* cb
,
2684 map
<string
, bufferlist
>& attrs
,
2685 bufferlist
* manifest_bl
)
2687 std::map
<std::string
, std::string
> crypt_http_responses_unused
;
2690 std::unique_ptr
<BlockCrypt
> block_crypt
;
2691 res
= rgw_s3_prepare_decrypt(s
, attrs
, &block_crypt
, crypt_http_responses_unused
);
2693 if (block_crypt
!= nullptr) {
2694 auto f
= std::unique_ptr
<RGWGetObj_BlockDecrypt
>(new RGWGetObj_BlockDecrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2695 //RGWGetObj_BlockDecrypt* f = new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt));
2697 if (manifest_bl
!= nullptr) {
2698 res
= f
->read_manifest(this, *manifest_bl
);
2700 *filter
= std::move(f
);
2709 int RGWPutObj_ObjStore_S3::get_encrypt_filter(
2710 std::unique_ptr
<rgw::sal::DataProcessor
> *filter
,
2711 rgw::sal::DataProcessor
*cb
)
2714 if (!multipart_upload_id
.empty()) {
2715 std::unique_ptr
<rgw::sal::MultipartUpload
> upload
=
2716 s
->bucket
->get_multipart_upload(s
->object
->get_name(),
2717 multipart_upload_id
);
2718 std::unique_ptr
<rgw::sal::Object
> obj
= upload
->get_meta_obj();
2719 obj
->set_in_extra_data(true);
2720 res
= obj
->get_obj_attrs(s
->obj_ctx
, s
->yield
, this);
2722 std::unique_ptr
<BlockCrypt
> block_crypt
;
2723 /* We are adding to existing object.
2724 * We use crypto mode that configured as if we were decrypting. */
2725 res
= rgw_s3_prepare_decrypt(s
, obj
->get_attrs(), &block_crypt
, crypt_http_responses
);
2726 if (res
== 0 && block_crypt
!= nullptr)
2727 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2729 /* it is ok, to not have encryption at all */
2733 std::unique_ptr
<BlockCrypt
> block_crypt
;
2734 res
= rgw_s3_prepare_encrypt(s
, attrs
, &block_crypt
, crypt_http_responses
);
2735 if (res
== 0 && block_crypt
!= nullptr) {
2736 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2742 void RGWPostObj_ObjStore_S3::rebuild_key(rgw::sal::Object
* obj
)
2744 string key
= obj
->get_name();
2745 static string var
= "${filename}";
2746 int pos
= key
.find(var
);
2750 string new_key
= key
.substr(0, pos
);
2751 new_key
.append(filename
);
2752 new_key
.append(key
.substr(pos
+ var
.size()));
2754 obj
->set_key(new_key
);
2757 std::string
RGWPostObj_ObjStore_S3::get_current_filename() const
2759 return s
->object
->get_name();
2762 std::string
RGWPostObj_ObjStore_S3::get_current_content_type() const
2764 return content_type
;
2767 int RGWPostObj_ObjStore_S3::get_params(optional_yield y
)
2769 op_ret
= RGWPostObj_ObjStore::get_params(y
);
2774 map_qs_metadata(s
, false);
2776 ldpp_dout(this, 20) << "adding bucket to policy env: " << s
->bucket
->get_name()
2778 env
.add_var("bucket", s
->bucket
->get_name());
2782 struct post_form_part part
;
2783 int r
= read_form_part_header(&part
, done
);
2787 if (s
->cct
->_conf
->subsys
.should_gather
<ceph_subsys_rgw
, 20>()) {
2788 ldpp_dout(this, 20) << "read part header -- part.name="
2789 << part
.name
<< dendl
;
2791 for (const auto& pair
: part
.fields
) {
2792 ldpp_dout(this, 20) << "field.name=" << pair
.first
<< dendl
;
2793 ldpp_dout(this, 20) << "field.val=" << pair
.second
.val
<< dendl
;
2794 ldpp_dout(this, 20) << "field.params:" << dendl
;
2796 for (const auto& param_pair
: pair
.second
.params
) {
2797 ldpp_dout(this, 20) << " " << param_pair
.first
2798 << " -> " << param_pair
.second
<< dendl
;
2803 if (done
) { /* unexpected here */
2804 err_msg
= "Malformed request";
2808 if (stringcasecmp(part
.name
, "file") == 0) { /* beginning of data transfer */
2809 struct post_part_field
& field
= part
.fields
["Content-Disposition"];
2810 map
<string
, string
>::iterator iter
= field
.params
.find("filename");
2811 if (iter
!= field
.params
.end()) {
2812 filename
= iter
->second
;
2814 parts
[part
.name
] = part
;
2819 uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
2820 r
= read_data(part
.data
, chunk_size
, boundary
, done
);
2821 if (r
< 0 || !boundary
) {
2822 err_msg
= "Couldn't find boundary";
2825 parts
[part
.name
] = part
;
2826 string
part_str(part
.data
.c_str(), part
.data
.length());
2827 env
.add_var(part
.name
, part_str
);
2830 for (auto &p
: parts
) {
2831 if (! boost::istarts_with(p
.first
, "x-amz-server-side-encryption")) {
2834 bufferlist
&d
{ p
.second
.data
};
2835 std::string v
{ rgw_trim_whitespace(std::string_view(d
.c_str(), d
.length())) };
2836 rgw_set_amz_meta_header(s
->info
.crypt_attribute_map
, p
.first
, v
, OVERWRITE
);
2838 int r
= get_encryption_defaults(s
);
2840 ldpp_dout(this, 5) << __func__
<< "(): get_encryption_defaults() returned ret=" << r
<< dendl
;
2845 if (!part_str(parts
, "key", &object_str
)) {
2846 err_msg
= "Key not specified";
2850 s
->object
= store
->get_object(rgw_obj_key(object_str
));
2852 rebuild_key(s
->object
.get());
2854 if (rgw::sal::Object::empty(s
->object
.get())) {
2855 err_msg
= "Empty object name";
2859 env
.add_var("key", s
->object
->get_name());
2861 part_str(parts
, "Content-Type", &content_type
);
2863 /* AWS permits POST without Content-Type: http://tracker.ceph.com/issues/20201 */
2864 if (! content_type
.empty()) {
2865 env
.add_var("Content-Type", content_type
);
2868 std::string storage_class
;
2869 part_str(parts
, "x-amz-storage-class", &storage_class
);
2871 if (! storage_class
.empty()) {
2872 s
->dest_placement
.storage_class
= storage_class
;
2873 if (!store
->get_zone()->get_params().valid_placement(s
->dest_placement
)) {
2874 ldpp_dout(this, 0) << "NOTICE: invalid dest placement: " << s
->dest_placement
.to_str() << dendl
;
2875 err_msg
= "The storage class you specified is not valid";
2880 map
<string
, struct post_form_part
, ltstr_nocase
>::iterator piter
=
2881 parts
.upper_bound(RGW_AMZ_META_PREFIX
);
2882 for (; piter
!= parts
.end(); ++piter
) {
2883 string n
= piter
->first
;
2884 if (strncasecmp(n
.c_str(), RGW_AMZ_META_PREFIX
,
2885 sizeof(RGW_AMZ_META_PREFIX
) - 1) != 0)
2888 string attr_name
= RGW_ATTR_PREFIX
;
2889 attr_name
.append(n
);
2891 /* need to null terminate it */
2892 bufferlist
& data
= piter
->second
.data
;
2893 string str
= string(data
.c_str(), data
.length());
2896 attr_bl
.append(str
.c_str(), str
.size() + 1);
2898 attrs
[attr_name
] = attr_bl
;
2900 // TODO: refactor this and the above loop to share code
2901 piter
= parts
.find(RGW_AMZ_WEBSITE_REDIRECT_LOCATION
);
2902 if (piter
!= parts
.end()) {
2903 string n
= piter
->first
;
2904 string attr_name
= RGW_ATTR_PREFIX
;
2905 attr_name
.append(n
);
2906 /* need to null terminate it */
2907 bufferlist
& data
= piter
->second
.data
;
2908 string str
= string(data
.c_str(), data
.length());
2911 attr_bl
.append(str
.c_str(), str
.size() + 1);
2913 attrs
[attr_name
] = attr_bl
;
2925 min_len
= post_policy
.min_length
;
2926 max_len
= post_policy
.max_length
;
2933 int RGWPostObj_ObjStore_S3::get_tags()
2936 if (part_str(parts
, "tagging", &tags_str
)) {
2937 RGWXMLParser parser
;
2938 if (!parser
.init()){
2939 ldpp_dout(this, 0) << "Couldn't init RGWObjTags XML parser" << dendl
;
2940 err_msg
= "Server couldn't process the request";
2941 return -EINVAL
; // TODO: This class of errors in rgw code should be a 5XX error
2943 if (!parser
.parse(tags_str
.c_str(), tags_str
.size(), 1)) {
2944 ldpp_dout(this,0 ) << "Invalid Tagging XML" << dendl
;
2945 err_msg
= "Invalid Tagging XML";
2949 RGWObjTagging_S3 tagging
;
2952 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
2953 } catch (RGWXMLDecoder::err
& err
) {
2954 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
2958 RGWObjTags obj_tags
;
2959 int r
= tagging
.rebuild(obj_tags
);
2964 obj_tags
.encode(tags_bl
);
2965 ldpp_dout(this, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
2966 attrs
[RGW_ATTR_TAGS
] = tags_bl
;
2973 int RGWPostObj_ObjStore_S3::get_policy(optional_yield y
)
2975 if (part_bl(parts
, "policy", &s
->auth
.s3_postobj_creds
.encoded_policy
)) {
2976 bool aws4_auth
= false;
2978 /* x-amz-algorithm handling */
2979 using rgw::auth::s3::AWS4_HMAC_SHA256_STR
;
2980 if ((part_str(parts
, "x-amz-algorithm", &s
->auth
.s3_postobj_creds
.x_amz_algorithm
)) &&
2981 (s
->auth
.s3_postobj_creds
.x_amz_algorithm
== AWS4_HMAC_SHA256_STR
)) {
2982 ldpp_dout(this, 0) << "Signature verification algorithm AWS v4 (AWS4-HMAC-SHA256)" << dendl
;
2985 ldpp_dout(this, 0) << "Signature verification algorithm AWS v2" << dendl
;
2988 // check that the signature matches the encoded policy
2992 /* x-amz-credential handling */
2993 if (!part_str(parts
, "x-amz-credential",
2994 &s
->auth
.s3_postobj_creds
.x_amz_credential
)) {
2995 ldpp_dout(this, 0) << "No S3 aws4 credential found!" << dendl
;
2996 err_msg
= "Missing aws4 credential";
3000 /* x-amz-signature handling */
3001 if (!part_str(parts
, "x-amz-signature",
3002 &s
->auth
.s3_postobj_creds
.signature
)) {
3003 ldpp_dout(this, 0) << "No aws4 signature found!" << dendl
;
3004 err_msg
= "Missing aws4 signature";
3008 /* x-amz-date handling */
3009 std::string received_date_str
;
3010 if (!part_str(parts
, "x-amz-date", &received_date_str
)) {
3011 ldpp_dout(this, 0) << "No aws4 date found!" << dendl
;
3012 err_msg
= "Missing aws4 date";
3018 // check that the signature matches the encoded policy
3019 if (!part_str(parts
, "AWSAccessKeyId",
3020 &s
->auth
.s3_postobj_creds
.access_key
)) {
3021 ldpp_dout(this, 0) << "No S3 aws2 access key found!" << dendl
;
3022 err_msg
= "Missing aws2 access key";
3026 if (!part_str(parts
, "signature", &s
->auth
.s3_postobj_creds
.signature
)) {
3027 ldpp_dout(this, 0) << "No aws2 signature found!" << dendl
;
3028 err_msg
= "Missing aws2 signature";
3033 if (part_str(parts
, "x-amz-security-token", &s
->auth
.s3_postobj_creds
.x_amz_security_token
)) {
3034 if (s
->auth
.s3_postobj_creds
.x_amz_security_token
.size() == 0) {
3035 err_msg
= "Invalid token";
3040 /* FIXME: this is a makeshift solution. The browser upload authentication will be
3041 * handled by an instance of rgw::auth::Completer spawned in Handler's authorize()
3043 const int ret
= rgw::auth::Strategy::apply(this, auth_registry_ptr
->get_s3_post(), s
, y
);
3047 /* Populate the owner info. */
3048 s
->owner
.set_id(s
->user
->get_id());
3049 s
->owner
.set_name(s
->user
->get_display_name());
3050 ldpp_dout(this, 20) << "Successful Signature Verification!" << dendl
;
3053 ceph::bufferlist decoded_policy
;
3055 decoded_policy
.decode_base64(s
->auth
.s3_postobj_creds
.encoded_policy
);
3056 } catch (buffer::error
& err
) {
3057 ldpp_dout(this, 0) << "failed to decode_base64 policy" << dendl
;
3058 err_msg
= "Could not decode policy";
3062 decoded_policy
.append('\0'); // NULL terminate
3063 ldpp_dout(this, 20) << "POST policy: " << decoded_policy
.c_str() << dendl
;
3066 int r
= post_policy
.from_json(decoded_policy
, err_msg
);
3068 if (err_msg
.empty()) {
3069 err_msg
= "Failed to parse policy";
3071 ldpp_dout(this, 0) << "failed to parse policy" << dendl
;
3077 post_policy
.set_var_checked("x-amz-signature");
3080 post_policy
.set_var_checked("AWSAccessKeyId");
3081 post_policy
.set_var_checked("signature");
3083 post_policy
.set_var_checked("policy");
3085 r
= post_policy
.check(&env
, err_msg
);
3087 if (err_msg
.empty()) {
3088 err_msg
= "Policy check failed";
3090 ldpp_dout(this, 0) << "policy check failed" << dendl
;
3095 ldpp_dout(this, 0) << "No attached policy found!" << dendl
;
3099 part_str(parts
, "acl", &canned_acl
);
3101 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3102 ldpp_dout(this, 20) << "canned_acl=" << canned_acl
<< dendl
;
3103 if (s3policy
.create_canned(s
->owner
, s
->bucket_owner
, canned_acl
) < 0) {
3104 err_msg
= "Bad canned ACLs";
3113 int RGWPostObj_ObjStore_S3::complete_get_params()
3117 struct post_form_part part
;
3118 int r
= read_form_part_header(&part
, done
);
3123 ceph::bufferlist part_data
;
3125 uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
3126 r
= read_data(part
.data
, chunk_size
, boundary
, done
);
3127 if (r
< 0 || !boundary
) {
3131 /* Just reading the data but not storing any results of that. */
3137 int RGWPostObj_ObjStore_S3::get_data(ceph::bufferlist
& bl
, bool& again
)
3142 const uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
3143 int r
= read_data(bl
, chunk_size
, boundary
, done
);
3150 /* Reached end of data, let's drain the rest of the params */
3151 r
= complete_get_params();
3162 void RGWPostObj_ObjStore_S3::send_response()
3164 if (op_ret
== 0 && parts
.count("success_action_redirect")) {
3167 part_str(parts
, "success_action_redirect", &redirect
);
3172 string etag_str
= "\"";
3174 etag_str
.append(etag
);
3175 etag_str
.append("\"");
3179 url_encode(s
->bucket_tenant
, tenant
); /* surely overkill, but cheap */
3180 url_encode(s
->bucket_name
, bucket
);
3181 url_encode(s
->object
->get_name(), key
);
3182 url_encode(etag_str
, etag_url
);
3184 if (!s
->bucket_tenant
.empty()) {
3186 * What we really would like is to quaily the bucket name, so
3187 * that the client could simply copy it and paste into next request.
3188 * Unfortunately, in S3 we cannot know if the client will decide
3189 * to come through DNS, with "bucket.tenant" sytanx, or through
3190 * URL with "tenant\bucket" syntax. Therefore, we provide the
3191 * tenant separately.
3193 redirect
.append("?tenant=");
3194 redirect
.append(tenant
);
3195 redirect
.append("&bucket=");
3196 redirect
.append(bucket
);
3198 redirect
.append("?bucket=");
3199 redirect
.append(bucket
);
3201 redirect
.append("&key=");
3202 redirect
.append(key
);
3203 redirect
.append("&etag=");
3204 redirect
.append(etag_url
);
3206 int r
= check_utf8(redirect
.c_str(), redirect
.size());
3211 dump_redirect(s
, redirect
);
3212 op_ret
= STATUS_REDIRECT
;
3213 } else if (op_ret
== 0 && parts
.count("success_action_status")) {
3214 string status_string
;
3215 uint32_t status_int
;
3217 part_str(parts
, "success_action_status", &status_string
);
3219 int r
= stringtoul(status_string
, &status_int
);
3225 switch (status_int
) {
3229 op_ret
= STATUS_CREATED
;
3232 op_ret
= STATUS_NO_CONTENT
;
3235 } else if (! op_ret
) {
3236 op_ret
= STATUS_NO_CONTENT
;
3240 if (op_ret
== STATUS_CREATED
) {
3241 for (auto &it
: crypt_http_responses
)
3242 dump_header(s
, it
.first
, it
.second
);
3243 s
->formatter
->open_object_section("PostResponse");
3244 std::string base_uri
= compute_domain_uri(s
);
3245 if (!s
->bucket_tenant
.empty()){
3246 s
->formatter
->dump_format("Location", "%s/%s:%s/%s",
3248 url_encode(s
->bucket_tenant
).c_str(),
3249 url_encode(s
->bucket_name
).c_str(),
3250 url_encode(s
->object
->get_name()).c_str());
3251 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3253 s
->formatter
->dump_format("Location", "%s/%s/%s",
3255 url_encode(s
->bucket_name
).c_str(),
3256 url_encode(s
->object
->get_name()).c_str());
3258 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3259 s
->formatter
->dump_string("Key", s
->object
->get_name());
3260 s
->formatter
->dump_string("ETag", etag
);
3261 s
->formatter
->close_section();
3263 s
->err
.message
= err_msg
;
3264 set_req_state_err(s
, op_ret
);
3267 dump_content_length(s
, s
->formatter
->get_len());
3269 end_header(s
, this);
3270 if (op_ret
!= STATUS_CREATED
)
3273 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3276 int RGWPostObj_ObjStore_S3::get_encrypt_filter(
3277 std::unique_ptr
<rgw::sal::DataProcessor
> *filter
,
3278 rgw::sal::DataProcessor
*cb
)
3280 std::unique_ptr
<BlockCrypt
> block_crypt
;
3281 int res
= rgw_s3_prepare_encrypt(s
, attrs
, &block_crypt
,
3282 crypt_http_responses
);
3283 if (res
== 0 && block_crypt
!= nullptr) {
3284 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
3289 int RGWDeleteObj_ObjStore_S3::get_params(optional_yield y
)
3291 const char *if_unmod
= s
->info
.env
->get("HTTP_X_AMZ_DELETE_IF_UNMODIFIED_SINCE");
3293 if (s
->system_request
) {
3294 s
->info
.args
.get_bool(RGW_SYS_PARAM_PREFIX
"no-precondition-error", &no_precondition_error
, false);
3298 std::string if_unmod_decoded
= url_decode(if_unmod
);
3301 if (utime_t::parse_date(if_unmod_decoded
, &epoch
, &nsec
) < 0) {
3302 ldpp_dout(this, 10) << "failed to parse time: " << if_unmod_decoded
<< dendl
;
3305 unmod_since
= utime_t(epoch
, nsec
).to_real_time();
3308 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
3309 if (bypass_gov_header
) {
3310 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
3311 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
3317 void RGWDeleteObj_ObjStore_S3::send_response()
3323 r
= STATUS_NO_CONTENT
;
3325 set_req_state_err(s
, r
);
3327 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
3328 if (delete_marker
) {
3329 dump_header(s
, "x-amz-delete-marker", "true");
3331 end_header(s
, this);
3334 int RGWCopyObj_ObjStore_S3::init_dest_policy()
3336 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3338 /* build a policy for the target object */
3339 int r
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
3343 dest_policy
= s3policy
;
3348 int RGWCopyObj_ObjStore_S3::get_params(optional_yield y
)
3350 //handle object lock
3351 auto obj_lock_mode_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_MODE");
3352 auto obj_lock_date_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE");
3353 auto obj_legal_hold_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_LEGAL_HOLD");
3354 if (obj_lock_mode_str
&& obj_lock_date_str
) {
3355 boost::optional
<ceph::real_time
> date
= ceph::from_iso_8601(obj_lock_date_str
);
3356 if (boost::none
== date
|| ceph::real_clock::to_time_t(*date
) <= ceph_clock_now()) {
3357 s
->err
.message
= "invalid x-amz-object-lock-retain-until-date value";
3358 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3361 if (strcmp(obj_lock_mode_str
, "GOVERNANCE") != 0 && strcmp(obj_lock_mode_str
, "COMPLIANCE") != 0) {
3362 s
->err
.message
= "invalid x-amz-object-lock-mode value";
3363 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3366 obj_retention
= new RGWObjectRetention(obj_lock_mode_str
, *date
);
3367 } else if (obj_lock_mode_str
|| obj_lock_date_str
) {
3368 s
->err
.message
= "need both x-amz-object-lock-mode and x-amz-object-lock-retain-until-date ";
3369 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3372 if (obj_legal_hold_str
) {
3373 if (strcmp(obj_legal_hold_str
, "ON") != 0 && strcmp(obj_legal_hold_str
, "OFF") != 0) {
3374 s
->err
.message
= "invalid x-amz-object-lock-legal-hold value";
3375 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3378 obj_legal_hold
= new RGWObjectLegalHold(obj_legal_hold_str
);
3381 if_mod
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_MODIFIED_SINCE");
3382 if_unmod
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_UNMODIFIED_SINCE");
3383 if_match
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_MATCH");
3384 if_nomatch
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_NONE_MATCH");
3386 src_tenant_name
= s
->src_tenant_name
;
3387 src_bucket_name
= s
->src_bucket_name
;
3388 dest_tenant_name
= s
->bucket
->get_tenant();
3389 dest_bucket_name
= s
->bucket
->get_name();
3390 dest_obj_name
= s
->object
->get_name();
3392 if (s
->system_request
) {
3393 source_zone
= s
->info
.args
.get(RGW_SYS_PARAM_PREFIX
"source-zone");
3394 s
->info
.args
.get_bool(RGW_SYS_PARAM_PREFIX
"copy-if-newer", ©_if_newer
, false);
3397 copy_source
= s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE");
3398 auto tmp_md_d
= s
->info
.env
->get("HTTP_X_AMZ_METADATA_DIRECTIVE");
3400 if (strcasecmp(tmp_md_d
, "COPY") == 0) {
3401 attrs_mod
= rgw::sal::ATTRSMOD_NONE
;
3402 } else if (strcasecmp(tmp_md_d
, "REPLACE") == 0) {
3403 attrs_mod
= rgw::sal::ATTRSMOD_REPLACE
;
3404 } else if (!source_zone
.empty()) {
3405 attrs_mod
= rgw::sal::ATTRSMOD_NONE
; // default for intra-zone_group copy
3407 s
->err
.message
= "Unknown metadata directive.";
3408 ldpp_dout(this, 0) << s
->err
.message
<< dendl
;
3411 md_directive
= tmp_md_d
;
3414 if (source_zone
.empty() &&
3415 (dest_tenant_name
.compare(src_tenant_name
) == 0) &&
3416 (dest_bucket_name
.compare(src_bucket_name
) == 0) &&
3417 (dest_obj_name
.compare(s
->src_object
->get_name()) == 0) &&
3418 s
->src_object
->get_instance().empty() &&
3419 (attrs_mod
!= rgw::sal::ATTRSMOD_REPLACE
)) {
3420 need_to_check_storage_class
= true;
3426 int RGWCopyObj_ObjStore_S3::check_storage_class(const rgw_placement_rule
& src_placement
)
3428 if (src_placement
== s
->dest_placement
) {
3429 /* can only copy object into itself if replacing attrs */
3430 s
->err
.message
= "This copy request is illegal because it is trying to copy "
3431 "an object to itself without changing the object's metadata, "
3432 "storage class, website redirect location or encryption attributes.";
3433 ldpp_dout(this, 0) << s
->err
.message
<< dendl
;
3434 return -ERR_INVALID_REQUEST
;
3439 void RGWCopyObj_ObjStore_S3::send_partial_response(off_t ofs
)
3441 if (! sent_header
) {
3443 set_req_state_err(s
, op_ret
);
3446 // Explicitly use chunked transfer encoding so that we can stream the result
3447 // to the user without having to wait for the full length of it.
3448 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3451 s
->formatter
->open_object_section_in_ns("CopyObjectResult", XMLNS_AWS_S3
);
3455 /* Send progress field. Note that this diverge from the original S3
3456 * spec. We do this in order to keep connection alive.
3458 s
->formatter
->dump_int("Progress", (uint64_t)ofs
);
3460 rgw_flush_formatter(s
, s
->formatter
);
3463 void RGWCopyObj_ObjStore_S3::send_response()
3466 send_partial_response(0);
3469 dump_time(s
, "LastModified", mtime
);
3470 if (!etag
.empty()) {
3471 s
->formatter
->dump_string("ETag", std::move(etag
));
3473 s
->formatter
->close_section();
3474 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3478 void RGWGetACLs_ObjStore_S3::send_response()
3481 set_req_state_err(s
, op_ret
);
3483 end_header(s
, this, "application/xml");
3485 rgw_flush_formatter(s
, s
->formatter
);
3489 int RGWPutACLs_ObjStore_S3::get_params(optional_yield y
)
3491 int ret
= RGWPutACLs_ObjStore::get_params(y
);
3493 const int ret_auth
= do_aws4_auth_completion();
3498 /* a request body is not required an S3 PutACLs request--n.b.,
3499 * s->length is non-null iff a content length was parsed (the
3500 * ACP or canned ACL could be in any of 3 headers, don't worry
3501 * about that here) */
3502 if ((ret
== -ERR_LENGTH_REQUIRED
) &&
3510 int RGWPutACLs_ObjStore_S3::get_policy_from_state(rgw::sal::Store
* store
,
3511 struct req_state
*s
,
3514 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3516 // bucket-* canned acls do not apply to bucket
3517 if (rgw::sal::Object::empty(s
->object
.get())) {
3518 if (s
->canned_acl
.find("bucket") != string::npos
)
3519 s
->canned_acl
.clear();
3522 int r
= create_s3_policy(s
, store
, s3policy
, owner
);
3526 s3policy
.to_xml(ss
);
3531 void RGWPutACLs_ObjStore_S3::send_response()
3534 set_req_state_err(s
, op_ret
);
3536 end_header(s
, this, "application/xml");
3540 void RGWGetLC_ObjStore_S3::execute(optional_yield y
)
3542 config
.set_ctx(s
->cct
);
3544 map
<string
, bufferlist
>::iterator aiter
= s
->bucket_attrs
.find(RGW_ATTR_LC
);
3545 if (aiter
== s
->bucket_attrs
.end()) {
3550 bufferlist::const_iterator iter
{&aiter
->second
};
3552 config
.decode(iter
);
3553 } catch (const buffer::error
& e
) {
3554 ldpp_dout(this, 0) << __func__
<< "decode life cycle config failed" << dendl
;
3560 void RGWGetLC_ObjStore_S3::send_response()
3563 if (op_ret
== -ENOENT
) {
3564 set_req_state_err(s
, ERR_NO_SUCH_LC
);
3566 set_req_state_err(s
, op_ret
);
3570 end_header(s
, this, "application/xml");
3576 encode_xml("LifecycleConfiguration", XMLNS_AWS_S3
, config
, s
->formatter
);
3577 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3580 void RGWPutLC_ObjStore_S3::send_response()
3583 set_req_state_err(s
, op_ret
);
3585 end_header(s
, this, "application/xml");
3589 void RGWDeleteLC_ObjStore_S3::send_response()
3592 op_ret
= STATUS_NO_CONTENT
;
3594 set_req_state_err(s
, op_ret
);
3597 end_header(s
, this, "application/xml");
3601 void RGWGetCORS_ObjStore_S3::send_response()
3604 if (op_ret
== -ENOENT
)
3605 set_req_state_err(s
, ERR_NO_SUCH_CORS_CONFIGURATION
);
3607 set_req_state_err(s
, op_ret
);
3610 end_header(s
, NULL
, "application/xml");
3614 RGWCORSConfiguration_S3
*s3cors
=
3615 static_cast<RGWCORSConfiguration_S3
*>(&bucket_cors
);
3624 int RGWPutCORS_ObjStore_S3::get_params(optional_yield y
)
3626 RGWCORSXMLParser_S3
parser(this, s
->cct
);
3627 RGWCORSConfiguration_S3
*cors_config
;
3629 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
3633 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
3638 if (!parser
.init()) {
3642 char* buf
= data
.c_str();
3643 if (!buf
|| !parser
.parse(buf
, data
.length(), 1)) {
3644 return -ERR_MALFORMED_XML
;
3647 static_cast<RGWCORSConfiguration_S3
*>(parser
.find_first(
3648 "CORSConfiguration"));
3650 return -ERR_MALFORMED_XML
;
3653 #define CORS_RULES_MAX_NUM 100
3654 int max_num
= s
->cct
->_conf
->rgw_cors_rules_max_num
;
3656 max_num
= CORS_RULES_MAX_NUM
;
3658 int cors_rules_num
= cors_config
->get_rules().size();
3659 if (cors_rules_num
> max_num
) {
3660 ldpp_dout(this, 4) << "An cors config can have up to "
3662 << " rules, request cors rules num: "
3663 << cors_rules_num
<< dendl
;
3664 op_ret
= -ERR_INVALID_CORS_RULES_ERROR
;
3665 s
->err
.message
= "The number of CORS rules should not exceed allowed limit of "
3666 + std::to_string(max_num
) + " rules.";
3667 return -ERR_INVALID_REQUEST
;
3670 // forward bucket cors requests to meta master zone
3671 if (!store
->is_meta_master()) {
3672 /* only need to keep this data around if we're not meta master */
3673 in_data
.append(data
);
3676 if (s
->cct
->_conf
->subsys
.should_gather
<ceph_subsys_rgw
, 15>()) {
3677 ldpp_dout(this, 15) << "CORSConfiguration";
3678 cors_config
->to_xml(*_dout
);
3682 cors_config
->encode(cors_bl
);
3687 void RGWPutCORS_ObjStore_S3::send_response()
3690 set_req_state_err(s
, op_ret
);
3692 end_header(s
, NULL
, "application/xml");
3696 void RGWDeleteCORS_ObjStore_S3::send_response()
3699 if (!r
|| r
== -ENOENT
)
3700 r
= STATUS_NO_CONTENT
;
3702 set_req_state_err(s
, r
);
3704 end_header(s
, NULL
);
3707 void RGWOptionsCORS_ObjStore_S3::send_response()
3709 string hdrs
, exp_hdrs
;
3710 uint32_t max_age
= CORS_MAX_AGE_INVALID
;
3711 /*EACCES means, there is no CORS registered yet for the bucket
3712 *ENOENT means, there is no match of the Origin in the list of CORSRule
3714 if (op_ret
== -ENOENT
)
3717 set_req_state_err(s
, op_ret
);
3719 end_header(s
, NULL
);
3722 get_response_params(hdrs
, exp_hdrs
, &max_age
);
3725 dump_access_control(s
, origin
, req_meth
, hdrs
.c_str(), exp_hdrs
.c_str(),
3727 end_header(s
, NULL
);
3730 void RGWPutBucketEncryption_ObjStore_S3::send_response()
3733 set_req_state_err(s
, op_ret
);
3739 void RGWGetBucketEncryption_ObjStore_S3::send_response()
3742 if (op_ret
== -ENOENT
)
3743 set_req_state_err(s
, ERR_NO_SUCH_BUCKET_ENCRYPTION_CONFIGURATION
);
3745 set_req_state_err(s
, op_ret
);
3749 end_header(s
, this, "application/xml");
3753 encode_xml("ServerSideEncryptionConfiguration", XMLNS_AWS_S3
,
3754 bucket_encryption_conf
, s
->formatter
);
3755 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3759 void RGWDeleteBucketEncryption_ObjStore_S3::send_response()
3762 op_ret
= STATUS_NO_CONTENT
;
3765 set_req_state_err(s
, op_ret
);
3770 void RGWGetRequestPayment_ObjStore_S3::send_response()
3773 end_header(s
, this, "application/xml");
3776 s
->formatter
->open_object_section_in_ns("RequestPaymentConfiguration", XMLNS_AWS_S3
);
3777 const char *payer
= requester_pays
? "Requester" : "BucketOwner";
3778 s
->formatter
->dump_string("Payer", payer
);
3779 s
->formatter
->close_section();
3780 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3783 class RGWSetRequestPaymentParser
: public RGWXMLParser
3785 XMLObj
*alloc_obj(const char *el
) override
{
3790 RGWSetRequestPaymentParser() {}
3791 ~RGWSetRequestPaymentParser() override
{}
3793 int get_request_payment_payer(bool *requester_pays
) {
3794 XMLObj
*config
= find_first("RequestPaymentConfiguration");
3798 *requester_pays
= false;
3800 XMLObj
*field
= config
->find_first("Payer");
3804 auto& s
= field
->get_data();
3806 if (stringcasecmp(s
, "Requester") == 0) {
3807 *requester_pays
= true;
3808 } else if (stringcasecmp(s
, "BucketOwner") != 0) {
3816 int RGWSetRequestPayment_ObjStore_S3::get_params(optional_yield y
)
3818 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
3821 std::tie(r
, in_data
) = read_all_input(s
, max_size
, false);
3828 RGWSetRequestPaymentParser parser
;
3830 if (!parser
.init()) {
3831 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
3835 char* buf
= in_data
.c_str();
3836 if (!parser
.parse(buf
, in_data
.length(), 1)) {
3837 ldpp_dout(this, 10) << "failed to parse data: " << buf
<< dendl
;
3841 return parser
.get_request_payment_payer(&requester_pays
);
3844 void RGWSetRequestPayment_ObjStore_S3::send_response()
3847 set_req_state_err(s
, op_ret
);
3852 int RGWInitMultipart_ObjStore_S3::get_params(optional_yield y
)
3854 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3855 op_ret
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
3864 void RGWInitMultipart_ObjStore_S3::send_response()
3867 set_req_state_err(s
, op_ret
);
3869 for (auto &it
: crypt_http_responses
)
3870 dump_header(s
, it
.first
, it
.second
);
3871 ceph::real_time abort_date
;
3873 bool exist_multipart_abort
= get_s3_multipart_abort_header(s
, mtime
, abort_date
, rule_id
);
3874 if (exist_multipart_abort
) {
3875 dump_time_header(s
, "x-amz-abort-date", abort_date
);
3876 dump_header_if_nonempty(s
, "x-amz-abort-rule-id", rule_id
);
3878 end_header(s
, this, "application/xml");
3881 s
->formatter
->open_object_section_in_ns("InitiateMultipartUploadResult", XMLNS_AWS_S3
);
3882 if (!s
->bucket_tenant
.empty())
3883 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3884 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3885 s
->formatter
->dump_string("Key", s
->object
->get_name());
3886 s
->formatter
->dump_string("UploadId", upload_id
);
3887 s
->formatter
->close_section();
3888 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3892 int RGWInitMultipart_ObjStore_S3::prepare_encryption(map
<string
, bufferlist
>& attrs
)
3895 res
= rgw_s3_prepare_encrypt(s
, attrs
, nullptr, crypt_http_responses
);
3899 int RGWCompleteMultipart_ObjStore_S3::get_params(optional_yield y
)
3901 int ret
= RGWCompleteMultipart_ObjStore::get_params(y
);
3906 map_qs_metadata(s
, true);
3908 return do_aws4_auth_completion();
3911 void RGWCompleteMultipart_ObjStore_S3::send_response()
3914 set_req_state_err(s
, op_ret
);
3916 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
3917 end_header(s
, this, "application/xml");
3920 s
->formatter
->open_object_section_in_ns("CompleteMultipartUploadResult", XMLNS_AWS_S3
);
3921 std::string base_uri
= compute_domain_uri(s
);
3922 if (!s
->bucket_tenant
.empty()) {
3923 s
->formatter
->dump_format("Location", "%s/%s:%s/%s",
3925 s
->bucket_tenant
.c_str(),
3926 s
->bucket_name
.c_str(),
3927 s
->object
->get_name().c_str()
3929 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3931 s
->formatter
->dump_format("Location", "%s/%s/%s",
3933 s
->bucket_name
.c_str(),
3934 s
->object
->get_name().c_str()
3937 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3938 s
->formatter
->dump_string("Key", s
->object
->get_name());
3939 s
->formatter
->dump_string("ETag", etag
);
3940 s
->formatter
->close_section();
3941 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3945 void RGWAbortMultipart_ObjStore_S3::send_response()
3949 r
= STATUS_NO_CONTENT
;
3951 set_req_state_err(s
, r
);
3953 end_header(s
, this);
3956 void RGWListMultipart_ObjStore_S3::send_response()
3959 set_req_state_err(s
, op_ret
);
3961 // Explicitly use chunked transfer encoding so that we can stream the result
3962 // to the user without having to wait for the full length of it.
3963 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3967 s
->formatter
->open_object_section_in_ns("ListPartsResult", XMLNS_AWS_S3
);
3968 map
<uint32_t, std::unique_ptr
<rgw::sal::MultipartPart
>>::iterator iter
;
3969 map
<uint32_t, std::unique_ptr
<rgw::sal::MultipartPart
>>::reverse_iterator test_iter
;
3972 iter
= upload
->get_parts().begin();
3973 test_iter
= upload
->get_parts().rbegin();
3974 if (test_iter
!= upload
->get_parts().rend()) {
3975 cur_max
= test_iter
->first
;
3977 if (!s
->bucket_tenant
.empty())
3978 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3979 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3980 s
->formatter
->dump_string("Key", s
->object
->get_name());
3981 s
->formatter
->dump_string("UploadId", upload_id
);
3982 s
->formatter
->dump_string("StorageClass", placement
->get_storage_class());
3983 s
->formatter
->dump_int("PartNumberMarker", marker
);
3984 s
->formatter
->dump_int("NextPartNumberMarker", cur_max
);
3985 s
->formatter
->dump_int("MaxParts", max_parts
);
3986 s
->formatter
->dump_string("IsTruncated", (truncated
? "true" : "false"));
3988 ACLOwner
& owner
= policy
.get_owner();
3989 dump_owner(s
, owner
.get_id(), owner
.get_display_name());
3991 for (; iter
!= upload
->get_parts().end(); ++iter
) {
3992 rgw::sal::MultipartPart
* part
= iter
->second
.get();
3994 s
->formatter
->open_object_section("Part");
3996 dump_time(s
, "LastModified", part
->get_mtime());
3998 s
->formatter
->dump_unsigned("PartNumber", part
->get_num());
3999 s
->formatter
->dump_format("ETag", "\"%s\"", part
->get_etag().c_str());
4000 s
->formatter
->dump_unsigned("Size", part
->get_size());
4001 s
->formatter
->close_section();
4003 s
->formatter
->close_section();
4004 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4008 void RGWListBucketMultiparts_ObjStore_S3::send_response()
4011 set_req_state_err(s
, op_ret
);
4014 // Explicitly use chunked transfer encoding so that we can stream the result
4015 // to the user without having to wait for the full length of it.
4016 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
4021 s
->formatter
->open_object_section_in_ns("ListMultipartUploadsResult", XMLNS_AWS_S3
);
4022 if (!s
->bucket_tenant
.empty())
4023 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
4024 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
4025 if (!prefix
.empty())
4026 s
->formatter
->dump_string("Prefix", prefix
);
4027 if (!marker_key
.empty())
4028 s
->formatter
->dump_string("KeyMarker", marker_key
);
4029 if (!marker_upload_id
.empty())
4030 s
->formatter
->dump_string("UploadIdMarker", marker_upload_id
);
4031 if (!next_marker_key
.empty())
4032 s
->formatter
->dump_string("NextKeyMarker", next_marker_key
);
4033 if (!next_marker_upload_id
.empty())
4034 s
->formatter
->dump_string("NextUploadIdMarker", next_marker_upload_id
);
4035 s
->formatter
->dump_int("MaxUploads", max_uploads
);
4036 if (!delimiter
.empty())
4037 s
->formatter
->dump_string("Delimiter", delimiter
);
4038 s
->formatter
->dump_string("IsTruncated", (is_truncated
? "true" : "false"));
4041 vector
<std::unique_ptr
<rgw::sal::MultipartUpload
>>::iterator iter
;
4042 for (iter
= uploads
.begin(); iter
!= uploads
.end(); ++iter
) {
4043 rgw::sal::MultipartUpload
* upload
= iter
->get();
4044 s
->formatter
->open_array_section("Upload");
4046 s
->formatter
->dump_string("Key", url_encode(upload
->get_key(), false));
4048 s
->formatter
->dump_string("Key", upload
->get_key());
4050 s
->formatter
->dump_string("UploadId", upload
->get_upload_id());
4051 const ACLOwner
& owner
= upload
->get_owner();
4052 dump_owner(s
, owner
.get_id(), owner
.get_display_name(), "Initiator");
4053 dump_owner(s
, owner
.get_id(), owner
.get_display_name()); // Owner
4054 s
->formatter
->dump_string("StorageClass", "STANDARD");
4055 dump_time(s
, "Initiated", upload
->get_mtime());
4056 s
->formatter
->close_section();
4058 if (!common_prefixes
.empty()) {
4059 s
->formatter
->open_array_section("CommonPrefixes");
4060 for (const auto& kv
: common_prefixes
) {
4062 s
->formatter
->dump_string("Prefix", url_encode(kv
.first
, false));
4064 s
->formatter
->dump_string("Prefix", kv
.first
);
4067 s
->formatter
->close_section();
4070 s
->formatter
->close_section();
4071 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4074 int RGWDeleteMultiObj_ObjStore_S3::get_params(optional_yield y
)
4076 int ret
= RGWDeleteMultiObj_ObjStore::get_params(y
);
4081 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
4082 if (bypass_gov_header
) {
4083 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
4084 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
4087 return do_aws4_auth_completion();
4090 void RGWDeleteMultiObj_ObjStore_S3::send_status()
4092 if (! status_dumped
) {
4094 set_req_state_err(s
, op_ret
);
4096 status_dumped
= true;
4100 void RGWDeleteMultiObj_ObjStore_S3::begin_response()
4103 if (!status_dumped
) {
4108 // Explicitly use chunked transfer encoding so that we can stream the result
4109 // to the user without having to wait for the full length of it.
4110 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
4111 s
->formatter
->open_object_section_in_ns("DeleteResult", XMLNS_AWS_S3
);
4113 rgw_flush_formatter(s
, s
->formatter
);
4116 void RGWDeleteMultiObj_ObjStore_S3::send_partial_response(rgw_obj_key
& key
,
4118 const string
& marker_version_id
, int ret
)
4121 if (ret
== 0 && !quiet
) {
4122 s
->formatter
->open_object_section("Deleted");
4123 s
->formatter
->dump_string("Key", key
.name
);
4124 if (!key
.instance
.empty()) {
4125 s
->formatter
->dump_string("VersionId", key
.instance
);
4127 if (delete_marker
) {
4128 s
->formatter
->dump_bool("DeleteMarker", true);
4129 s
->formatter
->dump_string("DeleteMarkerVersionId", marker_version_id
);
4131 s
->formatter
->close_section();
4132 } else if (ret
< 0) {
4133 struct rgw_http_error r
;
4136 s
->formatter
->open_object_section("Error");
4139 rgw_get_errno_s3(&r
, err_no
);
4141 s
->formatter
->dump_string("Key", key
.name
);
4142 s
->formatter
->dump_string("VersionId", key
.instance
);
4143 s
->formatter
->dump_string("Code", r
.s3_code
);
4144 s
->formatter
->dump_string("Message", r
.s3_code
);
4145 s
->formatter
->close_section();
4148 rgw_flush_formatter(s
, s
->formatter
);
4152 void RGWDeleteMultiObj_ObjStore_S3::end_response()
4155 s
->formatter
->close_section();
4156 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4159 void RGWGetObjLayout_ObjStore_S3::send_response()
4162 set_req_state_err(s
, op_ret
);
4164 end_header(s
, this, "application/json");
4172 f
.open_object_section("result");
4173 s
->object
->dump_obj_layout(this, s
->yield
, &f
, s
->obj_ctx
);
4175 rgw_flush_formatter(s
, &f
);
4178 int RGWConfigBucketMetaSearch_ObjStore_S3::get_params(optional_yield y
)
4180 auto iter
= s
->info
.x_meta_map
.find("x-amz-meta-search");
4181 if (iter
== s
->info
.x_meta_map
.end()) {
4182 s
->err
.message
= "X-Rgw-Meta-Search header not provided";
4183 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4187 list
<string
> expressions
;
4188 get_str_list(iter
->second
, ",", expressions
);
4190 for (auto& expression
: expressions
) {
4191 vector
<string
> args
;
4192 get_str_vec(expression
, ";", args
);
4195 s
->err
.message
= "invalid empty expression";
4196 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4199 if (args
.size() > 2) {
4200 s
->err
.message
= string("invalid expression: ") + expression
;
4201 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4205 string key
= boost::algorithm::to_lower_copy(rgw_trim_whitespace(args
[0]));
4207 if (args
.size() > 1) {
4208 val
= boost::algorithm::to_lower_copy(rgw_trim_whitespace(args
[1]));
4211 if (!boost::algorithm::starts_with(key
, RGW_AMZ_META_PREFIX
)) {
4212 s
->err
.message
= string("invalid expression, key must start with '" RGW_AMZ_META_PREFIX
"' : ") + expression
;
4213 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4217 key
= key
.substr(sizeof(RGW_AMZ_META_PREFIX
) - 1);
4219 ESEntityTypeMap::EntityType entity_type
;
4221 if (val
.empty() || val
== "str" || val
== "string") {
4222 entity_type
= ESEntityTypeMap::ES_ENTITY_STR
;
4223 } else if (val
== "int" || val
== "integer") {
4224 entity_type
= ESEntityTypeMap::ES_ENTITY_INT
;
4225 } else if (val
== "date" || val
== "datetime") {
4226 entity_type
= ESEntityTypeMap::ES_ENTITY_DATE
;
4228 s
->err
.message
= string("invalid entity type: ") + val
;
4229 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4233 mdsearch_config
[key
] = entity_type
;
4239 void RGWConfigBucketMetaSearch_ObjStore_S3::send_response()
4242 set_req_state_err(s
, op_ret
);
4244 end_header(s
, this);
4247 void RGWGetBucketMetaSearch_ObjStore_S3::send_response()
4250 set_req_state_err(s
, op_ret
);
4252 end_header(s
, NULL
, "application/xml");
4254 Formatter
*f
= s
->formatter
;
4255 f
->open_array_section("GetBucketMetaSearchResult");
4256 for (auto& e
: s
->bucket
->get_info().mdsearch_config
) {
4257 f
->open_object_section("Entry");
4258 string k
= string("x-amz-meta-") + e
.first
;
4259 f
->dump_string("Key", k
.c_str());
4262 case ESEntityTypeMap::ES_ENTITY_INT
:
4265 case ESEntityTypeMap::ES_ENTITY_DATE
:
4271 f
->dump_string("Type", type
);
4275 rgw_flush_formatter(s
, f
);
4278 void RGWDelBucketMetaSearch_ObjStore_S3::send_response()
4281 set_req_state_err(s
, op_ret
);
4283 end_header(s
, this);
4286 void RGWPutBucketObjectLock_ObjStore_S3::send_response()
4289 set_req_state_err(s
, op_ret
);
4295 void RGWGetBucketObjectLock_ObjStore_S3::send_response()
4298 set_req_state_err(s
, op_ret
);
4301 end_header(s
, this, "application/xml");
4307 encode_xml("ObjectLockConfiguration", s
->bucket
->get_info().obj_lock
, s
->formatter
);
4308 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4312 int RGWPutObjRetention_ObjStore_S3::get_params(optional_yield y
)
4314 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
4315 if (bypass_gov_header
) {
4316 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
4317 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
4320 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
4321 std::tie(op_ret
, data
) = read_all_input(s
, max_size
, false);
4325 void RGWPutObjRetention_ObjStore_S3::send_response()
4328 set_req_state_err(s
, op_ret
);
4334 void RGWGetObjRetention_ObjStore_S3::send_response()
4337 set_req_state_err(s
, op_ret
);
4340 end_header(s
, this, "application/xml");
4346 encode_xml("Retention", obj_retention
, s
->formatter
);
4347 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4350 void RGWPutObjLegalHold_ObjStore_S3::send_response()
4353 set_req_state_err(s
, op_ret
);
4359 void RGWGetObjLegalHold_ObjStore_S3::send_response()
4362 set_req_state_err(s
, op_ret
);
4365 end_header(s
, this, "application/xml");
4371 encode_xml("LegalHold", obj_legal_hold
, s
->formatter
);
4372 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4375 void RGWGetBucketPolicyStatus_ObjStore_S3::send_response()
4378 set_req_state_err(s
, op_ret
);
4381 end_header(s
, this, "application/xml");
4384 s
->formatter
->open_object_section_in_ns("PolicyStatus", XMLNS_AWS_S3
);
4385 // https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETPolicyStatus.html
4386 // mentions TRUE and FALSE, but boto/aws official clients seem to want lower
4387 // case which is returned by AWS as well; so let's be bug to bug compatible
4389 s
->formatter
->dump_bool("IsPublic", isPublic
);
4390 s
->formatter
->close_section();
4391 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4395 void RGWPutBucketPublicAccessBlock_ObjStore_S3::send_response()
4398 set_req_state_err(s
, op_ret
);
4404 void RGWGetBucketPublicAccessBlock_ObjStore_S3::send_response()
4407 set_req_state_err(s
, op_ret
);
4410 end_header(s
, this, "application/xml");
4413 access_conf
.dump_xml(s
->formatter
);
4414 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4417 RGWOp
*RGWHandler_REST_Service_S3::op_get()
4419 if (is_usage_op()) {
4420 return new RGWGetUsage_ObjStore_S3
;
4422 return new RGWListBuckets_ObjStore_S3
;
4426 RGWOp
*RGWHandler_REST_Service_S3::op_head()
4428 return new RGWListBuckets_ObjStore_S3
;
4431 RGWOp
*RGWHandler_REST_Service_S3::op_post()
4433 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
4437 std::tie(ret
, data
) = rgw_rest_read_all_input(s
, max_size
, false);
4442 const auto post_body
= data
.to_str();
4445 RGWHandler_REST_STS
sts_handler(auth_registry
, post_body
);
4446 sts_handler
.init(store
, s
, s
->cio
);
4447 auto op
= sts_handler
.get_op();
4454 RGWHandler_REST_IAM
iam_handler(auth_registry
, post_body
);
4455 iam_handler
.init(store
, s
, s
->cio
);
4456 auto op
= iam_handler
.get_op();
4463 RGWHandler_REST_PSTopic_AWS
topic_handler(auth_registry
, post_body
);
4464 topic_handler
.init(store
, s
, s
->cio
);
4465 auto op
= topic_handler
.get_op();
4474 RGWOp
*RGWHandler_REST_Bucket_S3::get_obj_op(bool get_data
) const
4479 s
->info
.args
.get_int("list-type", &list_type
, 1);
4480 switch (list_type
) {
4482 return new RGWListBucket_ObjStore_S3
;
4484 return new RGWListBucket_ObjStore_S3v2
;
4486 ldpp_dout(s
, 5) << __func__
<< ": unsupported list-type " << list_type
<< dendl
;
4487 return new RGWListBucket_ObjStore_S3
;
4490 return new RGWStatBucket_ObjStore_S3
;
4494 RGWOp
*RGWHandler_REST_Bucket_S3::op_get()
4496 if (s
->info
.args
.sub_resource_exists("encryption"))
4499 if (s
->info
.args
.sub_resource_exists("logging"))
4500 return new RGWGetBucketLogging_ObjStore_S3
;
4502 if (s
->info
.args
.sub_resource_exists("location"))
4503 return new RGWGetBucketLocation_ObjStore_S3
;
4505 if (s
->info
.args
.sub_resource_exists("versioning"))
4506 return new RGWGetBucketVersioning_ObjStore_S3
;
4508 if (s
->info
.args
.sub_resource_exists("website")) {
4509 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4512 return new RGWGetBucketWebsite_ObjStore_S3
;
4515 if (s
->info
.args
.exists("mdsearch")) {
4516 return new RGWGetBucketMetaSearch_ObjStore_S3
;
4520 return new RGWGetACLs_ObjStore_S3
;
4521 } else if (is_cors_op()) {
4522 return new RGWGetCORS_ObjStore_S3
;
4523 } else if (is_request_payment_op()) {
4524 return new RGWGetRequestPayment_ObjStore_S3
;
4525 } else if (s
->info
.args
.exists("uploads")) {
4526 return new RGWListBucketMultiparts_ObjStore_S3
;
4527 } else if(is_lc_op()) {
4528 return new RGWGetLC_ObjStore_S3
;
4529 } else if(is_policy_op()) {
4530 return new RGWGetBucketPolicy
;
4531 } else if (is_tagging_op()) {
4532 return new RGWGetBucketTags_ObjStore_S3
;
4533 } else if (is_object_lock_op()) {
4534 return new RGWGetBucketObjectLock_ObjStore_S3
;
4535 } else if (is_notification_op()) {
4536 return RGWHandler_REST_PSNotifs_S3::create_get_op();
4537 } else if (is_replication_op()) {
4538 return new RGWGetBucketReplication_ObjStore_S3
;
4539 } else if (is_policy_status_op()) {
4540 return new RGWGetBucketPolicyStatus_ObjStore_S3
;
4541 } else if (is_block_public_access_op()) {
4542 return new RGWGetBucketPublicAccessBlock_ObjStore_S3
;
4543 } else if (is_bucket_encryption_op()) {
4544 return new RGWGetBucketEncryption_ObjStore_S3
;
4546 return get_obj_op(true);
4549 RGWOp
*RGWHandler_REST_Bucket_S3::op_head()
4552 return new RGWGetACLs_ObjStore_S3
;
4553 } else if (s
->info
.args
.exists("uploads")) {
4554 return new RGWListBucketMultiparts_ObjStore_S3
;
4556 return get_obj_op(false);
4559 RGWOp
*RGWHandler_REST_Bucket_S3::op_put()
4561 if (s
->info
.args
.sub_resource_exists("logging") ||
4562 s
->info
.args
.sub_resource_exists("encryption"))
4564 if (s
->info
.args
.sub_resource_exists("versioning"))
4565 return new RGWSetBucketVersioning_ObjStore_S3
;
4566 if (s
->info
.args
.sub_resource_exists("website")) {
4567 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4570 return new RGWSetBucketWebsite_ObjStore_S3
;
4572 if (is_tagging_op()) {
4573 return new RGWPutBucketTags_ObjStore_S3
;
4574 } else if (is_acl_op()) {
4575 return new RGWPutACLs_ObjStore_S3
;
4576 } else if (is_cors_op()) {
4577 return new RGWPutCORS_ObjStore_S3
;
4578 } else if (is_request_payment_op()) {
4579 return new RGWSetRequestPayment_ObjStore_S3
;
4580 } else if(is_lc_op()) {
4581 return new RGWPutLC_ObjStore_S3
;
4582 } else if(is_policy_op()) {
4583 return new RGWPutBucketPolicy
;
4584 } else if (is_object_lock_op()) {
4585 return new RGWPutBucketObjectLock_ObjStore_S3
;
4586 } else if (is_notification_op()) {
4587 return RGWHandler_REST_PSNotifs_S3::create_put_op();
4588 } else if (is_replication_op()) {
4589 auto sync_policy_handler
= static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->get_sync_policy_handler(nullopt
);
4590 if (!sync_policy_handler
||
4591 sync_policy_handler
->is_legacy_config()) {
4595 return new RGWPutBucketReplication_ObjStore_S3
;
4596 } else if (is_block_public_access_op()) {
4597 return new RGWPutBucketPublicAccessBlock_ObjStore_S3
;
4598 } else if (is_bucket_encryption_op()) {
4599 return new RGWPutBucketEncryption_ObjStore_S3
;
4601 return new RGWCreateBucket_ObjStore_S3
;
4604 RGWOp
*RGWHandler_REST_Bucket_S3::op_delete()
4606 if (s
->info
.args
.sub_resource_exists("logging") ||
4607 s
->info
.args
.sub_resource_exists("encryption"))
4610 if (is_tagging_op()) {
4611 return new RGWDeleteBucketTags_ObjStore_S3
;
4612 } else if (is_cors_op()) {
4613 return new RGWDeleteCORS_ObjStore_S3
;
4614 } else if(is_lc_op()) {
4615 return new RGWDeleteLC_ObjStore_S3
;
4616 } else if(is_policy_op()) {
4617 return new RGWDeleteBucketPolicy
;
4618 } else if (is_notification_op()) {
4619 return RGWHandler_REST_PSNotifs_S3::create_delete_op();
4620 } else if (is_replication_op()) {
4621 return new RGWDeleteBucketReplication_ObjStore_S3
;
4622 } else if (is_block_public_access_op()) {
4623 return new RGWDeleteBucketPublicAccessBlock
;
4624 } else if (is_bucket_encryption_op()) {
4625 return new RGWDeleteBucketEncryption_ObjStore_S3
;
4628 if (s
->info
.args
.sub_resource_exists("website")) {
4629 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4632 return new RGWDeleteBucketWebsite_ObjStore_S3
;
4635 if (s
->info
.args
.exists("mdsearch")) {
4636 return new RGWDelBucketMetaSearch_ObjStore_S3
;
4639 return new RGWDeleteBucket_ObjStore_S3
;
4642 RGWOp
*RGWHandler_REST_Bucket_S3::op_post()
4644 if (s
->info
.args
.exists("delete")) {
4645 return new RGWDeleteMultiObj_ObjStore_S3
;
4648 if (s
->info
.args
.exists("mdsearch")) {
4649 return new RGWConfigBucketMetaSearch_ObjStore_S3
;
4652 return new RGWPostObj_ObjStore_S3
;
4655 RGWOp
*RGWHandler_REST_Bucket_S3::op_options()
4657 return new RGWOptionsCORS_ObjStore_S3
;
4660 RGWOp
*RGWHandler_REST_Obj_S3::get_obj_op(bool get_data
)
4662 RGWGetObj_ObjStore_S3
*get_obj_op
= new RGWGetObj_ObjStore_S3
;
4663 get_obj_op
->set_get_data(get_data
);
4667 RGWOp
*RGWHandler_REST_Obj_S3::op_get()
4670 return new RGWGetACLs_ObjStore_S3
;
4671 } else if (s
->info
.args
.exists("uploadId")) {
4672 return new RGWListMultipart_ObjStore_S3
;
4673 } else if (s
->info
.args
.exists("layout")) {
4674 return new RGWGetObjLayout_ObjStore_S3
;
4675 } else if (is_tagging_op()) {
4676 return new RGWGetObjTags_ObjStore_S3
;
4677 } else if (is_obj_retention_op()) {
4678 return new RGWGetObjRetention_ObjStore_S3
;
4679 } else if (is_obj_legal_hold_op()) {
4680 return new RGWGetObjLegalHold_ObjStore_S3
;
4682 return get_obj_op(true);
4685 RGWOp
*RGWHandler_REST_Obj_S3::op_head()
4688 return new RGWGetACLs_ObjStore_S3
;
4689 } else if (s
->info
.args
.exists("uploadId")) {
4690 return new RGWListMultipart_ObjStore_S3
;
4692 return get_obj_op(false);
4695 RGWOp
*RGWHandler_REST_Obj_S3::op_put()
4698 return new RGWPutACLs_ObjStore_S3
;
4699 } else if (is_tagging_op()) {
4700 return new RGWPutObjTags_ObjStore_S3
;
4701 } else if (is_obj_retention_op()) {
4702 return new RGWPutObjRetention_ObjStore_S3
;
4703 } else if (is_obj_legal_hold_op()) {
4704 return new RGWPutObjLegalHold_ObjStore_S3
;
4707 if (s
->init_state
.src_bucket
.empty())
4708 return new RGWPutObj_ObjStore_S3
;
4710 return new RGWCopyObj_ObjStore_S3
;
4713 RGWOp
*RGWHandler_REST_Obj_S3::op_delete()
4715 if (is_tagging_op()) {
4716 return new RGWDeleteObjTags_ObjStore_S3
;
4718 string upload_id
= s
->info
.args
.get("uploadId");
4720 if (upload_id
.empty())
4721 return new RGWDeleteObj_ObjStore_S3
;
4723 return new RGWAbortMultipart_ObjStore_S3
;
4726 RGWOp
*RGWHandler_REST_Obj_S3::op_post()
4728 if (s
->info
.args
.exists("uploadId"))
4729 return new RGWCompleteMultipart_ObjStore_S3
;
4731 if (s
->info
.args
.exists("uploads"))
4732 return new RGWInitMultipart_ObjStore_S3
;
4735 return rgw::s3select::create_s3select_op();
4737 return new RGWPostObj_ObjStore_S3
;
4740 RGWOp
*RGWHandler_REST_Obj_S3::op_options()
4742 return new RGWOptionsCORS_ObjStore_S3
;
4745 int RGWHandler_REST_S3::init_from_header(rgw::sal::Store
* store
,
4746 struct req_state
* s
,
4747 int default_formatter
,
4748 bool configurable_format
)
4753 const char *req_name
= s
->relative_uri
.c_str();
4756 if (*req_name
== '?') {
4759 p
= s
->info
.request_params
.c_str();
4762 s
->info
.args
.set(p
);
4763 s
->info
.args
.parse(s
);
4765 /* must be called after the args parsing */
4766 int ret
= allocate_formatter(s
, default_formatter
, configurable_format
);
4770 if (*req_name
!= '/')
4779 int pos
= req
.find('/');
4781 first
= req
.substr(0, pos
);
4787 * XXX The intent of the check for empty is apparently to let the bucket
4788 * name from DNS to be set ahead. However, we currently take the DNS
4789 * bucket and re-insert it into URL in rgw_rest.cc:RGWREST::preprocess().
4790 * So, this check is meaningless.
4792 * Rather than dropping this, the code needs to be changed into putting
4793 * the bucket (and its tenant) from DNS and Host: header (HTTP_HOST)
4794 * into req_status.bucket_name directly.
4796 if (s
->init_state
.url_bucket
.empty()) {
4797 // Save bucket to tide us over until token is parsed.
4798 s
->init_state
.url_bucket
= first
;
4799 string encoded_obj_str
;
4801 encoded_obj_str
= req
.substr(pos
+1);
4804 /* dang: s->bucket is never set here, since it's created with permissions.
4805 * These calls will always create an object with no bucket. */
4806 if (!encoded_obj_str
.empty()) {
4808 s
->object
= s
->bucket
->get_object(rgw_obj_key(encoded_obj_str
, s
->info
.args
.get("versionId")));
4810 s
->object
= store
->get_object(rgw_obj_key(encoded_obj_str
, s
->info
.args
.get("versionId")));
4815 s
->object
= s
->bucket
->get_object(rgw_obj_key(req_name
, s
->info
.args
.get("versionId")));
4817 s
->object
= store
->get_object(rgw_obj_key(req_name
, s
->info
.args
.get("versionId")));
4823 static int verify_mfa(rgw::sal::Store
* store
, RGWUserInfo
*user
,
4824 const string
& mfa_str
, bool *verified
, const DoutPrefixProvider
*dpp
, optional_yield y
)
4826 vector
<string
> params
;
4827 get_str_vec(mfa_str
, " ", params
);
4829 if (params
.size() != 2) {
4830 ldpp_dout(dpp
, 5) << "NOTICE: invalid mfa string provided: " << mfa_str
<< dendl
;
4834 string
& serial
= params
[0];
4835 string
& pin
= params
[1];
4837 auto i
= user
->mfa_ids
.find(serial
);
4838 if (i
== user
->mfa_ids
.end()) {
4839 ldpp_dout(dpp
, 5) << "NOTICE: user does not have mfa device with serial=" << serial
<< dendl
;
4843 int ret
= static_cast<rgw::sal::RadosStore
*>(store
)->svc()->cls
->mfa
.check_mfa(dpp
, user
->user_id
, serial
, pin
, y
);
4845 ldpp_dout(dpp
, 20) << "NOTICE: failed to check MFA, serial=" << serial
<< dendl
;
4854 int RGWHandler_REST_S3::postauth_init(optional_yield y
)
4856 struct req_init_state
*t
= &s
->init_state
;
4858 rgw_parse_url_bucket(t
->url_bucket
, s
->user
->get_tenant(),
4859 s
->bucket_tenant
, s
->bucket_name
);
4861 if (s
->auth
.identity
->get_identity_type() == TYPE_ROLE
) {
4862 s
->bucket_tenant
= s
->auth
.identity
->get_role_tenant();
4865 ldpp_dout(s
, 10) << "s->object=" << s
->object
4866 << " s->bucket=" << rgw_make_bucket_entry_name(s
->bucket_tenant
, s
->bucket_name
) << dendl
;
4869 ret
= rgw_validate_tenant_name(s
->bucket_tenant
);
4872 if (!s
->bucket_name
.empty() && !rgw::sal::Object::empty(s
->object
.get())) {
4873 ret
= validate_object_name(s
->object
->get_name());
4878 if (!t
->src_bucket
.empty()) {
4880 if (s
->auth
.identity
->get_identity_type() == TYPE_ROLE
) {
4881 auth_tenant
= s
->auth
.identity
->get_role_tenant();
4883 auth_tenant
= s
->user
->get_tenant();
4885 rgw_parse_url_bucket(t
->src_bucket
, auth_tenant
,
4886 s
->src_tenant_name
, s
->src_bucket_name
);
4887 ret
= rgw_validate_tenant_name(s
->src_tenant_name
);
4892 const char *mfa
= s
->info
.env
->get("HTTP_X_AMZ_MFA");
4894 ret
= verify_mfa(store
, &s
->user
->get_info(), string(mfa
), &s
->mfa_verified
, s
, y
);
4900 int RGWHandler_REST_S3::init(rgw::sal::Store
* store
, struct req_state
*s
,
4901 rgw::io::BasicClient
*cio
)
4907 ret
= rgw_validate_tenant_name(s
->bucket_tenant
);
4910 if (!s
->bucket_name
.empty()) {
4911 ret
= validate_object_name(s
->object
->get_name());
4916 const char *cacl
= s
->info
.env
->get("HTTP_X_AMZ_ACL");
4918 s
->canned_acl
= cacl
;
4920 s
->has_acl_header
= s
->info
.env
->exists_prefix("HTTP_X_AMZ_GRANT");
4922 const char *copy_source
= s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE");
4924 (! s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE_RANGE")) &&
4925 (! s
->info
.args
.exists("uploadId"))) {
4928 ret
= RGWCopyObj::parse_copy_location(copy_source
,
4929 s
->init_state
.src_bucket
,
4933 ldpp_dout(s
, 0) << "failed to parse copy location" << dendl
;
4934 return -EINVAL
; // XXX why not -ERR_INVALID_BUCKET_NAME or -ERR_BAD_URL?
4936 s
->src_object
= store
->get_object(key
);
4939 const char *sc
= s
->info
.env
->get("HTTP_X_AMZ_STORAGE_CLASS");
4941 s
->info
.storage_class
= sc
;
4944 return RGWHandler_REST::init(store
, s
, cio
);
4947 int RGWHandler_REST_S3::authorize(const DoutPrefixProvider
*dpp
, optional_yield y
)
4949 if (s
->info
.args
.exists("Action") && s
->info
.args
.get("Action") == "AssumeRoleWithWebIdentity") {
4950 return RGW_Auth_STS::authorize(dpp
, store
, auth_registry
, s
, y
);
4952 return RGW_Auth_S3::authorize(dpp
, store
, auth_registry
, s
, y
);
4955 enum class AwsVersion
{
4961 enum class AwsRoute
{
4967 static inline std::pair
<AwsVersion
, AwsRoute
>
4968 discover_aws_flavour(const req_info
& info
)
4970 using rgw::auth::s3::AWS4_HMAC_SHA256_STR
;
4972 AwsVersion version
= AwsVersion::UNKNOWN
;
4973 AwsRoute route
= AwsRoute::UNKNOWN
;
4975 const char* http_auth
= info
.env
->get("HTTP_AUTHORIZATION");
4976 if (http_auth
&& http_auth
[0]) {
4977 /* Authorization in Header */
4978 route
= AwsRoute::HEADERS
;
4980 if (!strncmp(http_auth
, AWS4_HMAC_SHA256_STR
,
4981 strlen(AWS4_HMAC_SHA256_STR
))) {
4983 version
= AwsVersion::V4
;
4984 } else if (!strncmp(http_auth
, "AWS ", 4)) {
4986 version
= AwsVersion::V2
;
4989 route
= AwsRoute::QUERY_STRING
;
4991 if (info
.args
.get("x-amz-algorithm") == AWS4_HMAC_SHA256_STR
) {
4993 version
= AwsVersion::V4
;
4994 } else if (!info
.args
.get("AWSAccessKeyId").empty()) {
4996 version
= AwsVersion::V2
;
5000 return std::make_pair(version
, route
);
5004 * verify that a signed request comes from the keyholder
5005 * by checking the signature against our locally-computed version
5007 * it tries AWS v4 before AWS v2
5009 int RGW_Auth_S3::authorize(const DoutPrefixProvider
*dpp
,
5010 rgw::sal::Store
* const store
,
5011 const rgw::auth::StrategyRegistry
& auth_registry
,
5012 struct req_state
* const s
, optional_yield y
)
5015 /* neither keystone and rados enabled; warn and exit! */
5016 if (!store
->ctx()->_conf
->rgw_s3_auth_use_rados
&&
5017 !store
->ctx()->_conf
->rgw_s3_auth_use_keystone
&&
5018 !store
->ctx()->_conf
->rgw_s3_auth_use_ldap
) {
5019 ldpp_dout(dpp
, 0) << "WARNING: no authorization backend enabled! Users will never authenticate." << dendl
;
5023 const auto ret
= rgw::auth::Strategy::apply(dpp
, auth_registry
.get_s3_main(), s
, y
);
5025 /* Populate the owner info. */
5026 s
->owner
.set_id(s
->user
->get_id());
5027 s
->owner
.set_name(s
->user
->get_display_name());
5032 int RGWHandler_Auth_S3::init(rgw::sal::Store
* store
, struct req_state
*state
,
5033 rgw::io::BasicClient
*cio
)
5035 int ret
= RGWHandler_REST_S3::init_from_header(store
, state
, RGW_FORMAT_JSON
, true);
5039 return RGWHandler_REST::init(store
, state
, cio
);
5042 RGWHandler_REST
* RGWRESTMgr_S3::get_handler(rgw::sal::Store
* store
,
5043 struct req_state
* const s
,
5044 const rgw::auth::StrategyRegistry
& auth_registry
,
5045 const std::string
& frontend_prefix
)
5047 bool is_s3website
= enable_s3website
&& (s
->prot_flags
& RGW_REST_WEBSITE
);
5049 RGWHandler_REST_S3::init_from_header(store
, s
,
5050 is_s3website
? RGW_FORMAT_HTML
:
5051 RGW_FORMAT_XML
, true);
5055 RGWHandler_REST
* handler
;
5056 // TODO: Make this more readable
5058 if (s
->init_state
.url_bucket
.empty()) {
5059 handler
= new RGWHandler_REST_Service_S3Website(auth_registry
);
5060 } else if (rgw::sal::Object::empty(s
->object
.get())) {
5061 handler
= new RGWHandler_REST_Bucket_S3Website(auth_registry
);
5063 handler
= new RGWHandler_REST_Obj_S3Website(auth_registry
);
5066 if (s
->init_state
.url_bucket
.empty()) {
5067 handler
= new RGWHandler_REST_Service_S3(auth_registry
, enable_sts
, enable_iam
, enable_pubsub
);
5068 } else if (!rgw::sal::Object::empty(s
->object
.get())) {
5069 handler
= new RGWHandler_REST_Obj_S3(auth_registry
);
5070 } else if (s
->info
.args
.exist_obj_excl_sub_resource()) {
5073 handler
= new RGWHandler_REST_Bucket_S3(auth_registry
, enable_pubsub
);
5077 ldpp_dout(s
, 20) << __func__
<< " handler=" << typeid(*handler
).name()
5082 bool RGWHandler_REST_S3Website::web_dir() const {
5083 std::string subdir_name
;
5084 if (!rgw::sal::Object::empty(s
->object
.get())) {
5085 subdir_name
= url_decode(s
->object
->get_name());
5088 if (subdir_name
.empty()) {
5090 } else if (subdir_name
.back() == '/' && subdir_name
.size() > 1) {
5091 subdir_name
.pop_back();
5094 std::unique_ptr
<rgw::sal::Object
> obj
= s
->bucket
->get_object(rgw_obj_key(subdir_name
));
5096 RGWObjectCtx
& obj_ctx
= *static_cast<RGWObjectCtx
*>(s
->obj_ctx
);
5097 obj
->set_atomic(&obj_ctx
);
5098 obj
->set_prefetch_data(&obj_ctx
);
5100 RGWObjState
* state
= nullptr;
5101 if (obj
->get_obj_state(s
, &obj_ctx
, &state
, s
->yield
) < 0) {
5104 if (! state
->exists
) {
5107 return state
->exists
;
5110 int RGWHandler_REST_S3Website::init(rgw::sal::Store
* store
, req_state
*s
,
5111 rgw::io::BasicClient
* cio
)
5113 // save the original object name before retarget() replaces it with the
5114 // result of get_effective_key(). the error_handler() needs the original
5115 // object name for redirect handling
5116 if (!rgw::sal::Object::empty(s
->object
.get())) {
5117 original_object_name
= s
->object
->get_name();
5119 original_object_name
= "";
5122 return RGWHandler_REST_S3::init(store
, s
, cio
);
5125 int RGWHandler_REST_S3Website::retarget(RGWOp
* op
, RGWOp
** new_op
, optional_yield y
) {
5127 ldpp_dout(s
, 10) << __func__
<< " Starting retarget" << dendl
;
5129 if (!(s
->prot_flags
& RGW_REST_WEBSITE
))
5132 if (rgw::sal::Bucket::empty(s
->bucket
.get())) {
5133 // TODO-FUTURE: if the bucket does not exist, maybe expose it here?
5134 return -ERR_NO_SUCH_BUCKET
;
5137 if (!s
->bucket
->get_info().has_website
) {
5138 // TODO-FUTURE: if the bucket has no WebsiteConfig, expose it here
5139 return -ERR_NO_SUCH_WEBSITE_CONFIGURATION
;
5142 rgw_obj_key new_obj
;
5144 if (!rgw::sal::Object::empty(s
->object
.get())) {
5145 key_name
= s
->object
->get_name();
5147 bool get_res
= s
->bucket
->get_info().website_conf
.get_effective_key(key_name
, &new_obj
.name
, web_dir());
5149 s
->err
.message
= "The IndexDocument Suffix is not configurated or not well formed!";
5150 ldpp_dout(s
, 5) << s
->err
.message
<< dendl
;
5154 ldpp_dout(s
, 10) << "retarget get_effective_key " << s
->object
<< " -> "
5155 << new_obj
<< dendl
;
5157 RGWBWRoutingRule rrule
;
5158 bool should_redirect
=
5159 s
->bucket
->get_info().website_conf
.should_redirect(new_obj
.name
, 0, &rrule
);
5161 if (should_redirect
) {
5162 const string
& hostname
= s
->info
.env
->get("HTTP_HOST", "");
5163 const string
& protocol
=
5164 (s
->info
.env
->get("SERVER_PORT_SECURE") ? "https" : "http");
5165 int redirect_code
= 0;
5166 rrule
.apply_rule(protocol
, hostname
, key_name
, &s
->redirect
,
5168 // APply a custom HTTP response code
5169 if (redirect_code
> 0)
5170 s
->err
.http_ret
= redirect_code
; // Apply a custom HTTP response code
5171 ldpp_dout(s
, 10) << "retarget redirect code=" << redirect_code
5172 << " proto+host:" << protocol
<< "://" << hostname
5173 << " -> " << s
->redirect
<< dendl
;
5174 return -ERR_WEBSITE_REDIRECT
;
5178 * FIXME: if s->object != new_obj, drop op and create a new op to handle
5179 * operation. Or remove this comment if it's not applicable anymore
5180 * dang: This could be problematic, since we're not actually replacing op, but
5181 * we are replacing s->object. Something might have a pointer to it.
5183 s
->object
= s
->bucket
->get_object(new_obj
);
5188 RGWOp
* RGWHandler_REST_S3Website::op_get()
5190 return get_obj_op(true);
5193 RGWOp
* RGWHandler_REST_S3Website::op_head()
5195 return get_obj_op(false);
5198 int RGWHandler_REST_S3Website::serve_errordoc(const DoutPrefixProvider
*dpp
, int http_ret
, const string
& errordoc_key
, optional_yield y
) {
5200 s
->formatter
->reset(); /* Try to throw it all away */
5202 std::shared_ptr
<RGWGetObj_ObjStore_S3Website
> getop( static_cast<RGWGetObj_ObjStore_S3Website
*>(op_get()));
5203 if (getop
.get() == NULL
) {
5204 return -1; // Trigger double error handler
5206 getop
->init(store
, s
, this);
5207 getop
->range_str
= NULL
;
5208 getop
->if_mod
= NULL
;
5209 getop
->if_unmod
= NULL
;
5210 getop
->if_match
= NULL
;
5211 getop
->if_nomatch
= NULL
;
5212 /* This is okay. It's an error, so nothing will run after this, and it can be
5213 * called by abort_early(), which can be called before s->object or s->bucket
5214 * are set up. Note, it won't have bucket. */
5215 s
->object
= store
->get_object(errordoc_key
);
5217 ret
= init_permissions(getop
.get(), y
);
5219 ldpp_dout(s
, 20) << "serve_errordoc failed, init_permissions ret=" << ret
<< dendl
;
5220 return -1; // Trigger double error handler
5223 ret
= read_permissions(getop
.get(), y
);
5225 ldpp_dout(s
, 20) << "serve_errordoc failed, read_permissions ret=" << ret
<< dendl
;
5226 return -1; // Trigger double error handler
5230 getop
->set_custom_http_response(http_ret
);
5233 ret
= getop
->init_processing(y
);
5235 ldpp_dout(s
, 20) << "serve_errordoc failed, init_processing ret=" << ret
<< dendl
;
5236 return -1; // Trigger double error handler
5239 ret
= getop
->verify_op_mask();
5241 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_op_mask ret=" << ret
<< dendl
;
5242 return -1; // Trigger double error handler
5245 ret
= getop
->verify_permission(y
);
5247 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_permission ret=" << ret
<< dendl
;
5248 return -1; // Trigger double error handler
5251 ret
= getop
->verify_params();
5253 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_params ret=" << ret
<< dendl
;
5254 return -1; // Trigger double error handler
5257 // No going back now
5260 * FIXME Missing headers:
5261 * With a working errordoc, the s3 error fields are rendered as HTTP headers,
5262 * x-amz-error-code: NoSuchKey
5263 * x-amz-error-message: The specified key does not exist.
5264 * x-amz-error-detail-Key: foo
5271 int RGWHandler_REST_S3Website::error_handler(int err_no
,
5272 string
* error_content
,
5274 int new_err_no
= -1;
5275 rgw_http_errors::const_iterator r
= rgw_http_s3_errors
.find(err_no
> 0 ? err_no
: -err_no
);
5276 int http_error_code
= -1;
5278 if (r
!= rgw_http_s3_errors
.end()) {
5279 http_error_code
= r
->second
.first
;
5281 ldpp_dout(s
, 10) << "RGWHandler_REST_S3Website::error_handler err_no=" << err_no
<< " http_ret=" << http_error_code
<< dendl
;
5283 RGWBWRoutingRule rrule
;
5284 bool have_bucket
= !rgw::sal::Bucket::empty(s
->bucket
.get());
5285 bool should_redirect
= false;
5288 s
->bucket
->get_info().website_conf
.should_redirect(original_object_name
,
5289 http_error_code
, &rrule
);
5292 if (should_redirect
) {
5293 const string
& hostname
= s
->info
.env
->get("HTTP_HOST", "");
5294 const string
& protocol
=
5295 (s
->info
.env
->get("SERVER_PORT_SECURE") ? "https" : "http");
5296 int redirect_code
= 0;
5297 rrule
.apply_rule(protocol
, hostname
, original_object_name
,
5298 &s
->redirect
, &redirect_code
);
5299 // Apply a custom HTTP response code
5300 if (redirect_code
> 0)
5301 s
->err
.http_ret
= redirect_code
; // Apply a custom HTTP response code
5302 ldpp_dout(s
, 10) << "error handler redirect code=" << redirect_code
5303 << " proto+host:" << protocol
<< "://" << hostname
5304 << " -> " << s
->redirect
<< dendl
;
5305 return -ERR_WEBSITE_REDIRECT
;
5306 } else if (err_no
== -ERR_WEBSITE_REDIRECT
) {
5307 // Do nothing here, this redirect will be handled in abort_early's ERR_WEBSITE_REDIRECT block
5308 // Do NOT fire the ErrorDoc handler
5309 } else if (have_bucket
&& !s
->bucket
->get_info().website_conf
.error_doc
.empty()) {
5310 /* This serves an entire page!
5311 On success, it will return zero, and no further content should be sent to the socket
5312 On failure, we need the double-error handler
5314 new_err_no
= RGWHandler_REST_S3Website::serve_errordoc(s
, http_error_code
, s
->bucket
->get_info().website_conf
.error_doc
, y
);
5315 if (new_err_no
!= -1) {
5316 err_no
= new_err_no
;
5319 ldpp_dout(s
, 20) << "No special error handling today!" << dendl
;
5325 RGWOp
* RGWHandler_REST_Obj_S3Website::get_obj_op(bool get_data
)
5327 /** If we are in website mode, then it is explicitly impossible to run GET or
5328 * HEAD on the actual directory. We must convert the request to run on the
5329 * suffix object instead!
5331 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5332 op
->set_get_data(get_data
);
5336 RGWOp
* RGWHandler_REST_Bucket_S3Website::get_obj_op(bool get_data
)
5338 /** If we are in website mode, then it is explicitly impossible to run GET or
5339 * HEAD on the actual directory. We must convert the request to run on the
5340 * suffix object instead!
5342 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5343 op
->set_get_data(get_data
);
5347 RGWOp
* RGWHandler_REST_Service_S3Website::get_obj_op(bool get_data
)
5349 /** If we are in website mode, then it is explicitly impossible to run GET or
5350 * HEAD on the actual directory. We must convert the request to run on the
5351 * suffix object instead!
5353 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5354 op
->set_get_data(get_data
);
5359 namespace rgw::auth::s3
{
5361 static rgw::auth::Completer::cmplptr_t
5362 null_completer_factory(const boost::optional
<std::string
>& secret_key
)
5368 AWSEngine::VersionAbstractor::auth_data_t
5369 AWSGeneralAbstractor::get_auth_data(const req_state
* const s
) const
5373 std::tie(version
, route
) = discover_aws_flavour(s
->info
);
5375 if (version
== AwsVersion::V2
) {
5376 return get_auth_data_v2(s
);
5377 } else if (version
== AwsVersion::V4
) {
5378 return get_auth_data_v4(s
, route
== AwsRoute::QUERY_STRING
);
5380 /* FIXME(rzarzynski): handle anon user. */
5385 boost::optional
<std::string
>
5386 AWSGeneralAbstractor::get_v4_canonical_headers(
5387 const req_info
& info
,
5388 const std::string_view
& signedheaders
,
5389 const bool using_qs
) const
5391 return rgw::auth::s3::get_v4_canonical_headers(info
, signedheaders
,
5395 AWSSignerV4::prepare_result_t
5396 AWSSignerV4::prepare(const DoutPrefixProvider
*dpp
,
5397 const std::string
& access_key_id
,
5398 const string
& region
,
5399 const string
& service
,
5400 const req_info
& info
,
5401 const bufferlist
*opt_content
,
5404 std::string signed_hdrs
;
5406 ceph::real_time timestamp
= ceph::real_clock::now();
5408 map
<string
, string
> extra_headers
;
5410 std::string date
= ceph::to_iso_8601_no_separators(timestamp
, ceph::iso_8601_format::YMDhms
);
5412 std::string credential_scope
= gen_v4_scope(timestamp
, region
, service
);
5414 extra_headers
["x-amz-date"] = date
;
5416 string content_hash
;
5419 content_hash
= rgw::auth::s3::calc_v4_payload_hash(opt_content
->to_str());
5420 extra_headers
["x-amz-content-sha256"] = content_hash
;
5424 /* craft canonical headers */
5425 std::string canonical_headers
= \
5426 gen_v4_canonical_headers(info
, extra_headers
, &signed_hdrs
);
5428 using sanitize
= rgw::crypt_sanitize::log_content
;
5429 ldpp_dout(dpp
, 10) << "canonical headers format = "
5430 << sanitize
{canonical_headers
} << dendl
;
5432 bool is_non_s3_op
= !s3_op
;
5434 const char* exp_payload_hash
= nullptr;
5435 string payload_hash
;
5437 //For non s3 ops, we need to calculate the payload hash
5438 payload_hash
= info
.args
.get("PayloadHash");
5439 exp_payload_hash
= payload_hash
.c_str();
5441 /* Get the expected hash. */
5442 if (content_hash
.empty()) {
5443 exp_payload_hash
= rgw::auth::s3::get_v4_exp_payload_hash(info
);
5445 exp_payload_hash
= content_hash
.c_str();
5449 /* Craft canonical URI. Using std::move later so let it be non-const. */
5450 auto canonical_uri
= rgw::auth::s3::gen_v4_canonical_uri(info
);
5453 /* Craft canonical query string. std::moving later so non-const here. */
5454 auto canonical_qs
= rgw::auth::s3::gen_v4_canonical_qs(info
);
5456 auto cct
= dpp
->get_cct();
5458 /* Craft canonical request. */
5459 auto canonical_req_hash
= \
5460 rgw::auth::s3::get_v4_canon_req_hash(cct
,
5462 std::move(canonical_uri
),
5463 std::move(canonical_qs
),
5464 std::move(canonical_headers
),
5469 auto string_to_sign
= \
5470 rgw::auth::s3::get_v4_string_to_sign(cct
,
5471 AWS4_HMAC_SHA256_STR
,
5474 std::move(canonical_req_hash
),
5477 const auto sig_factory
= gen_v4_signature
;
5479 /* Requests authenticated with the Query Parameters are treated as unsigned.
5480 * From "Authenticating Requests: Using Query Parameters (AWS Signature
5483 * You don't include a payload hash in the Canonical Request, because
5484 * when you create a presigned URL, you don't know the payload content
5485 * because the URL is used to upload an arbitrary payload. Instead, you
5486 * use a constant string UNSIGNED-PAYLOAD.
5488 * This means we have absolutely no business in spawning completer. Both
5489 * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
5490 * by default. We don't need to change that. */
5495 std::move(signed_hdrs
),
5496 std::move(string_to_sign
),
5497 std::move(extra_headers
),
5502 AWSSignerV4::signature_headers_t
5503 gen_v4_signature(const DoutPrefixProvider
*dpp
,
5504 const std::string_view
& secret_key
,
5505 const AWSSignerV4::prepare_result_t
& sig_info
)
5507 auto signature
= rgw::auth::s3::get_v4_signature(sig_info
.scope
,
5510 sig_info
.string_to_sign
,
5512 AWSSignerV4::signature_headers_t result
;
5514 for (auto& entry
: sig_info
.extra_headers
) {
5515 result
[entry
.first
] = entry
.second
;
5517 auto& payload_hash
= result
["x-amz-content-sha256"];
5518 if (payload_hash
.empty()) {
5519 payload_hash
= AWS4_UNSIGNED_PAYLOAD_HASH
;
5521 string auth_header
= string("AWS4-HMAC-SHA256 Credential=").append(sig_info
.access_key_id
) + "/";
5522 auth_header
.append(sig_info
.scope
+ ",SignedHeaders=")
5523 .append(sig_info
.signed_headers
+ ",Signature=")
5525 result
["Authorization"] = auth_header
;
5531 AWSEngine::VersionAbstractor::auth_data_t
5532 AWSGeneralAbstractor::get_auth_data_v4(const req_state
* const s
,
5533 const bool using_qs
) const
5535 std::string_view access_key_id
;
5536 std::string_view signed_hdrs
;
5538 std::string_view date
;
5539 std::string_view credential_scope
;
5540 std::string_view client_signature
;
5541 std::string_view session_token
;
5543 int ret
= rgw::auth::s3::parse_v4_credentials(s
->info
,
5556 /* craft canonical headers */
5557 boost::optional
<std::string
> canonical_headers
= \
5558 get_v4_canonical_headers(s
->info
, signed_hdrs
, using_qs
);
5559 if (canonical_headers
) {
5560 using sanitize
= rgw::crypt_sanitize::log_content
;
5561 ldpp_dout(s
, 10) << "canonical headers format = "
5562 << sanitize
{*canonical_headers
} << dendl
;
5567 bool is_non_s3_op
= rgw::auth::s3::is_non_s3_op(s
->op_type
);
5569 const char* exp_payload_hash
= nullptr;
5570 string payload_hash
;
5572 //For non s3 ops, we need to calculate the payload hash
5573 payload_hash
= s
->info
.args
.get("PayloadHash");
5574 exp_payload_hash
= payload_hash
.c_str();
5576 /* Get the expected hash. */
5577 exp_payload_hash
= rgw::auth::s3::get_v4_exp_payload_hash(s
->info
);
5580 /* Craft canonical URI. Using std::move later so let it be non-const. */
5581 auto canonical_uri
= rgw::auth::s3::get_v4_canonical_uri(s
->info
);
5583 /* Craft canonical query string. std::moving later so non-const here. */
5584 auto canonical_qs
= rgw::auth::s3::get_v4_canonical_qs(s
->info
, using_qs
);
5586 /* Craft canonical request. */
5587 auto canonical_req_hash
= \
5588 rgw::auth::s3::get_v4_canon_req_hash(s
->cct
,
5590 std::move(canonical_uri
),
5591 std::move(canonical_qs
),
5592 std::move(*canonical_headers
),
5597 auto string_to_sign
= \
5598 rgw::auth::s3::get_v4_string_to_sign(s
->cct
,
5599 AWS4_HMAC_SHA256_STR
,
5602 std::move(canonical_req_hash
),
5605 const auto sig_factory
= std::bind(rgw::auth::s3::get_v4_signature
,
5607 std::placeholders::_1
,
5608 std::placeholders::_2
,
5609 std::placeholders::_3
,
5612 /* Requests authenticated with the Query Parameters are treated as unsigned.
5613 * From "Authenticating Requests: Using Query Parameters (AWS Signature
5616 * You don't include a payload hash in the Canonical Request, because
5617 * when you create a presigned URL, you don't know the payload content
5618 * because the URL is used to upload an arbitrary payload. Instead, you
5619 * use a constant string UNSIGNED-PAYLOAD.
5621 * This means we have absolutely no business in spawning completer. Both
5622 * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
5623 * by default. We don't need to change that. */
5624 if (is_v4_payload_unsigned(exp_payload_hash
) || is_v4_payload_empty(s
) || is_non_s3_op
) {
5629 std::move(string_to_sign
),
5631 null_completer_factory
5634 /* We're going to handle a signed payload. Be aware that even empty HTTP
5635 * body (no payload) requires verification:
5637 * The x-amz-content-sha256 header is required for all AWS Signature
5638 * Version 4 requests. It provides a hash of the request payload. If
5639 * there is no payload, you must provide the hash of an empty string. */
5640 if (!is_v4_payload_streamed(exp_payload_hash
)) {
5641 ldpp_dout(s
, 10) << "delaying v4 auth" << dendl
;
5643 /* payload in a single chunk */
5646 case RGW_OP_CREATE_BUCKET
:
5647 case RGW_OP_PUT_OBJ
:
5648 case RGW_OP_PUT_ACLS
:
5649 case RGW_OP_PUT_CORS
:
5650 case RGW_OP_PUT_BUCKET_ENCRYPTION
:
5651 case RGW_OP_GET_BUCKET_ENCRYPTION
:
5652 case RGW_OP_DELETE_BUCKET_ENCRYPTION
:
5653 case RGW_OP_INIT_MULTIPART
: // in case that Init Multipart uses CHUNK encoding
5654 case RGW_OP_COMPLETE_MULTIPART
:
5655 case RGW_OP_SET_BUCKET_VERSIONING
:
5656 case RGW_OP_DELETE_MULTI_OBJ
:
5657 case RGW_OP_ADMIN_SET_METADATA
:
5658 case RGW_OP_SYNC_DATALOG_NOTIFY
:
5659 case RGW_OP_SYNC_MDLOG_NOTIFY
:
5660 case RGW_OP_PERIOD_POST
:
5661 case RGW_OP_SET_BUCKET_WEBSITE
:
5662 case RGW_OP_PUT_BUCKET_POLICY
:
5663 case RGW_OP_PUT_OBJ_TAGGING
:
5664 case RGW_OP_PUT_BUCKET_TAGGING
:
5665 case RGW_OP_PUT_BUCKET_REPLICATION
:
5667 case RGW_OP_SET_REQUEST_PAYMENT
:
5668 case RGW_OP_PUBSUB_NOTIF_CREATE
:
5669 case RGW_OP_PUBSUB_NOTIF_DELETE
:
5670 case RGW_OP_PUBSUB_NOTIF_LIST
:
5671 case RGW_OP_PUT_BUCKET_OBJ_LOCK
:
5672 case RGW_OP_PUT_OBJ_RETENTION
:
5673 case RGW_OP_PUT_OBJ_LEGAL_HOLD
:
5674 case RGW_STS_GET_SESSION_TOKEN
:
5675 case RGW_STS_ASSUME_ROLE
:
5676 case RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK
:
5677 case RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK
:
5678 case RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK
:
5679 case RGW_OP_GET_OBJ
://s3select its post-method(payload contain the query) , the request is get-object
5682 ldpp_dout(s
, 10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED" << dendl
;
5683 throw -ERR_NOT_IMPLEMENTED
;
5686 const auto cmpl_factory
= std::bind(AWSv4ComplSingle::create
,
5688 std::placeholders::_1
);
5693 std::move(string_to_sign
),
5698 /* IMHO "streamed" doesn't fit too good here. I would prefer to call
5699 * it "chunked" but let's be coherent with Amazon's terminology. */
5701 ldpp_dout(s
, 10) << "body content detected in multiple chunks" << dendl
;
5703 /* payload in multiple chunks */
5707 case RGW_OP_PUT_OBJ
:
5710 ldpp_dout(s
, 10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED (streaming mode)" << dendl
;
5711 throw -ERR_NOT_IMPLEMENTED
;
5714 ldpp_dout(s
, 10) << "aws4 seed signature ok... delaying v4 auth" << dendl
;
5716 /* In the case of streamed payload client sets the x-amz-content-sha256
5717 * to "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" but uses "UNSIGNED-PAYLOAD"
5718 * when constructing the Canonical Request. */
5720 /* In the case of single-chunk upload client set the header's value is
5721 * coherent with the one used for Canonical Request crafting. */
5723 /* In the case of query string-based authentication there should be no
5724 * x-amz-content-sha256 header and the value "UNSIGNED-PAYLOAD" is used
5726 const auto cmpl_factory
= std::bind(AWSv4ComplMulti::create
,
5731 std::placeholders::_1
);
5736 std::move(string_to_sign
),
5745 boost::optional
<std::string
>
5746 AWSGeneralBoto2Abstractor::get_v4_canonical_headers(
5747 const req_info
& info
,
5748 const std::string_view
& signedheaders
,
5749 const bool using_qs
) const
5751 return rgw::auth::s3::get_v4_canonical_headers(info
, signedheaders
,
5756 AWSEngine::VersionAbstractor::auth_data_t
5757 AWSGeneralAbstractor::get_auth_data_v2(const req_state
* const s
) const
5759 std::string_view access_key_id
;
5760 std::string_view signature
;
5761 std::string_view session_token
;
5764 const char* http_auth
= s
->info
.env
->get("HTTP_AUTHORIZATION");
5765 if (! http_auth
|| http_auth
[0] == '\0') {
5766 /* Credentials are provided in query string. We also need to verify
5767 * the "Expires" parameter now. */
5768 access_key_id
= s
->info
.args
.get("AWSAccessKeyId");
5769 signature
= s
->info
.args
.get("Signature");
5772 std::string_view expires
= s
->info
.args
.get("Expires");
5773 if (expires
.empty()) {
5777 /* It looks we have the guarantee that expires is a null-terminated,
5778 * and thus string_view::data() can be safely used. */
5779 const time_t exp
= atoll(expires
.data());
5786 if (s
->info
.args
.exists("x-amz-security-token")) {
5787 session_token
= s
->info
.args
.get("x-amz-security-token");
5788 if (session_token
.size() == 0) {
5794 /* The "Authorization" HTTP header is being used. */
5795 const std::string_view
auth_str(http_auth
+ strlen("AWS "));
5796 const size_t pos
= auth_str
.rfind(':');
5797 if (pos
!= std::string_view::npos
) {
5798 access_key_id
= auth_str
.substr(0, pos
);
5799 signature
= auth_str
.substr(pos
+ 1);
5802 if (s
->info
.env
->exists("HTTP_X_AMZ_SECURITY_TOKEN")) {
5803 session_token
= s
->info
.env
->get("HTTP_X_AMZ_SECURITY_TOKEN");
5804 if (session_token
.size() == 0) {
5810 /* Let's canonize the HTTP headers that are covered by the AWS auth v2. */
5811 std::string string_to_sign
;
5812 utime_t header_time
;
5813 if (! rgw_create_s3_canonical_header(s
, s
->info
, &header_time
, string_to_sign
,
5815 ldpp_dout(s
, 10) << "failed to create the canonized auth header\n"
5816 << rgw::crypt_sanitize::auth
{s
,string_to_sign
} << dendl
;
5820 ldpp_dout(s
, 10) << "string_to_sign:\n"
5821 << rgw::crypt_sanitize::auth
{s
,string_to_sign
} << dendl
;
5823 if (!qsr
&& !is_time_skew_ok(header_time
)) {
5824 throw -ERR_REQUEST_TIME_SKEWED
;
5828 std::move(access_key_id
),
5829 std::move(signature
),
5830 std::move(session_token
),
5831 std::move(string_to_sign
),
5832 rgw::auth::s3::get_v2_signature
,
5833 null_completer_factory
5838 AWSEngine::VersionAbstractor::auth_data_t
5839 AWSBrowserUploadAbstractor::get_auth_data_v2(const req_state
* const s
) const
5842 s
->auth
.s3_postobj_creds
.access_key
,
5843 s
->auth
.s3_postobj_creds
.signature
,
5844 s
->auth
.s3_postobj_creds
.x_amz_security_token
,
5845 s
->auth
.s3_postobj_creds
.encoded_policy
.to_str(),
5846 rgw::auth::s3::get_v2_signature
,
5847 null_completer_factory
5851 AWSEngine::VersionAbstractor::auth_data_t
5852 AWSBrowserUploadAbstractor::get_auth_data_v4(const req_state
* const s
) const
5854 const std::string_view credential
= s
->auth
.s3_postobj_creds
.x_amz_credential
;
5856 /* grab access key id */
5857 const size_t pos
= credential
.find("/");
5858 const std::string_view access_key_id
= credential
.substr(0, pos
);
5859 ldpp_dout(s
, 10) << "access key id = " << access_key_id
<< dendl
;
5861 /* grab credential scope */
5862 const std::string_view credential_scope
= credential
.substr(pos
+ 1);
5863 ldpp_dout(s
, 10) << "credential scope = " << credential_scope
<< dendl
;
5865 const auto sig_factory
= std::bind(rgw::auth::s3::get_v4_signature
,
5867 std::placeholders::_1
,
5868 std::placeholders::_2
,
5869 std::placeholders::_3
,
5874 s
->auth
.s3_postobj_creds
.signature
,
5875 s
->auth
.s3_postobj_creds
.x_amz_security_token
,
5876 s
->auth
.s3_postobj_creds
.encoded_policy
.to_str(),
5878 null_completer_factory
5882 AWSEngine::VersionAbstractor::auth_data_t
5883 AWSBrowserUploadAbstractor::get_auth_data(const req_state
* const s
) const
5885 if (s
->auth
.s3_postobj_creds
.x_amz_algorithm
== AWS4_HMAC_SHA256_STR
) {
5886 ldpp_dout(s
, 0) << "Signature verification algorithm AWS v4"
5887 << " (AWS4-HMAC-SHA256)" << dendl
;
5888 return get_auth_data_v4(s
);
5890 ldpp_dout(s
, 0) << "Signature verification algorithm AWS v2" << dendl
;
5891 return get_auth_data_v2(s
);
5896 AWSEngine::authenticate(const DoutPrefixProvider
* dpp
, const req_state
* const s
, optional_yield y
) const
5898 /* Small reminder: an ver_abstractor is allowed to throw! */
5899 const auto auth_data
= ver_abstractor
.get_auth_data(s
);
5901 if (auth_data
.access_key_id
.empty() || auth_data
.client_signature
.empty()) {
5902 return result_t::deny(-EINVAL
);
5904 return authenticate(dpp
,
5905 auth_data
.access_key_id
,
5906 auth_data
.client_signature
,
5907 auth_data
.session_token
,
5908 auth_data
.string_to_sign
,
5909 auth_data
.signature_factory
,
5910 auth_data
.completer_factory
,
5915 } // namespace rgw::auth::s3
5917 rgw::LDAPHelper
* rgw::auth::s3::LDAPEngine::ldh
= nullptr;
5918 std::mutex
rgw::auth::s3::LDAPEngine::mtx
;
5920 void rgw::auth::s3::LDAPEngine::init(CephContext
* const cct
)
5922 if (! cct
->_conf
->rgw_s3_auth_use_ldap
||
5923 cct
->_conf
->rgw_ldap_uri
.empty()) {
5928 std::lock_guard
<std::mutex
> lck(mtx
);
5930 const string
& ldap_uri
= cct
->_conf
->rgw_ldap_uri
;
5931 const string
& ldap_binddn
= cct
->_conf
->rgw_ldap_binddn
;
5932 const string
& ldap_searchdn
= cct
->_conf
->rgw_ldap_searchdn
;
5933 const string
& ldap_searchfilter
= cct
->_conf
->rgw_ldap_searchfilter
;
5934 const string
& ldap_dnattr
= cct
->_conf
->rgw_ldap_dnattr
;
5935 std::string ldap_bindpw
= parse_rgw_ldap_bindpw(cct
);
5937 ldh
= new rgw::LDAPHelper(ldap_uri
, ldap_binddn
, ldap_bindpw
,
5938 ldap_searchdn
, ldap_searchfilter
, ldap_dnattr
);
5946 bool rgw::auth::s3::LDAPEngine::valid() {
5947 std::lock_guard
<std::mutex
> lck(mtx
);
5951 rgw::auth::RemoteApplier::acl_strategy_t
5952 rgw::auth::s3::LDAPEngine::get_acl_strategy() const
5954 //This is based on the assumption that the default acl strategy in
5955 // get_perms_from_aclspec, will take care. Extra acl spec is not required.
5959 rgw::auth::RemoteApplier::AuthInfo
5960 rgw::auth::s3::LDAPEngine::get_creds_info(const rgw::RGWToken
& token
) const noexcept
5962 /* The short form of "using" can't be used here -- we're aliasing a class'
5964 using acct_privilege_t
= \
5965 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
5967 return rgw::auth::RemoteApplier::AuthInfo
{
5970 RGW_PERM_FULL_CONTROL
,
5971 acct_privilege_t::IS_PLAIN_ACCT
,
5972 rgw::auth::RemoteApplier::AuthInfo::NO_ACCESS_KEY
,
5973 rgw::auth::RemoteApplier::AuthInfo::NO_SUBUSER
,
5978 rgw::auth::Engine::result_t
5979 rgw::auth::s3::LDAPEngine::authenticate(
5980 const DoutPrefixProvider
* dpp
,
5981 const std::string_view
& access_key_id
,
5982 const std::string_view
& signature
,
5983 const std::string_view
& session_token
,
5984 const string_to_sign_t
& string_to_sign
,
5985 const signature_factory_t
&,
5986 const completer_factory_t
& completer_factory
,
5987 const req_state
* const s
,
5988 optional_yield y
) const
5990 /* boost filters and/or string_ref may throw on invalid input */
5991 rgw::RGWToken base64_token
;
5993 base64_token
= rgw::from_base64(access_key_id
);
5995 base64_token
= std::string("");
5998 if (! base64_token
.valid()) {
5999 return result_t::deny();
6002 //TODO: Uncomment, when we have a migration plan in place.
6003 //Check if a user of type other than 'ldap' is already present, if yes, then
6005 /*RGWUserInfo user_info;
6006 user_info.user_id = base64_token.id;
6007 if (rgw_get_user_info_by_uid(store, user_info.user_id, user_info) >= 0) {
6008 if (user_info.type != TYPE_LDAP) {
6009 ldpp_dout(dpp, 10) << "ERROR: User id of type: " << user_info.type << " is already present" << dendl;
6014 if (ldh
->auth(base64_token
.id
, base64_token
.key
) != 0) {
6015 return result_t::deny(-ERR_INVALID_ACCESS_KEY
);
6018 auto apl
= apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(),
6019 get_creds_info(base64_token
));
6020 return result_t::grant(std::move(apl
), completer_factory(boost::none
));
6021 } /* rgw::auth::s3::LDAPEngine::authenticate */
6023 void rgw::auth::s3::LDAPEngine::shutdown() {
6031 rgw::auth::Engine::result_t
6032 rgw::auth::s3::LocalEngine::authenticate(
6033 const DoutPrefixProvider
* dpp
,
6034 const std::string_view
& _access_key_id
,
6035 const std::string_view
& signature
,
6036 const std::string_view
& session_token
,
6037 const string_to_sign_t
& string_to_sign
,
6038 const signature_factory_t
& signature_factory
,
6039 const completer_factory_t
& completer_factory
,
6040 const req_state
* const s
,
6041 optional_yield y
) const
6043 /* get the user info */
6044 std::unique_ptr
<rgw::sal::User
> user
;
6045 const std::string
access_key_id(_access_key_id
);
6046 /* TODO(rzarzynski): we need to have string-view taking variant. */
6047 if (store
->get_user_by_access_key(dpp
, access_key_id
, y
, &user
) < 0) {
6048 ldpp_dout(dpp
, 5) << "error reading user info, uid=" << access_key_id
6049 << " can't authenticate" << dendl
;
6050 return result_t::deny(-ERR_INVALID_ACCESS_KEY
);
6052 //TODO: Uncomment, when we have a migration plan in place.
6054 if (s->user->type != TYPE_RGW) {
6055 ldpp_dout(dpp, 10) << "ERROR: User id of type: " << s->user->type
6056 << " is present" << dendl;
6061 const auto iter
= user
->get_info().access_keys
.find(access_key_id
);
6062 if (iter
== std::end(user
->get_info().access_keys
)) {
6063 ldpp_dout(dpp
, 0) << "ERROR: access key not encoded in user info" << dendl
;
6064 return result_t::deny(-EPERM
);
6066 const RGWAccessKey
& k
= iter
->second
;
6068 const VersionAbstractor::server_signature_t server_signature
= \
6069 signature_factory(cct
, k
.key
, string_to_sign
);
6070 auto compare
= signature
.compare(server_signature
);
6072 ldpp_dout(dpp
, 15) << "string_to_sign="
6073 << rgw::crypt_sanitize::log_content
{string_to_sign
}
6075 ldpp_dout(dpp
, 15) << "server signature=" << server_signature
<< dendl
;
6076 ldpp_dout(dpp
, 15) << "client signature=" << signature
<< dendl
;
6077 ldpp_dout(dpp
, 15) << "compare=" << compare
<< dendl
;
6080 return result_t::deny(-ERR_SIGNATURE_NO_MATCH
);
6083 auto apl
= apl_factory
->create_apl_local(cct
, s
, user
->get_info(),
6084 k
.subuser
, std::nullopt
, access_key_id
);
6085 return result_t::grant(std::move(apl
), completer_factory(k
.key
));
6088 rgw::auth::RemoteApplier::AuthInfo
6089 rgw::auth::s3::STSEngine::get_creds_info(const STS::SessionToken
& token
) const noexcept
6091 using acct_privilege_t
= \
6092 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
6094 return rgw::auth::RemoteApplier::AuthInfo
{
6098 (token
.is_admin
) ? acct_privilege_t::IS_ADMIN_ACCT
: acct_privilege_t::IS_PLAIN_ACCT
,
6099 token
.access_key_id
,
6100 rgw::auth::RemoteApplier::AuthInfo::NO_SUBUSER
,
6106 rgw::auth::s3::STSEngine::get_session_token(const DoutPrefixProvider
* dpp
, const std::string_view
& session_token
,
6107 STS::SessionToken
& token
) const
6109 string decodedSessionToken
;
6111 decodedSessionToken
= rgw::from_base64(session_token
);
6113 ldpp_dout(dpp
, 0) << "ERROR: Invalid session token, not base64 encoded." << dendl
;
6117 auto* cryptohandler
= cct
->get_crypto_handler(CEPH_CRYPTO_AES
);
6118 if (! cryptohandler
) {
6121 string secret_s
= cct
->_conf
->rgw_sts_key
;
6122 buffer::ptr
secret(secret_s
.c_str(), secret_s
.length());
6124 if (ret
= cryptohandler
->validate_secret(secret
); ret
< 0) {
6125 ldpp_dout(dpp
, 0) << "ERROR: Invalid secret key" << dendl
;
6129 std::unique_ptr
<CryptoKeyHandler
> keyhandler(cryptohandler
->get_key_handler(secret
, error
));
6135 string decrypted_str
;
6136 buffer::list en_input
, dec_output
;
6137 en_input
= buffer::list::static_from_string(decodedSessionToken
);
6139 ret
= keyhandler
->decrypt(en_input
, dec_output
, &error
);
6141 ldpp_dout(dpp
, 0) << "ERROR: Decryption failed: " << error
<< dendl
;
6145 dec_output
.append('\0');
6146 auto iter
= dec_output
.cbegin();
6147 decode(token
, iter
);
6148 } catch (const buffer::error
& e
) {
6149 ldpp_dout(dpp
, 0) << "ERROR: decode SessionToken failed: " << error
<< dendl
;
6156 rgw::auth::Engine::result_t
6157 rgw::auth::s3::STSEngine::authenticate(
6158 const DoutPrefixProvider
* dpp
,
6159 const std::string_view
& _access_key_id
,
6160 const std::string_view
& signature
,
6161 const std::string_view
& session_token
,
6162 const string_to_sign_t
& string_to_sign
,
6163 const signature_factory_t
& signature_factory
,
6164 const completer_factory_t
& completer_factory
,
6165 const req_state
* const s
,
6166 optional_yield y
) const
6168 if (! s
->info
.args
.exists("x-amz-security-token") &&
6169 ! s
->info
.env
->exists("HTTP_X_AMZ_SECURITY_TOKEN") &&
6170 s
->auth
.s3_postobj_creds
.x_amz_security_token
.empty()) {
6171 return result_t::deny();
6174 STS::SessionToken token
;
6175 if (int ret
= get_session_token(dpp
, session_token
, token
); ret
< 0) {
6176 return result_t::reject(ret
);
6179 //Check if access key is not the same passed in by client
6180 if (token
.access_key_id
!= _access_key_id
) {
6181 ldpp_dout(dpp
, 0) << "Invalid access key" << dendl
;
6182 return result_t::reject(-EPERM
);
6184 //Check if the token has expired
6185 if (! token
.expiration
.empty()) {
6186 std::string expiration
= token
.expiration
;
6187 if (! expiration
.empty()) {
6188 boost::optional
<real_clock::time_point
> exp
= ceph::from_iso_8601(expiration
, false);
6190 real_clock::time_point now
= real_clock::now();
6192 ldpp_dout(dpp
, 0) << "ERROR: Token expired" << dendl
;
6193 return result_t::reject(-EPERM
);
6196 ldpp_dout(dpp
, 0) << "ERROR: Invalid expiration: " << expiration
<< dendl
;
6197 return result_t::reject(-EPERM
);
6201 //Check for signature mismatch
6202 const VersionAbstractor::server_signature_t server_signature
= \
6203 signature_factory(cct
, token
.secret_access_key
, string_to_sign
);
6204 auto compare
= signature
.compare(server_signature
);
6206 ldpp_dout(dpp
, 15) << "string_to_sign="
6207 << rgw::crypt_sanitize::log_content
{string_to_sign
}
6209 ldpp_dout(dpp
, 15) << "server signature=" << server_signature
<< dendl
;
6210 ldpp_dout(dpp
, 15) << "client signature=" << signature
<< dendl
;
6211 ldpp_dout(dpp
, 15) << "compare=" << compare
<< dendl
;
6214 return result_t::reject(-ERR_SIGNATURE_NO_MATCH
);
6217 // Get all the authorization info
6218 std::unique_ptr
<rgw::sal::User
> user
;
6221 rgw::auth::RoleApplier::Role r
;
6222 rgw::auth::RoleApplier::TokenAttrs t_attrs
;
6223 if (! token
.roleId
.empty()) {
6224 std::unique_ptr
<rgw::sal::RGWRole
> role
= store
->get_role(token
.roleId
);
6225 if (role
->get_by_id(dpp
, y
) < 0) {
6226 return result_t::deny(-EPERM
);
6228 r
.id
= token
.roleId
;
6229 r
.name
= role
->get_name();
6230 r
.tenant
= role
->get_tenant();
6232 vector
<string
> role_policy_names
= role
->get_role_policy_names();
6233 for (auto& policy_name
: role_policy_names
) {
6235 if (int ret
= role
->get_role_policy(dpp
, policy_name
, perm_policy
); ret
== 0) {
6236 r
.role_policies
.push_back(std::move(perm_policy
));
6241 user
= store
->get_user(token
.user
);
6242 if (! token
.user
.empty() && token
.acct_type
!= TYPE_ROLE
) {
6244 int ret
= user
->load_user(dpp
, y
);
6246 ldpp_dout(dpp
, 5) << "ERROR: failed reading user info: uid=" << token
.user
<< dendl
;
6247 return result_t::reject(-EPERM
);
6251 if (token
.acct_type
== TYPE_KEYSTONE
|| token
.acct_type
== TYPE_LDAP
) {
6252 auto apl
= remote_apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(),
6253 get_creds_info(token
));
6254 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6255 } else if (token
.acct_type
== TYPE_ROLE
) {
6256 t_attrs
.user_id
= std::move(token
.user
); // This is mostly needed to assign the owner of a bucket during its creation
6257 t_attrs
.token_policy
= std::move(token
.policy
);
6258 t_attrs
.role_session_name
= std::move(token
.role_session
);
6259 t_attrs
.token_claims
= std::move(token
.token_claims
);
6260 t_attrs
.token_issued_at
= std::move(token
.issued_at
);
6261 t_attrs
.principal_tags
= std::move(token
.principal_tags
);
6262 auto apl
= role_apl_factory
->create_apl_role(cct
, s
, r
, t_attrs
);
6263 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6264 } else { // This is for all local users of type TYPE_RGW or TYPE_NONE
6266 auto apl
= local_apl_factory
->create_apl_local(cct
, s
, user
->get_info(), subuser
, token
.perm_mask
, std::string(_access_key_id
));
6267 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6271 bool rgw::auth::s3::S3AnonymousEngine::is_applicable(
6274 if (s
->op
== OP_OPTIONS
) {
6280 std::tie(version
, route
) = discover_aws_flavour(s
->info
);
6282 return route
== AwsRoute::QUERY_STRING
&& version
== AwsVersion::UNKNOWN
;