From: Casey Bodley Date: Thu, 1 Feb 2024 22:41:08 +0000 (-0500) Subject: rgw/iam: AttachUserPolicy adds managed user policy X-Git-Tag: v20.0.0~2159^2~81 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=bf64bc624dff5200964cd9763a4d6466edfe07e7;p=ceph.git rgw/iam: AttachUserPolicy adds managed user policy implement iam apis AttachUserPolicy, DetachUserPolicy, and ListAttachedUserPolicies to manipulate managed user policy the set of managed policy ARNs is stored in the user attr RGW_ATTR_MANAGED_POLICY for incoming requests, the policies from RGW_ATTR_MANAGED_POLICY are added to s->iam_user_policies at the same time as RGW_ATTR_USER_POLICY Signed-off-by: Casey Bodley --- diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 5f55ba21f714..dd90925f04a2 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -485,6 +485,9 @@ bool is_non_s3_op(RGWOpType op_type) case RGW_OP_GET_USER_POLICY: case RGW_OP_LIST_USER_POLICIES: case RGW_OP_DELETE_USER_POLICY: + case RGW_OP_ATTACH_USER_POLICY: + case RGW_OP_DETACH_USER_POLICY: + case RGW_OP_LIST_ATTACHED_USER_POLICIES: case RGW_OP_CREATE_OIDC_PROVIDER: case RGW_OP_DELETE_OIDC_PROVIDER: case RGW_OP_GET_OIDC_PROVIDER: diff --git a/src/rgw/rgw_common.h b/src/rgw/rgw_common.h index cf1345f35277..84da4833ce69 100644 --- a/src/rgw/rgw_common.h +++ b/src/rgw/rgw_common.h @@ -151,6 +151,7 @@ using ceph::crypto::MD5; /* IAM Policy */ #define RGW_ATTR_IAM_POLICY RGW_ATTR_PREFIX "iam-policy" #define RGW_ATTR_USER_POLICY RGW_ATTR_PREFIX "user-policy" +#define RGW_ATTR_MANAGED_POLICY RGW_ATTR_PREFIX "managed-policy" #define RGW_ATTR_PUBLIC_ACCESS RGW_ATTR_PREFIX "public-access" /* RGW File Attributes */ diff --git a/src/rgw/rgw_iam_managed_policy.cc b/src/rgw/rgw_iam_managed_policy.cc index 3034832b2032..6187d7737dbb 100644 --- a/src/rgw/rgw_iam_managed_policy.cc +++ b/src/rgw/rgw_iam_managed_policy.cc @@ -174,4 +174,18 @@ auto get_managed_policy(CephContext* cct, std::string_view arn) return {}; } +void encode(const ManagedPolicies& m, bufferlist& bl, uint64_t f) +{ + ENCODE_START(1, 1, bl); + encode(m.arns, bl); + ENCODE_FINISH(bl); +} + +void decode(ManagedPolicies& m, bufferlist::const_iterator& bl) +{ + DECODE_START(1, bl); + decode(m.arns, bl); + DECODE_FINISH(bl); +} + } // namespace rgw::IAM diff --git a/src/rgw/rgw_iam_managed_policy.h b/src/rgw/rgw_iam_managed_policy.h index 35a5cef73df9..37b519e535b6 100644 --- a/src/rgw/rgw_iam_managed_policy.h +++ b/src/rgw/rgw_iam_managed_policy.h @@ -16,8 +16,10 @@ #pragma once #include -#include +#include +#include #include "common/ceph_context.h" +#include "include/buffer_fwd.h" namespace rgw::IAM { @@ -27,4 +29,11 @@ struct Policy; auto get_managed_policy(CephContext* cct, std::string_view arn) -> std::optional; +/// A serializable container for managed policy ARNs. +struct ManagedPolicies { + boost::container::flat_set arns; +}; +void encode(const ManagedPolicies&, bufferlist&, uint64_t f=0); +void decode(ManagedPolicies&, bufferlist::const_iterator&); + } // namespace rgw::IAM diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index 1239de0433b5..c0a081054fa8 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -141,6 +141,9 @@ static const actpair actpairs[] = { "iam:GetUserPolicy", iamGetUserPolicy }, { "iam:DeleteUserPolicy", iamDeleteUserPolicy }, { "iam:ListUserPolicies", iamListUserPolicies }, + { "iam:AttachUserPolicy", iamAttachUserPolicy }, + { "iam:DetachUserPolicy", iamDetachUserPolicy }, + { "iam:ListAttachedUserPolicies", iamListAttachedUserPolicies }, { "iam:CreateRole", iamCreateRole}, { "iam:DeleteRole", iamDeleteRole}, { "iam:GetRole", iamGetRole}, @@ -1466,6 +1469,15 @@ const char* action_bit_string(uint64_t action) { case iamDeleteUserPolicy: return "iam:DeleteUserPolicy"; + case iamAttachUserPolicy: + return "iam:AttachUserPolicy"; + + case iamDetachUserPolicy: + return "iam:DetachUserPolicy"; + + case iamListAttachedUserPolicies: + return "iam:ListAttachedUserPolicies"; + case iamCreateRole: return "iam:CreateRole"; diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index 3b236f695b98..a8aa88dca7b0 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -124,6 +124,9 @@ enum { iamGetUserPolicy, iamDeleteUserPolicy, iamListUserPolicies, + iamAttachUserPolicy, + iamDetachUserPolicy, + iamListAttachedUserPolicies, iamCreateRole, iamDeleteRole, iamModifyRoleTrustPolicy, diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index cb8bbae5d99b..8dd80c9fe9c2 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -56,6 +56,7 @@ #include "rgw_torrent.h" #include "rgw_lua_data_filter.h" #include "rgw_lua.h" +#include "rgw_iam_managed_policy.h" #include "services/svc_zone.h" #include "services/svc_quota.h" @@ -363,6 +364,15 @@ vector get_iam_user_policy_from_attr(CephContext* cct, policies.emplace_back(cct, tenant, policy, false); } } + if (auto bl = attrs.find(RGW_ATTR_MANAGED_POLICY); bl != attrs.end()) { + rgw::IAM::ManagedPolicies policy_set; + decode(policy_set, bl->second); + for (const auto& arn : policy_set.arns) { + if (auto p = rgw::IAM::get_managed_policy(cct, arn); p) { + policies.push_back(std::move(*p)); + } + } + } return policies; } diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h index 71456f755c82..d71f174c37c2 100644 --- a/src/rgw/rgw_op_type.h +++ b/src/rgw/rgw_op_type.h @@ -65,6 +65,9 @@ enum RGWOpType { RGW_OP_GET_USER_POLICY, RGW_OP_LIST_USER_POLICIES, RGW_OP_DELETE_USER_POLICY, + RGW_OP_ATTACH_USER_POLICY, + RGW_OP_DETACH_USER_POLICY, + RGW_OP_LIST_ATTACHED_USER_POLICIES, RGW_OP_PUT_BUCKET_OBJ_LOCK, RGW_OP_GET_BUCKET_OBJ_LOCK, RGW_OP_PUT_OBJ_RETENTION, diff --git a/src/rgw/rgw_rest_iam.cc b/src/rgw/rgw_rest_iam.cc index 490a91ed44b0..cbd692f7914b 100644 --- a/src/rgw/rgw_rest_iam.cc +++ b/src/rgw/rgw_rest_iam.cc @@ -34,6 +34,9 @@ static const std::unordered_map op_generators = {"GetUserPolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetUserPolicy;}}, {"ListUserPolicies", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListUserPolicies;}}, {"DeleteUserPolicy", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWDeleteUserPolicy;}}, + {"AttachUserPolicy", make_iam_attach_user_policy_op}, + {"DetachUserPolicy", make_iam_detach_user_policy_op}, + {"ListAttachedUserPolicies", make_iam_list_attached_user_policies_op}, {"CreateOpenIDConnectProvider", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWCreateOIDCProvider;}}, {"ListOpenIDConnectProviders", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListOIDCProviders;}}, {"GetOpenIDConnectProvider", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWGetOIDCProvider;}}, diff --git a/src/rgw/rgw_rest_iam_user.cc b/src/rgw/rgw_rest_iam_user.cc index 23252e5ca645..8dd6ada3077a 100644 --- a/src/rgw/rgw_rest_iam_user.cc +++ b/src/rgw/rgw_rest_iam_user.cc @@ -20,6 +20,7 @@ #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" @@ -574,6 +575,20 @@ int RGWDeleteUser_IAM::check_empty() 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 user cannot be deleted until all managed policies are detached"; + return -ERR_DELETE_CONFLICT; + } + } return 0; } diff --git a/src/rgw/rgw_rest_user_policy.cc b/src/rgw/rgw_rest_user_policy.cc index b390bfb5b82a..79d7912d12a3 100644 --- a/src/rgw/rgw_rest_user_policy.cc +++ b/src/rgw/rgw_rest_user_policy.cc @@ -12,6 +12,7 @@ #include "rgw_string.h" #include "rgw_common.h" +#include "rgw_iam_managed_policy.h" #include "rgw_op.h" #include "rgw_process_env.h" #include "rgw_rest.h" @@ -335,3 +336,296 @@ void RGWDeleteUserPolicy::execute(optional_yield y) s->formatter->close_section(); s->formatter->close_section(); } + + +class RGWAttachUserPolicy_IAM : public RGWRestUserPolicy { + bufferlist post_body; + std::string policy_arn; + + int get_params() override; + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWAttachUserPolicy_IAM(const ceph::bufferlist& post_body) + : RGWRestUserPolicy(rgw::IAM::iamAttachUserPolicy, RGW_CAP_WRITE), + post_body(post_body) {} + + void execute(optional_yield y) override; + const char* name() const override { return "attach_user_policy"; } + RGWOpType get_type() override { return RGW_OP_ATTACH_USER_POLICY; } +}; + +int RGWAttachUserPolicy_IAM::get_params() +{ + policy_arn = s->info.args.get("PolicyArn"); + if (!validate_iam_policy_arn(policy_arn, s->err.message)) { + return -EINVAL; + } + + return RGWRestUserPolicy::get_params(); +} + +int RGWAttachUserPolicy_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("UserName"); + 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 RGWAttachUserPolicy_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 { + 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; + } + + rgw::IAM::ManagedPolicies policies; + auto& attrs = user->get_attrs(); + if (auto it = attrs.find(RGW_ATTR_MANAGED_POLICY); it != attrs.end()) { + decode(policies, it->second); + } + policies.arns.insert(policy_arn); + + bufferlist in_bl; + encode(policies, in_bl); + attrs[RGW_ATTR_MANAGED_POLICY] = in_bl; + + op_ret = user->store_user(this, s->yield, false); + if (op_ret < 0) { + op_ret = -ERR_INTERNAL_ERROR; + } + } catch (buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + op_ret = -EIO; + } catch (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; + } + + if (op_ret == 0) { + s->formatter->open_object_section_in_ns("AttachUserPolicyResponse", RGW_REST_IAM_XMLNS); + s->formatter->open_object_section("ResponseMetadata"); + s->formatter->dump_string("RequestId", s->trans_id); + s->formatter->close_section(); + s->formatter->close_section(); + } +} + + +class RGWRestAttachedUserPolicy : public RGWRestUserPolicy { + public: + using RGWRestUserPolicy::RGWRestUserPolicy; + int init_processing(optional_yield y) override; +}; + +int RGWRestAttachedUserPolicy::init_processing(optional_yield y) +{ + // managed policy is only supported for account users. adding them to + // non-account roles would give blanket permissions to all buckets + if (!std::holds_alternative(s->owner.id)) { + s->err.message = "Managed policies are only supported for account users"; + return -ERR_METHOD_NOT_ALLOWED; + } + + return RGWRestUserPolicy::init_processing(y); +} + +class RGWDetachUserPolicy_IAM : public RGWRestAttachedUserPolicy { + bufferlist post_body; + std::string policy_arn; + + int get_params() override; + int forward_to_master(optional_yield y, const rgw::SiteConfig& site); + public: + explicit RGWDetachUserPolicy_IAM(const bufferlist& post_body) + : RGWRestAttachedUserPolicy(rgw::IAM::iamDetachUserPolicy, RGW_CAP_WRITE), + post_body(post_body) {} + + void execute(optional_yield y) override; + const char* name() const override { return "detach_user_policy"; } + RGWOpType get_type() override { return RGW_OP_DETACH_USER_POLICY; } +}; + +int RGWDetachUserPolicy_IAM::get_params() +{ + policy_arn = s->info.args.get("PolicyArn"); + if (!validate_iam_policy_arn(policy_arn, s->err.message)) { + return -EINVAL; + } + + return RGWRestAttachedUserPolicy::get_params(); +} + +int RGWDetachUserPolicy_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("UserName"); + 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 RGWDetachUserPolicy_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 { + rgw::IAM::ManagedPolicies policies; + auto& attrs = user->get_attrs(); + if (auto it = attrs.find(RGW_ATTR_MANAGED_POLICY); it != attrs.end()) { + decode(policies, it->second); + } + + auto i = policies.arns.find(policy_arn); + if (i == policies.arns.end()) { + op_ret = ERR_NO_SUCH_ENTITY; + return; + } + policies.arns.erase(i); + + bufferlist in_bl; + encode(policies, in_bl); + attrs[RGW_ATTR_MANAGED_POLICY] = in_bl; + + op_ret = user->store_user(this, s->yield, false); + if (op_ret < 0) { + op_ret = -ERR_INTERNAL_ERROR; + } + } catch (buffer::error& err) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + op_ret = -EIO; + } + + if (op_ret == 0) { + s->formatter->open_object_section_in_ns("DetachUserPolicyResponse", RGW_REST_IAM_XMLNS); + s->formatter->open_object_section("ResponseMetadata"); + s->formatter->dump_string("RequestId", s->trans_id); + s->formatter->close_section(); + s->formatter->close_section(); + } +} + + +class RGWListAttachedUserPolicies_IAM : public RGWRestAttachedUserPolicy { + std::string marker; + int max_items = 100; + int get_params() override; + public: + RGWListAttachedUserPolicies_IAM() + : RGWRestAttachedUserPolicy(rgw::IAM::iamListAttachedUserPolicies, RGW_CAP_READ) + {} + void execute(optional_yield y) override; + const char* name() const override { return "list_attached_user_policies"; } + RGWOpType get_type() override { return RGW_OP_LIST_ATTACHED_USER_POLICIES; } +}; + +int RGWListAttachedUserPolicies_IAM::get_params() +{ + 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; + } + + return RGWRestAttachedUserPolicy::get_params(); +} + +void RGWListAttachedUserPolicies_IAM::execute(optional_yield y) +{ + rgw::IAM::ManagedPolicies policies; + const auto& attrs = user->get_attrs(); + 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; + op_ret = -EIO; + return; + } + } + + s->formatter->open_object_section_in_ns("ListAttachedUserPoliciesResponse", RGW_REST_IAM_XMLNS); + s->formatter->open_object_section("ResponseMetadata"); + s->formatter->dump_string("RequestId", s->trans_id); + s->formatter->close_section(); + s->formatter->open_object_section("ListAttachedUserPoliciesResult"); + s->formatter->open_array_section("AttachedPolicies"); + auto policy = policies.arns.lower_bound(marker); + for (; policy != policies.arns.end() && max_items > 0; ++policy, --max_items) { + s->formatter->open_object_section("member"); + std::string_view arn = *policy; + if (auto p = arn.find('/'); p != arn.npos) { + s->formatter->dump_string("PolicyName", arn.substr(p + 1)); + } + s->formatter->dump_string("PolicyArn", arn); + s->formatter->close_section(); // member + } + s->formatter->close_section(); // AttachedPolicies + const bool is_truncated = (policy != policies.arns.end()); + encode_json("IsTruncated", is_truncated, s->formatter); + if (is_truncated) { + encode_json("Marker", *policy, s->formatter); + } + s->formatter->close_section(); // ListAttachedUserPoliciesResult + s->formatter->close_section(); // ListAttachedUserPoliciesResponse +} + + +RGWOp* make_iam_attach_user_policy_op(const ceph::bufferlist& post_body) { + return new RGWAttachUserPolicy_IAM(post_body); +} + +RGWOp* make_iam_detach_user_policy_op(const ceph::bufferlist& post_body) { + return new RGWDetachUserPolicy_IAM(post_body); +} + +RGWOp* make_iam_list_attached_user_policies_op(const ceph::bufferlist& unused) { + return new RGWListAttachedUserPolicies_IAM(); +} diff --git a/src/rgw/rgw_rest_user_policy.h b/src/rgw/rgw_rest_user_policy.h index 681dfd80065c..f4188687f86e 100644 --- a/src/rgw/rgw_rest_user_policy.h +++ b/src/rgw/rgw_rest_user_policy.h @@ -65,3 +65,7 @@ public: const char* name() const override { return "delete_user_policy"; } RGWOpType get_type() override { return RGW_OP_DELETE_USER_POLICY; } }; + +RGWOp* make_iam_attach_user_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_detach_user_policy_op(const ceph::bufferlist& post_body); +RGWOp* make_iam_list_attached_user_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 b904f2e6e4e3..d929afa4b4d8 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -99,6 +99,7 @@ using rgw::IAM::iamGetRolePolicy; using rgw::IAM::iamGetOIDCProvider; using rgw::IAM::iamGetUser; using rgw::IAM::iamListUserPolicies; +using rgw::IAM::iamListAttachedUserPolicies; using rgw::IAM::iamListRoles; using rgw::IAM::iamListRolePolicies; using rgw::IAM::iamListOIDCProviders; @@ -799,6 +800,7 @@ TEST_F(ManagedPolicyTest, IAMReadOnlyAccess) act[iamGetOIDCProvider] = 1; act[iamGetUser] = 1; act[iamListUserPolicies] = 1; + act[iamListAttachedUserPolicies] = 1; act[iamListRoles] = 1; act[iamListRolePolicies] = 1; act[iamListOIDCProviders] = 1;