From 85986004d10e041ccf72cfcf86156742acfc82ce Mon Sep 17 00:00:00 2001 From: Naveen Paul Date: Wed, 19 Nov 2025 15:17:23 +0530 Subject: [PATCH] IAM serve flow Signed-off-by: Naveen Paul --- src/api/account_api.js | 567 ++++++++++++++++++- src/endpoint/iam/iam_rest.js | 17 +- src/sdk/accountspace_nb.js | 409 ++----------- src/server/system_services/account_server.js | 449 ++++++++++++++- src/util/account_util.js | 88 +-- 5 files changed, 1110 insertions(+), 420 deletions(-) diff --git a/src/api/account_api.js b/src/api/account_api.js index e9c4596781..f1eb1e7133 100644 --- a/src/api/account_api.js +++ b/src/api/account_api.js @@ -41,6 +41,12 @@ module.exports = { allow_bucket_creation: { type: 'boolean' }, + owner: { + type: 'string', + }, + iam_path: { + type: 'string', + }, roles: { type: 'array', items: { @@ -87,6 +93,12 @@ module.exports = { type: 'object', required: ['token'], properties: { + id: { + type: 'string' + }, + arn: { + type: 'string' + }, token: { type: 'string' }, @@ -612,9 +624,491 @@ module.exports = { system: 'admin' } }, - + get_user: { + method: 'GET', + doc: 'Get IAM user', + params: { + type: 'object', + properties: { + username: { + type: 'string' + }, + }, + }, + reply: { + $ref: '#/definitions/user_info', + }, + auth: { + system: 'admin' + } + }, + create_user: { + method: 'POST', + doc: 'Create IAM Users', + params: { + type: 'object', + required: ['email', 'name'], + properties: { + name: { $ref: 'common_api#/definitions/account_name' }, + email: { $ref: 'common_api#/definitions/email' }, + default_resource: { + type: 'string', + }, + s3_access: { + type: 'boolean' + }, + has_login: { // DEPRECATED - TO BE REMOVED + type: 'boolean' + }, + allow_bucket_creation: { + type: 'boolean' + }, + roles: { + type: 'array', + items: { + type: 'string', + enum: ['admin', 'user', 'viewer', 'operator'] + } + }, + owner: { + type: 'string', + }, + iam_path: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + required: ['arn', 'id'], + properties: { + id: { + type: 'string' + }, + arn: { + type: 'string' + }, + token: { + type: 'string' + }, + access_keys: { + type: 'array', + items: { + $ref: 'common_api#/definitions/access_keys' + } + }, + } + }, + auth: { + system: 'admin' + } + }, + update_user: { + method: 'PUT', + doc: 'Update IAM Users', + params: { + type: 'object', + required: ['username'], + properties: { + username: { + type: 'string' + }, + new_username: { + type: 'string' + }, + new_iam_path: { + type: 'string' + } + } + }, + reply: { + $ref: '#/definitions/user_info', + }, + auth: { + system: 'admin' + } + }, + delete_user: { + method: 'DELETE', + doc: 'Delete IAM Users', + params: { + type: 'object', + required: ['username'], + properties: { + username: { + type: 'string' + }, + } + }, + auth: { + system: 'admin' + } + }, + list_users: { + doc: 'List IAM Users', + method: 'GET', + params: { + type: 'object', + properties: { + iam_path_prefix: { + type: 'string', + }, + max_items: { + type: 'integer', + }, + marker: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + members: { + type: 'array', + items: { + $ref: '#/definitions/user_info' + }, + }, + is_truncated: { + type: 'boolean' + }, + } + }, + auth: { + system: 'admin' + } + }, + create_access_key: { + doc: 'Create accesskeys for users', + method: 'POST', + params: { + type: 'object', + properties: { + username: { + type: 'string', + }, + } + }, + reply: { + $ref: '#/definitions/user_accesskey_info' + }, + auth: { + system: 'admin' + } + }, + list_access_keys: { + doc: 'List IAM User accessKeys', + method: 'GET', + params: { + type: 'object', + properties: { + username: { + type: 'string', + }, + max_items: { + type: 'integer', + }, + marker: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + members: { + type: 'array', + items: { + $ref: '#/definitions/user_accesskey_info' + }, + }, + is_truncated: { + type: 'boolean', + }, + username: { + type: 'string', + } + } + }, + auth: { + system: 'admin' + } + }, + update_access_key: { + doc: 'Update accesskeys for users', + method: 'PUT', + params: { + type: 'object', + required: [ 'access_key', 'status'], + properties: { + username: { + type: 'string', + }, + access_key: { + type: 'string', + }, + status: { + type: 'string', + enum: [ + 'Inactive', + 'Active', + ] + }, + } + }, + auth: { + system: 'admin' + } + }, + delete_access_key: { + doc: 'Delete accesskeys for users', + method: 'DELETE', + params: { + type: 'object', + required: ['access_key'], + properties: { + username: { + type: 'string', + }, + access_key: { + type: 'string', + }, + } + }, + auth: { + system: 'admin' + } + }, + get_access_key_last_used: { + doc: 'Accesskey last used', + method: 'GET', + params: { + type: 'object', + required: ['access_key'], + properties: { + access_key: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + region: { + type: 'string', + }, + username: { + type: 'string', + }, + service_name: { + type: 'string', + }, + last_used_date: { + idate: true, + }, + }, + }, + auth: { + system: 'admin' + } + }, + tag_user: { + doc: 'Tag user', + method: 'POST', + params: { + type: 'object', + required: ['username', 'tags'], + properties: { + username: { + type: 'string', + }, + tags: { + type: 'array', + items: { + $ref: 'common_api#/definitions/tag', + } + }, + } + }, + auth: { + system: 'admin' + }, + }, + untag_user: { + doc: 'Remove user tags', + method: 'DELETE', + params: { + type: 'object', + required: ['username', 'tag_keys'], + properties: { + username: { + type: 'string', + }, + tag_keys: { + type: 'array', + items: { + type: 'string', + } + }, + } + }, + auth: { + system: 'admin' + }, + }, + list_user_tags: { + doc: 'List user tags', + method: 'GET', + params: { + type: 'object', + required: ['username'], + properties: { + username: { + type: 'string', + }, + max_items: { + type: 'integer', + }, + marker: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + tags: { + type: 'array', + items: { + type: 'object', + properties: { + member: { + $ref: '#/definitions/tag' + }, + }, + }, + }, + is_truncated: { + type: 'boolean', + }, + } + }, + auth: { + system: 'admin' + } + }, + put_user_policy: { + doc: 'Put user policy', + method: 'POST', + params: { + type: 'object', + required: ['username', 'policy_name', 'policy_document'], + properties: { + username: { + type: 'string', + }, + policy_name: { + type: 'string', + }, + policy_document: { + $ref: 'common_api#/definitions/iam_user_policy_document', + }, + } + }, + auth: { + system: 'admin' + } + }, + get_user_policy: { + doc: 'Get user policy', + method: 'GET', + params: { + type: 'object', + required: ['username', 'policy_name'], + properties: { + username: { + type: 'string', + }, + policy_name: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + username: { + type: 'string', + }, + policy_name: { + type: 'string', + }, + policy_document: { + type: 'string', + } + } + }, + auth: { + system: 'admin' + } + }, + delete_user_policy: { + doc: 'Delete user policy', + method: 'DELETE', + params: { + type: 'object', + required: ['username', 'policy_name'], + properties: { + username: { + type: 'string', + }, + policy_name: { + type: 'string', + }, + } + }, + auth: { + system: 'admin' + } + }, + list_user_policies: { + doc: 'List user policies', + method: 'GET', + params: { + type: 'object', + required: ['username'], + properties: { + username: { + type: 'string', + }, + max_items: { + type: 'integer', + }, + marker: { + type: 'string', + }, + } + }, + reply: { + type: 'object', + properties: { + is_truncated: { + type: 'boolean' + }, + members: { + type: 'array', + items: { + type: 'string', + } + } + } + }, + auth: { + system: 'admin' + } + }, }, - definitions: { account_info: { type: 'object', @@ -782,6 +1276,73 @@ module.exports = { } } }] - } + }, + user_info: { + type: 'object', + required: ['username'], + properties: { + user_id: { + type: 'string' + }, + username: { + type: 'string' + }, + arn: { + type: 'string' + }, + iam_path: { + type: 'string' + }, + create_date: { + idate: true, + }, + password_last_used: { + idate: true, + }, + access_keys: { + type: 'array', + items: { + $ref: 'common_api#/definitions/access_keys' + } + }, + } + }, + user_accesskey_info: { + type: 'object', + required: ['username', 'status', 'access_key'], + properties: { + username: { + type: 'string', + }, + status: { + type: 'string' + }, + create_date: { + idate: true, + }, + access_key: { + type: 'string' + }, + secret_key: { + type: 'string' + }, + } + }, + // Cant reuse the common_api tag, where key and value is small letter. GAP + tag: { + type: 'object', + required: [ + 'Key', + 'Value' + ], + properties: { + Key: { + type: 'string' + }, + Value: { + type: 'string' + }, + } + }, } }; diff --git a/src/endpoint/iam/iam_rest.js b/src/endpoint/iam/iam_rest.js index 67725fbae4..5b1bd1c0df 100644 --- a/src/endpoint/iam/iam_rest.js +++ b/src/endpoint/iam/iam_rest.js @@ -23,6 +23,18 @@ const RPC_ERRORS_TO_IAM = Object.freeze({ VALIDATION_ERROR: IamError.ValidationError, INVALID_INPUT: IamError.InvalidInput, MALFORMED_POLICY_DOCUMENT: IamError.MalformedPolicyDocument, + ENTITY_ALREADY_EXISTS: IamError.EntityAlreadyExists, + NO_SUCH_ENTITY: IamError.NoSuchEntity, + CONCURRENT_MODIFICATION: IamError.ConcurrentModification, + LIMIT_EXCEEDED: IamError.LimitExceeded, + SERVICE_FAILURE: IamError.ServiceFailure, + DELETE_CONFLICT: IamError.DeleteConflict, + ENTITY_TEMPORARILY_UNMODIFIABLE: IamError.EntityTemporarilyUnmodifiable, + INVALID_PARAMETER_VALUE: IamError.InvalidParameterValue, + EXPIRED_TOKEN: IamError.ExpiredToken, + ACCESS_DENIED_EXCEPTION: IamError.AccessDeniedException, + NOT_AUTHORIZED: IamError.NotAuthorized, + INTERNAL_FAILURE: IamError.InternalFailure, }); const ACTIONS = Object.freeze({ @@ -226,7 +238,10 @@ function handle_error(req, res, err) { const iam_err = ((err instanceof IamError) && err) || new IamError(RPC_ERRORS_TO_IAM[err.rpc_code] || IamError.InternalFailure); - + // Contanarized IAM always send RPC error. + if (!req.object_sdk.nsfs_config_root) { + iam_err.message = err.message; + } const reply = iam_err.reply(req.request_id); dbg.error('IAM ERROR', reply, req.method, req.originalUrl, diff --git a/src/sdk/accountspace_nb.js b/src/sdk/accountspace_nb.js index 12c938a242..bb389aeb3a 100644 --- a/src/sdk/accountspace_nb.js +++ b/src/sdk/accountspace_nb.js @@ -1,14 +1,9 @@ /* Copyright (C) 2024 NooBaa */ 'use strict'; -const _ = require('lodash'); const account_util = require('../util/account_util'); -const iam_utils = require('../endpoint/iam/iam_utils'); -const dbg = require('../util/debug_module')(__filename); -const IamError = require('../endpoint/iam/iam_errors').IamError; const system_store = require('..//server/system_services/system_store').get_instance(); -const { IAM_ACTIONS, IAM_DEFAULT_PATH, ACCESS_KEY_STATUS_ENUM, - MAX_TAGS } = require('../endpoint/iam/iam_constants'); +const { IAM_DEFAULT_PATH} = require('../endpoint/iam/iam_constants'); /* TODO: DISCUSS: @@ -41,154 +36,57 @@ class AccountSpaceNB { async create_user(params, account_sdk) { - const action = IAM_ACTIONS.CREATE_USER; const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - account_util._check_if_requesting_account_is_root_account(action, requesting_account, - { username: params.username, path: params.iam_path }); - account_util._check_username_already_exists(action, params, requesting_account); - const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), params.username, - params.iam_path || IAM_DEFAULT_PATH); const account_name = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); const req = { - rpc_params: { name: account_name, email: account_name, has_login: false, s3_access: true, allow_bucket_creation: true, - owner: requesting_account._id, - is_iam: true, + owner: requesting_account._id.toString(), iam_path: params.iam_path, - role: 'admin', - + roles: ['admin'], // TODO: default_resource remove - default_resource: 'noobaa-default-backing-store', - }, - account: requesting_account, - }; - const iam_account = await account_util.create_account(req); - + default_resource: requesting_account.default_resource.name, + }; + const iam_account = await account_sdk.rpc_client.account.create_user(req, requesting_account); // TODO : Clean account cache // TODO : Send Event - const requested_account = system_store.get_account_by_email(account_name); return { - iam_path: requested_account.iam_path || IAM_DEFAULT_PATH, + iam_path: params.iam_path || IAM_DEFAULT_PATH, username: params.username, user_id: iam_account.id, - arn: iam_arn, + arn: iam_account.arn, create_date: iam_account.create_date, }; - } async get_user(params, account_sdk) { - const action = IAM_ACTIONS.GET_USER; + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const username = iam_utils.get_iam_username(params.username || requested_account.name.unwrap()); - const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username, - requested_account.iam_path || IAM_DEFAULT_PATH); - const reply = { - user_id: requested_account._id.toString(), - iam_path: requested_account.iam_path || IAM_DEFAULT_PATH, - username: username, - arn: iam_arn, - // TODO: GAP Need to save created date - create_date: Date.now(), - // TODO: Dates missing : GAP - password_last_used: Date.now(), - }; - return reply; + return await account_sdk.rpc_client.account.get_user(params, requesting_account); } async update_user(params, account_sdk) { - const action = IAM_ACTIONS.UPDATE_USER; + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const old_username = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - account_util._check_if_requesting_account_is_root_account(action, requesting_account, - { username: params.username, iam_path: params.iam_path }); - account_util._check_if_account_exists(action, old_username); - const requested_account = system_store.get_account_by_email(old_username); - let iam_path = requested_account.iam_path; - let user_name = iam_utils.get_iam_username(requested_account.name.unwrap()); - account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - if (params.new_iam_path) iam_path = params.new_iam_path; - if (params.new_username) user_name = params.new_username; - const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), user_name, iam_path); - const new_account_name = account_util.get_account_name_from_username(user_name, requesting_account._id.toString()); - const updates = { - name: new_account_name, - email: new_account_name, - iam_path: iam_path, - }; - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: _.omitBy(updates, _.isUndefined), - }] - } - }); + return await account_sdk.rpc_client.account.update_user(params, requesting_account); // TODO : Clean account cache // TODO : Send Event - return { - iam_path: iam_path || IAM_DEFAULT_PATH, - username: user_name, - user_id: requested_account._id.toString(), - arn: iam_arn - }; - } async delete_user(params, account_sdk) { - const action = IAM_ACTIONS.DELETE_USER; const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const username = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - account_util._check_if_account_exists(action, username); - const requested_account = system_store.get_account_by_email(username); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - account_util._check_if_user_does_not_have_resources_before_deletion(action, requested_account); - const req = { - system: system_store.data.systems[0], - account: requested_account, - }; - return account_util.delete_account(req, requested_account); + return await account_sdk.rpc_client.account.delete_user(params, requesting_account); // TODO : clean account cache } async list_users(params, account_sdk) { - const action = IAM_ACTIONS.LIST_USERS; const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { }); - const is_truncated = false; // GAP - no pagination at this point - - - const requesting_account_iam_users = _.filter(system_store.data.accounts, function(account) { - // Check IAM user owner is same as requesting_account id - return account.owner?._id.toString() === requesting_account._id.toString(); - }); - let members = _.map(requesting_account_iam_users, function(iam_user) { - const iam_username = iam_utils.get_iam_username(iam_user.name.unwrap()); - const iam_path = iam_user.iam_path || IAM_DEFAULT_PATH; - const member = { - user_id: iam_user._id.toString(), - iam_path: iam_path, - username: iam_username, - arn: iam_utils.create_arn_for_user(iam_user.owner._id.toString(), iam_username, iam_path), - // TODO: GAP Need to save created date - create_date: Date.now(), - // TODO: GAP missing password_last_used - password_last_used: Date.now(), // GAP - }; - return member; - }); - members = members.sort((a, b) => a.username.localeCompare(b.username)); - return { members, is_truncated }; + return await account_sdk.rpc_client.account.list_users(params, requesting_account); + } ///////////////////////////////// @@ -196,115 +94,34 @@ class AccountSpaceNB { ///////////////////////////////// async create_access_key(params, account_sdk) { - const action = IAM_ACTIONS.CREATE_ACCESS_KEY; - const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - account_util._check_number_of_access_key_array(action, requested_account); - const req = { - rpc_params: { - email: requested_account.email, - is_iam: true, - }, - account: requesting_account, - }; - let iam_access_key; - try { - iam_access_key = await account_util.generate_account_keys(req); - } catch (err) { - dbg.error(`AccountSpaceNB.${action} error: `, err); - const message_with_details = `Create accesskey failed for the user with name ${account_util.get_iam_username(requested_account.email.unwrap())}.`; - const { code, http_code, type } = IamError.InternalFailure; - throw new IamError({ code, message: message_with_details, http_code, type }); - } - return { - username: iam_utils.get_iam_username(requested_account.name.unwrap()), - access_key: iam_access_key.access_key.unwrap(), - create_date: iam_access_key.creation_date, - status: ACCESS_KEY_STATUS_ENUM.ACTIVE, - secret_key: iam_access_key.secret_key.unwrap(), - }; + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); + return await account_sdk.rpc_client.account.create_access_key(params, requesting_account); } async get_access_key_last_used(params, account_sdk) { - const action = IAM_ACTIONS.GET_ACCESS_KEY_LAST_USED; - const dummy_region = 'us-west-2'; - const dummy_service_name = 's3'; const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - account_util._check_access_key_belongs_to_account(action, requesting_account, params.access_key); - // TODO: Need to return valid last_used_date date, Low priority. - const username = account_util._returned_username(requesting_account, requesting_account.name.unwrap(), false); - return { - region: dummy_region, // GAP - last_used_date: Date.now(), // GAP - service_name: dummy_service_name, // GAP - username: username ? iam_utils.get_iam_username(username) : undefined, - }; + return await account_sdk.rpc_client.account.get_access_key_last_used(params, requesting_account); } async update_access_key(params, account_sdk) { - const action = IAM_ACTIONS.UPDATE_ACCESS_KEY; - const access_key_id = params.access_key; + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - account_util._check_access_key_belongs_to_account(action, requested_account, access_key_id); - - const updating_access_key_obj = _.find(requested_account.access_keys, - access_key => access_key.access_key.unwrap() === access_key_id); - if (account_util._get_access_key_status(updating_access_key_obj.deactivated) === params.status) { - dbg.log0(`AccountSpaceNB.${action} status was not change, not updating the database`); - return; - } - const filtered_access_keys = account_util.get_non_updating_access_key(requested_account, access_key_id); - updating_access_key_obj.deactivated = account_util._check_access_key_is_deactivated(params.status); - updating_access_key_obj.secret_key = system_store.master_key_manager - .encrypt_sensitive_string_with_master_key_id(updating_access_key_obj.secret_key, requested_account.master_key_id._id); - filtered_access_keys.push(updating_access_key_obj); - - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: { access_keys: filtered_access_keys } - }] - } - }); + return await account_sdk.rpc_client.account.update_access_key(params, requesting_account); // TODO : clean account cache } async delete_access_key(params, account_sdk) { - const action = IAM_ACTIONS.DELETE_ACCESS_KEY; - const access_key_id = params.access_key; - const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - account_util._check_access_key_belongs_to_account(action, requested_account, access_key_id); - // Filter out the deleting access key from the access key list and save remaining accesskey. - const filtered_access_keys = account_util.get_non_updating_access_key(requested_account, access_key_id); - const updates = { - access_keys: filtered_access_keys, - }; - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: _.omitBy(updates, _.isUndefined), - }] - } - }); + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); + return await account_sdk.rpc_client.account.delete_access_key(params, requesting_account); // TODO : clean account cache } async list_access_keys(params, account_sdk) { - const action = IAM_ACTIONS.LIST_ACCESS_KEYS; - const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const is_truncated = false; // // GAP - no pagination at this point - let members = account_util._list_access_keys_from_account(requesting_account, requested_account, false); - members = members.sort((a, b) => a.access_key.localeCompare(b.access_key)); - return { members, is_truncated, - username: account_util._returned_username(requesting_account, requested_account.name.unwrap(), false) }; + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); + return await account_sdk.rpc_client.account.list_access_keys(params, requesting_account); } /////////////////////////// @@ -312,106 +129,21 @@ class AccountSpaceNB { /////////////////////////// async tag_user(params, account_sdk) { - const action = IAM_ACTIONS.TAG_USER; const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const username = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - account_util._check_if_account_exists(action, username); - - const requested_account = system_store.get_account_by_email(username); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - - const existing_tags = requested_account.tagging || []; - - const tags_map = new Map(); - for (const tag of existing_tags) { - tags_map.set(tag.key, tag.value); - } - for (const tag of params.tags) { - tags_map.set(tag.key, tag.value); - } - - // enforce AWS tag limit after merging - if (tags_map.size > MAX_TAGS) { - const message_with_details = `Failed to tag user. User cannot have more than ${MAX_TAGS} tags.`; - const { code, http_code, type } = IamError.LimitExceeded; - throw new IamError({ code, message: message_with_details, http_code, type }); - } - - const updated_tags = Array.from(tags_map.entries()).map(([key, value]) => ({ key, value })); - - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: { tagging: updated_tags } - }] - } - }); - - dbg.log1('AccountSpaceNB.tag_user: successfully tagged user', params.username, 'with', params.tags.length, 'tags'); + return await account_sdk.rpc_client.account.tag_user(params, requesting_account); } async untag_user(params, account_sdk) { - const action = IAM_ACTIONS.UNTAG_USER; - const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const username = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - account_util._check_if_account_exists(action, username); - - const requested_account = system_store.get_account_by_email(username); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - - const existing_tags = requested_account.tagging || []; - const tag_keys_set = new Set(params.tag_keys); - const updated_tags = existing_tags.filter(tag => !tag_keys_set.has(tag.key)); - - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: { tagging: updated_tags } - }] - } - }); - - dbg.log1('AccountSpaceNB.untag_user: successfully removed', params.tag_keys.length, 'tags from user', params.username); + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); + return await account_sdk.rpc_client.account.untag_user(params, requesting_account); } async list_user_tags(params, account_sdk) { - const action = IAM_ACTIONS.LIST_USER_TAGS; - const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const username = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - account_util._check_if_account_exists(action, username); - - const requested_account = system_store.get_account_by_email(username); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - - // TODO: Pagination not supported - currently returns all tags, ignoring marker and max_items params - const all_tags = requested_account.tagging || []; - const sorted_tags = all_tags.sort((a, b) => a.key.localeCompare(b.key)); - const tags = sorted_tags.length > 0 ? sorted_tags.map(tag => ({ - member: { - Key: tag.key, - Value: tag.value - } - })) : []; - - dbg.log1('AccountSpaceNB.list_user_tags: returning', tags.length, 'tags for user', params.username); + const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); + return await account_sdk.rpc_client.account.list_user_tags(params, requesting_account); - return { - tags: tags, - is_truncated: false - }; } //////////////////// @@ -419,100 +151,25 @@ class AccountSpaceNB { //////////////////// async put_user_policy(params, account_sdk) { - const action = IAM_ACTIONS.PUT_USER_POLICY; - dbg.log1(`AccountSpaceNB.${action}`, params); const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const iam_user_policies = requested_account.iam_user_policies || []; - const index_of_iam_user_policy = account_util._get_iam_user_policy_index(iam_user_policies, params.policy_name); - const iam_user_policy_to_add = { - policy_name: params.policy_name, - policy_document: params.policy_document, - }; - if (index_of_iam_user_policy === -1) { - iam_user_policies.push(iam_user_policy_to_add); - } else { - iam_user_policies[index_of_iam_user_policy] = iam_user_policy_to_add; - } - - account_util._check_total_policy_size(iam_user_policies, params.username); - - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: { iam_user_policies }, - }] - } - }); + return await account_sdk.rpc_client.account.put_user_policy(params, requesting_account); } async get_user_policy(params, account_sdk) { - const action = IAM_ACTIONS.GET_USER_POLICY; - dbg.log1(`AccountSpaceNB.${action}`, params); const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const iam_user_policies = requested_account.iam_user_policies || []; - const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, params.policy_name); - return { - username: params.username, - policy_name: params.policy_name, - policy_document: JSON.stringify(iam_user_policies[iam_user_policy_index].policy_document), - }; + return await account_sdk.rpc_client.account.get_user_policy(params, requesting_account); } async delete_user_policy(params, account_sdk) { - const action = IAM_ACTIONS.DELETE_USER_POLICY; - dbg.log1(`AccountSpaceNB.${action}`, params); const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const iam_user_policies = requested_account.iam_user_policies || []; - const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, params.policy_name); - iam_user_policies.splice(iam_user_policy_index, 1); - - await system_store.make_changes({ - update: { - accounts: [{ - _id: requested_account._id, - $set: { iam_user_policies }, - }] - } - }); + return await account_sdk.rpc_client.account.delete_user_policy(params, requesting_account); } async list_user_policies(params, account_sdk) { - const action = IAM_ACTIONS.LIST_USER_POLICIES; - dbg.log1(`AccountSpaceNB.${action}`, params); const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email); - const requested_account = validate_and_return_requested_account(params, action, requesting_account); - const is_truncated = false; // GAP - no pagination at this point - let members = _.map(requested_account.iam_user_policies || [], iam_user_policy => iam_user_policy.policy_name); - members = members.sort((a, b) => a.localeCompare(b)); - return { - is_truncated, - members - }; + return await account_sdk.rpc_client.account.list_user_policies(params, requesting_account); } } - -function validate_and_return_requested_account(params, action, requesting_account) { - const on_itself = !params.username; - let requested_account; - if (on_itself) { - // When accesskeyt API called without specific username, action on the same requesting account. - // So in that case requesting account and requested account is same. - requested_account = requesting_account; - } else { - account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - const account_email = account_util.get_account_name_from_username(params.username, requesting_account._id.toString()); - account_util._check_if_account_exists(action, account_email); - requested_account = system_store.get_account_by_email(account_email); - account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); - account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); - } - return requested_account; -} - // EXPORTS module.exports = AccountSpaceNB; diff --git a/src/server/system_services/account_server.js b/src/server/system_services/account_server.js index 3750ec576c..cd2c3782ff 100644 --- a/src/server/system_services/account_server.js +++ b/src/server/system_services/account_server.js @@ -23,6 +23,9 @@ const { Durations, LogsQueryClient } = require('@azure/monitor-query-logs'); const { ClientSecretCredential } = require("@azure/identity"); const noobaa_s3_client = require('../../sdk/noobaa_s3_client/noobaa_s3_client'); const account_util = require('./../../util/account_util'); +const iam_utils = require('../../endpoint/iam/iam_utils'); +const { IAM_ACTIONS, IAM_DEFAULT_PATH, ACCESS_KEY_STATUS_ENUM, + MAX_TAGS } = require('../../endpoint/iam/iam_constants'); const check_connection_timeout = 15 * 1000; @@ -34,13 +37,25 @@ const check_new_azure_connection_timeout = 20 * 1000; * */ async function create_account(req) { + const action = IAM_ACTIONS.CREATE_USER; + let iam_arn; + if (req.rpc_params.owner) { + const user_name = account_util.get_iam_username(req.rpc_params.email.unwrap()); + account_util._check_if_requesting_account_is_root_account(action, req.account, + { username: user_name, path: req.rpc_params.iam_path }); + account_util._check_username_already_exists(action, req.rpc_params.email, user_name); + iam_arn = iam_utils.create_arn_for_user(req.account._id.toString(), user_name, + req.rpc_params.iam_path || IAM_DEFAULT_PATH); + } else { + account_util.validate_create_account_permissions(req); + account_util.validate_create_account_params(req); + } + const {id, token, access_keys} = await account_util.create_account(req); - - account_util.validate_create_account_permissions(req); - account_util.validate_create_account_params(req); - const {token, access_keys} = await account_util.create_account(req); - + iam_arn = iam_arn || iam_utils.create_arn_for_root(id); return { + id, + arn: iam_arn, token, access_keys }; @@ -117,9 +132,8 @@ function read_account(req) { function read_account_by_access_key(req) { const { access_key } = req.rpc_params; - const account = _.find(system_store.data.accounts, acc => - acc.access_keys && acc.access_keys[0].access_key.unwrap() === access_key.unwrap() + acc.access_keys && acc.access_keys.length > 0 && acc.access_keys[0].access_key.unwrap() === access_key.unwrap() ); if (!account) throw new RpcError('NO_SUCH_ACCOUNT', 'No such account with credentials: ' + access_key); @@ -1168,6 +1182,410 @@ function _verify_can_delete_account(req, account_to_delete) { } } +/** + * + * IAM APIs methods + * + */ + +async function create_user(req) { + return await create_account(req); +} + +async function get_user(req) { + + const action = IAM_ACTIONS.GET_USER; + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const username = account_util.get_iam_username(req.rpc_params.username || requested_account.name.unwrap()); + const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), username, + requested_account.iam_path || IAM_DEFAULT_PATH); + return { + user_id: requested_account._id.toString(), + iam_path: requested_account.iam_path || IAM_DEFAULT_PATH, + username: username, + arn: iam_arn, + // TODO: GAP Need to save created date + create_date: Date.now(), + // TODO: Dates missing : GAP + password_last_used: Date.now(), + }; +} + +async function update_user(req) { + + const action = IAM_ACTIONS.UPDATE_USER; + const requesting_account = system_store.get_account_by_email(req.account.email); + const old_username = account_util.get_account_name_from_username(req.rpc_params.username, requesting_account._id.toString()); + account_util._check_if_requesting_account_is_root_account(action, requesting_account, + { username: req.rpc_params.username, iam_path: req.rpc_params.new_iam_path }); + account_util._check_if_account_exists(action, old_username); + const requested_account = system_store.get_account_by_email(old_username); + let iam_path = requested_account.iam_path; + let user_name = account_util.get_iam_username(requested_account.name.unwrap()); + // Change to complete user name + const new_username = account_util.get_account_name_from_username(req.rpc_params.new_username, requesting_account._id.toString()); + account_util._check_username_already_exists(action, new_username, req.rpc_params.new_username); + account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + if (req.rpc_params.new_iam_path) iam_path = req.rpc_params.new_iam_path; + if (req.rpc_params.new_username) user_name = req.rpc_params.new_username; + const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), user_name, iam_path); + const new_account_name = account_util.get_account_name_from_username(user_name, requesting_account._id.toString()); + const updates = { + name: new_account_name, + email: new_account_name, + iam_path: iam_path, + }; + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: _.omitBy(updates, _.isUndefined), + }] + } + }); + + return { + iam_path: iam_path || IAM_DEFAULT_PATH, + username: user_name, + user_id: requested_account._id.toString(), + arn: iam_arn + }; +} + +async function delete_user(req) { + + const action = IAM_ACTIONS.DELETE_USER; + const requesting_account = req.account; + const username = account_util.get_account_name_from_username(req.rpc_params.username, requesting_account._id.toString()); + account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: req.rpc_params.username }); + account_util._check_if_account_exists(action, username); + const requested_account = system_store.get_account_by_email(username); + account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + account_util._check_if_user_does_not_have_resources_before_deletion(action, requested_account); + const delete_user_info = { + system: system_store.data.systems[0], + account: requested_account, + }; + return account_util.delete_account(delete_user_info, requested_account); +} + +async function list_users(req) { + const action = IAM_ACTIONS.LIST_USERS; + const requesting_account = req.account; + account_util._check_if_requesting_account_is_root_account(action, requesting_account, { }); + const is_truncated = false; // GAP - no pagination at this point + + const requesting_account_iam_users = _.filter(system_store.data.accounts, function(account) { + // Check IAM user owner is same as requesting_account id + return account.owner?._id.toString() === requesting_account._id.toString(); + }); + let members = _.map(requesting_account_iam_users, function(iam_user) { + const iam_username = account_util.get_iam_username(iam_user.name.unwrap()); + const iam_path = iam_user.iam_path || IAM_DEFAULT_PATH; + let member; + // Check the iam_path_prefix and add only those satify the iam_path if exists + if (req.rpc_params.iam_path_prefix) { + if (iam_path.toUpperCase() === req.rpc_params.iam_path_prefix.toUpperCase()) { + member = account_util.return_list_member(iam_user, iam_path, iam_username); + } + } else { + member = account_util.return_list_member(iam_user, iam_path, iam_username); + } + return member; + }).filter(Boolean); + members = members.sort((a, b) => a.username.localeCompare(b.username)); + return { members, is_truncated }; +} + +async function create_access_key(req) { + const action = IAM_ACTIONS.CREATE_ACCESS_KEY; + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + account_util._check_number_of_access_key_array(action, requested_account); + const account_req = { + rpc_params: { + email: requested_account.email, + is_iam: true, + owner: requesting_account._id.toString(), + }, + account: requesting_account, + }; + let iam_access_key; + try { + iam_access_key = await account_util.generate_account_keys(account_req); + } catch (err) { + dbg.error(`AccountSpaceNB.${action} error: `, err); + const message_with_details = `Create accesskey failed for the user with name ${account_util.get_iam_username(requested_account.email.unwrap())}.`; + throw new RpcError('INTERNAL_FAILURE', message_with_details, 500); + } + + return { + username: account_util.get_iam_username(requested_account.name.unwrap()), + access_key: iam_access_key.access_key.unwrap(), + create_date: iam_access_key.creation_date, + status: ACCESS_KEY_STATUS_ENUM.ACTIVE, + secret_key: iam_access_key.secret_key.unwrap(), + }; +} + +async function list_access_keys(req) { + const action = IAM_ACTIONS.LIST_ACCESS_KEYS; + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const is_truncated = false; // // GAP - no pagination at this point + let members = account_util._list_access_keys_from_account(requesting_account, requested_account, false); + members = members.sort((a, b) => a.access_key.localeCompare(b.access_key)); + return { members, is_truncated, + username: account_util._returned_username(requesting_account, requested_account.name.unwrap(), false) }; +} + + +async function update_access_key(req) { + const action = IAM_ACTIONS.UPDATE_ACCESS_KEY; + const access_key_id = req.rpc_params.access_key; + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + account_util._check_access_key_belongs_to_account(action, requested_account, access_key_id); + + const updating_access_key_obj = _.find(requested_account.access_keys, + access_key => access_key.access_key.unwrap() === access_key_id); + if (account_util._get_access_key_status(updating_access_key_obj.deactivated) === req.rpc_params.status) { + dbg.log0(`AccountSpaceNB.${action} status was not change, not updating the database`); + return; + } + const filtered_access_keys = account_util.get_non_updating_access_key(requested_account, access_key_id); + updating_access_key_obj.deactivated = account_util._check_access_key_is_deactivated(req.rpc_params.status); + updating_access_key_obj.secret_key = system_store.master_key_manager + .encrypt_sensitive_string_with_master_key_id(updating_access_key_obj.secret_key, requested_account.master_key_id._id); + filtered_access_keys.push(updating_access_key_obj); + + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: { access_keys: filtered_access_keys } + }] + } + }); +} + +async function get_access_key_last_used(req) { + const action = IAM_ACTIONS.GET_ACCESS_KEY_LAST_USED; + const requesting_account = req.account; + const dummy_region = 'us-west-2'; + const dummy_service_name = 's3'; + account_util._check_access_key_belongs_to_account(action, requesting_account, req.rpc_params.access_key); + // TODO: Need to return valid last_used_date date, Low priority. + const username = account_util._returned_username(requesting_account, requesting_account.name.unwrap(), false); + return { + region: dummy_region, // GAP + last_used_date: Date.now(), // GAP + service_name: dummy_service_name, // GAP + username: username ? account_util.get_iam_username(username) : undefined, + }; +} + +async function delete_access_key(req) { + const action = IAM_ACTIONS.DELETE_ACCESS_KEY; + const access_key_id = req.rpc_params.access_key; + const requesting_account = req.account; + + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + account_util._check_access_key_belongs_to_account(action, requested_account, access_key_id); + // Filter out the deleting access key from the access key list and save remaining accesskey. + const filtered_access_keys = account_util.get_non_updating_access_key(requested_account, access_key_id); + const updates = { + access_keys: filtered_access_keys, + }; + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: _.omitBy(updates, _.isUndefined), + }] + } + }); +} + +async function tag_user(req) { + const action = IAM_ACTIONS.TAG_USER; + const requesting_account = req.account; + const username = account_util.get_account_name_from_username(req.rpc_params.username, requesting_account._id.toString()); + + account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: req.rpc_params.username }); + account_util._check_if_account_exists(action, username); + + const requested_account = system_store.get_account_by_email(username); + account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + + const existing_tags = requested_account.tagging || []; + + const tags_map = new Map(); + for (const tag of existing_tags) { + tags_map.set(tag.key, tag.value); + } + for (const tag of req.rpc_params.tags) { + tags_map.set(tag.key, tag.value); + } + + // enforce AWS tag limit after merging + if (tags_map.size > MAX_TAGS) { + const message_with_details = `Failed to tag user. User cannot have more than ${MAX_TAGS} tags.`; + throw new RpcError('LIMIT_EXCEEDED', message_with_details, 409); + } + + const updated_tags = Array.from(tags_map.entries()).map(([key, value]) => ({ key, value })); + + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: { tagging: updated_tags } + }] + } + }); + + dbg.log1('AccountSpaceNB.tag_user: successfully tagged user', req.rpc_params.username, 'with', req.rpc_params.tags.length, 'tags'); +} + +async function untag_user(req) { + const action = IAM_ACTIONS.UNTAG_USER; + const requesting_account = req.account; + const username = account_util.get_account_name_from_username(req.rpc_params.username, requesting_account._id.toString()); + + account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: req.rpc_params.username }); + account_util._check_if_account_exists(action, username); + + const requested_account = system_store.get_account_by_email(username); + account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + + const existing_tags = requested_account.tagging || []; + + const tag_keys_set = new Set(req.rpc_params.tag_keys); + const updated_tags = existing_tags.filter(tag => !tag_keys_set.has(tag.key)); + + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: { tagging: updated_tags } + }] + } + }); + + dbg.log1('AccountSpaceNB.untag_user: successfully removed', req.rpc_params.tag_keys.length, 'tags from user', req.rpc_params.username); +} + +async function list_user_tags(req) { + const action = IAM_ACTIONS.LIST_USER_TAGS; + const requesting_account = req.account; + const username = account_util.get_account_name_from_username(req.rpc_params.username, requesting_account._id.toString()); + + account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: req.rpc_params.username }); + account_util._check_if_account_exists(action, username); + + const requested_account = system_store.get_account_by_email(username); + account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + + // TODO: Pagination not supported - currently returns all tags, ignoring marker and max_items params + const all_tags = requested_account.tagging || []; + const sorted_tags = all_tags.sort((a, b) => a.key.localeCompare(b.key)); + + const tags = sorted_tags.length > 0 ? sorted_tags.map(tag => ({ + member: { + Key: tag.key, + Value: tag.value + } + })) : []; + dbg.log0('AccountSpaceNB.list_user_tags: returning', tags, 'tags for user', req.rpc_params.username); + + return { + tags: tags, + is_truncated: false + }; +} + +async function put_user_policy(req) { + const action = IAM_ACTIONS.PUT_USER_POLICY; + const requesting_account = req.account; + dbg.log1(`AccountSpaceNB.${action}`, req.rpc_params); + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const iam_user_policies = requested_account.iam_user_policies || []; + const index_of_iam_user_policy = account_util._get_iam_user_policy_index(iam_user_policies, req.rpc_params.policy_name); + const iam_user_policy_to_add = { + policy_name: req.rpc_params.policy_name, + policy_document: req.rpc_params.policy_document, + }; + if (index_of_iam_user_policy === -1) { + iam_user_policies.push(iam_user_policy_to_add); + } else { + iam_user_policies[index_of_iam_user_policy] = iam_user_policy_to_add; + } + + account_util._check_total_policy_size(iam_user_policies, req.rpc_params.username); + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: { iam_user_policies }, + }] + } + }); +} + +async function get_user_policy(req) { + const action = IAM_ACTIONS.GET_USER_POLICY; + dbg.log1(`AccountSpaceNB.${action}`, req.rpc_params); + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const iam_user_policies = requested_account.iam_user_policies || []; + const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, req.rpc_params.policy_name); + return { + username: req.rpc_params.username, + policy_name: req.rpc_params.policy_name, + policy_document: JSON.stringify(iam_user_policies[iam_user_policy_index].policy_document), + }; +} + +async function delete_user_policy(req) { + const action = IAM_ACTIONS.DELETE_USER_POLICY; + dbg.log1(`AccountSpaceNB.${action}`, req.rpc_params); + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const iam_user_policies = requested_account.iam_user_policies || []; + const iam_user_policy_index = account_util._check_user_policy_exists(action, iam_user_policies, req.rpc_params.policy_name); + iam_user_policies.splice(iam_user_policy_index, 1); + + await system_store.make_changes({ + update: { + accounts: [{ + _id: requested_account._id, + $set: { iam_user_policies }, + }] + } + }); +} + +async function list_user_policies(req) { + const action = IAM_ACTIONS.LIST_USER_POLICIES; + dbg.log1(`AccountSpaceNB.${action}`, req.rpc_params); + const requesting_account = req.account; + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); + const is_truncated = false; // GAP - no pagination at this point + let members = _.map(requested_account.iam_user_policies || [], iam_user_policy => iam_user_policy.policy_name); + members = members.sort((a, b) => a.localeCompare(b)); + return { + is_truncated, + members + }; +} + // EXPORTS exports.create_account = create_account; exports.create_external_user_account = create_external_user_account; @@ -1191,3 +1609,20 @@ exports.ensure_support_account = ensure_support_account; exports.verify_authorized_account = verify_authorized_account; exports.get_account_usage = get_account_usage; exports.read_account_by_access_key = read_account_by_access_key; +exports.create_user = create_user; +exports.get_user = get_user; +exports.update_user = update_user; +exports.delete_user = delete_user; +exports.list_users = list_users; +exports.create_access_key = create_access_key; +exports.update_access_key = update_access_key; +exports.delete_access_key = delete_access_key; +exports.tag_user = tag_user; +exports.untag_user = untag_user; +exports.list_user_tags = list_user_tags; +exports.list_access_keys = list_access_keys; +exports.put_user_policy = put_user_policy; +exports.get_user_policy = get_user_policy; +exports.delete_user_policy = delete_user_policy; +exports.list_user_policies = list_user_policies; +exports.get_access_key_last_used = get_access_key_last_used; diff --git a/src/util/account_util.js b/src/util/account_util.js index c032a7e345..3cbc5c3672 100644 --- a/src/util/account_util.js +++ b/src/util/account_util.js @@ -12,7 +12,6 @@ const auth_server = require('..//server/common_services/auth_server'); const system_store = require('..//server/system_services/system_store').get_instance(); const pool_server = require('../server/system_services/pool_server'); const { OP_NAME_TO_ACTION } = require('../endpoint/sts/sts_rest'); -const IamError = require('../endpoint/iam/iam_errors').IamError; const { create_arn_for_user, get_action_message_title, get_iam_username } = require('../endpoint/iam/iam_utils'); const { IAM_ACTIONS, MAX_NUMBER_OF_ACCESS_KEYS, IAM_DEFAULT_PATH, ACCESS_KEY_STATUS_ENUM, IAM_ACTIONS_USER_INLINE_POLICY, AWS_LIMIT_CHARS_USER_INlINE_POLICY } = require('../endpoint/iam/iam_constants'); @@ -104,14 +103,14 @@ async function create_account(req) { desc: `${account.email.unwrap()} was created ` + (req.account ? `by ${req.account.email.unwrap()}` : ``), }); } - const account_access_info = create_access_key_auth(req, account, req.rpc_params.is_iam); + const account_access_info = create_access_key_auth(req, account, req.rpc_params.owner); if (req.rpc_params.role_config) { validate_assume_role_policy(req.rpc_params.role_config.assume_role_policy); account.role_config = req.rpc_params.role_config; } // TODO : remove rpc_params - if (req.rpc_params.is_iam) { + if (req.rpc_params.owner) { account.owner = req.rpc_params.owner; account.iam_path = req.rpc_params.iam_path; } @@ -141,8 +140,8 @@ async function create_account(req) { return { token: auth_server.make_auth_token(auth), access_keys: account_access_info.decrypted_access_keys, - id: req.rpc_params.is_iam ? created_account._id.toString() : undefined, - create_date: req.rpc_params.is_iam ? Date.now() : undefined, // GAP: Do not save account creation date + id: created_account._id.toString(), + create_date: req.rpc_params.owner ? Date.now() : undefined, // GAP: Do not save account creation date }; } @@ -228,7 +227,7 @@ async function generate_account_keys(req) { access_key_obj.creation_date = now; decrypted_access_keys.creation_date = now; // IAM create accesskey will have multiple accesskeys. - if (req.rpc_params.is_iam) { + if (req.rpc_params.owner) { account_access_keys.push(access_key_obj); } else { // Noobaa CLI create and generate accesskey commands replace existing accesskey @@ -251,7 +250,7 @@ async function generate_account_keys(req) { account: account._id, desc: `Credentials for ${account.email.unwrap()} were regenerated ${req.account && 'by ' + req.account.email.unwrap()}`, }); - if (req.rpc_params.is_iam) { + if (req.rpc_params.owner) { return decrypted_access_keys; } } @@ -327,8 +326,7 @@ function _check_if_account_exists(action, email_wrapped) { if (!account) { dbg.error(`AccountSpaceNB.${action} username does not exist`, email_wrapped.unwrap()); const message_with_details = `The user with name ${get_iam_username(email_wrapped.unwrap())} cannot be found.`; - const { code, http_code, type } = IamError.NoSuchEntity; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('NO_SUCH_ENTITY', message_with_details); } } @@ -360,14 +358,12 @@ function _check_if_requesting_account_is_root_account(action, requesting_account } } -function _check_username_already_exists(action, params, requesting_account) { - const username = get_account_name_from_username(params.username, requesting_account._id); - const account = system_store.get_account_by_email(username); +function _check_username_already_exists(action, email, username) { + const account = system_store.get_account_by_email(email); if (account) { - dbg.error(`AccountSpaceNB.${action} username already exists`, params.username); - const message_with_details = `User with name ${params.username} already exists.`; - const { code, http_code, type } = IamError.EntityAlreadyExists; - throw new IamError({ code, message: message_with_details, http_code, type }); + dbg.error(`AccountSpaceNB.${action} username already exists`, username); + const message_with_details = `User with name ${username} already exists.`; + throw new RpcError('ENTITY_ALREADY_EXISTS', message_with_details); } } @@ -395,8 +391,7 @@ function _check_if_requested_is_owned_by_root_account(action, requesting_account requested_account.name.unwrap() : requested_account.name; dbg.error(`AccountSpaceNB.${action} requested account is not owned by root account`, username); const message_with_details = `The user with name ${get_iam_username(username)} cannot be found.`; - const { code, http_code, type } = IamError.NoSuchEntity; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('NO_SUCH_ENTITY', message_with_details); } } @@ -420,7 +415,7 @@ function _returned_username(requesting_account, username, on_itself) { function _throw_error_perform_action_from_root_accounts_manager_on_iam_user(action, requesting_account, requested_account) { dbg.error(`AccountSpaceNB.${action} root accounts manager cannot perform actions on IAM users`, requesting_account, requested_account); - throw new IamError(IamError.NotAuthorized); + throw new RpcError('NOT_AUTHORIZED', 'You do not have permission to perform this action.'); } // TODO: move to IamError class with a template @@ -432,22 +427,19 @@ function _throw_error_perform_action_on_another_root_account(action, requesting_ dbg.error(`AccountSpaceNB.${action} root account of requested account is different than requesting root account`, requesting_account._id.toString(), username); const message_with_details = `The user with name ${get_iam_username(username)} cannot be found.`; - const { code, http_code, type } = IamError.NoSuchEntity; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('NO_SUCH_ENTITY', message_with_details); } function _throw_error_no_such_entity_access_key(action, access_key_id) { dbg.error(`AccountSpaceNB.${action} access key does not exist`, access_key_id); const message_with_details = `The Access Key with id ${access_key_id} cannot be found`; - const { code, http_code, type } = IamError.NoSuchEntity; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('NO_SUCH_ENTITY', message_with_details); } function _throw_error_no_such_entity_policy(action, policy_name) { dbg.error(`AccountSpaceNB.${action} The user policy with name does not exist`, policy_name); const message_with_details = `The user policy with name ${policy_name} cannot be found`; - const { code, http_code, type } = IamError.NoSuchEntity; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('NO_SUCH_ENTITY', message_with_details); } function _throw_access_denied_error(action, requesting_account, details, entity) { @@ -471,16 +463,14 @@ function _throw_access_denied_error(action, requesting_account, details, entity) } else { message_with_details = basic_message + `access key ${details.access_key}`; } - const { code, http_code, type } = IamError.AccessDeniedException; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('UNAUTHORIZED', message_with_details); } function _throw_error_delete_conflict(action, account_to_delete, resource_name) { dbg.error(`AccountSpaceNB.${action} requested account ` + `${account_to_delete.name} ${account_to_delete._id} has ${resource_name}`); const message_with_details = `Cannot delete entity, must delete ${resource_name} first.`; - const { code, http_code, type } = IamError.DeleteConflict; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('DELETE_CONFLICT', message_with_details); } // ACCESS KEY VALIDATIONS @@ -490,8 +480,7 @@ function _check_number_of_access_key_array(action, requested_account) { dbg.error(`AccountSpaceNB.${action} cannot exceed quota for AccessKeysPerUser `, requested_account.name); const message_with_details = `Cannot exceed quota for AccessKeysPerUser: ${MAX_NUMBER_OF_ACCESS_KEYS}.`; - const { code, http_code, type } = IamError.LimitExceeded; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('LIMIT_EXCEEDED', message_with_details); } } @@ -597,8 +586,7 @@ function _check_total_policy_size(iam_user_policies, username) { const total_chars_size = _get_total_size_of_policies(iam_user_policies); if (total_chars_size > AWS_LIMIT_CHARS_USER_INlINE_POLICY) { const message_with_details = `Maximum policy size of 2048 bytes exceeded for user ${username}`; - const { code, http_code, type } = IamError.LimitExceeded; - throw new IamError({ code, message: message_with_details, http_code, type }); + throw new RpcError('LIMIT_EXCEEDED', message_with_details); } } @@ -712,6 +700,37 @@ function validate_create_account_params(req) { } } +function validate_and_return_requested_account(params, action, requesting_account) { + const on_itself = !params.username; + let requested_account; + if (on_itself) { + // When accesskeyt API called without specific username, action on the same requesting account. + // So in that case requesting account and requested account is same. + requested_account = requesting_account; + } else { + _check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); + const account_email = get_account_name_from_username(params.username, requesting_account._id.toString()); + _check_if_account_exists(action, account_email); + requested_account = system_store.get_account_by_email(account_email); + _check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account); + _check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account); + } + return requested_account; +} + +function return_list_member(iam_user, iam_path, iam_username) { + return { + user_id: iam_user._id.toString(), + iam_path: iam_path, + username: iam_username, + arn: create_arn_for_user(iam_user.owner._id.toString(), iam_username, iam_path), + // TODO: GAP Need to save created date + create_date: Date.now(), + // TODO: GAP missing password_last_used + password_last_used: Date.now(), // GAP + }; +} + exports.delete_account = delete_account; exports.create_account = create_account; @@ -735,3 +754,6 @@ exports._get_iam_user_policy_index = _get_iam_user_policy_index; exports._check_user_policy_exists = _check_user_policy_exists; exports._check_if_user_does_not_have_resources_before_deletion = _check_if_user_does_not_have_resources_before_deletion; exports._check_total_policy_size = _check_total_policy_size; +exports.validate_and_return_requested_account = validate_and_return_requested_account; +exports.get_iam_username = get_iam_username; +exports.return_list_member = return_list_member;