diff --git a/config.js b/config.js index 63cc9d58ab..8c743efb8c 100644 --- a/config.js +++ b/config.js @@ -890,6 +890,8 @@ config.NC_MASTER_KEYS_MANAGER_REFRESH_THRESHOLD = -1; // currently we want to di config.MASTER_KEYS_EXEC_MAX_RETRIES = 3; config.NC_DISABLE_ACCESS_CHECK = false; +config.NC_DISABLE_HEALTH_ACCESS_CHECK = false; +config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK = true; config.NC_DISABLE_SCHEMA_CHECK = false; ////////// GPFS ////////// diff --git a/docs/NooBaaNonContainerized/ConfigFileCustomizations.md b/docs/NooBaaNonContainerized/ConfigFileCustomizations.md index 0e363c9319..54598f4057 100644 --- a/docs/NooBaaNonContainerized/ConfigFileCustomizations.md +++ b/docs/NooBaaNonContainerized/ConfigFileCustomizations.md @@ -356,14 +356,16 @@ Warning: After setting this configuration, NooBaa will skip schema validations a ``` -### 25. Disable Read/Write accessibility check - +### 25. Disable Read accessibility check - * Key: `NC_DISABLE_ACCESS_CHECK` * Type: Boolean * Default: false -* Description: This flag will disable Read/Write accessibility validations in the following flows - - 1. Bucket creation/update - NooBaa will not validate that the bucket owner has read/write permissions to the bucket's path. - 2. Account creation/update - NooBaa will not validate that the account owner has read/write permissions to the account's new_buckets_path. - Warning - setting this configuration to true might result with unexpected behavior. +* Description: Setting this flag to true will disable Read accessibility validations in the following flows - + 1. Bucket creation/update - NooBaa will not validate that the bucket owner has read permissions to the bucket's path. + 2. Account creation/update - NooBaa will not validate that the account owner has read permissions to the account's new_buckets_path. + 3. Health buckets and accounts accessibility check. + Warning - setting this configuration to true might result with unexpected behavior. + * Steps: ``` 1. Open /path/to/config_dir/config.json file. @@ -372,6 +374,38 @@ Warning: After setting this configuration, NooBaa will skip schema validations a "NC_DISABLE_ACCESS_CHECK": true ``` +### 26. Disable Read accessibility check on the Health CLI - +* Key: `NC_DISABLE_HEALTH_ACCESS_CHECK` +* Type: Boolean +* Default: false +* Description: This flag will disable Read accessibility validations in Health check of buckets and accounts. + +* Steps: + ``` + 1. Open /path/to/config_dir/config.json file. + 2. Set the config key - + Example: + "NC_DISABLE_HEALTH_ACCESS_CHECK": true + ``` + +### 27. Disable Read/Write POSIX mode bits check - +* Key: `NC_DISABLE_POSIX_MODE_ACCESS_CHECK` +* Type: Boolean +* Default: true +* Description: Setting this flag to false will enable Read/Write mode bits accessibility validations by in the following flows - + 1. Bucket creation/update - NooBaa will validate that the bucket owner has read/write permissions to the bucket's path. + 2. Account creation/update - NooBaa will validate that the account owner has read/write permissions to the account's new_buckets_path. + 3. Health buckets and accounts accessibility check. + Warning - This configuration is disabled by default because it's not supporting ACLs, setting this configuration to false won't support a check of the ACLs and be based only on mode bits check. + +* Steps: + ``` + 1. Open /path/to/config_dir/config.json file. + 2. Set the config key - + Example: + "NC_DISABLE_POSIX_MODE_ACCESS_CHECK": false + ``` + ### 26. Set Endpoint process title - * Key: `ENDPOINT_PROCESS_TITLE` diff --git a/src/manage_nsfs/health.js b/src/manage_nsfs/health.js index be053ee9f3..efd3fb1af1 100644 --- a/src/manage_nsfs/health.js +++ b/src/manage_nsfs/health.js @@ -474,12 +474,14 @@ async function is_new_buckets_path_valid(config_file_path, config_data, new_buck } try { - await nb_native().fs.stat(account_fs_context, new_buckets_path); - const accessible = await native_fs_utils.is_dir_rw_accessible(account_fs_context, new_buckets_path); - if (!accessible) { - const new_err = new Error('ACCESS DENIED'); - new_err.code = 'EACCES'; - throw new_err; + if (!_should_skip_health_access_check()) { + await nb_native().fs.stat(account_fs_context, new_buckets_path); + const accessible = await native_fs_utils.is_dir_accessible(account_fs_context, new_buckets_path); + if (!accessible) { + const new_err = new Error('ACCESS DENIED'); + new_err.code = 'EACCES'; + throw new_err; + } } res_obj = get_valid_object(config_data.name, undefined, new_buckets_path); } catch (err) { @@ -505,7 +507,9 @@ async function is_new_buckets_path_valid(config_file_path, config_data, new_buck async function is_bucket_storage_path_exists(fs_context, config_data, storage_path) { let res_obj; try { - await nb_native().fs.stat(fs_context, storage_path); + if (!_should_skip_health_access_check()) { + await nb_native().fs.stat(fs_context, storage_path); + } res_obj = get_valid_object(config_data.name, undefined, storage_path); } catch (err) { let err_code; @@ -556,5 +560,13 @@ function get_invalid_object(name, config_path, storage_path, err_code) { }; } +/** + * _should_skip_access_check returns true if the health CLI should skip access check + * @returns {Boolean} + */ +function _should_skip_health_access_check() { + return config.NC_DISABLE_HEALTH_ACCESS_CHECK || config.NC_DISABLE_ACCESS_CHECK; +} + exports.get_health_status = get_health_status; exports.NSFSHealth = NSFSHealth; diff --git a/src/manage_nsfs/manage_nsfs_validations.js b/src/manage_nsfs/manage_nsfs_validations.js index 563e62cc39..3f86bc6c0c 100644 --- a/src/manage_nsfs/manage_nsfs_validations.js +++ b/src/manage_nsfs/manage_nsfs_validations.js @@ -359,9 +359,11 @@ async function validate_bucket_args(config_fs, data, action) { await check_new_name_exists(TYPES.BUCKET, config_fs, action, data); // in case we have the fs_backend it changes the fs_context that we use for the path const fs_context_fs_backend = native_fs_utils.get_process_fs_context(data.fs_backend); - const exists = await native_fs_utils.is_path_exists(fs_context_fs_backend, data.path); - if (!exists) { - throw_cli_error(ManageCLIError.InvalidStoragePath, data.path); + if (!config.NC_DISABLE_ACCESS_CHECK) { + const exists = await native_fs_utils.is_path_exists(fs_context_fs_backend, data.path); + if (!exists) { + throw_cli_error(ManageCLIError.InvalidStoragePath, data.path); + } } // bucket owner account validations @@ -369,7 +371,7 @@ async function validate_bucket_args(config_fs, data, action) { const account_fs_context = await native_fs_utils.get_fs_context(owner_account_data.nsfs_account_config, owner_account_data.nsfs_account_config.fs_backend); if (!config.NC_DISABLE_ACCESS_CHECK) { - const accessible = await native_fs_utils.is_dir_rw_accessible(account_fs_context, data.path); + const accessible = await native_fs_utils.is_dir_accessible(account_fs_context, data.path); if (!accessible) { throw_cli_error(ManageCLIError.InaccessibleStoragePath, data.path); } @@ -454,13 +456,15 @@ async function validate_account_args(config_fs, data, action, is_flag_iam_operat } // in case we have the fs_backend it changes the fs_context that we use for the new_buckets_path const fs_context_fs_backend = native_fs_utils.get_process_fs_context(data.fs_backend); - const exists = await native_fs_utils.is_path_exists(fs_context_fs_backend, data.nsfs_account_config.new_buckets_path); - if (!exists) { - throw_cli_error(ManageCLIError.InvalidAccountNewBucketsPath, data.nsfs_account_config.new_buckets_path); + if (!config.NC_DISABLE_ACCESS_CHECK) { + const exists = await native_fs_utils.is_path_exists(fs_context_fs_backend, data.nsfs_account_config.new_buckets_path); + if (!exists) { + throw_cli_error(ManageCLIError.InvalidAccountNewBucketsPath, data.nsfs_account_config.new_buckets_path); + } } if (!config.NC_DISABLE_ACCESS_CHECK) { const account_fs_context = await native_fs_utils.get_fs_context(data.nsfs_account_config, data.fs_backend); - const accessible = await native_fs_utils.is_dir_rw_accessible(account_fs_context, data.nsfs_account_config.new_buckets_path); + const accessible = await native_fs_utils.is_dir_accessible(account_fs_context, data.nsfs_account_config.new_buckets_path); if (!accessible) { throw_cli_error(ManageCLIError.InaccessibleAccountNewBucketsPath, data.nsfs_account_config.new_buckets_path); } diff --git a/src/sdk/config_fs.js b/src/sdk/config_fs.js index ee3901be80..adb3dff848 100644 --- a/src/sdk/config_fs.js +++ b/src/sdk/config_fs.js @@ -161,7 +161,7 @@ class ConfigFS { } /** - * create_config_json_file created the config.json file with the configuration data + * create_config_json_file creates the config.json file with the configuration data * @param {object} data * @returns {Promise} */ @@ -178,6 +178,13 @@ class ConfigFS { await native_fs_utils.update_config_file(this.fs_context, this.config_root, this.config_json_path, data); } + /** + * delete_config_json_file deletes the config.json file + * @returns {Promise} + */ + async delete_config_json_file() { + await native_fs_utils.delete_config_file(this.fs_context, this.config_root, this.config_json_path); + } /** * get_config_data reads a config file and returns its content diff --git a/src/server/system_services/schemas/nsfs_config_schema.js b/src/server/system_services/schemas/nsfs_config_schema.js index aaaa9ab7fa..a0613685e8 100644 --- a/src/server/system_services/schemas/nsfs_config_schema.js +++ b/src/server/system_services/schemas/nsfs_config_schema.js @@ -124,7 +124,15 @@ const nsfs_node_config_schema = { }, NC_DISABLE_ACCESS_CHECK: { type: 'boolean', - doc: 'indicate whether read/write access will be validated on bucket/account creation/update.' + doc: 'indicate whether read access will be validated on bucket/account creation/update and on the health check.' + }, + NC_DISABLE_HEALTH_ACCESS_CHECK: { + type: 'boolean', + doc: 'indicate whether read access will be validated on bucket/account health check.' + }, + NC_DISABLE_POSIX_MODE_ACCESS_CHECK: { + type: 'boolean', + doc: 'indicate whether posix mode read/write access will be validated on bucket/account creation/update and health check.' }, ENDPOINT_PROCESS_TITLE: { type: 'string', diff --git a/src/test/unit_tests/jest_tests/test_nc_nsfs_account_cli.test.js b/src/test/unit_tests/jest_tests/test_nc_nsfs_account_cli.test.js index 179d5ea8b8..4ddeaf30c2 100644 --- a/src/test/unit_tests/jest_tests/test_nc_nsfs_account_cli.test.js +++ b/src/test/unit_tests/jest_tests/test_nc_nsfs_account_cli.test.js @@ -1773,7 +1773,9 @@ describe('manage nsfs cli account flow', () => { describe('cli account flow distinguished_name - permissions', function() { const type = TYPES.ACCOUNT; const config_root = path.join(tmp_fs_path, 'config_root_manage_dn'); + const config_fs = new ConfigFS(config_root); const new_buckets_path = path.join(tmp_fs_path, 'new_buckets_path_user_dn_test/'); + const accounts = { root: { cli_options: { @@ -1903,32 +1905,34 @@ describe('cli account flow distinguished_name - permissions', function() { }, timeout); it('cli account update - should fail - no permissions to new_buckets_path', async function() { - const no_permissions_new_buckets_path = `${tmp_fs_path}/new_buckets_path_no_perm_to_owner/`; + const no_permissions_new_buckets_path = `${tmp_fs_path}/new_buckets_path_no_perm_to_owner1/`; await fs_utils.create_fresh_path(no_permissions_new_buckets_path); await fs_utils.file_must_exist(no_permissions_new_buckets_path); - await set_path_permissions_and_owner(new_buckets_path, accounts.accessible_user.fs_options, 0o077); + await set_path_permissions_and_owner(no_permissions_new_buckets_path, accounts.accessible_user.fs_options, 0o000); const action = ACTIONS.UPDATE; const update_options = { config_root, - name: accounts.root.cli_options.name, + ...accounts.accessible_user.cli_options, new_buckets_path: no_permissions_new_buckets_path, }; const res = await exec_manage_cli(type, action, update_options); expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleAccountNewBucketsPath.code); }, timeout); - it('cli account update - should fail - no write permissions of new_buckets_path', async function() { + it('cli account update - should fail - posix mode write permissions of new_buckets_path', async function() { const no_permissions_new_buckets_path = `${tmp_fs_path}/new_buckets_path_no_r_perm_to_owner/`; await fs_utils.create_fresh_path(no_permissions_new_buckets_path); await fs_utils.file_must_exist(no_permissions_new_buckets_path); - await set_path_permissions_and_owner(new_buckets_path, accounts.accessible_user.fs_options, 0o477); + await set_path_permissions_and_owner(no_permissions_new_buckets_path, accounts.accessible_user.fs_options, 0o477); const action = ACTIONS.UPDATE; const update_options = { config_root, - name: accounts.root.cli_options.name, + ...accounts.accessible_user.cli_options, new_buckets_path: no_permissions_new_buckets_path, }; + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_POSIX_MODE_ACCESS_CHECK: false })); const res = await exec_manage_cli(type, action, update_options); + await config_fs.delete_config_json_file(); expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleAccountNewBucketsPath.code); }, timeout); @@ -1936,23 +1940,42 @@ describe('cli account flow distinguished_name - permissions', function() { const no_permissions_new_buckets_path = `${tmp_fs_path}/new_buckets_path_no_w_perm_to_owner/`; await fs_utils.create_fresh_path(no_permissions_new_buckets_path); await fs_utils.file_must_exist(no_permissions_new_buckets_path); - await set_path_permissions_and_owner(new_buckets_path, accounts.accessible_user.fs_options, 0o277); + await set_path_permissions_and_owner(no_permissions_new_buckets_path, accounts.accessible_user.fs_options, 0o000); const action = ACTIONS.UPDATE; const update_options = { config_root, - name: accounts.root.cli_options.name, + ...accounts.accessible_user.cli_options, new_buckets_path: no_permissions_new_buckets_path, }; const res = await exec_manage_cli(type, action, update_options); expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleAccountNewBucketsPath.code); }); + it('cli account update - should succeed - no read permissions of new_buckets_path - NC_DISABLE_ACCESS_CHECK: true', async function() { + const no_permissions_new_buckets_path = `${tmp_fs_path}/new_buckets_path_no_w_perm_to_owner/`; + await fs_utils.create_fresh_path(no_permissions_new_buckets_path); + await fs_utils.file_must_exist(no_permissions_new_buckets_path); + + await set_path_permissions_and_owner(no_permissions_new_buckets_path, accounts.accessible_user.fs_options, 0o000); + const action = ACTIONS.UPDATE; + const update_options = { + config_root, + ...accounts.accessible_user.cli_options, + new_buckets_path: no_permissions_new_buckets_path, + }; + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + const res = await exec_manage_cli(type, action, update_options); + await config_fs.delete_config_json_file(); + assert_account(JSON.parse(res).response.reply, update_options, false); + }); + it('cli account create - account cant access new_bucket_path - NC_DISABLE_ACCESS_CHECK = true', async function() { let action = ACTIONS.ADD; config.NC_DISABLE_ACCESS_CHECK = true; set_nc_config_dir_in_config(config_root); - await fs.promises.writeFile(path.join(config_root, 'config.json'), JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); const res = await exec_manage_cli(type, action, accounts.inaccessible_user.cli_options); + await config_fs.delete_config_json_file(); expect(JSON.parse(res).response.code).toEqual(ManageCLIResponse.AccountCreated.code); assert_account(JSON.parse(res).response.reply, { ...accounts.inaccessible_user.cli_options }, false); action = ACTIONS.DELETE; diff --git a/src/test/unit_tests/jest_tests/test_nc_nsfs_bucket_cli.test.js b/src/test/unit_tests/jest_tests/test_nc_nsfs_bucket_cli.test.js index 024cb1b719..1a4cfb0818 100644 --- a/src/test/unit_tests/jest_tests/test_nc_nsfs_bucket_cli.test.js +++ b/src/test/unit_tests/jest_tests/test_nc_nsfs_bucket_cli.test.js @@ -113,13 +113,15 @@ describe('manage nsfs cli bucket flow', () => { expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleStoragePath.code); }); - it('should fail - cli create bucket - owner does not have write permission to path', async () => { + it('should fail - cli create bucket - owner does not have posix write permission to path', async () => { const action = ACTIONS.ADD; const bucket_options = { config_root, ...bucket_defaults}; await fs_utils.create_fresh_path(bucket_options.path); await fs_utils.file_must_exist(bucket_options.path); await set_path_permissions_and_owner(bucket_options.path, account_defaults, 0o477); + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_POSIX_MODE_ACCESS_CHECK: false })); const res = await exec_manage_cli(TYPES.BUCKET, action, bucket_options); + await config_fs.delete_config_json_file(); expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleStoragePath.code); }); @@ -150,9 +152,10 @@ describe('manage nsfs cli bucket flow', () => { await fs_utils.create_fresh_path(bucket_options.path); await fs_utils.file_must_exist(bucket_options.path); set_nc_config_dir_in_config(config_root); - await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); await set_path_permissions_and_owner(bucket_options.path, account_defaults, 0o000); + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); const res = await exec_manage_cli(TYPES.BUCKET, action, bucket_options); + await config_fs.delete_config_json_file(); expect(JSON.parse(res).response.code).toEqual(ManageCLIResponse.BucketCreated.code); }); @@ -532,7 +535,9 @@ describe('manage nsfs cli bucket flow', () => { await fs_utils.create_fresh_path(bucket_defaults.path); await fs_utils.file_must_exist(bucket_defaults.path); await set_path_permissions_and_owner(bucket_defaults.path, account_defaults2, 0o477); + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_POSIX_MODE_ACCESS_CHECK: false })); const res = await exec_manage_cli(TYPES.BUCKET, action, bucket_options); + await config_fs.delete_config_json_file(); expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleStoragePath.code); }); @@ -546,6 +551,18 @@ describe('manage nsfs cli bucket flow', () => { expect(JSON.parse(res.stdout).error.code).toBe(ManageCLIError.InaccessibleStoragePath.code); }); + it('cli update bucket owner - owner does not have read permission to path - NC_DISABLE_ACCESS_CHECK: true', async () => { + const action = ACTIONS.UPDATE; + const bucket_options = { config_root, name: bucket_defaults.name, owner: account_defaults2.name}; + await fs_utils.create_fresh_path(bucket_defaults.path); + await fs_utils.file_must_exist(bucket_defaults.path); + await set_path_permissions_and_owner(bucket_defaults.path, account_defaults2, 0o000); + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + const res = await exec_manage_cli(TYPES.BUCKET, action, bucket_options); + await config_fs.delete_config_json_file(); + expect(JSON.parse(res).response.code).toEqual(ManageCLIResponse.BucketUpdated.code); + }); + it('cli update bucket owner - account can access path', async () => { const action = ACTIONS.UPDATE; const bucket_options = { config_root, name: bucket_defaults.name, owner: account_defaults2.name}; diff --git a/src/test/unit_tests/jest_tests/test_nc_nsfs_config_schema_validation.test.js b/src/test/unit_tests/jest_tests/test_nc_nsfs_config_schema_validation.test.js index e0e8c3fffe..3e6916399c 100644 --- a/src/test/unit_tests/jest_tests/test_nc_nsfs_config_schema_validation.test.js +++ b/src/test/unit_tests/jest_tests/test_nc_nsfs_config_schema_validation.test.js @@ -180,6 +180,47 @@ describe('schema validation NC NSFS config', () => { assert_validation(config_data, reason, message); }); + it('nsfs_config valid config disable access check', () => { + const config_data = { NC_DISABLE_ACCESS_CHECK: true }; + nsfs_schema_utils.validate_nsfs_config_schema(config_data); + }); + + it('nsfs_config invalid config disable access check', () => { + const config_data = { NC_DISABLE_ACCESS_CHECK: 'bla' }; // string instead of boolean + const reason = 'Test should have failed because of wrong type ' + + 'NC_DISABLE_ACCESS_CHECK with string (instead of boolean)'; + const message = 'must be boolean'; + assert_validation(config_data, reason, message); + }); + + it('nsfs_config valid config disable health access check', () => { + const config_data = { NC_DISABLE_HEALTH_ACCESS_CHECK: true }; + nsfs_schema_utils.validate_nsfs_config_schema(config_data); + }); + + it('nsfs_config invalid config disable health access check', () => { + const config_data = { NC_DISABLE_HEALTH_ACCESS_CHECK: 'bla' }; // string instead of boolean + const reason = 'Test should have failed because of wrong type ' + + 'NC_DISABLE_HEALTH_ACCESS_CHECK with string (instead of boolean)'; + const message = 'must be boolean'; + assert_validation(config_data, reason, message); + }); + + it('nsfs_config valid config disable posix access check', () => { + const config_data = { NC_DISABLE_POSIX_MODE_ACCESS_CHECK: false }; + nsfs_schema_utils.validate_nsfs_config_schema(config_data); + }); + + it('nsfs_config invalid config disable posix access check', () => { + const config_data = { + NC_DISABLE_POSIX_MODE_ACCESS_CHECK: 'bla'// string instead of boolean + }; + const reason = 'Test should have failed because of wrong type ' + + 'NC_DISABLE_POSIX_MODE_ACCESS_CHECK with string (instead of boolean)'; + const message = 'must be boolean'; + assert_validation(config_data, reason, message); + }); + it('nsfs_config valid config virtual hosts', () => { const config_data = { VIRTUAL_HOSTS: 'my.virtual.domain1 my.virtual.domain2' diff --git a/src/test/unit_tests/jest_tests/test_nc_nsfs_new_buckets_path_validation.test.js b/src/test/unit_tests/jest_tests/test_nc_nsfs_new_buckets_path_validation.test.js index 50f5caecfd..01013beb45 100644 --- a/src/test/unit_tests/jest_tests/test_nc_nsfs_new_buckets_path_validation.test.js +++ b/src/test/unit_tests/jest_tests/test_nc_nsfs_new_buckets_path_validation.test.js @@ -6,9 +6,10 @@ process.env.DISABLE_INIT_RANDOM_SEED = "true"; const fs = require('fs'); const path = require('path'); +const config = require('../../../../config'); const fs_utils = require('../../../util/fs_utils'); const test_utils = require('../../system_tests/test_utils'); -const { get_fs_context, is_dir_rw_accessible } = require('../../../util/native_fs_utils'); +const { get_fs_context, is_dir_accessible } = require('../../../util/native_fs_utils'); const MAC_PLATFORM = 'darwin'; let tmp_fs_path = '/tmp/test_nc_nsfs_new_buckets_path_validation'; @@ -17,8 +18,9 @@ if (process.platform === MAC_PLATFORM) { } const timeout = 50000; -describe('new_buckets_path access validation account', () => { +describe('new_buckets_path posix mode access validation account', () => { const new_buckets_path = path.join(tmp_fs_path, 'new_buckets_path'); + config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK = false; const owner_user = 'owner_user'; const group_user = 'group_user'; const other_user = 'other_user'; @@ -237,7 +239,7 @@ async function set_path_permissions(path_to_change, new_path_mode) { */ async function path_accessible_to_account(path_to_check, account_data, is_accessible) { const fs_context = await get_fs_context(account_data); - const accessible = await is_dir_rw_accessible(fs_context, path_to_check); + const accessible = await is_dir_accessible(fs_context, path_to_check); if (is_accessible) { expect(accessible).toBe(true); } else { diff --git a/src/test/unit_tests/test_nc_nsfs_health.js b/src/test/unit_tests/test_nc_nsfs_health.js index 9c6aca62d9..a708e515c8 100644 --- a/src/test/unit_tests/test_nc_nsfs_health.js +++ b/src/test/unit_tests/test_nc_nsfs_health.js @@ -7,6 +7,7 @@ const path = require('path'); const mocha = require('mocha'); const sinon = require('sinon'); const assert = require('assert'); +const config = require('../../../config'); const fs_utils = require('../../util/fs_utils'); const nb_native = require('../../util/nb_native'); const { ConfigFS } = require('../../sdk/config_fs'); @@ -326,7 +327,8 @@ mocha.describe('nsfs nc health', function() { mocha.it('Account with inaccessible path - uid gid', async function() { await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); - await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_options}); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_options }); + await config_fs.delete_config_json_file(); Health.get_service_state.restore(); Health.get_endpoint_response.restore(); Health.all_account_details = true; @@ -345,6 +347,56 @@ mocha.describe('nsfs nc health', function() { await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_options.name}); }); + mocha.it('Account with inaccessible path - uid gid - NC_DISABLE_ACCESS_CHECK: true - should be valid', async function() { + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, debug: 5, ...account_inaccessible_options}); + await config_fs.delete_config_json_file(); + Health.get_service_state.restore(); + Health.get_endpoint_response.restore(); + Health.all_account_details = true; + Health.all_bucket_details = true; + const get_service_state = sinon.stub(Health, "get_service_state"); + get_service_state.onFirstCall().returns(Promise.resolve({ service_status: 'active', pid: 1000 })) + .onSecondCall().returns(Promise.resolve({ service_status: 'active', pid: 2000 })); + const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); + get_endpoint_response.onFirstCall().returns(Promise.resolve({response: {response_code: 'RUNNING', total_fork_count: 0}})); + config.NC_DISABLE_ACCESS_CHECK = true; + const health_status = await Health.nc_nsfs_health(); + config.NC_DISABLE_ACCESS_CHECK = false; + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_options.name}); + assert.strictEqual(health_status.checks.buckets_status.valid_buckets.length, 1); + assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 2); + assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 0); + const inaccessible_is_valid = health_status.checks.accounts_status.valid_accounts.find(account => + account.name === account_inaccessible_options.name); + assert.strictEqual(inaccessible_is_valid.storage_path, account_inaccessible_options.new_buckets_path); + }); + + mocha.it('Account with inaccessible path - uid gid - NC_DISABLE_HEALTH_ACCESS_CHECK: true - should be valid', async function() { + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_options }); + await config_fs.delete_config_json_file(); + Health.get_service_state.restore(); + Health.get_endpoint_response.restore(); + Health.all_account_details = true; + Health.all_bucket_details = true; + const get_service_state = sinon.stub(Health, "get_service_state"); + get_service_state.onFirstCall().returns(Promise.resolve({ service_status: 'active', pid: 1000 })) + .onSecondCall().returns(Promise.resolve({ service_status: 'active', pid: 2000 })); + const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); + get_endpoint_response.onFirstCall().returns(Promise.resolve({response: {response_code: 'RUNNING', total_fork_count: 0}})); + config.NC_DISABLE_HEALTH_ACCESS_CHECK = true; + const health_status = await Health.nc_nsfs_health(); + config.NC_DISABLE_HEALTH_ACCESS_CHECK = false; + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_options.name }); + assert.strictEqual(health_status.checks.buckets_status.valid_buckets.length, 1); + assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 2); + assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 0); + const inaccessible_is_valid = health_status.checks.accounts_status.valid_accounts.find(account => + account.name === account_inaccessible_options.name); + assert.strictEqual(inaccessible_is_valid.storage_path, account_inaccessible_options.new_buckets_path); + }); + mocha.it('Account with inaccessible path - dn', async function() { await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_dn_options }); @@ -358,12 +410,60 @@ mocha.describe('nsfs nc health', function() { const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); get_endpoint_response.onFirstCall().returns(Promise.resolve({response: {response_code: 'RUNNING', total_fork_count: 0}})); const health_status = await Health.nc_nsfs_health(); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_dn_options.name }); assert.strictEqual(health_status.checks.buckets_status.valid_buckets.length, 1); assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 1); assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 1); assert.strictEqual(health_status.checks.accounts_status.invalid_accounts[0].code, "ACCESS_DENIED"); assert.strictEqual(health_status.checks.accounts_status.invalid_accounts[0].name, account_inaccessible_dn_options.name); + }); + + mocha.it('Account with inaccessible path - dn - NC_DISABLE_ACCESS_CHECK: true - should be valid', async function() { + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_dn_options }); + Health.get_service_state.restore(); + Health.get_endpoint_response.restore(); + Health.all_account_details = true; + Health.all_bucket_details = true; + const get_service_state = sinon.stub(Health, "get_service_state"); + get_service_state.onFirstCall().returns(Promise.resolve({ service_status: 'active', pid: 1000 })) + .onSecondCall().returns(Promise.resolve({ service_status: 'active', pid: 2000 })); + const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); + get_endpoint_response.onFirstCall().returns(Promise.resolve({ response: { response_code: 'RUNNING', total_fork_count: 0 } })); + config.NC_DISABLE_ACCESS_CHECK = true; + const health_status = await Health.nc_nsfs_health(); + config.NC_DISABLE_ACCESS_CHECK = false; + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_dn_options.name }); + assert.strictEqual(health_status.checks.buckets_status.valid_buckets.length, 1); + assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 2); + assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 0); + const inaccessible_is_valid = health_status.checks.accounts_status.valid_accounts.find(account => + account.name === account_inaccessible_dn_options.name); + assert.strictEqual(inaccessible_is_valid.storage_path, account_inaccessible_dn_options.new_buckets_path); + }); + + mocha.it('Account with inaccessible path - dn - NC_DISABLE_HEALTH_ACCESS_CHECK: true - should be valid', async function() { + await config_fs.create_config_json_file(JSON.stringify({ NC_DISABLE_ACCESS_CHECK: true })); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, { config_root, ...account_inaccessible_dn_options }); + Health.get_service_state.restore(); + Health.get_endpoint_response.restore(); + Health.all_account_details = true; + Health.all_bucket_details = true; + const get_service_state = sinon.stub(Health, "get_service_state"); + get_service_state.onFirstCall().returns(Promise.resolve({ service_status: 'active', pid: 1000 })) + .onSecondCall().returns(Promise.resolve({ service_status: 'active', pid: 2000 })); + const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); + get_endpoint_response.onFirstCall().returns(Promise.resolve({ response: { response_code: 'RUNNING', total_fork_count: 0 } })); + config.NC_DISABLE_HEALTH_ACCESS_CHECK = true; + const health_status = await Health.nc_nsfs_health(); + config.NC_DISABLE_HEALTH_ACCESS_CHECK = false; await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_inaccessible_dn_options.name }); + assert.strictEqual(health_status.checks.buckets_status.valid_buckets.length, 1); + assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 2); + assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 0); + const inaccessible_is_valid = health_status.checks.accounts_status.valid_accounts.find(account => + account.name === account_inaccessible_dn_options.name); + assert.strictEqual(inaccessible_is_valid.storage_path, account_inaccessible_dn_options.new_buckets_path); }); mocha.it('Account with invalid dn', async function() { @@ -402,10 +502,10 @@ mocha.describe('nsfs nc health', function() { const get_endpoint_response = sinon.stub(Health, "get_endpoint_response"); get_endpoint_response.onFirstCall().returns(Promise.resolve({response: {response_code: 'RUNNING', total_fork_count: 0}})); const health_status = await Health.nc_nsfs_health(); + await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_valid.name }); assert.strictEqual(health_status.checks.accounts_status.valid_accounts.length, 1); assert.strictEqual(health_status.checks.accounts_status.invalid_accounts.length, 1); assert.strictEqual(health_status.checks.accounts_status.invalid_accounts[0].name, account_valid.name); - await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.DELETE, { config_root, name: account_valid.name }); }); mocha.it('Account with new_buckets_path missing and allow_bucket_creation true, invalid account', async function() { diff --git a/src/util/native_fs_utils.js b/src/util/native_fs_utils.js index 547c72657f..81ad1be49e 100644 --- a/src/util/native_fs_utils.js +++ b/src/util/native_fs_utils.js @@ -535,19 +535,24 @@ async function is_path_exists(fs_context, config_path, use_lstat = false) { } /** - * is_dir_rw_accessible validate the dir param accessible for read and write + * is_dir_accessible validate the dir param accessible for read by default + * if NC_DISABLE_POSIX_MODE_ACCESS_CHECK=false a read and write access check + * will be executed by checking mode bits * @param {nb.NativeFSContext} fs_context * @param {string} dir_path * @returns {Promise} */ /* eslint-disable no-bitwise */ -async function is_dir_rw_accessible(fs_context, dir_path) { +async function is_dir_accessible(fs_context, dir_path) { let stat; try { stat = await nb_native().fs.stat(fs_context, dir_path); } catch (err) { return false; } + + if (config.NC_DISABLE_POSIX_MODE_ACCESS_CHECK) return true; + const is_owner = fs_context.uid === stat.uid; const is_group = fs_context.gid === stat.gid; @@ -686,7 +691,7 @@ exports.get_process_fs_context = get_process_fs_context; exports.get_fs_context = get_fs_context; exports.validate_bucket_creation = validate_bucket_creation; exports.is_path_exists = is_path_exists; -exports.is_dir_rw_accessible = is_dir_rw_accessible; +exports.is_dir_accessible = is_dir_accessible; exports.folder_delete = folder_delete; exports.unlink_ignore_enoent = unlink_ignore_enoent; exports.get_bucket_tmpdir_full_path = get_bucket_tmpdir_full_path;