From 778e5e8b4c12c926d3df73aae065954abb76d4fe Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Tue, 19 Nov 2024 18:55:28 +0530 Subject: [PATCH 1/3] temp Signed-off-by: Utkarsh Srivastava --- src/endpoint/s3/ops/index.js | 1 + .../s3/ops/s3_put_public_access_block.js | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+) create mode 100644 src/endpoint/s3/ops/s3_put_public_access_block.js diff --git a/src/endpoint/s3/ops/index.js b/src/endpoint/s3/ops/index.js index a93984182d..7f1888bbbe 100644 --- a/src/endpoint/s3/ops/index.js +++ b/src/endpoint/s3/ops/index.js @@ -77,6 +77,7 @@ exports.put_object_legal_hold = require('./s3_put_object_legal_hold'); exports.put_object_retention = require('./s3_put_object_retention'); exports.put_object_tagging = require('./s3_put_object_tagging'); exports.put_object_uploadId = require('./s3_put_object_uploadId'); +exports.put_public_access_block = require('./s3_put_public_access_block'); const js_utils = require('../../../util/js_utils'); js_utils.deep_freeze(exports); diff --git a/src/endpoint/s3/ops/s3_put_public_access_block.js b/src/endpoint/s3/ops/s3_put_public_access_block.js new file mode 100644 index 0000000000..7be38fd1d2 --- /dev/null +++ b/src/endpoint/s3/ops/s3_put_public_access_block.js @@ -0,0 +1,22 @@ +/* Copyright (C) 2024 NooBaa */ +'use strict'; + +/** + * 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 +} + +module.exports = { + handler: put_public_access_block, + body: { + type: 'xml', + }, + reply: { + type: 'empty', + }, +}; + From ebd87db128aecda1142f182204f1c951e797f49f Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Tue, 3 Dec 2024 12:40:48 +0530 Subject: [PATCH 2/3] almost there Signed-off-by: Utkarsh Srivastava --- src/api/common_api.js | 9 ++++ src/endpoint/s3/ops/index.js | 2 + .../s3/ops/s3_delete_public_access_block.js | 20 ++++++++ .../s3/ops/s3_get_public_access_block.js | 35 +++++++++++++ .../s3/ops/s3_put_public_access_block.js | 5 +- src/endpoint/s3/s3_bucket_policy_utils.js | 28 +++++++++++ src/endpoint/s3/s3_utils.js | 20 ++++++++ src/sdk/bucketspace_fs.js | 49 +++++++++++++++++++ src/sdk/bucketspace_nb.js | 16 ++++++ src/sdk/nb.d.ts | 4 ++ src/sdk/object_sdk.js | 18 +++++++ src/server/system_services/bucket_server.js | 49 +++++++++++++++++++ .../schemas/nsfs_bucket_schema.js | 3 ++ 13 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 src/endpoint/s3/ops/s3_delete_public_access_block.js create mode 100644 src/endpoint/s3/ops/s3_get_public_access_block.js diff --git a/src/api/common_api.js b/src/api/common_api.js index 671f520cca..6c00fc7cf2 100644 --- a/src/api/common_api.js +++ b/src/api/common_api.js @@ -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' }, + }, } } }; diff --git a/src/endpoint/s3/ops/index.js b/src/endpoint/s3/ops/index.js index 7f1888bbbe..31ae3e455d 100644 --- a/src/endpoint/s3/ops/index.js +++ b/src/endpoint/s3/ops/index.js @@ -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'); @@ -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'); diff --git a/src/endpoint/s3/ops/s3_delete_public_access_block.js b/src/endpoint/s3/ops/s3_delete_public_access_block.js new file mode 100644 index 0000000000..ce3782c955 --- /dev/null +++ b/src/endpoint/s3/ops/s3_delete_public_access_block.js @@ -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', + }, +}; + diff --git a/src/endpoint/s3/ops/s3_get_public_access_block.js b/src/endpoint/s3/ops/s3_get_public_access_block.js new file mode 100644 index 0000000000..bff6438935 --- /dev/null +++ b/src/endpoint/s3/ops/s3_get_public_access_block.js @@ -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', + }, +}; + diff --git a/src/endpoint/s3/ops/s3_put_public_access_block.js b/src/endpoint/s3/ops/s3_put_public_access_block.js index 7be38fd1d2..696fc51c72 100644 --- a/src/endpoint/s3/ops/s3_put_public_access_block.js +++ b/src/endpoint/s3/ops/s3_put_public_access_block.js @@ -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 = { diff --git a/src/endpoint/s3/s3_bucket_policy_utils.js b/src/endpoint/s3/s3_bucket_policy_utils.js index d96be8b6e3..b3d1def522 100644 --- a/src/endpoint/s3/s3_bucket_policy_utils.js +++ b/src/endpoint/s3/s3_bucket_policy_utils.js @@ -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; diff --git a/src/endpoint/s3/s3_utils.js b/src/endpoint/s3/s3_utils.js index c2315b0b46..c4bf9e3daf 100644 --- a/src/endpoint/s3/s3_utils.js +++ b/src/endpoint/s3/s3_utils.js @@ -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; @@ -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; diff --git a/src/sdk/bucketspace_fs.js b/src/sdk/bucketspace_fs.js index 6f07c5ed7c..128abd4bea 100644 --- a/src/sdk/bucketspace_fs.js +++ b/src/sdk/bucketspace_fs.js @@ -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)); @@ -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 // ///////////////////////// diff --git a/src/sdk/bucketspace_nb.js b/src/sdk/bucketspace_nb.js index 88b15767da..5772aab2f0 100644 --- a/src/sdk/bucketspace_nb.js +++ b/src/sdk/bucketspace_nb.js @@ -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 diff --git a/src/sdk/nb.d.ts b/src/sdk/nb.d.ts index 29abd76fb4..47b68e6e00 100644 --- a/src/sdk/nb.d.ts +++ b/src/sdk/nb.d.ts @@ -818,6 +818,10 @@ interface Namespace { restore_object(params: object, object_sdk: ObjectSDK): Promise; get_object_attributes(params: object, object_sdk: ObjectSDK): Promise; + + get_public_access_block(params, object_sdk: ObjectSDK): Promise; + put_public_access_block(params, object_sdk: ObjectSDK): Promise; + delete_public_access_block(params, object_sdk: ObjectSDK): Promise; } interface BucketSpace { diff --git a/src/sdk/object_sdk.js b/src/sdk/object_sdk.js index 864e9a1ae6..33affc0060 100644 --- a/src/sdk/object_sdk.js +++ b/src/sdk/object_sdk.js @@ -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 diff --git a/src/server/system_services/bucket_server.js b/src/server/system_services/bucket_server.js index f4f994d73b..5f373c3f86 100644 --- a/src/server/system_services/bucket_server.js +++ b/src/server/system_services/bucket_server.js @@ -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: [{ @@ -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) { diff --git a/src/server/system_services/schemas/nsfs_bucket_schema.js b/src/server/system_services/schemas/nsfs_bucket_schema.js index 363f149004..b4630ed1b3 100644 --- a/src/server/system_services/schemas/nsfs_bucket_schema.js +++ b/src/server/system_services/schemas/nsfs_bucket_schema.js @@ -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', + } } }; From f26335e474531bdc3c51d65cd17b39701f5a11f4 Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Wed, 4 Dec 2024 17:24:31 +0530 Subject: [PATCH 3/3] temp 2 Signed-off-by: Utkarsh Srivastava --- src/server/system_services/schemas/bucket_schema.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/system_services/schemas/bucket_schema.js b/src/server/system_services/schemas/bucket_schema.js index 05446ed08b..5d96276b45 100644 --- a/src/server/system_services/schemas/bucket_schema.js +++ b/src/server/system_services/schemas/bucket_schema.js @@ -285,5 +285,8 @@ module.exports = { $ref: 'common_api#/definitions/bucket_notification' } }, + public_access_block: { + $ref: 'common_api#/definitions/public_access_block', + } } };