From 0144b3bf0d978acef8cffc78650d9dca073c025d Mon Sep 17 00:00:00 2001 From: Utkarsh Srivastava Date: Tue, 19 Nov 2024 18:55:28 +0530 Subject: [PATCH] CRUD PublicAccessBlock working Signed-off-by: Utkarsh Srivastava Complete BlockPublicAccess integration Signed-off-by: Utkarsh Srivastava --- src/api/bucket_api.js | 59 +++++++++++++++++++ src/api/common_api.js | 9 +++ src/endpoint/s3/ops/index.js | 3 + .../s3_delete_bucket_public_access_block.js | 20 +++++++ .../ops/s3_get_bucket_public_access_block.js | 39 ++++++++++++ .../ops/s3_put_bucket_public_access_block.js | 25 ++++++++ src/endpoint/s3/s3_bucket_policy_utils.js | 54 ++++++++++++++--- src/endpoint/s3/s3_rest.js | 10 +++- src/endpoint/s3/s3_utils.js | 20 +++++++ src/sdk/bucketspace_fs.js | 55 ++++++++++++++++- src/sdk/bucketspace_nb.js | 16 +++++ src/sdk/nb.d.ts | 4 ++ src/sdk/object_sdk.js | 18 ++++++ src/server/common_services/auth_server.js | 8 +-- src/server/system_services/bucket_server.js | 53 +++++++++++++++++ .../system_services/schemas/bucket_schema.js | 3 + .../schemas/nsfs_bucket_schema.js | 3 + 17 files changed, 379 insertions(+), 20 deletions(-) create mode 100644 src/endpoint/s3/ops/s3_delete_bucket_public_access_block.js create mode 100644 src/endpoint/s3/ops/s3_get_bucket_public_access_block.js create mode 100644 src/endpoint/s3/ops/s3_put_bucket_public_access_block.js diff --git a/src/api/bucket_api.js b/src/api/bucket_api.js index e06b09b46a..5816cf5c76 100644 --- a/src/api/bucket_api.js +++ b/src/api/bucket_api.js @@ -949,6 +949,65 @@ module.exports = { system: ['admin', 'user'] } }, + + get_public_access_block: { + method: 'GET', + params: { + type: 'object', + required: ['bucket_name'], + properties: { + bucket_name: { + $ref: 'common_api#/definitions/bucket_name' + } + } + }, + reply: { + type: 'object', + properties: { + public_access_block: { + $ref: 'common_api#/definitions/public_access_block' + } + } + }, + auth: { + system: ['admin', 'user'] + } + }, + + put_public_access_block: { + method: 'PUT', + params: { + type: 'object', + required: ['bucket_name', 'public_access_block'], + properties: { + bucket_name: { + $ref: 'common_api#/definitions/bucket_name' + }, + public_access_block: { + $ref: 'common_api#/definitions/public_access_block' + }, + }, + }, + auth: { + system: ['admin', 'user'] + } + }, + + delete_public_access_block: { + method: 'DELETE', + params: { + type: 'object', + required: ['bucket_name'], + properties: { + bucket_name: { + $ref: 'common_api#/definitions/bucket_name' + }, + }, + }, + auth: { + system: ['admin', 'user'] + } + }, }, definitions: { diff --git a/src/api/common_api.js b/src/api/common_api.js index 953e1b7722..de2fe44441 100644 --- a/src/api/common_api.js +++ b/src/api/common_api.js @@ -1480,6 +1480,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 a93984182d..1af81b2e22 100644 --- a/src/endpoint/s3/ops/index.js +++ b/src/endpoint/s3/ops/index.js @@ -12,6 +12,7 @@ exports.delete_bucket_policy = require('./s3_delete_bucket_policy'); 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_bucket_public_access_block = require('./s3_delete_bucket_public_access_block'); exports.delete_object = require('./s3_delete_object'); exports.delete_object_tagging = require('./s3_delete_object_tagging'); exports.delete_object_uploadId = require('./s3_delete_object_uploadId'); @@ -37,6 +38,7 @@ exports.get_bucket_uploads = require('./s3_get_bucket_uploads'); exports.get_bucket_versioning = require('./s3_get_bucket_versioning'); exports.get_bucket_versions = require('./s3_get_bucket_versions'); exports.get_bucket_website = require('./s3_get_bucket_website'); +exports.get_bucket_public_access_block = require('./s3_get_bucket_public_access_block'); exports.get_object = require('./s3_get_object'); exports.get_object_acl = require('./s3_get_object_acl'); exports.get_object_attributes = require('./s3_get_object_attributes'); @@ -71,6 +73,7 @@ exports.put_bucket_requestPayment = require('./s3_put_bucket_requestPayment'); exports.put_bucket_tagging = require('./s3_put_bucket_tagging'); exports.put_bucket_versioning = require('./s3_put_bucket_versioning'); exports.put_bucket_website = require('./s3_put_bucket_website'); +exports.put_bucket_public_access_block = require('./s3_put_bucket_public_access_block'); exports.put_object = require('./s3_put_object'); exports.put_object_acl = require('./s3_put_object_acl'); exports.put_object_legal_hold = require('./s3_put_object_legal_hold'); diff --git a/src/endpoint/s3/ops/s3_delete_bucket_public_access_block.js b/src/endpoint/s3/ops/s3_delete_bucket_public_access_block.js new file mode 100644 index 0000000000..ce3782c955 --- /dev/null +++ b/src/endpoint/s3/ops/s3_delete_bucket_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_bucket_public_access_block.js b/src/endpoint/s3/ops/s3_get_bucket_public_access_block.js new file mode 100644 index 0000000000..c099cfeea6 --- /dev/null +++ b/src/endpoint/s3/ops/s3_get_bucket_public_access_block.js @@ -0,0 +1,39 @@ +/* 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 { + PublicAccessBlockConfiguration: { + BlockPublicAcls: false, + IgnorePublicAcls: false, + BlockPublicPolicy: false, + RestrictPublicBuckets: false, + } + }; + } + + return { + PublicAccessBlockConfiguration: { + BlockPublicAcls: Boolean(reply.public_access_block.block_public_acls), + IgnorePublicAcls: Boolean(reply.public_access_block.ignore_public_acls), + BlockPublicPolicy: Boolean(reply.public_access_block.block_public_policy), + RestrictPublicBuckets: Boolean(reply.public_access_block.restrict_public_buckets), + } + }; +} + +module.exports = { + handler: get_public_access_block, + body: { + type: 'empty' + }, + reply: { + type: 'xml', + }, +}; + diff --git a/src/endpoint/s3/ops/s3_put_bucket_public_access_block.js b/src/endpoint/s3/ops/s3_put_bucket_public_access_block.js new file mode 100644 index 0000000000..696fc51c72 --- /dev/null +++ b/src/endpoint/s3/ops/s3_put_bucket_public_access_block.js @@ -0,0 +1,25 @@ +/* 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) { + 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 = { + handler: put_public_access_block, + body: { + type: 'xml', + }, + reply: { + type: 'empty', + }, +}; + diff --git a/src/endpoint/s3/s3_bucket_policy_utils.js b/src/endpoint/s3/s3_bucket_policy_utils.js index d96be8b6e3..4e79de4923 100644 --- a/src/endpoint/s3/s3_bucket_policy_utils.js +++ b/src/endpoint/s3/s3_bucket_policy_utils.js @@ -17,6 +17,7 @@ const OP_NAME_TO_ACTION = Object.freeze({ delete_bucket_replication: { regular: "s3:PutReplicationConfiguration" }, delete_bucket_tagging: { regular: "s3:PutBucketTagging" }, delete_bucket_website: { regular: "s3:DeleteBucketWebsite" }, + delete_bucket_public_access_block: { regular: "s3:PutBucketPublicAccessBlock" }, delete_bucket: { regular: "s3:DeleteBucket" }, delete_object_tagging: { regular: "s3:DeleteObjectTagging", versioned: "s3:DeleteObjectVersionTagging" }, delete_object_uploadId: { regular: "s3:AbortMultipartUpload" }, @@ -43,6 +44,7 @@ const OP_NAME_TO_ACTION = Object.freeze({ get_bucket_versions: { regular: "s3:ListBucketVersions" }, get_bucket_website: { regular: "s3:GetBucketWebsite" }, get_bucket_object_lock: { regular: "s3:GetBucketObjectLockConfiguration" }, + get_bucket_public_access_block: { regular: "s3:GetBucketPublicAccessBlock" }, get_bucket: { regular: "s3:ListBucket" }, get_object_acl: { regular: "s3:GetObjectAcl" }, get_object_attributes: { regular: ["s3:GetObject", "s3:GetObjectAttributes"], versioned: ["s3:GetObjectVersion", "s3:GetObjectVersionAttributes"] }, // Notice - special case @@ -80,6 +82,7 @@ const OP_NAME_TO_ACTION = Object.freeze({ put_bucket_versioning: { regular: "s3:PutBucketVersioning" }, put_bucket_website: { regular: "s3:PutBucketWebsite" }, put_bucket_object_lock: { regular: "s3:PutBucketObjectLockConfiguration" }, + put_bucket_public_access_block: { regular: "s3:PutBucketPublicAccessBlock" }, put_bucket: { regular: "s3:CreateBucket" }, put_object_acl: { regular: "s3:PutObjectAcl" }, put_object_tagging: { regular: "s3:PutObjectTagging", versioned: "s3:PutObjectVersionTagging" }, @@ -137,18 +140,20 @@ async function _is_object_tag_fit(req, predicate, value) { return res; } -async function has_bucket_policy_permission(policy, account, method, arn_path, req) { +async function has_bucket_policy_permission(policy, account, method, arn_path, req, disallow_public_access = false) { const [allow_statements, deny_statements] = _.partition(policy.Statement, statement => statement.Effect === 'Allow'); // the case where the permission is an array started in op get_object_attributes const method_arr = Array.isArray(method) ? method : [method]; // look for explicit denies - const res_arr_deny = await is_statement_fit_of_method_array(deny_statements, account, method_arr, arn_path, req); + const res_arr_deny = await is_statement_fit_of_method_array( + deny_statements, account, method_arr, arn_path, req, disallow_public_access); if (res_arr_deny.every(item => item)) return 'DENY'; // look for explicit allows - const res_arr_allow = await is_statement_fit_of_method_array(allow_statements, account, method_arr, arn_path, req); + const res_arr_allow = await is_statement_fit_of_method_array( + allow_statements, account, method_arr, arn_path, req, disallow_public_access); if (res_arr_allow.every(item => item)) return 'ALLOW'; // implicit deny @@ -168,7 +173,7 @@ function _is_action_fit(method, statement) { return statement.Action ? action_fit : !action_fit; } -function _is_principal_fit(account, statement) { +function _is_principal_fit(account, statement, ignore_anon_principal = false) { let statement_principal = statement.Principal || statement.NotPrincipal; let principal_fit = false; @@ -176,6 +181,11 @@ function _is_principal_fit(account, statement) { for (const principal of _.flatten([statement_principal])) { dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal, account); if ((principal.unwrap() === '*') || (principal.unwrap() === account)) { + if (ignore_anon_principal && principal.unwrap() === '*' && statement.Principal) { + // Ignore the "fit" if ignore_anon_principal is requested + continue; + } + principal_fit = true; break; } @@ -198,15 +208,15 @@ function _is_resource_fit(arn_path, statement) { return statement.Resource ? resource_fit : !resource_fit; } -async function is_statement_fit_of_method_array(statements, account, method_arr, arn_path, req) { +async function is_statement_fit_of_method_array(statements, account, method_arr, arn_path, req, disallow_public_access = false) { return Promise.all(method_arr.map(method_permission => - _is_statements_fit(statements, account, method_permission, arn_path, req))); + _is_statements_fit(statements, account, method_permission, arn_path, req, disallow_public_access))); } -async function _is_statements_fit(statements, account, method, arn_path, req) { +async function _is_statements_fit(statements, account, method, arn_path, req, disallow_public_access = false) { for (const statement of statements) { const action_fit = _is_action_fit(method, statement); - const principal_fit = _is_principal_fit(account, statement); + const principal_fit = _is_principal_fit(account, statement, disallow_public_access); const resource_fit = _is_resource_fit(arn_path, statement); const condition_fit = await _is_condition_fit(statement, req, method); @@ -295,6 +305,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_rest.js b/src/endpoint/s3/s3_rest.js index e7b2691542..07cf99f080 100755 --- a/src/endpoint/s3/s3_rest.js +++ b/src/endpoint/s3/s3_rest.js @@ -44,7 +44,8 @@ const BUCKET_SUB_RESOURCES = Object.freeze({ 'encryption': 'encryption', 'object-lock': 'object_lock', 'legal-hold': 'legal_hold', - 'retention': 'retention' + 'retention': 'retention', + 'publicAccessBlock': 'public_access_block' }); const OBJECT_SUB_RESOURCES = Object.freeze({ @@ -277,6 +278,8 @@ async function authorize_request_policy(req) { if (is_owner || is_iam_account_and_same_root_account_owner) return; throw new S3Error(S3Error.AccessDenied); } + + const public_access_block_cfg = await req.object_sdk.get_public_access_block({ name: req.params.bucket }); let permission; // In NC, we allow principal to be: // 1. account name (for backwards compatibility) @@ -284,12 +287,13 @@ async function authorize_request_policy(req) { // we start the permission check on account identifier intentionally if (account_identifier_id) { permission = await s3_bucket_policy_utils.has_bucket_policy_permission( - s3_policy, account_identifier_id, method, arn_path, req); + s3_policy, account_identifier_id, method, arn_path, req, public_access_block_cfg?.public_access_block?.restrict_public_buckets); } if ((!account_identifier_id || permission === "IMPLICIT_DENY") && account.owner === undefined) { permission = await s3_bucket_policy_utils.has_bucket_policy_permission( - s3_policy, account_identifier_name, method, arn_path, req); + s3_policy, account_identifier_name, method, arn_path, req, public_access_block_cfg?.public_access_block?.restrict_public_buckets + ); } if (permission === "DENY") throw new S3Error(S3Error.AccessDenied); diff --git a/src/endpoint/s3/s3_utils.js b/src/endpoint/s3/s3_utils.js index c2315b0b46..5efeb37f2e 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?.[0].toLowerCase?.() === 'true'; + } + if (access_cfg.RestrictPublicBuckets) { + parsed.restrict_public_buckets = access_cfg.RestrictPublicBuckets?.[0].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 b41cf681e7..b7faf05680 100644 --- a/src/sdk/bucketspace_fs.js +++ b/src/sdk/bucketspace_fs.js @@ -663,6 +663,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)); @@ -766,6 +774,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 // ///////////////////////// @@ -811,7 +860,8 @@ class BucketSpaceFS extends BucketSpaceSimpleFS { account._id, action, `arn:aws:s3:::${bucket.name.unwrap()}${bucket_path}`, - undefined + undefined, + bucket.public_access_block?.restrict_public_buckets, ); // we (currently) allow account identified to be both id and name, // so if by-id failed, try also name @@ -821,7 +871,8 @@ class BucketSpaceFS extends BucketSpaceSimpleFS { account.name.unwrap(), action, `arn:aws:s3:::${bucket.name.unwrap()}${bucket_path}`, - undefined + undefined, + bucket.public_access_block?.restrict_public_buckets, ); } 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 a124544134..553fc44da0 100644 --- a/src/sdk/nb.d.ts +++ b/src/sdk/nb.d.ts @@ -870,6 +870,10 @@ interface BucketSpace { is_nsfs_containerized_user_anonymous(token: string): boolean; is_nsfs_non_containerized_user_anonymous(token: string): boolean; + + get_public_access_block({ bucket_name }): Promise; + put_public_access_block({ bucket_name , public_access_block }): Promise; + delete_public_access_block({ bucket_name }): Promise; } /********************************************************** diff --git a/src/sdk/object_sdk.js b/src/sdk/object_sdk.js index 031ec858e3..382519182b 100644 --- a/src/sdk/object_sdk.js +++ b/src/sdk/object_sdk.js @@ -1203,6 +1203,24 @@ class ObjectSDK { } } + ////////////////////////// + // PUBLIC ACCESS BLOCK // + ////////////////////////// + + async get_public_access_block(params) { + const bs = this._get_bucketspace(); + return bs.get_public_access_block?.({ bucket_name: params.name }); + } + + async put_public_access_block(params) { + const bs = this._get_bucketspace(); + return bs.put_public_access_block?.({ bucket_name: params.name, public_access_block: params.public_access_block }); + } + + async delete_public_access_block(params) { + const bs = this._get_bucketspace(); + return bs.delete_public_access_block?.({ bucket_name: params.name }); + } } // EXPORT diff --git a/src/server/common_services/auth_server.js b/src/server/common_services/auth_server.js index 93160a1238..b59a9bd63e 100644 --- a/src/server/common_services/auth_server.js +++ b/src/server/common_services/auth_server.js @@ -621,12 +621,6 @@ function _prepare_auth_request(req) { return false; }; - req.check_bucket_action_permission = async function(bucket, action, bucket_path) { - if (!await has_bucket_action_permission(bucket, req.account, action, bucket_path)) { - throw new RpcError('UNAUTHORIZED', 'No permission to access bucket'); - } - }; - req.has_bucket_action_permission = async function(bucket, action, bucket_path) { return has_bucket_action_permission(bucket, req.account, action, bucket_path); }; @@ -690,7 +684,7 @@ async function has_bucket_action_permission(bucket, account, action, bucket_path account.email.unwrap(), action, `arn:aws:s3:::${bucket.name.unwrap()}${bucket_path}`, - undefined + undefined, ); if (result === 'DENY') return false; diff --git a/src/server/system_services/bucket_server.js b/src/server/system_services/bucket_server.js index f4f994d73b..c23ac3e12a 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.bucket_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.bucket_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.bucket_name); + await system_store.make_changes({ + update: { + buckets: [{ + _id: bucket._id, + $unset: { public_access_block: 1 } + }] + } + }); +} + // UTILS ////////////////////////////////////////////////////////// function validate_bucket_creation(req) { @@ -2188,3 +2237,7 @@ exports.put_bucket_replication = put_bucket_replication; exports.get_bucket_replication = get_bucket_replication; exports.delete_bucket_replication = delete_bucket_replication; exports.validate_replication = validate_replication; + +exports.get_public_access_block = get_public_access_block; +exports.put_public_access_block = put_public_access_block; +exports.delete_public_access_block = delete_public_access_block; 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', + } } }; 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', + } } };