@@ -241,7 +241,7 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
241241 * permissions
242242 * a. First iteration - map_with_concurrency with concurrency rate of 10 entries at the same time.
243243 * a.1. if entry is dir file/entry is non json - we will return undefined
244- * a.2. if bucket is unaccessible by bucket policy - we will return undefined
244+ * a.2. if bucket is not owned by the account (or their root account for IAM users) - we will return undefined
245245 * a.3. if underlying storage of the bucket is unaccessible by the account's uid/gid - we will return undefined
246246 * a.4. else - return the bucket config info.
247247 * b. Second iteration - filter empty entries - filter will remove undefined values produced by the map_with_concurrency().
@@ -271,9 +271,9 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
271271 if ( err . rpc_code !== 'NO_SUCH_BUCKET' ) throw err ;
272272 }
273273 if ( ! bucket ) return ;
274- const bucket_policy_accessible = await this . has_bucket_action_permission ( bucket , account , 's3:ListBucket' ) ;
275- dbg . log2 ( `list_buckets: bucket_name ${ bucket_name } bucket_policy_accessible` , bucket_policy_accessible ) ;
276- if ( ! bucket_policy_accessible ) return ;
274+
275+ if ( ! await this . has_bucket_ownership_permission ( bucket , account ) ) return ;
276+
277277 const fs_accessible = await this . validate_fs_bucket_access ( bucket , object_sdk ) ;
278278 if ( ! fs_accessible ) return ;
279279 return bucket ;
@@ -921,24 +921,66 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
921921 ///// UTILS /////
922922 /////////////////
923923
924- // TODO: move the following 3 functions - has_bucket_action_permission(), validate_fs_bucket_access(), _has_access_to_nsfs_dir()
924+ // TODO: move the following 5 functions - is_bucket_owner(), is_iam_and_same_root_account_owner(), has_bucket_ownership_permission(), has_bucket_action_permission(), validate_fs_bucket_access(), _has_access_to_nsfs_dir()
925925 // so they can be re-used
926+
927+ /**
928+ * is_bucket_owner checks if the account is the direct owner of the bucket
929+ * @param {Record<string, any> } bucket
930+ * @param {Record<string, any> } account
931+ * @returns {boolean }
932+ */
933+ is_bucket_owner ( bucket , account ) {
934+ if ( ! bucket ?. owner_account ?. id || ! account ?. _id ) return false ;
935+ return bucket . owner_account . id === account . _id ;
936+ }
937+
938+ /**
939+ * is_iam_and_same_root_account_owner checks if the account is an IAM user and the bucket is owned by their root account
940+ * @param {Record<string, any> } account
941+ * @param {Record<string, any> } bucket
942+ * @returns {boolean }
943+ */
944+ is_iam_and_same_root_account_owner ( account , bucket ) {
945+ if ( ! account ?. owner || ! bucket ?. owner_account ?. id ) return false ;
946+ return account . owner === bucket . owner_account . id ;
947+ }
948+
949+ /**
950+ * has_bucket_ownership_permission returns true if the account can list the bucket in ListBuckets operation
951+ *
952+ * aws-compliant behavior:
953+ * - Root accounts can list buckets they own
954+ * - IAM users can list their owner buckets
955+ *
956+ * @param {Record<string, any> } bucket
957+ * @param {Record<string, any> } account
958+ * @returns {Promise<boolean> }
959+ */
960+ async has_bucket_ownership_permission ( bucket , account ) {
961+ // check direct ownership
962+ if ( this . is_bucket_owner ( bucket , account ) ) return true ;
963+
964+ // special case: iam user can list the buckets of their owner
965+ if ( this . is_iam_and_same_root_account_owner ( account , bucket ) ) return true ;
966+
967+ return false ;
968+ }
969+
926970 async has_bucket_action_permission ( bucket , account , action , bucket_path = "" ) {
927971 dbg . log1 ( 'has_bucket_action_permission:' , bucket . name . unwrap ( ) , account . name . unwrap ( ) , account . _id , bucket . bucket_owner . unwrap ( ) ) ;
928972
929- const is_system_owner = Boolean ( bucket . system_owner ) && bucket . system_owner . unwrap ( ) === account . name . unwrap ( ) ;
973+ // system_owner is deprecated since version 5.18, so we don't need to check it
974+
975+ // check ownership: direct owner or IAM user inheritance
976+ const is_owner = this . is_bucket_owner ( bucket , account ) ;
930977
931- // If the system owner account wants to access the bucket, allow it
932- if ( is_system_owner ) return true ;
933- const is_owner = ( bucket . owner_account && bucket . owner_account . id === account . _id ) ;
934978 const bucket_policy = bucket . s3_policy ;
935979
936980 if ( ! bucket_policy ) {
937981 // in case we do not have bucket policy
938982 // we allow IAM account to access a bucket that that is owned by their root account
939- const is_iam_and_same_root_account_owner = account . owner !== undefined &&
940- account . owner === bucket . owner_account . id ;
941- return is_owner || is_iam_and_same_root_account_owner ;
983+ return is_owner || this . is_iam_and_same_root_account_owner ( account , bucket ) ;
942984 }
943985 if ( ! action ) {
944986 throw new Error ( 'has_bucket_action_permission: action is required' ) ;
0 commit comments