From 386276ed00e3619e119bdd18d6fe20b656d2f05d Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Sun, 11 Feb 2024 12:21:45 -0500 Subject: [PATCH] rgw/iam: add Group/GroupPolicy APIs Signed-off-by: Casey Bodley --- src/rgw/CMakeLists.txt | 1 + src/rgw/rgw_auth_s3.cc | 15 + src/rgw/rgw_iam_policy.cc | 60 + src/rgw/rgw_iam_policy.h | 15 + src/rgw/rgw_op_type.h | 15 + src/rgw/rgw_rest_iam.cc | 46 + src/rgw/rgw_rest_iam.h | 25 + src/rgw/rgw_rest_iam_group.cc | 2114 +++++++++++++++++++++++++++ src/rgw/rgw_rest_iam_group.h | 40 + src/test/rgw/test_rgw_iam_policy.cc | 12 + 10 files changed, 2343 insertions(+) create mode 100644 src/rgw/rgw_rest_iam_group.cc create mode 100644 src/rgw/rgw_rest_iam_group.h diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index 33ed562ea6f..a9594fcbcd2 100644 --- a/src/rgw/CMakeLists.txt +++ b/src/rgw/CMakeLists.txt @@ -111,6 +111,7 @@ set(librgw_common_srcs rgw_rest_metadata.cc rgw_rest_ratelimit.cc rgw_rest_role.cc + rgw_rest_iam_group.cc rgw_rest_iam_user.cc rgw_rest_s3.cc rgw_rest_pubsub.cc diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index e43fe24c5eb..61c2118b672 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -514,6 +514,21 @@ bool is_non_s3_op(RGWOpType op_type) case RGW_OP_UPDATE_ACCESS_KEY: case RGW_OP_DELETE_ACCESS_KEY: case RGW_OP_LIST_ACCESS_KEYS: + case RGW_OP_CREATE_GROUP: + case RGW_OP_GET_GROUP: + case RGW_OP_UPDATE_GROUP: + case RGW_OP_DELETE_GROUP: + case RGW_OP_LIST_GROUPS: + case RGW_OP_ADD_USER_TO_GROUP: + case RGW_OP_REMOVE_USER_FROM_GROUP: + case RGW_OP_LIST_GROUPS_FOR_USER: + case RGW_OP_PUT_GROUP_POLICY: + case RGW_OP_GET_GROUP_POLICY: + case RGW_OP_LIST_GROUP_POLICIES: + case RGW_OP_DELETE_GROUP_POLICY: + case RGW_OP_ATTACH_GROUP_POLICY: + case RGW_OP_DETACH_GROUP_POLICY: + case RGW_OP_LIST_ATTACHED_GROUP_POLICIES: return true; default: return false; diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index f00cbfeac36..cc8243022c5 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -173,6 +173,21 @@ static const actpair actpairs[] = { "iam:UpdateAccessKey", iamUpdateAccessKey}, { "iam:DeleteAccessKey", iamDeleteAccessKey}, { "iam:ListAccessKeys", iamListAccessKeys}, + { "iam:CreateGroup", iamCreateGroup}, + { "iam:GetGroup", iamGetGroup}, + { "iam:UpdateGroup", iamUpdateGroup}, + { "iam:DeleteGroup", iamDeleteGroup}, + { "iam:ListGroups", iamListGroups}, + { "iam:AddUserToGroup", iamAddUserToGroup}, + { "iam:RemoveUserFromGroup", iamRemoveUserFromGroup}, + { "iam:ListGroupsForUser", iamListGroupsForUser}, + { "iam:PutGroupPolicy", iamPutGroupPolicy }, + { "iam:GetGroupPolicy", iamGetGroupPolicy }, + { "iam:ListGroupPolicies", iamListGroupPolicies }, + { "iam:DeleteGroupPolicy", iamDeleteGroupPolicy }, + { "iam:AttachGroupPolicy", iamAttachGroupPolicy }, + { "iam:DetachGroupPolicy", iamDetachGroupPolicy }, + { "iam:ListAttachedGroupPolicies", iamListAttachedGroupPolicies }, { "iam:GenerateCredentialReport", iamGenerateCredentialReport}, { "iam:GenerateServiceLastAccessedDetails", iamGenerateServiceLastAccessedDetails}, { "iam:SimulateCustomPolicy", iamSimulateCustomPolicy}, @@ -1568,6 +1583,51 @@ const char* action_bit_string(uint64_t action) { case iamListAccessKeys: return "iam:ListAccessKeys"; + case iamCreateGroup: + return "iam:CreateGroup"; + + case iamGetGroup: + return "iam:GetGroup"; + + case iamUpdateGroup: + return "iam:UpdateGroup"; + + case iamDeleteGroup: + return "iam:DeleteGroup"; + + case iamListGroups: + return "iam:ListGroups"; + + case iamAddUserToGroup: + return "iam:AddUserToGroup"; + + case iamRemoveUserFromGroup: + return "iam:RemoveUserFromGroup"; + + case iamListGroupsForUser: + return "iam:ListGroupsForUser"; + + case iamPutGroupPolicy: + return "iam:PutGroupPolicy"; + + case iamGetGroupPolicy: + return "iam:GetGroupPolicy"; + + case iamListGroupPolicies: + return "iam:ListGroupPolicies"; + + case iamDeleteGroupPolicy: + return "iam:DeleteGroupPolicy"; + + case iamAttachGroupPolicy: + return "iam:AttachGroupPolicy"; + + case iamDetachGroupPolicy: + return "iam:DetachGroupPolicy"; + + case iamListAttachedGroupPolicies: + return "iam:ListAttachedGroupPolicies"; + case iamGenerateCredentialReport: return "iam:GenerateCredentialReport"; diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index cf5310bfc6f..6fa2437bcb4 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -156,6 +156,21 @@ enum { iamUpdateAccessKey, iamDeleteAccessKey, iamListAccessKeys, + iamCreateGroup, + iamGetGroup, + iamUpdateGroup, + iamDeleteGroup, + iamListGroups, + iamAddUserToGroup, + iamRemoveUserFromGroup, + iamListGroupsForUser, + iamPutGroupPolicy, + iamGetGroupPolicy, + iamListGroupPolicies, + iamDeleteGroupPolicy, + iamAttachGroupPolicy, + iamDetachGroupPolicy, + iamListAttachedGroupPolicies, iamGenerateCredentialReport, iamGenerateServiceLastAccessedDetails, iamSimulateCustomPolicy, diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h index 79edf3f2f50..fc661b51dbe 100644 --- a/src/rgw/rgw_op_type.h +++ b/src/rgw/rgw_op_type.h @@ -100,6 +100,21 @@ enum RGWOpType { RGW_OP_UPDATE_ACCESS_KEY, RGW_OP_DELETE_ACCESS_KEY, RGW_OP_LIST_ACCESS_KEYS, + RGW_OP_CREATE_GROUP, + RGW_OP_GET_GROUP, + RGW_OP_UPDATE_GROUP, + RGW_OP_DELETE_GROUP, + RGW_OP_LIST_GROUPS, + RGW_OP_ADD_USER_TO_GROUP, + RGW_OP_REMOVE_USER_FROM_GROUP, + RGW_OP_LIST_GROUPS_FOR_USER, + RGW_OP_PUT_GROUP_POLICY, + RGW_OP_GET_GROUP_POLICY, + RGW_OP_LIST_GROUP_POLICIES, + RGW_OP_DELETE_GROUP_POLICY, + RGW_OP_ATTACH_GROUP_POLICY, + RGW_OP_DETACH_GROUP_POLICY, + RGW_OP_LIST_ATTACHED_GROUP_POLICIES, /* rgw specific */ RGW_OP_ADMIN_SET_METADATA, RGW_OP_GET_OBJ_LAYOUT, diff --git a/src/rgw/rgw_rest_iam.cc b/src/rgw/rgw_rest_iam.cc index 80b4228b513..079a9f46071 100644 --- a/src/rgw/rgw_rest_iam.cc +++ b/src/rgw/rgw_rest_iam.cc @@ -10,6 +10,7 @@ #include "rgw_rest_role.h" #include "rgw_rest_user_policy.h" #include "rgw_rest_oidc_provider.h" +#include "rgw_rest_iam_group.h" #include "rgw_rest_iam_user.h" #include "rgw_rest_conn.h" #include "driver/rados/rgw_zone.h" @@ -57,6 +58,21 @@ static const std::unordered_map op_generators = {"UpdateAccessKey", make_iam_update_access_key_op}, {"DeleteAccessKey", make_iam_delete_access_key_op}, {"ListAccessKeys", make_iam_list_access_keys_op}, + {"CreateGroup", make_iam_create_group_op}, + {"GetGroup", make_iam_get_group_op}, + {"UpdateGroup", make_iam_update_group_op}, + {"DeleteGroup", make_iam_delete_group_op}, + {"ListGroups", make_iam_list_groups_op}, + {"AddUserToGroup", make_iam_add_user_to_group_op}, + {"RemoveUserFromGroup", make_iam_remove_user_from_group_op}, + {"ListGroupsForUser", make_iam_list_groups_for_user_op}, + {"PutGroupPolicy", make_iam_put_group_policy_op}, + {"GetGroupPolicy", make_iam_get_group_policy_op}, + {"ListGroupPolicies", make_iam_list_group_policies_op}, + {"DeleteGroupPolicy", make_iam_delete_group_policy_op}, + {"AttachGroupPolicy", make_iam_attach_group_policy_op}, + {"DetachGroupPolicy", make_iam_detach_group_policy_op}, + {"ListAttachedGroupPolicies", make_iam_list_attached_group_policies_op}, }; bool RGWHandler_REST_IAM::action_exists(const req_state* s) @@ -189,6 +205,26 @@ bool validate_iam_role_name(const std::string& name, std::string& err) return true; } +static constexpr size_t MAX_GROUP_NAME_LEN = 128; + +bool validate_iam_group_name(const std::string& name, std::string& err) +{ + if (name.empty()) { + err = "Missing required element GroupName"; + return false; + } + if (name.size() > MAX_GROUP_NAME_LEN) { + err = "GroupName too long"; + return false; + } + const std::regex pattern("[\\w+=,.@-]+"); + if (!std::regex_match(name, pattern)) { + err = "GroupName contains invalid characters"; + return false; + } + return true; +} + static constexpr size_t MAX_PATH_LEN = 512; bool validate_iam_path(const std::string& path, std::string& err) @@ -220,6 +256,16 @@ std::string iam_user_arn(const RGWUserInfo& info) acct, path, info.display_name); } +std::string iam_group_arn(const RGWGroupInfo& info) +{ + std::string_view path = info.path; + if (path.empty()) { + path = "/"; + } + return fmt::format("arn:aws:iam::{}:group{}{}", + info.account_id, path, info.name); +} + int forward_iam_request_to_master(const DoutPrefixProvider* dpp, const rgw::SiteConfig& site, const RGWUserInfo& user, diff --git a/src/rgw/rgw_rest_iam.h b/src/rgw/rgw_rest_iam.h index a9bb7127e03..0c2b262bd62 100644 --- a/src/rgw/rgw_rest_iam.h +++ b/src/rgw/rgw_rest_iam.h @@ -3,6 +3,8 @@ #pragma once +#include + #include "rgw_auth.h" #include "rgw_auth_filters.h" #include "rgw_rest.h" @@ -12,14 +14,17 @@ class DoutPrefixProvider; namespace rgw { class SiteConfig; } struct RGWUserInfo; +struct RGWGroupInfo; bool validate_iam_policy_name(const std::string& name, std::string& err); bool validate_iam_policy_arn(const std::string& arn, std::string& err); bool validate_iam_user_name(const std::string& name, std::string& err); bool validate_iam_role_name(const std::string& name, std::string& err); +bool validate_iam_group_name(const std::string& name, std::string& err); bool validate_iam_path(const std::string& path, std::string& err); std::string iam_user_arn(const RGWUserInfo& info); +std::string iam_group_arn(const RGWGroupInfo& info); int forward_iam_request_to_master(const DoutPrefixProvider* dpp, const rgw::SiteConfig& site, @@ -28,6 +33,26 @@ int forward_iam_request_to_master(const DoutPrefixProvider* dpp, RGWXMLDecoder::XMLParser& parser, req_info& req, optional_yield y); +/// Perform an atomic read-modify-write operation on the given group metadata. +/// Racing writes are detected here as ECANCELED errors, where we reload the +/// updated group metadata and retry the operation. +template F> +int retry_raced_group_write(const DoutPrefixProvider* dpp, optional_yield y, + rgw::sal::Driver* driver, RGWGroupInfo& info, + rgw::sal::Attrs& attrs, RGWObjVersionTracker& objv, + const F& f) +{ + int r = f(); + for (int i = 0; i < 10 && r == -ECANCELED; ++i) { + objv.clear(); + r = driver->load_group_by_id(dpp, y, info.id, info, attrs, objv); + if (r >= 0) { + r = f(); + } + } + return r; +} + class RGWHandler_REST_IAM : public RGWHandler_REST { const rgw::auth::StrategyRegistry& auth_registry; bufferlist bl_post_body; diff --git a/src/rgw/rgw_rest_iam_group.cc b/src/rgw/rgw_rest_iam_group.cc new file mode 100644 index 00000000000..307ba8937ba --- /dev/null +++ b/src/rgw/rgw_rest_iam_group.cc @@ -0,0 +1,2114 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright contributors to the Ceph project + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "rgw_rest_iam_group.h" + +#include +#include "include/buffer.h" +#include "common/errno.h" +#include "rgw_arn.h" +#include "rgw_common.h" +#include "rgw_iam_managed_policy.h" +#include "rgw_op.h" +#include "rgw_process_env.h" +#include "rgw_rest.h" +#include "rgw_rest_iam.h" + + +static std::string make_resource_name(const RGWGroupInfo& info) +{ + std::string_view path = info.path; + if (path.empty()) { + path = "/"; + } + return string_cat_reserve(path, info.name); +} + +static void dump_iam_group(const RGWGroupInfo& info, Formatter* f) +{ + encode_json("Path", info.path, f); + encode_json("GroupName", info.name, f); + encode_json("GroupId", info.id, f); + encode_json("Arn", iam_group_arn(info), f); +} + +static void dump_iam_user(const RGWUserInfo& info, Formatter* f) +{ + encode_json("Path", info.path, f); + encode_json("UserName", info.display_name, f); + encode_json("UserId", info.user_id, f); + encode_json("Arn", iam_user_arn(info), f); +} + + +// CreateGroup +class RGWCreateGroup_IAM : public RGWOp { + bufferlist post_body; + RGWGroupInfo info; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site, std::string& uid); + public: + explicit RGWCreateGroup_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "create_group"; } + RGWOpType get_type() override { return RGW_OP_CREATE_GROUP; } +}; + +int RGWCreateGroup_IAM::init_processing(optional_yield y) +{ + // use account id from authenticated user/role. with AssumeRole, this may not + // match the account of s->user + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + info.path = s->info.args.get("Path"); + if (info.path.empty()) { + info.path = "/"; + } else if (!validate_iam_path(info.path, s->err.message)) { + return -EINVAL; + } + + info.name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(info.name, s->err.message)) { + return -EINVAL; + } + + return 0; +} + +int RGWCreateGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamCreateGroup, true)) { + return 0; + } + return -EACCES; +} + +int RGWCreateGroup_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site, + std::string& id) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("Path"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + + XMLObj* response = parser.find_first("CreateGroupResponse");; + if (!response) { + ldpp_dout(this, 5) << "ERROR: unexpected xml: CreateGroupResponse" << dendl; + return -EINVAL; + } + + XMLObj* result = response->find_first("CreateGroupResult"); + if (!result) { + ldpp_dout(this, 5) << "ERROR: unexpected xml: CreateGroupResult" << dendl; + return -EINVAL; + } + + XMLObj* group = result->find_first("Group"); + if (!group) { + ldpp_dout(this, 5) << "ERROR: unexpected xml: Group" << dendl; + return -EINVAL; + } + + try { + RGWXMLDecoder::decode_xml("GroupId", id, group, true); + } catch (RGWXMLDecoder::err& err) { + ldpp_dout(this, 5) << "ERROR: unexpected xml: GroupId" << dendl; + return -EINVAL; + } + + ldpp_dout(this, 4) << "group id decoded from forwarded response is " << id << dendl; + return 0; +} + +void RGWCreateGroup_IAM::execute(optional_yield y) +{ + { + // check the current group count against account limit + RGWAccountInfo account; + rgw::sal::Attrs attrs; // unused + RGWObjVersionTracker objv; // unused + op_ret = driver->load_account_by_id(this, y, info.account_id, + account, attrs, objv); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to load iam account " + << info.account_id << ": " << cpp_strerror(op_ret) << dendl; + } + + if (account.max_groups >= 0) { // max_groups < 0 means unlimited + uint32_t count = 0; + op_ret = driver->count_account_groups(this, y, info.account_id, count); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to count groups for iam account " + << info.account_id << ": " << cpp_strerror(op_ret) << dendl; + return; + } + if (std::cmp_greater_equal(count, account.max_groups)) { + s->err.message = fmt::format("Group limit {} exceeded", + account.max_groups); + op_ret = -ERR_LIMIT_EXCEEDED; + return; + } + } + } + + // generate group id. forward_to_master() may overwrite this + uuid_d uuid; + uuid.generate_random(); + info.id = uuid.to_string(); + info.tenant = s->auth.identity->get_tenant(); + + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site, info.id); + if (op_ret) { + return; + } + } + + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + objv.generate_new_write_ver(get_cct()); + constexpr bool exclusive = true; + op_ret = driver->store_group(this, y, info, attrs, objv, exclusive, nullptr); +} + +void RGWCreateGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "CreateGroupResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "CreateGroupResult"}; + Formatter::ObjectSection group{*f, "Group"}; + dump_iam_group(info, f); + // /Group + // /CreateGroupResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /CreateGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// GetGroup +class RGWGetGroup_IAM : public RGWOp { + rgw_account_id account_id; + RGWGroupInfo info; + std::string marker; + int max_items = 100; + rgw::sal::UserList listing; + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "get_group"; } + RGWOpType get_type() override { return RGW_OP_GET_GROUP; } +}; + +int RGWGetGroup_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + marker = s->info.args.get("Marker"); + + int r = s->info.args.get_int("MaxItems", &max_items, max_items); + if (r < 0 || max_items > 1000) { + s->err.message = "Invalid value for MaxItems"; + return -EINVAL; + } + + rgw::sal::Attrs attrs_ignored; + RGWObjVersionTracker objv_ignored; + r = driver->load_group_by_name(this, y, account_id, name, info, + attrs_ignored, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWGetGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamGetGroup, true)) { + return 0; + } + return -EACCES; +} + +void RGWGetGroup_IAM::execute(optional_yield y) +{ + const auto& tenant = s->auth.identity->get_tenant(); + op_ret = driver->list_group_users(this, y, tenant, info.id, + marker, max_items, listing); +} + +void RGWGetGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "GetGroupResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "GetGroupResult"}; + { + Formatter::ObjectSection Group{*f, "Group"}; + dump_iam_group(info, f); + } // /Group + { + Formatter::ArraySection users{*f, "Users"}; + for (const auto& user : listing.users) { + Formatter::ObjectSection result{*f, "member"}; + dump_iam_user(user, f); + } // /member + } // /Users + const bool is_truncated = !listing.next_marker.empty(); + f->dump_bool("IsTruncated", is_truncated); + if (is_truncated) { + f->dump_string("Marker", listing.next_marker); + } + // /GetGroupResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /GetGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// UpdateGroup +class RGWUpdateGroup_IAM : public RGWOp { + bufferlist post_body; + std::string new_path; + std::string new_name; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWUpdateGroup_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "update_group"; } + RGWOpType get_type() override { return RGW_OP_UPDATE_GROUP; } +}; + +int RGWUpdateGroup_IAM::init_processing(optional_yield y) +{ + rgw_account_id account_id; + if (const auto* id = std::get_if(&s->owner.id); id) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + new_path = s->info.args.get("NewPath"); + if (!new_path.empty() && !validate_iam_path(new_path, s->err.message)) { + return -EINVAL; + } + + new_name = s->info.args.get("NewGroupName"); + if (!new_name.empty() && + !validate_iam_group_name(new_name, s->err.message)) { + return -EINVAL; + } + + const std::string name = s->info.args.get("GroupName"); + if (name.empty()) { + s->err.message = "Missing required element GroupName"; + return -EINVAL; + } + + int r = driver->load_group_by_name(this, y, account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWUpdateGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamUpdateGroup, true)) { + return 0; + } + return -EACCES; +} + +int RGWUpdateGroup_IAM::forward_to_master(optional_yield y, const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("NewPath"); + s->info.args.remove("NewGroupName"); + s->info.args.remove("GroupName"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWUpdateGroup_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y] { + const RGWGroupInfo old_info = info; + + if (!new_path.empty()) { + info.path = new_path; + } + if (!new_name.empty()) { + info.name = new_name; + } + + if (info.path == old_info.path && + info.name == old_info.name) { + return 0; // nothing to do, return success + } + + constexpr bool exclusive = false; + return driver->store_group(this, y, info, attrs, objv, + exclusive, &old_info); + }); +} + +void RGWUpdateGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "UpdateGroupResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "UpdateGroupResult"}; + Formatter::ObjectSection group{*f, "Group"}; + dump_iam_group(info, f); + // /Group + // /UpdateGroupResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /UpdateGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// DeleteGroup +class RGWDeleteGroup_IAM : public RGWOp { + bufferlist post_body; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + int check_empty(optional_yield y); + public: + explicit RGWDeleteGroup_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "delete_group"; } + RGWOpType get_type() override { return RGW_OP_DELETE_GROUP; } +}; + +int RGWDeleteGroup_IAM::init_processing(optional_yield y) +{ + rgw_account_id account_id; + if (const auto* id = std::get_if(&s->owner.id); id) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (name.empty()) { + s->err.message = "Missing required element GroupName"; + return -EINVAL; + } + + int r = driver->load_group_by_name(this, y, account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWDeleteGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamDeleteGroup, true)) { + return 0; + } + return -EACCES; +} + +int RGWDeleteGroup_IAM::forward_to_master(optional_yield y, const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +int RGWDeleteGroup_IAM::check_empty(optional_yield y) +{ + if (!s->penv.site->is_meta_master()) { + // only check on the master zone. if a forwarded DeleteGroup request + // succeeds on the master zone, it needs to succeed here too + return 0; + } + + // verify that all policies are removed first + if (auto p = attrs.find(RGW_ATTR_IAM_POLICY); p != attrs.end()) { + std::map policies; + try { + decode(policies, p->second); + } catch (const buffer::error&) { + ldpp_dout(this, 0) << "ERROR: failed to decode group policies" << dendl; + return -EIO; + } + + if (!policies.empty()) { + s->err.message = "The group cannot be deleted until all group policies are removed"; + return -ERR_DELETE_CONFLICT; + } + } + if (auto p = attrs.find(RGW_ATTR_MANAGED_POLICY); p != attrs.end()) { + rgw::IAM::ManagedPolicies policies; + try { + decode(policies, p->second); + } catch (const buffer::error&) { + ldpp_dout(this, 0) << "ERROR: failed to decode managed policies" << dendl; + return -EIO; + } + + if (!policies.arns.empty()) { + s->err.message = "The group cannot be deleted until all managed policies are detached"; + return -ERR_DELETE_CONFLICT; + } + } + + // check that group has no users + const std::string& tenant = s->auth.identity->get_tenant(); + rgw::sal::UserList listing; + int r = driver->list_group_users(this, y, tenant, info.id, "", 1, listing); + if (r < 0) { + return r; + } + + if (listing.users.size()) { + s->err.message = "The group cannot be deleted until all users are removed"; + return -ERR_DELETE_CONFLICT; + } + + return 0; +} + +void RGWDeleteGroup_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y] { + if (int r = check_empty(y); r < 0) { + return r; + } + return driver->remove_group(this, y, info, objv); + }); + + if (op_ret == -ENOENT) { + if (!site.is_meta_master()) { + // delete succeeded on the master, return that success here too + op_ret = 0; + } else { + s->err.message = "No such GroupName in the account"; + op_ret = -ERR_NO_SUCH_ENTITY; + } + } +} + +void RGWDeleteGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "DeleteGroupResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /DeleteGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListGroups +class RGWListGroups_IAM : public RGWOp { + rgw_account_id account_id; + std::string marker; + std::string path_prefix; + int max_items = 100; + + bool started_response = false; + void start_response(); + void end_response(std::string_view next_marker); + void send_response_data(std::span groups); + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "list_groups"; } + RGWOpType get_type() override { return RGW_OP_LIST_GROUPS; } +}; + +int RGWListGroups_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + marker = s->info.args.get("Marker"); + path_prefix = s->info.args.get("PathPrefix"); + + int r = s->info.args.get_int("MaxItems", &max_items, max_items); + if (r < 0 || max_items > 1000) { + s->err.message = "Invalid value for MaxItems"; + return -EINVAL; + } + + return 0; +} + +int RGWListGroups_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = ""; + const rgw::ARN arn{resource_name, "group", account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamListGroups, true)) { + return 0; + } + return -EACCES; +} + +void RGWListGroups_IAM::execute(optional_yield y) +{ + rgw::sal::GroupList listing; + listing.next_marker = marker; + + op_ret = driver->list_account_groups(this, y, account_id, + path_prefix, listing.next_marker, + max_items, listing); + if (op_ret == -ENOENT) { + op_ret = 0; + } else if (op_ret < 0) { + return; + } + + send_response_data(listing.groups); + + if (!started_response) { + started_response = true; + start_response(); + } + end_response(listing.next_marker); +} + +void RGWListGroups_IAM::start_response() +{ + const int64_t proposed_content_length = + op_ret ? NO_CONTENT_LENGTH : CHUNKED_TRANSFER_ENCODING; + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this, to_mime_type(s->format), proposed_content_length); + + if (op_ret) { + return; + } + + dump_start(s); // + s->formatter->open_object_section_in_ns("ListGroupsResponse", RGW_REST_IAM_XMLNS); + s->formatter->open_object_section("ListGroupsResult"); + s->formatter->open_array_section("Groups"); +} + +void RGWListGroups_IAM::end_response(std::string_view next_marker) +{ + s->formatter->close_section(); // Groups + + const bool truncated = !next_marker.empty(); + s->formatter->dump_bool("IsTruncated", truncated); + if (truncated) { + s->formatter->dump_string("Marker", next_marker); + } + + s->formatter->close_section(); // ListGroupsResult + s->formatter->close_section(); // ListGroupsResponse + rgw_flush_formatter_and_reset(s, s->formatter); +} + +void RGWListGroups_IAM::send_response_data(std::span groups) +{ + if (!started_response) { + started_response = true; + start_response(); + } + + for (const auto& info : groups) { + s->formatter->open_object_section("member"); + dump_iam_group(info, s->formatter); + s->formatter->close_section(); // member + } + + // flush after each chunk + rgw_flush_formatter(s, s->formatter); +} + +void RGWListGroups_IAM::send_response() +{ + if (!started_response) { // errored out before execute() wrote anything + start_response(); + } +} + + +// AddUserToGroup +class RGWAddUserToGroup_IAM : public RGWOp { + bufferlist post_body; + RGWGroupInfo group; + std::unique_ptr user; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWAddUserToGroup_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "add_user_to_group"; } + RGWOpType get_type() override { return RGW_OP_ADD_USER_TO_GROUP; } +}; + +int RGWAddUserToGroup_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + group.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + const std::string username = s->info.args.get("UserName"); + if (!validate_iam_user_name(username, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + rgw::sal::Attrs attrs_ignored; + RGWObjVersionTracker objv_ignored; + int r = driver->load_group_by_name(this, y, group.account_id, name, + group, attrs_ignored, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + if (r < 0) { + return r; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + r = driver->load_account_user_by_name(this, y, group.account_id, + tenant, username, &user); + if (r == -ENOENT) { + s->err.message = "No such UserName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWAddUserToGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(group); + const rgw::ARN arn{resource_name, "group", group.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamAddUserToGroup, true)) { + return 0; + } + return -EACCES; +} + +int RGWAddUserToGroup_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("UserName"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWAddUserToGroup_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + const bool inserted = info.group_ids.insert(group.id).second; + if (inserted) { + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); + } +} + +void RGWAddUserToGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "AddUserToGroupResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /AddUserToGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// RemoveUserFromGroup +class RGWRemoveUserFromGroup_IAM : public RGWOp { + bufferlist post_body; + RGWGroupInfo group; + std::unique_ptr user; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWRemoveUserFromGroup_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "remove_user_from_group"; } + RGWOpType get_type() override { return RGW_OP_REMOVE_USER_FROM_GROUP; } +}; + +int RGWRemoveUserFromGroup_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + group.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + const std::string username = s->info.args.get("UserName"); + if (!validate_iam_user_name(username, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + rgw::sal::Attrs attrs_ignored; + RGWObjVersionTracker objv_ignored; + int r = driver->load_group_by_name(this, y, group.account_id, name, + group, attrs_ignored, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + if (r < 0) { + return r; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + r = driver->load_account_user_by_name(this, y, group.account_id, + tenant, username, &user); + if (r == -ENOENT) { + s->err.message = "No such UserName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWRemoveUserFromGroup_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(group); + const rgw::ARN arn{resource_name, "group", group.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamRemoveUserFromGroup, true)) { + return 0; + } + return -EACCES; +} + +int RGWRemoveUserFromGroup_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("UserName"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWRemoveUserFromGroup_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + if (auto id = info.group_ids.find(group.id); + id != info.group_ids.end()) { + info.group_ids.erase(id); + + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); + } +} + +void RGWRemoveUserFromGroup_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "RemoveUserFromGroupResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /RemoveUserFromGroupResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListGroupsForUser +class RGWListGroupsForUser_IAM : public RGWOp { + rgw_account_id account_id; + std::string marker; + int max_items = 100; + std::unique_ptr user; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "list_groups_for_user"; } + RGWOpType get_type() override { return RGW_OP_LIST_GROUPS_FOR_USER; } +}; + +int RGWListGroupsForUser_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + marker = s->info.args.get("Marker"); + + int r = s->info.args.get_int("MaxItems", &max_items, max_items); + if (r < 0 || max_items > 1000) { + s->err.message = "Invalid value for MaxItems"; + return -EINVAL; + } + + const std::string username = s->info.args.get("UserName"); + if (!validate_iam_user_name(username, s->err.message)) { + return -EINVAL; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + r = driver->load_account_user_by_name(this, y, account_id, + tenant, username, &user); + if (r == -ENOENT) { + s->err.message = "No such UserName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWListGroupsForUser_IAM::verify_permission(optional_yield y) +{ + const RGWUserInfo& info = user->get_info(); + const std::string resource_name = string_cat_reserve(info.path, info.display_name); + const rgw::ARN arn{resource_name, "user", account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamListGroupsForUser, true)) { + return 0; + } + return -EACCES; +} + +void RGWListGroupsForUser_IAM::execute(optional_yield y) +{ + rgw::sal::GroupList listing; + listing.next_marker = marker; + + op_ret = user->list_groups(this, y, marker, max_items, listing); + if (op_ret == -ENOENT) { + op_ret = 0; + } else if (op_ret < 0) { + return; + } + + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "ListGroupsForUserResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "ListGroupsForUserResult"}; + { + Formatter::ArraySection groups{*f, "Groups"}; + for (const auto& info : listing.groups) { + Formatter::ObjectSection result{*f, "member"}; + dump_iam_group(info, s->formatter); + } // /member + } // /Groups + const bool truncated = !listing.next_marker.empty(); + f->dump_bool("IsTruncated", truncated); + if (truncated) { + f->dump_string("Marker", listing.next_marker); + } + } // /ListGroupsForUserResult + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /ListGroupsForUserResponse +} + +void RGWListGroupsForUser_IAM::send_response() +{ + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// PutGroupPolicy +class RGWPutGroupPolicy_IAM : public RGWOp { + bufferlist post_body; + std::string policy_name; + std::string policy_document; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWPutGroupPolicy_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "put_group_policy"; } + RGWOpType get_type() override { return RGW_OP_PUT_GROUP_POLICY; } +}; + +int RGWPutGroupPolicy_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + policy_name = s->info.args.get("PolicyName"); + if (!validate_iam_policy_name(policy_name, s->err.message)) { + return -EINVAL; + } + + policy_document = s->info.args.get("PolicyDocument"); + if (policy_document.empty()) { + s->err.message = "Missing required element PolicyDocument"; + return -EINVAL; + } + + // look up group by GroupName + int r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWPutGroupPolicy_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamPutGroupPolicy, true)) { + return 0; + } + return -EACCES; +} + +int RGWPutGroupPolicy_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("PolicyName"); + s->info.args.remove("PolicyDocument"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWPutGroupPolicy_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + try { + // validate the document + const rgw::IAM::Policy p( + s->cct, s->user->get_tenant(), policy_document, + s->cct->_conf.get_val("rgw_policy_reject_invalid_principals")); + } catch (rgw::IAM::PolicyParseException& e) { + s->err.message = std::move(e.msg); + op_ret = -ERR_MALFORMED_DOC; + return; + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y] { + std::map policies; + if (auto p = attrs.find(RGW_ATTR_IAM_POLICY); p != attrs.end()) try { + decode(policies, p->second); + } catch (const buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode group policies" << dendl; + return -EIO; + } + + policies[policy_name] = policy_document; + + constexpr size_t GROUP_POLICIES_MAX_NUM = 100; + if (policies.size() > GROUP_POLICIES_MAX_NUM) { + s->err.message = fmt::format("Group policy limit {} exceeded", + GROUP_POLICIES_MAX_NUM); + return -ERR_LIMIT_EXCEEDED; + } + + bufferlist bl; + encode(policies, bl); + attrs[RGW_ATTR_IAM_POLICY] = std::move(bl); + + constexpr bool exclusive = false; + return driver->store_group(this, y, info, attrs, objv, exclusive, &info); + }); +} + +void RGWPutGroupPolicy_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "PutGroupPolicyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /PutGroupPolicyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// GetGroupPolicy +class RGWGetGroupPolicy_IAM : public RGWOp { + std::string policy_name; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "get_group_policy"; } + RGWOpType get_type() override { return RGW_OP_GET_GROUP_POLICY; } +}; + +int RGWGetGroupPolicy_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + policy_name = s->info.args.get("PolicyName"); + if (!validate_iam_policy_name(policy_name, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + RGWObjVersionTracker objv_ignored; + int r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWGetGroupPolicy_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamGetGroupPolicy, true)) { + return 0; + } + return -EACCES; +} + +void RGWGetGroupPolicy_IAM::execute(optional_yield y) +{ + std::map policies; + if (auto p = attrs.find(RGW_ATTR_IAM_POLICY); p != attrs.end()) try { + decode(policies, p->second); + } catch (const buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode group policies" << dendl; + op_ret = -EIO; + return; + } + + auto policy = policies.find(policy_name); + if (policy == policies.end()) { + s->err.message = "No such PolicyName on the group"; + op_ret = -ERR_NO_SUCH_ENTITY; + return; + } + + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "GetGroupPolicyResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "GetGroupPolicyResult"}; + encode_json("GroupName", info.name, f); + encode_json("PolicyName", policy_name, f); + encode_json("PolicyDocument", policy->second, f); + // /GetGroupPolicyResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /GetGroupPolicyResponse +} + +void RGWGetGroupPolicy_IAM::send_response() +{ + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// DeleteGroupPolicy +class RGWDeleteGroupPolicy_IAM : public RGWOp { + bufferlist post_body; + std::string policy_name; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWDeleteGroupPolicy_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "detach_group_policy"; } + RGWOpType get_type() override { return RGW_OP_DETACH_GROUP_POLICY; } +}; + +int RGWDeleteGroupPolicy_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + policy_name = s->info.args.get("PolicyName"); + if (!validate_iam_policy_name(policy_name, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + int r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWDeleteGroupPolicy_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamDeleteGroupPolicy, true)) { + return 0; + } + return -EACCES; +} + +int RGWDeleteGroupPolicy_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("PolicyName"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWDeleteGroupPolicy_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y, &site] { + std::map policies; + if (auto it = attrs.find(RGW_ATTR_IAM_POLICY); it != attrs.end()) try { + decode(policies, it->second); + } catch (buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + return -EIO; + } + + auto i = policies.find(policy_name); + if (i == policies.end()) { + if (!site.is_meta_master()) { + return 0; // delete succeeded on the master + } + return -ERR_NO_SUCH_ENTITY; + } + policies.erase(i); + + bufferlist bl; + encode(policies, bl); + attrs[RGW_ATTR_IAM_POLICY] = std::move(bl); + + constexpr bool exclusive = false; + return driver->store_group(this, y, info, attrs, objv, exclusive, &info); + }); +} + +void RGWDeleteGroupPolicy_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "DeleteGroupPolicyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /DeleteGroupPolicyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListGroupPolicies +class RGWListGroupPolicies_IAM : public RGWOp { + bufferlist post_body; + std::string marker; + int max_items = 100; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "list_group_policies"; } + RGWOpType get_type() override { return RGW_OP_LIST_GROUP_POLICIES; } +}; + +int RGWListGroupPolicies_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + marker = s->info.args.get("Marker"); + + int r = s->info.args.get_int("MaxItems", &max_items, max_items); + if (r < 0 || max_items > 1000) { + s->err.message = "Invalid value for MaxItems"; + return -EINVAL; + } + + // look up group by GroupName + RGWObjVersionTracker objv_ignored; + r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWListGroupPolicies_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamListGroupPolicies, true)) { + return 0; + } + return -EACCES; +} + +void RGWListGroupPolicies_IAM::execute(optional_yield y) +{ + std::map policies; + if (auto p = attrs.find(RGW_ATTR_IAM_POLICY); p != attrs.end()) try { + decode(policies, p->second); + } catch (const buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + op_ret = -EIO; + } + + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "ListGroupPoliciesResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "ListGroupPoliciesResult"}; + auto policy = policies.lower_bound(marker); + { + Formatter::ArraySection names{*f, "PolicyNames"}; + for (; policy != policies.end() && max_items > 0; ++policy, --max_items) { + encode_json("member", policy->first, f); + } + } // /PolicyNames + const bool is_truncated = (policy != policies.end()); + encode_json("IsTruncated", is_truncated, f); + if (is_truncated) { + encode_json("Marker", policy->first, f); + } + } // /ListUserPoliciesResult + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /ListGroupPoliciesResponse +} + +void RGWListGroupPolicies_IAM::send_response() +{ + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// AttachGroupPolicy +class RGWAttachGroupPolicy_IAM : public RGWOp { + bufferlist post_body; + std::string policy_arn; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWAttachGroupPolicy_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "attach_group_policy"; } + RGWOpType get_type() override { return RGW_OP_ATTACH_GROUP_POLICY; } +}; + +int RGWAttachGroupPolicy_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + policy_arn = s->info.args.get("PolicyArn"); + if (!validate_iam_policy_arn(policy_arn, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + int r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWAttachGroupPolicy_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamAttachGroupPolicy, true)) { + return 0; + } + return -EACCES; +} + +int RGWAttachGroupPolicy_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("PolicyArn"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWAttachGroupPolicy_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + // validate the policy arn + try { + const auto p = rgw::IAM::get_managed_policy(s->cct, policy_arn); + if (!p) { + op_ret = -ERR_NO_SUCH_ENTITY; + s->err.message = "The requested PolicyArn is not recognized"; + return; + } + } catch (const rgw::IAM::PolicyParseException& e) { + ldpp_dout(this, 5) << "failed to parse policy: " << e.what() << dendl; + s->err.message = e.what(); + op_ret = -ERR_MALFORMED_DOC; + return; + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y] { + rgw::IAM::ManagedPolicies policies; + if (auto it = attrs.find(RGW_ATTR_MANAGED_POLICY); it != attrs.end()) try { + decode(policies, it->second); + } catch (buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + return -EIO; + } + + if (!policies.arns.insert(policy_arn).second) { + return 0; // nothing to do, return success + } + + bufferlist bl; + encode(policies, bl); + attrs[RGW_ATTR_MANAGED_POLICY] = std::move(bl); + + constexpr bool exclusive = false; + return driver->store_group(this, y, info, attrs, objv, exclusive, &info); + }); +} + +void RGWAttachGroupPolicy_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "AttachGroupPolicyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /AttachGroupPolicyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// DetachGroupPolicy +class RGWDetachGroupPolicy_IAM : public RGWOp { + bufferlist post_body; + std::string policy_arn; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + RGWObjVersionTracker objv; + + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWDetachGroupPolicy_IAM(const ceph::bufferlist& post_body) + : post_body(post_body) {} + + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "detach_group_policy"; } + RGWOpType get_type() override { return RGW_OP_DETACH_GROUP_POLICY; } +}; + +int RGWDetachGroupPolicy_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + policy_arn = s->info.args.get("PolicyArn"); + if (!validate_iam_policy_arn(policy_arn, s->err.message)) { + return -EINVAL; + } + + // look up group by GroupName + int r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWDetachGroupPolicy_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamDetachGroupPolicy, true)) { + return 0; + } + return -EACCES; +} + +int RGWDetachGroupPolicy_IAM::forward_to_master(optional_yield y, + const rgw::SiteConfig& site) +{ + RGWXMLDecoder::XMLParser parser; + if (!parser.init()) { + ldpp_dout(this, 0) << "ERROR: failed to initialize xml parser" << dendl; + return -EINVAL; + } + + s->info.args.remove("GroupName"); + s->info.args.remove("PolicyArn"); + s->info.args.remove("Action"); + s->info.args.remove("Version"); + + int r = forward_iam_request_to_master(this, site, s->user->get_info(), + post_body, parser, s->info, y); + if (r < 0) { + ldpp_dout(this, 20) << "ERROR: forward_iam_request_to_master failed with error code: " << r << dendl; + return r; + } + return 0; +} + +void RGWDetachGroupPolicy_IAM::execute(optional_yield y) +{ + const rgw::SiteConfig& site = *s->penv.site; + if (!site.is_meta_master()) { + op_ret = forward_to_master(y, site); + if (op_ret) { + return; + } + } + + op_ret = retry_raced_group_write(this, y, driver, info, attrs, objv, + [this, y, &site] { + rgw::IAM::ManagedPolicies policies; + if (auto it = attrs.find(RGW_ATTR_MANAGED_POLICY); it != attrs.end()) try { + decode(policies, it->second); + } catch (const buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + return -EIO; + } + + auto i = policies.arns.find(policy_arn); + if (i == policies.arns.end()) { + if (!site.is_meta_master()) { + return 0; // delete succeeded on the master + } + return -ERR_NO_SUCH_ENTITY; + } + policies.arns.erase(i); + + bufferlist bl; + encode(policies, bl); + attrs[RGW_ATTR_MANAGED_POLICY] = std::move(bl); + + constexpr bool exclusive = false; + return driver->store_group(this, y, info, attrs, objv, exclusive, &info); + }); +} + +void RGWDetachGroupPolicy_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "DetachGroupPolicyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /DetachGroupPolicyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListAttachedGroupPolicies +class RGWListAttachedGroupPolicies_IAM : public RGWOp { + bufferlist post_body; + RGWGroupInfo info; + rgw::sal::Attrs attrs; + std::string marker; + int max_items = 100; + + public: + int init_processing(optional_yield y) override; + int verify_permission(optional_yield y) override; + void execute(optional_yield y) override; + void send_response() override; + + const char* name() const override { return "list_attached_group_policies"; } + RGWOpType get_type() override { return RGW_OP_LIST_ATTACHED_GROUP_POLICIES; } +}; + +int RGWListAttachedGroupPolicies_IAM::init_processing(optional_yield y) +{ + if (const auto* id = std::get_if(&s->owner.id); id) { + info.account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string name = s->info.args.get("GroupName"); + if (!validate_iam_group_name(name, s->err.message)) { + return -EINVAL; + } + + marker = s->info.args.get("Marker"); + + int r = s->info.args.get_int("MaxItems", &max_items, max_items); + if (r < 0 || max_items > 1000) { + s->err.message = "Invalid value for MaxItems"; + return -EINVAL; + } + + // look up group by GroupName + RGWObjVersionTracker objv_ignored; + r = driver->load_group_by_name(this, y, info.account_id, name, + info, attrs, objv_ignored); + if (r == -ENOENT) { + s->err.message = "No such GroupName in the account"; + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWListAttachedGroupPolicies_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "group", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamListAttachedGroupPolicies, true)) { + return 0; + } + return -EACCES; +} + +void RGWListAttachedGroupPolicies_IAM::execute(optional_yield y) +{ + rgw::IAM::ManagedPolicies policies; + if (auto p = attrs.find(RGW_ATTR_MANAGED_POLICY); p != attrs.end()) try { + decode(policies, p->second); + } catch (const buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + op_ret = -EIO; + } + + + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "ListAttachedGroupPoliciesResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "ListAttachedGroupPoliciesResult"}; + + auto policy = policies.arns.lower_bound(marker); + { + Formatter::ArraySection arr{*f, "AttachedPolicies"}; + for (; policy != policies.arns.end() && max_items > 0; ++policy, --max_items) { + Formatter::ObjectSection result{*f, "member"}; + std::string_view arn = *policy; + if (auto p = arn.find('/'); p != arn.npos) { + encode_json("PolicyName", arn.substr(p + 1), f); + } + encode_json("PolicyArn", arn, f); + } + } // /AttachedPolicies + const bool is_truncated = (policy != policies.arns.end()); + encode_json("IsTruncated", is_truncated, f); + if (is_truncated) { + encode_json("Marker", *policy, f); + } + // /ListAttachedUserPoliciesResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /ListAttachedGroupPoliciesResponse +} + +void RGWListAttachedGroupPolicies_IAM::send_response() +{ + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +RGWOp* make_iam_create_group_op(const ceph::bufferlist& post_body) { + return new RGWCreateGroup_IAM(post_body); +} +RGWOp* make_iam_get_group_op(const ceph::bufferlist&) { + return new RGWGetGroup_IAM; +} +RGWOp* make_iam_update_group_op(const ceph::bufferlist& post_body) { + return new RGWUpdateGroup_IAM(post_body); +} +RGWOp* make_iam_delete_group_op(const ceph::bufferlist& post_body) { + return new RGWDeleteGroup_IAM(post_body); +} +RGWOp* make_iam_list_groups_op(const ceph::bufferlist&) { + return new RGWListGroups_IAM; +} + +RGWOp* make_iam_add_user_to_group_op(const ceph::bufferlist& post_body) { + return new RGWAddUserToGroup_IAM(post_body); +} +RGWOp* make_iam_remove_user_from_group_op(const ceph::bufferlist& post_body) { + return new RGWRemoveUserFromGroup_IAM(post_body); +} +RGWOp* make_iam_list_groups_for_user_op(const ceph::bufferlist& unused) { + return new RGWListGroupsForUser_IAM; +} + +RGWOp* make_iam_put_group_policy_op(const ceph::bufferlist& post_body) { + return new RGWPutGroupPolicy_IAM(post_body); +} +RGWOp* make_iam_get_group_policy_op(const ceph::bufferlist& unused) { + return new RGWGetGroupPolicy_IAM; +} +RGWOp* make_iam_delete_group_policy_op(const ceph::bufferlist& post_body) { + return new RGWDeleteGroupPolicy_IAM(post_body); +} +RGWOp* make_iam_list_group_policies_op(const ceph::bufferlist& unused) { + return new RGWListGroupPolicies_IAM; +} +RGWOp* make_iam_attach_group_policy_op(const ceph::bufferlist& post_body) { + return new RGWAttachGroupPolicy_IAM(post_body); +} +RGWOp* make_iam_detach_group_policy_op(const ceph::bufferlist& post_body) { + return new RGWDetachGroupPolicy_IAM(post_body); +} +RGWOp* make_iam_list_attached_group_policies_op(const ceph::bufferlist& unused) { + return new RGWListAttachedGroupPolicies_IAM(); +} + diff --git a/src/rgw/rgw_rest_iam_group.h b/src/rgw/rgw_rest_iam_group.h new file mode 100644 index 00000000000..861b7e0e3c5 --- /dev/null +++ b/src/rgw/rgw_rest_iam_group.h @@ -0,0 +1,40 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab ft=cpp + +/* + * Ceph - scalable distributed file system + * + * Copyright contributors to the Ceph project + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#pragma once + +#include "include/buffer_fwd.h" + +class RGWOp; + +// IAM Group op factory functions +RGWOp* make_iam_create_group_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_get_group_op(const ceph::bufferlist& unused); +RGWOp* make_iam_update_group_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_delete_group_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_list_groups_op(const ceph::bufferlist& unused); + +RGWOp* make_iam_add_user_to_group_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_remove_user_from_group_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_list_groups_for_user_op(const ceph::bufferlist& unused); + +// IAM GroupPolicy op factory functions +RGWOp* make_iam_put_group_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_get_group_policy_op(const ceph::bufferlist& unused); +RGWOp* make_iam_delete_group_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_list_group_policies_op(const ceph::bufferlist& unused); +RGWOp* make_iam_attach_group_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_detach_group_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_list_attached_group_policies_op(const ceph::bufferlist& unused); diff --git a/src/test/rgw/test_rgw_iam_policy.cc b/src/test/rgw/test_rgw_iam_policy.cc index 26a644625b1..d96e965d860 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -107,6 +107,12 @@ using rgw::IAM::iamListOIDCProviders; using rgw::IAM::iamListRoleTags; using rgw::IAM::iamListUsers; using rgw::IAM::iamListAccessKeys; +using rgw::IAM::iamGetGroup; +using rgw::IAM::iamListGroups; +using rgw::IAM::iamListGroupsForUser; +using rgw::IAM::iamGetGroupPolicy; +using rgw::IAM::iamListGroupPolicies; +using rgw::IAM::iamListAttachedGroupPolicies; using rgw::IAM::iamSimulateCustomPolicy; using rgw::IAM::iamSimulatePrincipalPolicy; using rgw::IAM::snsGetTopicAttributes; @@ -809,6 +815,12 @@ TEST_F(ManagedPolicyTest, IAMReadOnlyAccess) act[iamListRoleTags] = 1; act[iamListUsers] = 1; act[iamListAccessKeys] = 1; + act[iamGetGroup] = 1; + act[iamListGroups] = 1; + act[iamListGroupsForUser] = 1; + act[iamGetGroupPolicy] = 1; + act[iamListGroupPolicies] = 1; + act[iamListAttachedGroupPolicies] = 1; act[iamSimulateCustomPolicy] = 1; act[iamSimulatePrincipalPolicy] = 1; -- 2.39.5