Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -1018,6 +1018,9 @@ config.NSFS_LIST_IGNORE_ENTRY_ON_EACCES = true;
// we will for now handle the same way also EINVAL error - for gpfs stat issues on list (.snapshots)
config.NSFS_LIST_IGNORE_ENTRY_ON_EINVAL = true;

config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER = 'x-noobaa-custom-bucket-path';
config.NSFS_CUSTOM_BUCKET_PATH_ALLOWED_LIST = ''; // colon separated list of paths prefixes

////////////////////////////
// NSFS NON CONTAINERIZED //
////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions src/api/account_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ module.exports = {
supplemental_groups: {
$ref: 'common_api#/definitions/supplemental_groups'
},
custom_bucket_path_allowed_list: { type: 'string' },
}
},
},
Expand Down
1 change: 1 addition & 0 deletions src/api/bucket_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module.exports = {
},
bucket_claim: { $ref: '#/definitions/bucket_claim' },
force_md5_etag: { type: 'boolean' },
custom_bucket_path: { type: 'string' }
}
},
reply: {
Expand Down
4 changes: 3 additions & 1 deletion src/api/common_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1454,14 +1454,16 @@ module.exports = {
supplemental_groups: {
$ref: '#/definitions/supplemental_groups'
},
custom_bucket_path_allowed_list: { type: 'string' },
}
}, {
type: 'object',
required: ['distinguished_name', 'new_buckets_path', 'nsfs_only'],
properties: {
distinguished_name: { wrapper: SensitiveString },
new_buckets_path: { type: 'string' },
nsfs_only: { type: 'boolean' }
nsfs_only: { type: 'boolean' },
custom_bucket_path_allowed_list: { type: 'string' },
}
}]
},
Expand Down
5 changes: 4 additions & 1 deletion src/cmd/manage_nsfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,8 @@ async function fetch_account_data(action, user_input) {
uid: user_input.user ? undefined : user_input.uid,
gid: user_input.user ? undefined : user_input.gid,
new_buckets_path: user_input.new_buckets_path,
fs_backend: user_input.fs_backend ? String(user_input.fs_backend) : config.NSFS_NC_STORAGE_BACKEND
fs_backend: user_input.fs_backend ? String(user_input.fs_backend) : config.NSFS_NC_STORAGE_BACKEND,
custom_bucket_path_allowed_list: user_input.custom_bucket_path_allowed_list,
},
default_connection: user_input.default_connection === undefined ? undefined : String(user_input.default_connection)
};
Expand Down Expand Up @@ -542,6 +543,8 @@ async function fetch_account_data(action, user_input) {
} else { // string of true or false
data.allow_bucket_creation = user_input.allow_bucket_creation.toLowerCase() === 'true';
}
// custom_bucket_path_allowed_list deletion specified with empty string ''
data.nsfs_account_config.custom_bucket_path_allowed_list = data.nsfs_account_config.custom_bucket_path_allowed_list || undefined;

return data;
}
Expand Down
3 changes: 2 additions & 1 deletion src/endpoint/s3/ops/s3_put_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const config = require('../../../../config');
async function put_bucket(req, res) {
const lock_enabled = config.WORM_ENABLED ? req.headers['x-amz-bucket-object-lock-enabled'] &&
req.headers['x-amz-bucket-object-lock-enabled'].toUpperCase() === 'TRUE' : undefined;
await req.object_sdk.create_bucket({ name: req.params.bucket, lock_enabled: lock_enabled });
const custom_bucket_path = req.headers[config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER];
await req.object_sdk.create_bucket({ name: req.params.bucket, lock_enabled, custom_bucket_path });
if (config.allow_anonymous_access_in_test && req.headers['x-amz-acl'] === 'public-read') { // For now we will enable only for tests
const policy = {
Version: '2012-10-17',
Expand Down
6 changes: 4 additions & 2 deletions src/manage_nsfs/manage_nsfs_constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const FROM_FILE = 'from_file';
const ANONYMOUS = 'anonymous';

const VALID_OPTIONS_ACCOUNT = {
'add': new Set(['name', 'uid', 'gid', 'supplemental_groups', 'new_buckets_path', 'user', 'access_key', 'secret_key', 'fs_backend', 'allow_bucket_creation', 'force_md5_etag', 'iam_operate_on_root_account', 'default_connection', FROM_FILE, ...CLI_MUTUAL_OPTIONS]),
'update': new Set(['name', 'uid', 'gid', 'supplemental_groups', 'new_buckets_path', 'user', 'access_key', 'secret_key', 'fs_backend', 'allow_bucket_creation', 'force_md5_etag', 'iam_operate_on_root_account', 'new_name', 'regenerate', 'default_connection', ...CLI_MUTUAL_OPTIONS]),
'add': new Set(['name', 'uid', 'gid', 'supplemental_groups', 'new_buckets_path', 'custom_bucket_path_allowed_list', 'user', 'access_key', 'secret_key', 'fs_backend', 'allow_bucket_creation', 'force_md5_etag', 'iam_operate_on_root_account', 'default_connection', FROM_FILE, ...CLI_MUTUAL_OPTIONS]),
'update': new Set(['name', 'uid', 'gid', 'supplemental_groups', 'new_buckets_path', 'custom_bucket_path_allowed_list', 'user', 'access_key', 'secret_key', 'fs_backend', 'allow_bucket_creation', 'force_md5_etag', 'iam_operate_on_root_account', 'new_name', 'regenerate', 'default_connection', ...CLI_MUTUAL_OPTIONS]),
'delete': new Set(['name', ...CLI_MUTUAL_OPTIONS]),
'list': new Set(['wide', 'show_secrets', 'gid', 'uid', 'user', 'name', 'access_key', ...CLI_MUTUAL_OPTIONS]),
'status': new Set(['name', 'access_key', 'show_secrets', ...CLI_MUTUAL_OPTIONS]),
Expand Down Expand Up @@ -123,6 +123,7 @@ const OPTION_TYPE = {
gid: 'number',
supplemental_groups: 'string',
new_buckets_path: 'string',
custom_bucket_path_allowed_list: 'string',
user: 'string',
access_key: 'string',
secret_key: 'string',
Expand Down Expand Up @@ -196,6 +197,7 @@ const UNSETTABLE_OPTIONS_OBJ = Object.freeze({
'force_md5_etag': CLI_EMPTY_STRING,
'supplemental_groups': CLI_EMPTY_STRING,
'new_buckets_path': CLI_EMPTY_STRING,
'custom_bucket_path_allowed_list': CLI_EMPTY_STRING,
'ips': CLI_EMPTY_STRING_ARRAY,
});

Expand Down
2 changes: 2 additions & 0 deletions src/manage_nsfs/manage_nsfs_help_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Flags:
--force_md5_etag <true | false> (optional) Set the account to force md5 etag calculation. (unset with '') (will override default config.NSFS_NC_STORAGE_BACKEND)
--iam_operate_on_root_account <true | false> (optional) Set the account to create root accounts instead of IAM users in IAM API requests.
--from_file <string> (optional) Use details from the JSON file, there is no need to mention all the properties individually in the CLI
--custom_bucket_path_allowed_list <string> (optional) Set the list of allowed custom bucket paths, separated by colons (:) example: '/gpfs/data/custom1/:/gpfs/data/custom2/'
`;

const ACCOUNT_FLAGS_UPDATE = `
Expand Down Expand Up @@ -170,6 +171,7 @@ Flags:
--allow_bucket_creation <true | false> (optional) Update the account to explicitly allow or block bucket creation
--force_md5_etag <true | false> (optional) Update the account to force md5 etag calculation (unset with '') (will override default config.NSFS_NC_STORAGE_BACKEND)
--iam_operate_on_root_account <true | false> (optional) Update the account to create root accounts instead of IAM users in IAM API requests.
--custom_bucket_path_allowed_list <string> (optional) Update the list of allowed custom bucket paths, separated by colons (:) example: '/gpfs/data/custom1/:/gpfs/data/custom2/' (override;unset with '')
`;

const ACCOUNT_FLAGS_DELETE = `
Expand Down
1 change: 1 addition & 0 deletions src/sdk/accountspace_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ class AccountSpaceFS {
supplemental_groups: requesting_account.nsfs_account_config.supplemental_groups,
new_buckets_path: requesting_account.nsfs_account_config.new_buckets_path,
fs_backend: requesting_account.nsfs_account_config.fs_backend,
custom_bucket_path_allowed_list: requesting_account.nsfs_account_config.custom_bucket_path_allowed_list,
}
};
if (requesting_account.iam_operate_on_root_account) {
Expand Down
14 changes: 12 additions & 2 deletions src/sdk/bucketspace_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,9 +303,19 @@ class BucketSpaceFS extends BucketSpaceSimpleFS {
const fs_context = this.prepare_fs_context(sdk);
validate_bucket_creation(params);

const { name } = params;
const { name, custom_bucket_path } = params;
const bucket_config_path = this.config_fs.get_bucket_path_by_name(name);
const bucket_storage_path = path.join(sdk.requesting_account.nsfs_account_config.new_buckets_path, name);
if (custom_bucket_path) {
const allowed_list = sdk.requesting_account.nsfs_account_config.custom_bucket_path_allowed_list ||
config.NSFS_CUSTOM_BUCKET_PATH_ALLOWED_LIST;
const allowed_path_prefixes = allowed_list ? allowed_list.split(':').map(p => p.trim()).filter(p => p) : [];
if (!allowed_path_prefixes.length || !allowed_path_prefixes.some(prefix => custom_bucket_path.startsWith(prefix))) {
const message = `Not allowed to create new buckets: ${custom_bucket_path} outside of the custom_bucket_path_allowed_list: ${allowed_list}`;
dbg.error(`BucketSpaceFS.create_bucket: ${message}`);
throw new RpcError('UNAUTHORIZED', message);
}
}
const bucket_storage_path = custom_bucket_path || path.join(sdk.requesting_account.nsfs_account_config.new_buckets_path, name);

dbg.log0(`BucketSpaceFS.create_bucket
requesting_account=${util.inspect(sdk.requesting_account)},
Expand Down
6 changes: 4 additions & 2 deletions src/server/system_services/schemas/nsfs_account_schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ module.exports = {
new_buckets_path: { type: 'string' },
fs_backend: {
$ref: 'common_api#/definitions/fs_backend'
}
},
custom_bucket_path_allowed_list: { type: 'string' },
}
}, {
type: 'object',
Expand All @@ -97,7 +98,8 @@ module.exports = {
new_buckets_path: { type: 'string' },
fs_backend: {
$ref: 'common_api#/definitions/fs_backend'
}
},
custom_bucket_path_allowed_list: { type: 'string' },
}
}]
},
Expand Down
120 changes: 119 additions & 1 deletion src/test/integration_tests/nsfs/test_nsfs_integration.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* Copyright (C) 2020 NooBaa */
/*eslint max-lines: ["error", 2500]*/
/*eslint max-lines: ["error", 3000]*/
/*eslint max-lines-per-function: ["error", 1300]*/
/*eslint max-statements: ["error", 80, { "ignoreTopLevelFunctions": true }]*/
'use strict';
Expand Down Expand Up @@ -87,6 +87,7 @@ mocha.describe('bucket operations - namespace_fs', function() {
let s3_wrong_uid;
let s3_correct_uid;
let s3_correct_uid_default_nsr;
let s3_correct_uid_bucket_path;
let account_no_perm_dn;
let s3_correct_dn_default_nsr;
const no_permissions_dn = 'no_permissions_dn';
Expand Down Expand Up @@ -389,6 +390,123 @@ mocha.describe('bucket operations - namespace_fs', function() {
await fs_utils.file_must_exist(path.join(s3_new_buckets_path, bucket_name + '-s3'));
});

mocha.it('create s3 bucket with x-noobaa-custom-bucket-path no custom-bucket-path-allowed_list - should fail', async function() {
// only NC supports creating buckets on custom paths
if (!is_nc_coretest) this.skip(); // eslint-disable-line no-invalid-this
const new_buckets_path = get_new_buckets_path_by_test_env(tmp_fs_root, s3_new_buckets_dir);
const x_nsfs_bucket_path = `${new_buckets_path}${bucket_name}-custom-path`;
s3_correct_uid_bucket_path = new S3(s3_creds);
s3_correct_uid_bucket_path.middlewareStack.add(
(next, context) => args => {
args.request.headers[config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER] = x_nsfs_bucket_path;
return next(args);
},
{
step: "finalizeRequest",
name: "addCustomHeader",
}
);
try {
const res = await s3_correct_uid_bucket_path.createBucket({ Bucket: bucket_name + '-s3-custom-fail', });
assert.fail(inspect(res));
} catch (err) {
assert.strictEqual(err.Code, 'AccessDenied');
}
await fs_utils.file_must_not_exist(x_nsfs_bucket_path);
});

mocha.it('create s3 bucket with x-noobaa-custom-bucket-path', async function() {
// only NC supports creating buckets on custom paths
if (!is_nc_coretest) this.skip(); // eslint-disable-line no-invalid-this
const new_buckets_path = get_new_buckets_path_by_test_env(tmp_fs_root, s3_new_buckets_dir);
const account_s3_bucket_path = await rpc_client.account.create_account({
...new_account_params,
email: 'account_s3_bucket_path@noobaa.com',
name: 'account_s3_bucket_path',
s3_access: true,
default_resource: nsr,
nsfs_account_config: {
uid: process.getuid(),
gid: process.getgid(),
new_buckets_path: new_buckets_path,
nsfs_only: false,
custom_bucket_path_allowed_list: `${tmp_fs_root}:/bla/`,
}
});
s3_creds.credentials = {
accessKeyId: account_s3_bucket_path.access_keys[0].access_key.unwrap(),
secretAccessKey: account_s3_bucket_path.access_keys[0].secret_key.unwrap(),
};
s3_creds.endpoint = coretest.get_http_address();
s3_correct_uid_bucket_path = new S3(s3_creds);
const x_nsfs_bucket_path = `${new_buckets_path}/${bucket_name}-custom-path`;
s3_correct_uid_bucket_path.middlewareStack.remove("addCustomHeader");
s3_correct_uid_bucket_path.middlewareStack.add(
(next, context) => args => {
args.request.headers[config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER] = x_nsfs_bucket_path;
return next(args);
},
{
step: "finalizeRequest",
name: "addCustomHeader",
}
);
const res = await s3_correct_uid_bucket_path.createBucket({ Bucket: bucket_name + '-s3-custom', });
console.log(inspect(res));
await fs_utils.file_must_exist(x_nsfs_bucket_path);
});

mocha.it('create s3 bucket with x-noobaa-custom-bucket-path fail as directory exists', async function() {
// only NC supports creating buckets on custom paths
if (!is_nc_coretest) this.skip(); // eslint-disable-line no-invalid-this
const new_buckets_path = get_new_buckets_path_by_test_env(tmp_fs_root, s3_new_buckets_dir);
const x_nsfs_bucket_path = `${new_buckets_path}${bucket_name}-custom-path`; // already created in previous test
// this is the path that was created if x_nsfs_bucket_path header wasn't used
const no_x_nsfs_bucket_path = `${new_buckets_path}${bucket_name}-s3-custom-fail`;
s3_correct_uid_bucket_path.middlewareStack.remove("addCustomHeader");
s3_correct_uid_bucket_path.middlewareStack.add(
(next, context) => args => {
args.request.headers[config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER] = x_nsfs_bucket_path;
return next(args);
},
{
step: "finalizeRequest",
name: "addCustomHeader",
}
);
try {
const res = await s3_correct_uid_bucket_path.createBucket({ Bucket: bucket_name + '-s3-custom-fail', });
assert.fail(inspect(res));
} catch (err) {
assert.strictEqual(err.Code, 'BucketAlreadyExists');
}
await fs_utils.file_must_not_exist(no_x_nsfs_bucket_path);
});

mocha.it('create s3 bucket with x-noobaa-custom-bucket-path fail as not in account custom bucket_path', async function() {
// only NC supports creating buckets on custom paths
if (!is_nc_coretest) this.skip(); // eslint-disable-line no-invalid-this
const x_nsfs_bucket_path = `/root/${bucket_name}-custom-path`; // already created in previous test
s3_correct_uid_bucket_path.middlewareStack.remove("addCustomHeader");
s3_correct_uid_bucket_path.middlewareStack.add(
(next, context) => args => {
args.request.headers[config.NSFS_CUSTOM_BUCKET_PATH_HTTP_HEADER] = x_nsfs_bucket_path;
return next(args);
},
{
step: "finalizeRequest",
name: "addCustomHeader",
}
);
try {
const res = await s3_correct_uid_bucket_path.createBucket({ Bucket: bucket_name + '-s3-custom-fail', });
assert.fail(inspect(res));
} catch (err) {
assert.strictEqual(err.Code, 'AccessDenied');
}
await fs_utils.file_must_not_exist(x_nsfs_bucket_path);
});

mocha.it('get bucket acl - rpc bucket', async function() {
const res = await s3_correct_uid_default_nsr.getBucketAcl({ Bucket: first_bucket });
const bucket_info = await rpc_client.bucket.read_bucket({ name: first_bucket });
Expand Down
4 changes: 3 additions & 1 deletion src/test/utils/coretest/nc_coretest.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,8 @@ async function create_account_manage(options) {
uid: options.nsfs_account_config.uid,
gid: options.nsfs_account_config.gid,
access_key: options.access_key,
secret_key: options.secret_key
secret_key: options.secret_key,
custom_bucket_path_allowed_list: options.nsfs_account_config.custom_bucket_path_allowed_list,
};
const res = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, cli_options);
const json_account = JSON.parse(res);
Expand Down Expand Up @@ -434,6 +435,7 @@ async function update_account_s3_access_manage(options) {
distinguished_name: options.nsfs_account_config.distinguished_name,
uid: options.nsfs_account_config.uid,
gid: options.nsfs_account_config.gid,
custom_bucket_path_allowed_list: options.nsfs_account_config.custom_bucket_path_allowed_list,
};
await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.UPDATE, cli_options);
}
Expand Down