From: Casey Bodley Date: Wed, 20 Dec 2023 20:01:00 +0000 (-0500) Subject: rgw/iam: add IAM AccessKey apis X-Git-Tag: testing/wip-yuriw-testing-20240416.150233~10^2~116 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=fe6159c1e7cc3c2fc8a07fff4ff73180fd78a582;p=ceph-ci.git rgw/iam: add IAM AccessKey apis Signed-off-by: Casey Bodley (cherry picked from commit 927d533308359f506eba1ee56a560692d9049d62) --- diff --git a/src/rgw/rgw_auth_s3.cc b/src/rgw/rgw_auth_s3.cc index be12f50a20d..5f55ba21f71 100644 --- a/src/rgw/rgw_auth_s3.cc +++ b/src/rgw/rgw_auth_s3.cc @@ -504,6 +504,10 @@ bool is_non_s3_op(RGWOpType op_type) case RGW_OP_UPDATE_USER: case RGW_OP_DELETE_USER: case RGW_OP_LIST_USERS: + case RGW_OP_CREATE_ACCESS_KEY: + case RGW_OP_UPDATE_ACCESS_KEY: + case RGW_OP_DELETE_ACCESS_KEY: + case RGW_OP_LIST_ACCESS_KEYS: return true; default: return false; diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index 8d71ec5c688..0a954a3c954 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -158,6 +158,10 @@ static const actpair actpairs[] = { "iam:UpdateUser", iamUpdateUser}, { "iam:DeleteUser", iamDeleteUser}, { "iam:ListUsers", iamListUsers}, + { "iam:CreateAccessKey", iamCreateAccessKey}, + { "iam:UpdateAccessKey", iamUpdateAccessKey}, + { "iam:DeleteAccessKey", iamDeleteAccessKey}, + { "iam:ListAccessKeys", iamListAccessKeys}, { "sts:AssumeRole", stsAssumeRole}, { "sts:AssumeRoleWithWebIdentity", stsAssumeRoleWithWebIdentity}, { "sts:GetSessionToken", stsGetSessionToken}, @@ -1474,6 +1478,18 @@ const char* action_bit_string(uint64_t action) { case iamListUsers: return "iam:ListUsers"; + case iamCreateAccessKey: + return "iam:CreateAccessKey"; + + case iamUpdateAccessKey: + return "iam:UpdateAccessKey"; + + case iamDeleteAccessKey: + return "iam:DeleteAccessKey"; + + case iamListAccessKeys: + return "iam:ListAccessKeys"; + case stsAssumeRole: return "sts:AssumeRole"; diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index bf4983695c3..232896a97f4 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -139,6 +139,10 @@ enum { iamUpdateUser, iamDeleteUser, iamListUsers, + iamCreateAccessKey, + iamUpdateAccessKey, + iamDeleteAccessKey, + iamListAccessKeys, iamAll, stsAssumeRole, diff --git a/src/rgw/rgw_op_type.h b/src/rgw/rgw_op_type.h index 128479143e0..71456f755c8 100644 --- a/src/rgw/rgw_op_type.h +++ b/src/rgw/rgw_op_type.h @@ -90,6 +90,10 @@ enum RGWOpType { RGW_OP_UPDATE_USER, RGW_OP_DELETE_USER, RGW_OP_LIST_USERS, + RGW_OP_CREATE_ACCESS_KEY, + RGW_OP_UPDATE_ACCESS_KEY, + RGW_OP_DELETE_ACCESS_KEY, + RGW_OP_LIST_ACCESS_KEYS, /* 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 c391761ccd6..a3a7cee6a23 100644 --- a/src/rgw/rgw_rest_iam.cc +++ b/src/rgw/rgw_rest_iam.cc @@ -45,6 +45,10 @@ static const std::unordered_map op_generators = {"UpdateUser", make_iam_update_user_op}, {"DeleteUser", make_iam_delete_user_op}, {"ListUsers", make_iam_list_users_op}, + {"CreateAccessKey", make_iam_create_access_key_op}, + {"UpdateAccessKey", make_iam_update_access_key_op}, + {"DeleteAccessKey", make_iam_delete_access_key_op}, + {"ListAccessKeys", make_iam_list_access_keys_op}, }; bool RGWHandler_REST_IAM::action_exists(const req_state* s) diff --git a/src/rgw/rgw_rest_iam_user.cc b/src/rgw/rgw_rest_iam_user.cc index 54bbcc10433..9194d98f98a 100644 --- a/src/rgw/rgw_rest_iam_user.cc +++ b/src/rgw/rgw_rest_iam_user.cc @@ -593,6 +593,494 @@ void RGWListUsers_IAM::send_response() } +void dump_access_key(const RGWAccessKey& key, Formatter* f) +{ + encode_json("AccessKeyId", key.id, f); + encode_json("Status", key.active ? "Active" : "Inactive", f); + encode_json("CreateDate", key.create_date, f); +} + +// CreateAccessKey +class RGWCreateAccessKey_IAM : public RGWOp { + std::unique_ptr user; + RGWAccessKey key; + 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_access_key"; } + RGWOpType get_type() override { return RGW_OP_CREATE_ACCESS_KEY; } +}; + +int RGWCreateAccessKey_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()) { + // 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; + } + 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(); + 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 RGWCreateAccessKey_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::iamCreateAccessKey, true)) { + return 0; + } + return -EACCES; +} + +void RGWCreateAccessKey_IAM::execute(optional_yield y) +{ + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + std::optional max_keys; + { + // read account's access key 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_access_keys >= 0) { // max < 0 means unlimited + max_keys = account.max_access_keys; + } + } + + if (rgw_generate_access_key(this, y, driver, key.id) < 0) { + s->err.message = "failed to generate s3 access key"; + op_ret = -ERR_INTERNAL_ERROR; + return; + } + rgw_generate_secret_key(get_cct(), key.key); + key.create_date = ceph::real_clock::now(); + info.access_keys[key.id] = key; + + // check the current count against account limit + if (max_keys && std::cmp_greater(info.access_keys.size(), *max_keys)) { + s->err.message = fmt::format("Access key limit {} exceeded", *max_keys); + op_ret = -ERR_LIMIT_EXCEEDED; + return; + } + + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); +} + +void RGWCreateAccessKey_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "CreateAccessKeyResponse", RGW_REST_IAM_XMLNS}; + { + Formatter::ObjectSection result{*f, "CreateAccessKeyResult"}; + Formatter::ObjectSection accesskey{*f, "AccessKey"}; + encode_json("UserName", user->get_display_name(), f); + dump_access_key(key, f); + encode_json("SecretAccessKey", key.key, f); + // /AccessKey + // /CreateAccessKeyResult + } + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /CreateAccessKeyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// UpdateAccessKey +class RGWUpdateAccessKey_IAM : public RGWOp { + std::string access_key_id; + bool new_status = false; + 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_access_key"; } + RGWOpType get_type() override { return RGW_OP_UPDATE_ACCESS_KEY; } +}; + +int RGWUpdateAccessKey_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; + } + + access_key_id = s->info.args.get("AccessKeyId"); + if (access_key_id.empty()) { + s->err.message = "Missing required element AccessKeyId"; + return -EINVAL; + } + + const std::string status = s->info.args.get("Status"); + if (status == "Active") { + new_status = true; + } else if (status == "Inactive") { + new_status = false; + } else { + if (status.empty()) { + s->err.message = "Missing required element Status"; + } else { + s->err.message = "Invalid value for Status"; + } + return -EINVAL; + } + + 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; + } + 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(); + 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 RGWUpdateAccessKey_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::iamUpdateAccessKey, true)) { + return 0; + } + return -EACCES; +} + +void RGWUpdateAccessKey_IAM::execute(optional_yield y) +{ + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + auto key = info.access_keys.find(access_key_id); + if (key == info.access_keys.end()) { + s->err.message = "No such AccessKeyId in the user"; + op_ret = -ERR_NO_SUCH_ENTITY; + return; + } + + if (key->second.active == new_status) { + return; // nothing to do, return success + } + + key->second.active = new_status; + + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); +} + +void RGWUpdateAccessKey_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "UpdateAccessKeyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /UpdateAccessKeyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + +// DeleteAccessKey +class RGWDeleteAccessKey_IAM : public RGWOp { + std::string access_key_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 "delete_access_key"; } + RGWOpType get_type() override { return RGW_OP_DELETE_ACCESS_KEY; } +}; + +int RGWDeleteAccessKey_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; + } + + access_key_id = s->info.args.get("AccessKeyId"); + if (access_key_id.empty()) { + s->err.message = "Missing required element AccessKeyId"; + return -EINVAL; + } + + 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; + } + 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(); + 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 RGWDeleteAccessKey_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::iamDeleteAccessKey, true)) { + return 0; + } + return -EACCES; +} + +void RGWDeleteAccessKey_IAM::execute(optional_yield y) +{ + RGWUserInfo& info = user->get_info(); + RGWUserInfo old_info = info; + + auto key = info.access_keys.find(access_key_id); + if (key == info.access_keys.end()) { + s->err.message = "No such AccessKeyId in the user"; + op_ret = -ERR_NO_SUCH_ENTITY; + return; + } + + info.access_keys.erase(key); + + constexpr bool exclusive = false; + op_ret = user->store_user(this, y, exclusive, &old_info); +} + +void RGWDeleteAccessKey_IAM::send_response() +{ + if (!op_ret) { + dump_start(s); // + Formatter* f = s->formatter; + Formatter::ObjectSection response{*f, "DeleteAccessKeyResponse", RGW_REST_IAM_XMLNS}; + Formatter::ObjectSection metadata{*f, "ResponseMetadata"}; + f->dump_string("RequestId", s->trans_id); + // /ResponseMetadata + // /DeleteAccessKeyResponse + } + + set_req_state_err(s, op_ret); + dump_errno(s); + end_header(s, this); +} + + +// ListAccessKeys +class RGWListAccessKeys_IAM : public RGWOp { + std::unique_ptr user; + std::string marker; + int max_items = 100; + + bool started_response = false; + void start_response(); + 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_access_keys"; } + RGWOpType get_type() override { return RGW_OP_LIST_ACCESS_KEYS; } +}; + +int RGWListAccessKeys_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; + } + + 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 (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; + } + 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) { + return -ERR_NO_SUCH_ENTITY; + } + return r; +} + +int RGWListAccessKeys_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::iamListAccessKeys, true)) { + return 0; + } + return -EACCES; +} + +void RGWListAccessKeys_IAM::execute(optional_yield y) +{ + start_response(); + started_response = true; + + dump_start(s); // + + Formatter* f = s->formatter; + f->open_object_section_in_ns("ListAccessKeysResponse", RGW_REST_IAM_XMLNS); + f->open_object_section("ListAccessKeysResult"); + encode_json("UserName", user->get_display_name(), f); + f->open_array_section("AccessKeyMetadata"); + + const RGWUserInfo& info = user->get_info(); + + auto key = info.access_keys.lower_bound(marker); + for (int i = 0; i < max_items && key != info.access_keys.end(); ++i, ++key) { + f->open_object_section("member"); + encode_json("UserName", user->get_display_name(), f); + dump_access_key(key->second, f); + f->close_section(); // member + } + + f->close_section(); // AccessKeyMetadata + + const bool truncated = (key != info.access_keys.end()); + f->dump_bool("IsTruncated", truncated); + if (truncated) { + f->dump_string("Marker", key->second.id); + } + + f->close_section(); // ListAccessKeysResult + f->close_section(); // ListAccessKeysResponse + rgw_flush_formatter_and_reset(s, f); +} + +void RGWListAccessKeys_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); +} + +void RGWListAccessKeys_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; } @@ -608,3 +1096,16 @@ RGWOp* make_iam_delete_user_op(const ceph::bufferlist&) { RGWOp* make_iam_list_users_op(const ceph::bufferlist&) { return new RGWListUsers_IAM; } + +RGWOp* make_iam_create_access_key_op(const ceph::bufferlist& unused) { + return new RGWCreateAccessKey_IAM; +} +RGWOp* make_iam_update_access_key_op(const ceph::bufferlist& unused) { + return new RGWUpdateAccessKey_IAM; +} +RGWOp* make_iam_delete_access_key_op(const ceph::bufferlist& unused) { + return new RGWDeleteAccessKey_IAM; +} +RGWOp* make_iam_list_access_keys_op(const ceph::bufferlist& unused) { + return new RGWListAccessKeys_IAM; +} diff --git a/src/rgw/rgw_rest_iam_user.h b/src/rgw/rgw_rest_iam_user.h index 06d5ce6b580..ed7e960b229 100644 --- a/src/rgw/rgw_rest_iam_user.h +++ b/src/rgw/rgw_rest_iam_user.h @@ -25,3 +25,9 @@ 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); + +// AccessKey op factory functions +RGWOp* make_iam_create_access_key_op(const ceph::bufferlist& unused); +RGWOp* make_iam_update_access_key_op(const ceph::bufferlist& unused); +RGWOp* make_iam_delete_access_key_op(const ceph::bufferlist& unused); +RGWOp* make_iam_list_access_keys_op(const ceph::bufferlist& unused);