Skip to content

Commit e917402

Browse files
committed
Account with NotPrincipal should explicitly provide permission
The is the bucket policy with below combination Effect : DENY, Action: $OPERATION, NotPrincipal: $ACCOUNT The permission on the "Action" for the $ACCOUNT mentioned in the "NotPrincipal" should be explicitly given. Example: For "Action: get_object", if we want to DENY permission for * (all accounts) and we want to give explicit permission to any one or few accounts, then we can include that account as part of part of "NotPrincipal" Check DFBUGS-1519 for steps to reproduce the issue Fixes: https://issues.redhat.com/browse/DFBUGS-1519 Signed-off-by: Vinayakswami Hariharmath <[email protected]>
1 parent b487b1a commit e917402

File tree

3 files changed

+75
-18
lines changed

3 files changed

+75
-18
lines changed

src/endpoint/s3/s3_bucket_policy_utils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ function _is_principal_fit(account, statement, ignore_public_principal = false)
189189
statement_principal = statement_principal.AWS ? statement_principal.AWS : statement_principal;
190190
for (const principal of _.flatten([statement_principal])) {
191191
dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal, account);
192-
if ((principal.unwrap() === '*') || (principal.unwrap() === account)) {
192+
if ((principal.unwrap() === '*') || (principal.unwrap() === account.id) || (principal.unwrap() === account.name)) {
193193
if (ignore_public_principal && principal.unwrap() === '*' && statement.Principal) {
194194
// Ignore the "fit" if ignore_public_principal is requested
195195
continue;

src/endpoint/s3/s3_rest.js

+29-17
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,24 @@ async function authorize_request_policy(req) {
229229
if (!req.params.bucket) return;
230230
if (req.op_name === 'put_bucket') return;
231231

232+
// Allow bucket owner or system owner to put a bucket policy
233+
if (req.op_name === 'put_bucket_policy') {
234+
const { system_owner, bucket_owner } = await req.object_sdk.read_bucket_sdk_policy_info(req.params.bucket);
235+
const account = req.object_sdk.requesting_account;
236+
const account_identifier_name = req.object_sdk.nsfs_config_root ? account.name.unwrap() : account.email.unwrap();
237+
const account_identifier_id = req.object_sdk.nsfs_config_root ? account._id : undefined;
238+
239+
const is_system_owner = (Boolean(system_owner) && system_owner.unwrap() === account_identifier_name) ||
240+
(Boolean(system_owner) && system_owner.unwrap() === account_identifier_id);
241+
const is_bucket_owner = (Boolean(bucket_owner) && bucket_owner.unwrap() === account_identifier_name) ||
242+
(Boolean(bucket_owner) && bucket_owner.unwrap() === account_identifier_id);
243+
244+
if (is_system_owner || is_bucket_owner) return;
245+
246+
// If the account is neither the system owner nor the bucket owner, deny access
247+
throw new S3Error(S3Error.AccessDenied);
248+
}
249+
232250
// owner_account is { id: bucket.owner_account, email: bucket.bucket_owner };
233251
const { s3_policy, system_owner, bucket_owner, owner_account } = await req.object_sdk.read_bucket_sdk_policy_info(req.params.bucket);
234252
const auth_token = req.object_sdk.get_auth_token();
@@ -279,30 +297,24 @@ async function authorize_request_policy(req) {
279297
if (is_owner || is_iam_account_and_same_root_account_owner) return;
280298
throw new S3Error(S3Error.AccessDenied);
281299
}
282-
// in case we have bucket policy
283-
let permission_by_id;
284-
let permission_by_name;
285300

286301
const public_access_block_cfg = await req.object_sdk.get_public_access_block({ name: req.params.bucket });
287302
// In NC, we allow principal to be:
288303
// 1. account name (for backwards compatibility)
289304
// 2. account id
290305
// we start the permission check on account identifier intentionally
291-
if (account_identifier_id) {
292-
permission_by_id = await s3_bucket_policy_utils.has_bucket_policy_permission(
293-
s3_policy, account_identifier_id, method, arn_path, req, public_access_block_cfg?.public_access_block?.restrict_public_buckets);
294-
dbg.log3('authorize_request_policy: permission_by_id', permission_by_id);
295-
}
296-
if (permission_by_id === "DENY") throw new S3Error(S3Error.AccessDenied);
297306

298-
if ((!account_identifier_id || permission_by_id !== "DENY") && account.owner === undefined) {
299-
permission_by_name = await s3_bucket_policy_utils.has_bucket_policy_permission(
300-
s3_policy, account_identifier_name, method, arn_path, req, public_access_block_cfg?.public_access_block?.restrict_public_buckets
301-
);
302-
dbg.log3('authorize_request_policy: permission_by_name', permission_by_name);
303-
}
304-
if (permission_by_name === "DENY") throw new S3Error(S3Error.AccessDenied);
305-
if ((permission_by_id === "ALLOW" || permission_by_name === "ALLOW") || is_owner) return;
307+
const account_identifier = {
308+
id: account_identifier_id,
309+
name: account_identifier_name,
310+
};
311+
312+
const bucket_permission = await s3_bucket_policy_utils.has_bucket_policy_permission(
313+
s3_policy, account_identifier, method, arn_path, req, public_access_block_cfg?.public_access_block?.restrict_public_buckets);
314+
dbg.log3('authorize_request_policy: bucket_permission', bucket_permission);
315+
316+
if (bucket_permission === "DENY") throw new S3Error(S3Error.AccessDenied);
317+
if (bucket_permission === "ALLOW") return;
306318

307319
throw new S3Error(S3Error.AccessDenied);
308320
}

src/test/unit_tests/test_s3_bucket_policy.js

+45
Original file line numberDiff line numberDiff line change
@@ -1645,6 +1645,51 @@ mocha.describe('s3_bucket_policy', function() {
16451645
Bucket: BKT,
16461646
Key: KEY,
16471647
}));
1648+
1649+
});
1650+
1651+
mocha.it('should be able to use notPrincipal with Effect Deny', async function() {
1652+
const self = this; // eslint-disable-line no-invalid-this
1653+
self.timeout(15000);
1654+
const auth_put_policy = {
1655+
Version: '2012-10-17',
1656+
Statement: [
1657+
{
1658+
Effect: 'Allow',
1659+
Principal: { AWS: [user_a, user_b] },
1660+
Action: ['s3:*'],
1661+
Resource: [`arn:aws:s3:::${BKT}/*`]
1662+
},
1663+
{
1664+
Effect: 'Deny',
1665+
NotPrincipal: { AWS: user_a },
1666+
Action: ['s3:GetObject'],
1667+
Resource: [`arn:aws:s3:::${BKT}/*`]
1668+
}
1669+
]};
1670+
let res = await s3_owner.putBucketPolicy({
1671+
Bucket: BKT,
1672+
Policy: JSON.stringify(auth_put_policy)
1673+
});
1674+
assert.equal(res.$metadata.httpStatusCode, 200);
1675+
1676+
res = await s3_a.putObject({
1677+
Body: BODY,
1678+
Bucket: BKT,
1679+
Key: KEY,
1680+
});
1681+
assert.equal(res.$metadata.httpStatusCode, 200);
1682+
1683+
res = await s3_a.getObject({
1684+
Bucket: BKT,
1685+
Key: KEY,
1686+
});
1687+
assert.equal(res.$metadata.httpStatusCode, 200);
1688+
1689+
await assert_throws_async(s3_b.getObject({
1690+
Bucket: BKT,
1691+
Key: KEY,
1692+
}));
16481693
});
16491694

16501695
mocha.it('should be able to use notResource', async function() {

0 commit comments

Comments
 (0)