From: Casey Bodley Date: Mon, 18 Dec 2023 01:33:06 +0000 (-0500) Subject: rgw/iam: add initial IAM User apis X-Git-Tag: testing/wip-pdonnell-testing-20240416.232051-debug~25^2~120 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=a9c49a5ce7a2eb74e50cde11f6a8aab32764aa89;p=ceph-ci.git rgw/iam: add initial IAM User apis Signed-off-by: Casey Bodley --- diff --git a/src/rgw/CMakeLists.txt b/src/rgw/CMakeLists.txt index 84a5bc9e56c..cc22cfacd29 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_user.cc rgw_rest_s3.cc rgw_rest_pubsub.cc rgw_rest_zero.cc diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index 54dcbec50f9..be12f50a20d 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -498,6 +498,12 @@ bool is_non_s3_op(RGWOpType op_type) case RGW_OP_LIST_ROLE_TAGS: case RGW_OP_UNTAG_ROLE: case RGW_OP_UPDATE_ROLE: + + case RGW_OP_CREATE_USER: + case RGW_OP_GET_USER: + case RGW_OP_UPDATE_USER: + case RGW_OP_DELETE_USER: + case RGW_OP_LIST_USERS: return true; default: return false; diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 7ba3089f7f4..05b58e3a2d9 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -81,7 +81,7 @@ rgw_http_errors rgw_http_s3_errors({ { ERR_INVALID_WEBSITE_ROUTING_RULES_ERROR, {400, "InvalidRequest" }}, { ERR_INVALID_ENCRYPTION_ALGORITHM, {400, "InvalidEncryptionAlgorithmError" }}, { ERR_INVALID_RETENTION_PERIOD,{400, "InvalidRetentionPeriod"}}, - { ERR_LIMIT_EXCEEDED, {400, "LimitExceeded" }}, + { ERR_LIMIT_EXCEEDED, {409, "LimitExceeded" }}, { ERR_LENGTH_REQUIRED, {411, "MissingContentLength" }}, { EACCES, {403, "AccessDenied" }}, { EPERM, {403, "AccessDenied" }}, @@ -135,6 +135,7 @@ rgw_http_errors rgw_http_s3_errors({ { ERR_NO_SUCH_BUCKET_ENCRYPTION_CONFIGURATION, {404, "ServerSideEncryptionConfigurationNotFoundError"}}, { ERR_NO_SUCH_PUBLIC_ACCESS_BLOCK_CONFIGURATION, {404, "NoSuchPublicAccessBlockConfiguration"}}, { ERR_ACCOUNT_EXISTS, {409, "AccountAlreadyExists"}}, + { ECANCELED, {409, "ConcurrentModification"}}, }); rgw_http_errors rgw_http_swift_errors({ diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index 813b78f161e..8d71ec5c688 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -153,6 +153,11 @@ static const actpair actpairs[] = { "iam:ListRoleTags", iamListRoleTags}, { "iam:UntagRole", iamUntagRole}, { "iam:UpdateRole", iamUpdateRole}, + { "iam:CreateUser", iamCreateUser}, + { "iam:GetUser", iamGetUser}, + { "iam:UpdateUser", iamUpdateUser}, + { "iam:DeleteUser", iamDeleteUser}, + { "iam:ListUsers", iamListUsers}, { "sts:AssumeRole", stsAssumeRole}, { "sts:AssumeRoleWithWebIdentity", stsAssumeRoleWithWebIdentity}, { "sts:GetSessionToken", stsGetSessionToken}, @@ -1454,6 +1459,21 @@ const char* action_bit_string(uint64_t action) { case iamUpdateRole: return "iam:UpdateRole"; + case iamCreateUser: + return "iam:CreateUser"; + + case iamGetUser: + return "iam:GetUser"; + + case iamUpdateUser: + return "iam:UpdateUser"; + + case iamDeleteUser: + return "iam:DeleteUser"; + + case iamListUsers: + return "iam:ListUsers"; + case stsAssumeRole: return "sts:AssumeRole"; diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index c6d3bc9ad77..bf4983695c3 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -134,6 +134,11 @@ enum { iamListRoleTags, iamUntagRole, iamUpdateRole, + iamCreateUser, + iamGetUser, + iamUpdateUser, + iamDeleteUser, + iamListUsers, iamAll, stsAssumeRole, diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h index 53ef18696de..128479143e0 100644 --- a/src/rgw/rgw_op_type.h +++ b/src/rgw/rgw_op_type.h @@ -85,6 +85,11 @@ enum RGWOpType { RGW_OP_LIST_ROLE_TAGS, RGW_OP_UNTAG_ROLE, RGW_OP_UPDATE_ROLE, + RGW_OP_CREATE_USER, + RGW_OP_GET_USER, + RGW_OP_UPDATE_USER, + RGW_OP_DELETE_USER, + RGW_OP_LIST_USERS, /* 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 b9e8779c10a..c391761ccd6 100644 --- a/src/rgw/rgw_rest_iam.cc +++ b/src/rgw/rgw_rest_iam.cc @@ -1,6 +1,7 @@ // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab ft=cpp +#include #include #include "rgw_auth_s3.h" @@ -9,6 +10,7 @@ #include "rgw_rest_role.h" #include "rgw_rest_user_policy.h" #include "rgw_rest_oidc_provider.h" +#include "rgw_rest_iam_user.h" #define dout_context g_ceph_context #define dout_subsys ceph_subsys_rgw @@ -37,7 +39,12 @@ static const std::unordered_map op_generators = {"TagRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWTagRole(bl_post_body);}}, {"ListRoleTags", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWListRoleTags;}}, {"UntagRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWUntagRole(bl_post_body);}}, - {"UpdateRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWUpdateRole(bl_post_body);}} + {"UpdateRole", [](const bufferlist& bl_post_body) -> RGWOp* {return new RGWUpdateRole(bl_post_body);}}, + {"CreateUser", make_iam_create_user_op}, + {"GetUser", make_iam_get_user_op}, + {"UpdateUser", make_iam_update_user_op}, + {"DeleteUser", make_iam_delete_user_op}, + {"ListUsers", make_iam_list_users_op}, }; bool RGWHandler_REST_IAM::action_exists(const req_state* s) @@ -88,3 +95,54 @@ RGWRESTMgr_IAM::get_handler(rgw::sal::Driver* driver, bufferlist bl; return new RGWHandler_REST_IAM(auth_registry, bl); } + +static constexpr size_t MAX_USER_NAME_LEN = 64; + +bool validate_iam_user_name(const std::string& name, std::string& err) +{ + if (name.empty()) { + err = "Missing required element UserName"; + return false; + } + if (name.size() > MAX_USER_NAME_LEN) { + err = "UserName too long"; + return false; + } + const std::regex pattern("[\\w+=,.@-]+"); + if (!std::regex_match(name, pattern)) { + err = "UserName 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) +{ + if (path.size() > MAX_PATH_LEN) { + err = "Path too long"; + return false; + } + const std::regex pattern("(/[!-~]+/)|(/)"); + if (!std::regex_match(path, pattern)) { + err = "Path contains invalid characters"; + return false; + } + return true; +} + +std::string iam_user_arn(const RGWUserInfo& info) +{ + if (info.type == TYPE_ROOT) { + return fmt::format("arn:aws:iam::{}:root", info.account_id); + } + std::string_view acct = !info.account_id.empty() + ? info.account_id : info.user_id.tenant; + std::string_view path = info.path; + if (path.empty()) { + path = "/"; + } + return fmt::format("arn:aws:iam::{}:user{}{}", + acct, path, info.display_name); +} diff --git a/src/rgw/rgw_rest_iam.h b/src/rgw/rgw_rest_iam.h index c1edd201e37..e2cac242c8b 100644 --- a/src/rgw/rgw_rest_iam.h +++ b/src/rgw/rgw_rest_iam.h @@ -8,6 +8,13 @@ #include "rgw_rest.h" +struct RGWUserInfo; + +bool validate_iam_user_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); + 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_user.cc b/src/rgw/rgw_rest_iam_user.cc new file mode 100644 index 00000000000..54bbcc10433 --- /dev/null +++ b/src/rgw/rgw_rest_iam_user.cc @@ -0,0 +1,610 @@ +// -*- 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_user.h" + +#include +#include "include/buffer.h" +#include "common/errno.h" +#include "rgw_arn.h" +#include "rgw_common.h" +#include "rgw_op.h" +#include "rgw_rest.h" +#include "rgw_rest_iam.h" + + +static std::string make_resource_name(const RGWUserInfo& info) +{ + std::string_view path = info.path; + if (path.empty()) { + path = "/"; + } + return string_cat_reserve(path, info.display_name); +} + +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); + encode_json("CreateDate", info.create_date, f); +} + + +// CreateUser +class RGWCreateUser_IAM : public RGWOp { + RGWUserInfo info; + 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 "create_user"; } + RGWOpType get_type() override { return RGW_OP_CREATE_USER; } +}; + +int RGWCreateUser_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.display_name = s->info.args.get("UserName"); + if (!validate_iam_user_name(info.display_name, s->err.message)) { + return -EINVAL; + } + + // TODO: Tags + return 0; +} + +int RGWCreateUser_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "user", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamCreateUser, true)) { + return 0; + } + return -EACCES; +} + +void RGWCreateUser_IAM::execute(optional_yield y) +{ + // check the current user 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; + return; + } + + if (account.max_users >= 0) { // max_users < 0 means unlimited + uint32_t count = 0; + op_ret = driver->count_account_users(this, y, info.account_id, count); + if (op_ret < 0) { + ldpp_dout(this, 4) << "failed to count users for iam account " + << info.account_id << ": " << cpp_strerror(op_ret) << dendl; + return; + } + if (std::cmp_greater_equal(count, account.max_users)) { + s->err.message = fmt::format("User limit {} exceeded", + account.max_users); + op_ret = ERR_LIMIT_EXCEEDED; + return; + } + } + + // generate user id + uuid_d uuid; + uuid.generate_random(); + info.user_id.id = uuid.to_string(); + info.user_id.tenant = s->auth.identity->get_tenant(); + + info.create_date = ceph::real_clock::now(); + + std::unique_ptr user = driver->get_user(info.user_id); + user->get_info() = info; + + constexpr bool exclusive = true; + op_ret = user->store_user(this, y, exclusive, nullptr); +} + +void RGWCreateUser_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "CreateUserResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "CreateUserResult"}; + Formatter::ObjectSection user{*f, "User"}; + dump_iam_user(info, f); + // /User + // /CreateUserResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /CreateUserResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// GetUser +class RGWGetUser_IAM : public RGWOp { + rgw_account_id account_id; + 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 "get_user"; } + RGWOpType get_type() override { return RGW_OP_GET_USER; } +}; + +int RGWGetUser_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) { + account_id = *id; + } else { + return -ERR_METHOD_NOT_ALLOWED; + } + + const std::string username = s->info.args.get("UserName"); + if (username.empty()) { + // If you do not specify a user name, IAM determines the user name + // implicitly based on the AWS access key ID signing the request. + // This operation works for access keys under the AWS account. + // Consequently, you can use this operation to manage AWS account + // root user credentials. + user = s->user->clone(); + return 0; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + int 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 RGWGetUser_IAM::verify_permission(optional_yield y) +{ + const RGWUserInfo& info = user->get_info(); + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "user", account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamGetUser, true)) { + return 0; + } + return -EACCES; +} + +void RGWGetUser_IAM::execute(optional_yield y) +{ +} + +void RGWGetUser_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "GetUserResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "GetUserResult"}; + Formatter::ObjectSection User{*f, "User"}; + dump_iam_user(user->get_info(), f); + // /User + // /GetUserResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /GetUserResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// UpdateUser +class RGWUpdateUser_IAM : public RGWOp { + std::string new_path; + std::string new_username; + 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 "update_user"; } + RGWOpType get_type() override { return RGW_OP_UPDATE_USER; } +}; + +int RGWUpdateUser_IAM::init_processing(optional_yield y) +{ + // use account id from authenticated user/role. with AssumeRole, this may not + // match the account of s->user + 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_username = s->info.args.get("NewUserName"); + if (!new_username.empty() && + !validate_iam_user_name(new_username, s->err.message)) { + return -EINVAL; + } + + const std::string username = s->info.args.get("UserName"); + if (username.empty()) { + s->err.message = "Missing required element UserName"; + return -EINVAL; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + int 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 RGWUpdateUser_IAM::verify_permission(optional_yield y) +{ + const RGWUserInfo& info = user->get_info(); + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "user", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamUpdateUser, true)) { + return 0; + } + return -EACCES; +} + +void RGWUpdateUser_IAM::execute(optional_yield y) +{ + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + if (!new_path.empty()) { + info.path = new_path; + } + if (!new_username.empty()) { + info.display_name = new_username; + } + + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); +} + +void RGWUpdateUser_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "UpdateUserResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "UpdateUserResult"}; + Formatter::ObjectSection User{*f, "User"}; + dump_iam_user(user->get_info(), f); + // /User + // /UpdateUserResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /UpdateUserResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// DeleteUser +class RGWDeleteUser_IAM : public RGWOp { + 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 "delete_user"; } + RGWOpType get_type() override { return RGW_OP_DELETE_USER; } +}; + +int RGWDeleteUser_IAM::init_processing(optional_yield y) +{ + // use account id from authenticated user/role. with AssumeRole, this may not + // match the account of s->user + 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 username = s->info.args.get("UserName"); + if (username.empty()) { + s->err.message = "Missing required element UserName"; + return -EINVAL; + } + + // look up user by UserName + const std::string& tenant = s->auth.identity->get_tenant(); + int 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 RGWDeleteUser_IAM::verify_permission(optional_yield y) +{ + const RGWUserInfo& info = user->get_info(); + const std::string resource_name = make_resource_name(info); + const rgw::ARN arn{resource_name, "user", info.account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamDeleteUser, true)) { + return 0; + } + return -EACCES; +} + +void RGWDeleteUser_IAM::execute(optional_yield y) +{ + // verify that all user resources are removed first + const RGWUserInfo& info = user->get_info(); + if (!info.access_keys.empty()) { + s->err.message = "The user cannot be deleted until its AccessKeys are removed"; + op_ret = -ERR_DELETE_CONFLICT; + return; + } + + const auto& attrs = user->get_attrs(); + if (auto p = attrs.find(RGW_ATTR_USER_POLICY); p != attrs.end()) { + std::map policies; + try { + decode(policies, p->second); + } catch (const buffer::error&) { + ldpp_dout(this, 0) << "ERROR: failed to decode user policies" << dendl; + op_ret = -EIO; + return; + } + + if (!policies.empty()) { + s->err.message = "The user cannot be deleted until all user policies are removed"; + op_ret = -ERR_DELETE_CONFLICT; + return; + } + } + + op_ret = user->remove_user(this, y); +} + +void RGWDeleteUser_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "DeleteUserResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /DeleteUserResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListUsers +class RGWListUsers_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 users); + 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_users"; } + RGWOpType get_type() override { return RGW_OP_LIST_USERS; } +}; + +int RGWListUsers_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) { + 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 RGWListUsers_IAM::verify_permission(optional_yield y) +{ + const std::string resource_name = ""; + const rgw::ARN arn{resource_name, "user", account_id, true}; + if (verify_user_permission(this, s, arn, rgw::IAM::iamListUsers, true)) { + return 0; + } + return -EACCES; +} + +void RGWListUsers_IAM::execute(optional_yield y) +{ + const std::string& tenant = s->auth.identity->get_tenant(); + + rgw::sal::UserList listing; + listing.next_marker = marker; + + op_ret = driver->list_account_users(this, y, account_id, tenant, + 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.users); + + if (!started_response) { + started_response = true; + start_response(); + } + end_response(listing.next_marker); +} + +void RGWListUsers_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("ListUsersResponse", RGW_REST_IAM_XMLNS); + s->formatter->open_object_section("ListUsersResult"); + s->formatter->open_array_section("Users"); +} + +void RGWListUsers_IAM::end_response(std::string_view next_marker) +{ + s->formatter->close_section(); // Users + + 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(); // ListUsersResult + s->formatter->close_section(); // ListUsersResponse + rgw_flush_formatter_and_reset(s, s->formatter); +} + +void RGWListUsers_IAM::send_response_data(std::span users) +{ + if (!started_response) { + started_response = true; + start_response(); + } + + for (const auto& info : users) { + s->formatter->open_object_section("member"); + dump_iam_user(info, s->formatter); + s->formatter->close_section(); // member + } + + // flush after each chunk + rgw_flush_formatter(s, s->formatter); +} + +void RGWListUsers_IAM::send_response() +{ + if (!started_response) { // errored out before execute() wrote anything + start_response(); + } +} + + +RGWOp* make_iam_create_user_op(const ceph::bufferlist&) { + return new RGWCreateUser_IAM; +} +RGWOp* make_iam_get_user_op(const ceph::bufferlist&) { + return new RGWGetUser_IAM; +} +RGWOp* make_iam_update_user_op(const ceph::bufferlist&) { + return new RGWUpdateUser_IAM; +} +RGWOp* make_iam_delete_user_op(const ceph::bufferlist&) { + return new RGWDeleteUser_IAM; +} +RGWOp* make_iam_list_users_op(const ceph::bufferlist&) { + return new RGWListUsers_IAM; +} diff --git a/src/rgw/rgw_rest_iam_user.h b/src/rgw/rgw_rest_iam_user.h new file mode 100644 index 00000000000..06d5ce6b580 --- /dev/null +++ b/src/rgw/rgw_rest_iam_user.h @@ -0,0 +1,27 @@ +// -*- 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 User op factory functions +RGWOp* make_iam_create_user_op(const ceph::bufferlist& unused); +RGWOp* make_iam_get_user_op(const ceph::bufferlist& unused); +RGWOp* make_iam_update_user_op(const ceph::bufferlist& unused); +RGWOp* make_iam_delete_user_op(const ceph::bufferlist& unused); +RGWOp* make_iam_list_users_op(const ceph::bufferlist& unused);