@@ -8,9 +8,10 @@ const dbg = require('../util/debug_module')(__filename);
88const P = require ( '../util/promise' ) ;
99const { ConfigFS, CONFIG_TYPES } = require ( './config_fs' ) ;
1010const 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' ) ;
1415const IamError = require ( '../endpoint/iam/iam_errors' ) . IamError ;
1516const cloud_utils = require ( '../util/cloud_utils' ) ;
1617const 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