From d46d9d54c53ea5b85da394ecfc5f73307151b85c Mon Sep 17 00:00:00 2001 From: shirady <57721533+shirady@users.noreply.github.com> Date: Thu, 11 Dec 2025 08:07:11 +0200 Subject: [PATCH] IAM | Add Test Cases on IAM API Signed-off-by: shirady <57721533+shirady@users.noreply.github.com> --- src/server/system_services/account_server.js | 48 +-- .../api/iam/test_iam_basic_integration.js | 403 ++++++++++++++++-- src/util/account_util.js | 73 ++-- 3 files changed, 418 insertions(+), 106 deletions(-) diff --git a/src/server/system_services/account_server.js b/src/server/system_services/account_server.js index 2641b45eea..5c604732fb 100644 --- a/src/server/system_services/account_server.js +++ b/src/server/system_services/account_server.js @@ -50,7 +50,7 @@ async function create_account(req) { 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); + const { id, token, access_keys } = await account_util.create_account(req); iam_arn = iam_arn || iam_utils.create_arn_for_root(id); return { @@ -1142,7 +1142,7 @@ function _list_connection_usage(account, credentials) { entity: pool.name, external_entity: pool.cloud_pool_info.target_bucket })); - const namespace_resource_usage = _.map( + const namespace_resource_usage = _.map( _.filter(system_store.data.namespace_resources, ns => ( ns.connection && ns.connection.endpoint_type === credentials.endpoint_type && @@ -1206,7 +1206,7 @@ async function get_user(req) { const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); const 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); + requested_account.iam_path || IAM_DEFAULT_PATH); const tags = account_util.get_sorted_list_tags_for_user(requested_account.tagging); return { user_id: requested_account._id.toString(), @@ -1221,14 +1221,9 @@ async function get_user(req) { } async function update_user(req) { - const action = IAM_ACTIONS.UPDATE_USER; const requesting_account = req.account; - const old_account_email_wrapped = account_util.get_account_email_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 }); - const requested_account = account_util._check_if_account_exists(action, old_account_email_wrapped, req.rpc_params.username); + const requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); let iam_path = requested_account.iam_path; let user_name = req.rpc_params.username; // Change to complete user name @@ -1240,8 +1235,6 @@ async function update_user(req) { requesting_account._id.toString()); account_util._check_username_already_exists(action, new_account_email_wrapped, 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); @@ -1279,14 +1272,9 @@ async function update_user(req) { } async function delete_user(req) { - const action = IAM_ACTIONS.DELETE_USER; const requesting_account = req.account; - const account_email_wrapped = account_util.get_account_email_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 }); - const requested_account = account_util._check_if_account_exists(action, account_email_wrapped, req.rpc_params.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 requested_account = account_util.validate_and_return_requested_account(req.rpc_params, action, requesting_account); account_util._check_if_user_does_not_have_resources_before_deletion(action, requested_account); return account_util.delete_account(req, requested_account); } @@ -1294,7 +1282,7 @@ async function delete_user(req) { 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, { }); + 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) { const owner_account_id = account_util.get_owner_account_id(account); @@ -1322,7 +1310,8 @@ async function list_users(req) { 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); + const requested_account = account_util.validate_and_return_requested_account_with_option_itself( + req.rpc_params, action, requesting_account); account_util._check_number_of_access_key_array(action, requested_account); const account_req = { rpc_params: { @@ -1353,12 +1342,16 @@ async function create_access_key(req) { 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 requested_account = account_util.validate_and_return_requested_account_with_option_itself( + 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) }; + 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) + }; } @@ -1366,7 +1359,8 @@ 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); + const requested_account = account_util.validate_and_return_requested_account_with_option_itself( + 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, @@ -1422,8 +1416,8 @@ 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); + const requested_account = account_util.validate_and_return_requested_account_with_option_itself( + 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); @@ -1583,7 +1577,7 @@ async function delete_user_policy(req) { 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 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); diff --git a/src/test/integration_tests/api/iam/test_iam_basic_integration.js b/src/test/integration_tests/api/iam/test_iam_basic_integration.js index e17880842f..492bd631e9 100644 --- a/src/test/integration_tests/api/iam/test_iam_basic_integration.js +++ b/src/test/integration_tests/api/iam/test_iam_basic_integration.js @@ -10,22 +10,22 @@ const assert = require('assert'); const SensitiveString = require('../../../../util/sensitive_string'); const fs_utils = require('../../../../util/fs_utils'); const { TMP_PATH, generate_nsfs_account, get_new_buckets_path_by_test_env, generate_iam_client, - require_coretest, is_nc_coretest } = require('../../../system_tests/test_utils'); + require_coretest, is_nc_coretest } = require('../../../system_tests/test_utils'); const { ListUsersCommand, CreateUserCommand, GetUserCommand, UpdateUserCommand, DeleteUserCommand, - ListAccessKeysCommand, CreateAccessKeyCommand, GetAccessKeyLastUsedCommand, - UpdateAccessKeyCommand, DeleteAccessKeyCommand, - ListUserPoliciesCommand, PutUserPolicyCommand, DeleteUserPolicyCommand, GetUserPolicyCommand, - ListUserTagsCommand, TagUserCommand, UntagUserCommand, - ListGroupsForUserCommand, ListAccountAliasesCommand, ListAttachedGroupPoliciesCommand, - ListAttachedRolePoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, - ListGroupPoliciesCommand, ListGroupsCommand, ListInstanceProfilesCommand, - ListInstanceProfilesForRoleCommand, ListInstanceProfileTagsCommand, ListMFADevicesCommand, - ListMFADeviceTagsCommand, ListOpenIDConnectProvidersCommand, ListOpenIDConnectProviderTagsCommand, - ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListRolesCommand, - ListRoleTagsCommand, ListSAMLProvidersCommand, ListServerCertificatesCommand, - ListServerCertificateTagsCommand, ListServiceSpecificCredentialsCommand, - ListSigningCertificatesCommand, ListSSHPublicKeysCommand, - ListVirtualMFADevicesCommand } = require('@aws-sdk/client-iam'); + ListAccessKeysCommand, CreateAccessKeyCommand, GetAccessKeyLastUsedCommand, + UpdateAccessKeyCommand, DeleteAccessKeyCommand, + ListUserPoliciesCommand, PutUserPolicyCommand, DeleteUserPolicyCommand, GetUserPolicyCommand, + ListUserTagsCommand, TagUserCommand, UntagUserCommand, + ListGroupsForUserCommand, ListAccountAliasesCommand, ListAttachedGroupPoliciesCommand, + ListAttachedRolePoliciesCommand, ListAttachedUserPoliciesCommand, ListEntitiesForPolicyCommand, + ListGroupPoliciesCommand, ListGroupsCommand, ListInstanceProfilesCommand, + ListInstanceProfilesForRoleCommand, ListInstanceProfileTagsCommand, ListMFADevicesCommand, + ListMFADeviceTagsCommand, ListOpenIDConnectProvidersCommand, ListOpenIDConnectProviderTagsCommand, + ListPoliciesCommand, ListPolicyTagsCommand, ListPolicyVersionsCommand, ListRolesCommand, + ListRoleTagsCommand, ListSAMLProvidersCommand, ListServerCertificatesCommand, + ListServerCertificateTagsCommand, ListServiceSpecificCredentialsCommand, + ListSigningCertificatesCommand, ListSSHPublicKeysCommand, + ListVirtualMFADevicesCommand } = require('@aws-sdk/client-iam'); const { ACCESS_KEY_STATUS_ENUM } = require('../../../../endpoint/iam/iam_constants'); const IamError = require('../../../../endpoint/iam/iam_errors').IamError; @@ -41,6 +41,7 @@ const { rpc_client, EMAIL, get_current_setup_options, stop_nsfs_process, start_n let iam_account; let account_res; let config_root; +let coretest_endpoint_iam; mocha.describe('IAM integration tests', async function() { this.timeout(50000); // eslint-disable-line no-invalid-this @@ -68,7 +69,7 @@ mocha.describe('IAM integration tests', async function() { } // needed details for creating the account (and then the client) - const coretest_endpoint_iam = coretest.get_https_address_iam(); + coretest_endpoint_iam = coretest.get_https_address_iam(); const access_key = account_res.access_key instanceof SensitiveString ? account_res.access_key.unwrap() : account_res.access_key; @@ -428,15 +429,15 @@ mocha.describe('IAM integration tests', async function() { const sorted = arr => _.sortBy(arr, 'Key'); assert.deepEqual(sorted(response2.Tags), sorted(user_tags)); - // verify it with get user (Tags are included in the User object) - const input3 = { - UserName: username4 - }; - const command3 = new GetUserCommand(input3); - const response3 = await iam_account.send(command3); - _check_status_code_ok(response3); - assert.equal(response3.User.Tags.length, 2); - assert.deepEqual(sorted(response3.User.Tags), sorted(user_tags)); + // verify it with get user (Tags are included in the User object) + const input3 = { + UserName: username4 + }; + const command3 = new GetUserCommand(input3); + const response3 = await iam_account.send(command3); + _check_status_code_ok(response3); + assert.equal(response3.User.Tags.length, 2); + assert.deepEqual(sorted(response3.User.Tags), sorted(user_tags)); }); mocha.it('untag user', async function() { @@ -948,13 +949,21 @@ mocha.describe('IAM integration tests', async function() { const username_uppercase = username.toUpperCase(); const username2 = 'Leonardo'; + let access_key_id; + let iam_user_client; + mocha.describe('IAM CreateUser API', async function() { mocha.before(async () => { - await create_iam_user(username); + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); }); mocha.after(async () => { - await delete_iam_user(username); + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); }); mocha.it('create a user with username that already exists should fail', async function() { @@ -998,19 +1007,83 @@ mocha.describe('IAM integration tests', async function() { assert.equal(err_code, IamError.EntityAlreadyExists.code); } }); + + mocha.it('create a user - requester is IAM user - should fail', async function() { + const username_by_iam_user = 'username-test-by-iam-user'; + try { + const input = { + UserName: username_by_iam_user + }; + const command = new CreateUserCommand(input); + await iam_user_client.send(command); + assert.fail('create user - requester is IAM user - should throw an error'); + } catch (err) { + const err_code = err.Error?.Code || err.Code; + assert.equal(err_code, IamError.AccessDeniedException.code); + } + }); + }); + + mocha.describe('IAM GetUser API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('get a user - non-existing user - should fail', async function() { + try { + const input = { + UserName: 'non-existing-user' + }; + const command = new GetUserCommand(input); + await iam_account.send(command); + assert.fail('get user - non-existing user - should throw an error'); + } catch (err) { + const err_code = err.Error.Code; + assert.equal(err_code, IamError.NoSuchEntity.code); + } + }); + + mocha.it('get a user - requester is IAM user - should fail', async function() { + try { + const input = { + UserName: username + }; + const command = new GetUserCommand(input); + await iam_user_client.send(command); + assert.fail('get user - requester is IAM user - should throw an error'); + } catch (err) { + const err_code = err.Error?.Code || err.Code; + assert.equal(err_code, IamError.AccessDeniedException.code); + } + }); }); mocha.describe('IAM UpdateUser API', async function() { mocha.before(async () => { // create 2 users - await create_iam_user(username); - await create_iam_user(username2); + await create_iam_user(iam_account, username); + await create_iam_user(iam_account, username2); + + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); }); mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); // delete 2 users - await delete_iam_user(username); - await delete_iam_user(username2); + await delete_iam_user(iam_account, username); + await delete_iam_user(iam_account, username2); }); mocha.it('update a user with same username', async function() { @@ -1052,31 +1125,138 @@ mocha.describe('IAM integration tests', async function() { assert.equal(err_code, IamError.EntityAlreadyExists.code); } }); + + mocha.it('update a user - non-existing user - should fail', async function() { + try { + const input = { + UserName: 'non-existing-user', + NewUserName: 'new-non-existing-user' + }; + const command = new UpdateUserCommand(input); + await iam_account.send(command); + assert.fail('update user - non-existing user - should throw an error'); + } catch (err) { + const err_code = err.Error.Code; + assert.equal(err_code, IamError.NoSuchEntity.code); + } + }); + + mocha.it('update a user - requester is IAM user - should fail', async function() { + try { + const input = { + UserName: username, + NewUserName: username2, + }; + const command = new UpdateUserCommand(input); + await iam_user_client.send(command); + assert.fail('update user - requester is IAM user - should throw an error'); + } catch (err) { + const err_code = err.Error?.Code || err.Code; + assert.equal(err_code, IamError.AccessDeniedException.code); + } + }); + }); + + mocha.describe('IAM DeleteUser API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('delete a user - non-existing user - should fail', async function() { + try { + const input = { + UserName: 'non-existing-user' + }; + const command = new DeleteUserCommand(input); + await iam_account.send(command); + assert.fail('delete user - non-existing user - should throw an error'); + } catch (err) { + const err_code = err.Error.Code; + assert.equal(err_code, IamError.NoSuchEntity.code); + } + }); + + mocha.it('delete a user - requester is IAM user - should fail', async function() { + try { + const input = { + UserName: username + }; + const command = new DeleteUserCommand(input); + await iam_user_client.send(command); + assert.fail('delete user - requester is IAM user - should throw an error'); + } catch (err) { + const err_code = err.Error?.Code || err.Code; + assert.equal(err_code, IamError.AccessDeniedException.code); + } + }); }); }); mocha.describe('IAM Access Keys API', async function() { - const username2 = 'Alejandro'; + const username = 'Alejandro'; let access_key_id; + let access_key_id_2; + let iam_user_client; + + mocha.describe('IAM CreateAccessKey API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_access_key_iam_user(iam_account, access_key_id_2, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('create second access key - requester is IAM user', async function() { + const input = { + UserName: username + }; + const command = new CreateAccessKeyCommand(input); + const response = await iam_user_client.send(command); + access_key_id_2 = response.AccessKey.AccessKeyId; + _check_status_code_ok(response); + assert.equal(response.AccessKey.UserName, username); + assert(response.AccessKey.AccessKeyId !== undefined); + assert(response.AccessKey.SecretAccessKey !== undefined); + assert.equal(response.AccessKey.Status, ACCESS_KEY_STATUS_ENUM.ACTIVE); + }); + }); mocha.describe('IAM GetAccessKeyLastUsed API', async function() { mocha.before(async () => { - await create_iam_user(username2); - const res = await create_access_key_iam_user(username2); + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); }); mocha.after(async () => { - await delete_access_key_iam_user(access_key_id, username2); - await delete_iam_user(username2); + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); }); - mocha.it('get access key last used with invalid access key ID should fail', async function() { - const access_key_id_invalid = access_key_id + '0'; + mocha.it('get access key last used with non-existing access key ID should fail', async function() { + const access_key_id_non_existing = access_key_id + '0'; try { const input = { - AccessKeyId: access_key_id_invalid + AccessKeyId: access_key_id_non_existing }; const command = new GetAccessKeyLastUsedCommand(input); await iam_account.send(command); @@ -1086,6 +1266,131 @@ mocha.describe('IAM integration tests', async function() { assert.equal(err_code, IamError.NoSuchEntity.code); } }); + + mocha.it('get access key (last used) - requester is IAM user', async function() { + const input = { + AccessKeyId: access_key_id + }; + const command = new GetAccessKeyLastUsedCommand(input); + const response = await iam_user_client.send(command); + _check_status_code_ok(response); + assert.equal(response.UserName, username); + assert(response.AccessKeyLastUsed.LastUsedDate !== undefined); + assert(response.AccessKeyLastUsed.ServiceName !== undefined); + assert(response.AccessKeyLastUsed.Region !== undefined); + }); + }); + + mocha.describe('IAM UpdateAccessKey API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + const res2 = await create_access_key_iam_user(iam_account, username); + access_key_id_2 = res2.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_access_key_iam_user(iam_account, access_key_id_2, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('update access key - requester is IAM user', async function() { + const input = { + UserName: username, + AccessKeyId: access_key_id_2, + Status: ACCESS_KEY_STATUS_ENUM.INACTIVE + }; + const command = new UpdateAccessKeyCommand(input); + const response = await iam_user_client.send(command); + _check_status_code_ok(response); + + // verify it using list access keys (from the account) + const input2 = { + UserName: username + }; + const command2 = new ListAccessKeysCommand(input2); + const response2 = await iam_account.send(command2); + _check_status_code_ok(response2); + assert.equal(response2.AccessKeyMetadata.length, 2); + for (const access_key of response2.AccessKeyMetadata) { + if (access_key.AccessKeyId === access_key_id_2) { + assert.equal(access_key.Status, ACCESS_KEY_STATUS_ENUM.INACTIVE); + } else if (access_key.AccessKeyId === access_key_id) { + assert.equal(access_key.Status, ACCESS_KEY_STATUS_ENUM.ACTIVE); + } + } + }); + }); + + mocha.describe('IAM DeleteAccessKey API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + const res2 = await create_access_key_iam_user(iam_account, username); + access_key_id_2 = res2.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('delete access key - requester is IAM user', async function() { + const input = { + UserName: username, + AccessKeyId: access_key_id_2 + }; + const command = new DeleteAccessKeyCommand(input); + const response = await iam_user_client.send(command); + _check_status_code_ok(response); + + // verify it using list access keys (from the account) + const input2 = { + UserName: username + }; + const command2 = new ListAccessKeysCommand(input2); + const response2 = await iam_account.send(command2); + _check_status_code_ok(response2); + assert.equal(response2.AccessKeyMetadata.length, 1); + assert.equal(response2.AccessKeyMetadata[0].UserName, username); + assert.equal(response2.AccessKeyMetadata[0].AccessKeyId, access_key_id); + assert.equal(response2.AccessKeyMetadata[0].Status, ACCESS_KEY_STATUS_ENUM.ACTIVE); + }); + }); + + mocha.describe('IAM ListAccessKeys API', async function() { + mocha.before(async () => { + await create_iam_user(iam_account, username); + const res = await create_access_key_iam_user(iam_account, username); + access_key_id = res.access_key_id; + // create IAM client for the IAM user + iam_user_client = generate_iam_client(res.access_key_id, res.secret_access_key, coretest_endpoint_iam); + }); + + mocha.after(async () => { + await delete_access_key_iam_user(iam_account, access_key_id, username); + await delete_iam_user(iam_account, username); + }); + + mocha.it('list access keys - requester is IAM user', async function() { + const input = { + UserName: username + }; + const command = new ListAccessKeysCommand(input); + const response = await iam_user_client.send(command); + _check_status_code_ok(response); + assert.equal(response.AccessKeyMetadata.length, 1); + assert.equal(response.AccessKeyMetadata[0].UserName, username); + assert.equal(response.AccessKeyMetadata[0].AccessKeyId, access_key_id); + assert.equal(response.AccessKeyMetadata[0].Status, ACCESS_KEY_STATUS_ENUM.ACTIVE); + }); }); }); }); @@ -1102,28 +1407,30 @@ function _check_status_code_ok(response) { /** * Create an IAM user with the given username. * use this function for before/after hooks to avoid code duplication + * @param {object} iam_client * @param {string} username_to_create */ -async function create_iam_user(username_to_create) { +async function create_iam_user(iam_client, username_to_create) { const input = { UserName: username_to_create }; const command = new CreateUserCommand(input); - const response = await iam_account.send(command); + const response = await iam_client.send(command); _check_status_code_ok(response); } /** * Delete an IAM user with the given username. * use this function for before/after hooks to avoid code duplication + * @param {object} iam_client * @param {string} username_to_delete */ -async function delete_iam_user(username_to_delete) { +async function delete_iam_user(iam_client, username_to_delete) { const input = { UserName: username_to_delete }; const command = new DeleteUserCommand(input); - const response = await iam_account.send(command); + const response = await iam_client.send(command); _check_status_code_ok(response); } @@ -1131,14 +1438,15 @@ async function delete_iam_user(username_to_delete) { /** * Create an IAM user's access key with the given username. * use this function for before/after hooks to avoid code duplication + * @param {object} iam_client * @param {string} username */ -async function create_access_key_iam_user(username) { +async function create_access_key_iam_user(iam_client, username) { const input = { UserName: username }; const command = new CreateAccessKeyCommand(input); - const response = await iam_account.send(command); + const response = await iam_client.send(command); _check_status_code_ok(response); return { access_key_id: response.AccessKey.AccessKeyId, secret_access_key: response.AccessKey.SecretAccessKey }; } @@ -1147,15 +1455,16 @@ async function create_access_key_iam_user(username) { /** * Delete an IAM user's access key with the given access key ID and username. * use this function for before/after hooks to avoid code duplication + * @param {object} iam_client * @param {string} access_key_to_delete * @param {string} username */ -async function delete_access_key_iam_user(access_key_to_delete, username) { +async function delete_access_key_iam_user(iam_client, access_key_to_delete, username) { const input = { UserName: username, AccessKeyId: access_key_to_delete }; const command = new DeleteAccessKeyCommand(input); - const response = await iam_account.send(command); + const response = await iam_client.send(command); _check_status_code_ok(response); } diff --git a/src/util/account_util.js b/src/util/account_util.js index 93b0bdf6b0..af7e26dd1c 100644 --- a/src/util/account_util.js +++ b/src/util/account_util.js @@ -385,7 +385,7 @@ function _check_if_requested_is_owned_by_root_account(action, requesting_account const is_user_account_to_get_owned_by_root_user = _check_root_account_owns_user(requesting_account, requested_account); if (!is_user_account_to_get_owned_by_root_user) { const username = requested_account.name instanceof SensitiveString ? - requested_account.name.unwrap() : requested_account.name; + 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 ${username} cannot be found.`; throw new RpcError('NO_SUCH_ENTITY', message_with_details); @@ -393,18 +393,18 @@ function _check_if_requested_is_owned_by_root_account(action, requesting_account } /** -* _returned_username would return the username of IAM Access key API: - * 1. undefined - for root accounts manager on root account (no username, only account name) - * for root account on itself - * 2. username - for IAM user - * @param {object} requesting_account - * @param {Object} username - * @param {boolean} on_itself - */ + * _returned_username would return the username of IAM Access key API: + * 1. undefined - for root accounts manager on root account (no username, only account name) + * for root account on itself + * 2. username - for IAM user + * @param {object} requesting_account + * @param {Object} username + * @param {boolean} on_itself + */ function _returned_username(requesting_account, username, on_itself) { if ((requesting_account.iam_operate_on_root_account) || (_check_root_account(requesting_account) && on_itself)) { - return undefined; + return undefined; } return username instanceof SensitiveString ? username.unwrap() : username; } @@ -441,11 +441,12 @@ function _throw_error_no_such_entity_policy(action, policy_name) { function _throw_access_denied_error(action, requesting_account, details, entity) { const full_action_name = get_action_message_title(action); - const account_id_for_arn = _get_account_owner_id_for_arn(requesting_account).toString(); - const arn_for_requesting_account = create_arn_for_user(account_id_for_arn, requesting_account.name.unwrap(), - requesting_account.iam_path || IAM_DEFAULT_PATH); + const account_id = _get_account_owner_id_for_arn(requesting_account); + const account_id_for_arn = String(account_id); + const arn_for_requesting_account = account_id ? create_arn_for_user(account_id_for_arn, requesting_account.name.unwrap(), + requesting_account.iam_path || IAM_DEFAULT_PATH) : ''; const basic_message = `User: ${arn_for_requesting_account} is not authorized to perform:` + - `${full_action_name} on resource: `; + `${full_action_name} on resource: `; let message_with_details; if (entity === 'USER') { let user_message; @@ -456,7 +457,7 @@ function _throw_access_denied_error(action, requesting_account, details, entity) user_message = create_arn_for_user(account_id_for_arn, details.username, details.path); } message_with_details = basic_message + - `${user_message} because no identity-based policy allows the ${full_action_name} action`; + `${user_message} because no identity-based policy allows the ${full_action_name} action`; } else { message_with_details = basic_message + `access key ${details.access_key}`; } @@ -464,18 +465,18 @@ function _throw_access_denied_error(action, requesting_account, details, entity) } 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.`; - throw new RpcError('DELETE_CONFLICT', message_with_details); - } + 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.`; + throw new RpcError('DELETE_CONFLICT', message_with_details); +} // ACCESS KEY VALIDATIONS function _check_number_of_access_key_array(action, requested_account) { if (requested_account.access_keys && requested_account.access_keys.length >= MAX_NUMBER_OF_ACCESS_KEYS) { dbg.error(`AccountSpaceNB.${action} cannot exceed quota for AccessKeysPerUser `, - requested_account.name); + requested_account.name); const message_with_details = `Cannot exceed quota for AccessKeysPerUser: ${MAX_NUMBER_OF_ACCESS_KEYS}.`; throw new RpcError('LIMIT_EXCEEDED', message_with_details); } @@ -514,7 +515,7 @@ function _check_access_key_belongs_to_account(action, requested_account, access_ function _check_specific_access_key_exists(access_keys, access_key_to_find) { for (const access_key_obj of access_keys) { const access_key = access_key_obj.access_key instanceof SensitiveString ? - access_key_obj.access_key.unwrap() : access_key_obj.access_key; + access_key_obj.access_key.unwrap() : access_key_obj.access_key; if (access_key_to_find === access_key) { return true; } @@ -535,7 +536,7 @@ function _check_specific_access_key_exists(access_keys, access_key_to_find) { function _get_account_owner_id_for_arn(requesting_account, requested_account) { if (!requesting_account.iam_operate_on_root_account) { if (requesting_account.owner !== undefined) { - return requesting_account.owner._id; + return get_owner_account_id(requesting_account); } return requesting_account._id; } @@ -598,7 +599,7 @@ function _check_user_policy_exists(action, iam_user_policies, policy_name) { function _get_iam_user_policy_index(iam_user_policies, policy_name) { const iam_user_policy_index = iam_user_policies.findIndex(current_iam_user_policy => - current_iam_user_policy.policy_name === policy_name); + current_iam_user_policy.policy_name === policy_name); return iam_user_policy_index; } @@ -720,7 +721,9 @@ function validate_create_account_params(req) { } } -function validate_and_return_requested_account(params, action, requesting_account) { +// implicit policy - we allow a few actions that a IAM user can run on himself +// Currently - all access key actions +function validate_and_return_requested_account_with_option_itself(params, action, requesting_account) { const requester_username = requesting_account.name.unwrap(); // check if root account or IAM user is operating on themselves (with or without --user-name flag) const no_username_or_self_operation = !params.username; // can be root account or IAM user @@ -738,15 +741,20 @@ function validate_and_return_requested_account(params, action, requesting_accoun throw new RpcError('NOT_AUTHORIZED', 'You do not have permission to perform this action.'); } } else { - _check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); - const account_email = get_account_email_from_username(params.username, requesting_account._id.toString()); - requested_account = _check_if_account_exists(action, account_email, params.username); - _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 validate_and_return_requested_account(params, action, requesting_account); } return requested_account; } +function validate_and_return_requested_account(params, action, requesting_account) { + _check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username }); + const account_email = get_account_email_from_username(params.username, requesting_account._id.toString()); + const requested_account = _check_if_account_exists(action, account_email, params.username); + _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) { const owner_account_id = get_owner_account_id(iam_user); return { @@ -774,10 +782,10 @@ function get_sorted_list_tags_for_user(user_tagging) { } function get_system_id_for_events(req) { - const sys_id = req.rpc_params.new_system_parameters ? + const sys_id = req.rpc_params.new_system_parameters ? system_store.parse_system_store_id(req.rpc_params.new_system_parameters.new_system_id) : req.system && req.system._id; - return sys_id; + return sys_id; } exports.delete_account = delete_account; @@ -803,6 +811,7 @@ 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.validate_and_return_requested_account_with_option_itself = validate_and_return_requested_account_with_option_itself; exports.return_list_member = return_list_member; exports.get_owner_account_id = get_owner_account_id; exports.get_sorted_list_tags_for_user = get_sorted_list_tags_for_user;