Skip to content

Commit

Permalink
noobaa-core: Bucket Policy Condition to access Specific VersionID
Browse files Browse the repository at this point in the history
We need to implement a Policy check so that a user can access
only specific version of an object for a given account

JIRA: https://issues.redhat.com/browse/MCGI-264

Signed-off-by: Ashish Pandey <[email protected]>
  • Loading branch information
aspandey committed Feb 11, 2025
1 parent 23aec8d commit d7fdb67
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 3 deletions.
12 changes: 10 additions & 2 deletions src/endpoint/s3/s3_bucket_policy_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,14 @@ const predicate_map = {

const condition_fit_functions = {
's3:ExistingObjectTag': _is_object_tag_fit,
's3:x-amz-server-side-encryption': _is_server_side_encryption_fit
's3:x-amz-server-side-encryption': _is_server_side_encryption_fit,
's3:VersionId': _is_object_version_fit
};

const supported_actions = {
's3:ExistingObjectTag': ['s3:DeleteObjectTagging', 's3:DeleteObjectVersionTagging', 's3:GetObject', 's3:GetObjectAcl', 's3:GetObjectTagging', 's3:GetObjectVersion', 's3:GetObjectVersionTagging', 's3:PutObjectAcl', 's3:PutObjectTagging', 's3:PutObjectVersionTagging'],
's3:x-amz-server-side-encryption': ['s3:PutObject']
's3:x-amz-server-side-encryption': ['s3:PutObject'],
's3:VersionId': ['s3:GetObjectVersion', 's3:DeleteObjectVersion']
};

const SUPPORTED_BUCKET_POLICY_CONDITIONS = Object.keys(supported_actions);
Expand All @@ -136,6 +138,12 @@ async function _is_object_tag_fit(req, predicate, value) {
dbg.log1('bucket_policy: object tag fit?', value, tag, res);
return res;
}
async function _is_object_version_fit(req, predicate, value) {
const version_id = req.query.versionId;
const res = predicate(version_id, value);
dbg.log1('Condition statement: Requested version-id, value and result :', version_id, value, res);
return res;
}

async function has_bucket_policy_permission(policy, account, method, arn_path, req) {
const [allow_statements, deny_statements] = _.partition(policy.Statement, statement => statement.Effect === 'Allow');
Expand Down
157 changes: 156 additions & 1 deletion src/test/unit_tests/test_s3_bucket_policy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* Copyright (C) 2016 NooBaa */
/* eslint max-lines-per-function: ['error', 650] */
/* eslint max-lines: ["error", 2500] */
'use strict';

// setup coretest first to prepare the env
Expand Down Expand Up @@ -35,6 +36,8 @@ const BKT = 'test2-bucket-policy-ops';
const BKT_B = 'test2-bucket-policy-ops-1';
const BKT_C = 'test2-bucket-policy-ops-2';
const BKT_D = 'test2-bucket-policy-ops-3';
const VER_BKT = 'test-object-ver-policy-ops';

const KEY = 'file1.txt';
const user_a = 'alice';
const user_b = 'bob';
Expand Down Expand Up @@ -137,6 +140,8 @@ async function setup() {
await s3_owner.createBucket({ Bucket: BKT });
await s3_owner.createBucket({ Bucket: BKT_C });
await s3_owner.createBucket({ Bucket: BKT_D });
await s3_owner.createBucket({ Bucket: VER_BKT });

s3_anon = new S3({
...s3_creds,
credentials: {
Expand All @@ -150,7 +155,7 @@ async function setup() {
});
}

/*eslint max-lines-per-function: ["error", 2000]*/
/*eslint max-lines-per-function: ["error", 3000]*/
mocha.describe('s3_bucket_policy', function() {
mocha.before(setup);
mocha.it('should fail setting bucket policy when user doesn\'t exist', async function() {
Expand Down Expand Up @@ -1963,4 +1968,154 @@ mocha.describe('s3_bucket_policy', function() {
assert.strictEqual(res.PolicyStatus.IsPublic, true);
});
});
mocha.describe('s3_bucket_policy: Granting access to a specific version of an object ', function() {
mocha.it('should be able to put access permission based on VersionId', async function() {
const self = this; // eslint-disable-line no-invalid-this
self.timeout(15000);
const object_key = 'allowed_file_1.txt';

await s3_owner.putBucketVersioning({
Bucket: VER_BKT,
VersioningConfiguration: {
MFADelete: 'Disabled',
Status: 'Enabled'
}
});

// Create an object and upload different copies of same object
const first_res = await s3_owner.putObject({
Body: 'Some data for the file... bla bla bla... version I',
Bucket: VER_BKT,
Key: object_key
});
const second_res = await s3_owner.putObject({
Body: 'Some data for the file... bla bla bla bla... version II',
Bucket: VER_BKT,
Key: object_key
});
const third_res = await s3_owner.putObject({
Body: 'Some data for the file... bla bla bla bla... version III',
Bucket: VER_BKT,
Key: object_key
});

const version_policy = {
Version: '2012-10-17',
Statement: [
{
Sid: 'id-1',
Effect: 'Allow',
Principal: { AWS: user_b },
Action: ['s3:*'],
Resource: [`arn:aws:s3:::${VER_BKT}/${object_key}`]
},
{
Sid: 'id-2',
Effect: 'Allow',
Principal: { AWS: user_a },
Action: ['s3:GetObjectVersion'],
Resource: [`arn:aws:s3:::${VER_BKT}/${object_key}`]
},
{
Sid: 'id-3',
Effect: 'Deny',
Principal: { AWS: user_a },
Action: ['s3:GetObjectVersion'],
Resource: [`arn:aws:s3:::${VER_BKT}/${object_key}`],
Condition: {
StringNotEquals: {
's3:VersionId': first_res.VersionId // Use one of the VersionId to set policy
}
}
},
{
Sid: 'id-4',
Effect: 'Deny',
Principal: { AWS: user_a },
Action: ['s3:DeleteObjectVersion'],
Resource: [`arn:aws:s3:::${VER_BKT}/${object_key}`],
Condition: {
StringNotEquals: {
's3:VersionId': first_res.VersionId // Use one of the VersionId to set policy
}
}
}
]
};
// Put new policy to allow user_b full access while user_a has access on one VersionId
const res_put_bucket_policy = await s3_owner.putBucketPolicy({
Bucket: VER_BKT,
Policy: JSON.stringify(version_policy)
});
assert.equal(res_put_bucket_policy.$metadata.httpStatusCode, 200);

const res_get_bucket_policy = await s3_owner.getBucketPolicy({
Bucket: VER_BKT,
});
assert.equal(res_get_bucket_policy.$metadata.httpStatusCode, 200);

// user_a to access allowed version - should pass
const res1 = await s3_a.getObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: first_res.VersionId
});
assert.equal(res1.$metadata.httpStatusCode, 200);

// Access Denied: when user_a wants to access other versions
await assert_throws_async(s3_a.getObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: third_res.VersionId
}));

// Access Denied: user_a is trying to access latest version
await assert_throws_async(s3_a.getObject({
Bucket: VER_BKT,
Key: object_key,
}));

// Access Denied: when user_a wants to DELETE third versions
await assert_throws_async(s3_a.deleteObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: third_res.VersionId
}));

// Access Denied: user_a is trying to DELETE latest version
await assert_throws_async(s3_a.deleteObject({
Bucket: VER_BKT,
Key: object_key,
}));

// DELETE Allowed: user_a is trying to DELETE first version
const res_del = await s3_a.getObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: first_res.VersionId
});
assert.equal(res_del.$metadata.httpStatusCode, 200);

// Access should be allowed for all versions for user_b
const res2 = await s3_b.getObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: first_res.VersionId
});
assert.equal(res2.$metadata.httpStatusCode, 200);

const res3 = await s3_b.getObject({
Bucket: VER_BKT,
Key: object_key,
VersionId: second_res.VersionId
});
assert.equal(res3.$metadata.httpStatusCode, 200);

const res4 = await s3_b.getObject({
Bucket: VER_BKT,
Key: object_key,
});
assert.equal(res4.$metadata.httpStatusCode, 200);
});
});
});

0 comments on commit d7fdb67

Please sign in to comment.