Skip to content

Commit 45ca4a4

Browse files
Sakshi MunjalSakshi Munjal
authored andcommitted
crud operations + assume role
Signed-off-by: Sakshi Munjal <sakshimunjal@Sakshis-MacBook-Pro.local>
1 parent ae08790 commit 45ca4a4

11 files changed

Lines changed: 756 additions & 36 deletions

File tree

src/cmd/nsfs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ async function main(argv = minimist(process.argv.slice(2))) {
265265
const sts_bucketspace = nsfs_config_root ?
266266
new BucketSpaceFS({ config_root: nsfs_config_root }, endpoint_stats_collector.instance()) :
267267
new BucketSpaceSimpleFS({ fs_root });
268-
req.sts_sdk = new StsSDK(null, null, sts_bucketspace);
268+
req.sts_sdk = new StsSDK(null, null, sts_bucketspace, req.object_sdk.accountspace);
269269
}
270270
});
271271
if (config.ALLOW_HTTP) {

src/endpoint/iam/iam_utils.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ function parse_role_arn(role_arn) {
8484
}
8585

8686
/**
87-
* resolve_iam_role_by_arn resolves IAM role entity from a role ARN
87+
* resolve_iam_role_by_arn resolves IAM role entity from a role ARN (containerized / system_store).
88+
* NC callers should use accountspace.get_role_by_arn via object_sdk / sts_sdk instead.
8889
* @param {string} role_arn
8990
* @returns {Promise<{iam_role?: object, account_id?: string, role_name?: string, error?: string}>}
9091
*/
@@ -1295,14 +1296,17 @@ function _get_assumed_role_session_info(req) {
12951296
* @param {object} account
12961297
* @param {boolean} is_iam_user
12971298
* @param {string} [assumed_role_arn]
1299+
* @param {object} [object_sdk] - NC uses object_sdk.accountspace.get_role_by_arn
12981300
* @returns {Promise<object[]|null>} policies, or null if assumed role could not be resolved
12991301
*/
1300-
async function _get_identity_policies(account, is_iam_user, assumed_role_arn) {
1302+
async function _get_identity_policies(account, is_iam_user, assumed_role_arn, object_sdk) {
13011303
if (is_iam_user) {
13021304
return account.iam_user_policies || [];
13031305
}
1304-
const resolved_role = await resolve_iam_role_by_arn(assumed_role_arn);
1305-
if (!resolved_role.iam_role) return null;
1306+
const resolved_role = object_sdk?.accountspace ?
1307+
await object_sdk.accountspace.get_role_by_arn({ role_arn: assumed_role_arn }) :
1308+
await resolve_iam_role_by_arn(assumed_role_arn);
1309+
if (!resolved_role?.iam_role) return null;
13061310
return resolved_role.iam_role.iam_role_policies || [];
13071311
}
13081312

@@ -1336,13 +1340,20 @@ async function authorize_request_iam_policy_impl(req, method, bucket_name, servi
13361340
principal_arn: assumed_role_arn,
13371341
};
13381342

1339-
const iam_policies = await _get_identity_policies(account, is_iam_user, assumed_role_arn);
1343+
const iam_policies = await _get_identity_policies(
1344+
account,
1345+
is_iam_user,
1346+
assumed_role_arn,
1347+
req.object_sdk
1348+
);
13401349
if (iam_policies === null) {
13411350
dbg.error('authorize_request_iam_policy: failed to resolve IAM role for assumed session token');
13421351
return deny_result;
13431352
}
13441353
if (iam_policies.length === 0) {
1345-
if (is_iam_user && req.object_sdk.nsfs_config_root) return true; // We do not have IAM policies in NC yet
1354+
// TODO: remove NC empty-policy allow when PutRolePolicy (Phase 2) is implemented
1355+
// NC: IAM user / role inline policies are Phase 2; allow until PutRolePolicy exists
1356+
if (req.object_sdk.nsfs_config_root && (is_iam_user || is_assumed_role_session)) return true;
13461357
dbg.error('authorize_request_iam_policy:', iam_identity, 'has no inline policies configured');
13471358
return deny_result;
13481359
}

src/endpoint/sts/sts_rest.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,11 @@ function handle_error(req, res, err) {
238238
* @returns {Promise<Object>} - Assume role policy document
239239
*/
240240
async function get_assume_role_policy(req) {
241+
if (is_nc_environment()) {
242+
// NC: role resolve via injected AccountSpaceFS
243+
return req.sts_sdk.get_assume_role_policy(req.body.role_arn);
244+
}
245+
241246
// TODO: Get the iam_role from cache
242247
const resolved_role = await resolve_iam_role_by_arn(req.body.role_arn);
243248
return resolved_role.iam_role?.assume_role_policy_document;

src/manage_nsfs/nsfs_schema_utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const validate_logging = ajv.compile(log_schema);
4747

4848
/**
4949
* validate_account_schema validates an account object against the NC NSFS account schema
50+
* Same schema used for role
5051
* @param {object} account
5152
*/
5253
function validate_account_schema(account) {

src/sdk/accountspace_fs.js

Lines changed: 211 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ const dbg = require('../util/debug_module')(__filename);
88
const P = require('../util/promise');
99
const { ConfigFS, CONFIG_TYPES } = require('./config_fs');
1010
const native_fs_utils = require('../util/native_fs_utils');
11-
const { create_arn_for_user, create_arn_for_root, get_action_message_title, check_iam_path_was_set } = require('../endpoint/iam/iam_utils');
12-
const { IAM_ACTIONS, MAX_NUMBER_OF_ACCESS_KEYS, IAM_DEFAULT_PATH,
13-
ACCESS_KEY_STATUS_ENUM, IDENTITY_ENUM } = require('../endpoint/iam/iam_constants');
11+
const { create_arn_for_user, create_arn_for_root, create_arn_for_role, get_action_message_title, check_iam_path_was_set,
12+
parse_role_arn } = require('../endpoint/iam/iam_utils');
13+
const { IAM_ACTIONS, MAX_NUMBER_OF_ACCESS_KEYS, MAX_NUMBER_OF_IAM_ROLES, IAM_DEFAULT_PATH,
14+
ACCESS_KEY_STATUS_ENUM, IDENTITY_ENUM, DEFAULT_MAX_SESSION_DURATION_SECS } = require('../endpoint/iam/iam_constants');
1415
const IamError = require('../endpoint/iam/iam_errors').IamError;
1516
const cloud_utils = require('../util/cloud_utils');
1617
const SensitiveString = require('../util/sensitive_string');
@@ -589,40 +590,164 @@ class AccountSpaceFS {
589590
// ROLE //
590591
////////////
591592

593+
// 1 - check requesting account is root
594+
// 2 - verify role_name is unique under the account
595+
// 3 - enforce per-account role limit
596+
// 4 - write role identity.json + symlink
592597
async create_role(params, account_sdk) {
593598
const action = IAM_ACTIONS.CREATE_ROLE;
594599
dbg.log1(`AccountSpaceFS.${action}`, params);
595-
const { code, http_code, type } = IamError.NotImplemented;
596-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
600+
try {
601+
const requesting_account = account_sdk.requesting_account;
602+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
603+
const owner_account_id = requesting_account._id;
604+
const role_exists = await this.config_fs.is_role_exists_by_name(params.role_name, owner_account_id);
605+
if (role_exists) {
606+
const { code, http_code, type } = IamError.EntityAlreadyExists;
607+
throw new IamError({ code, message: `Role with name ${params.role_name} already exists.`, http_code, type });
608+
}
609+
const existing_roles = await this.config_fs.list_roles_under_account(owner_account_id);
610+
if (existing_roles.length >= MAX_NUMBER_OF_IAM_ROLES) {
611+
const { code, http_code, type } = IamError.LimitExceeded;
612+
throw new IamError({ code, message: `Cannot exceed quota for RolesPerAccount: ${MAX_NUMBER_OF_IAM_ROLES}.`, http_code, type });
613+
}
614+
const role_data = this._new_role_defaults(requesting_account, params);
615+
await this.config_fs.create_role_config_file(role_data);
616+
const arn = create_arn_for_role(owner_account_id, role_data.name, role_data.iam_path);
617+
return {
618+
role_name: role_data.name,
619+
role_id: role_data._id,
620+
iam_path: role_data.iam_path || IAM_DEFAULT_PATH,
621+
arn,
622+
create_date: role_data.creation_date,
623+
assume_role_policy_document: role_data.assume_role_policy_document,
624+
description: role_data.description,
625+
max_session_duration: role_data.max_session_duration,
626+
};
627+
} catch (err) {
628+
dbg.error(`AccountSpaceFS.${action} error`, err);
629+
throw err;
630+
}
597631
}
598632

633+
// 1 - check requesting account is root
634+
// 2 - read role from filesystem
599635
async get_role(params, account_sdk) {
600636
const action = IAM_ACTIONS.GET_ROLE;
601637
dbg.log1(`AccountSpaceFS.${action}`, params);
602-
const { code, http_code, type } = IamError.NotImplemented;
603-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
638+
try {
639+
const requesting_account = account_sdk.requesting_account;
640+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
641+
const owner_account_id = requesting_account._id;
642+
const role_data = await this.config_fs.get_role_by_name(params.role_name, owner_account_id);
643+
if (!role_data) {
644+
const { code, http_code, type } = IamError.NoSuchEntity;
645+
throw new IamError({ code, message: `The role with name ${params.role_name} cannot be found.`, http_code, type });
646+
}
647+
const arn = create_arn_for_role(owner_account_id, role_data.name, role_data.iam_path);
648+
return {
649+
role_name: role_data.name,
650+
role_id: role_data._id,
651+
iam_path: role_data.iam_path || IAM_DEFAULT_PATH,
652+
arn,
653+
create_date: role_data.creation_date,
654+
assume_role_policy_document: role_data.assume_role_policy_document,
655+
description: role_data.description,
656+
max_session_duration: role_data.max_session_duration,
657+
};
658+
} catch (err) {
659+
dbg.error(`AccountSpaceFS.${action} error`, err);
660+
throw err;
661+
}
604662
}
605663

664+
// 1 - check requesting account is root
665+
// 2 - read role, apply updates (description / max_session_duration), rewrite
606666
async update_role(params, account_sdk) {
607667
const action = IAM_ACTIONS.UPDATE_ROLE;
608668
dbg.log1(`AccountSpaceFS.${action}`, params);
609-
const { code, http_code, type } = IamError.NotImplemented;
610-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
669+
try {
670+
const requesting_account = account_sdk.requesting_account;
671+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
672+
const owner_account_id = requesting_account._id;
673+
const role_data = await this.config_fs.get_role_by_name(params.role_name, owner_account_id);
674+
if (!role_data) {
675+
const { code, http_code, type } = IamError.NoSuchEntity;
676+
throw new IamError({ code, message: `The role with name ${params.role_name} cannot be found.`, http_code, type });
677+
}
678+
if (params.description !== undefined) role_data.description = params.description;
679+
if (params.max_session_duration !== undefined) role_data.max_session_duration = params.max_session_duration;
680+
await this.config_fs.update_role_config_file(role_data);
681+
return {};
682+
} catch (err) {
683+
dbg.error(`AccountSpaceFS.${action} error`, err);
684+
throw err;
685+
}
611686
}
612687

688+
// 1 - check requesting account is root
689+
// 2 - read role then delete its files and symlink
613690
async delete_role(params, account_sdk) {
614691
const action = IAM_ACTIONS.DELETE_ROLE;
615692
dbg.log1(`AccountSpaceFS.${action}`, params);
616-
const { code, http_code, type } = IamError.NotImplemented;
617-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
693+
try {
694+
const requesting_account = account_sdk.requesting_account;
695+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
696+
const owner_account_id = requesting_account._id;
697+
const role_data = await this.config_fs.get_role_by_name(params.role_name, owner_account_id);
698+
if (!role_data) {
699+
const { code, http_code, type } = IamError.NoSuchEntity;
700+
throw new IamError({ code, message: `The role with name ${params.role_name} cannot be found.`, http_code, type });
701+
}
702+
await this.config_fs.delete_role_config_file(role_data);
703+
} catch (err) {
704+
dbg.error(`AccountSpaceFS.${action} error`, err);
705+
throw err;
706+
}
618707
}
619708

709+
// 1 - check requesting account is root
710+
// 2 - list roles under the account from filesystem
620711
async list_roles(params, account_sdk) {
621712
const action = IAM_ACTIONS.LIST_ROLES;
622-
dbg.log1(`AccountSpaceFS.${action} (returns empty list on every request)`, params);
623-
const is_truncated = false;
624-
const members = [];
625-
return { members, is_truncated };
713+
dbg.log1(`AccountSpaceFS.${action}`, params);
714+
try {
715+
const requesting_account = account_sdk.requesting_account;
716+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
717+
const owner_account_id = requesting_account._id;
718+
const is_truncated = false;
719+
const members = await this._list_config_files_for_roles(owner_account_id, params.iam_path_prefix);
720+
return { members, is_truncated };
721+
} catch (err) {
722+
dbg.error(`AccountSpaceFS.${action} error`, err);
723+
throw err;
724+
}
725+
}
726+
727+
/**
728+
* get_role_by_arn resolves a role (and owner access key) from a role ARN via config_fs.
729+
* NC-only helper used by STS AssumeRole and S3 assumed-role identity policy checks.
730+
* @param {{ role_arn: string }} params
731+
* @returns {Promise<{iam_role?: object, account_id?: string, role_name?: string, access_key?: string, error?: string}>}
732+
*/
733+
async get_role_by_arn(params) {
734+
const parsed = parse_role_arn(params.role_arn);
735+
if (parsed.error) return { error: parsed.error };
736+
const { account_id, role_name } = parsed;
737+
const iam_role = await this.config_fs.get_role_by_name(role_name, account_id, { silent_if_missing: true });
738+
if (!iam_role) return { error: 'NO_SUCH_ROLE', account_id, role_name };
739+
740+
const owner_account = await this.config_fs.get_identity_by_id(account_id, CONFIG_TYPES.ACCOUNT,
741+
{ show_secrets: true, decrypt_secret_key: true });
742+
if (!owner_account) {
743+
return { error: 'NO_SUCH_ACCOUNT', account_id, role_name };
744+
}
745+
if (!owner_account.access_keys?.length) {
746+
return { error: 'ACCESS_DENIED', account_id, role_name };
747+
}
748+
const raw_access_key = owner_account.access_keys[0].access_key;
749+
const access_key = typeof raw_access_key === 'string' ? raw_access_key : raw_access_key.unwrap();
750+
return { iam_role, account_id, role_name, access_key };
626751
}
627752

628753
async put_role_policy(params, account_sdk) {
@@ -654,11 +779,27 @@ class AccountSpaceFS {
654779
return { members, is_truncated };
655780
}
656781

782+
// 1 - check requesting account is root
783+
// 2 - read role, replace assume_role_policy_document, rewrite
657784
async update_assume_role_policy(params, account_sdk) {
658785
const action = IAM_ACTIONS.UPDATE_ASSUME_ROLE_POLICY;
659786
dbg.log1(`AccountSpaceFS.${action}`, params);
660-
const { code, http_code, type } = IamError.NotImplemented;
661-
throw new IamError({ code, message: 'NotImplemented', http_code, type });
787+
try {
788+
const requesting_account = account_sdk.requesting_account;
789+
this._check_if_requesting_account_is_root_account(action, requesting_account, {});
790+
const owner_account_id = requesting_account._id;
791+
const role_data = await this.config_fs.get_role_by_name(params.role_name, owner_account_id);
792+
if (!role_data) {
793+
const { code, http_code, type } = IamError.NoSuchEntity;
794+
throw new IamError({ code, message: `The role with name ${params.role_name} cannot be found.`, http_code, type });
795+
}
796+
role_data.assume_role_policy_document = params.policy_document;
797+
await this.config_fs.update_role_config_file(role_data);
798+
return {};
799+
} catch (err) {
800+
dbg.error(`AccountSpaceFS.${action} error`, err);
801+
throw err;
802+
}
662803
}
663804

664805
////////////////////////
@@ -701,6 +842,59 @@ class AccountSpaceFS {
701842
return user_defaults;
702843
}
703844

845+
/**
846+
* _new_role_defaults builds the initial role data object for a CreateRole call.
847+
* @param {object} requesting_account
848+
* @param {object} params
849+
* @returns {object}
850+
*/
851+
_new_role_defaults(requesting_account, params) {
852+
return {
853+
_id: generate_id(),
854+
name: params.role_name,
855+
owner: requesting_account._id,
856+
iam_path: params.iam_path || IAM_DEFAULT_PATH,
857+
creation_date: new Date().toISOString(),
858+
description: params.description || '',
859+
max_session_duration: params.max_session_duration || DEFAULT_MAX_SESSION_DURATION_SECS,
860+
assume_role_policy_document: params.assume_role_policy_document || {},
861+
nsfs_account_config: requesting_account.nsfs_account_config?.distinguished_name ?
862+
{ distinguished_name: requesting_account.nsfs_account_config.distinguished_name } :
863+
{ uid: requesting_account.nsfs_account_config?.uid, gid: requesting_account.nsfs_account_config?.gid }
864+
};
865+
}
866+
867+
/**
868+
* _list_config_files_for_roles reads every role under an owner account and returns member objects.
869+
* @param {string} owner_account_id
870+
* @param {string} [iam_path_prefix]
871+
* @returns {Promise<object[]>}
872+
*/
873+
async _list_config_files_for_roles(owner_account_id, iam_path_prefix) {
874+
const role_names = await this.config_fs.list_roles_under_account(owner_account_id);
875+
const should_filter_by_prefix = check_iam_path_was_set(iam_path_prefix);
876+
877+
const members = await P.map_with_concurrency(10, role_names, async role_name => {
878+
const role_data = await this.config_fs.get_role_by_name(role_name, owner_account_id);
879+
if (!role_data) return undefined;
880+
if (should_filter_by_prefix) {
881+
if (!role_data.iam_path?.startsWith(iam_path_prefix)) return undefined;
882+
}
883+
const arn = create_arn_for_role(owner_account_id, role_data.name, role_data.iam_path);
884+
return {
885+
role_name: role_data.name,
886+
role_id: role_data._id,
887+
iam_path: role_data.iam_path || IAM_DEFAULT_PATH,
888+
arn,
889+
create_date: role_data.creation_date,
890+
assume_role_policy_document: role_data.assume_role_policy_document,
891+
description: role_data.description,
892+
max_session_duration: role_data.max_session_duration,
893+
};
894+
});
895+
return members.filter(item => item !== undefined);
896+
}
897+
704898
_check_root_account(account) {
705899
return account.owner === undefined;
706900
}

0 commit comments

Comments
 (0)