From 0e5ffbdac587c11c2338b4fcbac7531195ea0f4f Mon Sep 17 00:00:00 2001 From: Aayush Chouhan Date: Fri, 21 Nov 2025 14:54:50 +0530 Subject: [PATCH] Fix (containerized) - list_buckets allowing unauthorized bucket access Signed-off-by: Aayush Chouhan --- src/server/common_services/auth_server.js | 88 +++++++++- src/server/system_services/bucket_server.js | 10 +- .../api/s3/test_s3_list_buckets.js | 48 ++++++ .../integration_tests/api/sts/test_sts.js | 11 +- .../nsfs/test_nsfs_integration.js | 150 ++++-------------- 5 files changed, 180 insertions(+), 127 deletions(-) diff --git a/src/server/common_services/auth_server.js b/src/server/common_services/auth_server.js index 8fe41c0dd6..c672a80ad1 100644 --- a/src/server/common_services/auth_server.js +++ b/src/server/common_services/auth_server.js @@ -496,6 +496,10 @@ function _prepare_auth_request(req) { req.has_bucket_action_permission = async function(bucket, action, bucket_path, req_query) { return has_bucket_action_permission(bucket, req.account, action, req_query, bucket_path); }; + + req.has_bucket_ownership_permission = function(bucket) { + return has_bucket_ownership_permission(bucket, req.account, req.auth && req.auth.role); + }; } function _get_auth_info(account, system, authorized_by, role, extra) { @@ -520,6 +524,73 @@ function _get_auth_info(account, system, authorized_by, role, extra) { return response; } +/** + * is_system_owner checks if the account is the system owner + * @param {Record} bucket + * @param {Record} account + * @returns {boolean} + */ +function is_system_owner(bucket, account) { + if (!bucket?.system?.owner?.email || !account?.email) return false; + return bucket.system.owner.email.unwrap() === account.email.unwrap(); +} + +/** + * is_bucket_owner checks if the account is the direct owner of the bucket + * @param {Record} bucket + * @param {Record} account + * @returns {boolean} + */ +function is_bucket_owner(bucket, account) { + if (!bucket?.owner_account?.email || !account?.email) return false; + return bucket.owner_account.email.unwrap() === account.email.unwrap(); +} + +/** + * is_bucket_claim_owner checks if the account is the OBC (ObjectBucketClaim) owner of the bucket + * @param {Record} bucket + * @param {Record} account + * @returns {boolean} + */ +function is_bucket_claim_owner(bucket, account) { + if (!account?.bucket_claim_owner || !bucket?.name) return false; + return account.bucket_claim_owner.name.unwrap() === bucket.name.unwrap(); +} + +/** + * has_bucket_ownership_permission returns true if the account can list the bucket in ListBuckets operation + * + * aws-compliant behavior: + * - System owner can list all the buckets + * - Operator account (noobaa cli) can list all the buckets + * - Root accounts can list buckets they own + * - OBC owner can list their buckets + * - IAM users can list their owner buckets + * + * @param {Record} bucket + * @param {Record} account + * @param {string} role + * @returns {Promise} + */ +async function has_bucket_ownership_permission(bucket, account, role) { + // system owner can list all the buckets + if (is_system_owner(bucket, account)) return true; + + // operator account (noobaa cli) can list all the buckets + if (role === 'operator') return true; + + // check direct ownership + if (is_bucket_owner(bucket, account)) return true; + + // special case: check bucket claim ownership (OBC) + if (is_bucket_claim_owner(bucket, account)) return true; + + // special case: iam user can list the buckets of their owner + // TODO: handle iam user + + return false; +} + /** * has_bucket_action_permission returns true if the requesting account has permission to perform * the given action on the given bucket. @@ -538,19 +609,21 @@ function _get_auth_info(account, system, authorized_by, role, extra) { */ async function has_bucket_action_permission(bucket, account, action, req_query, bucket_path = "") { dbg.log1('has_bucket_action_permission:', bucket.name, account.email, bucket.owner_account.email); - // If the system owner account wants to access the bucket, allow it - if (bucket.system.owner.email.unwrap() === account.email.unwrap()) return true; - const is_owner = (bucket.owner_account.email.unwrap() === account.email.unwrap()) || - (account.bucket_claim_owner && account.bucket_claim_owner.name.unwrap() === bucket.name.unwrap()); + // system owner can access all buckets + if (is_system_owner(bucket, account)) return true; + + // check ownership: direct owner or OBC + const has_owner_access = is_bucket_owner(bucket, account) || is_bucket_claim_owner(bucket, account); + const bucket_policy = bucket.s3_policy; if (!bucket_policy) { // in case we do not have bucket policy - // we allow IAM account to access a bucket that that is owned by their root account + // we allow IAM account to access a bucket that is owned by their root account const is_iam_and_same_root_account_owner = account.owner !== undefined && account.owner._id.toString() === bucket.owner_account._id.toString(); - return is_owner || is_iam_and_same_root_account_owner; + return has_owner_access || is_iam_and_same_root_account_owner; } if (!action) { throw new Error('has_bucket_action_permission: action is required'); @@ -566,7 +639,8 @@ async function has_bucket_action_permission(bucket, account, action, req_query, ); if (result === 'DENY') return false; - return is_owner || result === 'ALLOW'; + + return has_owner_access || result === 'ALLOW'; } /** diff --git a/src/server/system_services/bucket_server.js b/src/server/system_services/bucket_server.js index e336e4e9d6..d11e779b56 100644 --- a/src/server/system_services/bucket_server.js +++ b/src/server/system_services/bucket_server.js @@ -1096,9 +1096,13 @@ async function list_buckets(req) { let continuation_token = req.rpc_params?.continuation_token; const max_buckets = req.rpc_params?.max_buckets; - const accessible_bucket_list = system_store.data.buckets.filter( - async bucket => await req.has_s3_bucket_permission(bucket, "s3:ListBucket", req) && !bucket.deleting - ); + // filter buckets based on ownership + const bucket_permissions = await P.map(system_store.data.buckets, async bucket => { + if (bucket.deleting) return null; + const has_permission = await req.has_bucket_ownership_permission(bucket); + return has_permission ? bucket : null; + }); + const accessible_bucket_list = bucket_permissions.filter(bucket => bucket !== null); accessible_bucket_list.sort((a, b) => a.name.unwrap().localeCompare(b.name.unwrap())); diff --git a/src/test/integration_tests/api/s3/test_s3_list_buckets.js b/src/test/integration_tests/api/s3/test_s3_list_buckets.js index 04b62fa793..3b7b5ab85a 100644 --- a/src/test/integration_tests/api/s3/test_s3_list_buckets.js +++ b/src/test/integration_tests/api/s3/test_s3_list_buckets.js @@ -127,5 +127,53 @@ mocha.describe('s3_ops', function() { }); }); + mocha.describe('list_buckets permissions', function() { + this.timeout(60000); + let s3_account_a; + let s3_account_b; + + async function create_account_and_client(name) { + const account = await rpc_client.account.create_account({ + name, email: name, has_login: false, s3_access: true, + default_resource: coretest.POOL_LIST[0].name + }); + return new S3Client({ + ...s3_client_params, + credentials: { + accessKeyId: account.access_keys[0].access_key.unwrap(), + secretAccessKey: account.access_keys[0].secret_key.unwrap(), + } + }); + } + + mocha.before(async function() { + s3_account_a = await create_account_and_client('account-a'); + s3_account_b = await create_account_and_client('account-b'); + await s3_account_a.send(new CreateBucketCommand({ Bucket: 'bucket-a' })); + await s3_account_b.send(new CreateBucketCommand({ Bucket: 'bucket-b' })); + await s3.send(new CreateBucketCommand({ Bucket: 'admin-buck' })); + }); + + mocha.after(async function() { + await s3_account_a.send(new DeleteBucketCommand({ Bucket: 'bucket-a' })); + await s3_account_b.send(new DeleteBucketCommand({ Bucket: 'bucket-b' })); + await s3.send(new DeleteBucketCommand({ Bucket: 'admin-buck' })); + await rpc_client.account.delete_account({ email: 'account-a' }); + await rpc_client.account.delete_account({ email: 'account-b' }); + }); + + mocha.it('accounts should list only owned buckets', async function() { + const buckets_a = (await s3_account_a.send(new ListBucketsCommand())).Buckets.map(b => b.Name); + const buckets_b = (await s3_account_b.send(new ListBucketsCommand())).Buckets.map(b => b.Name); + assert.deepStrictEqual(buckets_a, ['bucket-a']); + assert.deepStrictEqual(buckets_b, ['bucket-b']); + }); + + mocha.it('admin should lists all the buckets', async function() { + const buckets = (await s3.send(new ListBucketsCommand())).Buckets.map(b => b.Name); + assert(buckets.includes('bucket-a') && buckets.includes('bucket-b') && buckets.includes('admin-buck')); + }); + }); + }); diff --git a/src/test/integration_tests/api/sts/test_sts.js b/src/test/integration_tests/api/sts/test_sts.js index f98bd3a627..ea94ed65a6 100644 --- a/src/test/integration_tests/api/sts/test_sts.js +++ b/src/test/integration_tests/api/sts/test_sts.js @@ -457,6 +457,7 @@ function verify_session_token(session_token, access_key, secret_key, assumed_rol mocha.describe('Session token tests', function() { const { rpc_client } = coretest; const alice2 = 'alice2'; + const alice2_buck = 'alice2-test-bucket'; const bob2 = 'bob2'; const charlie2 = 'charlie2'; const accounts = [{ email: alice2 }, { email: bob2 }, { email: charlie2 }]; @@ -466,6 +467,8 @@ mocha.describe('Session token tests', function() { mocha.after(async function() { const self = this; // eslint-disable-line no-invalid-this self.timeout(60000); + + await accounts[0].s3.deleteBucket({ Bucket: alice2_buck }).promise(); for (const account of accounts) { await rpc_client.account.delete_account({ email: account.email }); } @@ -542,6 +545,10 @@ mocha.describe('Session token tests', function() { name: 'first.bucket', policy: s3accesspolicy, }); + + // create a bucket owned by alice2 for ListBuckets to work + // Note: bucket policy is not related to ListBuckets operation + await accounts[0].s3.createBucket({ Bucket: alice2_buck }).promise(); }); mocha.it('user b assume role of user a - default expiry - list s3 - should be allowed', async function() { @@ -564,7 +571,7 @@ mocha.describe('Session token tests', function() { }); const buckets1 = await temp_s3_with_session_token.listBuckets().promise(); - assert.ok(buckets1.Buckets.length > 0); + assert.ok(buckets1.Buckets[0].Name === alice2_buck); }); mocha.it('user b assume role of user a - valid expiry via durationSeconds - list s3 - should be allowed', async function() { @@ -589,7 +596,7 @@ mocha.describe('Session token tests', function() { }); const buckets1 = await temp_s3_with_session_token.listBuckets().promise(); - assert.ok(buckets1.Buckets.length > 0); + assert.ok(buckets1.Buckets[0].Name === alice2_buck); }); mocha.it('user b assume role of user a - invalid expiry via durationSeconds - should be rejected', async function() { diff --git a/src/test/integration_tests/nsfs/test_nsfs_integration.js b/src/test/integration_tests/nsfs/test_nsfs_integration.js index e8c866efce..0552475dae 100644 --- a/src/test/integration_tests/nsfs/test_nsfs_integration.js +++ b/src/test/integration_tests/nsfs/test_nsfs_integration.js @@ -249,14 +249,12 @@ mocha.describe('bucket operations - namespace_fs', function() { s3_wrong_uid = new S3(s3_creds); }); mocha.it('list buckets with wrong uid, gid', async function() { - // Give s3_wrong_uid access to the required buckets - const s3_policy = generate_s3_policy('*', first_bucket, ['s3:*']); - await rpc_client.bucket.put_bucket_policy({ name: first_bucket, policy: s3_policy.policy }); - + await s3_wrong_uid.createBucket({ Bucket: 'bucket-wrong-uid' }); const res = await s3_wrong_uid.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([first_bucket], [bucket_name], res.Buckets); + const list_ok = bucket_in_list(['bucket-wrong-uid'], [], res.Buckets); assert.ok(list_ok); + await s3_wrong_uid.deleteBucket({ Bucket: 'bucket-wrong-uid' }); }); mocha.it('update account', async function() { this.timeout(600000); // eslint-disable-line no-invalid-this @@ -390,6 +388,17 @@ mocha.describe('bucket operations - namespace_fs', function() { }); mocha.it('get bucket acl - rpc bucket', async function() { + // Grant s3_correct_uid_default_nsr permission to get bucket ACL on first_bucket + let account_principal; + if (is_nc_coretest) { + account_principal = 'account_s3_correct_uid'; + } else { + const account_info = await rpc_client.account.read_account({ email: 'account_s3_correct_uid@noobaa.com' }); + account_principal = iam_utils.create_arn_for_root(account_info._id.toString()); + } + const generated = generate_s3_policy(account_principal, first_bucket, ['s3:GetBucketAcl']); + await rpc_client.bucket.put_bucket_policy({ name: first_bucket, policy: generated.policy }); + const res = await s3_correct_uid_default_nsr.getBucketAcl({ Bucket: first_bucket }); const bucket_info = await rpc_client.bucket.read_bucket({ name: first_bucket }); assert.equal(res.Owner.DisplayName, bucket_info.owner_account.email); @@ -514,13 +523,12 @@ mocha.describe('bucket operations - namespace_fs', function() { } }); mocha.it('list buckets with uid, gid', async function() { - // Give s3_correct_uid access to the required buckets - const generated = generate_s3_policy('*', bucket_name, ['s3:*']); - await rpc_client.bucket.put_bucket_policy({ name: generated.params.bucket, policy: generated.policy }); + await s3_correct_uid.createBucket({ Bucket: 'bucket-correct-uid' }); const res = await s3_correct_uid.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name], [], res.Buckets); + const list_ok = bucket_in_list(['bucket-correct-uid'], [], res.Buckets); assert.ok(list_ok); + await s3_correct_uid.deleteBucket({ Bucket: 'bucket-correct-uid' }); }); mocha.it('list buckets with dn', async function() { // Give s3_correct_uid access to the required buckets @@ -1059,11 +1067,10 @@ mocha.describe('list objects - namespace_fs', async function() { await fs_utils.folder_delete(tmp_fs_root_ls1); }); - mocha.it('list buckets - uid & gid mismatch - should fail', async function() { + mocha.it('list buckets - uid & gid mismatch - should list nothing initially', async function() { const { s3_client } = accounts.account1; const res = await s3_client.listBuckets({}); - assert.equal(res.Buckets.length, 1); - assert.equal(res.Buckets[0].Name, first_bucket); + assert.equal(res.Buckets.length, 0); }); mocha.it('put object 1 account_with_access - uid & gid mismatch - should fail', async function() { @@ -1104,18 +1111,17 @@ mocha.describe('list objects - namespace_fs', async function() { } }); - mocha.it('list buckets - uid & gid mismatch - account1', async function() { + mocha.it('list buckets - uid & gid mismatch - account1 lists owned bucket', async function() { const { s3_client } = accounts.account1; const res = await s3_client.listBuckets({}); - assert.equal(res.Buckets.length, 2); - assert.equal(res.Buckets.filter(bucket => bucket.Name === s3_b_name || bucket.Name === first_bucket).length, 2); + assert.equal(res.Buckets.length, 1); + assert.equal(res.Buckets[0].Name, s3_b_name); }); - mocha.it('list buckets - uid & gid mismatch - account2', async function() { + mocha.it('list buckets - uid & gid mismatch - account2 lists nothing', async function() { const { s3_client } = accounts.account2; const res = await s3_client.listBuckets({}); - assert.equal(res.Buckets.length, 1); - assert.equal(res.Buckets[0].Name, first_bucket); + assert.equal(res.Buckets.length, 0); }); mocha.it('create s3 bucket by root', async function() { @@ -1125,10 +1131,11 @@ mocha.describe('list objects - namespace_fs', async function() { await rpc_client.bucket.put_bucket_policy({ name: s3_root_b_name, policy: s3_policy.policy }); }); - mocha.it('list buckets - uid & gid match - account with permission', async function() { + mocha.it('list buckets - uid & gid match - account lists owned bucket', async function() { const { s3_client } = accounts.account4; const res = await s3_client.listBuckets({}); - assert.equal(res.Buckets.length, 4); + assert.ok(res.Buckets.length >= 1); + assert.ok(res.Buckets.some(b => b.Name === s3_root_b_name)); }); mocha.it('change mode of /bucket123/ to 0o777: ', async function() { @@ -1281,7 +1288,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put bucket allowed non nsfs buckets - default pool', async function() { const s3_account = accounts.account1; await s3_account.createBucket({ Bucket: regular_bucket_name[0] }); - // Give account access to the required buckets await Promise.all( [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2, regular_bucket_name[0]] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1294,7 +1300,7 @@ mocha.describe('nsfs account configurations', function() { ); const res = await s3_account.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2, regular_bucket_name[0]], [], res.Buckets); + const list_ok = bucket_in_list([regular_bucket_name[0]], [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); assert.ok(list_ok); }); @@ -1302,7 +1308,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put bucket allowed non nsfs buckets - default nsr - nsfs', async function() { const s3_account = accounts.account3; await s3_account.createBucket({ Bucket: regular_bucket_name[1] }); - // Give account access to the required buckets await Promise.all( [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2, regular_bucket_name[1]] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1315,14 +1320,13 @@ mocha.describe('nsfs account configurations', function() { ); const res = await s3_account.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2, regular_bucket_name[1]], [], res.Buckets); + const list_ok = bucket_in_list([regular_bucket_name[1]], [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); assert.ok(list_ok); }); mocha.it('s3 put bucket not allowed non nsfs buckets - default nsr - nsfs', async function() { const s3_account = accounts.account_nsfs_only3; await s3_account.createBucket({ Bucket: regular_bucket_name[2] }); - // Give account access to the required buckets await Promise.all( [bucket_name1, regular_bucket_name[2]] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1335,7 +1339,7 @@ mocha.describe('nsfs account configurations', function() { ); const res = await s3_account.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name1, regular_bucket_name[2]], [non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); + const list_ok = bucket_in_list([regular_bucket_name[2]], [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); assert.ok(list_ok); }); @@ -1377,7 +1381,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 list buckets allowed non nsfs buckets', async function() { const s3_account = accounts.account1; - // Give account access to the required buckets await Promise.all( [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1390,14 +1393,13 @@ mocha.describe('nsfs account configurations', function() { ); const res = await s3_account.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], [], res.Buckets); + const list_ok = bucket_in_list([regular_bucket_name[0]], [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); assert.ok(list_ok); }); mocha.it('s3 list buckets allowed non nsfs buckets', async function() { const s3_account = accounts.account_nsfs_only1; - // Give account access to the required buckets await Promise.all( [bucket_name1] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1410,7 +1412,7 @@ mocha.describe('nsfs account configurations', function() { ); const res = await s3_account.listBuckets({}); console.log(inspect(res)); - const list_ok = bucket_in_list([bucket_name1], [non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); + const list_ok = bucket_in_list([], [bucket_name1, non_nsfs_bucket1, non_nsfs_bucket2], res.Buckets); assert.ok(list_ok); }); @@ -1461,7 +1463,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put object using nsfs_only=false account - second.bucket(s3 compatible namespace bucket)', async function() { const s3_account = accounts.account1; - // Give account access to the required buckets await Promise.all( [non_nsfs_bucket2] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1481,7 +1482,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put object using nsfs_only=true account - second.bucket(s3 compatible namespace bucket)', async function() { const s3_account = accounts.account_nsfs_only1; - // Give account access to the required buckets await Promise.all( [non_nsfs_bucket2] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1502,7 +1502,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put object allowed non nsfs buckets - nsfs bucket', async function() { const s3_account = accounts.account1; - // Give account access to the required buckets await Promise.all( [bucket_name1] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1522,7 +1521,6 @@ mocha.describe('nsfs account configurations', function() { mocha.it('s3 put object allowed non nsfs buckets - nsfs bucket', async function() { const s3_account = accounts.account_nsfs_only1; - // Give account access to the required buckets await Promise.all( [bucket_name1] .map(bucket => generate_s3_policy('*', bucket, ['s3:*'])) @@ -1665,92 +1663,14 @@ mocha.describe('list buckets - namespace_fs', async function() { await fs_utils.folder_delete(tmp_fs_root_ls); }); - mocha.it('list buckets - each account can list only its own bucket - no bucket policies applied ', async function() { + mocha.it('list buckets - each account can list only its own bucket', async function() { for (const account of Object.values(accounts)) { const { s3_client } = account; const res = await s3_client.listBuckets({}); const buckets = res.Buckets.map(bucket => bucket.Name).sort(); - assert.equal(buckets.length, 2); - assert.deepStrictEqual(buckets, [account.bucket, first_bucket]); - } - }); - - mocha.it('account1 - all accounts are allowed to list bucket1', async function() { - this.timeout(50000); // eslint-disable-line no-invalid-this - // allow all accounts to list bucket1 - const public_bucket = accounts.account1.bucket; - const bucket_policy = generate_s3_policy('*', public_bucket, ['s3:ListBucket']); - await rpc_client.bucket.put_bucket_policy({ - name: public_bucket, - policy: bucket_policy.policy, - }); - // check account2 and account3 can list bucket1 - // account2/account3 should be able to list - - // 1. first.bucket - // 2. buckets owned by the account (account2 can list bucket2 / account3 can list bucket3) - // 3. bucket1 (by the given bucket policy) - // account4 can not list bucket1 because of missing fs access permissions (unmatching uid/gid) - // account4 can list - - // 1. first.bucket - // 2. bucket4 - owned by the account - for (const account_name of ['account2', 'account3', 'account4']) { - const account = accounts[account_name]; - const { s3_client, bucket } = account; - const res = await s3_client.listBuckets({}); - const buckets = res.Buckets.map(bucket_info => bucket_info.Name).sort(); - if (account_name === 'account4') { - assert.equal(buckets.length, 2); - assert.deepStrictEqual(buckets, [bucket, first_bucket]); - } else { - assert.equal(buckets.length, 3); - assert.deepStrictEqual(buckets, [accounts.account1.bucket, bucket, first_bucket]); - } + assert.equal(buckets.length, 1); + assert.deepStrictEqual(buckets, [account.bucket]); } - - // delete bucket policy - await rpc_client.bucket.put_bucket_policy({ - name: public_bucket, - policy: '', - }); - }); - - mocha.it('account2 - set allow only account1 list bucket2, account1/account2 can list bucket2 but account3 cant', async function() { - this.timeout(50000); // eslint-disable-line no-invalid-this - const bucket2 = accounts.account2.bucket; - const account_name = 'account1'; - // on NC the account identifier is account name, and on containerized it's the account's email - // allow bucket2 to be listed by account1 - const account1_principal = is_nc_coretest ? account_name : `${account_name}@noobaa.com`; - const bucket_policy = generate_s3_policy(account1_principal, bucket2, ['s3:ListBucket']); - await rpc_client.bucket.put_bucket_policy({ - name: bucket2, - policy: bucket_policy.policy, - }); - - // account1 can list - - // 1. bucket1 - // 2. bucket2 (due to the policy) - // 3. first.bucket - let s3_client = accounts.account1.s3_client; - let res = await s3_client.listBuckets({}); - let buckets = res.Buckets.map(bucket => bucket.Name).sort(); - assert.equal(buckets.length, 3); - assert.deepStrictEqual(buckets, [accounts.account1.bucket, accounts.account2.bucket, first_bucket]); - - // account3 can list - - // 1. bucket3 - // 2. first.bucket - s3_client = accounts.account3.s3_client; - res = await s3_client.listBuckets({}); - buckets = res.Buckets.map(bucket => bucket.Name).sort(); - assert.equal(buckets.length, 2); - assert.deepStrictEqual(buckets, [accounts.account3.bucket, first_bucket]); - - // delete bucket policy - await rpc_client.bucket.put_bucket_policy({ - name: bucket2, - policy: '', - }); }); });