]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/iam: AttachUserPolicy adds managed user policy
authorCasey Bodley <cbodley@redhat.com>
Thu, 1 Feb 2024 22:41:08 +0000 (17:41 -0500)
committerCasey Bodley <cbodley@redhat.com>
Fri, 12 Apr 2024 19:34:28 +0000 (15:34 -0400)
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 <cbodley@redhat.com>
(cherry picked from commit bf64bc624dff5200964cd9763a4d6466edfe07e7)

13 files changed:
src/rgw/rgw_auth_s3.cc
src/rgw/rgw_common.h
src/rgw/rgw_iam_managed_policy.cc
src/rgw/rgw_iam_managed_policy.h
src/rgw/rgw_iam_policy.cc
src/rgw/rgw_iam_policy.h
src/rgw/rgw_op.cc
src/rgw/rgw_op_type.h
src/rgw/rgw_rest_iam.cc
src/rgw/rgw_rest_iam_user.cc
src/rgw/rgw_rest_user_policy.cc
src/rgw/rgw_rest_user_policy.h
src/test/rgw/test_rgw_iam_policy.cc

index 5f55ba21f714cad2c148b53251d0eca3b7017450..dd90925f04a2501d8cb634172743355e3ef53289 100644 (file)
@@ -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:
index 207625dc4a754c21af739122dcc4180deb5f7cf4..5536c3a3e60ad85fc991c65e584e7a196fa525b5 100644 (file)
@@ -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 */
index 3034832b2032a22d8dd149d2ed8f6d81c6b28d8d..6187d7737dbb648dd89c6e01f4a10a39fdd64c6b 100644 (file)
@@ -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
index 35a5cef73df9c8283d6eaf4e151e986b3ca6e9aa..37b519e535b6d91db52ab1520e2747e17b3e7bcf 100644 (file)
 #pragma once
 
 #include <optional>
-#include <string_view>
+#include <string>
+#include <boost/container/flat_set.hpp>
 #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<Policy>;
 
+/// A serializable container for managed policy ARNs.
+struct ManagedPolicies {
+  boost::container::flat_set<std::string> arns;
+};
+void encode(const ManagedPolicies&, bufferlist&, uint64_t f=0);
+void decode(ManagedPolicies&, bufferlist::const_iterator&);
+
 } // namespace rgw::IAM
index 1239de0433b5e6801adb4cb4f54ca1b52e7bdc00..c0a081054fa81e425b468fb5dccaacadff23c4d3 100644 (file)
@@ -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";
 
index 3b236f695b9894fcd8592447ac2ff4016b83e5ab..a8aa88dca7b025791e0a88ac491b1814b255fdbf 100644 (file)
@@ -124,6 +124,9 @@ enum {
   iamGetUserPolicy,
   iamDeleteUserPolicy,
   iamListUserPolicies,
+  iamAttachUserPolicy,
+  iamDetachUserPolicy,
+  iamListAttachedUserPolicies,
   iamCreateRole,
   iamDeleteRole,
   iamModifyRoleTrustPolicy,
index cb8bbae5d99bbd6bc76a73cc672431aeb17ddd86..8dd80c9fe9c243fe45cd9a95da06805b48c004d1 100644 (file)
@@ -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<Policy> 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;
 }
 
index 71456f755c82bbfb9539a5dcad8636dd712c979d..d71f174c37c264c4f6c416fa0cdde0e32f564b36 100644 (file)
@@ -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,
index 490a91ed44b00c9327cd0638d2556511d87e629d..cbd692f7914bff7018650fbee37170597aaeec3e 100644 (file)
@@ -34,6 +34,9 @@ static const std::unordered_map<std::string_view, op_generator> 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;}},
index 23252e5ca6455b971acd6ed3eeb0f0b5ee92da5d..8dd6ada3077a37d4eee4c2141d6c28f1c0390325 100644 (file)
@@ -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;
 }
index b390bfb5b82a5abd04b61beec8e5228cfa654cfa..79d7912d12a3da031ce4bbca7d1e1ad4c67ebf9e 100644 (file)
@@ -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<rgw_account_id>(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();
+}
index 681dfd80065cee62d0fa01d4ae8c1e8b2d6425a8..f4188687f86edbddf40131aeea39ed5a6c3cac4b 100644 (file)
@@ -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);
index 3ecbb782e5ce5feb1a589b3be2c40145d749bff0..6bdfd081b25363dab92117d6b639e7e1328501de 100644 (file)
@@ -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;