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 int RGWGetObj_ObjStore_S3Website::send_response_data(bufferlist
& bl
, off_t bl_ofs
, off_t bl_len
) {
145 map
<string
, bufferlist
>::iterator iter
;
146 iter
= attrs
.find(RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION
);
147 if (iter
!= attrs
.end()) {
148 bufferlist
&bl
= iter
->second
;
149 s
->redirect
= bl
.c_str();
150 s
->err
.http_ret
= 301;
151 ldpp_dout(this, 20) << __CEPH_ASSERT_FUNCTION
<< " redirecting per x-amz-website-redirect-location=" << s
->redirect
<< dendl
;
152 op_ret
= -ERR_WEBSITE_REDIRECT
;
153 set_req_state_err(s
, op_ret
);
155 dump_content_length(s
, 0);
156 dump_redirect(s
, s
->redirect
);
160 return RGWGetObj_ObjStore_S3::send_response_data(bl
, bl_ofs
, bl_len
);
164 int RGWGetObj_ObjStore_S3Website::send_response_data_error(optional_yield y
)
166 return RGWGetObj_ObjStore_S3::send_response_data_error(y
);
169 int RGWGetObj_ObjStore_S3::get_params(optional_yield y
)
171 // for multisite sync requests, only read the slo manifest itself, rather than
172 // all of the data from its parts. the parts will sync as separate objects
173 skip_manifest
= s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"sync-manifest");
175 // multisite sync requests should fetch encrypted data, along with the
176 // attributes needed to support decryption on the other zone
177 if (s
->system_request
) {
178 skip_decrypt
= s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"skip-decrypt");
181 return RGWGetObj_ObjStore::get_params(y
);
184 int RGWGetObj_ObjStore_S3::send_response_data_error(optional_yield y
)
187 return send_response_data(bl
, 0 , 0);
191 int decode_attr_bl_single_value(map
<string
, bufferlist
>& attrs
, const char *attr_name
, T
*result
, T def_val
)
193 map
<string
, bufferlist
>::iterator iter
= attrs
.find(attr_name
);
194 if (iter
== attrs
.end()) {
198 bufferlist
& bl
= iter
->second
;
199 if (bl
.length() == 0) {
203 auto bliter
= bl
.cbegin();
205 decode(*result
, bliter
);
206 } catch (buffer::error
& err
) {
212 inline bool str_has_cntrl(const std::string s
) {
213 return std::any_of(s
.begin(), s
.end(), ::iscntrl
);
216 inline bool str_has_cntrl(const char* s
) {
218 return str_has_cntrl(_s
);
221 int RGWGetObj_ObjStore_S3::send_response_data(bufferlist
& bl
, off_t bl_ofs
,
224 const char *content_type
= NULL
;
225 string content_type_str
;
226 map
<string
, string
> response_attrs
;
227 map
<string
, string
>::iterator riter
;
228 bufferlist metadata_bl
;
230 string expires
= get_s3_expiration_header(s
, lastmod
);
235 if (custom_http_ret
) {
236 set_req_state_err(s
, 0);
237 dump_errno(s
, custom_http_ret
);
239 set_req_state_err(s
, (partial_content
&& !op_ret
) ? STATUS_PARTIAL_CONTENT
248 dump_range(s
, start
, end
, s
->obj_size
);
250 if (s
->system_request
&&
251 s
->info
.args
.exists(RGW_SYS_PARAM_PREFIX
"prepend-metadata")) {
253 dump_header(s
, "Rgwx-Object-Size", (long long)total_len
);
257 * in this case, we're not returning the object's content, only the prepended
263 /* JSON encode object metadata */
265 jf
.open_object_section("obj_metadata");
266 encode_json("attrs", attrs
, &jf
);
268 encode_json("mtime", ut
, &jf
);
272 metadata_bl
.append(ss
.str());
273 dump_header(s
, "Rgwx-Embedded-Metadata-Len", metadata_bl
.length());
274 total_len
+= metadata_bl
.length();
277 if (s
->system_request
&& !real_clock::is_zero(lastmod
)) {
278 /* we end up dumping mtime in two different methods, a bit redundant */
279 dump_epoch_header(s
, "Rgwx-Mtime", lastmod
);
281 int r
= decode_attr_bl_single_value(attrs
, RGW_ATTR_PG_VER
, &pg_ver
, (uint64_t)0);
283 ldpp_dout(this, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl
;
285 dump_header(s
, "Rgwx-Obj-PG-Ver", pg_ver
);
287 uint32_t source_zone_short_id
= 0;
288 r
= decode_attr_bl_single_value(attrs
, RGW_ATTR_SOURCE_ZONE
, &source_zone_short_id
, (uint32_t)0);
290 ldpp_dout(this, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl
;
292 if (source_zone_short_id
!= 0) {
293 dump_header(s
, "Rgwx-Source-Zone-Short-Id", source_zone_short_id
);
297 for (auto &it
: crypt_http_responses
)
298 dump_header(s
, it
.first
, it
.second
);
300 dump_content_length(s
, total_len
);
301 dump_last_modified(s
, lastmod
);
302 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
303 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
305 if (attrs
.find(RGW_ATTR_APPEND_PART_NUM
) != attrs
.end()) {
306 dump_header(s
, "x-rgw-object-type", "Appendable");
307 dump_header(s
, "x-rgw-next-append-position", s
->obj_size
);
309 dump_header(s
, "x-rgw-object-type", "Normal");
313 if (! lo_etag
.empty()) {
314 /* Handle etag of Swift API's large objects (DLO/SLO). It's entirerly
315 * legit to perform GET on them through S3 API. In such situation,
316 * a client should receive the composited content with corresponding
318 dump_etag(s
, lo_etag
);
320 auto iter
= attrs
.find(RGW_ATTR_ETAG
);
321 if (iter
!= attrs
.end()) {
322 dump_etag(s
, iter
->second
.to_str());
326 for (struct response_attr_param
*p
= resp_attr_params
; p
->param
; p
++) {
328 string val
= s
->info
.args
.get(p
->param
, &exists
);
330 /* reject unauthenticated response header manipulation, see
331 * https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html */
332 if (s
->auth
.identity
->is_anonymous()) {
333 return -ERR_INVALID_REQUEST
;
335 /* HTTP specification says no control characters should be present in
336 * header values: https://tools.ietf.org/html/rfc7230#section-3.2
337 * field-vchar = VCHAR / obs-text
339 * Failure to validate this permits a CRLF injection in HTTP headers,
340 * whereas S3 GetObject only permits specific headers.
342 if(str_has_cntrl(val
)) {
343 /* TODO: return a more distinct error in future;
344 * stating what the problem is */
345 return -ERR_INVALID_REQUEST
;
348 if (strcmp(p
->param
, "response-content-type") != 0) {
349 response_attrs
[p
->http_attr
] = val
;
351 content_type_str
= val
;
352 content_type
= content_type_str
.c_str();
357 for (auto iter
= attrs
.begin(); iter
!= attrs
.end(); ++iter
) {
358 const char *name
= iter
->first
.c_str();
359 map
<string
, string
>::iterator aiter
= rgw_to_http_attrs
.find(name
);
360 if (aiter
!= rgw_to_http_attrs
.end()) {
361 if (response_attrs
.count(aiter
->second
) == 0) {
362 /* Was not already overridden by a response param. */
364 size_t len
= iter
->second
.length();
365 string
s(iter
->second
.c_str(), len
);
366 while (len
&& !s
[len
- 1]) {
370 response_attrs
[aiter
->second
] = s
;
372 } else if (iter
->first
.compare(RGW_ATTR_CONTENT_TYPE
) == 0) {
373 /* Special handling for content_type. */
375 content_type_str
= rgw_bl_str(iter
->second
);
376 content_type
= content_type_str
.c_str();
378 } else if (strcmp(name
, RGW_ATTR_SLO_UINDICATOR
) == 0) {
379 // this attr has an extra length prefix from encode() in prior versions
380 dump_header(s
, "X-Object-Meta-Static-Large-Object", "True");
381 } else if (strncmp(name
, RGW_ATTR_META_PREFIX
,
382 sizeof(RGW_ATTR_META_PREFIX
)-1) == 0) {
383 /* User custom metadata. */
384 name
+= sizeof(RGW_ATTR_PREFIX
) - 1;
385 dump_header(s
, name
, iter
->second
);
386 } else if (iter
->first
.compare(RGW_ATTR_TAGS
) == 0) {
389 auto it
= iter
->second
.cbegin();
391 } catch (buffer::error
&err
) {
392 ldpp_dout(this,0) << "Error caught buffer::error couldn't decode TagSet " << dendl
;
394 dump_header(s
, RGW_AMZ_TAG_COUNT
, obj_tags
.count());
395 } else if (iter
->first
.compare(RGW_ATTR_OBJECT_RETENTION
) == 0 && get_retention
){
396 RGWObjectRetention retention
;
398 decode(retention
, iter
->second
);
399 dump_header(s
, "x-amz-object-lock-mode", retention
.get_mode());
400 string date
= ceph::to_iso_8601(retention
.get_retain_until_date());
401 dump_header(s
, "x-amz-object-lock-retain-until-date", date
.c_str());
402 } catch (buffer::error
& err
) {
403 ldpp_dout(this, 0) << "ERROR: failed to decode RGWObjectRetention" << dendl
;
405 } else if (iter
->first
.compare(RGW_ATTR_OBJECT_LEGAL_HOLD
) == 0 && get_legal_hold
) {
406 RGWObjectLegalHold legal_hold
;
408 decode(legal_hold
, iter
->second
);
409 dump_header(s
, "x-amz-object-lock-legal-hold",legal_hold
.get_status());
410 } catch (buffer::error
& err
) {
411 ldpp_dout(this, 0) << "ERROR: failed to decode RGWObjectLegalHold" << dendl
;
418 for (riter
= response_attrs
.begin(); riter
!= response_attrs
.end();
420 dump_header(s
, riter
->first
, riter
->second
);
423 if (op_ret
== -ERR_NOT_MODIFIED
) {
427 content_type
= "binary/octet-stream";
429 end_header(s
, this, content_type
);
432 if (metadata_bl
.length()) {
433 dump_body(s
, metadata_bl
);
438 if (get_data
&& !op_ret
) {
439 int r
= dump_body(s
, bl
.c_str() + bl_ofs
, bl_len
);
447 int RGWGetObj_ObjStore_S3::get_decrypt_filter(std::unique_ptr
<RGWGetObj_Filter
> *filter
, RGWGetObj_Filter
* cb
, bufferlist
* manifest_bl
)
449 if (skip_decrypt
) { // bypass decryption for multisite sync requests
454 std::unique_ptr
<BlockCrypt
> block_crypt
;
455 res
= rgw_s3_prepare_decrypt(s
, attrs
, &block_crypt
, crypt_http_responses
);
457 if (block_crypt
!= nullptr) {
458 auto f
= std::make_unique
<RGWGetObj_BlockDecrypt
>(s
, s
->cct
, cb
, std::move(block_crypt
));
459 if (manifest_bl
!= nullptr) {
460 res
= f
->read_manifest(this, *manifest_bl
);
462 *filter
= std::move(f
);
469 int RGWGetObj_ObjStore_S3::verify_requester(const rgw::auth::StrategyRegistry
& auth_registry
, optional_yield y
)
472 ret
= RGWOp::verify_requester(auth_registry
, y
);
473 if(!s
->user
->get_caps().check_cap("amz-cache", RGW_CAP_READ
) && !ret
&& s
->info
.env
->exists("HTTP_X_AMZ_CACHE"))
474 ret
= override_range_hdr(auth_registry
, y
);
478 int RGWGetObj_ObjStore_S3::override_range_hdr(const rgw::auth::StrategyRegistry
& auth_registry
, optional_yield y
)
481 ldpp_dout(this, 10) << "cache override headers" << dendl
;
482 RGWEnv
* rgw_env
= const_cast<RGWEnv
*>(s
->info
.env
);
483 const char* backup_range
= rgw_env
->get("HTTP_RANGE");
484 const char hdrs_split
[2] = {(char)178,'\0'};
485 const char kv_split
[2] = {(char)177,'\0'};
486 const char* cache_hdr
= rgw_env
->get("HTTP_X_AMZ_CACHE");
487 for (std::string_view hdr
: ceph::split(cache_hdr
, hdrs_split
)) {
488 auto kv
= ceph::split(hdr
, kv_split
);
490 if (std::distance(k
, kv
.end()) != 2) {
493 auto v
= std::next(k
);
494 std::string key
= "HTTP_";
496 boost::replace_all(key
, "-", "_");
497 rgw_env
->set(std::move(key
), std::string(*v
));
498 ldpp_dout(this, 10) << "after splitting cache kv key: " << key
<< " " << rgw_env
->get(key
.c_str()) << dendl
;
500 ret
= RGWOp::verify_requester(auth_registry
, y
);
501 if(!ret
&& backup_range
) {
502 rgw_env
->set("HTTP_RANGE",backup_range
);
504 rgw_env
->remove("HTTP_RANGE");
510 void RGWGetObjTags_ObjStore_S3::send_response_data(bufferlist
& bl
)
513 end_header(s
, this, "application/xml");
516 s
->formatter
->open_object_section_in_ns("Tagging", XMLNS_AWS_S3
);
517 s
->formatter
->open_object_section("TagSet");
519 RGWObjTagSet_S3 tagset
;
520 auto iter
= bl
.cbegin();
523 } catch (buffer::error
& err
) {
524 ldpp_dout(this,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl
;
528 tagset
.dump_xml(s
->formatter
);
530 s
->formatter
->close_section();
531 s
->formatter
->close_section();
532 rgw_flush_formatter_and_reset(s
, s
->formatter
);
536 int RGWPutObjTags_ObjStore_S3::get_params(optional_yield y
)
544 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
548 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
553 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
554 return -ERR_MALFORMED_XML
;
557 RGWObjTagging_S3 tagging
;
560 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
561 } catch (RGWXMLDecoder::err
& err
) {
562 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
563 return -ERR_MALFORMED_XML
;
567 r
= tagging
.rebuild(obj_tags
);
571 obj_tags
.encode(tags_bl
);
572 ldpp_dout(this, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
577 void RGWPutObjTags_ObjStore_S3::send_response()
580 set_req_state_err(s
, op_ret
);
582 end_header(s
, this, "application/xml");
587 void RGWDeleteObjTags_ObjStore_S3::send_response()
593 r
= STATUS_NO_CONTENT
;
595 set_req_state_err(s
, r
);
600 void RGWGetBucketTags_ObjStore_S3::send_response_data(bufferlist
& bl
)
603 set_req_state_err(s
, op_ret
);
605 end_header(s
, this, "application/xml");
609 s
->formatter
->open_object_section_in_ns("Tagging", XMLNS_AWS_S3
);
610 s
->formatter
->open_object_section("TagSet");
612 RGWObjTagSet_S3 tagset
;
613 auto iter
= bl
.cbegin();
616 } catch (buffer::error
& err
) {
617 ldpp_dout(this,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl
;
621 tagset
.dump_xml(s
->formatter
);
623 s
->formatter
->close_section();
624 s
->formatter
->close_section();
625 rgw_flush_formatter_and_reset(s
, s
->formatter
);
629 int RGWPutBucketTags_ObjStore_S3::get_params(const DoutPrefixProvider
*dpp
, optional_yield y
)
637 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
641 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
646 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
647 return -ERR_MALFORMED_XML
;
650 RGWObjTagging_S3 tagging
;
652 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
653 } catch (RGWXMLDecoder::err
& err
) {
655 ldpp_dout(dpp
, 5) << "Malformed tagging request: " << err
<< dendl
;
656 return -ERR_MALFORMED_XML
;
659 RGWObjTags
obj_tags(50); // A tag set can contain as many as 50 tags, or it can be empty.
660 r
= tagging
.rebuild(obj_tags
);
664 obj_tags
.encode(tags_bl
);
665 ldpp_dout(dpp
, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
667 // forward bucket tags requests to meta master zone
668 if (!store
->is_meta_master()) {
669 /* only need to keep this data around if we're not meta master */
670 in_data
= std::move(data
);
676 void RGWPutBucketTags_ObjStore_S3::send_response()
679 set_req_state_err(s
, op_ret
);
681 end_header(s
, this, "application/xml");
685 void RGWDeleteBucketTags_ObjStore_S3::send_response()
688 set_req_state_err(s
, op_ret
);
690 end_header(s
, this, "application/xml");
696 bool is_valid_status(const string
& s
) {
697 return (s
== "Enabled" ||
701 static string enabled_group_id
= "s3-bucket-replication:enabled";
702 static string disabled_group_id
= "s3-bucket-replication:disabled";
704 struct ReplicationConfiguration
{
708 struct DeleteMarkerReplication
{
711 void decode_xml(XMLObj
*obj
) {
712 RGWXMLDecoder::decode_xml("Status", status
, obj
);
715 void dump_xml(Formatter
*f
) const {
716 encode_xml("Status", status
, f
);
719 bool is_valid(CephContext
*cct
) const {
720 bool result
= is_valid_status(status
);
722 ldout(cct
, 5) << "NOTICE: bad status provided in DeleteMarkerReplication element (status=" << status
<< ")" << dendl
;
728 struct Source
{ /* rgw extension */
729 std::vector
<string
> zone_names
;
731 void decode_xml(XMLObj
*obj
) {
732 RGWXMLDecoder::decode_xml("Zone", zone_names
, obj
);
735 void dump_xml(Formatter
*f
) const {
736 encode_xml("Zone", zone_names
, f
);
741 struct AccessControlTranslation
{
744 void decode_xml(XMLObj
*obj
) {
745 RGWXMLDecoder::decode_xml("Owner", owner
, obj
);
747 void dump_xml(Formatter
*f
) const {
748 encode_xml("Owner", owner
, f
);
752 std::optional
<AccessControlTranslation
> acl_translation
;
753 std::optional
<string
> account
;
755 std::optional
<string
> storage_class
;
756 std::vector
<string
> zone_names
;
758 void decode_xml(XMLObj
*obj
) {
759 RGWXMLDecoder::decode_xml("AccessControlTranslation", acl_translation
, obj
);
760 RGWXMLDecoder::decode_xml("Account", account
, obj
);
761 if (account
&& account
->empty()) {
764 RGWXMLDecoder::decode_xml("Bucket", bucket
, obj
);
765 RGWXMLDecoder::decode_xml("StorageClass", storage_class
, obj
);
766 if (storage_class
&& storage_class
->empty()) {
767 storage_class
.reset();
769 RGWXMLDecoder::decode_xml("Zone", zone_names
, obj
); /* rgw extension */
772 void dump_xml(Formatter
*f
) const {
773 encode_xml("AccessControlTranslation", acl_translation
, f
);
774 encode_xml("Account", account
, f
);
775 encode_xml("Bucket", bucket
, f
);
776 encode_xml("StorageClass", storage_class
, f
);
777 encode_xml("Zone", zone_names
, f
);
787 return key
.empty() && value
.empty();
790 void decode_xml(XMLObj
*obj
) {
791 RGWXMLDecoder::decode_xml("Key", key
, obj
);
792 RGWXMLDecoder::decode_xml("Value", value
, obj
);
795 void dump_xml(Formatter
*f
) const {
796 encode_xml("Key", key
, f
);
797 encode_xml("Value", value
, f
);
802 std::optional
<string
> prefix
;
803 std::vector
<Tag
> tags
;
810 void decode_xml(XMLObj
*obj
) {
811 std::vector
<Tag
> _tags
;
812 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
813 if (prefix
&& prefix
->empty()) {
816 RGWXMLDecoder::decode_xml("Tag", _tags
, obj
);
817 for (auto& t
: _tags
) {
819 tags
.push_back(std::move(t
));
824 void dump_xml(Formatter
*f
) const {
825 encode_xml("Prefix", prefix
, f
);
826 encode_xml("Tag", tags
, f
);
830 std::optional
<string
> prefix
;
831 std::optional
<Tag
> tag
;
832 std::optional
<AndElements
> and_elements
;
835 return (!prefix
&& !tag
&& !and_elements
);
838 void decode_xml(XMLObj
*obj
) {
839 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
840 if (prefix
&& prefix
->empty()) {
843 RGWXMLDecoder::decode_xml("Tag", tag
, obj
);
844 if (tag
&& tag
->empty()) {
847 RGWXMLDecoder::decode_xml("And", and_elements
, obj
);
848 if (and_elements
&& and_elements
->empty()) {
849 and_elements
.reset();
853 void dump_xml(Formatter
*f
) const {
854 encode_xml("Prefix", prefix
, f
);
855 encode_xml("Tag", tag
, f
);
856 encode_xml("And", and_elements
, f
);
859 bool is_valid(CephContext
*cct
) const {
861 ldout(cct
, 5) << "NOTICE: both tag and prefix were provided in replication filter rule" << dendl
;
866 if (prefix
&& and_elements
->prefix
) {
867 ldout(cct
, 5) << "NOTICE: too many prefixes were provided in re" << dendl
;
874 int to_sync_pipe_filter(CephContext
*cct
,
875 rgw_sync_pipe_filter
*f
) const {
876 if (!is_valid(cct
)) {
883 f
->tags
.insert(rgw_sync_pipe_filter_tag(tag
->key
, tag
->value
));
887 if (and_elements
->prefix
) {
888 f
->prefix
= *and_elements
->prefix
;
890 for (auto& t
: and_elements
->tags
) {
891 f
->tags
.insert(rgw_sync_pipe_filter_tag(t
.key
, t
.value
));
897 void from_sync_pipe_filter(const rgw_sync_pipe_filter
& f
) {
898 if (f
.prefix
&& f
.tags
.empty()) {
903 and_elements
.emplace();
904 and_elements
->prefix
= f
.prefix
;
905 } else if (f
.tags
.size() == 1) {
906 auto iter
= f
.tags
.begin();
907 if (iter
== f
.tags
.end()) {
908 /* should never happen */
914 tag
->value
= t
.value
;
918 if (f
.tags
.empty()) {
923 and_elements
.emplace();
926 for (auto& t
: f
.tags
) {
927 auto& tag
= and_elements
->tags
.emplace_back();
934 set
<rgw_zone_id
> get_zone_ids_from_names(rgw::sal::Store
* store
,
935 const vector
<string
>& zone_names
) const {
936 set
<rgw_zone_id
> ids
;
938 for (auto& name
: zone_names
) {
940 if (static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->find_zone_id_by_name(name
, &id
)) {
941 ids
.insert(std::move(id
));
948 vector
<string
> get_zone_names_from_ids(rgw::sal::Store
* store
,
949 const set
<rgw_zone_id
>& zone_ids
) const {
950 vector
<string
> names
;
952 for (auto& id
: zone_ids
) {
954 if (static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->find_zone(id
, &zone
)) {
955 names
.emplace_back(zone
->name
);
962 std::optional
<DeleteMarkerReplication
> delete_marker_replication
;
963 std::optional
<Source
> source
;
964 Destination destination
;
965 std::optional
<Filter
> filter
;
970 void decode_xml(XMLObj
*obj
) {
971 RGWXMLDecoder::decode_xml("DeleteMarkerReplication", delete_marker_replication
, obj
);
972 RGWXMLDecoder::decode_xml("Source", source
, obj
);
973 RGWXMLDecoder::decode_xml("Destination", destination
, obj
);
974 RGWXMLDecoder::decode_xml("ID", id
, obj
);
976 std::optional
<string
> prefix
;
977 RGWXMLDecoder::decode_xml("Prefix", prefix
, obj
);
980 filter
->prefix
= prefix
;
984 RGWXMLDecoder::decode_xml("Filter", filter
, obj
);
986 /* don't want to have filter reset because it might have been initialized
987 * when decoding prefix
989 RGWXMLDecoder::decode_xml("Filter", *filter
, obj
);
992 RGWXMLDecoder::decode_xml("Priority", priority
, obj
);
993 RGWXMLDecoder::decode_xml("Status", status
, obj
);
996 void dump_xml(Formatter
*f
) const {
997 encode_xml("DeleteMarkerReplication", delete_marker_replication
, f
);
998 encode_xml("Source", source
, f
);
999 encode_xml("Destination", destination
, f
);
1000 encode_xml("Filter", filter
, f
);
1001 encode_xml("ID", id
, f
);
1002 encode_xml("Priority", priority
, f
);
1003 encode_xml("Status", status
, f
);
1006 bool is_valid(CephContext
*cct
) const {
1007 if (!is_valid_status(status
)) {
1008 ldout(cct
, 5) << "NOTICE: bad status provided in rule (status=" << status
<< ")" << dendl
;
1011 if ((filter
&& !filter
->is_valid(cct
)) ||
1012 (delete_marker_replication
&& !delete_marker_replication
->is_valid(cct
))) {
1018 int to_sync_policy_pipe(req_state
*s
, rgw::sal::Store
* store
,
1019 rgw_sync_bucket_pipes
*pipe
,
1020 bool *enabled
) const {
1021 if (!is_valid(s
->cct
)) {
1026 pipe
->params
.priority
= priority
;
1028 const auto& user_id
= s
->user
->get_id();
1030 rgw_bucket_key
dest_bk(user_id
.tenant
,
1031 destination
.bucket
);
1033 if (source
&& !source
->zone_names
.empty()) {
1034 pipe
->source
.zones
= get_zone_ids_from_names(store
, source
->zone_names
);
1036 pipe
->source
.set_all_zones(true);
1038 if (!destination
.zone_names
.empty()) {
1039 pipe
->dest
.zones
= get_zone_ids_from_names(store
, destination
.zone_names
);
1041 pipe
->dest
.set_all_zones(true);
1043 pipe
->dest
.bucket
.emplace(dest_bk
);
1046 int r
= filter
->to_sync_pipe_filter(s
->cct
, &pipe
->params
.source
.filter
);
1051 if (destination
.acl_translation
) {
1053 u
.tenant
= user_id
.tenant
;
1054 u
.from_str(destination
.acl_translation
->owner
); /* explicit tenant will override tenant,
1055 otherwise will inherit it from s->user */
1056 pipe
->params
.dest
.acl_translation
.emplace();
1057 pipe
->params
.dest
.acl_translation
->owner
= u
;
1059 pipe
->params
.dest
.storage_class
= destination
.storage_class
;
1061 *enabled
= (status
== "Enabled");
1063 pipe
->params
.mode
= rgw_sync_pipe_params::Mode::MODE_USER
;
1064 pipe
->params
.user
= user_id
.to_str();
1069 void from_sync_policy_pipe(rgw::sal::Store
* store
,
1070 const rgw_sync_bucket_pipes
& pipe
,
1073 status
= (enabled
? "Enabled" : "Disabled");
1074 priority
= pipe
.params
.priority
;
1076 if (pipe
.source
.all_zones
) {
1078 } else if (pipe
.source
.zones
) {
1080 source
->zone_names
= get_zone_names_from_ids(store
, *pipe
.source
.zones
);
1083 if (!pipe
.dest
.all_zones
&&
1085 destination
.zone_names
= get_zone_names_from_ids(store
, *pipe
.dest
.zones
);
1088 if (pipe
.params
.dest
.acl_translation
) {
1089 destination
.acl_translation
.emplace();
1090 destination
.acl_translation
->owner
= pipe
.params
.dest
.acl_translation
->owner
.to_str();
1093 if (pipe
.params
.dest
.storage_class
) {
1094 destination
.storage_class
= *pipe
.params
.dest
.storage_class
;
1097 if (pipe
.dest
.bucket
) {
1098 destination
.bucket
= pipe
.dest
.bucket
->get_key();
1102 filter
->from_sync_pipe_filter(pipe
.params
.source
.filter
);
1104 if (filter
->empty()) {
1110 std::vector
<Rule
> rules
;
1112 void decode_xml(XMLObj
*obj
) {
1113 RGWXMLDecoder::decode_xml("Role", role
, obj
);
1114 RGWXMLDecoder::decode_xml("Rule", rules
, obj
);
1117 void dump_xml(Formatter
*f
) const {
1118 encode_xml("Role", role
, f
);
1119 encode_xml("Rule", rules
, f
);
1122 int to_sync_policy_groups(req_state
*s
, rgw::sal::Store
* store
,
1123 vector
<rgw_sync_policy_group
> *result
) const {
1126 rgw_sync_policy_group
& enabled_group
= (*result
)[0];
1127 rgw_sync_policy_group
& disabled_group
= (*result
)[1];
1129 enabled_group
.id
= enabled_group_id
;
1130 enabled_group
.status
= rgw_sync_policy_group::Status::ENABLED
;
1131 disabled_group
.id
= disabled_group_id
;
1132 disabled_group
.status
= rgw_sync_policy_group::Status::ALLOWED
; /* not enabled, not forbidden */
1134 for (auto& rule
: rules
) {
1135 rgw_sync_bucket_pipes pipe
;
1137 int r
= rule
.to_sync_policy_pipe(s
, store
, &pipe
, &enabled
);
1139 ldpp_dout(s
, 5) << "NOTICE: failed to convert replication configuration into sync policy pipe (rule.id=" << rule
.id
<< "): " << cpp_strerror(-r
) << dendl
;
1144 enabled_group
.pipes
.emplace_back(std::move(pipe
));
1146 disabled_group
.pipes
.emplace_back(std::move(pipe
));
1152 void from_sync_policy_group(rgw::sal::Store
* store
,
1153 const rgw_sync_policy_group
& group
) {
1155 bool enabled
= (group
.status
== rgw_sync_policy_group::Status::ENABLED
);
1157 for (auto& pipe
: group
.pipes
) {
1158 auto& rule
= rules
.emplace_back();
1159 rule
.from_sync_policy_pipe(store
, pipe
, enabled
);
1166 void RGWGetBucketReplication_ObjStore_S3::send_response_data()
1169 set_req_state_err(s
, op_ret
);
1171 end_header(s
, this, "application/xml");
1174 ReplicationConfiguration conf
;
1176 if (s
->bucket
->get_info().sync_policy
) {
1177 auto policy
= s
->bucket
->get_info().sync_policy
;
1179 auto iter
= policy
->groups
.find(enabled_group_id
);
1180 if (iter
!= policy
->groups
.end()) {
1181 conf
.from_sync_policy_group(store
, iter
->second
);
1183 iter
= policy
->groups
.find(disabled_group_id
);
1184 if (iter
!= policy
->groups
.end()) {
1185 conf
.from_sync_policy_group(store
, iter
->second
);
1190 s
->formatter
->open_object_section_in_ns("ReplicationConfiguration", XMLNS_AWS_S3
);
1191 conf
.dump_xml(s
->formatter
);
1192 s
->formatter
->close_section();
1193 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1197 int RGWPutBucketReplication_ObjStore_S3::get_params(optional_yield y
)
1199 RGWXMLParser parser
;
1201 if (!parser
.init()){
1205 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
1209 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
1214 if (!parser
.parse(data
.c_str(), data
.length(), 1)) {
1215 return -ERR_MALFORMED_XML
;
1218 ReplicationConfiguration conf
;
1220 RGWXMLDecoder::decode_xml("ReplicationConfiguration", conf
, &parser
);
1221 } catch (RGWXMLDecoder::err
& err
) {
1223 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
1224 return -ERR_MALFORMED_XML
;
1227 r
= conf
.to_sync_policy_groups(s
, store
, &sync_policy_groups
);
1232 // forward requests to meta master zone
1233 if (!store
->is_meta_master()) {
1234 /* only need to keep this data around if we're not meta master */
1235 in_data
= std::move(data
);
1241 void RGWPutBucketReplication_ObjStore_S3::send_response()
1244 set_req_state_err(s
, op_ret
);
1246 end_header(s
, this, "application/xml");
1250 void RGWDeleteBucketReplication_ObjStore_S3::update_sync_policy(rgw_sync_policy_info
*policy
)
1252 policy
->groups
.erase(enabled_group_id
);
1253 policy
->groups
.erase(disabled_group_id
);
1256 void RGWDeleteBucketReplication_ObjStore_S3::send_response()
1259 set_req_state_err(s
, op_ret
);
1261 end_header(s
, this, "application/xml");
1265 void RGWListBuckets_ObjStore_S3::send_response_begin(bool has_buckets
)
1268 set_req_state_err(s
, op_ret
);
1271 // Explicitly use chunked transfer encoding so that we can stream the result
1272 // to the user without having to wait for the full length of it.
1273 end_header(s
, NULL
, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1276 list_all_buckets_start(s
);
1277 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1278 s
->formatter
->open_array_section("Buckets");
1283 void RGWListBuckets_ObjStore_S3::send_response_data(rgw::sal::BucketList
& buckets
)
1288 auto& m
= buckets
.get_buckets();
1290 for (auto iter
= m
.begin(); iter
!= m
.end(); ++iter
) {
1291 auto& bucket
= iter
->second
;
1292 dump_bucket(s
, *bucket
);
1294 rgw_flush_formatter(s
, s
->formatter
);
1297 void RGWListBuckets_ObjStore_S3::send_response_end()
1300 s
->formatter
->close_section();
1301 list_all_buckets_end(s
);
1302 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1306 int RGWGetUsage_ObjStore_S3::get_params(optional_yield y
)
1308 start_date
= s
->info
.args
.get("start-date");
1309 end_date
= s
->info
.args
.get("end-date");
1313 static void dump_usage_categories_info(Formatter
*formatter
, const rgw_usage_log_entry
& entry
, map
<string
, bool> *categories
)
1315 formatter
->open_array_section("categories");
1316 map
<string
, rgw_usage_data
>::const_iterator uiter
;
1317 for (uiter
= entry
.usage_map
.begin(); uiter
!= entry
.usage_map
.end(); ++uiter
) {
1318 if (categories
&& !categories
->empty() && !categories
->count(uiter
->first
))
1320 const rgw_usage_data
& usage
= uiter
->second
;
1321 formatter
->open_object_section("Entry");
1322 encode_json("Category", uiter
->first
, formatter
);
1323 encode_json("BytesSent", usage
.bytes_sent
, formatter
);
1324 encode_json("BytesReceived", usage
.bytes_received
, formatter
);
1325 encode_json("Ops", usage
.ops
, formatter
);
1326 encode_json("SuccessfulOps", usage
.successful_ops
, formatter
);
1327 formatter
->close_section(); // Entry
1329 formatter
->close_section(); // Category
1332 static void dump_usage_bucket_info(Formatter
*formatter
, const std::string
& name
, const bucket_meta_entry
& entry
)
1334 formatter
->open_object_section("Entry");
1335 encode_json("Bucket", name
, formatter
);
1336 encode_json("Bytes", entry
.size
, formatter
);
1337 encode_json("Bytes_Rounded", entry
.size_rounded
, formatter
);
1338 formatter
->close_section(); // entry
1341 void RGWGetUsage_ObjStore_S3::send_response()
1344 set_req_state_err(s
, op_ret
);
1347 // Explicitly use chunked transfer encoding so that we can stream the result
1348 // to the user without having to wait for the full length of it.
1349 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1354 Formatter
*formatter
= s
->formatter
;
1356 bool user_section_open
= false;
1358 formatter
->open_object_section("Usage");
1359 if (show_log_entries
) {
1360 formatter
->open_array_section("Entries");
1362 map
<rgw_user_bucket
, rgw_usage_log_entry
>::iterator iter
;
1363 for (iter
= usage
.begin(); iter
!= usage
.end(); ++iter
) {
1364 const rgw_user_bucket
& ub
= iter
->first
;
1365 const rgw_usage_log_entry
& entry
= iter
->second
;
1367 if (show_log_entries
) {
1368 if (ub
.user
.compare(last_owner
) != 0) {
1369 if (user_section_open
) {
1370 formatter
->close_section();
1371 formatter
->close_section();
1373 formatter
->open_object_section("User");
1374 formatter
->dump_string("Owner", ub
.user
);
1375 formatter
->open_array_section("Buckets");
1376 user_section_open
= true;
1377 last_owner
= ub
.user
;
1379 formatter
->open_object_section("Bucket");
1380 formatter
->dump_string("Bucket", ub
.bucket
);
1381 utime_t
ut(entry
.epoch
, 0);
1382 ut
.gmtime(formatter
->dump_stream("Time"));
1383 formatter
->dump_int("Epoch", entry
.epoch
);
1384 dump_usage_categories_info(formatter
, entry
, &categories
);
1385 formatter
->close_section(); // bucket
1388 summary_map
[ub
.user
].aggregate(entry
, &categories
);
1391 if (show_log_entries
) {
1392 if (user_section_open
) {
1393 formatter
->close_section(); // buckets
1394 formatter
->close_section(); //user
1396 formatter
->close_section(); // entries
1400 formatter
->open_array_section("Summary");
1401 map
<string
, rgw_usage_log_entry
>::iterator siter
;
1402 for (siter
= summary_map
.begin(); siter
!= summary_map
.end(); ++siter
) {
1403 const rgw_usage_log_entry
& entry
= siter
->second
;
1404 formatter
->open_object_section("User");
1405 formatter
->dump_string("User", siter
->first
);
1406 dump_usage_categories_info(formatter
, entry
, &categories
);
1407 rgw_usage_data total_usage
;
1408 entry
.sum(total_usage
, categories
);
1409 formatter
->open_object_section("Total");
1410 encode_json("BytesSent", total_usage
.bytes_sent
, formatter
);
1411 encode_json("BytesReceived", total_usage
.bytes_received
, formatter
);
1412 encode_json("Ops", total_usage
.ops
, formatter
);
1413 encode_json("SuccessfulOps", total_usage
.successful_ops
, formatter
);
1414 formatter
->close_section(); // total
1415 formatter
->close_section(); // user
1418 if (s
->cct
->_conf
->rgw_rest_getusage_op_compat
) {
1419 formatter
->open_object_section("Stats");
1422 // send info about quota config
1423 auto user_info
= s
->user
->get_info();
1424 encode_json("QuotaMaxBytes", user_info
.user_quota
.max_size
, formatter
);
1425 encode_json("QuotaMaxBuckets", user_info
.max_buckets
, formatter
);
1426 encode_json("QuotaMaxObjCount", user_info
.user_quota
.max_objects
, formatter
);
1427 encode_json("QuotaMaxBytesPerBucket", user_info
.bucket_quota
.max_objects
, formatter
);
1428 encode_json("QuotaMaxObjCountPerBucket", user_info
.bucket_quota
.max_size
, formatter
);
1429 // send info about user's capacity utilization
1430 encode_json("TotalBytes", stats
.size
, formatter
);
1431 encode_json("TotalBytesRounded", stats
.size_rounded
, formatter
);
1432 encode_json("TotalEntries", stats
.num_objects
, formatter
);
1434 if (s
->cct
->_conf
->rgw_rest_getusage_op_compat
) {
1435 formatter
->close_section(); //Stats
1438 formatter
->close_section(); // summary
1441 formatter
->open_array_section("CapacityUsed");
1442 formatter
->open_object_section("User");
1443 formatter
->open_array_section("Buckets");
1444 for (const auto& biter
: buckets_usage
) {
1445 const bucket_meta_entry
& entry
= biter
.second
;
1446 dump_usage_bucket_info(formatter
, biter
.first
, entry
);
1448 formatter
->close_section(); // Buckets
1449 formatter
->close_section(); // User
1450 formatter
->close_section(); // CapacityUsed
1452 formatter
->close_section(); // usage
1453 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1456 int RGWListBucket_ObjStore_S3::get_common_params()
1458 list_versions
= s
->info
.args
.exists("versions");
1459 prefix
= s
->info
.args
.get("prefix");
1462 s
->info
.args
.get_bool("allow-unordered", &allow_unordered
, false);
1463 delimiter
= s
->info
.args
.get("delimiter");
1464 max_keys
= s
->info
.args
.get("max-keys");
1465 op_ret
= parse_max_keys();
1469 encoding_type
= s
->info
.args
.get("encoding-type");
1470 if (s
->system_request
) {
1471 s
->info
.args
.get_bool("objs-container", &objs_container
, false);
1472 const char *shard_id_str
= s
->info
.env
->get("HTTP_RGWX_SHARD_ID");
1475 shard_id
= strict_strtol(shard_id_str
, 10, &err
);
1477 ldpp_dout(this, 5) << "bad shard id specified: " << shard_id_str
<< dendl
;
1481 shard_id
= s
->bucket_instance_shard_id
;
1487 int RGWListBucket_ObjStore_S3::get_params(optional_yield y
)
1489 int ret
= get_common_params();
1493 if (!list_versions
) {
1494 marker
= s
->info
.args
.get("marker");
1496 marker
.name
= s
->info
.args
.get("key-marker");
1497 marker
.instance
= s
->info
.args
.get("version-id-marker");
1502 int RGWListBucket_ObjStore_S3v2::get_params(optional_yield y
)
1504 int ret
= get_common_params();
1508 s
->info
.args
.get_bool("fetch-owner", &fetchOwner
, false);
1509 startAfter
= s
->info
.args
.get("start-after", &start_after_exist
);
1510 continuation_token
= s
->info
.args
.get("continuation-token", &continuation_token_exist
);
1511 if(!continuation_token_exist
) {
1512 marker
= startAfter
;
1514 marker
= continuation_token
;
1519 void RGWListBucket_ObjStore_S3::send_common_versioned_response()
1521 if (!s
->bucket_tenant
.empty()) {
1522 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
1524 s
->formatter
->dump_string("Name", s
->bucket_name
);
1525 s
->formatter
->dump_string("Prefix", prefix
);
1526 s
->formatter
->dump_int("MaxKeys", max
);
1527 if (!delimiter
.empty()) {
1528 s
->formatter
->dump_string("Delimiter", delimiter
);
1530 s
->formatter
->dump_string("IsTruncated", (max
&& is_truncated
? "true"
1533 if (!common_prefixes
.empty()) {
1534 map
<string
, bool>::iterator pref_iter
;
1535 for (pref_iter
= common_prefixes
.begin();
1536 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1537 s
->formatter
->open_array_section("CommonPrefixes");
1539 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1541 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1544 s
->formatter
->close_section();
1549 void RGWListBucket_ObjStore_S3::send_versioned_response()
1551 s
->formatter
->open_object_section_in_ns("ListVersionsResult", XMLNS_AWS_S3
);
1552 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1553 s
->formatter
->dump_string("EncodingType", "url");
1556 RGWListBucket_ObjStore_S3::send_common_versioned_response();
1557 s
->formatter
->dump_string("KeyMarker", marker
.name
);
1558 s
->formatter
->dump_string("VersionIdMarker", marker
.instance
);
1559 if (is_truncated
&& !next_marker
.empty()) {
1560 s
->formatter
->dump_string("NextKeyMarker", next_marker
.name
);
1561 if (next_marker
.instance
.empty()) {
1562 s
->formatter
->dump_string("NextVersionIdMarker", "null");
1565 s
->formatter
->dump_string("NextVersionIdMarker", next_marker
.instance
);
1570 if (objs_container
) {
1571 s
->formatter
->open_array_section("Entries");
1574 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1575 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1576 const char *section_name
= (iter
->is_delete_marker() ? "DeleteMarker"
1578 s
->formatter
->open_object_section(section_name
);
1579 if (objs_container
) {
1580 s
->formatter
->dump_bool("IsDeleteMarker", iter
->is_delete_marker());
1582 rgw_obj_key
key(iter
->key
);
1585 url_encode(key
.name
, key_name
);
1586 s
->formatter
->dump_string("Key", key_name
);
1589 s
->formatter
->dump_string("Key", key
.name
);
1591 string version_id
= key
.instance
;
1592 if (version_id
.empty()) {
1593 version_id
= "null";
1595 if (s
->system_request
) {
1596 if (iter
->versioned_epoch
> 0) {
1597 s
->formatter
->dump_int("VersionedEpoch", iter
->versioned_epoch
);
1599 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1600 utime_t
ut(iter
->meta
.mtime
);
1601 ut
.gmtime_nsec(s
->formatter
->dump_stream("RgwxMtime"));
1603 s
->formatter
->dump_string("VersionId", version_id
);
1604 s
->formatter
->dump_bool("IsLatest", iter
->is_current());
1605 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1606 if (!iter
->is_delete_marker()) {
1607 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1608 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1609 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1610 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1612 dump_owner(s
, rgw_user(iter
->meta
.owner
), iter
->meta
.owner_display_name
);
1613 if (iter
->meta
.appendable
) {
1614 s
->formatter
->dump_string("Type", "Appendable");
1616 s
->formatter
->dump_string("Type", "Normal");
1618 s
->formatter
->close_section(); // Version/DeleteMarker
1620 if (objs_container
) {
1621 s
->formatter
->close_section(); // Entries
1623 s
->formatter
->close_section(); // ListVersionsResult
1625 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1629 void RGWListBucket_ObjStore_S3::send_common_response()
1631 if (!s
->bucket_tenant
.empty()) {
1632 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
1634 s
->formatter
->dump_string("Name", s
->bucket_name
);
1635 s
->formatter
->dump_string("Prefix", prefix
);
1636 s
->formatter
->dump_int("MaxKeys", max
);
1637 if (!delimiter
.empty()) {
1638 s
->formatter
->dump_string("Delimiter", delimiter
);
1640 s
->formatter
->dump_string("IsTruncated", (max
&& is_truncated
? "true"
1643 if (!common_prefixes
.empty()) {
1644 map
<string
, bool>::iterator pref_iter
;
1645 for (pref_iter
= common_prefixes
.begin();
1646 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1647 s
->formatter
->open_array_section("CommonPrefixes");
1649 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1651 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1653 s
->formatter
->close_section();
1658 void RGWListBucket_ObjStore_S3::send_response()
1661 set_req_state_err(s
, op_ret
);
1665 // Explicitly use chunked transfer encoding so that we can stream the result
1666 // to the user without having to wait for the full length of it.
1667 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1672 if (list_versions
) {
1673 send_versioned_response();
1677 s
->formatter
->open_object_section_in_ns("ListBucketResult", XMLNS_AWS_S3
);
1678 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1679 s
->formatter
->dump_string("EncodingType", "url");
1682 RGWListBucket_ObjStore_S3::send_common_response();
1684 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1685 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1686 rgw_obj_key
key(iter
->key
);
1687 s
->formatter
->open_array_section("Contents");
1690 url_encode(key
.name
, key_name
);
1691 s
->formatter
->dump_string("Key", key_name
);
1693 s
->formatter
->dump_string("Key", key
.name
);
1695 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1696 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1697 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1698 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1699 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1700 dump_owner(s
, rgw_user(iter
->meta
.owner
), iter
->meta
.owner_display_name
);
1701 if (s
->system_request
) {
1702 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1704 if (iter
->meta
.appendable
) {
1705 s
->formatter
->dump_string("Type", "Appendable");
1707 s
->formatter
->dump_string("Type", "Normal");
1709 s
->formatter
->close_section();
1712 s
->formatter
->dump_string("Marker", marker
.name
);
1713 if (is_truncated
&& !next_marker
.empty()) {
1714 s
->formatter
->dump_string("NextMarker", next_marker
.name
);
1716 s
->formatter
->close_section();
1717 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1720 void RGWListBucket_ObjStore_S3v2::send_versioned_response()
1722 s
->formatter
->open_object_section_in_ns("ListVersionsResult", XMLNS_AWS_S3
);
1723 RGWListBucket_ObjStore_S3v2::send_common_versioned_response();
1724 s
->formatter
->dump_string("KeyContinuationToken", marker
.name
);
1725 s
->formatter
->dump_string("VersionIdContinuationToken", marker
.instance
);
1726 if (is_truncated
&& !next_marker
.empty()) {
1727 s
->formatter
->dump_string("NextKeyContinuationToken", next_marker
.name
);
1728 s
->formatter
->dump_string("NextVersionIdContinuationToken", next_marker
.instance
);
1731 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1732 s
->formatter
->dump_string("EncodingType", "url");
1737 if (objs_container
) {
1738 s
->formatter
->open_array_section("Entries");
1741 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1742 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1743 const char *section_name
= (iter
->is_delete_marker() ? "DeleteContinuationToken"
1745 s
->formatter
->open_object_section(section_name
);
1746 if (objs_container
) {
1747 s
->formatter
->dump_bool("IsDeleteContinuationToken", iter
->is_delete_marker());
1749 rgw_obj_key
key(iter
->key
);
1752 url_encode(key
.name
, key_name
);
1753 s
->formatter
->dump_string("Key", key_name
);
1756 s
->formatter
->dump_string("Key", key
.name
);
1758 string version_id
= key
.instance
;
1759 if (version_id
.empty()) {
1760 version_id
= "null";
1762 if (s
->system_request
) {
1763 if (iter
->versioned_epoch
> 0) {
1764 s
->formatter
->dump_int("VersionedEpoch", iter
->versioned_epoch
);
1766 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1767 utime_t
ut(iter
->meta
.mtime
);
1768 ut
.gmtime_nsec(s
->formatter
->dump_stream("RgwxMtime"));
1770 s
->formatter
->dump_string("VersionId", version_id
);
1771 s
->formatter
->dump_bool("IsLatest", iter
->is_current());
1772 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1773 if (!iter
->is_delete_marker()) {
1774 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1775 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1776 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1777 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1779 if (fetchOwner
== true) {
1780 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1782 s
->formatter
->close_section();
1786 if (objs_container
) {
1787 s
->formatter
->close_section();
1790 if (!common_prefixes
.empty()) {
1791 map
<string
, bool>::iterator pref_iter
;
1792 for (pref_iter
= common_prefixes
.begin();
1793 pref_iter
!= common_prefixes
.end(); ++pref_iter
) {
1794 s
->formatter
->open_array_section("CommonPrefixes");
1796 s
->formatter
->dump_string("Prefix", url_encode(pref_iter
->first
, false));
1798 s
->formatter
->dump_string("Prefix", pref_iter
->first
);
1801 s
->formatter
->dump_int("KeyCount",objs
.size());
1802 if (start_after_exist
) {
1803 s
->formatter
->dump_string("StartAfter", startAfter
);
1805 s
->formatter
->close_section();
1809 s
->formatter
->close_section();
1810 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1814 void RGWListBucket_ObjStore_S3v2::send_response()
1817 set_req_state_err(s
, op_ret
);
1821 // Explicitly use chunked transfer encoding so that we can stream the result
1822 // to the user without having to wait for the full length of it.
1823 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
1828 if (list_versions
) {
1829 send_versioned_response();
1833 s
->formatter
->open_object_section_in_ns("ListBucketResult", XMLNS_AWS_S3
);
1834 if (strcasecmp(encoding_type
.c_str(), "url") == 0) {
1835 s
->formatter
->dump_string("EncodingType", "url");
1839 RGWListBucket_ObjStore_S3::send_common_response();
1841 vector
<rgw_bucket_dir_entry
>::iterator iter
;
1842 for (iter
= objs
.begin(); iter
!= objs
.end(); ++iter
) {
1843 rgw_obj_key
key(iter
->key
);
1844 s
->formatter
->open_array_section("Contents");
1847 url_encode(key
.name
, key_name
);
1848 s
->formatter
->dump_string("Key", key_name
);
1851 s
->formatter
->dump_string("Key", key
.name
);
1853 dump_time(s
, "LastModified", iter
->meta
.mtime
);
1854 s
->formatter
->dump_format("ETag", "\"%s\"", iter
->meta
.etag
.c_str());
1855 s
->formatter
->dump_int("Size", iter
->meta
.accounted_size
);
1856 auto& storage_class
= rgw_placement_rule::get_canonical_storage_class(iter
->meta
.storage_class
);
1857 s
->formatter
->dump_string("StorageClass", storage_class
.c_str());
1858 if (fetchOwner
== true) {
1859 dump_owner(s
, s
->user
->get_id(), s
->user
->get_display_name());
1861 if (s
->system_request
) {
1862 s
->formatter
->dump_string("RgwxTag", iter
->tag
);
1864 if (iter
->meta
.appendable
) {
1865 s
->formatter
->dump_string("Type", "Appendable");
1867 s
->formatter
->dump_string("Type", "Normal");
1869 s
->formatter
->close_section();
1872 if (continuation_token_exist
) {
1873 s
->formatter
->dump_string("ContinuationToken", continuation_token
);
1875 if (is_truncated
&& !next_marker
.empty()) {
1876 s
->formatter
->dump_string("NextContinuationToken", next_marker
.name
);
1878 s
->formatter
->dump_int("KeyCount", objs
.size() + common_prefixes
.size());
1879 if (start_after_exist
) {
1880 s
->formatter
->dump_string("StartAfter", startAfter
);
1882 s
->formatter
->close_section();
1883 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1886 void RGWGetBucketLogging_ObjStore_S3::send_response()
1889 end_header(s
, this, "application/xml");
1892 s
->formatter
->open_object_section_in_ns("BucketLoggingStatus", XMLNS_AWS_S3
);
1893 s
->formatter
->close_section();
1894 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1897 void RGWGetBucketLocation_ObjStore_S3::send_response()
1900 end_header(s
, this);
1903 RGWZoneGroup zonegroup
;
1906 int ret
= store
->get_zone()->get_zonegroup(s
->bucket
->get_info().zonegroup
, zonegroup
);
1908 api_name
= zonegroup
.api_name
;
1910 if (s
->bucket
->get_info().zonegroup
!= "default") {
1911 api_name
= s
->bucket
->get_info().zonegroup
;
1915 s
->formatter
->dump_format_ns("LocationConstraint", XMLNS_AWS_S3
,
1916 "%s", api_name
.c_str());
1917 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1920 void RGWGetBucketVersioning_ObjStore_S3::send_response()
1923 set_req_state_err(s
, op_ret
);
1925 end_header(s
, this, "application/xml");
1928 s
->formatter
->open_object_section_in_ns("VersioningConfiguration", XMLNS_AWS_S3
);
1930 const char *status
= (versioning_enabled
? "Enabled" : "Suspended");
1931 s
->formatter
->dump_string("Status", status
);
1932 const char *mfa_status
= (mfa_enabled
? "Enabled" : "Disabled");
1933 s
->formatter
->dump_string("MfaDelete", mfa_status
);
1935 s
->formatter
->close_section();
1936 rgw_flush_formatter_and_reset(s
, s
->formatter
);
1939 struct ver_config_status
{
1940 int status
{VersioningSuspended
};
1946 } mfa_status
{MFA_UNKNOWN
};
1949 void decode_xml(XMLObj
*obj
) {
1952 RGWXMLDecoder::decode_xml("Status", status_str
, obj
);
1953 if (status_str
== "Enabled") {
1954 status
= VersioningEnabled
;
1955 } else if (status_str
!= "Suspended") {
1956 status
= VersioningStatusInvalid
;
1960 if (RGWXMLDecoder::decode_xml("MfaDelete", mfa_str
, obj
)) {
1961 if (mfa_str
== "Enabled") {
1962 mfa_status
= MFA_ENABLED
;
1963 } else if (mfa_str
== "Disabled") {
1964 mfa_status
= MFA_DISABLED
;
1972 int RGWSetBucketVersioning_ObjStore_S3::get_params(optional_yield y
)
1977 read_all_input(s
, s
->cct
->_conf
->rgw_max_put_param_size
, false);
1982 RGWXMLDecoder::XMLParser parser
;
1983 if (!parser
.init()) {
1984 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
1988 char* buf
= data
.c_str();
1989 if (!parser
.parse(buf
, data
.length(), 1)) {
1990 ldpp_dout(this, 10) << "NOTICE: failed to parse data: " << buf
<< dendl
;
1995 ver_config_status status_conf
;
1997 if (!RGWXMLDecoder::decode_xml("VersioningConfiguration", status_conf
, &parser
)) {
1998 ldpp_dout(this, 10) << "NOTICE: bad versioning config input" << dendl
;
2002 if (!store
->is_meta_master()) {
2003 /* only need to keep this data around if we're not meta master */
2004 in_data
.append(data
);
2007 versioning_status
= status_conf
.status
;
2008 if (versioning_status
== VersioningStatusInvalid
) {
2012 if (status_conf
.mfa_status
!= ver_config_status::MFA_UNKNOWN
) {
2013 mfa_set_status
= true;
2014 switch (status_conf
.mfa_status
) {
2015 case ver_config_status::MFA_DISABLED
:
2018 case ver_config_status::MFA_ENABLED
:
2022 ldpp_dout(this, 0) << "ERROR: RGWSetBucketVersioning_ObjStore_S3::get_params(optional_yield y): unexpected switch case mfa_status=" << status_conf
.mfa_status
<< dendl
;
2025 } else if (status_conf
.retcode
< 0) {
2026 r
= status_conf
.retcode
;
2031 void RGWSetBucketVersioning_ObjStore_S3::send_response()
2034 set_req_state_err(s
, op_ret
);
2036 end_header(s
, this, "application/xml");
2039 int RGWSetBucketWebsite_ObjStore_S3::get_params(optional_yield y
)
2041 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
2045 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
2051 in_data
.append(data
);
2053 RGWXMLDecoder::XMLParser parser
;
2054 if (!parser
.init()) {
2055 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
2059 char* buf
= data
.c_str();
2060 if (!parser
.parse(buf
, data
.length(), 1)) {
2061 ldpp_dout(this, 5) << "failed to parse xml: " << buf
<< dendl
;
2066 RGWXMLDecoder::decode_xml("WebsiteConfiguration", website_conf
, &parser
, true);
2067 } catch (RGWXMLDecoder::err
& err
) {
2068 ldpp_dout(this, 5) << "unexpected xml: " << buf
<< dendl
;
2072 if (website_conf
.is_redirect_all
&& website_conf
.redirect_all
.hostname
.empty()) {
2073 s
->err
.message
= "A host name must be provided to redirect all requests (e.g. \"example.com\").";
2074 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2076 } else if (!website_conf
.is_redirect_all
&& !website_conf
.is_set_index_doc
) {
2077 s
->err
.message
= "A value for IndexDocument Suffix must be provided if RedirectAllRequestsTo is empty";
2078 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2080 } else if (!website_conf
.is_redirect_all
&& website_conf
.is_set_index_doc
&&
2081 website_conf
.index_doc_suffix
.empty()) {
2082 s
->err
.message
= "The IndexDocument Suffix is not well formed";
2083 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
2087 #define WEBSITE_ROUTING_RULES_MAX_NUM 50
2088 int max_num
= s
->cct
->_conf
->rgw_website_routing_rules_max_num
;
2090 max_num
= WEBSITE_ROUTING_RULES_MAX_NUM
;
2092 int routing_rules_num
= website_conf
.routing_rules
.rules
.size();
2093 if (routing_rules_num
> max_num
) {
2094 ldpp_dout(this, 4) << "An website routing config can have up to "
2096 << " rules, request website routing rules num: "
2097 << routing_rules_num
<< dendl
;
2098 op_ret
= -ERR_INVALID_WEBSITE_ROUTING_RULES_ERROR
;
2099 s
->err
.message
= std::to_string(routing_rules_num
) +" routing rules provided, the number of routing rules in a website configuration is limited to "
2100 + std::to_string(max_num
)
2102 return -ERR_INVALID_REQUEST
;
2108 void RGWSetBucketWebsite_ObjStore_S3::send_response()
2111 set_req_state_err(s
, op_ret
);
2113 end_header(s
, this, "application/xml");
2116 void RGWDeleteBucketWebsite_ObjStore_S3::send_response()
2119 op_ret
= STATUS_NO_CONTENT
;
2121 set_req_state_err(s
, op_ret
);
2123 end_header(s
, this, "application/xml");
2126 void RGWGetBucketWebsite_ObjStore_S3::send_response()
2129 set_req_state_err(s
, op_ret
);
2131 end_header(s
, this, "application/xml");
2138 RGWBucketWebsiteConf
& conf
= s
->bucket
->get_info().website_conf
;
2140 s
->formatter
->open_object_section_in_ns("WebsiteConfiguration", XMLNS_AWS_S3
);
2141 conf
.dump_xml(s
->formatter
);
2142 s
->formatter
->close_section(); // WebsiteConfiguration
2143 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2146 static void dump_bucket_metadata(struct req_state
*s
, rgw::sal::Bucket
* bucket
)
2148 dump_header(s
, "X-RGW-Object-Count", static_cast<long long>(bucket
->get_count()));
2149 dump_header(s
, "X-RGW-Bytes-Used", static_cast<long long>(bucket
->get_size()));
2150 // only bucket's owner is allowed to get the quota settings of the account
2151 if (bucket
->is_owner(s
->user
.get())) {
2152 auto user_info
= s
->user
->get_info();
2153 dump_header(s
, "X-RGW-Quota-User-Size", static_cast<long long>(user_info
.user_quota
.max_size
));
2154 dump_header(s
, "X-RGW-Quota-User-Objects", static_cast<long long>(user_info
.user_quota
.max_objects
));
2155 dump_header(s
, "X-RGW-Quota-Max-Buckets", static_cast<long long>(user_info
.max_buckets
));
2156 dump_header(s
, "X-RGW-Quota-Bucket-Size", static_cast<long long>(user_info
.bucket_quota
.max_size
));
2157 dump_header(s
, "X-RGW-Quota-Bucket-Objects", static_cast<long long>(user_info
.bucket_quota
.max_objects
));
2161 void RGWStatBucket_ObjStore_S3::send_response()
2164 dump_bucket_metadata(s
, bucket
.get());
2167 set_req_state_err(s
, op_ret
);
2170 end_header(s
, this);
2174 static int create_s3_policy(struct req_state
*s
, rgw::sal::Store
* store
,
2175 RGWAccessControlPolicy_S3
& s3policy
,
2178 if (s
->has_acl_header
) {
2179 if (!s
->canned_acl
.empty())
2180 return -ERR_INVALID_REQUEST
;
2182 return s3policy
.create_from_headers(s
, store
, s
->info
.env
, owner
);
2185 return s3policy
.create_canned(owner
, s
->bucket_owner
, s
->canned_acl
);
2188 class RGWLocationConstraint
: public XMLObj
2191 RGWLocationConstraint() {}
2192 ~RGWLocationConstraint() override
{}
2193 bool xml_end(const char *el
) override
{
2197 location_constraint
= get_data();
2202 string location_constraint
;
2205 class RGWCreateBucketConfig
: public XMLObj
2208 RGWCreateBucketConfig() {}
2209 ~RGWCreateBucketConfig() override
{}
2212 class RGWCreateBucketParser
: public RGWXMLParser
2214 XMLObj
*alloc_obj(const char *el
) override
{
2219 RGWCreateBucketParser() {}
2220 ~RGWCreateBucketParser() override
{}
2222 bool get_location_constraint(string
& zone_group
) {
2223 XMLObj
*config
= find_first("CreateBucketConfiguration");
2227 XMLObj
*constraint
= config
->find_first("LocationConstraint");
2231 zone_group
= constraint
->get_data();
2237 int RGWCreateBucket_ObjStore_S3::get_params(optional_yield y
)
2239 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
2240 bool relaxed_names
= s
->cct
->_conf
->rgw_relaxed_s3_bucket_names
;
2243 if (!s
->system_request
) {
2244 r
= valid_s3_bucket_name(s
->bucket_name
, relaxed_names
);
2248 r
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
2254 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
2258 std::tie(op_ret
, data
) = read_all_input(s
, max_size
, false);
2260 if ((op_ret
< 0) && (op_ret
!= -ERR_LENGTH_REQUIRED
))
2263 in_data
.append(data
);
2265 if (data
.length()) {
2266 RGWCreateBucketParser parser
;
2268 if (!parser
.init()) {
2269 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
2273 char* buf
= data
.c_str();
2274 bool success
= parser
.parse(buf
, data
.length(), 1);
2275 ldpp_dout(this, 20) << "create bucket input data=" << buf
<< dendl
;
2278 ldpp_dout(this, 0) << "failed to parse input: " << buf
<< dendl
;
2282 if (!parser
.get_location_constraint(location_constraint
)) {
2283 ldpp_dout(this, 0) << "provided input did not specify location constraint correctly" << dendl
;
2287 ldpp_dout(this, 10) << "create bucket location constraint: "
2288 << location_constraint
<< dendl
;
2291 size_t pos
= location_constraint
.find(':');
2292 if (pos
!= string::npos
) {
2293 placement_rule
.init(location_constraint
.substr(pos
+ 1), s
->info
.storage_class
);
2294 location_constraint
= location_constraint
.substr(0, pos
);
2296 placement_rule
.storage_class
= s
->info
.storage_class
;
2298 auto iter
= s
->info
.x_meta_map
.find("x-amz-bucket-object-lock-enabled");
2299 if (iter
!= s
->info
.x_meta_map
.end()) {
2300 if (!boost::algorithm::iequals(iter
->second
, "true") && !boost::algorithm::iequals(iter
->second
, "false")) {
2303 obj_lock_enabled
= boost::algorithm::iequals(iter
->second
, "true");
2308 void RGWCreateBucket_ObjStore_S3::send_response()
2310 if (op_ret
== -ERR_BUCKET_EXISTS
)
2313 set_req_state_err(s
, op_ret
);
2320 if (s
->system_request
) {
2321 JSONFormatter f
; /* use json formatter for system requests output */
2323 f
.open_object_section("info");
2324 encode_json("entry_point_object_ver", ep_objv
, &f
);
2325 encode_json("object_ver", info
.objv_tracker
.read_version
, &f
);
2326 encode_json("bucket_info", info
, &f
);
2328 rgw_flush_formatter_and_reset(s
, &f
);
2332 void RGWDeleteBucket_ObjStore_S3::send_response()
2336 r
= STATUS_NO_CONTENT
;
2338 set_req_state_err(s
, r
);
2340 end_header(s
, this);
2343 static inline void map_qs_metadata(struct req_state
* s
)
2345 /* merge S3 valid user metadata from the query-string into
2346 * x_meta_map, which maps them to attributes */
2347 const auto& params
= const_cast<RGWHTTPArgs
&>(s
->info
.args
).get_params();
2348 for (const auto& elt
: params
) {
2349 std::string k
= boost::algorithm::to_lower_copy(elt
.first
);
2350 if (k
.find("x-amz-meta-") == /* offset */ 0) {
2351 rgw_add_amz_meta_header(s
->info
.x_meta_map
, k
, elt
.second
);
2356 int RGWPutObj_ObjStore_S3::get_params(optional_yield y
)
2359 return -ERR_LENGTH_REQUIRED
;
2365 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
2366 ret
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
2372 if_match
= s
->info
.env
->get("HTTP_IF_MATCH");
2373 if_nomatch
= s
->info
.env
->get("HTTP_IF_NONE_MATCH");
2375 /* handle object tagging */
2376 auto tag_str
= s
->info
.env
->get("HTTP_X_AMZ_TAGGING");
2378 obj_tags
= std::make_unique
<RGWObjTags
>();
2379 ret
= obj_tags
->set_from_string(tag_str
);
2381 ldpp_dout(this,0) << "setting obj tags failed with " << ret
<< dendl
;
2382 if (ret
== -ERR_INVALID_TAG
){
2383 ret
= -EINVAL
; //s3 returns only -EINVAL for PUT requests
2390 //handle object lock
2391 auto obj_lock_mode_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_MODE");
2392 auto obj_lock_date_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE");
2393 auto obj_legal_hold_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_LEGAL_HOLD");
2394 if (obj_lock_mode_str
&& obj_lock_date_str
) {
2395 boost::optional
<ceph::real_time
> date
= ceph::from_iso_8601(obj_lock_date_str
);
2396 if (boost::none
== date
|| ceph::real_clock::to_time_t(*date
) <= ceph_clock_now()) {
2398 ldpp_dout(this,0) << "invalid x-amz-object-lock-retain-until-date value" << dendl
;
2401 if (strcmp(obj_lock_mode_str
, "GOVERNANCE") != 0 && strcmp(obj_lock_mode_str
, "COMPLIANCE") != 0) {
2403 ldpp_dout(this,0) << "invalid x-amz-object-lock-mode value" << dendl
;
2406 obj_retention
= new RGWObjectRetention(obj_lock_mode_str
, *date
);
2407 } else if ((obj_lock_mode_str
&& !obj_lock_date_str
) || (!obj_lock_mode_str
&& obj_lock_date_str
)) {
2409 ldpp_dout(this,0) << "need both x-amz-object-lock-mode and x-amz-object-lock-retain-until-date " << dendl
;
2412 if (obj_legal_hold_str
) {
2413 if (strcmp(obj_legal_hold_str
, "ON") != 0 && strcmp(obj_legal_hold_str
, "OFF") != 0) {
2415 ldpp_dout(this,0) << "invalid x-amz-object-lock-legal-hold value" << dendl
;
2418 obj_legal_hold
= new RGWObjectLegalHold(obj_legal_hold_str
);
2420 if (!s
->bucket
->get_info().obj_lock_enabled() && (obj_retention
|| obj_legal_hold
)) {
2421 ldpp_dout(this, 0) << "ERROR: object retention or legal hold can't be set if bucket object lock not configured" << dendl
;
2422 ret
= -ERR_INVALID_REQUEST
;
2425 multipart_upload_id
= s
->info
.args
.get("uploadId");
2426 multipart_part_str
= s
->info
.args
.get("partNumber");
2427 if (!multipart_part_str
.empty()) {
2429 multipart_part_num
= strict_strtol(multipart_part_str
.c_str(), 10, &err
);
2431 ldpp_dout(s
, 10) << "bad part number: " << multipart_part_str
<< ": " << err
<< dendl
;
2434 } else if (!multipart_upload_id
.empty()) {
2435 ldpp_dout(s
, 10) << "part number with no multipart upload id" << dendl
;
2439 append
= s
->info
.args
.exists("append");
2441 string pos_str
= s
->info
.args
.get("position");
2443 long long pos_tmp
= strict_strtoll(pos_str
.c_str(), 10, &err
);
2445 ldpp_dout(s
, 10) << "bad position: " << pos_str
<< ": " << err
<< dendl
;
2447 } else if (pos_tmp
< 0) {
2448 ldpp_dout(s
, 10) << "bad position: " << pos_str
<< ": " << "position shouldn't be negative" << dendl
;
2451 position
= uint64_t(pos_tmp
);
2454 return RGWPutObj_ObjStore::get_params(y
);
2457 int RGWPutObj_ObjStore_S3::get_data(bufferlist
& bl
)
2459 const int ret
= RGWPutObj_ObjStore::get_data(bl
);
2461 const int ret_auth
= do_aws4_auth_completion();
2470 static int get_success_retcode(int code
)
2474 return STATUS_CREATED
;
2476 return STATUS_NO_CONTENT
;
2481 void RGWPutObj_ObjStore_S3::send_response()
2484 set_req_state_err(s
, op_ret
);
2487 if (s
->cct
->_conf
->rgw_s3_success_create_obj_status
) {
2488 op_ret
= get_success_retcode(
2489 s
->cct
->_conf
->rgw_s3_success_create_obj_status
);
2490 set_req_state_err(s
, op_ret
);
2493 string expires
= get_s3_expiration_header(s
, mtime
);
2495 if (copy_source
.empty()) {
2498 dump_content_length(s
, 0);
2499 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
2500 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
2501 for (auto &it
: crypt_http_responses
)
2502 dump_header(s
, it
.first
, it
.second
);
2505 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
2506 dump_header_if_nonempty(s
, "x-amz-expiration", expires
);
2507 end_header(s
, this, "application/xml");
2511 time_t secs
= (time_t)ut
.sec();
2512 gmtime_r(&secs
, &tmp
);
2513 char buf
[TIME_BUF_SIZE
];
2514 s
->formatter
->open_object_section_in_ns("CopyPartResult",
2515 "http://s3.amazonaws.com/doc/2006-03-01/");
2516 if (strftime(buf
, sizeof(buf
), "%Y-%m-%dT%T.000Z", &tmp
) > 0) {
2517 s
->formatter
->dump_string("LastModified", buf
);
2519 s
->formatter
->dump_string("ETag", etag
);
2520 s
->formatter
->close_section();
2521 rgw_flush_formatter_and_reset(s
, s
->formatter
);
2526 if (op_ret
== 0 || op_ret
== -ERR_POSITION_NOT_EQUAL_TO_LENGTH
) {
2527 dump_header(s
, "x-rgw-next-append-position", cur_accounted_size
);
2530 if (s
->system_request
&& !real_clock::is_zero(mtime
)) {
2531 dump_epoch_header(s
, "Rgwx-Mtime", mtime
);
2533 end_header(s
, this);
2536 static inline void set_attr(map
<string
, bufferlist
>& attrs
, const char* key
, const std::string
& value
)
2540 attrs
.emplace(key
, std::move(bl
));
2543 static inline void set_attr(map
<string
, bufferlist
>& attrs
, const char* key
, const char* value
)
2547 attrs
.emplace(key
, std::move(bl
));
2550 int RGWPutObj_ObjStore_S3::get_decrypt_filter(
2551 std::unique_ptr
<RGWGetObj_Filter
>* filter
,
2552 RGWGetObj_Filter
* cb
,
2553 map
<string
, bufferlist
>& attrs
,
2554 bufferlist
* manifest_bl
)
2556 std::map
<std::string
, std::string
> crypt_http_responses_unused
;
2559 std::unique_ptr
<BlockCrypt
> block_crypt
;
2560 res
= rgw_s3_prepare_decrypt(s
, attrs
, &block_crypt
, crypt_http_responses_unused
);
2562 if (block_crypt
!= nullptr) {
2563 auto f
= std::unique_ptr
<RGWGetObj_BlockDecrypt
>(new RGWGetObj_BlockDecrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2564 //RGWGetObj_BlockDecrypt* f = new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt));
2566 if (manifest_bl
!= nullptr) {
2567 res
= f
->read_manifest(this, *manifest_bl
);
2569 *filter
= std::move(f
);
2578 int RGWPutObj_ObjStore_S3::get_encrypt_filter(
2579 std::unique_ptr
<rgw::sal::DataProcessor
> *filter
,
2580 rgw::sal::DataProcessor
*cb
)
2583 if (!multipart_upload_id
.empty()) {
2584 std::unique_ptr
<rgw::sal::MultipartUpload
> upload
=
2585 s
->bucket
->get_multipart_upload(s
->object
->get_name(),
2586 multipart_upload_id
);
2587 std::unique_ptr
<rgw::sal::Object
> obj
= upload
->get_meta_obj();
2588 obj
->set_in_extra_data(true);
2589 res
= obj
->get_obj_attrs(s
->obj_ctx
, s
->yield
, this);
2591 std::unique_ptr
<BlockCrypt
> block_crypt
;
2592 /* We are adding to existing object.
2593 * We use crypto mode that configured as if we were decrypting. */
2594 res
= rgw_s3_prepare_decrypt(s
, obj
->get_attrs(), &block_crypt
, crypt_http_responses
);
2595 if (res
== 0 && block_crypt
!= nullptr)
2596 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2598 /* it is ok, to not have encryption at all */
2602 std::unique_ptr
<BlockCrypt
> block_crypt
;
2603 res
= rgw_s3_prepare_encrypt(s
, attrs
, nullptr, &block_crypt
, crypt_http_responses
);
2604 if (res
== 0 && block_crypt
!= nullptr) {
2605 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
2611 void RGWPostObj_ObjStore_S3::rebuild_key(rgw::sal::Object
* obj
)
2613 string key
= obj
->get_name();
2614 static string var
= "${filename}";
2615 int pos
= key
.find(var
);
2619 string new_key
= key
.substr(0, pos
);
2620 new_key
.append(filename
);
2621 new_key
.append(key
.substr(pos
+ var
.size()));
2623 obj
->set_key(new_key
);
2626 std::string
RGWPostObj_ObjStore_S3::get_current_filename() const
2628 return s
->object
->get_name();
2631 std::string
RGWPostObj_ObjStore_S3::get_current_content_type() const
2633 return content_type
;
2636 int RGWPostObj_ObjStore_S3::get_params(optional_yield y
)
2638 op_ret
= RGWPostObj_ObjStore::get_params(y
);
2645 ldpp_dout(this, 20) << "adding bucket to policy env: " << s
->bucket
->get_name()
2647 env
.add_var("bucket", s
->bucket
->get_name());
2651 struct post_form_part part
;
2652 int r
= read_form_part_header(&part
, done
);
2656 if (s
->cct
->_conf
->subsys
.should_gather
<ceph_subsys_rgw
, 20>()) {
2657 ldpp_dout(this, 20) << "read part header -- part.name="
2658 << part
.name
<< dendl
;
2660 for (const auto& pair
: part
.fields
) {
2661 ldpp_dout(this, 20) << "field.name=" << pair
.first
<< dendl
;
2662 ldpp_dout(this, 20) << "field.val=" << pair
.second
.val
<< dendl
;
2663 ldpp_dout(this, 20) << "field.params:" << dendl
;
2665 for (const auto& param_pair
: pair
.second
.params
) {
2666 ldpp_dout(this, 20) << " " << param_pair
.first
2667 << " -> " << param_pair
.second
<< dendl
;
2672 if (done
) { /* unexpected here */
2673 err_msg
= "Malformed request";
2677 if (stringcasecmp(part
.name
, "file") == 0) { /* beginning of data transfer */
2678 struct post_part_field
& field
= part
.fields
["Content-Disposition"];
2679 map
<string
, string
>::iterator iter
= field
.params
.find("filename");
2680 if (iter
!= field
.params
.end()) {
2681 filename
= iter
->second
;
2683 parts
[part
.name
] = part
;
2688 uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
2689 r
= read_data(part
.data
, chunk_size
, boundary
, done
);
2690 if (r
< 0 || !boundary
) {
2691 err_msg
= "Couldn't find boundary";
2694 parts
[part
.name
] = part
;
2695 string
part_str(part
.data
.c_str(), part
.data
.length());
2696 env
.add_var(part
.name
, part_str
);
2700 if (!part_str(parts
, "key", &object_str
)) {
2701 err_msg
= "Key not specified";
2705 s
->object
= store
->get_object(rgw_obj_key(object_str
));
2707 rebuild_key(s
->object
.get());
2709 if (rgw::sal::Object::empty(s
->object
.get())) {
2710 err_msg
= "Empty object name";
2714 env
.add_var("key", s
->object
->get_name());
2716 part_str(parts
, "Content-Type", &content_type
);
2718 /* AWS permits POST without Content-Type: http://tracker.ceph.com/issues/20201 */
2719 if (! content_type
.empty()) {
2720 env
.add_var("Content-Type", content_type
);
2723 std::string storage_class
;
2724 part_str(parts
, "x-amz-storage-class", &storage_class
);
2726 if (! storage_class
.empty()) {
2727 s
->dest_placement
.storage_class
= storage_class
;
2728 if (!store
->get_zone()->get_params().valid_placement(s
->dest_placement
)) {
2729 ldpp_dout(this, 0) << "NOTICE: invalid dest placement: " << s
->dest_placement
.to_str() << dendl
;
2730 err_msg
= "The storage class you specified is not valid";
2735 map
<string
, struct post_form_part
, ltstr_nocase
>::iterator piter
=
2736 parts
.upper_bound(RGW_AMZ_META_PREFIX
);
2737 for (; piter
!= parts
.end(); ++piter
) {
2738 string n
= piter
->first
;
2739 if (strncasecmp(n
.c_str(), RGW_AMZ_META_PREFIX
,
2740 sizeof(RGW_AMZ_META_PREFIX
) - 1) != 0)
2743 string attr_name
= RGW_ATTR_PREFIX
;
2744 attr_name
.append(n
);
2746 /* need to null terminate it */
2747 bufferlist
& data
= piter
->second
.data
;
2748 string str
= string(data
.c_str(), data
.length());
2751 attr_bl
.append(str
.c_str(), str
.size() + 1);
2753 attrs
[attr_name
] = attr_bl
;
2755 // TODO: refactor this and the above loop to share code
2756 piter
= parts
.find(RGW_AMZ_WEBSITE_REDIRECT_LOCATION
);
2757 if (piter
!= parts
.end()) {
2758 string n
= piter
->first
;
2759 string attr_name
= RGW_ATTR_PREFIX
;
2760 attr_name
.append(n
);
2761 /* need to null terminate it */
2762 bufferlist
& data
= piter
->second
.data
;
2763 string str
= string(data
.c_str(), data
.length());
2766 attr_bl
.append(str
.c_str(), str
.size() + 1);
2768 attrs
[attr_name
] = attr_bl
;
2771 int r
= get_policy(y
);
2780 min_len
= post_policy
.min_length
;
2781 max_len
= post_policy
.max_length
;
2788 int RGWPostObj_ObjStore_S3::get_tags()
2791 if (part_str(parts
, "tagging", &tags_str
)) {
2792 RGWXMLParser parser
;
2793 if (!parser
.init()){
2794 ldpp_dout(this, 0) << "Couldn't init RGWObjTags XML parser" << dendl
;
2795 err_msg
= "Server couldn't process the request";
2796 return -EINVAL
; // TODO: This class of errors in rgw code should be a 5XX error
2798 if (!parser
.parse(tags_str
.c_str(), tags_str
.size(), 1)) {
2799 ldpp_dout(this,0 ) << "Invalid Tagging XML" << dendl
;
2800 err_msg
= "Invalid Tagging XML";
2804 RGWObjTagging_S3 tagging
;
2807 RGWXMLDecoder::decode_xml("Tagging", tagging
, &parser
);
2808 } catch (RGWXMLDecoder::err
& err
) {
2809 ldpp_dout(this, 5) << "Malformed tagging request: " << err
<< dendl
;
2813 RGWObjTags obj_tags
;
2814 int r
= tagging
.rebuild(obj_tags
);
2819 obj_tags
.encode(tags_bl
);
2820 ldpp_dout(this, 20) << "Read " << obj_tags
.count() << "tags" << dendl
;
2821 attrs
[RGW_ATTR_TAGS
] = tags_bl
;
2828 int RGWPostObj_ObjStore_S3::get_policy(optional_yield y
)
2830 if (part_bl(parts
, "policy", &s
->auth
.s3_postobj_creds
.encoded_policy
)) {
2831 bool aws4_auth
= false;
2833 /* x-amz-algorithm handling */
2834 using rgw::auth::s3::AWS4_HMAC_SHA256_STR
;
2835 if ((part_str(parts
, "x-amz-algorithm", &s
->auth
.s3_postobj_creds
.x_amz_algorithm
)) &&
2836 (s
->auth
.s3_postobj_creds
.x_amz_algorithm
== AWS4_HMAC_SHA256_STR
)) {
2837 ldpp_dout(this, 0) << "Signature verification algorithm AWS v4 (AWS4-HMAC-SHA256)" << dendl
;
2840 ldpp_dout(this, 0) << "Signature verification algorithm AWS v2" << dendl
;
2843 // check that the signature matches the encoded policy
2847 /* x-amz-credential handling */
2848 if (!part_str(parts
, "x-amz-credential",
2849 &s
->auth
.s3_postobj_creds
.x_amz_credential
)) {
2850 ldpp_dout(this, 0) << "No S3 aws4 credential found!" << dendl
;
2851 err_msg
= "Missing aws4 credential";
2855 /* x-amz-signature handling */
2856 if (!part_str(parts
, "x-amz-signature",
2857 &s
->auth
.s3_postobj_creds
.signature
)) {
2858 ldpp_dout(this, 0) << "No aws4 signature found!" << dendl
;
2859 err_msg
= "Missing aws4 signature";
2863 /* x-amz-date handling */
2864 std::string received_date_str
;
2865 if (!part_str(parts
, "x-amz-date", &received_date_str
)) {
2866 ldpp_dout(this, 0) << "No aws4 date found!" << dendl
;
2867 err_msg
= "Missing aws4 date";
2873 // check that the signature matches the encoded policy
2874 if (!part_str(parts
, "AWSAccessKeyId",
2875 &s
->auth
.s3_postobj_creds
.access_key
)) {
2876 ldpp_dout(this, 0) << "No S3 aws2 access key found!" << dendl
;
2877 err_msg
= "Missing aws2 access key";
2881 if (!part_str(parts
, "signature", &s
->auth
.s3_postobj_creds
.signature
)) {
2882 ldpp_dout(this, 0) << "No aws2 signature found!" << dendl
;
2883 err_msg
= "Missing aws2 signature";
2888 if (part_str(parts
, "x-amz-security-token", &s
->auth
.s3_postobj_creds
.x_amz_security_token
)) {
2889 if (s
->auth
.s3_postobj_creds
.x_amz_security_token
.size() == 0) {
2890 err_msg
= "Invalid token";
2895 /* FIXME: this is a makeshift solution. The browser upload authentication will be
2896 * handled by an instance of rgw::auth::Completer spawned in Handler's authorize()
2898 const int ret
= rgw::auth::Strategy::apply(this, auth_registry_ptr
->get_s3_post(), s
, y
);
2902 /* Populate the owner info. */
2903 s
->owner
.set_id(s
->user
->get_id());
2904 s
->owner
.set_name(s
->user
->get_display_name());
2905 ldpp_dout(this, 20) << "Successful Signature Verification!" << dendl
;
2908 ceph::bufferlist decoded_policy
;
2910 decoded_policy
.decode_base64(s
->auth
.s3_postobj_creds
.encoded_policy
);
2911 } catch (buffer::error
& err
) {
2912 ldpp_dout(this, 0) << "failed to decode_base64 policy" << dendl
;
2913 err_msg
= "Could not decode policy";
2917 decoded_policy
.append('\0'); // NULL terminate
2918 ldpp_dout(this, 20) << "POST policy: " << decoded_policy
.c_str() << dendl
;
2921 int r
= post_policy
.from_json(decoded_policy
, err_msg
);
2923 if (err_msg
.empty()) {
2924 err_msg
= "Failed to parse policy";
2926 ldpp_dout(this, 0) << "failed to parse policy" << dendl
;
2932 post_policy
.set_var_checked("x-amz-signature");
2935 post_policy
.set_var_checked("AWSAccessKeyId");
2936 post_policy
.set_var_checked("signature");
2938 post_policy
.set_var_checked("policy");
2940 r
= post_policy
.check(&env
, err_msg
);
2942 if (err_msg
.empty()) {
2943 err_msg
= "Policy check failed";
2945 ldpp_dout(this, 0) << "policy check failed" << dendl
;
2950 ldpp_dout(this, 0) << "No attached policy found!" << dendl
;
2954 part_str(parts
, "acl", &canned_acl
);
2956 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
2957 ldpp_dout(this, 20) << "canned_acl=" << canned_acl
<< dendl
;
2958 if (s3policy
.create_canned(s
->owner
, s
->bucket_owner
, canned_acl
) < 0) {
2959 err_msg
= "Bad canned ACLs";
2968 int RGWPostObj_ObjStore_S3::complete_get_params()
2972 struct post_form_part part
;
2973 int r
= read_form_part_header(&part
, done
);
2978 ceph::bufferlist part_data
;
2980 uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
2981 r
= read_data(part
.data
, chunk_size
, boundary
, done
);
2982 if (r
< 0 || !boundary
) {
2986 /* Just reading the data but not storing any results of that. */
2992 int RGWPostObj_ObjStore_S3::get_data(ceph::bufferlist
& bl
, bool& again
)
2997 const uint64_t chunk_size
= s
->cct
->_conf
->rgw_max_chunk_size
;
2998 int r
= read_data(bl
, chunk_size
, boundary
, done
);
3005 /* Reached end of data, let's drain the rest of the params */
3006 r
= complete_get_params();
3017 void RGWPostObj_ObjStore_S3::send_response()
3019 if (op_ret
== 0 && parts
.count("success_action_redirect")) {
3022 part_str(parts
, "success_action_redirect", &redirect
);
3027 string etag_str
= "\"";
3029 etag_str
.append(etag
);
3030 etag_str
.append("\"");
3034 url_encode(s
->bucket_tenant
, tenant
); /* surely overkill, but cheap */
3035 url_encode(s
->bucket_name
, bucket
);
3036 url_encode(s
->object
->get_name(), key
);
3037 url_encode(etag_str
, etag_url
);
3039 if (!s
->bucket_tenant
.empty()) {
3041 * What we really would like is to quaily the bucket name, so
3042 * that the client could simply copy it and paste into next request.
3043 * Unfortunately, in S3 we cannot know if the client will decide
3044 * to come through DNS, with "bucket.tenant" sytanx, or through
3045 * URL with "tenant\bucket" syntax. Therefore, we provide the
3046 * tenant separately.
3048 redirect
.append("?tenant=");
3049 redirect
.append(tenant
);
3050 redirect
.append("&bucket=");
3051 redirect
.append(bucket
);
3053 redirect
.append("?bucket=");
3054 redirect
.append(bucket
);
3056 redirect
.append("&key=");
3057 redirect
.append(key
);
3058 redirect
.append("&etag=");
3059 redirect
.append(etag_url
);
3061 int r
= check_utf8(redirect
.c_str(), redirect
.size());
3066 dump_redirect(s
, redirect
);
3067 op_ret
= STATUS_REDIRECT
;
3068 } else if (op_ret
== 0 && parts
.count("success_action_status")) {
3069 string status_string
;
3070 uint32_t status_int
;
3072 part_str(parts
, "success_action_status", &status_string
);
3074 int r
= stringtoul(status_string
, &status_int
);
3080 switch (status_int
) {
3084 op_ret
= STATUS_CREATED
;
3087 op_ret
= STATUS_NO_CONTENT
;
3090 } else if (! op_ret
) {
3091 op_ret
= STATUS_NO_CONTENT
;
3095 if (op_ret
== STATUS_CREATED
) {
3096 for (auto &it
: crypt_http_responses
)
3097 dump_header(s
, it
.first
, it
.second
);
3098 s
->formatter
->open_object_section("PostResponse");
3099 std::string base_uri
= compute_domain_uri(s
);
3100 if (!s
->bucket_tenant
.empty()){
3101 s
->formatter
->dump_format("Location", "%s/%s:%s/%s",
3103 url_encode(s
->bucket_tenant
).c_str(),
3104 url_encode(s
->bucket_name
).c_str(),
3105 url_encode(s
->object
->get_name()).c_str());
3106 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3108 s
->formatter
->dump_format("Location", "%s/%s/%s",
3110 url_encode(s
->bucket_name
).c_str(),
3111 url_encode(s
->object
->get_name()).c_str());
3113 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3114 s
->formatter
->dump_string("Key", s
->object
->get_name());
3115 s
->formatter
->dump_string("ETag", etag
);
3116 s
->formatter
->close_section();
3118 s
->err
.message
= err_msg
;
3119 set_req_state_err(s
, op_ret
);
3122 dump_content_length(s
, s
->formatter
->get_len());
3124 end_header(s
, this);
3125 if (op_ret
!= STATUS_CREATED
)
3128 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3131 int RGWPostObj_ObjStore_S3::get_encrypt_filter(
3132 std::unique_ptr
<rgw::sal::DataProcessor
> *filter
,
3133 rgw::sal::DataProcessor
*cb
)
3135 std::unique_ptr
<BlockCrypt
> block_crypt
;
3136 int res
= rgw_s3_prepare_encrypt(s
, attrs
, &parts
, &block_crypt
,
3137 crypt_http_responses
);
3138 if (res
== 0 && block_crypt
!= nullptr) {
3139 filter
->reset(new RGWPutObj_BlockEncrypt(s
, s
->cct
, cb
, std::move(block_crypt
)));
3144 int RGWDeleteObj_ObjStore_S3::get_params(optional_yield y
)
3146 const char *if_unmod
= s
->info
.env
->get("HTTP_X_AMZ_DELETE_IF_UNMODIFIED_SINCE");
3148 if (s
->system_request
) {
3149 s
->info
.args
.get_bool(RGW_SYS_PARAM_PREFIX
"no-precondition-error", &no_precondition_error
, false);
3153 std::string if_unmod_decoded
= url_decode(if_unmod
);
3156 if (utime_t::parse_date(if_unmod_decoded
, &epoch
, &nsec
) < 0) {
3157 ldpp_dout(this, 10) << "failed to parse time: " << if_unmod_decoded
<< dendl
;
3160 unmod_since
= utime_t(epoch
, nsec
).to_real_time();
3163 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
3164 if (bypass_gov_header
) {
3165 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
3166 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
3172 void RGWDeleteObj_ObjStore_S3::send_response()
3178 r
= STATUS_NO_CONTENT
;
3180 set_req_state_err(s
, r
);
3182 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
3183 if (delete_marker
) {
3184 dump_header(s
, "x-amz-delete-marker", "true");
3186 end_header(s
, this);
3189 int RGWCopyObj_ObjStore_S3::init_dest_policy()
3191 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3193 /* build a policy for the target object */
3194 int r
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
3198 dest_policy
= s3policy
;
3203 int RGWCopyObj_ObjStore_S3::get_params(optional_yield y
)
3205 //handle object lock
3206 auto obj_lock_mode_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_MODE");
3207 auto obj_lock_date_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_RETAIN_UNTIL_DATE");
3208 auto obj_legal_hold_str
= s
->info
.env
->get("HTTP_X_AMZ_OBJECT_LOCK_LEGAL_HOLD");
3209 if (obj_lock_mode_str
&& obj_lock_date_str
) {
3210 boost::optional
<ceph::real_time
> date
= ceph::from_iso_8601(obj_lock_date_str
);
3211 if (boost::none
== date
|| ceph::real_clock::to_time_t(*date
) <= ceph_clock_now()) {
3212 s
->err
.message
= "invalid x-amz-object-lock-retain-until-date value";
3213 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3216 if (strcmp(obj_lock_mode_str
, "GOVERNANCE") != 0 && strcmp(obj_lock_mode_str
, "COMPLIANCE") != 0) {
3217 s
->err
.message
= "invalid x-amz-object-lock-mode value";
3218 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3221 obj_retention
= new RGWObjectRetention(obj_lock_mode_str
, *date
);
3222 } else if (obj_lock_mode_str
|| obj_lock_date_str
) {
3223 s
->err
.message
= "need both x-amz-object-lock-mode and x-amz-object-lock-retain-until-date ";
3224 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3227 if (obj_legal_hold_str
) {
3228 if (strcmp(obj_legal_hold_str
, "ON") != 0 && strcmp(obj_legal_hold_str
, "OFF") != 0) {
3229 s
->err
.message
= "invalid x-amz-object-lock-legal-hold value";
3230 ldpp_dout(this,0) << s
->err
.message
<< dendl
;
3233 obj_legal_hold
= new RGWObjectLegalHold(obj_legal_hold_str
);
3236 if_mod
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_MODIFIED_SINCE");
3237 if_unmod
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_UNMODIFIED_SINCE");
3238 if_match
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_MATCH");
3239 if_nomatch
= s
->info
.env
->get("HTTP_X_AMZ_COPY_IF_NONE_MATCH");
3241 src_tenant_name
= s
->src_tenant_name
;
3242 src_bucket_name
= s
->src_bucket_name
;
3243 dest_tenant_name
= s
->bucket
->get_tenant();
3244 dest_bucket_name
= s
->bucket
->get_name();
3245 dest_obj_name
= s
->object
->get_name();
3247 if (s
->system_request
) {
3248 source_zone
= s
->info
.args
.get(RGW_SYS_PARAM_PREFIX
"source-zone");
3249 s
->info
.args
.get_bool(RGW_SYS_PARAM_PREFIX
"copy-if-newer", ©_if_newer
, false);
3252 copy_source
= s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE");
3253 auto tmp_md_d
= s
->info
.env
->get("HTTP_X_AMZ_METADATA_DIRECTIVE");
3255 if (strcasecmp(tmp_md_d
, "COPY") == 0) {
3256 attrs_mod
= rgw::sal::ATTRSMOD_NONE
;
3257 } else if (strcasecmp(tmp_md_d
, "REPLACE") == 0) {
3258 attrs_mod
= rgw::sal::ATTRSMOD_REPLACE
;
3259 } else if (!source_zone
.empty()) {
3260 attrs_mod
= rgw::sal::ATTRSMOD_NONE
; // default for intra-zone_group copy
3262 s
->err
.message
= "Unknown metadata directive.";
3263 ldpp_dout(this, 0) << s
->err
.message
<< dendl
;
3266 md_directive
= tmp_md_d
;
3269 if (source_zone
.empty() &&
3270 (dest_tenant_name
.compare(src_tenant_name
) == 0) &&
3271 (dest_bucket_name
.compare(src_bucket_name
) == 0) &&
3272 (dest_obj_name
.compare(s
->src_object
->get_name()) == 0) &&
3273 s
->src_object
->get_instance().empty() &&
3274 (attrs_mod
!= rgw::sal::ATTRSMOD_REPLACE
)) {
3275 need_to_check_storage_class
= true;
3281 int RGWCopyObj_ObjStore_S3::check_storage_class(const rgw_placement_rule
& src_placement
)
3283 if (src_placement
== s
->dest_placement
) {
3284 /* can only copy object into itself if replacing attrs */
3285 s
->err
.message
= "This copy request is illegal because it is trying to copy "
3286 "an object to itself without changing the object's metadata, "
3287 "storage class, website redirect location or encryption attributes.";
3288 ldpp_dout(this, 0) << s
->err
.message
<< dendl
;
3289 return -ERR_INVALID_REQUEST
;
3294 void RGWCopyObj_ObjStore_S3::send_partial_response(off_t ofs
)
3296 if (! sent_header
) {
3298 set_req_state_err(s
, op_ret
);
3301 // Explicitly use chunked transfer encoding so that we can stream the result
3302 // to the user without having to wait for the full length of it.
3303 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3306 s
->formatter
->open_object_section_in_ns("CopyObjectResult", XMLNS_AWS_S3
);
3310 /* Send progress field. Note that this diverge from the original S3
3311 * spec. We do this in order to keep connection alive.
3313 s
->formatter
->dump_int("Progress", (uint64_t)ofs
);
3315 rgw_flush_formatter(s
, s
->formatter
);
3318 void RGWCopyObj_ObjStore_S3::send_response()
3321 send_partial_response(0);
3324 dump_time(s
, "LastModified", mtime
);
3325 if (!etag
.empty()) {
3326 s
->formatter
->dump_string("ETag", std::move(etag
));
3328 s
->formatter
->close_section();
3329 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3333 void RGWGetACLs_ObjStore_S3::send_response()
3336 set_req_state_err(s
, op_ret
);
3338 end_header(s
, this, "application/xml");
3340 rgw_flush_formatter(s
, s
->formatter
);
3344 int RGWPutACLs_ObjStore_S3::get_params(optional_yield y
)
3346 int ret
= RGWPutACLs_ObjStore::get_params(y
);
3348 const int ret_auth
= do_aws4_auth_completion();
3353 /* a request body is not required an S3 PutACLs request--n.b.,
3354 * s->length is non-null iff a content length was parsed (the
3355 * ACP or canned ACL could be in any of 3 headers, don't worry
3356 * about that here) */
3357 if ((ret
== -ERR_LENGTH_REQUIRED
) &&
3365 int RGWPutACLs_ObjStore_S3::get_policy_from_state(rgw::sal::Store
* store
,
3366 struct req_state
*s
,
3369 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3371 // bucket-* canned acls do not apply to bucket
3372 if (rgw::sal::Object::empty(s
->object
.get())) {
3373 if (s
->canned_acl
.find("bucket") != string::npos
)
3374 s
->canned_acl
.clear();
3377 int r
= create_s3_policy(s
, store
, s3policy
, owner
);
3381 s3policy
.to_xml(ss
);
3386 void RGWPutACLs_ObjStore_S3::send_response()
3389 set_req_state_err(s
, op_ret
);
3391 end_header(s
, this, "application/xml");
3395 void RGWGetLC_ObjStore_S3::execute(optional_yield y
)
3397 config
.set_ctx(s
->cct
);
3399 map
<string
, bufferlist
>::iterator aiter
= s
->bucket_attrs
.find(RGW_ATTR_LC
);
3400 if (aiter
== s
->bucket_attrs
.end()) {
3405 bufferlist::const_iterator iter
{&aiter
->second
};
3407 config
.decode(iter
);
3408 } catch (const buffer::error
& e
) {
3409 ldpp_dout(this, 0) << __func__
<< "decode life cycle config failed" << dendl
;
3415 void RGWGetLC_ObjStore_S3::send_response()
3418 if (op_ret
== -ENOENT
) {
3419 set_req_state_err(s
, ERR_NO_SUCH_LC
);
3421 set_req_state_err(s
, op_ret
);
3425 end_header(s
, this, "application/xml");
3431 encode_xml("LifecycleConfiguration", XMLNS_AWS_S3
, config
, s
->formatter
);
3432 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3435 void RGWPutLC_ObjStore_S3::send_response()
3438 set_req_state_err(s
, op_ret
);
3440 end_header(s
, this, "application/xml");
3444 void RGWDeleteLC_ObjStore_S3::send_response()
3447 op_ret
= STATUS_NO_CONTENT
;
3449 set_req_state_err(s
, op_ret
);
3452 end_header(s
, this, "application/xml");
3456 void RGWGetCORS_ObjStore_S3::send_response()
3459 if (op_ret
== -ENOENT
)
3460 set_req_state_err(s
, ERR_NO_SUCH_CORS_CONFIGURATION
);
3462 set_req_state_err(s
, op_ret
);
3465 end_header(s
, NULL
, "application/xml");
3469 RGWCORSConfiguration_S3
*s3cors
=
3470 static_cast<RGWCORSConfiguration_S3
*>(&bucket_cors
);
3479 int RGWPutCORS_ObjStore_S3::get_params(optional_yield y
)
3481 RGWCORSXMLParser_S3
parser(this, s
->cct
);
3482 RGWCORSConfiguration_S3
*cors_config
;
3484 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
3488 std::tie(r
, data
) = read_all_input(s
, max_size
, false);
3493 if (!parser
.init()) {
3497 char* buf
= data
.c_str();
3498 if (!buf
|| !parser
.parse(buf
, data
.length(), 1)) {
3499 return -ERR_MALFORMED_XML
;
3502 static_cast<RGWCORSConfiguration_S3
*>(parser
.find_first(
3503 "CORSConfiguration"));
3505 return -ERR_MALFORMED_XML
;
3508 #define CORS_RULES_MAX_NUM 100
3509 int max_num
= s
->cct
->_conf
->rgw_cors_rules_max_num
;
3511 max_num
= CORS_RULES_MAX_NUM
;
3513 int cors_rules_num
= cors_config
->get_rules().size();
3514 if (cors_rules_num
> max_num
) {
3515 ldpp_dout(this, 4) << "An cors config can have up to "
3517 << " rules, request cors rules num: "
3518 << cors_rules_num
<< dendl
;
3519 op_ret
= -ERR_INVALID_CORS_RULES_ERROR
;
3520 s
->err
.message
= "The number of CORS rules should not exceed allowed limit of "
3521 + std::to_string(max_num
) + " rules.";
3522 return -ERR_INVALID_REQUEST
;
3525 // forward bucket cors requests to meta master zone
3526 if (!store
->is_meta_master()) {
3527 /* only need to keep this data around if we're not meta master */
3528 in_data
.append(data
);
3531 if (s
->cct
->_conf
->subsys
.should_gather
<ceph_subsys_rgw
, 15>()) {
3532 ldpp_dout(this, 15) << "CORSConfiguration";
3533 cors_config
->to_xml(*_dout
);
3537 cors_config
->encode(cors_bl
);
3542 void RGWPutCORS_ObjStore_S3::send_response()
3545 set_req_state_err(s
, op_ret
);
3547 end_header(s
, NULL
, "application/xml");
3551 void RGWDeleteCORS_ObjStore_S3::send_response()
3554 if (!r
|| r
== -ENOENT
)
3555 r
= STATUS_NO_CONTENT
;
3557 set_req_state_err(s
, r
);
3559 end_header(s
, NULL
);
3562 void RGWOptionsCORS_ObjStore_S3::send_response()
3564 string hdrs
, exp_hdrs
;
3565 uint32_t max_age
= CORS_MAX_AGE_INVALID
;
3566 /*EACCES means, there is no CORS registered yet for the bucket
3567 *ENOENT means, there is no match of the Origin in the list of CORSRule
3569 if (op_ret
== -ENOENT
)
3572 set_req_state_err(s
, op_ret
);
3574 end_header(s
, NULL
);
3577 get_response_params(hdrs
, exp_hdrs
, &max_age
);
3580 dump_access_control(s
, origin
, req_meth
, hdrs
.c_str(), exp_hdrs
.c_str(),
3582 end_header(s
, NULL
);
3585 void RGWPutBucketEncryption_ObjStore_S3::send_response()
3588 set_req_state_err(s
, op_ret
);
3594 void RGWGetBucketEncryption_ObjStore_S3::send_response()
3597 if (op_ret
== -ENOENT
)
3598 set_req_state_err(s
, ERR_NO_SUCH_BUCKET_ENCRYPTION_CONFIGURATION
);
3600 set_req_state_err(s
, op_ret
);
3604 end_header(s
, this, "application/xml");
3608 encode_xml("ServerSideEncryptionConfiguration", bucket_encryption_conf
, s
->formatter
);
3609 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3613 void RGWDeleteBucketEncryption_ObjStore_S3::send_response()
3616 op_ret
= STATUS_NO_CONTENT
;
3619 set_req_state_err(s
, op_ret
);
3624 void RGWGetRequestPayment_ObjStore_S3::send_response()
3627 end_header(s
, this, "application/xml");
3630 s
->formatter
->open_object_section_in_ns("RequestPaymentConfiguration", XMLNS_AWS_S3
);
3631 const char *payer
= requester_pays
? "Requester" : "BucketOwner";
3632 s
->formatter
->dump_string("Payer", payer
);
3633 s
->formatter
->close_section();
3634 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3637 class RGWSetRequestPaymentParser
: public RGWXMLParser
3639 XMLObj
*alloc_obj(const char *el
) override
{
3644 RGWSetRequestPaymentParser() {}
3645 ~RGWSetRequestPaymentParser() override
{}
3647 int get_request_payment_payer(bool *requester_pays
) {
3648 XMLObj
*config
= find_first("RequestPaymentConfiguration");
3652 *requester_pays
= false;
3654 XMLObj
*field
= config
->find_first("Payer");
3658 auto& s
= field
->get_data();
3660 if (stringcasecmp(s
, "Requester") == 0) {
3661 *requester_pays
= true;
3662 } else if (stringcasecmp(s
, "BucketOwner") != 0) {
3670 int RGWSetRequestPayment_ObjStore_S3::get_params(optional_yield y
)
3672 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
3675 std::tie(r
, in_data
) = read_all_input(s
, max_size
, false);
3682 RGWSetRequestPaymentParser parser
;
3684 if (!parser
.init()) {
3685 ldpp_dout(this, 0) << "ERROR: failed to initialize parser" << dendl
;
3689 char* buf
= in_data
.c_str();
3690 if (!parser
.parse(buf
, in_data
.length(), 1)) {
3691 ldpp_dout(this, 10) << "failed to parse data: " << buf
<< dendl
;
3695 return parser
.get_request_payment_payer(&requester_pays
);
3698 void RGWSetRequestPayment_ObjStore_S3::send_response()
3701 set_req_state_err(s
, op_ret
);
3706 int RGWInitMultipart_ObjStore_S3::get_params(optional_yield y
)
3708 RGWAccessControlPolicy_S3
s3policy(s
->cct
);
3709 op_ret
= create_s3_policy(s
, store
, s3policy
, s
->owner
);
3718 void RGWInitMultipart_ObjStore_S3::send_response()
3721 set_req_state_err(s
, op_ret
);
3723 for (auto &it
: crypt_http_responses
)
3724 dump_header(s
, it
.first
, it
.second
);
3725 ceph::real_time abort_date
;
3727 bool exist_multipart_abort
= get_s3_multipart_abort_header(s
, mtime
, abort_date
, rule_id
);
3728 if (exist_multipart_abort
) {
3729 dump_time_header(s
, "x-amz-abort-date", abort_date
);
3730 dump_header_if_nonempty(s
, "x-amz-abort-rule-id", rule_id
);
3732 end_header(s
, this, "application/xml");
3735 s
->formatter
->open_object_section_in_ns("InitiateMultipartUploadResult", XMLNS_AWS_S3
);
3736 if (!s
->bucket_tenant
.empty())
3737 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3738 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3739 s
->formatter
->dump_string("Key", s
->object
->get_name());
3740 s
->formatter
->dump_string("UploadId", upload_id
);
3741 s
->formatter
->close_section();
3742 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3746 int RGWInitMultipart_ObjStore_S3::prepare_encryption(map
<string
, bufferlist
>& attrs
)
3749 res
= rgw_s3_prepare_encrypt(s
, attrs
, nullptr, nullptr, crypt_http_responses
);
3753 int RGWCompleteMultipart_ObjStore_S3::get_params(optional_yield y
)
3755 int ret
= RGWCompleteMultipart_ObjStore::get_params(y
);
3762 return do_aws4_auth_completion();
3765 void RGWCompleteMultipart_ObjStore_S3::send_response()
3768 set_req_state_err(s
, op_ret
);
3770 dump_header_if_nonempty(s
, "x-amz-version-id", version_id
);
3771 end_header(s
, this, "application/xml");
3774 s
->formatter
->open_object_section_in_ns("CompleteMultipartUploadResult", XMLNS_AWS_S3
);
3775 std::string base_uri
= compute_domain_uri(s
);
3776 if (!s
->bucket_tenant
.empty()) {
3777 s
->formatter
->dump_format("Location", "%s/%s:%s/%s",
3779 s
->bucket_tenant
.c_str(),
3780 s
->bucket_name
.c_str(),
3781 s
->object
->get_name().c_str()
3783 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3785 s
->formatter
->dump_format("Location", "%s/%s/%s",
3787 s
->bucket_name
.c_str(),
3788 s
->object
->get_name().c_str()
3791 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3792 s
->formatter
->dump_string("Key", s
->object
->get_name());
3793 s
->formatter
->dump_string("ETag", etag
);
3794 s
->formatter
->close_section();
3795 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3799 void RGWAbortMultipart_ObjStore_S3::send_response()
3803 r
= STATUS_NO_CONTENT
;
3805 set_req_state_err(s
, r
);
3807 end_header(s
, this);
3810 void RGWListMultipart_ObjStore_S3::send_response()
3813 set_req_state_err(s
, op_ret
);
3815 // Explicitly use chunked transfer encoding so that we can stream the result
3816 // to the user without having to wait for the full length of it.
3817 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3821 s
->formatter
->open_object_section_in_ns("ListPartsResult", XMLNS_AWS_S3
);
3822 map
<uint32_t, std::unique_ptr
<rgw::sal::MultipartPart
>>::iterator iter
;
3823 map
<uint32_t, std::unique_ptr
<rgw::sal::MultipartPart
>>::reverse_iterator test_iter
;
3826 iter
= upload
->get_parts().begin();
3827 test_iter
= upload
->get_parts().rbegin();
3828 if (test_iter
!= upload
->get_parts().rend()) {
3829 cur_max
= test_iter
->first
;
3831 if (!s
->bucket_tenant
.empty())
3832 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3833 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3834 s
->formatter
->dump_string("Key", s
->object
->get_name());
3835 s
->formatter
->dump_string("UploadId", upload_id
);
3836 s
->formatter
->dump_string("StorageClass", placement
->get_storage_class());
3837 s
->formatter
->dump_int("PartNumberMarker", marker
);
3838 s
->formatter
->dump_int("NextPartNumberMarker", cur_max
);
3839 s
->formatter
->dump_int("MaxParts", max_parts
);
3840 s
->formatter
->dump_string("IsTruncated", (truncated
? "true" : "false"));
3842 ACLOwner
& owner
= policy
.get_owner();
3843 dump_owner(s
, owner
.get_id(), owner
.get_display_name());
3845 for (; iter
!= upload
->get_parts().end(); ++iter
) {
3846 rgw::sal::MultipartPart
* part
= iter
->second
.get();
3848 s
->formatter
->open_object_section("Part");
3850 dump_time(s
, "LastModified", part
->get_mtime());
3852 s
->formatter
->dump_unsigned("PartNumber", part
->get_num());
3853 s
->formatter
->dump_format("ETag", "\"%s\"", part
->get_etag().c_str());
3854 s
->formatter
->dump_unsigned("Size", part
->get_size());
3855 s
->formatter
->close_section();
3857 s
->formatter
->close_section();
3858 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3862 void RGWListBucketMultiparts_ObjStore_S3::send_response()
3865 set_req_state_err(s
, op_ret
);
3868 // Explicitly use chunked transfer encoding so that we can stream the result
3869 // to the user without having to wait for the full length of it.
3870 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3875 s
->formatter
->open_object_section_in_ns("ListMultipartUploadsResult", XMLNS_AWS_S3
);
3876 if (!s
->bucket_tenant
.empty())
3877 s
->formatter
->dump_string("Tenant", s
->bucket_tenant
);
3878 s
->formatter
->dump_string("Bucket", s
->bucket_name
);
3879 if (!prefix
.empty())
3880 s
->formatter
->dump_string("Prefix", prefix
);
3881 if (!marker_key
.empty())
3882 s
->formatter
->dump_string("KeyMarker", marker_key
);
3883 if (!marker_upload_id
.empty())
3884 s
->formatter
->dump_string("UploadIdMarker", marker_upload_id
);
3885 if (!next_marker_key
.empty())
3886 s
->formatter
->dump_string("NextKeyMarker", next_marker_key
);
3887 if (!next_marker_upload_id
.empty())
3888 s
->formatter
->dump_string("NextUploadIdMarker", next_marker_upload_id
);
3889 s
->formatter
->dump_int("MaxUploads", max_uploads
);
3890 if (!delimiter
.empty())
3891 s
->formatter
->dump_string("Delimiter", delimiter
);
3892 s
->formatter
->dump_string("IsTruncated", (is_truncated
? "true" : "false"));
3895 vector
<std::unique_ptr
<rgw::sal::MultipartUpload
>>::iterator iter
;
3896 for (iter
= uploads
.begin(); iter
!= uploads
.end(); ++iter
) {
3897 rgw::sal::MultipartUpload
* upload
= iter
->get();
3898 s
->formatter
->open_array_section("Upload");
3900 s
->formatter
->dump_string("Key", url_encode(upload
->get_key(), false));
3902 s
->formatter
->dump_string("Key", upload
->get_key());
3904 s
->formatter
->dump_string("UploadId", upload
->get_upload_id());
3905 const ACLOwner
& owner
= upload
->get_owner();
3906 dump_owner(s
, owner
.get_id(), owner
.get_display_name(), "Initiator");
3907 dump_owner(s
, owner
.get_id(), owner
.get_display_name()); // Owner
3908 s
->formatter
->dump_string("StorageClass", "STANDARD");
3909 dump_time(s
, "Initiated", upload
->get_mtime());
3910 s
->formatter
->close_section();
3912 if (!common_prefixes
.empty()) {
3913 s
->formatter
->open_array_section("CommonPrefixes");
3914 for (const auto& kv
: common_prefixes
) {
3916 s
->formatter
->dump_string("Prefix", url_encode(kv
.first
, false));
3918 s
->formatter
->dump_string("Prefix", kv
.first
);
3921 s
->formatter
->close_section();
3924 s
->formatter
->close_section();
3925 rgw_flush_formatter_and_reset(s
, s
->formatter
);
3928 int RGWDeleteMultiObj_ObjStore_S3::get_params(optional_yield y
)
3930 int ret
= RGWDeleteMultiObj_ObjStore::get_params(y
);
3935 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
3936 if (bypass_gov_header
) {
3937 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
3938 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
3941 return do_aws4_auth_completion();
3944 void RGWDeleteMultiObj_ObjStore_S3::send_status()
3946 if (! status_dumped
) {
3948 set_req_state_err(s
, op_ret
);
3950 status_dumped
= true;
3954 void RGWDeleteMultiObj_ObjStore_S3::begin_response()
3957 if (!status_dumped
) {
3962 // Explicitly use chunked transfer encoding so that we can stream the result
3963 // to the user without having to wait for the full length of it.
3964 end_header(s
, this, "application/xml", CHUNKED_TRANSFER_ENCODING
);
3965 s
->formatter
->open_object_section_in_ns("DeleteResult", XMLNS_AWS_S3
);
3967 rgw_flush_formatter(s
, s
->formatter
);
3970 void RGWDeleteMultiObj_ObjStore_S3::send_partial_response(rgw_obj_key
& key
,
3972 const string
& marker_version_id
, int ret
)
3975 if (ret
== 0 && !quiet
) {
3976 s
->formatter
->open_object_section("Deleted");
3977 s
->formatter
->dump_string("Key", key
.name
);
3978 if (!key
.instance
.empty()) {
3979 s
->formatter
->dump_string("VersionId", key
.instance
);
3981 if (delete_marker
) {
3982 s
->formatter
->dump_bool("DeleteMarker", true);
3983 s
->formatter
->dump_string("DeleteMarkerVersionId", marker_version_id
);
3985 s
->formatter
->close_section();
3986 } else if (ret
< 0) {
3987 struct rgw_http_error r
;
3990 s
->formatter
->open_object_section("Error");
3993 rgw_get_errno_s3(&r
, err_no
);
3995 s
->formatter
->dump_string("Key", key
.name
);
3996 s
->formatter
->dump_string("VersionId", key
.instance
);
3997 s
->formatter
->dump_string("Code", r
.s3_code
);
3998 s
->formatter
->dump_string("Message", r
.s3_code
);
3999 s
->formatter
->close_section();
4002 rgw_flush_formatter(s
, s
->formatter
);
4006 void RGWDeleteMultiObj_ObjStore_S3::end_response()
4009 s
->formatter
->close_section();
4010 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4013 void RGWGetObjLayout_ObjStore_S3::send_response()
4016 set_req_state_err(s
, op_ret
);
4018 end_header(s
, this, "application/json");
4026 f
.open_object_section("result");
4027 s
->object
->dump_obj_layout(this, s
->yield
, &f
, s
->obj_ctx
);
4029 rgw_flush_formatter(s
, &f
);
4032 int RGWConfigBucketMetaSearch_ObjStore_S3::get_params(optional_yield y
)
4034 auto iter
= s
->info
.x_meta_map
.find("x-amz-meta-search");
4035 if (iter
== s
->info
.x_meta_map
.end()) {
4036 s
->err
.message
= "X-Rgw-Meta-Search header not provided";
4037 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4041 list
<string
> expressions
;
4042 get_str_list(iter
->second
, ",", expressions
);
4044 for (auto& expression
: expressions
) {
4045 vector
<string
> args
;
4046 get_str_vec(expression
, ";", args
);
4049 s
->err
.message
= "invalid empty expression";
4050 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4053 if (args
.size() > 2) {
4054 s
->err
.message
= string("invalid expression: ") + expression
;
4055 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4059 string key
= boost::algorithm::to_lower_copy(rgw_trim_whitespace(args
[0]));
4061 if (args
.size() > 1) {
4062 val
= boost::algorithm::to_lower_copy(rgw_trim_whitespace(args
[1]));
4065 if (!boost::algorithm::starts_with(key
, RGW_AMZ_META_PREFIX
)) {
4066 s
->err
.message
= string("invalid expression, key must start with '" RGW_AMZ_META_PREFIX
"' : ") + expression
;
4067 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4071 key
= key
.substr(sizeof(RGW_AMZ_META_PREFIX
) - 1);
4073 ESEntityTypeMap::EntityType entity_type
;
4075 if (val
.empty() || val
== "str" || val
== "string") {
4076 entity_type
= ESEntityTypeMap::ES_ENTITY_STR
;
4077 } else if (val
== "int" || val
== "integer") {
4078 entity_type
= ESEntityTypeMap::ES_ENTITY_INT
;
4079 } else if (val
== "date" || val
== "datetime") {
4080 entity_type
= ESEntityTypeMap::ES_ENTITY_DATE
;
4082 s
->err
.message
= string("invalid entity type: ") + val
;
4083 ldpp_dout(this, 5) << s
->err
.message
<< dendl
;
4087 mdsearch_config
[key
] = entity_type
;
4093 void RGWConfigBucketMetaSearch_ObjStore_S3::send_response()
4096 set_req_state_err(s
, op_ret
);
4098 end_header(s
, this);
4101 void RGWGetBucketMetaSearch_ObjStore_S3::send_response()
4104 set_req_state_err(s
, op_ret
);
4106 end_header(s
, NULL
, "application/xml");
4108 Formatter
*f
= s
->formatter
;
4109 f
->open_array_section("GetBucketMetaSearchResult");
4110 for (auto& e
: s
->bucket
->get_info().mdsearch_config
) {
4111 f
->open_object_section("Entry");
4112 string k
= string("x-amz-meta-") + e
.first
;
4113 f
->dump_string("Key", k
.c_str());
4116 case ESEntityTypeMap::ES_ENTITY_INT
:
4119 case ESEntityTypeMap::ES_ENTITY_DATE
:
4125 f
->dump_string("Type", type
);
4129 rgw_flush_formatter(s
, f
);
4132 void RGWDelBucketMetaSearch_ObjStore_S3::send_response()
4135 set_req_state_err(s
, op_ret
);
4137 end_header(s
, this);
4140 void RGWPutBucketObjectLock_ObjStore_S3::send_response()
4143 set_req_state_err(s
, op_ret
);
4149 void RGWGetBucketObjectLock_ObjStore_S3::send_response()
4152 set_req_state_err(s
, op_ret
);
4155 end_header(s
, this, "application/xml");
4161 encode_xml("ObjectLockConfiguration", s
->bucket
->get_info().obj_lock
, s
->formatter
);
4162 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4166 int RGWPutObjRetention_ObjStore_S3::get_params(optional_yield y
)
4168 const char *bypass_gov_header
= s
->info
.env
->get("HTTP_X_AMZ_BYPASS_GOVERNANCE_RETENTION");
4169 if (bypass_gov_header
) {
4170 std::string bypass_gov_decoded
= url_decode(bypass_gov_header
);
4171 bypass_governance_mode
= boost::algorithm::iequals(bypass_gov_decoded
, "true");
4174 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
4175 std::tie(op_ret
, data
) = read_all_input(s
, max_size
, false);
4179 void RGWPutObjRetention_ObjStore_S3::send_response()
4182 set_req_state_err(s
, op_ret
);
4188 void RGWGetObjRetention_ObjStore_S3::send_response()
4191 set_req_state_err(s
, op_ret
);
4194 end_header(s
, this, "application/xml");
4200 encode_xml("Retention", obj_retention
, s
->formatter
);
4201 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4204 void RGWPutObjLegalHold_ObjStore_S3::send_response()
4207 set_req_state_err(s
, op_ret
);
4213 void RGWGetObjLegalHold_ObjStore_S3::send_response()
4216 set_req_state_err(s
, op_ret
);
4219 end_header(s
, this, "application/xml");
4225 encode_xml("LegalHold", obj_legal_hold
, s
->formatter
);
4226 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4229 void RGWGetBucketPolicyStatus_ObjStore_S3::send_response()
4232 set_req_state_err(s
, op_ret
);
4235 end_header(s
, this, "application/xml");
4238 s
->formatter
->open_object_section_in_ns("PolicyStatus", XMLNS_AWS_S3
);
4239 // https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGETPolicyStatus.html
4240 // mentions TRUE and FALSE, but boto/aws official clients seem to want lower
4241 // case which is returned by AWS as well; so let's be bug to bug compatible
4243 s
->formatter
->dump_bool("IsPublic", isPublic
);
4244 s
->formatter
->close_section();
4245 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4249 void RGWPutBucketPublicAccessBlock_ObjStore_S3::send_response()
4252 set_req_state_err(s
, op_ret
);
4258 void RGWGetBucketPublicAccessBlock_ObjStore_S3::send_response()
4261 set_req_state_err(s
, op_ret
);
4264 end_header(s
, this, "application/xml");
4267 access_conf
.dump_xml(s
->formatter
);
4268 rgw_flush_formatter_and_reset(s
, s
->formatter
);
4271 RGWOp
*RGWHandler_REST_Service_S3::op_get()
4273 if (is_usage_op()) {
4274 return new RGWGetUsage_ObjStore_S3
;
4276 return new RGWListBuckets_ObjStore_S3
;
4280 RGWOp
*RGWHandler_REST_Service_S3::op_head()
4282 return new RGWListBuckets_ObjStore_S3
;
4285 RGWOp
*RGWHandler_REST_Service_S3::op_post()
4287 const auto max_size
= s
->cct
->_conf
->rgw_max_put_param_size
;
4291 std::tie(ret
, data
) = rgw_rest_read_all_input(s
, max_size
, false);
4296 const auto post_body
= data
.to_str();
4299 RGWHandler_REST_STS
sts_handler(auth_registry
, post_body
);
4300 sts_handler
.init(store
, s
, s
->cio
);
4301 auto op
= sts_handler
.get_op();
4308 RGWHandler_REST_IAM
iam_handler(auth_registry
, post_body
);
4309 iam_handler
.init(store
, s
, s
->cio
);
4310 auto op
= iam_handler
.get_op();
4317 RGWHandler_REST_PSTopic_AWS
topic_handler(auth_registry
, post_body
);
4318 topic_handler
.init(store
, s
, s
->cio
);
4319 auto op
= topic_handler
.get_op();
4328 RGWOp
*RGWHandler_REST_Bucket_S3::get_obj_op(bool get_data
) const
4333 s
->info
.args
.get_int("list-type", &list_type
, 1);
4334 switch (list_type
) {
4336 return new RGWListBucket_ObjStore_S3
;
4338 return new RGWListBucket_ObjStore_S3v2
;
4340 ldpp_dout(s
, 5) << __func__
<< ": unsupported list-type " << list_type
<< dendl
;
4341 return new RGWListBucket_ObjStore_S3
;
4344 return new RGWStatBucket_ObjStore_S3
;
4348 RGWOp
*RGWHandler_REST_Bucket_S3::op_get()
4350 if (s
->info
.args
.sub_resource_exists("encryption"))
4353 if (s
->info
.args
.sub_resource_exists("logging"))
4354 return new RGWGetBucketLogging_ObjStore_S3
;
4356 if (s
->info
.args
.sub_resource_exists("location"))
4357 return new RGWGetBucketLocation_ObjStore_S3
;
4359 if (s
->info
.args
.sub_resource_exists("versioning"))
4360 return new RGWGetBucketVersioning_ObjStore_S3
;
4362 if (s
->info
.args
.sub_resource_exists("website")) {
4363 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4366 return new RGWGetBucketWebsite_ObjStore_S3
;
4369 if (s
->info
.args
.exists("mdsearch")) {
4370 return new RGWGetBucketMetaSearch_ObjStore_S3
;
4374 return new RGWGetACLs_ObjStore_S3
;
4375 } else if (is_cors_op()) {
4376 return new RGWGetCORS_ObjStore_S3
;
4377 } else if (is_request_payment_op()) {
4378 return new RGWGetRequestPayment_ObjStore_S3
;
4379 } else if (s
->info
.args
.exists("uploads")) {
4380 return new RGWListBucketMultiparts_ObjStore_S3
;
4381 } else if(is_lc_op()) {
4382 return new RGWGetLC_ObjStore_S3
;
4383 } else if(is_policy_op()) {
4384 return new RGWGetBucketPolicy
;
4385 } else if (is_tagging_op()) {
4386 return new RGWGetBucketTags_ObjStore_S3
;
4387 } else if (is_object_lock_op()) {
4388 return new RGWGetBucketObjectLock_ObjStore_S3
;
4389 } else if (is_notification_op()) {
4390 return RGWHandler_REST_PSNotifs_S3::create_get_op();
4391 } else if (is_replication_op()) {
4392 return new RGWGetBucketReplication_ObjStore_S3
;
4393 } else if (is_policy_status_op()) {
4394 return new RGWGetBucketPolicyStatus_ObjStore_S3
;
4395 } else if (is_block_public_access_op()) {
4396 return new RGWGetBucketPublicAccessBlock_ObjStore_S3
;
4397 } else if (is_bucket_encryption_op()) {
4398 return new RGWGetBucketEncryption_ObjStore_S3
;
4400 return get_obj_op(true);
4403 RGWOp
*RGWHandler_REST_Bucket_S3::op_head()
4406 return new RGWGetACLs_ObjStore_S3
;
4407 } else if (s
->info
.args
.exists("uploads")) {
4408 return new RGWListBucketMultiparts_ObjStore_S3
;
4410 return get_obj_op(false);
4413 RGWOp
*RGWHandler_REST_Bucket_S3::op_put()
4415 if (s
->info
.args
.sub_resource_exists("logging") ||
4416 s
->info
.args
.sub_resource_exists("encryption"))
4418 if (s
->info
.args
.sub_resource_exists("versioning"))
4419 return new RGWSetBucketVersioning_ObjStore_S3
;
4420 if (s
->info
.args
.sub_resource_exists("website")) {
4421 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4424 return new RGWSetBucketWebsite_ObjStore_S3
;
4426 if (is_tagging_op()) {
4427 return new RGWPutBucketTags_ObjStore_S3
;
4428 } else if (is_acl_op()) {
4429 return new RGWPutACLs_ObjStore_S3
;
4430 } else if (is_cors_op()) {
4431 return new RGWPutCORS_ObjStore_S3
;
4432 } else if (is_request_payment_op()) {
4433 return new RGWSetRequestPayment_ObjStore_S3
;
4434 } else if(is_lc_op()) {
4435 return new RGWPutLC_ObjStore_S3
;
4436 } else if(is_policy_op()) {
4437 return new RGWPutBucketPolicy
;
4438 } else if (is_object_lock_op()) {
4439 return new RGWPutBucketObjectLock_ObjStore_S3
;
4440 } else if (is_notification_op()) {
4441 return RGWHandler_REST_PSNotifs_S3::create_put_op();
4442 } else if (is_replication_op()) {
4443 auto sync_policy_handler
= static_cast<rgw::sal::RadosStore
*>(store
)->svc()->zone
->get_sync_policy_handler(nullopt
);
4444 if (!sync_policy_handler
||
4445 sync_policy_handler
->is_legacy_config()) {
4449 return new RGWPutBucketReplication_ObjStore_S3
;
4450 } else if (is_block_public_access_op()) {
4451 return new RGWPutBucketPublicAccessBlock_ObjStore_S3
;
4452 } else if (is_bucket_encryption_op()) {
4453 return new RGWPutBucketEncryption_ObjStore_S3
;
4455 return new RGWCreateBucket_ObjStore_S3
;
4458 RGWOp
*RGWHandler_REST_Bucket_S3::op_delete()
4460 if (s
->info
.args
.sub_resource_exists("logging") ||
4461 s
->info
.args
.sub_resource_exists("encryption"))
4464 if (is_tagging_op()) {
4465 return new RGWDeleteBucketTags_ObjStore_S3
;
4466 } else if (is_cors_op()) {
4467 return new RGWDeleteCORS_ObjStore_S3
;
4468 } else if(is_lc_op()) {
4469 return new RGWDeleteLC_ObjStore_S3
;
4470 } else if(is_policy_op()) {
4471 return new RGWDeleteBucketPolicy
;
4472 } else if (is_notification_op()) {
4473 return RGWHandler_REST_PSNotifs_S3::create_delete_op();
4474 } else if (is_replication_op()) {
4475 return new RGWDeleteBucketReplication_ObjStore_S3
;
4476 } else if (is_block_public_access_op()) {
4477 return new RGWDeleteBucketPublicAccessBlock
;
4478 } else if (is_bucket_encryption_op()) {
4479 return new RGWDeleteBucketEncryption_ObjStore_S3
;
4482 if (s
->info
.args
.sub_resource_exists("website")) {
4483 if (!s
->cct
->_conf
->rgw_enable_static_website
) {
4486 return new RGWDeleteBucketWebsite_ObjStore_S3
;
4489 if (s
->info
.args
.exists("mdsearch")) {
4490 return new RGWDelBucketMetaSearch_ObjStore_S3
;
4493 return new RGWDeleteBucket_ObjStore_S3
;
4496 RGWOp
*RGWHandler_REST_Bucket_S3::op_post()
4498 if (s
->info
.args
.exists("delete")) {
4499 return new RGWDeleteMultiObj_ObjStore_S3
;
4502 if (s
->info
.args
.exists("mdsearch")) {
4503 return new RGWConfigBucketMetaSearch_ObjStore_S3
;
4506 return new RGWPostObj_ObjStore_S3
;
4509 RGWOp
*RGWHandler_REST_Bucket_S3::op_options()
4511 return new RGWOptionsCORS_ObjStore_S3
;
4514 RGWOp
*RGWHandler_REST_Obj_S3::get_obj_op(bool get_data
)
4516 RGWGetObj_ObjStore_S3
*get_obj_op
= new RGWGetObj_ObjStore_S3
;
4517 get_obj_op
->set_get_data(get_data
);
4521 RGWOp
*RGWHandler_REST_Obj_S3::op_get()
4524 return new RGWGetACLs_ObjStore_S3
;
4525 } else if (s
->info
.args
.exists("uploadId")) {
4526 return new RGWListMultipart_ObjStore_S3
;
4527 } else if (s
->info
.args
.exists("layout")) {
4528 return new RGWGetObjLayout_ObjStore_S3
;
4529 } else if (is_tagging_op()) {
4530 return new RGWGetObjTags_ObjStore_S3
;
4531 } else if (is_obj_retention_op()) {
4532 return new RGWGetObjRetention_ObjStore_S3
;
4533 } else if (is_obj_legal_hold_op()) {
4534 return new RGWGetObjLegalHold_ObjStore_S3
;
4536 return get_obj_op(true);
4539 RGWOp
*RGWHandler_REST_Obj_S3::op_head()
4542 return new RGWGetACLs_ObjStore_S3
;
4543 } else if (s
->info
.args
.exists("uploadId")) {
4544 return new RGWListMultipart_ObjStore_S3
;
4546 return get_obj_op(false);
4549 RGWOp
*RGWHandler_REST_Obj_S3::op_put()
4552 return new RGWPutACLs_ObjStore_S3
;
4553 } else if (is_tagging_op()) {
4554 return new RGWPutObjTags_ObjStore_S3
;
4555 } else if (is_obj_retention_op()) {
4556 return new RGWPutObjRetention_ObjStore_S3
;
4557 } else if (is_obj_legal_hold_op()) {
4558 return new RGWPutObjLegalHold_ObjStore_S3
;
4561 if (s
->init_state
.src_bucket
.empty())
4562 return new RGWPutObj_ObjStore_S3
;
4564 return new RGWCopyObj_ObjStore_S3
;
4567 RGWOp
*RGWHandler_REST_Obj_S3::op_delete()
4569 if (is_tagging_op()) {
4570 return new RGWDeleteObjTags_ObjStore_S3
;
4572 string upload_id
= s
->info
.args
.get("uploadId");
4574 if (upload_id
.empty())
4575 return new RGWDeleteObj_ObjStore_S3
;
4577 return new RGWAbortMultipart_ObjStore_S3
;
4580 RGWOp
*RGWHandler_REST_Obj_S3::op_post()
4582 if (s
->info
.args
.exists("uploadId"))
4583 return new RGWCompleteMultipart_ObjStore_S3
;
4585 if (s
->info
.args
.exists("uploads"))
4586 return new RGWInitMultipart_ObjStore_S3
;
4589 return rgw::s3select::create_s3select_op();
4591 return new RGWPostObj_ObjStore_S3
;
4594 RGWOp
*RGWHandler_REST_Obj_S3::op_options()
4596 return new RGWOptionsCORS_ObjStore_S3
;
4599 int RGWHandler_REST_S3::init_from_header(rgw::sal::Store
* store
,
4600 struct req_state
* s
,
4601 int default_formatter
,
4602 bool configurable_format
)
4607 const char *req_name
= s
->relative_uri
.c_str();
4610 if (*req_name
== '?') {
4613 p
= s
->info
.request_params
.c_str();
4616 s
->info
.args
.set(p
);
4617 s
->info
.args
.parse(s
);
4619 /* must be called after the args parsing */
4620 int ret
= allocate_formatter(s
, default_formatter
, configurable_format
);
4624 if (*req_name
!= '/')
4633 int pos
= req
.find('/');
4635 first
= req
.substr(0, pos
);
4641 * XXX The intent of the check for empty is apparently to let the bucket
4642 * name from DNS to be set ahead. However, we currently take the DNS
4643 * bucket and re-insert it into URL in rgw_rest.cc:RGWREST::preprocess().
4644 * So, this check is meaningless.
4646 * Rather than dropping this, the code needs to be changed into putting
4647 * the bucket (and its tenant) from DNS and Host: header (HTTP_HOST)
4648 * into req_status.bucket_name directly.
4650 if (s
->init_state
.url_bucket
.empty()) {
4651 // Save bucket to tide us over until token is parsed.
4652 s
->init_state
.url_bucket
= first
;
4653 string encoded_obj_str
;
4655 encoded_obj_str
= req
.substr(pos
+1);
4658 /* dang: s->bucket is never set here, since it's created with permissions.
4659 * These calls will always create an object with no bucket. */
4660 if (!encoded_obj_str
.empty()) {
4662 s
->object
= s
->bucket
->get_object(rgw_obj_key(encoded_obj_str
, s
->info
.args
.get("versionId")));
4664 s
->object
= store
->get_object(rgw_obj_key(encoded_obj_str
, s
->info
.args
.get("versionId")));
4669 s
->object
= s
->bucket
->get_object(rgw_obj_key(req_name
, s
->info
.args
.get("versionId")));
4671 s
->object
= store
->get_object(rgw_obj_key(req_name
, s
->info
.args
.get("versionId")));
4677 static int verify_mfa(rgw::sal::Store
* store
, RGWUserInfo
*user
,
4678 const string
& mfa_str
, bool *verified
, const DoutPrefixProvider
*dpp
, optional_yield y
)
4680 vector
<string
> params
;
4681 get_str_vec(mfa_str
, " ", params
);
4683 if (params
.size() != 2) {
4684 ldpp_dout(dpp
, 5) << "NOTICE: invalid mfa string provided: " << mfa_str
<< dendl
;
4688 string
& serial
= params
[0];
4689 string
& pin
= params
[1];
4691 auto i
= user
->mfa_ids
.find(serial
);
4692 if (i
== user
->mfa_ids
.end()) {
4693 ldpp_dout(dpp
, 5) << "NOTICE: user does not have mfa device with serial=" << serial
<< dendl
;
4697 int ret
= static_cast<rgw::sal::RadosStore
*>(store
)->svc()->cls
->mfa
.check_mfa(dpp
, user
->user_id
, serial
, pin
, y
);
4699 ldpp_dout(dpp
, 20) << "NOTICE: failed to check MFA, serial=" << serial
<< dendl
;
4708 int RGWHandler_REST_S3::postauth_init(optional_yield y
)
4710 struct req_init_state
*t
= &s
->init_state
;
4712 rgw_parse_url_bucket(t
->url_bucket
, s
->user
->get_tenant(),
4713 s
->bucket_tenant
, s
->bucket_name
);
4715 if (s
->auth
.identity
->get_identity_type() == TYPE_ROLE
) {
4716 s
->bucket_tenant
= s
->auth
.identity
->get_role_tenant();
4719 ldpp_dout(s
, 10) << "s->object=" << s
->object
4720 << " s->bucket=" << rgw_make_bucket_entry_name(s
->bucket_tenant
, s
->bucket_name
) << dendl
;
4723 ret
= rgw_validate_tenant_name(s
->bucket_tenant
);
4726 if (!s
->bucket_name
.empty() && !rgw::sal::Object::empty(s
->object
.get())) {
4727 ret
= validate_object_name(s
->object
->get_name());
4732 if (!t
->src_bucket
.empty()) {
4734 if (s
->auth
.identity
->get_identity_type() == TYPE_ROLE
) {
4735 auth_tenant
= s
->auth
.identity
->get_role_tenant();
4737 auth_tenant
= s
->user
->get_tenant();
4739 rgw_parse_url_bucket(t
->src_bucket
, auth_tenant
,
4740 s
->src_tenant_name
, s
->src_bucket_name
);
4741 ret
= rgw_validate_tenant_name(s
->src_tenant_name
);
4746 const char *mfa
= s
->info
.env
->get("HTTP_X_AMZ_MFA");
4748 ret
= verify_mfa(store
, &s
->user
->get_info(), string(mfa
), &s
->mfa_verified
, s
, y
);
4754 int RGWHandler_REST_S3::init(rgw::sal::Store
* store
, struct req_state
*s
,
4755 rgw::io::BasicClient
*cio
)
4761 ret
= rgw_validate_tenant_name(s
->bucket_tenant
);
4764 if (!s
->bucket_name
.empty()) {
4765 ret
= validate_object_name(s
->object
->get_name());
4770 const char *cacl
= s
->info
.env
->get("HTTP_X_AMZ_ACL");
4772 s
->canned_acl
= cacl
;
4774 s
->has_acl_header
= s
->info
.env
->exists_prefix("HTTP_X_AMZ_GRANT");
4776 const char *copy_source
= s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE");
4778 (! s
->info
.env
->get("HTTP_X_AMZ_COPY_SOURCE_RANGE")) &&
4779 (! s
->info
.args
.exists("uploadId"))) {
4782 ret
= RGWCopyObj::parse_copy_location(copy_source
,
4783 s
->init_state
.src_bucket
,
4787 ldpp_dout(s
, 0) << "failed to parse copy location" << dendl
;
4788 return -EINVAL
; // XXX why not -ERR_INVALID_BUCKET_NAME or -ERR_BAD_URL?
4790 s
->src_object
= store
->get_object(key
);
4793 const char *sc
= s
->info
.env
->get("HTTP_X_AMZ_STORAGE_CLASS");
4795 s
->info
.storage_class
= sc
;
4798 return RGWHandler_REST::init(store
, s
, cio
);
4801 int RGWHandler_REST_S3::authorize(const DoutPrefixProvider
*dpp
, optional_yield y
)
4803 if (s
->info
.args
.exists("Action") && s
->info
.args
.get("Action") == "AssumeRoleWithWebIdentity") {
4804 return RGW_Auth_STS::authorize(dpp
, store
, auth_registry
, s
, y
);
4806 return RGW_Auth_S3::authorize(dpp
, store
, auth_registry
, s
, y
);
4809 enum class AwsVersion
{
4815 enum class AwsRoute
{
4821 static inline std::pair
<AwsVersion
, AwsRoute
>
4822 discover_aws_flavour(const req_info
& info
)
4824 using rgw::auth::s3::AWS4_HMAC_SHA256_STR
;
4826 AwsVersion version
= AwsVersion::UNKNOWN
;
4827 AwsRoute route
= AwsRoute::UNKNOWN
;
4829 const char* http_auth
= info
.env
->get("HTTP_AUTHORIZATION");
4830 if (http_auth
&& http_auth
[0]) {
4831 /* Authorization in Header */
4832 route
= AwsRoute::HEADERS
;
4834 if (!strncmp(http_auth
, AWS4_HMAC_SHA256_STR
,
4835 strlen(AWS4_HMAC_SHA256_STR
))) {
4837 version
= AwsVersion::V4
;
4838 } else if (!strncmp(http_auth
, "AWS ", 4)) {
4840 version
= AwsVersion::V2
;
4843 route
= AwsRoute::QUERY_STRING
;
4845 if (info
.args
.get("x-amz-algorithm") == AWS4_HMAC_SHA256_STR
) {
4847 version
= AwsVersion::V4
;
4848 } else if (!info
.args
.get("AWSAccessKeyId").empty()) {
4850 version
= AwsVersion::V2
;
4854 return std::make_pair(version
, route
);
4858 * verify that a signed request comes from the keyholder
4859 * by checking the signature against our locally-computed version
4861 * it tries AWS v4 before AWS v2
4863 int RGW_Auth_S3::authorize(const DoutPrefixProvider
*dpp
,
4864 rgw::sal::Store
* const store
,
4865 const rgw::auth::StrategyRegistry
& auth_registry
,
4866 struct req_state
* const s
, optional_yield y
)
4869 /* neither keystone and rados enabled; warn and exit! */
4870 if (!store
->ctx()->_conf
->rgw_s3_auth_use_rados
&&
4871 !store
->ctx()->_conf
->rgw_s3_auth_use_keystone
&&
4872 !store
->ctx()->_conf
->rgw_s3_auth_use_ldap
) {
4873 ldpp_dout(dpp
, 0) << "WARNING: no authorization backend enabled! Users will never authenticate." << dendl
;
4877 const auto ret
= rgw::auth::Strategy::apply(dpp
, auth_registry
.get_s3_main(), s
, y
);
4879 /* Populate the owner info. */
4880 s
->owner
.set_id(s
->user
->get_id());
4881 s
->owner
.set_name(s
->user
->get_display_name());
4886 int RGWHandler_Auth_S3::init(rgw::sal::Store
* store
, struct req_state
*state
,
4887 rgw::io::BasicClient
*cio
)
4889 int ret
= RGWHandler_REST_S3::init_from_header(store
, state
, RGW_FORMAT_JSON
, true);
4893 return RGWHandler_REST::init(store
, state
, cio
);
4896 RGWHandler_REST
* RGWRESTMgr_S3::get_handler(rgw::sal::Store
* store
,
4897 struct req_state
* const s
,
4898 const rgw::auth::StrategyRegistry
& auth_registry
,
4899 const std::string
& frontend_prefix
)
4901 bool is_s3website
= enable_s3website
&& (s
->prot_flags
& RGW_REST_WEBSITE
);
4903 RGWHandler_REST_S3::init_from_header(store
, s
,
4904 is_s3website
? RGW_FORMAT_HTML
:
4905 RGW_FORMAT_XML
, true);
4909 RGWHandler_REST
* handler
;
4910 // TODO: Make this more readable
4912 if (s
->init_state
.url_bucket
.empty()) {
4913 handler
= new RGWHandler_REST_Service_S3Website(auth_registry
);
4914 } else if (rgw::sal::Object::empty(s
->object
.get())) {
4915 handler
= new RGWHandler_REST_Bucket_S3Website(auth_registry
);
4917 handler
= new RGWHandler_REST_Obj_S3Website(auth_registry
);
4920 if (s
->init_state
.url_bucket
.empty()) {
4921 handler
= new RGWHandler_REST_Service_S3(auth_registry
, enable_sts
, enable_iam
, enable_pubsub
);
4922 } else if (!rgw::sal::Object::empty(s
->object
.get())) {
4923 handler
= new RGWHandler_REST_Obj_S3(auth_registry
);
4924 } else if (s
->info
.args
.exist_obj_excl_sub_resource()) {
4927 handler
= new RGWHandler_REST_Bucket_S3(auth_registry
, enable_pubsub
);
4931 ldpp_dout(s
, 20) << __func__
<< " handler=" << typeid(*handler
).name()
4936 bool RGWHandler_REST_S3Website::web_dir() const {
4937 std::string subdir_name
;
4938 if (!rgw::sal::Object::empty(s
->object
.get())) {
4939 subdir_name
= url_decode(s
->object
->get_name());
4942 if (subdir_name
.empty()) {
4944 } else if (subdir_name
.back() == '/' && subdir_name
.size() > 1) {
4945 subdir_name
.pop_back();
4948 std::unique_ptr
<rgw::sal::Object
> obj
= s
->bucket
->get_object(rgw_obj_key(subdir_name
));
4950 RGWObjectCtx
& obj_ctx
= *static_cast<RGWObjectCtx
*>(s
->obj_ctx
);
4951 obj
->set_atomic(&obj_ctx
);
4952 obj
->set_prefetch_data(&obj_ctx
);
4954 RGWObjState
* state
= nullptr;
4955 if (obj
->get_obj_state(s
, &obj_ctx
, &state
, s
->yield
) < 0) {
4958 if (! state
->exists
) {
4961 return state
->exists
;
4964 int RGWHandler_REST_S3Website::init(rgw::sal::Store
* store
, req_state
*s
,
4965 rgw::io::BasicClient
* cio
)
4967 // save the original object name before retarget() replaces it with the
4968 // result of get_effective_key(). the error_handler() needs the original
4969 // object name for redirect handling
4970 if (!rgw::sal::Object::empty(s
->object
.get())) {
4971 original_object_name
= s
->object
->get_name();
4973 original_object_name
= "";
4976 return RGWHandler_REST_S3::init(store
, s
, cio
);
4979 int RGWHandler_REST_S3Website::retarget(RGWOp
* op
, RGWOp
** new_op
, optional_yield y
) {
4981 ldpp_dout(s
, 10) << __func__
<< " Starting retarget" << dendl
;
4983 if (!(s
->prot_flags
& RGW_REST_WEBSITE
))
4986 if (!s
->bucket
->get_info().has_website
) {
4987 // TODO-FUTURE: if the bucket has no WebsiteConfig, expose it here
4988 return -ERR_NO_SUCH_WEBSITE_CONFIGURATION
;
4991 rgw_obj_key new_obj
;
4993 if (!rgw::sal::Object::empty(s
->object
.get())) {
4994 key_name
= s
->object
->get_name();
4996 bool get_res
= s
->bucket
->get_info().website_conf
.get_effective_key(key_name
, &new_obj
.name
, web_dir());
4998 s
->err
.message
= "The IndexDocument Suffix is not configurated or not well formed!";
4999 ldpp_dout(s
, 5) << s
->err
.message
<< dendl
;
5003 ldpp_dout(s
, 10) << "retarget get_effective_key " << s
->object
<< " -> "
5004 << new_obj
<< dendl
;
5006 RGWBWRoutingRule rrule
;
5007 bool should_redirect
=
5008 s
->bucket
->get_info().website_conf
.should_redirect(new_obj
.name
, 0, &rrule
);
5010 if (should_redirect
) {
5011 const string
& hostname
= s
->info
.env
->get("HTTP_HOST", "");
5012 const string
& protocol
=
5013 (s
->info
.env
->get("SERVER_PORT_SECURE") ? "https" : "http");
5014 int redirect_code
= 0;
5015 rrule
.apply_rule(protocol
, hostname
, key_name
, &s
->redirect
,
5017 // APply a custom HTTP response code
5018 if (redirect_code
> 0)
5019 s
->err
.http_ret
= redirect_code
; // Apply a custom HTTP response code
5020 ldpp_dout(s
, 10) << "retarget redirect code=" << redirect_code
5021 << " proto+host:" << protocol
<< "://" << hostname
5022 << " -> " << s
->redirect
<< dendl
;
5023 return -ERR_WEBSITE_REDIRECT
;
5027 * FIXME: if s->object != new_obj, drop op and create a new op to handle
5028 * operation. Or remove this comment if it's not applicable anymore
5029 * dang: This could be problematic, since we're not actually replacing op, but
5030 * we are replacing s->object. Something might have a pointer to it.
5032 s
->object
= s
->bucket
->get_object(new_obj
);
5037 RGWOp
* RGWHandler_REST_S3Website::op_get()
5039 return get_obj_op(true);
5042 RGWOp
* RGWHandler_REST_S3Website::op_head()
5044 return get_obj_op(false);
5047 int RGWHandler_REST_S3Website::serve_errordoc(const DoutPrefixProvider
*dpp
, int http_ret
, const string
& errordoc_key
, optional_yield y
) {
5049 s
->formatter
->reset(); /* Try to throw it all away */
5051 std::shared_ptr
<RGWGetObj_ObjStore_S3Website
> getop( static_cast<RGWGetObj_ObjStore_S3Website
*>(op_get()));
5052 if (getop
.get() == NULL
) {
5053 return -1; // Trigger double error handler
5055 getop
->init(store
, s
, this);
5056 getop
->range_str
= NULL
;
5057 getop
->if_mod
= NULL
;
5058 getop
->if_unmod
= NULL
;
5059 getop
->if_match
= NULL
;
5060 getop
->if_nomatch
= NULL
;
5061 /* This is okay. It's an error, so nothing will run after this, and it can be
5062 * called by abort_early(), which can be called before s->object or s->bucket
5063 * are set up. Note, it won't have bucket. */
5064 s
->object
= store
->get_object(errordoc_key
);
5066 ret
= init_permissions(getop
.get(), y
);
5068 ldpp_dout(s
, 20) << "serve_errordoc failed, init_permissions ret=" << ret
<< dendl
;
5069 return -1; // Trigger double error handler
5072 ret
= read_permissions(getop
.get(), y
);
5074 ldpp_dout(s
, 20) << "serve_errordoc failed, read_permissions ret=" << ret
<< dendl
;
5075 return -1; // Trigger double error handler
5079 getop
->set_custom_http_response(http_ret
);
5082 ret
= getop
->init_processing(y
);
5084 ldpp_dout(s
, 20) << "serve_errordoc failed, init_processing ret=" << ret
<< dendl
;
5085 return -1; // Trigger double error handler
5088 ret
= getop
->verify_op_mask();
5090 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_op_mask ret=" << ret
<< dendl
;
5091 return -1; // Trigger double error handler
5094 ret
= getop
->verify_permission(y
);
5096 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_permission ret=" << ret
<< dendl
;
5097 return -1; // Trigger double error handler
5100 ret
= getop
->verify_params();
5102 ldpp_dout(s
, 20) << "serve_errordoc failed, verify_params ret=" << ret
<< dendl
;
5103 return -1; // Trigger double error handler
5106 // No going back now
5109 * FIXME Missing headers:
5110 * With a working errordoc, the s3 error fields are rendered as HTTP headers,
5111 * x-amz-error-code: NoSuchKey
5112 * x-amz-error-message: The specified key does not exist.
5113 * x-amz-error-detail-Key: foo
5120 int RGWHandler_REST_S3Website::error_handler(int err_no
,
5121 string
* error_content
,
5123 int new_err_no
= -1;
5124 rgw_http_errors::const_iterator r
= rgw_http_s3_errors
.find(err_no
> 0 ? err_no
: -err_no
);
5125 int http_error_code
= -1;
5127 if (r
!= rgw_http_s3_errors
.end()) {
5128 http_error_code
= r
->second
.first
;
5130 ldpp_dout(s
, 10) << "RGWHandler_REST_S3Website::error_handler err_no=" << err_no
<< " http_ret=" << http_error_code
<< dendl
;
5132 RGWBWRoutingRule rrule
;
5133 bool have_bucket
= !rgw::sal::Bucket::empty(s
->bucket
.get());
5134 bool should_redirect
= false;
5137 s
->bucket
->get_info().website_conf
.should_redirect(original_object_name
,
5138 http_error_code
, &rrule
);
5141 if (should_redirect
) {
5142 const string
& hostname
= s
->info
.env
->get("HTTP_HOST", "");
5143 const string
& protocol
=
5144 (s
->info
.env
->get("SERVER_PORT_SECURE") ? "https" : "http");
5145 int redirect_code
= 0;
5146 rrule
.apply_rule(protocol
, hostname
, original_object_name
,
5147 &s
->redirect
, &redirect_code
);
5148 // Apply a custom HTTP response code
5149 if (redirect_code
> 0)
5150 s
->err
.http_ret
= redirect_code
; // Apply a custom HTTP response code
5151 ldpp_dout(s
, 10) << "error handler redirect code=" << redirect_code
5152 << " proto+host:" << protocol
<< "://" << hostname
5153 << " -> " << s
->redirect
<< dendl
;
5154 return -ERR_WEBSITE_REDIRECT
;
5155 } else if (err_no
== -ERR_WEBSITE_REDIRECT
) {
5156 // Do nothing here, this redirect will be handled in abort_early's ERR_WEBSITE_REDIRECT block
5157 // Do NOT fire the ErrorDoc handler
5158 } else if (have_bucket
&& !s
->bucket
->get_info().website_conf
.error_doc
.empty()) {
5159 /* This serves an entire page!
5160 On success, it will return zero, and no further content should be sent to the socket
5161 On failure, we need the double-error handler
5163 new_err_no
= RGWHandler_REST_S3Website::serve_errordoc(s
, http_error_code
, s
->bucket
->get_info().website_conf
.error_doc
, y
);
5164 if (new_err_no
!= -1) {
5165 err_no
= new_err_no
;
5168 ldpp_dout(s
, 20) << "No special error handling today!" << dendl
;
5174 RGWOp
* RGWHandler_REST_Obj_S3Website::get_obj_op(bool get_data
)
5176 /** If we are in website mode, then it is explicitly impossible to run GET or
5177 * HEAD on the actual directory. We must convert the request to run on the
5178 * suffix object instead!
5180 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5181 op
->set_get_data(get_data
);
5185 RGWOp
* RGWHandler_REST_Bucket_S3Website::get_obj_op(bool get_data
)
5187 /** If we are in website mode, then it is explicitly impossible to run GET or
5188 * HEAD on the actual directory. We must convert the request to run on the
5189 * suffix object instead!
5191 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5192 op
->set_get_data(get_data
);
5196 RGWOp
* RGWHandler_REST_Service_S3Website::get_obj_op(bool get_data
)
5198 /** If we are in website mode, then it is explicitly impossible to run GET or
5199 * HEAD on the actual directory. We must convert the request to run on the
5200 * suffix object instead!
5202 RGWGetObj_ObjStore_S3Website
* op
= new RGWGetObj_ObjStore_S3Website
;
5203 op
->set_get_data(get_data
);
5208 namespace rgw::auth::s3
{
5210 static rgw::auth::Completer::cmplptr_t
5211 null_completer_factory(const boost::optional
<std::string
>& secret_key
)
5217 AWSEngine::VersionAbstractor::auth_data_t
5218 AWSGeneralAbstractor::get_auth_data(const req_state
* const s
) const
5222 std::tie(version
, route
) = discover_aws_flavour(s
->info
);
5224 if (version
== AwsVersion::V2
) {
5225 return get_auth_data_v2(s
);
5226 } else if (version
== AwsVersion::V4
) {
5227 return get_auth_data_v4(s
, route
== AwsRoute::QUERY_STRING
);
5229 /* FIXME(rzarzynski): handle anon user. */
5234 boost::optional
<std::string
>
5235 AWSGeneralAbstractor::get_v4_canonical_headers(
5236 const req_info
& info
,
5237 const std::string_view
& signedheaders
,
5238 const bool using_qs
) const
5240 return rgw::auth::s3::get_v4_canonical_headers(info
, signedheaders
,
5244 AWSSignerV4::prepare_result_t
5245 AWSSignerV4::prepare(const DoutPrefixProvider
*dpp
,
5246 const std::string
& access_key_id
,
5247 const string
& region
,
5248 const string
& service
,
5249 const req_info
& info
,
5250 const bufferlist
*opt_content
,
5253 std::string signed_hdrs
;
5255 ceph::real_time timestamp
= ceph::real_clock::now();
5257 map
<string
, string
> extra_headers
;
5259 std::string date
= ceph::to_iso_8601_no_separators(timestamp
, ceph::iso_8601_format::YMDhms
);
5261 std::string credential_scope
= gen_v4_scope(timestamp
, region
, service
);
5263 extra_headers
["x-amz-date"] = date
;
5265 string content_hash
;
5268 content_hash
= rgw::auth::s3::calc_v4_payload_hash(opt_content
->to_str());
5269 extra_headers
["x-amz-content-sha256"] = content_hash
;
5273 /* craft canonical headers */
5274 std::string canonical_headers
= \
5275 gen_v4_canonical_headers(info
, extra_headers
, &signed_hdrs
);
5277 using sanitize
= rgw::crypt_sanitize::log_content
;
5278 ldpp_dout(dpp
, 10) << "canonical headers format = "
5279 << sanitize
{canonical_headers
} << dendl
;
5281 bool is_non_s3_op
= !s3_op
;
5283 const char* exp_payload_hash
= nullptr;
5284 string payload_hash
;
5286 //For non s3 ops, we need to calculate the payload hash
5287 payload_hash
= info
.args
.get("PayloadHash");
5288 exp_payload_hash
= payload_hash
.c_str();
5290 /* Get the expected hash. */
5291 if (content_hash
.empty()) {
5292 exp_payload_hash
= rgw::auth::s3::get_v4_exp_payload_hash(info
);
5294 exp_payload_hash
= content_hash
.c_str();
5298 /* Craft canonical URI. Using std::move later so let it be non-const. */
5299 auto canonical_uri
= rgw::auth::s3::gen_v4_canonical_uri(info
);
5302 /* Craft canonical query string. std::moving later so non-const here. */
5303 auto canonical_qs
= rgw::auth::s3::gen_v4_canonical_qs(info
);
5305 auto cct
= dpp
->get_cct();
5307 /* Craft canonical request. */
5308 auto canonical_req_hash
= \
5309 rgw::auth::s3::get_v4_canon_req_hash(cct
,
5311 std::move(canonical_uri
),
5312 std::move(canonical_qs
),
5313 std::move(canonical_headers
),
5318 auto string_to_sign
= \
5319 rgw::auth::s3::get_v4_string_to_sign(cct
,
5320 AWS4_HMAC_SHA256_STR
,
5323 std::move(canonical_req_hash
),
5326 const auto sig_factory
= gen_v4_signature
;
5328 /* Requests authenticated with the Query Parameters are treated as unsigned.
5329 * From "Authenticating Requests: Using Query Parameters (AWS Signature
5332 * You don't include a payload hash in the Canonical Request, because
5333 * when you create a presigned URL, you don't know the payload content
5334 * because the URL is used to upload an arbitrary payload. Instead, you
5335 * use a constant string UNSIGNED-PAYLOAD.
5337 * This means we have absolutely no business in spawning completer. Both
5338 * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
5339 * by default. We don't need to change that. */
5344 std::move(signed_hdrs
),
5345 std::move(string_to_sign
),
5346 std::move(extra_headers
),
5351 AWSSignerV4::signature_headers_t
5352 gen_v4_signature(const DoutPrefixProvider
*dpp
,
5353 const std::string_view
& secret_key
,
5354 const AWSSignerV4::prepare_result_t
& sig_info
)
5356 auto signature
= rgw::auth::s3::get_v4_signature(sig_info
.scope
,
5359 sig_info
.string_to_sign
,
5361 AWSSignerV4::signature_headers_t result
;
5363 for (auto& entry
: sig_info
.extra_headers
) {
5364 result
[entry
.first
] = entry
.second
;
5366 auto& payload_hash
= result
["x-amz-content-sha256"];
5367 if (payload_hash
.empty()) {
5368 payload_hash
= AWS4_UNSIGNED_PAYLOAD_HASH
;
5370 string auth_header
= string("AWS4-HMAC-SHA256 Credential=").append(sig_info
.access_key_id
) + "/";
5371 auth_header
.append(sig_info
.scope
+ ",SignedHeaders=")
5372 .append(sig_info
.signed_headers
+ ",Signature=")
5374 result
["Authorization"] = auth_header
;
5380 AWSEngine::VersionAbstractor::auth_data_t
5381 AWSGeneralAbstractor::get_auth_data_v4(const req_state
* const s
,
5382 const bool using_qs
) const
5384 std::string_view access_key_id
;
5385 std::string_view signed_hdrs
;
5387 std::string_view date
;
5388 std::string_view credential_scope
;
5389 std::string_view client_signature
;
5390 std::string_view session_token
;
5392 int ret
= rgw::auth::s3::parse_v4_credentials(s
->info
,
5405 /* craft canonical headers */
5406 boost::optional
<std::string
> canonical_headers
= \
5407 get_v4_canonical_headers(s
->info
, signed_hdrs
, using_qs
);
5408 if (canonical_headers
) {
5409 using sanitize
= rgw::crypt_sanitize::log_content
;
5410 ldpp_dout(s
, 10) << "canonical headers format = "
5411 << sanitize
{*canonical_headers
} << dendl
;
5416 bool is_non_s3_op
= rgw::auth::s3::is_non_s3_op(s
->op_type
);
5418 const char* exp_payload_hash
= nullptr;
5419 string payload_hash
;
5421 //For non s3 ops, we need to calculate the payload hash
5422 payload_hash
= s
->info
.args
.get("PayloadHash");
5423 exp_payload_hash
= payload_hash
.c_str();
5425 /* Get the expected hash. */
5426 exp_payload_hash
= rgw::auth::s3::get_v4_exp_payload_hash(s
->info
);
5429 /* Craft canonical URI. Using std::move later so let it be non-const. */
5430 auto canonical_uri
= rgw::auth::s3::get_v4_canonical_uri(s
->info
);
5432 /* Craft canonical query string. std::moving later so non-const here. */
5433 auto canonical_qs
= rgw::auth::s3::get_v4_canonical_qs(s
->info
, using_qs
);
5435 /* Craft canonical request. */
5436 auto canonical_req_hash
= \
5437 rgw::auth::s3::get_v4_canon_req_hash(s
->cct
,
5439 std::move(canonical_uri
),
5440 std::move(canonical_qs
),
5441 std::move(*canonical_headers
),
5446 auto string_to_sign
= \
5447 rgw::auth::s3::get_v4_string_to_sign(s
->cct
,
5448 AWS4_HMAC_SHA256_STR
,
5451 std::move(canonical_req_hash
),
5454 const auto sig_factory
= std::bind(rgw::auth::s3::get_v4_signature
,
5456 std::placeholders::_1
,
5457 std::placeholders::_2
,
5458 std::placeholders::_3
,
5461 /* Requests authenticated with the Query Parameters are treated as unsigned.
5462 * From "Authenticating Requests: Using Query Parameters (AWS Signature
5465 * You don't include a payload hash in the Canonical Request, because
5466 * when you create a presigned URL, you don't know the payload content
5467 * because the URL is used to upload an arbitrary payload. Instead, you
5468 * use a constant string UNSIGNED-PAYLOAD.
5470 * This means we have absolutely no business in spawning completer. Both
5471 * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false
5472 * by default. We don't need to change that. */
5473 if (is_v4_payload_unsigned(exp_payload_hash
) || is_v4_payload_empty(s
) || is_non_s3_op
) {
5478 std::move(string_to_sign
),
5480 null_completer_factory
5483 /* We're going to handle a signed payload. Be aware that even empty HTTP
5484 * body (no payload) requires verification:
5486 * The x-amz-content-sha256 header is required for all AWS Signature
5487 * Version 4 requests. It provides a hash of the request payload. If
5488 * there is no payload, you must provide the hash of an empty string. */
5489 if (!is_v4_payload_streamed(exp_payload_hash
)) {
5490 ldpp_dout(s
, 10) << "delaying v4 auth" << dendl
;
5492 /* payload in a single chunk */
5495 case RGW_OP_CREATE_BUCKET
:
5496 case RGW_OP_PUT_OBJ
:
5497 case RGW_OP_PUT_ACLS
:
5498 case RGW_OP_PUT_CORS
:
5499 case RGW_OP_PUT_BUCKET_ENCRYPTION
:
5500 case RGW_OP_GET_BUCKET_ENCRYPTION
:
5501 case RGW_OP_DELETE_BUCKET_ENCRYPTION
:
5502 case RGW_OP_INIT_MULTIPART
: // in case that Init Multipart uses CHUNK encoding
5503 case RGW_OP_COMPLETE_MULTIPART
:
5504 case RGW_OP_SET_BUCKET_VERSIONING
:
5505 case RGW_OP_DELETE_MULTI_OBJ
:
5506 case RGW_OP_ADMIN_SET_METADATA
:
5507 case RGW_OP_SYNC_DATALOG_NOTIFY
:
5508 case RGW_OP_SYNC_MDLOG_NOTIFY
:
5509 case RGW_OP_PERIOD_POST
:
5510 case RGW_OP_SET_BUCKET_WEBSITE
:
5511 case RGW_OP_PUT_BUCKET_POLICY
:
5512 case RGW_OP_PUT_OBJ_TAGGING
:
5513 case RGW_OP_PUT_BUCKET_TAGGING
:
5514 case RGW_OP_PUT_BUCKET_REPLICATION
:
5516 case RGW_OP_SET_REQUEST_PAYMENT
:
5517 case RGW_OP_PUBSUB_NOTIF_CREATE
:
5518 case RGW_OP_PUBSUB_NOTIF_DELETE
:
5519 case RGW_OP_PUBSUB_NOTIF_LIST
:
5520 case RGW_OP_PUT_BUCKET_OBJ_LOCK
:
5521 case RGW_OP_PUT_OBJ_RETENTION
:
5522 case RGW_OP_PUT_OBJ_LEGAL_HOLD
:
5523 case RGW_STS_GET_SESSION_TOKEN
:
5524 case RGW_STS_ASSUME_ROLE
:
5525 case RGW_OP_PUT_BUCKET_PUBLIC_ACCESS_BLOCK
:
5526 case RGW_OP_GET_BUCKET_PUBLIC_ACCESS_BLOCK
:
5527 case RGW_OP_DELETE_BUCKET_PUBLIC_ACCESS_BLOCK
:
5528 case RGW_OP_GET_OBJ
://s3select its post-method(payload contain the query) , the request is get-object
5531 ldpp_dout(s
, 10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED" << dendl
;
5532 throw -ERR_NOT_IMPLEMENTED
;
5535 const auto cmpl_factory
= std::bind(AWSv4ComplSingle::create
,
5537 std::placeholders::_1
);
5542 std::move(string_to_sign
),
5547 /* IMHO "streamed" doesn't fit too good here. I would prefer to call
5548 * it "chunked" but let's be coherent with Amazon's terminology. */
5550 ldpp_dout(s
, 10) << "body content detected in multiple chunks" << dendl
;
5552 /* payload in multiple chunks */
5556 case RGW_OP_PUT_OBJ
:
5559 ldpp_dout(s
, 10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED (streaming mode)" << dendl
;
5560 throw -ERR_NOT_IMPLEMENTED
;
5563 ldpp_dout(s
, 10) << "aws4 seed signature ok... delaying v4 auth" << dendl
;
5565 /* In the case of streamed payload client sets the x-amz-content-sha256
5566 * to "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" but uses "UNSIGNED-PAYLOAD"
5567 * when constructing the Canonical Request. */
5569 /* In the case of single-chunk upload client set the header's value is
5570 * coherent with the one used for Canonical Request crafting. */
5572 /* In the case of query string-based authentication there should be no
5573 * x-amz-content-sha256 header and the value "UNSIGNED-PAYLOAD" is used
5575 const auto cmpl_factory
= std::bind(AWSv4ComplMulti::create
,
5580 std::placeholders::_1
);
5585 std::move(string_to_sign
),
5594 boost::optional
<std::string
>
5595 AWSGeneralBoto2Abstractor::get_v4_canonical_headers(
5596 const req_info
& info
,
5597 const std::string_view
& signedheaders
,
5598 const bool using_qs
) const
5600 return rgw::auth::s3::get_v4_canonical_headers(info
, signedheaders
,
5605 AWSEngine::VersionAbstractor::auth_data_t
5606 AWSGeneralAbstractor::get_auth_data_v2(const req_state
* const s
) const
5608 std::string_view access_key_id
;
5609 std::string_view signature
;
5610 std::string_view session_token
;
5613 const char* http_auth
= s
->info
.env
->get("HTTP_AUTHORIZATION");
5614 if (! http_auth
|| http_auth
[0] == '\0') {
5615 /* Credentials are provided in query string. We also need to verify
5616 * the "Expires" parameter now. */
5617 access_key_id
= s
->info
.args
.get("AWSAccessKeyId");
5618 signature
= s
->info
.args
.get("Signature");
5621 std::string_view expires
= s
->info
.args
.get("Expires");
5622 if (expires
.empty()) {
5626 /* It looks we have the guarantee that expires is a null-terminated,
5627 * and thus string_view::data() can be safely used. */
5628 const time_t exp
= atoll(expires
.data());
5635 if (s
->info
.args
.exists("x-amz-security-token")) {
5636 session_token
= s
->info
.args
.get("x-amz-security-token");
5637 if (session_token
.size() == 0) {
5643 /* The "Authorization" HTTP header is being used. */
5644 const std::string_view
auth_str(http_auth
+ strlen("AWS "));
5645 const size_t pos
= auth_str
.rfind(':');
5646 if (pos
!= std::string_view::npos
) {
5647 access_key_id
= auth_str
.substr(0, pos
);
5648 signature
= auth_str
.substr(pos
+ 1);
5651 if (s
->info
.env
->exists("HTTP_X_AMZ_SECURITY_TOKEN")) {
5652 session_token
= s
->info
.env
->get("HTTP_X_AMZ_SECURITY_TOKEN");
5653 if (session_token
.size() == 0) {
5659 /* Let's canonize the HTTP headers that are covered by the AWS auth v2. */
5660 std::string string_to_sign
;
5661 utime_t header_time
;
5662 if (! rgw_create_s3_canonical_header(s
, s
->info
, &header_time
, string_to_sign
,
5664 ldpp_dout(s
, 10) << "failed to create the canonized auth header\n"
5665 << rgw::crypt_sanitize::auth
{s
,string_to_sign
} << dendl
;
5669 ldpp_dout(s
, 10) << "string_to_sign:\n"
5670 << rgw::crypt_sanitize::auth
{s
,string_to_sign
} << dendl
;
5672 if (!qsr
&& !is_time_skew_ok(header_time
)) {
5673 throw -ERR_REQUEST_TIME_SKEWED
;
5677 std::move(access_key_id
),
5678 std::move(signature
),
5679 std::move(session_token
),
5680 std::move(string_to_sign
),
5681 rgw::auth::s3::get_v2_signature
,
5682 null_completer_factory
5687 AWSEngine::VersionAbstractor::auth_data_t
5688 AWSBrowserUploadAbstractor::get_auth_data_v2(const req_state
* const s
) const
5691 s
->auth
.s3_postobj_creds
.access_key
,
5692 s
->auth
.s3_postobj_creds
.signature
,
5693 s
->auth
.s3_postobj_creds
.x_amz_security_token
,
5694 s
->auth
.s3_postobj_creds
.encoded_policy
.to_str(),
5695 rgw::auth::s3::get_v2_signature
,
5696 null_completer_factory
5700 AWSEngine::VersionAbstractor::auth_data_t
5701 AWSBrowserUploadAbstractor::get_auth_data_v4(const req_state
* const s
) const
5703 const std::string_view credential
= s
->auth
.s3_postobj_creds
.x_amz_credential
;
5705 /* grab access key id */
5706 const size_t pos
= credential
.find("/");
5707 const std::string_view access_key_id
= credential
.substr(0, pos
);
5708 ldpp_dout(s
, 10) << "access key id = " << access_key_id
<< dendl
;
5710 /* grab credential scope */
5711 const std::string_view credential_scope
= credential
.substr(pos
+ 1);
5712 ldpp_dout(s
, 10) << "credential scope = " << credential_scope
<< dendl
;
5714 const auto sig_factory
= std::bind(rgw::auth::s3::get_v4_signature
,
5716 std::placeholders::_1
,
5717 std::placeholders::_2
,
5718 std::placeholders::_3
,
5723 s
->auth
.s3_postobj_creds
.signature
,
5724 s
->auth
.s3_postobj_creds
.x_amz_security_token
,
5725 s
->auth
.s3_postobj_creds
.encoded_policy
.to_str(),
5727 null_completer_factory
5731 AWSEngine::VersionAbstractor::auth_data_t
5732 AWSBrowserUploadAbstractor::get_auth_data(const req_state
* const s
) const
5734 if (s
->auth
.s3_postobj_creds
.x_amz_algorithm
== AWS4_HMAC_SHA256_STR
) {
5735 ldpp_dout(s
, 0) << "Signature verification algorithm AWS v4"
5736 << " (AWS4-HMAC-SHA256)" << dendl
;
5737 return get_auth_data_v4(s
);
5739 ldpp_dout(s
, 0) << "Signature verification algorithm AWS v2" << dendl
;
5740 return get_auth_data_v2(s
);
5745 AWSEngine::authenticate(const DoutPrefixProvider
* dpp
, const req_state
* const s
, optional_yield y
) const
5747 /* Small reminder: an ver_abstractor is allowed to throw! */
5748 const auto auth_data
= ver_abstractor
.get_auth_data(s
);
5750 if (auth_data
.access_key_id
.empty() || auth_data
.client_signature
.empty()) {
5751 return result_t::deny(-EINVAL
);
5753 return authenticate(dpp
,
5754 auth_data
.access_key_id
,
5755 auth_data
.client_signature
,
5756 auth_data
.session_token
,
5757 auth_data
.string_to_sign
,
5758 auth_data
.signature_factory
,
5759 auth_data
.completer_factory
,
5764 } // namespace rgw::auth::s3
5766 rgw::LDAPHelper
* rgw::auth::s3::LDAPEngine::ldh
= nullptr;
5767 std::mutex
rgw::auth::s3::LDAPEngine::mtx
;
5769 void rgw::auth::s3::LDAPEngine::init(CephContext
* const cct
)
5771 if (! cct
->_conf
->rgw_s3_auth_use_ldap
||
5772 cct
->_conf
->rgw_ldap_uri
.empty()) {
5777 std::lock_guard
<std::mutex
> lck(mtx
);
5779 const string
& ldap_uri
= cct
->_conf
->rgw_ldap_uri
;
5780 const string
& ldap_binddn
= cct
->_conf
->rgw_ldap_binddn
;
5781 const string
& ldap_searchdn
= cct
->_conf
->rgw_ldap_searchdn
;
5782 const string
& ldap_searchfilter
= cct
->_conf
->rgw_ldap_searchfilter
;
5783 const string
& ldap_dnattr
= cct
->_conf
->rgw_ldap_dnattr
;
5784 std::string ldap_bindpw
= parse_rgw_ldap_bindpw(cct
);
5786 ldh
= new rgw::LDAPHelper(ldap_uri
, ldap_binddn
, ldap_bindpw
,
5787 ldap_searchdn
, ldap_searchfilter
, ldap_dnattr
);
5795 bool rgw::auth::s3::LDAPEngine::valid() {
5796 std::lock_guard
<std::mutex
> lck(mtx
);
5800 rgw::auth::RemoteApplier::acl_strategy_t
5801 rgw::auth::s3::LDAPEngine::get_acl_strategy() const
5803 //This is based on the assumption that the default acl strategy in
5804 // get_perms_from_aclspec, will take care. Extra acl spec is not required.
5808 rgw::auth::RemoteApplier::AuthInfo
5809 rgw::auth::s3::LDAPEngine::get_creds_info(const rgw::RGWToken
& token
) const noexcept
5811 /* The short form of "using" can't be used here -- we're aliasing a class'
5813 using acct_privilege_t
= \
5814 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
5816 return rgw::auth::RemoteApplier::AuthInfo
{
5819 RGW_PERM_FULL_CONTROL
,
5820 acct_privilege_t::IS_PLAIN_ACCT
,
5825 rgw::auth::Engine::result_t
5826 rgw::auth::s3::LDAPEngine::authenticate(
5827 const DoutPrefixProvider
* dpp
,
5828 const std::string_view
& access_key_id
,
5829 const std::string_view
& signature
,
5830 const std::string_view
& session_token
,
5831 const string_to_sign_t
& string_to_sign
,
5832 const signature_factory_t
&,
5833 const completer_factory_t
& completer_factory
,
5834 const req_state
* const s
,
5835 optional_yield y
) const
5837 /* boost filters and/or string_ref may throw on invalid input */
5838 rgw::RGWToken base64_token
;
5840 base64_token
= rgw::from_base64(access_key_id
);
5842 base64_token
= std::string("");
5845 if (! base64_token
.valid()) {
5846 return result_t::deny();
5849 //TODO: Uncomment, when we have a migration plan in place.
5850 //Check if a user of type other than 'ldap' is already present, if yes, then
5852 /*RGWUserInfo user_info;
5853 user_info.user_id = base64_token.id;
5854 if (rgw_get_user_info_by_uid(store, user_info.user_id, user_info) >= 0) {
5855 if (user_info.type != TYPE_LDAP) {
5856 ldpp_dout(dpp, 10) << "ERROR: User id of type: " << user_info.type << " is already present" << dendl;
5861 if (ldh
->auth(base64_token
.id
, base64_token
.key
) != 0) {
5862 return result_t::deny(-ERR_INVALID_ACCESS_KEY
);
5865 auto apl
= apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(),
5866 get_creds_info(base64_token
));
5867 return result_t::grant(std::move(apl
), completer_factory(boost::none
));
5868 } /* rgw::auth::s3::LDAPEngine::authenticate */
5870 void rgw::auth::s3::LDAPEngine::shutdown() {
5878 rgw::auth::Engine::result_t
5879 rgw::auth::s3::LocalEngine::authenticate(
5880 const DoutPrefixProvider
* dpp
,
5881 const std::string_view
& _access_key_id
,
5882 const std::string_view
& signature
,
5883 const std::string_view
& session_token
,
5884 const string_to_sign_t
& string_to_sign
,
5885 const signature_factory_t
& signature_factory
,
5886 const completer_factory_t
& completer_factory
,
5887 const req_state
* const s
,
5888 optional_yield y
) const
5890 /* get the user info */
5891 std::unique_ptr
<rgw::sal::User
> user
;
5892 const std::string
access_key_id(_access_key_id
);
5893 /* TODO(rzarzynski): we need to have string-view taking variant. */
5894 if (store
->get_user_by_access_key(dpp
, access_key_id
, y
, &user
) < 0) {
5895 ldpp_dout(dpp
, 5) << "error reading user info, uid=" << access_key_id
5896 << " can't authenticate" << dendl
;
5897 return result_t::deny(-ERR_INVALID_ACCESS_KEY
);
5899 //TODO: Uncomment, when we have a migration plan in place.
5901 if (s->user->type != TYPE_RGW) {
5902 ldpp_dout(dpp, 10) << "ERROR: User id of type: " << s->user->type
5903 << " is present" << dendl;
5908 const auto iter
= user
->get_info().access_keys
.find(access_key_id
);
5909 if (iter
== std::end(user
->get_info().access_keys
)) {
5910 ldpp_dout(dpp
, 0) << "ERROR: access key not encoded in user info" << dendl
;
5911 return result_t::deny(-EPERM
);
5913 const RGWAccessKey
& k
= iter
->second
;
5915 const VersionAbstractor::server_signature_t server_signature
= \
5916 signature_factory(cct
, k
.key
, string_to_sign
);
5917 auto compare
= signature
.compare(server_signature
);
5919 ldpp_dout(dpp
, 15) << "string_to_sign="
5920 << rgw::crypt_sanitize::log_content
{string_to_sign
}
5922 ldpp_dout(dpp
, 15) << "server signature=" << server_signature
<< dendl
;
5923 ldpp_dout(dpp
, 15) << "client signature=" << signature
<< dendl
;
5924 ldpp_dout(dpp
, 15) << "compare=" << compare
<< dendl
;
5927 return result_t::deny(-ERR_SIGNATURE_NO_MATCH
);
5930 auto apl
= apl_factory
->create_apl_local(cct
, s
, user
->get_info(),
5931 k
.subuser
, std::nullopt
);
5932 return result_t::grant(std::move(apl
), completer_factory(k
.key
));
5935 rgw::auth::RemoteApplier::AuthInfo
5936 rgw::auth::s3::STSEngine::get_creds_info(const STS::SessionToken
& token
) const noexcept
5938 using acct_privilege_t
= \
5939 rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t
;
5941 return rgw::auth::RemoteApplier::AuthInfo
{
5945 (token
.is_admin
) ? acct_privilege_t::IS_ADMIN_ACCT
: acct_privilege_t::IS_PLAIN_ACCT
,
5951 rgw::auth::s3::STSEngine::get_session_token(const DoutPrefixProvider
* dpp
, const std::string_view
& session_token
,
5952 STS::SessionToken
& token
) const
5954 string decodedSessionToken
;
5956 decodedSessionToken
= rgw::from_base64(session_token
);
5958 ldpp_dout(dpp
, 0) << "ERROR: Invalid session token, not base64 encoded." << dendl
;
5962 auto* cryptohandler
= cct
->get_crypto_handler(CEPH_CRYPTO_AES
);
5963 if (! cryptohandler
) {
5966 string secret_s
= cct
->_conf
->rgw_sts_key
;
5967 buffer::ptr
secret(secret_s
.c_str(), secret_s
.length());
5969 if (ret
= cryptohandler
->validate_secret(secret
); ret
< 0) {
5970 ldpp_dout(dpp
, 0) << "ERROR: Invalid secret key" << dendl
;
5974 std::unique_ptr
<CryptoKeyHandler
> keyhandler(cryptohandler
->get_key_handler(secret
, error
));
5980 string decrypted_str
;
5981 buffer::list en_input
, dec_output
;
5982 en_input
= buffer::list::static_from_string(decodedSessionToken
);
5984 ret
= keyhandler
->decrypt(en_input
, dec_output
, &error
);
5986 ldpp_dout(dpp
, 0) << "ERROR: Decryption failed: " << error
<< dendl
;
5990 dec_output
.append('\0');
5991 auto iter
= dec_output
.cbegin();
5992 decode(token
, iter
);
5993 } catch (const buffer::error
& e
) {
5994 ldpp_dout(dpp
, 0) << "ERROR: decode SessionToken failed: " << error
<< dendl
;
6001 rgw::auth::Engine::result_t
6002 rgw::auth::s3::STSEngine::authenticate(
6003 const DoutPrefixProvider
* dpp
,
6004 const std::string_view
& _access_key_id
,
6005 const std::string_view
& signature
,
6006 const std::string_view
& session_token
,
6007 const string_to_sign_t
& string_to_sign
,
6008 const signature_factory_t
& signature_factory
,
6009 const completer_factory_t
& completer_factory
,
6010 const req_state
* const s
,
6011 optional_yield y
) const
6013 if (! s
->info
.args
.exists("x-amz-security-token") &&
6014 ! s
->info
.env
->exists("HTTP_X_AMZ_SECURITY_TOKEN") &&
6015 s
->auth
.s3_postobj_creds
.x_amz_security_token
.empty()) {
6016 return result_t::deny();
6019 STS::SessionToken token
;
6020 if (int ret
= get_session_token(dpp
, session_token
, token
); ret
< 0) {
6021 return result_t::reject(ret
);
6024 //Check if access key is not the same passed in by client
6025 if (token
.access_key_id
!= _access_key_id
) {
6026 ldpp_dout(dpp
, 0) << "Invalid access key" << dendl
;
6027 return result_t::reject(-EPERM
);
6029 //Check if the token has expired
6030 if (! token
.expiration
.empty()) {
6031 std::string expiration
= token
.expiration
;
6032 if (! expiration
.empty()) {
6033 boost::optional
<real_clock::time_point
> exp
= ceph::from_iso_8601(expiration
, false);
6035 real_clock::time_point now
= real_clock::now();
6037 ldpp_dout(dpp
, 0) << "ERROR: Token expired" << dendl
;
6038 return result_t::reject(-EPERM
);
6041 ldpp_dout(dpp
, 0) << "ERROR: Invalid expiration: " << expiration
<< dendl
;
6042 return result_t::reject(-EPERM
);
6046 //Check for signature mismatch
6047 const VersionAbstractor::server_signature_t server_signature
= \
6048 signature_factory(cct
, token
.secret_access_key
, string_to_sign
);
6049 auto compare
= signature
.compare(server_signature
);
6051 ldpp_dout(dpp
, 15) << "string_to_sign="
6052 << rgw::crypt_sanitize::log_content
{string_to_sign
}
6054 ldpp_dout(dpp
, 15) << "server signature=" << server_signature
<< dendl
;
6055 ldpp_dout(dpp
, 15) << "client signature=" << signature
<< dendl
;
6056 ldpp_dout(dpp
, 15) << "compare=" << compare
<< dendl
;
6059 return result_t::reject(-ERR_SIGNATURE_NO_MATCH
);
6062 // Get all the authorization info
6063 std::unique_ptr
<rgw::sal::User
> user
;
6066 rgw::auth::RoleApplier::Role r
;
6067 rgw::auth::RoleApplier::TokenAttrs t_attrs
;
6068 if (! token
.roleId
.empty()) {
6069 std::unique_ptr
<rgw::sal::RGWRole
> role
= store
->get_role(token
.roleId
);
6070 if (role
->get_by_id(dpp
, y
) < 0) {
6071 return result_t::deny(-EPERM
);
6073 r
.id
= token
.roleId
;
6074 r
.name
= role
->get_name();
6075 r
.tenant
= role
->get_tenant();
6077 vector
<string
> role_policy_names
= role
->get_role_policy_names();
6078 for (auto& policy_name
: role_policy_names
) {
6080 if (int ret
= role
->get_role_policy(dpp
, policy_name
, perm_policy
); ret
== 0) {
6081 r
.role_policies
.push_back(std::move(perm_policy
));
6086 user
= store
->get_user(token
.user
);
6087 if (! token
.user
.empty() && token
.acct_type
!= TYPE_ROLE
) {
6089 int ret
= user
->load_user(dpp
, y
);
6091 ldpp_dout(dpp
, 5) << "ERROR: failed reading user info: uid=" << token
.user
<< dendl
;
6092 return result_t::reject(-EPERM
);
6096 if (token
.acct_type
== TYPE_KEYSTONE
|| token
.acct_type
== TYPE_LDAP
) {
6097 auto apl
= remote_apl_factory
->create_apl_remote(cct
, s
, get_acl_strategy(),
6098 get_creds_info(token
));
6099 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6100 } else if (token
.acct_type
== TYPE_ROLE
) {
6101 t_attrs
.user_id
= std::move(token
.user
); // This is mostly needed to assign the owner of a bucket during its creation
6102 t_attrs
.token_policy
= std::move(token
.policy
);
6103 t_attrs
.role_session_name
= std::move(token
.role_session
);
6104 t_attrs
.token_claims
= std::move(token
.token_claims
);
6105 t_attrs
.token_issued_at
= std::move(token
.issued_at
);
6106 t_attrs
.principal_tags
= std::move(token
.principal_tags
);
6107 auto apl
= role_apl_factory
->create_apl_role(cct
, s
, r
, t_attrs
);
6108 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6109 } else { // This is for all local users of type TYPE_RGW or TYPE_NONE
6111 auto apl
= local_apl_factory
->create_apl_local(cct
, s
, user
->get_info(), subuser
, token
.perm_mask
);
6112 return result_t::grant(std::move(apl
), completer_factory(token
.secret_access_key
));
6116 bool rgw::auth::s3::S3AnonymousEngine::is_applicable(
6119 if (s
->op
== OP_OPTIONS
) {
6125 std::tie(version
, route
) = discover_aws_flavour(s
->info
);
6127 return route
== AwsRoute::QUERY_STRING
&& version
== AwsVersion::UNKNOWN
;