// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include #include #include #include "common/ceph_crypto.h" #include "common/Formatter.h" #include "common/utf8.h" #include "common/ceph_json.h" #include "common/safe_io.h" #include "common/backport14.h" #include #include #include #include "rgw_rest.h" #include "rgw_rest_s3.h" #include "rgw_rest_s3website.h" #include "rgw_auth_s3.h" #include "rgw_acl.h" #include "rgw_policy_s3.h" #include "rgw_user.h" #include "rgw_cors.h" #include "rgw_cors_s3.h" #include "rgw_tag_s3.h" #include "rgw_client_io.h" #include "rgw_keystone.h" #include "rgw_auth_keystone.h" #include "rgw_auth_registry.h" #include "rgw_es_query.h" #include // for 'typeid' #include "rgw_ldap.h" #include "rgw_token.h" #include "rgw_rest_role.h" #include "rgw_crypt.h" #include "rgw_crypt_sanitize.h" #include "include/assert.h" #define dout_context g_ceph_context #define dout_subsys ceph_subsys_rgw using namespace rgw; using namespace ceph::crypto; using std::get; void list_all_buckets_start(struct req_state *s) { s->formatter->open_array_section_in_ns("ListAllMyBucketsResult", XMLNS_AWS_S3); } void list_all_buckets_end(struct req_state *s) { s->formatter->close_section(); } void dump_bucket(struct req_state *s, RGWBucketEnt& obj) { s->formatter->open_object_section("Bucket"); s->formatter->dump_string("Name", obj.bucket.name); dump_time(s, "CreationDate", &obj.creation_time); s->formatter->close_section(); } void rgw_get_errno_s3(rgw_http_error *e , int err_no) { rgw_http_errors::const_iterator r = rgw_http_s3_errors.find(err_no); if (r != rgw_http_s3_errors.end()) { e->http_ret = r->second.first; e->s3_code = r->second.second; } else { e->http_ret = 500; e->s3_code = "UnknownError"; } } struct response_attr_param { const char *param; const char *http_attr; }; static struct response_attr_param resp_attr_params[] = { {"response-content-type", "Content-Type"}, {"response-content-language", "Content-Language"}, {"response-expires", "Expires"}, {"response-cache-control", "Cache-Control"}, {"response-content-disposition", "Content-Disposition"}, {"response-content-encoding", "Content-Encoding"}, {NULL, NULL}, }; int RGWGetObj_ObjStore_S3Website::send_response_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { map::iterator iter; iter = attrs.find(RGW_ATTR_AMZ_WEBSITE_REDIRECT_LOCATION); if (iter != attrs.end()) { bufferlist &bl = iter->second; s->redirect = string(bl.c_str(), bl.length()); s->err.http_ret = 301; ldout(s->cct, 20) << __CEPH_ASSERT_FUNCTION << " redirecting per x-amz-website-redirect-location=" << s->redirect << dendl; op_ret = -ERR_WEBSITE_REDIRECT; set_req_state_err(s, op_ret); dump_errno(s); dump_content_length(s, 0); dump_redirect(s, s->redirect); end_header(s, this); return op_ret; } else { return RGWGetObj_ObjStore_S3::send_response_data(bl, bl_ofs, bl_len); } } int RGWGetObj_ObjStore_S3Website::send_response_data_error() { return RGWGetObj_ObjStore_S3::send_response_data_error(); } int RGWGetObj_ObjStore_S3::get_params() { // for multisite sync requests, only read the slo manifest itself, rather than // all of the data from its parts. the parts will sync as separate objects skip_manifest = s->info.args.exists(RGW_SYS_PARAM_PREFIX "sync-manifest"); // multisite sync requests should fetch encrypted data, along with the // attributes needed to support decryption on the other zone if (s->system_request) { skip_decrypt = s->info.args.exists(RGW_SYS_PARAM_PREFIX "skip-decrypt"); } return RGWGetObj_ObjStore::get_params(); } int RGWGetObj_ObjStore_S3::send_response_data_error() { bufferlist bl; return send_response_data(bl, 0 , 0); } template int decode_attr_bl_single_value(map& attrs, const char *attr_name, T *result, T def_val) { map::iterator iter = attrs.find(attr_name); if (iter == attrs.end()) { *result = def_val; return 0; } bufferlist& bl = iter->second; if (bl.length() == 0) { *result = def_val; return 0; } bufferlist::iterator bliter = bl.begin(); try { ::decode(*result, bliter); } catch (buffer::error& err) { return -EIO; } return 0; } int RGWGetObj_ObjStore_S3::send_response_data(bufferlist& bl, off_t bl_ofs, off_t bl_len) { const char *content_type = NULL; string content_type_str; map response_attrs; map::iterator riter; bufferlist metadata_bl; if (sent_header) goto send_data; if (custom_http_ret) { set_req_state_err(s, 0); dump_errno(s, custom_http_ret); } else { set_req_state_err(s, (partial_content && !op_ret) ? STATUS_PARTIAL_CONTENT : op_ret); dump_errno(s); } if (op_ret) goto done; if (range_str) dump_range(s, start, end, s->obj_size); if (s->system_request && s->info.args.exists(RGW_SYS_PARAM_PREFIX "prepend-metadata")) { dump_header(s, "Rgwx-Object-Size", (long long)total_len); if (rgwx_stat) { /* * in this case, we're not returning the object's content, only the prepended * extra metadata */ total_len = 0; } /* JSON encode object metadata */ JSONFormatter jf; jf.open_object_section("obj_metadata"); encode_json("attrs", attrs, &jf); utime_t ut(lastmod); encode_json("mtime", ut, &jf); jf.close_section(); stringstream ss; jf.flush(ss); metadata_bl.append(ss.str()); dump_header(s, "Rgwx-Embedded-Metadata-Len", metadata_bl.length()); total_len += metadata_bl.length(); } if (s->system_request && !real_clock::is_zero(lastmod)) { /* we end up dumping mtime in two different methods, a bit redundant */ dump_epoch_header(s, "Rgwx-Mtime", lastmod); uint64_t pg_ver = 0; int r = decode_attr_bl_single_value(attrs, RGW_ATTR_PG_VER, &pg_ver, (uint64_t)0); if (r < 0) { ldout(s->cct, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl; } dump_header(s, "Rgwx-Obj-PG-Ver", pg_ver); uint32_t source_zone_short_id = 0; r = decode_attr_bl_single_value(attrs, RGW_ATTR_SOURCE_ZONE, &source_zone_short_id, (uint32_t)0); if (r < 0) { ldout(s->cct, 0) << "ERROR: failed to decode pg ver attr, ignoring" << dendl; } if (source_zone_short_id != 0) { dump_header(s, "Rgwx-Source-Zone-Short-Id", source_zone_short_id); } } for (auto &it : crypt_http_responses) dump_header(s, it.first, it.second); dump_content_length(s, total_len); dump_last_modified(s, lastmod); if (!version_id.empty()) { dump_header(s, "x-amz-version-id", version_id); } if (! op_ret) { if (! lo_etag.empty()) { /* Handle etag of Swift API's large objects (DLO/SLO). It's entirerly * legit to perform GET on them through S3 API. In such situation, * a client should receive the composited content with corresponding * etag value. */ dump_etag(s, lo_etag); } else { auto iter = attrs.find(RGW_ATTR_ETAG); if (iter != attrs.end()) { dump_etag(s, iter->second); } } for (struct response_attr_param *p = resp_attr_params; p->param; p++) { bool exists; string val = s->info.args.get(p->param, &exists); if (exists) { if (strcmp(p->param, "response-content-type") != 0) { response_attrs[p->http_attr] = val; } else { content_type_str = val; content_type = content_type_str.c_str(); } } } for (auto iter = attrs.begin(); iter != attrs.end(); ++iter) { const char *name = iter->first.c_str(); map::iterator aiter = rgw_to_http_attrs.find(name); if (aiter != rgw_to_http_attrs.end()) { if (response_attrs.count(aiter->second) == 0) { /* Was not already overridden by a response param. */ response_attrs[aiter->second] = iter->second.c_str(); } } else if (iter->first.compare(RGW_ATTR_CONTENT_TYPE) == 0) { /* Special handling for content_type. */ if (!content_type) { content_type = iter->second.c_str(); } } else if (strcmp(name, RGW_ATTR_SLO_UINDICATOR) == 0) { // this attr has an extra length prefix from ::encode() in prior versions dump_header(s, "X-Object-Meta-Static-Large-Object", "True"); } else if (strncmp(name, RGW_ATTR_META_PREFIX, sizeof(RGW_ATTR_META_PREFIX)-1) == 0) { /* User custom metadata. */ name += sizeof(RGW_ATTR_PREFIX) - 1; dump_header(s, name, iter->second); } else if (iter->first.compare(RGW_ATTR_TAGS) == 0) { RGWObjTags obj_tags; try{ bufferlist::iterator it = iter->second.begin(); obj_tags.decode(it); } catch (buffer::error &err) { ldout(s->cct,0) << "Error caught buffer::error couldn't decode TagSet " << dendl; } dump_header(s, RGW_AMZ_TAG_COUNT, obj_tags.count()); } } } done: for (riter = response_attrs.begin(); riter != response_attrs.end(); ++riter) { dump_header(s, riter->first, riter->second); } if (op_ret == -ERR_NOT_MODIFIED) { end_header(s, this); } else { if (!content_type) content_type = "binary/octet-stream"; end_header(s, this, content_type); } if (metadata_bl.length()) { dump_body(s, metadata_bl); } sent_header = true; send_data: if (get_data && !op_ret) { int r = dump_body(s, bl.c_str() + bl_ofs, bl_len); if (r < 0) return r; } return 0; } int RGWGetObj_ObjStore_S3::get_decrypt_filter(std::unique_ptr *filter, RGWGetDataCB* cb, bufferlist* manifest_bl) { if (skip_decrypt) { // bypass decryption for multisite sync requests return 0; } int res = 0; std::unique_ptr block_crypt; res = rgw_s3_prepare_decrypt(s, attrs, &block_crypt, crypt_http_responses); if (res == 0) { if (block_crypt != nullptr) { auto f = std::unique_ptr(new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt))); //RGWGetObj_BlockDecrypt* f = new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt)); if (f != nullptr) { if (manifest_bl != nullptr) { res = f->read_manifest(*manifest_bl); if (res == 0) { *filter = std::move(f); } } } } } return res; } void RGWGetObjTags_ObjStore_S3::send_response_data(bufferlist& bl) { dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); s->formatter->open_object_section_in_ns("Tagging", XMLNS_AWS_S3); s->formatter->open_object_section("TagSet"); if (has_tags){ RGWObjTagSet_S3 tagset; bufferlist::iterator iter = bl.begin(); try { tagset.decode(iter); } catch (buffer::error& err) { ldout(s->cct,0) << "ERROR: caught buffer::error, couldn't decode TagSet" << dendl; op_ret= -EIO; return; } tagset.dump_xml(s->formatter); } s->formatter->close_section(); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } int RGWPutObjTags_ObjStore_S3::get_params() { RGWObjTagsXMLParser parser; if (!parser.init()){ return -EINVAL; } char *data=nullptr; int len=0; const auto max_size = s->cct->_conf->rgw_max_put_param_size; int r = rgw_rest_read_all_input(s, &data, &len, max_size, false); if (r < 0) return r; auto data_deleter = std::unique_ptr{data, free}; if (!parser.parse(data, len, 1)) { return -ERR_MALFORMED_XML; } RGWObjTagSet_S3 *obj_tags_s3; RGWObjTagging_S3 *tagging; tagging = static_cast(parser.find_first("Tagging")); obj_tags_s3 = static_cast(tagging->find_first("TagSet")); if(!obj_tags_s3){ return -ERR_MALFORMED_XML; } RGWObjTags obj_tags; r = obj_tags_s3->rebuild(obj_tags); if (r < 0) return r; obj_tags.encode(tags_bl); ldout(s->cct, 20) << "Read " << obj_tags.count() << "tags" << dendl; return 0; } void RGWPutObjTags_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); } void RGWDeleteObjTags_ObjStore_S3::send_response() { int r = op_ret; if (r == -ENOENT) r = 0; if (!r) r = STATUS_NO_CONTENT; set_req_state_err(s, r); dump_errno(s); end_header(s, this); } void RGWListBuckets_ObjStore_S3::send_response_begin(bool has_buckets) { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); dump_start(s); end_header(s, NULL, "application/xml"); if (! op_ret) { list_all_buckets_start(s); dump_owner(s, s->user->user_id, s->user->display_name); s->formatter->open_array_section("Buckets"); sent_data = true; } } void RGWListBuckets_ObjStore_S3::send_response_data(RGWUserBuckets& buckets) { if (!sent_data) return; map& m = buckets.get_buckets(); map::iterator iter; for (iter = m.begin(); iter != m.end(); ++iter) { RGWBucketEnt obj = iter->second; dump_bucket(s, obj); } rgw_flush_formatter(s, s->formatter); } void RGWListBuckets_ObjStore_S3::send_response_end() { if (sent_data) { s->formatter->close_section(); list_all_buckets_end(s); rgw_flush_formatter_and_reset(s, s->formatter); } } int RGWGetUsage_ObjStore_S3::get_params() { start_date = s->info.args.get("start-date"); end_date = s->info.args.get("end-date"); return 0; } static void dump_usage_categories_info(Formatter *formatter, const rgw_usage_log_entry& entry, map *categories) { formatter->open_array_section("categories"); map::const_iterator uiter; for (uiter = entry.usage_map.begin(); uiter != entry.usage_map.end(); ++uiter) { if (categories && !categories->empty() && !categories->count(uiter->first)) continue; const rgw_usage_data& usage = uiter->second; formatter->open_object_section("Entry"); formatter->dump_string("Category", uiter->first); formatter->dump_int("BytesSent", usage.bytes_sent); formatter->dump_int("BytesReceived", usage.bytes_received); formatter->dump_int("Ops", usage.ops); formatter->dump_int("SuccessfulOps", usage.successful_ops); formatter->close_section(); // Entry } formatter->close_section(); // Category } static void dump_usage_bucket_info(Formatter *formatter, const std::string& name, const cls_user_bucket_entry& entry) { formatter->open_object_section("Entry"); formatter->dump_string("Bucket", name); formatter->dump_int("Bytes", entry.size); formatter->dump_int("Bytes_Rounded", entry.size_rounded); formatter->close_section(); // entry } void RGWGetUsage_ObjStore_S3::send_response() { if (op_ret < 0) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); if (op_ret < 0) return; Formatter *formatter = s->formatter; string last_owner; bool user_section_open = false; formatter->open_object_section("Usage"); if (show_log_entries) { formatter->open_array_section("Entries"); } map::iterator iter; for (iter = usage.begin(); iter != usage.end(); ++iter) { const rgw_user_bucket& ub = iter->first; const rgw_usage_log_entry& entry = iter->second; if (show_log_entries) { if (ub.user.compare(last_owner) != 0) { if (user_section_open) { formatter->close_section(); formatter->close_section(); } formatter->open_object_section("User"); formatter->dump_string("Owner", ub.user); formatter->open_array_section("Buckets"); user_section_open = true; last_owner = ub.user; } formatter->open_object_section("Bucket"); formatter->dump_string("Bucket", ub.bucket); utime_t ut(entry.epoch, 0); ut.gmtime(formatter->dump_stream("Time")); formatter->dump_int("Epoch", entry.epoch); dump_usage_categories_info(formatter, entry, &categories); formatter->close_section(); // bucket } summary_map[ub.user].aggregate(entry, &categories); } if (show_log_entries) { if (user_section_open) { formatter->close_section(); // buckets formatter->close_section(); //user } formatter->close_section(); // entries } if (show_log_sum) { formatter->open_array_section("Summary"); map::iterator siter; for (siter = summary_map.begin(); siter != summary_map.end(); ++siter) { const rgw_usage_log_entry& entry = siter->second; formatter->open_object_section("User"); formatter->dump_string("User", siter->first); dump_usage_categories_info(formatter, entry, &categories); rgw_usage_data total_usage; entry.sum(total_usage, categories); formatter->open_object_section("Total"); formatter->dump_int("BytesSent", total_usage.bytes_sent); formatter->dump_int("BytesReceived", total_usage.bytes_received); formatter->dump_int("Ops", total_usage.ops); formatter->dump_int("SuccessfulOps", total_usage.successful_ops); formatter->close_section(); // total formatter->close_section(); // user } if (s->cct->_conf->rgw_rest_getusage_op_compat) { formatter->open_object_section("Stats"); } formatter->dump_int("TotalBytes", header.stats.total_bytes); formatter->dump_int("TotalBytesRounded", header.stats.total_bytes_rounded); formatter->dump_int("TotalEntries", header.stats.total_entries); if (s->cct->_conf->rgw_rest_getusage_op_compat) { formatter->close_section(); //Stats } formatter->close_section(); // summary } formatter->open_array_section("CapacityUsed"); formatter->open_object_section("User"); formatter->open_array_section("Buckets"); for (const auto& biter : buckets_usage) { const cls_user_bucket_entry& entry = biter.second; dump_usage_bucket_info(formatter, biter.first, entry); } formatter->close_section(); // Buckets formatter->close_section(); // User formatter->close_section(); // CapacityUsed formatter->close_section(); // usage rgw_flush_formatter_and_reset(s, s->formatter); } int RGWListBucket_ObjStore_S3::get_params() { list_versions = s->info.args.exists("versions"); prefix = s->info.args.get("prefix"); if (!list_versions) { marker = s->info.args.get("marker"); } else { marker.name = s->info.args.get("key-marker"); marker.instance = s->info.args.get("version-id-marker"); } max_keys = s->info.args.get("max-keys"); op_ret = parse_max_keys(); if (op_ret < 0) { return op_ret; } delimiter = s->info.args.get("delimiter"); encoding_type = s->info.args.get("encoding-type"); if (s->system_request) { s->info.args.get_bool("objs-container", &objs_container, false); const char *shard_id_str = s->info.env->get("HTTP_RGWX_SHARD_ID"); if (shard_id_str) { string err; shard_id = strict_strtol(shard_id_str, 10, &err); if (!err.empty()) { ldout(s->cct, 5) << "bad shard id specified: " << shard_id_str << dendl; return -EINVAL; } } else { shard_id = s->bucket_instance_shard_id; } } return 0; } void RGWListBucket_ObjStore_S3::send_versioned_response() { s->formatter->open_object_section_in_ns("ListVersionsResult", XMLNS_AWS_S3); if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Name", s->bucket_name); s->formatter->dump_string("Prefix", prefix); s->formatter->dump_string("KeyMarker", marker.name); s->formatter->dump_string("VersionIdMarker", marker.instance); if (is_truncated && !next_marker.empty()) { s->formatter->dump_string("NextKeyMarker", next_marker.name); s->formatter->dump_string("NextVersionIdMarker", next_marker.instance); } s->formatter->dump_int("MaxKeys", max); if (!delimiter.empty()) s->formatter->dump_string("Delimiter", delimiter); s->formatter->dump_string("IsTruncated", (max && is_truncated ? "true" : "false")); bool encode_key = false; if (strcasecmp(encoding_type.c_str(), "url") == 0) { s->formatter->dump_string("EncodingType", "url"); encode_key = true; } if (op_ret >= 0) { if (objs_container) { s->formatter->open_array_section("Entries"); } vector::iterator iter; for (iter = objs.begin(); iter != objs.end(); ++iter) { const char *section_name = (iter->is_delete_marker() ? "DeleteMarker" : "Version"); s->formatter->open_object_section(section_name); if (objs_container) { s->formatter->dump_bool("IsDeleteMarker", iter->is_delete_marker()); } rgw_obj_key key(iter->key); if (encode_key) { string key_name; url_encode(key.name, key_name); s->formatter->dump_string("Key", key_name); } else { s->formatter->dump_string("Key", key.name); } string version_id = key.instance; if (version_id.empty()) { version_id = "null"; } if (s->system_request) { if (iter->versioned_epoch > 0) { s->formatter->dump_int("VersionedEpoch", iter->versioned_epoch); } s->formatter->dump_string("RgwxTag", iter->tag); utime_t ut(iter->meta.mtime); ut.gmtime_nsec(s->formatter->dump_stream("RgwxMtime")); } s->formatter->dump_string("VersionId", version_id); s->formatter->dump_bool("IsLatest", iter->is_current()); dump_time(s, "LastModified", &iter->meta.mtime); if (!iter->is_delete_marker()) { s->formatter->dump_format("ETag", "\"%s\"", iter->meta.etag.c_str()); s->formatter->dump_int("Size", iter->meta.accounted_size); s->formatter->dump_string("StorageClass", "STANDARD"); } dump_owner(s, iter->meta.owner, iter->meta.owner_display_name); s->formatter->close_section(); } if (objs_container) { s->formatter->close_section(); } if (!common_prefixes.empty()) { map::iterator pref_iter; for (pref_iter = common_prefixes.begin(); pref_iter != common_prefixes.end(); ++pref_iter) { s->formatter->open_array_section("CommonPrefixes"); s->formatter->dump_string("Prefix", pref_iter->first); s->formatter->close_section(); } } } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWListBucket_ObjStore_S3::send_response() { if (op_ret < 0) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); if (op_ret < 0) return; if (list_versions) { send_versioned_response(); return; } s->formatter->open_object_section_in_ns("ListBucketResult", XMLNS_AWS_S3); if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Name", s->bucket_name); s->formatter->dump_string("Prefix", prefix); s->formatter->dump_string("Marker", marker.name); if (is_truncated && !next_marker.empty()) s->formatter->dump_string("NextMarker", next_marker.name); s->formatter->dump_int("MaxKeys", max); if (!delimiter.empty()) s->formatter->dump_string("Delimiter", delimiter); s->formatter->dump_string("IsTruncated", (max && is_truncated ? "true" : "false")); bool encode_key = false; if (strcasecmp(encoding_type.c_str(), "url") == 0) { s->formatter->dump_string("EncodingType", "url"); encode_key = true; } if (op_ret >= 0) { vector::iterator iter; for (iter = objs.begin(); iter != objs.end(); ++iter) { rgw_obj_key key(iter->key); s->formatter->open_array_section("Contents"); if (encode_key) { string key_name; url_encode(key.name, key_name); s->formatter->dump_string("Key", key_name); } else { s->formatter->dump_string("Key", key.name); } dump_time(s, "LastModified", &iter->meta.mtime); s->formatter->dump_format("ETag", "\"%s\"", iter->meta.etag.c_str()); s->formatter->dump_int("Size", iter->meta.accounted_size); s->formatter->dump_string("StorageClass", "STANDARD"); dump_owner(s, iter->meta.owner, iter->meta.owner_display_name); if (s->system_request) { s->formatter->dump_string("RgwxTag", iter->tag); } s->formatter->close_section(); } if (!common_prefixes.empty()) { map::iterator pref_iter; for (pref_iter = common_prefixes.begin(); pref_iter != common_prefixes.end(); ++pref_iter) { s->formatter->open_array_section("CommonPrefixes"); s->formatter->dump_string("Prefix", pref_iter->first); s->formatter->close_section(); } } } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWGetBucketLogging_ObjStore_S3::send_response() { dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); s->formatter->open_object_section_in_ns("BucketLoggingStatus", XMLNS_AWS_S3); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWGetBucketLocation_ObjStore_S3::send_response() { dump_errno(s); end_header(s, this); dump_start(s); RGWZoneGroup zonegroup; string api_name; int ret = store->get_zonegroup(s->bucket_info.zonegroup, zonegroup); if (ret >= 0) { api_name = zonegroup.api_name; } else { if (s->bucket_info.zonegroup != "default") { api_name = s->bucket_info.zonegroup; } } s->formatter->dump_format_ns("LocationConstraint", XMLNS_AWS_S3, "%s", api_name.c_str()); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWGetBucketVersioning_ObjStore_S3::send_response() { dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); s->formatter->open_object_section_in_ns("VersioningConfiguration", XMLNS_AWS_S3); if (versioned) { const char *status = (versioning_enabled ? "Enabled" : "Suspended"); s->formatter->dump_string("Status", status); } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } class RGWSetBucketVersioningParser : public RGWXMLParser { XMLObj *alloc_obj(const char *el) override { return new XMLObj; } public: RGWSetBucketVersioningParser() {} ~RGWSetBucketVersioningParser() override {} int get_versioning_status(bool *status) { XMLObj *config = find_first("VersioningConfiguration"); if (!config) return -EINVAL; *status = false; XMLObj *field = config->find_first("Status"); if (!field) return 0; string& s = field->get_data(); if (stringcasecmp(s, "Enabled") == 0) { *status = true; } else if (stringcasecmp(s, "Suspended") != 0) { return -EINVAL; } return 0; } }; int RGWSetBucketVersioning_ObjStore_S3::get_params() { char *data = nullptr; int len = 0; int r = rgw_rest_read_all_input(s, &data, &len, s->cct->_conf->rgw_max_put_param_size, false); if (r < 0) { return r; } auto data_deleter = std::unique_ptr{data, free}; r = do_aws4_auth_completion(); if (r < 0) { return r; } RGWSetBucketVersioningParser parser; if (!parser.init()) { ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl; r = -EIO; return r; } if (!parser.parse(data, len, 1)) { ldout(s->cct, 10) << "failed to parse data: " << data << dendl; r = -EINVAL; return r; } if (!store->is_meta_master()) { /* only need to keep this data around if we're not meta master */ in_data.append(data, len); } r = parser.get_versioning_status(&enable_versioning); return r; } void RGWSetBucketVersioning_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s); } int RGWSetBucketWebsite_ObjStore_S3::get_params() { char *data = nullptr; int len = 0; const auto max_size = s->cct->_conf->rgw_max_put_param_size; int r = rgw_rest_read_all_input(s, &data, &len, max_size, false); if (r < 0) { return r; } auto data_deleter = std::unique_ptr{data, free}; r = do_aws4_auth_completion(); if (r < 0) { return r; } bufferptr in_ptr(data, len); in_data.append(in_ptr); RGWXMLDecoder::XMLParser parser; if (!parser.init()) { ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl; return -EIO; } if (!parser.parse(data, len, 1)) { string str(data, len); ldout(s->cct, 5) << "failed to parse xml: " << str << dendl; return -EINVAL; } try { RGWXMLDecoder::decode_xml("WebsiteConfiguration", website_conf, &parser, true); } catch (RGWXMLDecoder::err& err) { string str(data, len); ldout(s->cct, 5) << "unexpected xml: " << str << dendl; return -EINVAL; } return 0; } void RGWSetBucketWebsite_ObjStore_S3::send_response() { if (op_ret < 0) set_req_state_err(s, op_ret); dump_errno(s); end_header(s); } void RGWDeleteBucketWebsite_ObjStore_S3::send_response() { if (op_ret == 0) { op_ret = STATUS_NO_CONTENT; } set_req_state_err(s, op_ret); dump_errno(s); end_header(s); } void RGWGetBucketWebsite_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); if (op_ret < 0) { return; } RGWBucketWebsiteConf& conf = s->bucket_info.website_conf; s->formatter->open_object_section_in_ns("WebsiteConfiguration", XMLNS_AWS_S3); conf.dump_xml(s->formatter); s->formatter->close_section(); // WebsiteConfiguration rgw_flush_formatter_and_reset(s, s->formatter); } static void dump_bucket_metadata(struct req_state *s, RGWBucketEnt& bucket) { dump_header(s, "X-RGW-Object-Count", static_cast(bucket.count)); dump_header(s, "X-RGW-Bytes-Used", static_cast(bucket.size)); } void RGWStatBucket_ObjStore_S3::send_response() { if (op_ret >= 0) { dump_bucket_metadata(s, bucket); } set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this); dump_start(s); } static int create_s3_policy(struct req_state *s, RGWRados *store, RGWAccessControlPolicy_S3& s3policy, ACLOwner& owner) { if (s->has_acl_header) { if (!s->canned_acl.empty()) return -ERR_INVALID_REQUEST; return s3policy.create_from_headers(store, s->info.env, owner); } return s3policy.create_canned(owner, s->bucket_owner, s->canned_acl); } class RGWLocationConstraint : public XMLObj { public: RGWLocationConstraint() {} ~RGWLocationConstraint() override {} bool xml_end(const char *el) override { if (!el) return false; location_constraint = get_data(); return true; } string location_constraint; }; class RGWCreateBucketConfig : public XMLObj { public: RGWCreateBucketConfig() {} ~RGWCreateBucketConfig() override {} }; class RGWCreateBucketParser : public RGWXMLParser { XMLObj *alloc_obj(const char *el) override { return new XMLObj; } public: RGWCreateBucketParser() {} ~RGWCreateBucketParser() override {} bool get_location_constraint(string& zone_group) { XMLObj *config = find_first("CreateBucketConfiguration"); if (!config) return false; XMLObj *constraint = config->find_first("LocationConstraint"); if (!constraint) return false; zone_group = constraint->get_data(); return true; } }; int RGWCreateBucket_ObjStore_S3::get_params() { RGWAccessControlPolicy_S3 s3policy(s->cct); int r = create_s3_policy(s, store, s3policy, s->owner); if (r < 0) return r; policy = s3policy; int len = 0; char *data = nullptr; const auto max_size = s->cct->_conf->rgw_max_put_param_size; op_ret = rgw_rest_read_all_input(s, &data, &len, max_size, false); if ((op_ret < 0) && (op_ret != -ERR_LENGTH_REQUIRED)) return op_ret; auto data_deleter = std::unique_ptr{data, free}; const int auth_ret = do_aws4_auth_completion(); if (auth_ret < 0) { return auth_ret; } bufferptr in_ptr(data, len); in_data.append(in_ptr); if (len) { RGWCreateBucketParser parser; if (!parser.init()) { ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl; return -EIO; } bool success = parser.parse(data, len, 1); ldout(s->cct, 20) << "create bucket input data=" << data << dendl; if (!success) { ldout(s->cct, 0) << "failed to parse input: " << data << dendl; return -EINVAL; } if (!parser.get_location_constraint(location_constraint)) { ldout(s->cct, 0) << "provided input did not specify location constraint correctly" << dendl; return -EINVAL; } ldout(s->cct, 10) << "create bucket location constraint: " << location_constraint << dendl; } size_t pos = location_constraint.find(':'); if (pos != string::npos) { placement_rule = location_constraint.substr(pos + 1); location_constraint = location_constraint.substr(0, pos); } return 0; } void RGWCreateBucket_ObjStore_S3::send_response() { if (op_ret == -ERR_BUCKET_EXISTS) op_ret = 0; if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s); if (op_ret < 0) return; if (s->system_request) { JSONFormatter f; /* use json formatter for system requests output */ f.open_object_section("info"); encode_json("entry_point_object_ver", ep_objv, &f); encode_json("object_ver", info.objv_tracker.read_version, &f); encode_json("bucket_info", info, &f); f.close_section(); rgw_flush_formatter_and_reset(s, &f); } } void RGWDeleteBucket_ObjStore_S3::send_response() { int r = op_ret; if (!r) r = STATUS_NO_CONTENT; set_req_state_err(s, r); dump_errno(s); end_header(s, this); if (s->system_request) { JSONFormatter f; /* use json formatter for system requests output */ f.open_object_section("info"); encode_json("object_ver", objv_tracker.read_version, &f); f.close_section(); rgw_flush_formatter_and_reset(s, &f); } } int RGWPutObj_ObjStore_S3::get_params() { if (!s->length) return -ERR_LENGTH_REQUIRED; RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); map src_attrs; size_t pos; int ret; RGWAccessControlPolicy_S3 s3policy(s->cct); ret = create_s3_policy(s, store, s3policy, s->owner); if (ret < 0) return ret; policy = s3policy; if_match = s->info.env->get("HTTP_IF_MATCH"); if_nomatch = s->info.env->get("HTTP_IF_NONE_MATCH"); copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); copy_source_range = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE_RANGE"); /* handle x-amz-copy-source */ if (copy_source) { if (*copy_source == '/') ++copy_source; copy_source_bucket_name = copy_source; pos = copy_source_bucket_name.find("/"); if (pos == std::string::npos) { ret = -EINVAL; ldout(s->cct, 5) << "x-amz-copy-source bad format" << dendl; return ret; } copy_source_object_name = copy_source_bucket_name.substr(pos + 1, copy_source_bucket_name.size()); copy_source_bucket_name = copy_source_bucket_name.substr(0, pos); #define VERSION_ID_STR "?versionId=" pos = copy_source_object_name.find(VERSION_ID_STR); if (pos == std::string::npos) { copy_source_object_name = url_decode(copy_source_object_name); } else { copy_source_version_id = copy_source_object_name.substr(pos + sizeof(VERSION_ID_STR) - 1); copy_source_object_name = url_decode(copy_source_object_name.substr(0, pos)); } pos = copy_source_bucket_name.find(":"); if (pos == std::string::npos) { copy_source_tenant_name = s->src_tenant_name; } else { copy_source_tenant_name = copy_source_bucket_name.substr(0, pos); copy_source_bucket_name = copy_source_bucket_name.substr(pos + 1, copy_source_bucket_name.size()); if (copy_source_bucket_name.empty()) { ret = -EINVAL; ldout(s->cct, 5) << "source bucket name is empty" << dendl; return ret; } } ret = store->get_bucket_info(obj_ctx, copy_source_tenant_name, copy_source_bucket_name, copy_source_bucket_info, NULL, &src_attrs); if (ret < 0) { ldout(s->cct, 5) << __func__ << "(): get_bucket_info() returned ret=" << ret << dendl; return ret; } /* handle x-amz-copy-source-range */ if (copy_source_range) { string range = copy_source_range; pos = range.find("="); if (pos == std::string::npos) { ret = -EINVAL; ldout(s->cct, 5) << "x-amz-copy-source-range bad format" << dendl; return ret; } range = range.substr(pos + 1); pos = range.find("-"); if (pos == std::string::npos) { ret = -EINVAL; ldout(s->cct, 5) << "x-amz-copy-source-range bad format" << dendl; return ret; } string first = range.substr(0, pos); string last = range.substr(pos + 1); copy_source_range_fst = strtoull(first.c_str(), NULL, 10); copy_source_range_lst = strtoull(last.c_str(), NULL, 10); } } /* copy_source */ /* handle object tagging */ auto tag_str = s->info.env->get("HTTP_X_AMZ_TAGGING"); if (tag_str){ obj_tags = ceph::make_unique(); ret = obj_tags->set_from_string(tag_str); if (ret < 0){ ldout(s->cct,0) << "setting obj tags failed with " << ret << dendl; if (ret == -ERR_INVALID_TAG){ ret = -EINVAL; //s3 returns only -EINVAL for PUT requests } return ret; } } return RGWPutObj_ObjStore::get_params(); } int RGWPutObj_ObjStore_S3::get_data(bufferlist& bl) { const int ret = RGWPutObj_ObjStore::get_data(bl); if (ret == 0) { const int ret_auth = do_aws4_auth_completion(); if (ret_auth < 0) { return ret_auth; } } return ret; } static int get_success_retcode(int code) { switch (code) { case 201: return STATUS_CREATED; case 204: return STATUS_NO_CONTENT; } return 0; } void RGWPutObj_ObjStore_S3::send_response() { if (op_ret) { set_req_state_err(s, op_ret); dump_errno(s); } else { if (s->cct->_conf->rgw_s3_success_create_obj_status) { op_ret = get_success_retcode( s->cct->_conf->rgw_s3_success_create_obj_status); set_req_state_err(s, op_ret); } if (!copy_source) { dump_errno(s); dump_etag(s, etag); dump_content_length(s, 0); for (auto &it : crypt_http_responses) dump_header(s, it.first, it.second); } else { dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); struct tm tmp; utime_t ut(mtime); time_t secs = (time_t)ut.sec(); gmtime_r(&secs, &tmp); char buf[TIME_BUF_SIZE]; s->formatter->open_object_section_in_ns("CopyPartResult", "http://s3.amazonaws.com/doc/2006-03-01/"); if (strftime(buf, sizeof(buf), "%Y-%m-%dT%T.000Z", &tmp) > 0) { s->formatter->dump_string("LastModified", buf); } s->formatter->dump_string("ETag", etag); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); return; } } if (s->system_request && !real_clock::is_zero(mtime)) { dump_epoch_header(s, "Rgwx-Mtime", mtime); } end_header(s, this); } static inline int get_obj_attrs(RGWRados *store, struct req_state *s, rgw_obj& obj, map& attrs) { RGWRados::Object op_target(store, s->bucket_info, *static_cast(s->obj_ctx), obj); RGWRados::Object::Read read_op(&op_target); read_op.params.attrs = &attrs; return read_op.prepare(); } static inline void set_attr(map& attrs, const char* key, const std::string& value) { bufferlist bl; ::encode(value,bl); attrs.emplace(key, std::move(bl)); } static inline void set_attr(map& attrs, const char* key, const char* value) { bufferlist bl; ::encode(value,bl); attrs.emplace(key, std::move(bl)); } int RGWPutObj_ObjStore_S3::get_decrypt_filter( std::unique_ptr* filter, RGWGetDataCB* cb, map& attrs, bufferlist* manifest_bl) { std::map crypt_http_responses_unused; int res = 0; std::unique_ptr block_crypt; res = rgw_s3_prepare_decrypt(s, attrs, &block_crypt, crypt_http_responses_unused); if (res == 0) { if (block_crypt != nullptr) { auto f = std::unique_ptr(new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt))); //RGWGetObj_BlockDecrypt* f = new RGWGetObj_BlockDecrypt(s->cct, cb, std::move(block_crypt)); if (f != nullptr) { if (manifest_bl != nullptr) { res = f->read_manifest(*manifest_bl); if (res == 0) { *filter = std::move(f); } } } } } return res; } int RGWPutObj_ObjStore_S3::get_encrypt_filter( std::unique_ptr* filter, RGWPutObjDataProcessor* cb) { int res = 0; RGWPutObjProcessor_Multipart* multi_processor=dynamic_cast(cb); if (multi_processor != nullptr) { RGWMPObj* mp = nullptr; multi_processor->get_mp(&mp); if (mp != nullptr) { map xattrs; string meta_oid; meta_oid = mp->get_meta(); rgw_obj obj; obj.init_ns(s->bucket, meta_oid, RGW_OBJ_NS_MULTIPART); obj.set_in_extra_data(true); res = get_obj_attrs(store, s, obj, xattrs); if (res == 0) { std::unique_ptr block_crypt; /* We are adding to existing object. * We use crypto mode that configured as if we were decrypting. */ res = rgw_s3_prepare_decrypt(s, xattrs, &block_crypt, crypt_http_responses); if (res == 0 && block_crypt != nullptr) *filter = std::unique_ptr( new RGWPutObj_BlockEncrypt(s->cct, cb, std::move(block_crypt))); } } /* it is ok, to not have encryption at all */ } else { std::unique_ptr block_crypt; res = rgw_s3_prepare_encrypt(s, attrs, nullptr, &block_crypt, crypt_http_responses); if (res == 0 && block_crypt != nullptr) { *filter = std::unique_ptr( new RGWPutObj_BlockEncrypt(s->cct, cb, std::move(block_crypt))); } } return res; } void RGWPostObj_ObjStore_S3::rebuild_key(string& key) { static string var = "${filename}"; int pos = key.find(var); if (pos < 0) return; string new_key = key.substr(0, pos); new_key.append(filename); new_key.append(key.substr(pos + var.size())); key = new_key; } std::string RGWPostObj_ObjStore_S3::get_current_filename() const { return s->object.name; } std::string RGWPostObj_ObjStore_S3::get_current_content_type() const { return content_type; } int RGWPostObj_ObjStore_S3::get_params() { op_ret = RGWPostObj_ObjStore::get_params(); if (op_ret < 0) { return op_ret; } ldout(s->cct, 20) << "adding bucket to policy env: " << s->bucket.name << dendl; env.add_var("bucket", s->bucket.name); bool done; do { struct post_form_part part; int r = read_form_part_header(&part, done); if (r < 0) return r; if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 20)) { ldout(s->cct, 20) << "read part header -- part.name=" << part.name << dendl; for (const auto& pair : part.fields) { ldout(s->cct, 20) << "field.name=" << pair.first << dendl; ldout(s->cct, 20) << "field.val=" << pair.second.val << dendl; ldout(s->cct, 20) << "field.params:" << dendl; for (const auto& param_pair : pair.second.params) { ldout(s->cct, 20) << " " << param_pair.first << " -> " << param_pair.second << dendl; } } } if (done) { /* unexpected here */ err_msg = "Malformed request"; return -EINVAL; } if (stringcasecmp(part.name, "file") == 0) { /* beginning of data transfer */ struct post_part_field& field = part.fields["Content-Disposition"]; map::iterator iter = field.params.find("filename"); if (iter != field.params.end()) { filename = iter->second; } parts[part.name] = part; break; } bool boundary; uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; r = read_data(part.data, chunk_size, boundary, done); if (r < 0 || !boundary) { err_msg = "Couldn't find boundary"; return -EINVAL; } parts[part.name] = part; string part_str(part.data.c_str(), part.data.length()); env.add_var(part.name, part_str); } while (!done); string object_str; if (!part_str(parts, "key", &object_str)) { err_msg = "Key not specified"; return -EINVAL; } s->object = rgw_obj_key(object_str); rebuild_key(s->object.name); if (s->object.empty()) { err_msg = "Empty object name"; return -EINVAL; } env.add_var("key", s->object.name); part_str(parts, "Content-Type", &content_type); env.add_var("Content-Type", content_type); map::iterator piter = parts.upper_bound(RGW_AMZ_META_PREFIX); for (; piter != parts.end(); ++piter) { string n = piter->first; if (strncasecmp(n.c_str(), RGW_AMZ_META_PREFIX, sizeof(RGW_AMZ_META_PREFIX) - 1) != 0) break; string attr_name = RGW_ATTR_PREFIX; attr_name.append(n); /* need to null terminate it */ bufferlist& data = piter->second.data; string str = string(data.c_str(), data.length()); bufferlist attr_bl; attr_bl.append(str.c_str(), str.size() + 1); attrs[attr_name] = attr_bl; } // TODO: refactor this and the above loop to share code piter = parts.find(RGW_AMZ_WEBSITE_REDIRECT_LOCATION); if (piter != parts.end()) { string n = piter->first; string attr_name = RGW_ATTR_PREFIX; attr_name.append(n); /* need to null terminate it */ bufferlist& data = piter->second.data; string str = string(data.c_str(), data.length()); bufferlist attr_bl; attr_bl.append(str.c_str(), str.size() + 1); attrs[attr_name] = attr_bl; } int r = get_policy(); if (r < 0) return r; r = get_tags(); if (r < 0) return r; min_len = post_policy.min_length; max_len = post_policy.max_length; return 0; } int RGWPostObj_ObjStore_S3::get_tags() { string tags_str; if (part_str(parts, "tagging", &tags_str)) { RGWObjTagsXMLParser parser; if (!parser.init()){ ldout(s->cct, 0) << "Couldn't init RGWObjTags XML parser" << dendl; err_msg = "Server couldn't process the request"; return -EINVAL; // TODO: This class of errors in rgw code should be a 5XX error } if (!parser.parse(tags_str.c_str(), tags_str.size(), 1)) { ldout(s->cct,0 ) << "Invalid Tagging XML" << dendl; err_msg = "Invalid Tagging XML"; return -EINVAL; } RGWObjTagSet_S3 *obj_tags_s3; RGWObjTagging_S3 *tagging; tagging = static_cast(parser.find_first("Tagging")); obj_tags_s3 = static_cast(tagging->find_first("TagSet")); if(!obj_tags_s3){ return -ERR_MALFORMED_XML; } RGWObjTags obj_tags; int r = obj_tags_s3->rebuild(obj_tags); if (r < 0) return r; bufferlist tags_bl; obj_tags.encode(tags_bl); ldout(s->cct, 20) << "Read " << obj_tags.count() << "tags" << dendl; attrs[RGW_ATTR_TAGS] = tags_bl; } return 0; } int RGWPostObj_ObjStore_S3::get_policy() { if (part_bl(parts, "policy", &s->auth.s3_postobj_creds.encoded_policy)) { bool aws4_auth = false; /* x-amz-algorithm handling */ using rgw::auth::s3::AWS4_HMAC_SHA256_STR; if ((part_str(parts, "x-amz-algorithm", &s->auth.s3_postobj_creds.x_amz_algorithm)) && (s->auth.s3_postobj_creds.x_amz_algorithm == AWS4_HMAC_SHA256_STR)) { ldout(s->cct, 0) << "Signature verification algorithm AWS v4 (AWS4-HMAC-SHA256)" << dendl; aws4_auth = true; } else { ldout(s->cct, 0) << "Signature verification algorithm AWS v2" << dendl; } // check that the signature matches the encoded policy if (aws4_auth) { /* AWS4 */ /* x-amz-credential handling */ if (!part_str(parts, "x-amz-credential", &s->auth.s3_postobj_creds.x_amz_credential)) { ldout(s->cct, 0) << "No S3 aws4 credential found!" << dendl; err_msg = "Missing aws4 credential"; return -EINVAL; } /* x-amz-signature handling */ if (!part_str(parts, "x-amz-signature", &s->auth.s3_postobj_creds.signature)) { ldout(s->cct, 0) << "No aws4 signature found!" << dendl; err_msg = "Missing aws4 signature"; return -EINVAL; } /* x-amz-date handling */ std::string received_date_str; if (!part_str(parts, "x-amz-date", &received_date_str)) { ldout(s->cct, 0) << "No aws4 date found!" << dendl; err_msg = "Missing aws4 date"; return -EINVAL; } } else { /* AWS2 */ // check that the signature matches the encoded policy if (!part_str(parts, "AWSAccessKeyId", &s->auth.s3_postobj_creds.access_key)) { ldout(s->cct, 0) << "No S3 aws2 access key found!" << dendl; err_msg = "Missing aws2 access key"; return -EINVAL; } if (!part_str(parts, "signature", &s->auth.s3_postobj_creds.signature)) { ldout(s->cct, 0) << "No aws2 signature found!" << dendl; err_msg = "Missing aws2 signature"; return -EINVAL; } } /* FIXME: this is a makeshift solution. The browser upload authentication will be * handled by an instance of rgw::auth::Completer spawned in Handler's authorize() * method. */ const int ret = rgw::auth::Strategy::apply(auth_registry_ptr->get_s3_post(), s); if (ret != 0) { return -EACCES; } else { /* Populate the owner info. */ s->owner.set_id(s->user->user_id); s->owner.set_name(s->user->display_name); ldout(s->cct, 20) << "Successful Signature Verification!" << dendl; } ceph::bufferlist decoded_policy; try { decoded_policy.decode_base64(s->auth.s3_postobj_creds.encoded_policy); } catch (buffer::error& err) { ldout(s->cct, 0) << "failed to decode_base64 policy" << dendl; err_msg = "Could not decode policy"; return -EINVAL; } decoded_policy.append('\0'); // NULL terminate ldout(s->cct, 20) << "POST policy: " << decoded_policy.c_str() << dendl; int r = post_policy.from_json(decoded_policy, err_msg); if (r < 0) { if (err_msg.empty()) { err_msg = "Failed to parse policy"; } ldout(s->cct, 0) << "failed to parse policy" << dendl; return -EINVAL; } if (aws4_auth) { /* AWS4 */ post_policy.set_var_checked("x-amz-signature"); } else { /* AWS2 */ post_policy.set_var_checked("AWSAccessKeyId"); post_policy.set_var_checked("signature"); } post_policy.set_var_checked("policy"); r = post_policy.check(&env, err_msg); if (r < 0) { if (err_msg.empty()) { err_msg = "Policy check failed"; } ldout(s->cct, 0) << "policy check failed" << dendl; return r; } } else { ldout(s->cct, 0) << "No attached policy found!" << dendl; } string canned_acl; part_str(parts, "acl", &canned_acl); RGWAccessControlPolicy_S3 s3policy(s->cct); ldout(s->cct, 20) << "canned_acl=" << canned_acl << dendl; if (s3policy.create_canned(s->owner, s->bucket_owner, canned_acl) < 0) { err_msg = "Bad canned ACLs"; return -EINVAL; } policy = s3policy; return 0; } int RGWPostObj_ObjStore_S3::complete_get_params() { bool done; do { struct post_form_part part; int r = read_form_part_header(&part, done); if (r < 0) { return r; } ceph::bufferlist part_data; bool boundary; uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; r = read_data(part.data, chunk_size, boundary, done); if (r < 0 || !boundary) { return -EINVAL; } /* Just reading the data but not storing any results of that. */ } while (!done); return 0; } int RGWPostObj_ObjStore_S3::get_data(ceph::bufferlist& bl, bool& again) { bool boundary; bool done; const uint64_t chunk_size = s->cct->_conf->rgw_max_chunk_size; int r = read_data(bl, chunk_size, boundary, done); if (r < 0) { return r; } if (boundary) { if (!done) { /* Reached end of data, let's drain the rest of the params */ r = complete_get_params(); if (r < 0) { return r; } } } again = !boundary; return bl.length(); } void RGWPostObj_ObjStore_S3::send_response() { if (op_ret == 0 && parts.count("success_action_redirect")) { string redirect; part_str(parts, "success_action_redirect", &redirect); string tenant; string bucket; string key; string etag_str = "\""; etag_str.append(etag); etag_str.append("\""); string etag_url; url_encode(s->bucket_tenant, tenant); /* surely overkill, but cheap */ url_encode(s->bucket_name, bucket); url_encode(s->object.name, key); url_encode(etag_str, etag_url); if (!s->bucket_tenant.empty()) { /* * What we really would like is to quaily the bucket name, so * that the client could simply copy it and paste into next request. * Unfortunately, in S3 we cannot know if the client will decide * to come through DNS, with "bucket.tenant" sytanx, or through * URL with "tenant\bucket" syntax. Therefore, we provide the * tenant separately. */ redirect.append("?tenant="); redirect.append(tenant); redirect.append("&bucket="); redirect.append(bucket); } else { redirect.append("?bucket="); redirect.append(bucket); } redirect.append("&key="); redirect.append(key); redirect.append("&etag="); redirect.append(etag_url); int r = check_utf8(redirect.c_str(), redirect.size()); if (r < 0) { op_ret = r; goto done; } dump_redirect(s, redirect); op_ret = STATUS_REDIRECT; } else if (op_ret == 0 && parts.count("success_action_status")) { string status_string; uint32_t status_int; part_str(parts, "success_action_status", &status_string); int r = stringtoul(status_string, &status_int); if (r < 0) { op_ret = r; goto done; } switch (status_int) { case 200: break; case 201: op_ret = STATUS_CREATED; break; default: op_ret = STATUS_NO_CONTENT; break; } } else if (! op_ret) { op_ret = STATUS_NO_CONTENT; } done: if (op_ret == STATUS_CREATED) { for (auto &it : crypt_http_responses) dump_header(s, it.first, it.second); s->formatter->open_object_section("PostResponse"); if (g_conf->rgw_dns_name.length()) s->formatter->dump_format("Location", "%s/%s", s->info.script_uri.c_str(), s->object.name.c_str()); if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Bucket", s->bucket_name); s->formatter->dump_string("Key", s->object.name); s->formatter->close_section(); } s->err.message = err_msg; set_req_state_err(s, op_ret); dump_errno(s); if (op_ret >= 0) { dump_content_length(s, s->formatter->get_len()); } end_header(s, this); if (op_ret != STATUS_CREATED) return; rgw_flush_formatter_and_reset(s, s->formatter); } int RGWPostObj_ObjStore_S3::get_encrypt_filter( std::unique_ptr* filter, RGWPutObjDataProcessor* cb) { int res = 0; std::unique_ptr block_crypt; res = rgw_s3_prepare_encrypt(s, attrs, &parts, &block_crypt, crypt_http_responses); if (res == 0 && block_crypt != nullptr) { *filter = std::unique_ptr( new RGWPutObj_BlockEncrypt(s->cct, cb, std::move(block_crypt))); } else *filter = nullptr; return res; } int RGWDeleteObj_ObjStore_S3::get_params() { const char *if_unmod = s->info.env->get("HTTP_X_AMZ_DELETE_IF_UNMODIFIED_SINCE"); if (s->system_request) { s->info.args.get_bool(RGW_SYS_PARAM_PREFIX "no-precondition-error", &no_precondition_error, false); } if (if_unmod) { std::string if_unmod_decoded = url_decode(if_unmod); uint64_t epoch; uint64_t nsec; if (utime_t::parse_date(if_unmod_decoded, &epoch, &nsec) < 0) { ldout(s->cct, 10) << "failed to parse time: " << if_unmod_decoded << dendl; return -EINVAL; } unmod_since = utime_t(epoch, nsec).to_real_time(); } return 0; } void RGWDeleteObj_ObjStore_S3::send_response() { int r = op_ret; if (r == -ENOENT) r = 0; if (!r) r = STATUS_NO_CONTENT; set_req_state_err(s, r); dump_errno(s); if (!version_id.empty()) { dump_header(s, "x-amz-version-id", version_id); } if (delete_marker) { dump_header(s, "x-amz-delete-marker", "true"); } end_header(s, this); } int RGWCopyObj_ObjStore_S3::init_dest_policy() { RGWAccessControlPolicy_S3 s3policy(s->cct); /* build a policy for the target object */ int r = create_s3_policy(s, store, s3policy, s->owner); if (r < 0) return r; dest_policy = s3policy; return 0; } int RGWCopyObj_ObjStore_S3::get_params() { if_mod = s->info.env->get("HTTP_X_AMZ_COPY_IF_MODIFIED_SINCE"); if_unmod = s->info.env->get("HTTP_X_AMZ_COPY_IF_UNMODIFIED_SINCE"); if_match = s->info.env->get("HTTP_X_AMZ_COPY_IF_MATCH"); if_nomatch = s->info.env->get("HTTP_X_AMZ_COPY_IF_NONE_MATCH"); src_tenant_name = s->src_tenant_name; src_bucket_name = s->src_bucket_name; src_object = s->src_object; dest_tenant_name = s->bucket.tenant; dest_bucket_name = s->bucket.name; dest_object = s->object.name; if (s->system_request) { source_zone = s->info.args.get(RGW_SYS_PARAM_PREFIX "source-zone"); s->info.args.get_bool(RGW_SYS_PARAM_PREFIX "copy-if-newer", ©_if_newer, false); if (!source_zone.empty()) { client_id = s->info.args.get(RGW_SYS_PARAM_PREFIX "client-id"); op_id = s->info.args.get(RGW_SYS_PARAM_PREFIX "op-id"); if (client_id.empty() || op_id.empty()) { ldout(s->cct, 0) << RGW_SYS_PARAM_PREFIX "client-id or " RGW_SYS_PARAM_PREFIX "op-id were not provided, " "required for intra-region copy" << dendl; return -EINVAL; } } } const char *md_directive = s->info.env->get("HTTP_X_AMZ_METADATA_DIRECTIVE"); if (md_directive) { if (strcasecmp(md_directive, "COPY") == 0) { attrs_mod = RGWRados::ATTRSMOD_NONE; } else if (strcasecmp(md_directive, "REPLACE") == 0) { attrs_mod = RGWRados::ATTRSMOD_REPLACE; } else if (!source_zone.empty()) { attrs_mod = RGWRados::ATTRSMOD_NONE; // default for intra-zone_group copy } else { ldout(s->cct, 0) << "invalid metadata directive" << dendl; return -EINVAL; } } if (source_zone.empty() && (dest_tenant_name.compare(src_tenant_name) == 0) && (dest_bucket_name.compare(src_bucket_name) == 0) && (dest_object.compare(src_object.name) == 0) && src_object.instance.empty() && (attrs_mod != RGWRados::ATTRSMOD_REPLACE)) { /* can only copy object into itself if replacing attrs */ ldout(s->cct, 0) << "can't copy object into itself if not replacing attrs" << dendl; return -ERR_INVALID_REQUEST; } return 0; } void RGWCopyObj_ObjStore_S3::send_partial_response(off_t ofs) { if (! sent_header) { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); if (op_ret == 0) { s->formatter->open_object_section_in_ns("CopyObjectResult", XMLNS_AWS_S3); } sent_header = true; } else { /* Send progress field. Note that this diverge from the original S3 * spec. We do this in order to keep connection alive. */ s->formatter->dump_int("Progress", (uint64_t)ofs); } rgw_flush_formatter(s, s->formatter); } void RGWCopyObj_ObjStore_S3::send_response() { if (!sent_header) send_partial_response(0); if (op_ret == 0) { dump_time(s, "LastModified", &mtime); std::string etag_str = etag.to_str(); if (! etag_str.empty()) { s->formatter->dump_string("ETag", std::move(etag_str)); } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } } void RGWGetACLs_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); rgw_flush_formatter(s, s->formatter); dump_body(s, acls); } int RGWPutACLs_ObjStore_S3::get_params() { int ret = RGWPutACLs_ObjStore::get_params(); if (ret >= 0) { const int ret_auth = do_aws4_auth_completion(); if (ret_auth < 0) { return ret_auth; } } return ret; } int RGWPutACLs_ObjStore_S3::get_policy_from_state(RGWRados *store, struct req_state *s, stringstream& ss) { RGWAccessControlPolicy_S3 s3policy(s->cct); // bucket-* canned acls do not apply to bucket if (s->object.empty()) { if (s->canned_acl.find("bucket") != string::npos) s->canned_acl.clear(); } int r = create_s3_policy(s, store, s3policy, owner); if (r < 0) return r; s3policy.to_xml(ss); return 0; } void RGWPutACLs_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); } void RGWGetLC_ObjStore_S3::execute() { config.set_ctx(s->cct); map::iterator aiter = s->bucket_attrs.find(RGW_ATTR_LC); if (aiter == s->bucket_attrs.end()) { op_ret = -ENOENT; return; } bufferlist::iterator iter(&aiter->second); try { config.decode(iter); } catch (const buffer::error& e) { ldout(s->cct, 0) << __func__ << "decode life cycle config failed" << dendl; op_ret = -EIO; return; } } void RGWGetLC_ObjStore_S3::send_response() { if (op_ret) { if (op_ret == -ENOENT) { set_req_state_err(s, ERR_NO_SUCH_LC); } else { set_req_state_err(s, op_ret); } } dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); if (op_ret < 0) return; config.dump_xml(s->formatter); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWPutLC_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); } void RGWDeleteLC_ObjStore_S3::send_response() { if (op_ret == 0) op_ret = STATUS_NO_CONTENT; if (op_ret) { set_req_state_err(s, op_ret); } dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); } void RGWGetCORS_ObjStore_S3::send_response() { if (op_ret) { if (op_ret == -ENOENT) set_req_state_err(s, ERR_NOT_FOUND); else set_req_state_err(s, op_ret); } dump_errno(s); end_header(s, NULL, "application/xml"); dump_start(s); if (! op_ret) { string cors; RGWCORSConfiguration_S3 *s3cors = static_cast(&bucket_cors); stringstream ss; s3cors->to_xml(ss); cors = ss.str(); dump_body(s, cors); } } int RGWPutCORS_ObjStore_S3::get_params() { int r; char *data = nullptr; int len = 0; RGWCORSXMLParser_S3 parser(s->cct); RGWCORSConfiguration_S3 *cors_config; const auto max_size = s->cct->_conf->rgw_max_put_param_size; r = rgw_rest_read_all_input(s, &data, &len, max_size, false); if (r < 0) { return r; } auto data_deleter = std::unique_ptr{data, free}; r = do_aws4_auth_completion(); if (r < 0) { return r; } if (!parser.init()) { return -EINVAL; } if (!data || !parser.parse(data, len, 1)) { return -EINVAL; } cors_config = static_cast(parser.find_first( "CORSConfiguration")); if (!cors_config) { return -EINVAL; } // forward bucket cors requests to meta master zone if (!store->is_meta_master()) { /* only need to keep this data around if we're not meta master */ in_data.append(data, len); } if (s->cct->_conf->subsys.should_gather(ceph_subsys_rgw, 15)) { ldout(s->cct, 15) << "CORSConfiguration"; cors_config->to_xml(*_dout); *_dout << dendl; } cors_config->encode(cors_bl); return 0; } void RGWPutCORS_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, NULL, "application/xml"); dump_start(s); } void RGWDeleteCORS_ObjStore_S3::send_response() { int r = op_ret; if (!r || r == -ENOENT) r = STATUS_NO_CONTENT; set_req_state_err(s, r); dump_errno(s); end_header(s, NULL); } void RGWOptionsCORS_ObjStore_S3::send_response() { string hdrs, exp_hdrs; uint32_t max_age = CORS_MAX_AGE_INVALID; /*EACCES means, there is no CORS registered yet for the bucket *ENOENT means, there is no match of the Origin in the list of CORSRule */ if (op_ret == -ENOENT) op_ret = -EACCES; if (op_ret < 0) { set_req_state_err(s, op_ret); dump_errno(s); end_header(s, NULL); return; } get_response_params(hdrs, exp_hdrs, &max_age); dump_errno(s); dump_access_control(s, origin, req_meth, hdrs.c_str(), exp_hdrs.c_str(), max_age); end_header(s, NULL); } void RGWGetRequestPayment_ObjStore_S3::send_response() { dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); s->formatter->open_object_section_in_ns("RequestPaymentConfiguration", XMLNS_AWS_S3); const char *payer = requester_pays ? "Requester" : "BucketOwner"; s->formatter->dump_string("Payer", payer); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } class RGWSetRequestPaymentParser : public RGWXMLParser { XMLObj *alloc_obj(const char *el) override { return new XMLObj; } public: RGWSetRequestPaymentParser() {} ~RGWSetRequestPaymentParser() override {} int get_request_payment_payer(bool *requester_pays) { XMLObj *config = find_first("RequestPaymentConfiguration"); if (!config) return -EINVAL; *requester_pays = false; XMLObj *field = config->find_first("Payer"); if (!field) return 0; string& s = field->get_data(); if (stringcasecmp(s, "Requester") == 0) { *requester_pays = true; } else if (stringcasecmp(s, "BucketOwner") != 0) { return -EINVAL; } return 0; } }; int RGWSetRequestPayment_ObjStore_S3::get_params() { char *data; int len = 0; const auto max_size = s->cct->_conf->rgw_max_put_param_size; int r = rgw_rest_read_all_input(s, &data, &len, max_size, false); if (r < 0) { return r; } RGWSetRequestPaymentParser parser; if (!parser.init()) { ldout(s->cct, 0) << "ERROR: failed to initialize parser" << dendl; r = -EIO; goto done; } if (!parser.parse(data, len, 1)) { ldout(s->cct, 10) << "failed to parse data: " << data << dendl; r = -EINVAL; goto done; } r = parser.get_request_payment_payer(&requester_pays); done: free(data); return r; } void RGWSetRequestPayment_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s); } int RGWInitMultipart_ObjStore_S3::get_params() { RGWAccessControlPolicy_S3 s3policy(s->cct); op_ret = create_s3_policy(s, store, s3policy, s->owner); if (op_ret < 0) return op_ret; policy = s3policy; return 0; } void RGWInitMultipart_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); for (auto &it : crypt_http_responses) dump_header(s, it.first, it.second); end_header(s, this, "application/xml"); if (op_ret == 0) { dump_start(s); s->formatter->open_object_section_in_ns("InitiateMultipartUploadResult", XMLNS_AWS_S3); if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Bucket", s->bucket_name); s->formatter->dump_string("Key", s->object.name); s->formatter->dump_string("UploadId", upload_id); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } } int RGWInitMultipart_ObjStore_S3::prepare_encryption(map& attrs) { int res = 0; res = rgw_s3_prepare_encrypt(s, attrs, nullptr, nullptr, crypt_http_responses); return res; } int RGWCompleteMultipart_ObjStore_S3::get_params() { int ret = RGWCompleteMultipart_ObjStore::get_params(); if (ret < 0) { return ret; } return do_aws4_auth_completion(); } void RGWCompleteMultipart_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); if (op_ret == 0) { dump_start(s); s->formatter->open_object_section_in_ns("CompleteMultipartUploadResult", XMLNS_AWS_S3); if (!s->bucket_tenant.empty()) { if (s->info.domain.length()) { s->formatter->dump_format("Location", "%s.%s.%s", s->bucket_name.c_str(), s->bucket_tenant.c_str(), s->info.domain.c_str()); } s->formatter->dump_string("Tenant", s->bucket_tenant); } else { if (s->info.domain.length()) { s->formatter->dump_format("Location", "%s.%s", s->bucket_name.c_str(), s->info.domain.c_str()); } } s->formatter->dump_string("Bucket", s->bucket_name); s->formatter->dump_string("Key", s->object.name); s->formatter->dump_string("ETag", etag); s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } } void RGWAbortMultipart_ObjStore_S3::send_response() { int r = op_ret; if (!r) r = STATUS_NO_CONTENT; set_req_state_err(s, r); dump_errno(s); end_header(s, this); } void RGWListMultipart_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); if (op_ret == 0) { dump_start(s); s->formatter->open_object_section_in_ns("ListPartsResult", XMLNS_AWS_S3); map::iterator iter; map::reverse_iterator test_iter; int cur_max = 0; iter = parts.begin(); test_iter = parts.rbegin(); if (test_iter != parts.rend()) { cur_max = test_iter->first; } if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Bucket", s->bucket_name); s->formatter->dump_string("Key", s->object.name); s->formatter->dump_string("UploadId", upload_id); s->formatter->dump_string("StorageClass", "STANDARD"); s->formatter->dump_int("PartNumberMarker", marker); s->formatter->dump_int("NextPartNumberMarker", cur_max); s->formatter->dump_int("MaxParts", max_parts); s->formatter->dump_string("IsTruncated", (truncated ? "true" : "false")); ACLOwner& owner = policy.get_owner(); dump_owner(s, owner.get_id(), owner.get_display_name()); for (; iter != parts.end(); ++iter) { RGWUploadPartInfo& info = iter->second; s->formatter->open_object_section("Part"); dump_time(s, "LastModified", &info.modified); s->formatter->dump_unsigned("PartNumber", info.num); s->formatter->dump_format("ETag", "\"%s\"", info.etag.c_str()); s->formatter->dump_unsigned("Size", info.accounted_size); s->formatter->close_section(); } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } } void RGWListBucketMultiparts_ObjStore_S3::send_response() { if (op_ret < 0) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/xml"); dump_start(s); if (op_ret < 0) return; s->formatter->open_object_section_in_ns("ListMultipartUploadsResult", XMLNS_AWS_S3); if (!s->bucket_tenant.empty()) s->formatter->dump_string("Tenant", s->bucket_tenant); s->formatter->dump_string("Bucket", s->bucket_name); if (!prefix.empty()) s->formatter->dump_string("ListMultipartUploadsResult.Prefix", prefix); string& key_marker = marker.get_key(); if (!key_marker.empty()) s->formatter->dump_string("KeyMarker", key_marker); string& upload_id_marker = marker.get_upload_id(); if (!upload_id_marker.empty()) s->formatter->dump_string("UploadIdMarker", upload_id_marker); string next_key = next_marker.mp.get_key(); if (!next_key.empty()) s->formatter->dump_string("NextKeyMarker", next_key); string next_upload_id = next_marker.mp.get_upload_id(); if (!next_upload_id.empty()) s->formatter->dump_string("NextUploadIdMarker", next_upload_id); s->formatter->dump_int("MaxUploads", max_uploads); if (!delimiter.empty()) s->formatter->dump_string("Delimiter", delimiter); s->formatter->dump_string("IsTruncated", (is_truncated ? "true" : "false")); if (op_ret >= 0) { vector::iterator iter; for (iter = uploads.begin(); iter != uploads.end(); ++iter) { RGWMPObj& mp = iter->mp; s->formatter->open_array_section("Upload"); s->formatter->dump_string("Key", mp.get_key()); s->formatter->dump_string("UploadId", mp.get_upload_id()); dump_owner(s, s->user->user_id, s->user->display_name, "Initiator"); dump_owner(s, s->user->user_id, s->user->display_name); s->formatter->dump_string("StorageClass", "STANDARD"); dump_time(s, "Initiated", &iter->obj.meta.mtime); s->formatter->close_section(); } if (!common_prefixes.empty()) { s->formatter->open_array_section("CommonPrefixes"); map::iterator pref_iter; for (pref_iter = common_prefixes.begin(); pref_iter != common_prefixes.end(); ++pref_iter) { s->formatter->dump_string("CommonPrefixes.Prefix", pref_iter->first); } s->formatter->close_section(); } } s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } int RGWDeleteMultiObj_ObjStore_S3::get_params() { int ret = RGWDeleteMultiObj_ObjStore::get_params(); if (ret < 0) { return ret; } return do_aws4_auth_completion(); } void RGWDeleteMultiObj_ObjStore_S3::send_status() { if (! status_dumped) { if (op_ret < 0) set_req_state_err(s, op_ret); dump_errno(s); status_dumped = true; } } void RGWDeleteMultiObj_ObjStore_S3::begin_response() { if (!status_dumped) { send_status(); } dump_start(s); end_header(s, this, "application/xml"); s->formatter->open_object_section_in_ns("DeleteResult", XMLNS_AWS_S3); rgw_flush_formatter(s, s->formatter); } void RGWDeleteMultiObj_ObjStore_S3::send_partial_response(rgw_obj_key& key, bool delete_marker, const string& marker_version_id, int ret) { if (!key.empty()) { if (op_ret == 0 && !quiet) { s->formatter->open_object_section("Deleted"); s->formatter->dump_string("Key", key.name); if (!key.instance.empty()) { s->formatter->dump_string("VersionId", key.instance); } if (delete_marker) { s->formatter->dump_bool("DeleteMarker", true); s->formatter->dump_string("DeleteMarkerVersionId", marker_version_id); } s->formatter->close_section(); } else if (op_ret < 0) { struct rgw_http_error r; int err_no; s->formatter->open_object_section("Error"); err_no = -op_ret; rgw_get_errno_s3(&r, err_no); s->formatter->dump_string("Key", key.name); s->formatter->dump_string("VersionId", key.instance); s->formatter->dump_int("Code", r.http_ret); s->formatter->dump_string("Message", r.s3_code); s->formatter->close_section(); } rgw_flush_formatter(s, s->formatter); } } void RGWDeleteMultiObj_ObjStore_S3::end_response() { s->formatter->close_section(); rgw_flush_formatter_and_reset(s, s->formatter); } void RGWGetObjLayout_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this, "application/json"); JSONFormatter f; if (op_ret < 0) { return; } f.open_object_section("result"); ::encode_json("head", head_obj, &f); ::encode_json("manifest", *manifest, &f); f.open_array_section("data_location"); for (auto miter = manifest->obj_begin(); miter != manifest->obj_end(); ++miter) { f.open_object_section("obj"); rgw_raw_obj raw_loc = miter.get_location().get_raw_obj(store); ::encode_json("ofs", miter.get_ofs(), &f); ::encode_json("loc", raw_loc, &f); ::encode_json("loc_ofs", miter.location_ofs(), &f); ::encode_json("loc_size", miter.get_stripe_size(), &f); f.close_section(); rgw_flush_formatter(s, &f); } f.close_section(); f.close_section(); rgw_flush_formatter(s, &f); } int RGWConfigBucketMetaSearch_ObjStore_S3::get_params() { auto iter = s->info.x_meta_map.find("x-amz-meta-search"); if (iter == s->info.x_meta_map.end()) { s->err.message = "X-Rgw-Meta-Search header not provided"; ldout(s->cct, 5) << s->err.message << dendl; return -EINVAL; } list expressions; get_str_list(iter->second, ",", expressions); for (auto& expression : expressions) { vector args; get_str_vec(expression, ";", args); if (args.empty()) { s->err.message = "invalid empty expression"; ldout(s->cct, 5) << s->err.message << dendl; return -EINVAL; } if (args.size() > 2) { s->err.message = string("invalid expression: ") + expression; ldout(s->cct, 5) << s->err.message << dendl; return -EINVAL; } string key = boost::algorithm::to_lower_copy(rgw_trim_whitespace(args[0])); string val; if (args.size() > 1) { val = boost::algorithm::to_lower_copy(rgw_trim_whitespace(args[1])); } if (!boost::algorithm::starts_with(key, RGW_AMZ_META_PREFIX)) { s->err.message = string("invalid expression, key must start with '" RGW_AMZ_META_PREFIX "' : ") + expression; ldout(s->cct, 5) << s->err.message << dendl; return -EINVAL; } key = key.substr(sizeof(RGW_AMZ_META_PREFIX) - 1); ESEntityTypeMap::EntityType entity_type; if (val.empty() || val == "str" || val == "string") { entity_type = ESEntityTypeMap::ES_ENTITY_STR; } else if (val == "int" || val == "integer") { entity_type = ESEntityTypeMap::ES_ENTITY_INT; } else if (val == "date" || val == "datetime") { entity_type = ESEntityTypeMap::ES_ENTITY_DATE; } else { s->err.message = string("invalid entity type: ") + val; ldout(s->cct, 5) << s->err.message << dendl; return -EINVAL; } mdsearch_config[key] = entity_type; } return 0; } void RGWConfigBucketMetaSearch_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this); } void RGWGetBucketMetaSearch_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, NULL, "application/xml"); Formatter *f = s->formatter; f->open_array_section("GetBucketMetaSearchResult"); for (auto& e : s->bucket_info.mdsearch_config) { f->open_object_section("Entry"); string k = string("x-amz-meta-") + e.first; f->dump_string("Key", k.c_str()); const char *type; switch (e.second) { case ESEntityTypeMap::ES_ENTITY_INT: type = "int"; break; case ESEntityTypeMap::ES_ENTITY_DATE: type = "date"; break; default: type = "str"; } f->dump_string("Type", type); f->close_section(); } f->close_section(); rgw_flush_formatter(s, f); } void RGWDelBucketMetaSearch_ObjStore_S3::send_response() { if (op_ret) set_req_state_err(s, op_ret); dump_errno(s); end_header(s, this); } RGWOp *RGWHandler_REST_Service_S3::op_get() { if (is_usage_op()) { return new RGWGetUsage_ObjStore_S3; } else { return new RGWListBuckets_ObjStore_S3; } } RGWOp *RGWHandler_REST_Service_S3::op_head() { return new RGWListBuckets_ObjStore_S3; } RGWOp *RGWHandler_REST_Service_S3::op_post() { if (s->info.args.exists("Action")) { string action = s->info.args.get("Action"); if (action.compare("CreateRole") == 0) return new RGWCreateRole; if (action.compare("DeleteRole") == 0) return new RGWDeleteRole; if (action.compare("GetRole") == 0) return new RGWGetRole; if (action.compare("UpdateAssumeRolePolicy") == 0) return new RGWModifyRole; if (action.compare("ListRoles") == 0) return new RGWListRoles; if (action.compare("PutRolePolicy") == 0) return new RGWPutRolePolicy; if (action.compare("GetRolePolicy") == 0) return new RGWGetRolePolicy; if (action.compare("ListRolePolicies") == 0) return new RGWListRolePolicies; if (action.compare("DeleteRolePolicy") == 0) return new RGWDeleteRolePolicy; } return NULL; } RGWOp *RGWHandler_REST_Bucket_S3::get_obj_op(bool get_data) { // Non-website mode if (get_data) { return new RGWListBucket_ObjStore_S3; } else { return new RGWStatBucket_ObjStore_S3; } } RGWOp *RGWHandler_REST_Bucket_S3::op_get() { if (s->info.args.sub_resource_exists("logging")) return new RGWGetBucketLogging_ObjStore_S3; if (s->info.args.sub_resource_exists("location")) return new RGWGetBucketLocation_ObjStore_S3; if (s->info.args.sub_resource_exists("versioning")) return new RGWGetBucketVersioning_ObjStore_S3; if (s->info.args.sub_resource_exists("website")) { if (!s->cct->_conf->rgw_enable_static_website) { return NULL; } return new RGWGetBucketWebsite_ObjStore_S3; } if (s->info.args.exists("mdsearch")) { return new RGWGetBucketMetaSearch_ObjStore_S3; } if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } else if (is_cors_op()) { return new RGWGetCORS_ObjStore_S3; } else if (is_request_payment_op()) { return new RGWGetRequestPayment_ObjStore_S3; } else if (s->info.args.exists("uploads")) { return new RGWListBucketMultiparts_ObjStore_S3; } else if(is_lc_op()) { return new RGWGetLC_ObjStore_S3; } else if(is_policy_op()) { return new RGWGetBucketPolicy; } return get_obj_op(true); } RGWOp *RGWHandler_REST_Bucket_S3::op_head() { if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } else if (s->info.args.exists("uploads")) { return new RGWListBucketMultiparts_ObjStore_S3; } return get_obj_op(false); } RGWOp *RGWHandler_REST_Bucket_S3::op_put() { if (s->info.args.sub_resource_exists("logging")) return NULL; if (s->info.args.sub_resource_exists("versioning")) return new RGWSetBucketVersioning_ObjStore_S3; if (s->info.args.sub_resource_exists("website")) { if (!s->cct->_conf->rgw_enable_static_website) { return NULL; } return new RGWSetBucketWebsite_ObjStore_S3; } if (is_acl_op()) { return new RGWPutACLs_ObjStore_S3; } else if (is_cors_op()) { return new RGWPutCORS_ObjStore_S3; } else if (is_request_payment_op()) { return new RGWSetRequestPayment_ObjStore_S3; } else if(is_lc_op()) { return new RGWPutLC_ObjStore_S3; } else if(is_policy_op()) { return new RGWPutBucketPolicy; } return new RGWCreateBucket_ObjStore_S3; } RGWOp *RGWHandler_REST_Bucket_S3::op_delete() { if (is_cors_op()) { return new RGWDeleteCORS_ObjStore_S3; } else if(is_lc_op()) { return new RGWDeleteLC_ObjStore_S3; } else if(is_policy_op()) { return new RGWDeleteBucketPolicy; } if (s->info.args.sub_resource_exists("website")) { if (!s->cct->_conf->rgw_enable_static_website) { return NULL; } return new RGWDeleteBucketWebsite_ObjStore_S3; } if (s->info.args.exists("mdsearch")) { return new RGWDelBucketMetaSearch_ObjStore_S3; } return new RGWDeleteBucket_ObjStore_S3; } RGWOp *RGWHandler_REST_Bucket_S3::op_post() { if (s->info.args.exists("delete")) { return new RGWDeleteMultiObj_ObjStore_S3; } if (s->info.args.exists("mdsearch")) { return new RGWConfigBucketMetaSearch_ObjStore_S3; } return new RGWPostObj_ObjStore_S3; } RGWOp *RGWHandler_REST_Bucket_S3::op_options() { return new RGWOptionsCORS_ObjStore_S3; } RGWOp *RGWHandler_REST_Obj_S3::get_obj_op(bool get_data) { if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } RGWGetObj_ObjStore_S3 *get_obj_op = new RGWGetObj_ObjStore_S3; get_obj_op->set_get_data(get_data); return get_obj_op; } RGWOp *RGWHandler_REST_Obj_S3::op_get() { if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } else if (s->info.args.exists("uploadId")) { return new RGWListMultipart_ObjStore_S3; } else if (s->info.args.exists("layout")) { return new RGWGetObjLayout_ObjStore_S3; } else if (is_tagging_op()) { return new RGWGetObjTags_ObjStore_S3; } return get_obj_op(true); } RGWOp *RGWHandler_REST_Obj_S3::op_head() { if (is_acl_op()) { return new RGWGetACLs_ObjStore_S3; } else if (s->info.args.exists("uploadId")) { return new RGWListMultipart_ObjStore_S3; } return get_obj_op(false); } RGWOp *RGWHandler_REST_Obj_S3::op_put() { if (is_acl_op()) { return new RGWPutACLs_ObjStore_S3; } else if (is_tagging_op()) { return new RGWPutObjTags_ObjStore_S3; } if (s->init_state.src_bucket.empty()) return new RGWPutObj_ObjStore_S3; else return new RGWCopyObj_ObjStore_S3; } RGWOp *RGWHandler_REST_Obj_S3::op_delete() { if (is_tagging_op()) { return new RGWDeleteObjTags_ObjStore_S3; } string upload_id = s->info.args.get("uploadId"); if (upload_id.empty()) return new RGWDeleteObj_ObjStore_S3; else return new RGWAbortMultipart_ObjStore_S3; } RGWOp *RGWHandler_REST_Obj_S3::op_post() { if (s->info.args.exists("uploadId")) return new RGWCompleteMultipart_ObjStore_S3; if (s->info.args.exists("uploads")) return new RGWInitMultipart_ObjStore_S3; return new RGWPostObj_ObjStore_S3; } RGWOp *RGWHandler_REST_Obj_S3::op_options() { return new RGWOptionsCORS_ObjStore_S3; } int RGWHandler_REST_S3::init_from_header(struct req_state* s, int default_formatter, bool configurable_format) { string req; string first; const char *req_name = s->relative_uri.c_str(); const char *p; if (*req_name == '?') { p = req_name; } else { p = s->info.request_params.c_str(); } s->info.args.set(p); s->info.args.parse(); /* must be called after the args parsing */ int ret = allocate_formatter(s, default_formatter, configurable_format); if (ret < 0) return ret; if (*req_name != '/') return 0; req_name++; if (!*req_name) return 0; req = req_name; int pos = req.find('/'); if (pos >= 0) { first = req.substr(0, pos); } else { first = req; } /* * XXX The intent of the check for empty is apparently to let the bucket * name from DNS to be set ahead. However, we currently take the DNS * bucket and re-insert it into URL in rgw_rest.cc:RGWREST::preprocess(). * So, this check is meaningless. * * Rather than dropping this, the code needs to be changed into putting * the bucket (and its tenant) from DNS and Host: header (HTTP_HOST) * into req_status.bucket_name directly. */ if (s->init_state.url_bucket.empty()) { // Save bucket to tide us over until token is parsed. s->init_state.url_bucket = first; if (pos >= 0) { string encoded_obj_str = req.substr(pos+1); s->object = rgw_obj_key(encoded_obj_str, s->info.args.get("versionId")); } } else { s->object = rgw_obj_key(req_name, s->info.args.get("versionId")); } return 0; } int RGWHandler_REST_S3::postauth_init() { struct req_init_state *t = &s->init_state; bool relaxed_names = s->cct->_conf->rgw_relaxed_s3_bucket_names; rgw_parse_url_bucket(t->url_bucket, s->user->user_id.tenant, s->bucket_tenant, s->bucket_name); dout(10) << "s->object=" << (!s->object.empty() ? s->object : rgw_obj_key("")) << " s->bucket=" << rgw_make_bucket_entry_name(s->bucket_tenant, s->bucket_name) << dendl; int ret; ret = rgw_validate_tenant_name(s->bucket_tenant); if (ret) return ret; if (!s->bucket_name.empty()) { ret = valid_s3_bucket_name(s->bucket_name, relaxed_names); if (ret) return ret; ret = validate_object_name(s->object.name); if (ret) return ret; } if (!t->src_bucket.empty()) { rgw_parse_url_bucket(t->src_bucket, s->user->user_id.tenant, s->src_tenant_name, s->src_bucket_name); ret = rgw_validate_tenant_name(s->src_tenant_name); if (ret) return ret; ret = valid_s3_bucket_name(s->src_bucket_name, relaxed_names); if (ret) return ret; } return 0; } int RGWHandler_REST_S3::init(RGWRados *store, struct req_state *s, rgw::io::BasicClient *cio) { int ret; s->dialect = "s3"; ret = rgw_validate_tenant_name(s->bucket_tenant); if (ret) return ret; bool relaxed_names = s->cct->_conf->rgw_relaxed_s3_bucket_names; if (!s->bucket_name.empty()) { ret = valid_s3_bucket_name(s->bucket_name, relaxed_names); if (ret) return ret; ret = validate_object_name(s->object.name); if (ret) return ret; } const char *cacl = s->info.env->get("HTTP_X_AMZ_ACL"); if (cacl) s->canned_acl = cacl; s->has_acl_header = s->info.env->exists_prefix("HTTP_X_AMZ_GRANT"); const char *copy_source = s->info.env->get("HTTP_X_AMZ_COPY_SOURCE"); if (copy_source && !s->info.env->get("HTTP_X_AMZ_COPY_SOURCE_RANGE")) { ret = RGWCopyObj::parse_copy_location(copy_source, s->init_state.src_bucket, s->src_object); if (!ret) { ldout(s->cct, 0) << "failed to parse copy location" << dendl; return -EINVAL; // XXX why not -ERR_INVALID_BUCKET_NAME or -ERR_BAD_URL? } } return RGWHandler_REST::init(store, s, cio); } enum class AwsVersion { UNKOWN, V2, V4 }; enum class AwsRoute { UNKOWN, QUERY_STRING, HEADERS }; static inline std::pair discover_aws_flavour(const req_info& info) { using rgw::auth::s3::AWS4_HMAC_SHA256_STR; AwsVersion version = AwsVersion::UNKOWN; AwsRoute route = AwsRoute::UNKOWN; const char* http_auth = info.env->get("HTTP_AUTHORIZATION"); if (http_auth && http_auth[0]) { /* Authorization in Header */ route = AwsRoute::HEADERS; if (!strncmp(http_auth, AWS4_HMAC_SHA256_STR, strlen(AWS4_HMAC_SHA256_STR))) { /* AWS v4 */ version = AwsVersion::V4; } else if (!strncmp(http_auth, "AWS ", 4)) { /* AWS v2 */ version = AwsVersion::V2; } } else { route = AwsRoute::QUERY_STRING; if (info.args.get("X-Amz-Algorithm") == AWS4_HMAC_SHA256_STR) { /* AWS v4 */ version = AwsVersion::V4; } else if (!info.args.get("AWSAccessKeyId").empty()) { /* AWS v2 */ version = AwsVersion::V2; } } return std::make_pair(version, route); } static void init_anon_user(struct req_state *s) { rgw_get_anon_user(*(s->user)); s->perm_mask = RGW_PERM_FULL_CONTROL; } /* * verify that a signed request comes from the keyholder * by checking the signature against our locally-computed version * * it tries AWS v4 before AWS v2 */ int RGW_Auth_S3::authorize(RGWRados* const store, const rgw::auth::StrategyRegistry& auth_registry, struct req_state* const s) { /* neither keystone and rados enabled; warn and exit! */ if (!store->ctx()->_conf->rgw_s3_auth_use_rados && !store->ctx()->_conf->rgw_s3_auth_use_keystone && !store->ctx()->_conf->rgw_s3_auth_use_ldap) { dout(0) << "WARNING: no authorization backend enabled! Users will never authenticate." << dendl; return -EPERM; } const auto ret = rgw::auth::Strategy::apply(auth_registry.get_s3_main(), s); if (ret == 0) { /* Populate the owner info. */ s->owner.set_id(s->user->user_id); s->owner.set_name(s->user->display_name); } return ret; } int RGWHandler_Auth_S3::init(RGWRados *store, struct req_state *state, rgw::io::BasicClient *cio) { int ret = RGWHandler_REST_S3::init_from_header(state, RGW_FORMAT_JSON, true); if (ret < 0) return ret; return RGWHandler_REST::init(store, state, cio); } RGWHandler_REST* RGWRESTMgr_S3::get_handler(struct req_state* const s, const rgw::auth::StrategyRegistry& auth_registry, const std::string& frontend_prefix) { bool is_s3website = enable_s3website && (s->prot_flags & RGW_REST_WEBSITE); int ret = RGWHandler_REST_S3::init_from_header(s, is_s3website ? RGW_FORMAT_HTML : RGW_FORMAT_XML, true); if (ret < 0) return NULL; RGWHandler_REST* handler; // TODO: Make this more readable if (is_s3website) { if (s->init_state.url_bucket.empty()) { handler = new RGWHandler_REST_Service_S3Website(auth_registry); } else if (s->object.empty()) { handler = new RGWHandler_REST_Bucket_S3Website(auth_registry); } else { handler = new RGWHandler_REST_Obj_S3Website(auth_registry); } } else { if (s->init_state.url_bucket.empty()) { handler = new RGWHandler_REST_Service_S3(auth_registry); } else if (s->object.empty()) { handler = new RGWHandler_REST_Bucket_S3(auth_registry); } else { handler = new RGWHandler_REST_Obj_S3(auth_registry); } } ldout(s->cct, 20) << __func__ << " handler=" << typeid(*handler).name() << dendl; return handler; } bool RGWHandler_REST_S3Website::web_dir() const { std::string subdir_name = url_decode(s->object.name); if (subdir_name.empty()) { return false; } else if (subdir_name.back() == '/') { subdir_name.pop_back(); } rgw_obj obj(s->bucket, subdir_name); RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); obj_ctx.obj.set_atomic(obj); obj_ctx.obj.set_prefetch_data(obj); RGWObjState* state = nullptr; if (store->get_obj_state(&obj_ctx, s->bucket_info, obj, &state, false) < 0) { return false; } if (! state->exists) { return false; } return state->exists; } int RGWHandler_REST_S3Website::retarget(RGWOp* op, RGWOp** new_op) { *new_op = op; ldout(s->cct, 10) << __func__ << "Starting retarget" << dendl; if (!(s->prot_flags & RGW_REST_WEBSITE)) return 0; RGWObjectCtx& obj_ctx = *static_cast(s->obj_ctx); int ret = store->get_bucket_info(obj_ctx, s->bucket_tenant, s->bucket_name, s->bucket_info, NULL, &s->bucket_attrs); if (ret < 0) { // TODO-FUTURE: if the bucket does not exist, maybe expose it here? return -ERR_NO_SUCH_BUCKET; } if (!s->bucket_info.has_website) { // TODO-FUTURE: if the bucket has no WebsiteConfig, expose it here return -ERR_NO_SUCH_WEBSITE_CONFIGURATION; } rgw_obj_key new_obj; s->bucket_info.website_conf.get_effective_key(s->object.name, &new_obj.name, web_dir()); ldout(s->cct, 10) << "retarget get_effective_key " << s->object << " -> " << new_obj << dendl; RGWBWRoutingRule rrule; bool should_redirect = s->bucket_info.website_conf.should_redirect(new_obj.name, 0, &rrule); if (should_redirect) { const string& hostname = s->info.env->get("HTTP_HOST", ""); const string& protocol = (s->info.env->get("SERVER_PORT_SECURE") ? "https" : "http"); int redirect_code = 0; rrule.apply_rule(protocol, hostname, s->object.name, &s->redirect, &redirect_code); // APply a custom HTTP response code if (redirect_code > 0) s->err.http_ret = redirect_code; // Apply a custom HTTP response code ldout(s->cct, 10) << "retarget redirect code=" << redirect_code << " proto+host:" << protocol << "://" << hostname << " -> " << s->redirect << dendl; return -ERR_WEBSITE_REDIRECT; } /* * FIXME: if s->object != new_obj, drop op and create a new op to handle * operation. Or remove this comment if it's not applicable anymore */ s->object = new_obj; return 0; } RGWOp* RGWHandler_REST_S3Website::op_get() { return get_obj_op(true); } RGWOp* RGWHandler_REST_S3Website::op_head() { return get_obj_op(false); } int RGWHandler_REST_S3Website::serve_errordoc(int http_ret, const string& errordoc_key) { int ret = 0; s->formatter->reset(); /* Try to throw it all away */ std::shared_ptr getop( static_cast(op_get())); if (getop.get() == NULL) { return -1; // Trigger double error handler } getop->init(store, s, this); getop->range_str = NULL; getop->if_mod = NULL; getop->if_unmod = NULL; getop->if_match = NULL; getop->if_nomatch = NULL; s->object = errordoc_key; ret = init_permissions(getop.get()); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, init_permissions ret=" << ret << dendl; return -1; // Trigger double error handler } ret = read_permissions(getop.get()); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, read_permissions ret=" << ret << dendl; return -1; // Trigger double error handler } if (http_ret) { getop->set_custom_http_response(http_ret); } ret = getop->init_processing(); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, init_processing ret=" << ret << dendl; return -1; // Trigger double error handler } ret = getop->verify_op_mask(); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, verify_op_mask ret=" << ret << dendl; return -1; // Trigger double error handler } ret = getop->verify_permission(); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, verify_permission ret=" << ret << dendl; return -1; // Trigger double error handler } ret = getop->verify_params(); if (ret < 0) { ldout(s->cct, 20) << "serve_errordoc failed, verify_params ret=" << ret << dendl; return -1; // Trigger double error handler } // No going back now getop->pre_exec(); /* * FIXME Missing headers: * With a working errordoc, the s3 error fields are rendered as HTTP headers, * x-amz-error-code: NoSuchKey * x-amz-error-message: The specified key does not exist. * x-amz-error-detail-Key: foo */ getop->execute(); getop->complete(); return 0; } int RGWHandler_REST_S3Website::error_handler(int err_no, string* error_content) { int new_err_no = -1; rgw_http_errors::const_iterator r = rgw_http_s3_errors.find(err_no > 0 ? err_no : -err_no); int http_error_code = -1; if (r != rgw_http_s3_errors.end()) { http_error_code = r->second.first; } ldout(s->cct, 10) << "RGWHandler_REST_S3Website::error_handler err_no=" << err_no << " http_ret=" << http_error_code << dendl; RGWBWRoutingRule rrule; bool should_redirect = s->bucket_info.website_conf.should_redirect(s->object.name, http_error_code, &rrule); if (should_redirect) { const string& hostname = s->info.env->get("HTTP_HOST", ""); const string& protocol = (s->info.env->get("SERVER_PORT_SECURE") ? "https" : "http"); int redirect_code = 0; rrule.apply_rule(protocol, hostname, s->object.name, &s->redirect, &redirect_code); // Apply a custom HTTP response code if (redirect_code > 0) s->err.http_ret = redirect_code; // Apply a custom HTTP response code ldout(s->cct, 10) << "error handler redirect code=" << redirect_code << " proto+host:" << protocol << "://" << hostname << " -> " << s->redirect << dendl; return -ERR_WEBSITE_REDIRECT; } else if (err_no == -ERR_WEBSITE_REDIRECT) { // Do nothing here, this redirect will be handled in abort_early's ERR_WEBSITE_REDIRECT block // Do NOT fire the ErrorDoc handler } else if (!s->bucket_info.website_conf.error_doc.empty()) { /* This serves an entire page! On success, it will return zero, and no further content should be sent to the socket On failure, we need the double-error handler */ new_err_no = RGWHandler_REST_S3Website::serve_errordoc(http_error_code, s->bucket_info.website_conf.error_doc); if (new_err_no && new_err_no != -1) { err_no = new_err_no; } } else { ldout(s->cct, 20) << "No special error handling today!" << dendl; } return err_no; } RGWOp* RGWHandler_REST_Obj_S3Website::get_obj_op(bool get_data) { /** If we are in website mode, then it is explicitly impossible to run GET or * HEAD on the actual directory. We must convert the request to run on the * suffix object instead! */ RGWGetObj_ObjStore_S3Website* op = new RGWGetObj_ObjStore_S3Website; op->set_get_data(get_data); return op; } RGWOp* RGWHandler_REST_Bucket_S3Website::get_obj_op(bool get_data) { /** If we are in website mode, then it is explicitly impossible to run GET or * HEAD on the actual directory. We must convert the request to run on the * suffix object instead! */ RGWGetObj_ObjStore_S3Website* op = new RGWGetObj_ObjStore_S3Website; op->set_get_data(get_data); return op; } RGWOp* RGWHandler_REST_Service_S3Website::get_obj_op(bool get_data) { /** If we are in website mode, then it is explicitly impossible to run GET or * HEAD on the actual directory. We must convert the request to run on the * suffix object instead! */ RGWGetObj_ObjStore_S3Website* op = new RGWGetObj_ObjStore_S3Website; op->set_get_data(get_data); return op; } namespace rgw { namespace auth { namespace s3 { bool AWSGeneralAbstractor::is_time_skew_ok(const utime_t& header_time, const bool qsr) const { /* Check for time skew first. */ const time_t req_sec = header_time.sec(); time_t now; time(&now); if ((req_sec < now - RGW_AUTH_GRACE_MINS * 60 || req_sec > now + RGW_AUTH_GRACE_MINS * 60) && !qsr) { ldout(cct, 10) << "req_sec=" << req_sec << " now=" << now << "; now - RGW_AUTH_GRACE_MINS=" << now - RGW_AUTH_GRACE_MINS * 60 << "; now + RGW_AUTH_GRACE_MINS=" << now + RGW_AUTH_GRACE_MINS * 60 << dendl; ldout(cct, 0) << "NOTICE: request time skew too big now=" << utime_t(now, 0) << " req_time=" << header_time << dendl; return false; } else { return true; } } static rgw::auth::Completer::cmplptr_t null_completer_factory(const boost::optional& secret_key) { return nullptr; } AWSEngine::VersionAbstractor::auth_data_t AWSGeneralAbstractor::get_auth_data(const req_state* const s) const { AwsVersion version; AwsRoute route; std::tie(version, route) = discover_aws_flavour(s->info); if (version == AwsVersion::V2) { return get_auth_data_v2(s); } else if (version == AwsVersion::V4) { return get_auth_data_v4(s, route == AwsRoute::QUERY_STRING); } else { /* FIXME(rzarzynski): handle anon user. */ throw -EINVAL; } } boost::optional AWSGeneralAbstractor::get_v4_canonical_headers( const req_info& info, const boost::string_view& signedheaders, const bool using_qs) const { return rgw::auth::s3::get_v4_canonical_headers(info, signedheaders, using_qs, false); } AWSEngine::VersionAbstractor::auth_data_t AWSGeneralAbstractor::get_auth_data_v4(const req_state* const s, /* FIXME: const. */ bool using_qs) const { boost::string_view access_key_id; boost::string_view signed_hdrs; boost::string_view date; boost::string_view credential_scope; boost::string_view client_signature; int ret = rgw::auth::s3::parse_credentials(s->info, access_key_id, credential_scope, signed_hdrs, client_signature, date, using_qs); if (ret < 0) { throw ret; } /* craft canonical headers */ boost::optional canonical_headers = \ get_v4_canonical_headers(s->info, signed_hdrs, using_qs); if (canonical_headers) { ldout(s->cct, 10) << "canonical headers format = " << *canonical_headers << dendl; } else { throw -EPERM; } /* Get the expected hash. */ auto exp_payload_hash = rgw::auth::s3::get_v4_exp_payload_hash(s->info); /* Craft canonical URI. Using std::move later so let it be non-const. */ auto canonical_uri = rgw::auth::s3::get_v4_canonical_uri(s->info); /* Craft canonical query string. std::moving later so non-const here. */ auto canonical_qs = rgw::auth::s3::get_v4_canonical_qs(s->info, using_qs); /* Craft canonical request. */ auto canonical_req_hash = \ rgw::auth::s3::get_v4_canon_req_hash(s->cct, s->info.method, std::move(canonical_uri), std::move(canonical_qs), std::move(*canonical_headers), signed_hdrs, exp_payload_hash); auto string_to_sign = \ rgw::auth::s3::get_v4_string_to_sign(s->cct, AWS4_HMAC_SHA256_STR, date, credential_scope, std::move(canonical_req_hash)); const auto sig_factory = std::bind(rgw::auth::s3::get_v4_signature, credential_scope, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); /* Requests authenticated with the Query Parameters are treated as unsigned. * From "Authenticating Requests: Using Query Parameters (AWS Signature * Version 4)": * * You don't include a payload hash in the Canonical Request, because * when you create a presigned URL, you don't know the payload content * because the URL is used to upload an arbitrary payload. Instead, you * use a constant string UNSIGNED-PAYLOAD. * * This means we have absolutely no business in spawning completer. Both * aws4_auth_needs_complete and aws4_auth_streaming_mode are set to false * by default. We don't need to change that. */ if (is_v4_payload_unsigned(exp_payload_hash) || is_v4_payload_empty(s)) { return { access_key_id, client_signature, std::move(string_to_sign), sig_factory, null_completer_factory }; } else { /* We're going to handle a signed payload. Be aware that even empty HTTP * body (no payload) requires verification: * * The x-amz-content-sha256 header is required for all AWS Signature * Version 4 requests. It provides a hash of the request payload. If * there is no payload, you must provide the hash of an empty string. */ if (!is_v4_payload_streamed(exp_payload_hash)) { ldout(s->cct, 10) << "delaying v4 auth" << dendl; /* payload in a single chunk */ switch (s->op_type) { case RGW_OP_CREATE_BUCKET: case RGW_OP_PUT_OBJ: case RGW_OP_PUT_ACLS: case RGW_OP_PUT_CORS: case RGW_OP_COMPLETE_MULTIPART: case RGW_OP_SET_BUCKET_VERSIONING: case RGW_OP_DELETE_MULTI_OBJ: case RGW_OP_ADMIN_SET_METADATA: case RGW_OP_SET_BUCKET_WEBSITE: case RGW_OP_PUT_BUCKET_POLICY: case RGW_OP_PUT_OBJ_TAGGING: case RGW_OP_PUT_LC: break; default: dout(10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED" << dendl; throw -ERR_NOT_IMPLEMENTED; } const auto cmpl_factory = std::bind(AWSv4ComplSingle::create, s, std::placeholders::_1); return { access_key_id, client_signature, std::move(string_to_sign), sig_factory, cmpl_factory }; } else { /* IMHO "streamed" doesn't fit too good here. I would prefer to call * it "chunked" but let's be coherent with Amazon's terminology. */ dout(10) << "body content detected in multiple chunks" << dendl; /* payload in multiple chunks */ switch(s->op_type) { case RGW_OP_PUT_OBJ: break; default: dout(10) << "ERROR: AWS4 completion for this operation NOT IMPLEMENTED (streaming mode)" << dendl; throw -ERR_NOT_IMPLEMENTED; } dout(10) << "aws4 seed signature ok... delaying v4 auth" << dendl; /* In the case of streamed payload client sets the x-amz-content-sha256 * to "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" but uses "UNSIGNED-PAYLOAD" * when constructing the Canonical Request. */ /* In the case of single-chunk upload client set the header's value is * coherent with the one used for Canonical Request crafting. */ /* In the case of query string-based authentication there should be no * x-amz-content-sha256 header and the value "UNSIGNED-PAYLOAD" is used * for CanonReq. */ const auto cmpl_factory = std::bind(AWSv4ComplMulti::create, s, date, credential_scope, client_signature, std::placeholders::_1); return { access_key_id, client_signature, std::move(string_to_sign), sig_factory, cmpl_factory }; } } } boost::optional AWSGeneralBoto2Abstractor::get_v4_canonical_headers( const req_info& info, const boost::string_view& signedheaders, const bool using_qs) const { return rgw::auth::s3::get_v4_canonical_headers(info, signedheaders, using_qs, true); } AWSEngine::VersionAbstractor::auth_data_t AWSGeneralAbstractor::get_auth_data_v2(const req_state* const s) const { boost::string_view access_key_id; boost::string_view signature; bool qsr = false; const char* http_auth = s->info.env->get("HTTP_AUTHORIZATION"); if (! http_auth || http_auth[0] == '\0') { /* Credentials are provided in query string. We also need to verify * the "Expires" parameter now. */ access_key_id = s->info.args.get("AWSAccessKeyId"); signature = s->info.args.get("Signature"); qsr = true; boost::string_view expires = s->info.args.get("Expires"); if (! expires.empty()) { /* It looks we have the guarantee that expires is a null-terminated, * and thus string_view::data() can be safely used. */ const time_t exp = atoll(expires.data()); time_t now; time(&now); if (now >= exp) { throw -EPERM; } } } else { /* The "Authorization" HTTP header is being used. */ const boost::string_view auth_str(http_auth + strlen("AWS ")); const size_t pos = auth_str.rfind(':'); if (pos != boost::string_view::npos) { access_key_id = auth_str.substr(0, pos); signature = auth_str.substr(pos + 1); } } /* Let's canonize the HTTP headers that are covered by the AWS auth v2. */ std::string string_to_sign; utime_t header_time; if (! rgw_create_s3_canonical_header(s->info, &header_time, string_to_sign, qsr)) { ldout(cct, 10) << "failed to create the canonized auth header\n" << rgw::crypt_sanitize::auth{s,string_to_sign} << dendl; throw -EPERM; } ldout(cct, 10) << "string_to_sign:\n" << rgw::crypt_sanitize::auth{s,string_to_sign} << dendl; if (! is_time_skew_ok(header_time, qsr)) { throw -ERR_REQUEST_TIME_SKEWED; } return { std::move(access_key_id), std::move(signature), std::move(string_to_sign), rgw::auth::s3::get_v2_signature, null_completer_factory }; } AWSEngine::VersionAbstractor::auth_data_t AWSBrowserUploadAbstractor::get_auth_data_v2(const req_state* const s) const { return { s->auth.s3_postobj_creds.access_key, s->auth.s3_postobj_creds.signature, s->auth.s3_postobj_creds.encoded_policy.to_str(), rgw::auth::s3::get_v2_signature, null_completer_factory }; } AWSEngine::VersionAbstractor::auth_data_t AWSBrowserUploadAbstractor::get_auth_data_v4(const req_state* const s) const { const boost::string_view credential = s->auth.s3_postobj_creds.x_amz_credential; /* grab access key id */ const size_t pos = credential.find("/"); const boost::string_view access_key_id = credential.substr(0, pos); dout(10) << "access key id = " << access_key_id << dendl; /* grab credential scope */ const boost::string_view credential_scope = credential.substr(pos + 1); dout(10) << "credential scope = " << credential_scope << dendl; const auto sig_factory = std::bind(rgw::auth::s3::get_v4_signature, credential_scope, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); return { access_key_id, s->auth.s3_postobj_creds.signature, s->auth.s3_postobj_creds.encoded_policy.to_str(), sig_factory, null_completer_factory }; } AWSEngine::VersionAbstractor::auth_data_t AWSBrowserUploadAbstractor::get_auth_data(const req_state* const s) const { if (s->auth.s3_postobj_creds.x_amz_algorithm == AWS4_HMAC_SHA256_STR) { ldout(s->cct, 0) << "Signature verification algorithm AWS v4" << " (AWS4-HMAC-SHA256)" << dendl; return get_auth_data_v4(s); } else { ldout(s->cct, 0) << "Signature verification algorithm AWS v2" << dendl; return get_auth_data_v2(s); } } AWSEngine::result_t AWSEngine::authenticate(const req_state* const s) const { /* Small reminder: an ver_abstractor is allowed to throw! */ const auto auth_data = ver_abstractor.get_auth_data(s); if (auth_data.access_key_id.empty() || auth_data.client_signature.empty()) { return result_t::deny(-EINVAL); } else { return authenticate(auth_data.access_key_id, auth_data.client_signature, auth_data.string_to_sign, auth_data.signature_factory, auth_data.completer_factory, s); } } } /* namespace s3 */ } /* namespace auth */ } /* namespace rgw */ rgw::LDAPHelper* rgw::auth::s3::LDAPEngine::ldh = nullptr; std::mutex rgw::auth::s3::LDAPEngine::mtx; void rgw::auth::s3::LDAPEngine::init(CephContext* const cct) { if (! ldh) { std::lock_guard lck(mtx); if (! ldh) { const string& ldap_uri = cct->_conf->rgw_ldap_uri; const string& ldap_binddn = cct->_conf->rgw_ldap_binddn; const string& ldap_searchdn = cct->_conf->rgw_ldap_searchdn; const string& ldap_searchfilter = cct->_conf->rgw_ldap_searchfilter; const string& ldap_dnattr = cct->_conf->rgw_ldap_dnattr; std::string ldap_bindpw = parse_rgw_ldap_bindpw(cct); ldh = new rgw::LDAPHelper(ldap_uri, ldap_binddn, ldap_bindpw, ldap_searchdn, ldap_searchfilter, ldap_dnattr); ldh->init(); ldh->bind(); } } } rgw::auth::RemoteApplier::acl_strategy_t rgw::auth::s3::LDAPEngine::get_acl_strategy() const { //This is based on the assumption that the default acl strategy in // get_perms_from_aclspec, will take care. Extra acl spec is not required. return nullptr; } rgw::auth::RemoteApplier::AuthInfo rgw::auth::s3::LDAPEngine::get_creds_info(const rgw::RGWToken& token) const noexcept { /* The short form of "using" can't be used here -- we're aliasing a class' * member. */ using acct_privilege_t = \ rgw::auth::RemoteApplier::AuthInfo::acct_privilege_t; return rgw::auth::RemoteApplier::AuthInfo { rgw_user(token.id), token.id, RGW_PERM_FULL_CONTROL, acct_privilege_t::IS_PLAIN_ACCT, TYPE_LDAP }; } rgw::auth::Engine::result_t rgw::auth::s3::LDAPEngine::authenticate( const boost::string_view& access_key_id, const boost::string_view& signature, const string_to_sign_t& string_to_sign, const signature_factory_t&, const completer_factory_t& completer_factory, const req_state* const s) const { /* boost filters and/or string_ref may throw on invalid input */ rgw::RGWToken base64_token; try { base64_token = rgw::from_base64(access_key_id); } catch (...) { base64_token = std::string(""); } if (! base64_token.valid()) { return result_t::deny(); } //TODO: Uncomment, when we have a migration plan in place. //Check if a user of type other than 'ldap' is already present, if yes, then //return error. /*RGWUserInfo user_info; user_info.user_id = base64_token.id; if (rgw_get_user_info_by_uid(store, user_info.user_id, user_info) >= 0) { if (user_info.type != TYPE_LDAP) { ldout(cct, 10) << "ERROR: User id of type: " << user_info.type << " is already present" << dendl; return nullptr; } }*/ if (ldh->auth(base64_token.id, base64_token.key) != 0) { return result_t::deny(); } auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(), get_creds_info(base64_token)); return result_t::grant(std::move(apl), completer_factory(boost::none)); } /* LocalEndgine */ rgw::auth::Engine::result_t rgw::auth::s3::LocalEngine::authenticate( const boost::string_view& _access_key_id, const boost::string_view& signature, const string_to_sign_t& string_to_sign, const signature_factory_t& signature_factory, const completer_factory_t& completer_factory, const req_state* const s) const { /* get the user info */ RGWUserInfo user_info; /* TODO(rzarzynski): we need to have string-view taking variant. */ const std::string access_key_id = _access_key_id.to_string(); if (rgw_get_user_info_by_access_key(store, access_key_id, user_info) < 0) { ldout(cct, 5) << "error reading user info, uid=" << access_key_id << " can't authenticate" << dendl; return result_t::deny(-ERR_INVALID_ACCESS_KEY); } //TODO: Uncomment, when we have a migration plan in place. /*else { if (s->user->type != TYPE_RGW) { ldout(cct, 10) << "ERROR: User id of type: " << s->user->type << " is present" << dendl; throw -EPERM; } }*/ const auto iter = user_info.access_keys.find(access_key_id); if (iter == std::end(user_info.access_keys)) { ldout(cct, 0) << "ERROR: access key not encoded in user info" << dendl; return result_t::deny(-EPERM); } const RGWAccessKey& k = iter->second; const VersionAbstractor::server_signature_t server_signature = \ signature_factory(cct, k.key, string_to_sign); ldout(cct, 15) << "string_to_sign=" << rgw::crypt_sanitize::log_content{string_to_sign} << dendl; ldout(cct, 15) << "server signature=" << server_signature << dendl; ldout(cct, 15) << "client signature=" << signature << dendl; ldout(cct, 15) << "compare=" << signature.compare(server_signature) << dendl; if (static_cast(server_signature) != signature) { return result_t::deny(-ERR_SIGNATURE_NO_MATCH); } auto apl = apl_factory->create_apl_local(cct, s, user_info, k.subuser); return result_t::grant(std::move(apl), completer_factory(k.key)); } bool rgw::auth::s3::S3AnonymousEngine::is_applicable( const req_state* s ) const noexcept { if (s->op == OP_OPTIONS) { return true; } AwsVersion version; AwsRoute route; std::tie(version, route) = discover_aws_flavour(s->info); return route == AwsRoute::QUERY_STRING && version == AwsVersion::UNKOWN; }