Skip to content

Commit

Permalink
almost there
Browse files Browse the repository at this point in the history
Signed-off-by: Utkarsh Srivastava <[email protected]>
  • Loading branch information
tangledbytes committed Dec 5, 2024
1 parent 778e5e8 commit ebd87db
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 1 deletion.
9 changes: 9 additions & 0 deletions src/api/common_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1464,6 +1464,15 @@ module.exports = {
}
}
}
},
public_access_block: {
type: 'object',
properties: {
block_public_acls: { type: 'boolean' },
ignore_public_acls: { type: 'boolean' },
block_public_policy: { type: 'boolean' },
restrict_public_buckets: { type: 'boolean' },
},
}
}
};
2 changes: 2 additions & 0 deletions src/endpoint/s3/ops/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ exports.delete_bucket_replication = require('./s3_delete_bucket_replication');
exports.delete_bucket_tagging = require('./s3_delete_bucket_tagging');
exports.delete_bucket_website = require('./s3_delete_bucket_website');
exports.delete_object = require('./s3_delete_object');
exports.delete_public_access_block = require('./s3_put_public_access_block');
exports.delete_object_tagging = require('./s3_delete_object_tagging');
exports.delete_object_uploadId = require('./s3_delete_object_uploadId');
exports.get_bucket = require('./s3_get_bucket');
Expand Down Expand Up @@ -44,6 +45,7 @@ exports.get_object_legal_hold = require('./s3_get_object_legal_hold');
exports.get_object_retention = require('./s3_get_object_retention');
exports.get_object_tagging = require('./s3_get_object_tagging');
exports.get_object_uploadId = require('./s3_get_object_uploadId');
exports.get_public_access_block = require('./s3_get_public_access_block');
exports.get_service = require('./s3_get_service');
exports.head_bucket = require('./s3_head_bucket');
exports.head_object = require('./s3_head_object');
Expand Down
20 changes: 20 additions & 0 deletions src/endpoint/s3/ops/s3_delete_public_access_block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Copyright (C) 2024 NooBaa */
'use strict';

/**
* https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeletePublicAccessBlock.html
*/
async function delete_public_access_block(req) {
await req.object_sdk.delete_public_access_block({ name: req.params.bucket });
}

module.exports = {
handler: delete_public_access_block,
body: {
type: 'empty',
},
reply: {
type: 'empty',
},
};

35 changes: 35 additions & 0 deletions src/endpoint/s3/ops/s3_get_public_access_block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* Copyright (C) 2024 NooBaa */
'use strict';

/**
* https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetPublicAccessBlock.html
*/
async function get_public_access_block(req) {
const reply = await req.object_sdk.get_public_access_block({ name: req.params.bucket });
if (!reply.public_access_block) {
return {
block_public_acls: false,
ignore_public_acls: false,
block_public_policy: false,
restrict_public_buckets: false,
};
}

return {
block_public_acls: Boolean(reply.public_access_block.block_public_acls),
ignore_public_acls: Boolean(reply.public_access_block.ignore_public_acls),
block_public_policy: Boolean(reply.public_access_block.block_public_policy),
restrict_public_buckets: Boolean(reply.public_access_block.restrict_public_buckets),
};
}

module.exports = {
handler: get_public_access_block,
body: {
type: 'empty'
},
reply: {
type: 'json',
},
};

5 changes: 4 additions & 1 deletion src/endpoint/s3/ops/s3_put_public_access_block.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* Copyright (C) 2024 NooBaa */
'use strict';

const s3_utils = require('../s3_utils');

/**
* https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutPublicAccessBlock.html
* @param {*} req
* @param {*} res
*/
async function put_public_access_block(req, res) {
// Do something in this function
const public_access_block = s3_utils.parse_body_public_access_block(req);
await req.object_sdk.put_public_access_block({ name: req.params.bucket, public_access_block });
}

module.exports = {
Expand Down
28 changes: 28 additions & 0 deletions src/endpoint/s3/s3_bucket_policy_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,34 @@ async function validate_s3_policy(policy, bucket_name, get_account_handler) {
}
}

/**
* allows_public_access returns true if a policy will allow public access
* to a resource
*
* NOTE: It assumes that the given policy has already been validated
* @param {*} policy
* @returns {boolean}
*/
function allows_public_access(policy) {
for (const statement of policy.statement) {
if (statement.Effect === 'Deny') continue;

const statement_principal = statement.principal;
if (statement_principal.AWS) {
for (const principal of _.flatten([statement_principal.AWS])) {
if (typeof principal === 'string' ? principal === '*' : principal.unwrap() === '*') {
return true;
}
}
} else if (typeof statement_principal === 'string' ? statement_principal === '*' : statement_principal.unwrap() === '*') {
return true;
}
}

return false;
}

exports.OP_NAME_TO_ACTION = OP_NAME_TO_ACTION;
exports.has_bucket_policy_permission = has_bucket_policy_permission;
exports.validate_s3_policy = validate_s3_policy;
exports.allows_public_access = allows_public_access;
20 changes: 20 additions & 0 deletions src/endpoint/s3/s3_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,25 @@ function key_marker_to_cont_tok(key_marker, objects_arr, is_truncated) {
return Buffer.from(j).toString('base64');
}

function parse_body_public_access_block(req) {
const parsed = {};

const access_cfg = req.body.PublicAccessBlockConfiguration;
if (!access_cfg) throw new S3Error(S3Error.MalformedXML);

if (access_cfg.BlockPublicAcls || access_cfg.IgnorePublicAcls) {
throw new S3Error(S3Error.AccessControlListNotSupported);
}
if (access_cfg.BlockPublicPolicy) {
parsed.block_public_policy = access_cfg.BlockPublicPolicy.toLowerCase?.() === 'true';
}
if (access_cfg.RestrictPublicBuckets) {
parsed.restrict_public_buckets = access_cfg.RestrictPublicBuckets.toLowerCase?.() === 'true';
}

return parsed;
}

exports.STORAGE_CLASS_STANDARD = STORAGE_CLASS_STANDARD;
exports.STORAGE_CLASS_GLACIER = STORAGE_CLASS_GLACIER;
exports.STORAGE_CLASS_GLACIER_IR = STORAGE_CLASS_GLACIER_IR;
Expand Down Expand Up @@ -828,5 +847,6 @@ exports.set_response_supported_storage_classes = set_response_supported_storage_
exports.cont_tok_to_key_marker = cont_tok_to_key_marker;
exports.key_marker_to_cont_tok = key_marker_to_cont_tok;
exports.parse_sse_c = parse_sse_c;
exports.parse_body_public_access_block = parse_body_public_access_block;
exports.OBJECT_ATTRIBUTES = OBJECT_ATTRIBUTES;
exports.OBJECT_ATTRIBUTES_UNSUPPORTED = OBJECT_ATTRIBUTES_UNSUPPORTED;
49 changes: 49 additions & 0 deletions src/sdk/bucketspace_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,14 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
const { name, policy } = params;
dbg.log0('BucketSpaceFS.put_bucket_policy: Bucket name, policy', name, policy);
const bucket = await this.config_fs.get_bucket_by_name(name);

if (
bucket.public_access_block?.block_public_policy &&
bucket_policy_utils.allows_public_access(policy)
) {
throw new S3Error(S3Error.AccessDenied);
}

bucket.s3_policy = policy;
// We need to validate bucket schema here as well for checking the policy schema
nsfs_schema_utils.validate_bucket_schema(_.omitBy(bucket, _.isUndefined));
Expand Down Expand Up @@ -738,6 +746,47 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
}
}

/////////////////////////
// PUBLIC ACCESS BLOCK //
/////////////////////////

async get_public_access_block(params, object_sdk) {
try {
const { bucket_name } = params;
dbg.log0('BucketSpaceFS.get_public_access_block: Bucket name', bucket_name);
const bucket = await this.config_fs.get_bucket_by_name(bucket_name);
return {
public_access_block: bucket.public_access_block,
};
} catch (error) {
throw translate_error_codes(error, entity_enum.BUCKET);
}
}

async put_public_access_block(params, object_sdk) {
try {
const { bucket_name, public_access_block } = params;
dbg.log0('BucketSpaceFS.put_public_access_block: Bucket name', bucket_name, ", public_access_block ", public_access_block);
const bucket = await this.config_fs.get_bucket_by_name(bucket_name);
bucket.public_access_block = public_access_block;
await this.config_fs.update_bucket_config_file(bucket);
} catch (error) {
throw translate_error_codes(error, entity_enum.BUCKET);
}
}

async delete_public_access_block(params, object_sdk) {
try {
const { bucket_name } = params;
dbg.log0('BucketSpaceFS.delete_public_access_block: Bucket name', bucket_name);
const bucket = await this.config_fs.get_bucket_by_name(bucket_name);
delete bucket.public_access_block;
await this.config_fs.update_bucket_config_file(bucket);
} catch (error) {
throw translate_error_codes(error, entity_enum.BUCKET);
}
}

/////////////////////////
// DEFAULT OBJECT LOCK //
/////////////////////////
Expand Down
16 changes: 16 additions & 0 deletions src/sdk/bucketspace_nb.js
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,22 @@ class BucketSpaceNB {
return this.rpc_client.bucket.put_object_lock_configuration(params);
}

/////////////////////////
// PUBLIC ACCESS BLOCK //
/////////////////////////

async get_public_access_block(params, object_sdk) {
return this.rpc_client.bucket.get_public_access_block(params);
}

async put_public_access_block(params, object_sdk) {
return this.rpc_client.bucket.put_public_access_block(params);
}

async delete_public_access_block(params, object_sdk) {
return this.rpc_client.bucket.delete_public_access_block(params);
}

// nsfs


Expand Down
4 changes: 4 additions & 0 deletions src/sdk/nb.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,10 @@ interface Namespace {

restore_object(params: object, object_sdk: ObjectSDK): Promise<any>;
get_object_attributes(params: object, object_sdk: ObjectSDK): Promise<any>;

get_public_access_block(params, object_sdk: ObjectSDK): Promise<any>;
put_public_access_block(params, object_sdk: ObjectSDK): Promise<any>;
delete_public_access_block(params, object_sdk: ObjectSDK): Promise<any>;
}

interface BucketSpace {
Expand Down
18 changes: 18 additions & 0 deletions src/sdk/object_sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,24 @@ class ObjectSDK {
}
}

//////////////////////////
// PUBLIC ACCESS BLOCK //
//////////////////////////

async get_public_access_block(params) {
const ns = await this._get_bucket_namespace(params.bucket);
return ns.get_public_access_block?.(params, this);
}

async put_public_access_block(params) {
const ns = await this._get_bucket_namespace(params.bucket);
return ns.put_public_access_block?.(params, this);
}

async delete_public_access_block(params) {
const ns = await this._get_bucket_namespace(params.bucket);
return ns.delete_public_access_block?.(params, this);
}
}

// EXPORT
Expand Down
49 changes: 49 additions & 0 deletions src/server/system_services/bucket_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,15 @@ async function put_bucket_policy(req) {
const bucket = find_bucket(req, req.rpc_params.name);
await bucket_policy_utils.validate_s3_policy(req.rpc_params.policy, bucket.name,
principal => system_store.get_account_by_email(principal));

if (
bucket.public_access_block?.block_public_policy &&
bucket_policy_utils.allows_public_access(req.rpc_params.policy)
) {
// Should result in AccessDenied error
throw new RpcError('UNAUTHORIZED');
}

await system_store.make_changes({
update: {
buckets: [{
Expand Down Expand Up @@ -1441,6 +1450,46 @@ async function update_all_buckets_default_pool(req) {
});
}

/**
*
* PUBLIC_ACCESS_BLOCK
*
*/

async function get_public_access_block(req) {
dbg.log0('get_public_access_block:', req.rpc_params);
const bucket = find_bucket(req, req.rpc_params.name);
return {
public_access_block: bucket.public_access_block,
};
}

async function put_public_access_block(req) {
dbg.log0('put_public_access_block:', req.rpc_params);
const bucket = find_bucket(req, req.rpc_params.name);
await system_store.make_changes({
update: {
buckets: [{
_id: bucket._id,
public_access_block: req.rpc_params.public_access_block,
}]
}
});
}

async function delete_public_access_block(req) {
dbg.log0('delete_public_access_block:', req.rpc_params);
const bucket = find_bucket(req, req.rpc_params.name);
await system_store.make_changes({
update: {
buckets: [{
_id: bucket._id,
$unset: { public_access_block: 1 }
}]
}
});
}

// UTILS //////////////////////////////////////////////////////////

function validate_bucket_creation(req) {
Expand Down
3 changes: 3 additions & 0 deletions src/server/system_services/schemas/nsfs_bucket_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,8 @@ module.exports = {
cors_configuration_rules: {
$ref: 'common_api#/definitions/bucket_cors_configuration'
},
public_access_block: {
$ref: 'common_api#/definitions/public_access_block',
}
}
};

0 comments on commit ebd87db

Please sign in to comment.