Skip to content

Commit c29e90a

Browse files
committed
NC | bucket owner removal
Signed-off-by: Romy <35330373+romayalon@users.noreply.github.com>
1 parent dd4e50a commit c29e90a

16 files changed

+198
-152
lines changed

docs/NooBaaNonContainerized/GettingStarted.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ make_bucket: s3bucket
187187
#### 3. Check that the bucket configuration file was created successfully -
188188
```sh
189189
sudo cat /etc/noobaa.conf.d/buckets/s3bucket.json
190-
{"_id":"65cb1efcbec92b33220112d7","name":"s3bucket","owner_account":"65cb1e7c9e6ae40d499c0ae4","bucket_owner":"account1","versioning":"DISABLED","creation_date":"2023-09-26T05:56:16.252Z","path":"/tmp/fs1/s3bucket","should_create_underlying_storage":true}
190+
{"_id":"65cb1efcbec92b33220112d7","name":"s3bucket","owner_account":"65cb1e7c9e6ae40d499c0ae4","versioning":"DISABLED","creation_date":"2023-09-26T05:56:16.252Z","path":"/tmp/fs1/s3bucket","should_create_underlying_storage":true}
191191
```
192192
193193
#### 4. Check that the underlying file system bucket directory was created successfully -

src/cmd/manage_nsfs.js

Lines changed: 48 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const noobaa_cli_diagnose = require('../manage_nsfs/diagnose');
2121
const nsfs_schema_utils = require('../manage_nsfs/nsfs_schema_utils');
2222
const { print_usage } = require('../manage_nsfs/manage_nsfs_help_utils');
2323
const { TYPES, ACTIONS, LIST_ACCOUNT_FILTERS, LIST_BUCKET_FILTERS, GLACIER_ACTIONS } = require('../manage_nsfs/manage_nsfs_constants');
24-
const { throw_cli_error, write_stdout_response, get_boolean_or_string_value, has_access_keys, set_debug_level } = require('../manage_nsfs/manage_nsfs_cli_utils');
24+
const { throw_cli_error, get_bucket_owner_account, write_stdout_response, get_boolean_or_string_value, has_access_keys, set_debug_level } = require('../manage_nsfs/manage_nsfs_cli_utils');
2525
const manage_nsfs_validations = require('../manage_nsfs/manage_nsfs_validations');
2626
const nc_mkm = require('../manage_nsfs/nc_master_key_manager').get_instance();
2727

@@ -97,7 +97,6 @@ async function fetch_bucket_data(action, user_input) {
9797
_id: undefined,
9898
name: user_input.name === undefined ? undefined : String(user_input.name),
9999
owner_account: undefined,
100-
bucket_owner: user_input.owner,
101100
tag: undefined, // if we would add the option to tag a bucket using CLI, this should be changed
102101
versioning: action === ACTIONS.ADD ? 'DISABLED' : undefined,
103102
creation_date: action === ACTIONS.ADD ? new Date().toISOString() : undefined,
@@ -126,6 +125,13 @@ async function fetch_bucket_data(action, user_input) {
126125
data = await fetch_existing_bucket_data(data);
127126
}
128127

128+
//if we're updating the owner, needs to override owner in file with the owner from user input.
129+
//if we're adding a bucket, need to set its owner id field
130+
if ((action === ACTIONS.UPDATE && user_input.owner) || (action === ACTIONS.ADD)) {
131+
const account = await get_bucket_owner_account(config_fs, String(user_input.owner));
132+
data.owner_account = account._id;
133+
}
134+
129135
// override values
130136
// fs_backend deletion specified with empty string '' (but it is not part of the schema)
131137
data.fs_backend = data.fs_backend || undefined;
@@ -159,15 +165,17 @@ async function add_bucket(data) {
159165
// for validating against the schema we need an object, hence we parse it back to object
160166
nsfs_schema_utils.validate_bucket_schema(JSON.parse(data_json));
161167
await config_fs.create_bucket_config_file(data.name, data_json);
162-
write_stdout_response(ManageCLIResponse.BucketCreated, data_json, { bucket: data.name });
168+
await set_bucker_owner(data);
169+
write_stdout_response(ManageCLIResponse.BucketCreated, data, { bucket: data.name });
163170
}
164171

165172
async function get_bucket_status(data) {
166173
await manage_nsfs_validations.validate_bucket_args(config_fs, data, ACTIONS.STATUS);
167174

168175
try {
169-
const config_data = await config_fs.get_bucket_by_name(data.name);
170-
write_stdout_response(ManageCLIResponse.BucketStatus, config_data);
176+
const bucket_data = await config_fs.get_bucket_by_name(data.name);
177+
await set_bucker_owner(bucket_data);
178+
write_stdout_response(ManageCLIResponse.BucketStatus, bucket_data);
171179
} catch (err) {
172180
const err_code = err.code === 'EACCES' ? ManageCLIError.AccessDenied : ManageCLIError.NoSuchBucket;
173181
throw_cli_error(err_code, data.name);
@@ -185,9 +193,11 @@ async function update_bucket(data) {
185193
// We take an object that was stringify
186194
// (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters)
187195
// for validating against the schema we need an object, hence we parse it back to object
188-
nsfs_schema_utils.validate_bucket_schema(JSON.parse(data));
196+
const parsed_data = JSON.parse(data);
197+
nsfs_schema_utils.validate_bucket_schema(parsed_data);
189198
await config_fs.update_bucket_config_file(cur_name, data);
190-
write_stdout_response(ManageCLIResponse.BucketUpdated, data);
199+
await set_bucker_owner(parsed_data);
200+
write_stdout_response(ManageCLIResponse.BucketUpdated, parsed_data);
191201
return;
192202
}
193203

@@ -203,6 +213,8 @@ async function update_bucket(data) {
203213
nsfs_schema_utils.validate_bucket_schema(JSON.parse(data));
204214
await config_fs.create_bucket_config_file(new_name, data);
205215
await config_fs.delete_bucket_config_file(cur_name);
216+
data = JSON.parse(data);
217+
await set_bucker_owner(data);
206218
write_stdout_response(ManageCLIResponse.BucketUpdated, data);
207219
}
208220

@@ -577,7 +589,7 @@ function filter_bucket(bucket, filters) {
577589
* @param {object} [filters]
578590
*/
579591
async function list_config_files(type, wide, show_secrets, filters = {}) {
580-
let entries = [];
592+
let entry_names = [];
581593
const should_filter = Object.keys(filters).length > 0;
582594
const is_filter_by_name = filters.name !== undefined;
583595

@@ -592,24 +604,35 @@ async function list_config_files(type, wide, show_secrets, filters = {}) {
592604
// in case we have a filter by name, we don't need to read all the entries and iterate them
593605
// instead we "mock" the entries array to have one entry and it is the name by the filter (we add it for performance)
594606
if (is_filter_by_name) {
595-
entries = [filters.name];
607+
entry_names = [filters.name];
596608
} else if (type === TYPES.ACCOUNT) {
597-
entries = await config_fs.list_accounts();
609+
entry_names = await config_fs.list_accounts();
598610
} else if (type === TYPES.BUCKET) {
599-
entries = await config_fs.list_buckets();
611+
entry_names = await config_fs.list_buckets();
600612
}
601613

602-
let config_files_list = await P.map_with_concurrency(10, entries, async entry => {
614+
// temporary cache for mapping bucker owner_account (id) -> bucket_owner (name)
615+
const bucket_owners_map = {};
616+
let config_files_list = await P.map_with_concurrency(10, entry_names, async entry_name => {
603617
if (wide || should_filter) {
604618
const data = type === TYPES.ACCOUNT ?
605-
await config_fs.get_account_by_name(entry, options) :
606-
await config_fs.get_bucket_by_name(entry, options);
619+
await config_fs.get_account_by_name(entry_name, options) :
620+
await config_fs.get_bucket_by_name(entry_name, options);
607621
if (!data) return undefined;
608622
if (should_filter && !filter_list_item(type, data, filters)) return undefined;
609623
// remove secrets on !show_secrets && should filter
610-
return wide ? _.omit(data, show_secrets ? [] : ['access_keys']) : { name: entry };
624+
if (!wide) return { name: entry_name };
625+
if (type === TYPES.ACCOUNT) return _.omit(data, show_secrets ? [] : ['access_keys']);
626+
if (type === TYPES.BUCKET) {
627+
data.bucket_owner = bucket_owners_map[data.owner_account];
628+
if (!data.bucket_owner) {
629+
await set_bucker_owner(data);
630+
bucket_owners_map[data.owner_account] = data.bucket_owner;
631+
}
632+
return data;
633+
}
611634
} else {
612-
return { name: entry };
635+
return { name: entry_name };
613636
}
614637
});
615638
// it inserts undefined for the entry '.noobaa-config-nsfs' and we wish to remove it
@@ -646,6 +669,15 @@ function get_access_keys(action, user_input) {
646669
return { access_keys, new_access_key };
647670
}
648671

672+
/**
673+
* set_bucker_owner gets bucket owner from cache by its id and sets bucket_owner name on the bucket data
674+
* @param {object} bucket_data
675+
*/
676+
async function set_bucker_owner(bucket_data) {
677+
const account_data = await config_fs.get_identity_by_id(bucket_data.owner_account, TYPES.ACCOUNT, { silent_if_missing: true});
678+
bucket_data.bucket_owner = account_data?.name;
679+
}
680+
649681
async function whitelist_ips_management(args) {
650682
const ips = args.ips;
651683
manage_nsfs_validations.validate_whitelist_arg(ips);

src/cmd/nsfs.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,6 @@ class NsfsObjectSDK extends ObjectSDK {
207207
Principal: [new SensitiveString('*')],
208208
}]
209209
},
210-
system_owner: new SensitiveString('nsfs'),
211210
bucket_owner: new SensitiveString('nsfs'),
212211
owner_account: new SensitiveString('nsfs-id'), // temp
213212
};

src/manage_nsfs/manage_nsfs_cli_utils.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,20 @@ function write_stdout_response(response_code, detail, event_arg) {
3737
* get_bucket_owner_account will return the account of the bucket_owner
3838
* otherwise it would throw an error
3939
* @param {import('../sdk/config_fs').ConfigFS} config_fs
40-
* @param {string} bucket_owner
40+
* @param {string} [bucket_owner]
41+
* @param {string} [owner_account_id]
4142
*/
42-
async function get_bucket_owner_account(config_fs, bucket_owner) {
43+
async function get_bucket_owner_account(config_fs, bucket_owner, owner_account_id) {
4344
try {
44-
const account = await config_fs.get_account_by_name(bucket_owner);
45+
const account = bucket_owner ?
46+
await config_fs.get_account_by_name(bucket_owner) :
47+
await config_fs.get_identity_by_id(owner_account_id);
4548
return account;
4649
} catch (err) {
4750
if (err.code === 'ENOENT') {
48-
const detail_msg = `bucket owner ${bucket_owner} does not exists`;
51+
const detail_msg = bucket_owner ?
52+
`bucket owner name ${bucket_owner} does not exists` :
53+
`bucket owner id ${owner_account_id} does not exists`;
4954
throw_cli_error(ManageCLIError.BucketSetForbiddenBucketOwnerNotExists, detail_msg, {bucket_owner: bucket_owner});
5055
}
5156
throw err;

src/manage_nsfs/manage_nsfs_logging.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/* Copyright (C) 2024 NooBaa */
22
'use strict';
33

4+
const AWS = require('aws-sdk');
45
const config = require('../../config');
5-
const { throw_cli_error, write_stdout_response} = require('../manage_nsfs/manage_nsfs_cli_utils');
6+
const http_utils = require('../util/http_utils');
7+
const { CONFIG_TYPES } = require('../sdk/config_fs');
8+
const { export_logs_to_target } = require('../util/bucket_logs_utils');
69
const ManageCLIError = require('../manage_nsfs/manage_nsfs_cli_errors').ManageCLIError;
710
const ManageCLIResponse = require('../manage_nsfs/manage_nsfs_cli_responses').ManageCLIResponse;
8-
const { export_logs_to_target } = require('../util/bucket_logs_utils');
9-
const http_utils = require('../util/http_utils');
10-
const AWS = require('aws-sdk');
11+
const { throw_cli_error, write_stdout_response} = require('../manage_nsfs/manage_nsfs_cli_utils');
1112

1213
let config_fs;
1314
/** This command goes over the logs in the persistent log and move the entries to log objects in the target buckets
@@ -39,8 +40,9 @@ async function export_bucket_logging(shared_config_fs) {
3940
*/
4041
async function get_bucket_owner_keys(log_bucket_name) {
4142
const log_bucket_config_data = await config_fs.get_bucket_by_name(log_bucket_name);
42-
const log_bucket_owner = log_bucket_config_data.bucket_owner;
43-
const owner_config_data = await config_fs.get_account_by_name(log_bucket_owner, { show_secrets: true, decrypt_secret_key: true });
43+
const log_bucket_owner = log_bucket_config_data.owner_account;
44+
const owner_config_data = await config_fs.get_identity_by_id(log_bucket_owner,
45+
CONFIG_TYPES.ACCOUNT, { show_secrets: true, decrypt_secret_key: true });
4446
return owner_config_data.access_keys;
4547
}
4648

src/manage_nsfs/manage_nsfs_validations.js

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const string_utils = require('../util/string_utils');
99
const native_fs_utils = require('../util/native_fs_utils');
1010
const ManageCLIError = require('../manage_nsfs/manage_nsfs_cli_errors').ManageCLIError;
1111
const bucket_policy_utils = require('../endpoint/s3/s3_bucket_policy_utils');
12-
const { throw_cli_error, get_bucket_owner_account, get_options_from_file, get_boolean_or_string_value,
13-
check_root_account_owns_user } = require('../manage_nsfs/manage_nsfs_cli_utils');
12+
const { throw_cli_error, get_options_from_file, get_boolean_or_string_value,
13+
check_root_account_owns_user, get_bucket_owner_account} = require('../manage_nsfs/manage_nsfs_cli_utils');
1414
const { TYPES, ACTIONS, VALID_OPTIONS, OPTION_TYPE, FROM_FILE, BOOLEAN_STRING_VALUES, BOOLEAN_STRING_OPTIONS,
1515
GLACIER_ACTIONS, LIST_UNSETABLE_OPTIONS, ANONYMOUS, DIAGNOSE_ACTIONS } = require('../manage_nsfs/manage_nsfs_constants');
1616
const iam_utils = require('../endpoint/iam/iam_utils');
@@ -316,7 +316,7 @@ async function validate_bucket_args(config_fs, data, action) {
316316
if (action === ACTIONS.ADD || action === ACTIONS.UPDATE) {
317317
if (action === ACTIONS.ADD) native_fs_utils.validate_bucket_creation({ name: data.name });
318318
if ((action === ACTIONS.UPDATE) && (data.new_name !== undefined)) native_fs_utils.validate_bucket_creation({ name: data.new_name });
319-
if ((action === ACTIONS.ADD) && (data.bucket_owner === undefined)) throw_cli_error(ManageCLIError.MissingBucketOwnerFlag);
319+
if ((action === ACTIONS.ADD) && (data.owner_account === undefined)) throw_cli_error(ManageCLIError.MissingBucketOwnerFlag);
320320
if (!data.path) throw_cli_error(ManageCLIError.MissingBucketPathFlag);
321321
// fs_backend='' used for deletion of the fs_backend property
322322
if (data.fs_backend !== undefined && !['GPFS', 'CEPH_FS', 'NFSv4'].includes(data.fs_backend)) {
@@ -328,30 +328,33 @@ async function validate_bucket_args(config_fs, data, action) {
328328
if (!exists) {
329329
throw_cli_error(ManageCLIError.InvalidStoragePath, data.path);
330330
}
331-
const account = await get_bucket_owner_account(config_fs, data.bucket_owner);
332-
const account_fs_context = await native_fs_utils.get_fs_context(account.nsfs_account_config, data.fs_backend);
331+
332+
// bucket owner account validations
333+
const owner_account_data = await get_bucket_owner_account(config_fs, undefined, data.owner_account);
334+
const account_fs_context = await native_fs_utils.get_fs_context(owner_account_data.nsfs_account_config,
335+
owner_account_data.nsfs_account_config.fs_backend);
333336
if (!config.NC_DISABLE_ACCESS_CHECK) {
334337
const accessible = await native_fs_utils.is_dir_rw_accessible(account_fs_context, data.path);
335338
if (!accessible) {
336339
throw_cli_error(ManageCLIError.InaccessibleStoragePath, data.path);
337340
}
338341
}
339342
if (action === ACTIONS.ADD) {
340-
if (!account.allow_bucket_creation) {
341-
const detail_msg = `${data.bucket_owner} account not allowed to create new buckets. ` +
343+
if (!owner_account_data.allow_bucket_creation) {
344+
const detail_msg = `${owner_account_data.name} account not allowed to create new buckets. ` +
342345
`Please make sure to have a valid new_buckets_path and enable the flag allow_bucket_creation`;
343346
throw_cli_error(ManageCLIError.BucketCreationNotAllowed, detail_msg);
347+
}
344348
}
345-
data.owner_account = account._id; // TODO move this assignment to better place
346-
}
347-
if (account.owner) {
348-
const detail_msg = `account ${data.bucket_owner} is IAM account`;
349-
throw_cli_error(ManageCLIError.BucketSetForbiddenBucketOwnerIsIAMAccount, detail_msg, {bucket_owner: data.bucket_owner});
349+
if (owner_account_data.owner) {
350+
const detail_msg = `account ${owner_account_data.name} is IAM account`;
351+
throw_cli_error(ManageCLIError.BucketSetForbiddenBucketOwnerIsIAMAccount, detail_msg,
352+
{ bucket_owner: owner_account_data.name });
350353
}
351354
if (data.s3_policy) {
352355
try {
353356
await bucket_policy_utils.validate_s3_policy(data.s3_policy, data.name,
354-
async principal => config_fs.is_account_exists_by_name(principal, account.owner)
357+
async principal => config_fs.is_account_exists_by_name(principal, owner_account_data.owner)
355358
);
356359
} catch (err) {
357360
dbg.error('validate_bucket_args invalid bucket policy err:', err);
@@ -440,7 +443,7 @@ async function validate_account_args(config_fs, data, action, is_flag_iam_operat
440443
* @param {object} data
441444
*/
442445
async function validate_account_resources_before_deletion(config_fs, data) {
443-
await validate_account_not_owns_buckets(config_fs, data.name);
446+
await validate_account_not_owns_buckets(config_fs, data);
444447
// If it is root account (not owned by other account) then we check that it doesn't owns IAM accounts
445448
if (data.owner === undefined) {
446449
await check_if_root_account_does_not_have_IAM_users(config_fs, data, ACTIONS.DELETE);
@@ -476,14 +479,14 @@ function _validate_access_keys(access_key, secret_key) {
476479
* validate_delete_account will check if the account has at least one bucket
477480
* in case it finds one, it would throw an error
478481
* @param {import('../sdk/config_fs').ConfigFS} config_fs
479-
* @param {string} account_name
482+
* @param {Object} account_data
480483
*/
481-
async function validate_account_not_owns_buckets(config_fs, account_name) {
484+
async function validate_account_not_owns_buckets(config_fs, account_data) {
482485
const bucket_names = await config_fs.list_buckets();
483486
await P.map_with_concurrency(10, bucket_names, async bucket_name => {
484487
const data = await config_fs.get_bucket_by_name(bucket_name, { silent_if_missing: true });
485-
if (data && data.bucket_owner === account_name) {
486-
const detail_msg = `Account ${account_name} has bucket ${data.name}`;
488+
if (data && data.owner_account === account_data._id) {
489+
const detail_msg = `Account ${account_data.name} has bucket ${data.name}`;
487490
throw_cli_error(ManageCLIError.AccountDeleteForbiddenHasBuckets, detail_msg);
488491
}
489492
return data;

src/sdk/accountspace_fs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ class AccountSpaceFS {
672672
const bucket_names = await this.config_fs.list_buckets();
673673
await P.map_with_concurrency(10, bucket_names, async bucket_name => {
674674
const bucket_data = await this.config_fs.get_bucket_by_name(bucket_name, { silent_if_missing: true});
675-
if (bucket_data && bucket_data.bucket_owner === account_to_delete.name) {
675+
if (bucket_data && bucket_data.owner_account === account_to_delete._id) {
676676
this._throw_error_delete_conflict(action, account_to_delete, resource_name);
677677
}
678678
return bucket_data;

0 commit comments

Comments
 (0)