Skip to content

Commit 7add82e

Browse files
Merge pull request #7966 from romayalon/romy-5.15-backports
5.15 | Backports
2 parents d4917eb + e7931fe commit 7add82e

17 files changed

+736
-47
lines changed

config.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -751,9 +751,19 @@ config.NSFS_GLACIER_MIGRATE_INTERVAL = 15 * 60 * 1000;
751751
// of `manage_nsfs glacier restore`
752752
config.NSFS_GLACIER_RESTORE_INTERVAL = 15 * 60 * 1000;
753753

754-
// NSFS_GLACIER_EXPIRY_INTERVAL indicates the interval between runs
755-
// of `manage_nsfs glacier expiry`
756-
config.NSFS_GLACIER_EXPIRY_INTERVAL = 12 * 60 * 60 * 1000;
754+
// NSFS_GLACIER_EXPIRY_RUN_TIME must be of the format hh:mm which specifies
755+
// when NooBaa should allow running glacier expiry process
756+
// NOTE: This will also be in the same timezone as specified in
757+
// NSFS_GLACIER_EXPIRY_TZ
758+
config.NSFS_GLACIER_EXPIRY_RUN_TIME = '03:00';
759+
760+
// NSFS_GLACIER_EXPIRY_RUN_TIME_TOLERANCE_MINS configures the delay
761+
// tolerance in minutes.
762+
//
763+
// eg. If the expiry run time is set to 03:00 and the tolerance is
764+
// set to be 2 mins then the expiry can trigger till 03:02 (unless
765+
// already triggered between 03:00 - 03:02
766+
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS = 2 * 60;
757767

758768
/** @type {'UTC' | 'LOCAL'} */
759769
config.NSFS_GLACIER_EXPIRY_TZ = 'LOCAL';
@@ -812,6 +822,15 @@ config.NSFS_WHITELIST = [];
812822
config.NSFS_HEALTH_ENDPOINT_RETRY_COUNT = 3;
813823
config.NSFS_HEALTH_ENDPOINT_RETRY_DELAY = 10;
814824

825+
826+
/** @type {'file' | 'executable'} */
827+
config.NC_MASTER_KEYS_STORE_TYPE = 'file';
828+
// unless override in config.json, the default will be the config_dir/master_keys.json
829+
config.NC_MASTER_KEYS_FILE_LOCATION = '';
830+
config.NC_MASTER_KEYS_GET_EXECUTABLE = '';
831+
config.NC_MASTER_KEYS_PUT_EXECUTABLE = '';
832+
config.NC_MASTER_KEYS_MANAGER_REFRESH_THRESHOLD = 5 * 60 * 1000; // 5 minutes
833+
815834
//Quota
816835
config.QUOTA_LOW_THRESHOLD = 80;
817836
config.QUOTA_MAX_OBJECTS = Number.MAX_SAFE_INTEGER;

docs/dev_guide/NonContainerizedDeveloperCustomizations.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,74 @@ Example:
337337
3. systemctl restart noobaa_nsfs
338338
```
339339

340+
## 19. Set Master Keys Store type-
341+
**Description -** This flag will set the type of the master keys store used by NooBaa.
342+
343+
**Configuration Key -** NC_MASTER_KEYS_STORE_TYPE
344+
345+
**Type -** string
346+
347+
**Default -** 'file'
348+
**Steps -**
349+
```
350+
1. Open /path/to/config_dir/config.json file.
351+
2. Set the config key -
352+
Example:
353+
"NC_MASTER_KEYS_STORE_TYPE": 'executable'
354+
3. systemctl restart noobaa_nsfs
355+
```
356+
357+
## 20. Set Master Keys File Location -
358+
**Description -** This flag will set the location of the master keys file used by NooBaa.
359+
360+
**Configuration Key -** NC_MASTER_KEYS_FILE_LOCATION
361+
362+
**Type -** string
363+
364+
**Default -** '/etc/noobaa.conf.d/master_keys.json'
365+
**Steps -**
366+
```
367+
1. Open /path/to/config_dir/config.json file.
368+
2. Set the config key -
369+
Example:
370+
"NC_MASTER_KEYS_FILE_LOCATION": '/private/tmp/master_keys.json'
371+
3. systemctl restart noobaa_nsfs
372+
```
373+
374+
## 21. Set Master Keys GET executable script -
375+
**Description -** This flag will set the location of the executable script for reading the master keys file used by NooBa.
376+
377+
**Configuration Key -** NC_MASTER_KEYS_GET_EXECUTABLE
378+
379+
**Type -** string
380+
381+
**Default -** undefined
382+
**Steps -**
383+
```
384+
1. Open /path/to/config_dir/config.json file.
385+
2. Set the config key -
386+
Example:
387+
"NC_MASTER_KEYS_GET_EXECUTABLE": '/private/tmp/get_master_keys.sh'
388+
3. systemctl restart noobaa_nsfs
389+
```
390+
391+
## 22. Set Master Keys PUT executable script -
392+
**Description -** This flag will set the location of the executable script for updating the master keys file used by NooBa.
393+
394+
**Configuration Key -** NC_MASTER_KEYS_PUT_EXECUTABLE
395+
396+
**Type -** string
397+
398+
**Default -** undefined
399+
**Steps -**
400+
```
401+
1. Open /path/to/config_dir/config.json file.
402+
2. Set the config key -
403+
Example:
404+
"NC_MASTER_KEYS_PUT_EXECUTABLE": '/private/tmp/put_master_keys.sh'
405+
3. systemctl restart noobaa_nsfs
406+
```
407+
340408
## Config.json example
341409
```
342410
> cat /path/to/config_dir/config.json

src/cmd/manage_nsfs.js

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const { print_usage } = require('../manage_nsfs/manage_nsfs_help_utils');
2525
const { TYPES, ACTIONS, VALID_OPTIONS, OPTION_TYPE, FROM_FILE, BOOLEAN_STRING_VALUES,
2626
LIST_ACCOUNT_FILTERS, LIST_BUCKET_FILTERS, GLACIER_ACTIONS } = require('../manage_nsfs/manage_nsfs_constants');
2727
const NoobaaEvent = require('../manage_nsfs/manage_nsfs_events_utils').NoobaaEvent;
28+
const nc_mkm = require('../manage_nsfs/nc_master_key_manager').get_instance();
2829

2930
function throw_cli_error(error_code, detail, event_arg) {
3031
const error_event = NSFS_CLI_ERROR_EVENT_MAP[error_code.code];
@@ -200,7 +201,7 @@ async function add_bucket(data) {
200201
const fs_context = native_fs_utils.get_process_fs_context(config_root_backend);
201202
const bucket_conf_path = get_config_file_path(buckets_dir_path, data.name);
202203
const exists = await native_fs_utils.is_path_exists(fs_context, bucket_conf_path);
203-
if (exists) throw_cli_error(ManageCLIError.BucketAlreadyExists, data.name, {bucket: data.name});
204+
if (exists) throw_cli_error(ManageCLIError.BucketAlreadyExists, data.name, { bucket: data.name });
204205
data._id = mongo_utils.mongoObjectId();
205206
data.owner_account = account_id;
206207
const data_json = JSON.stringify(data);
@@ -209,7 +210,7 @@ async function add_bucket(data) {
209210
// for validating against the schema we need an object, hence we parse it back to object
210211
nsfs_schema_utils.validate_bucket_schema(JSON.parse(data_json));
211212
await native_fs_utils.create_config_file(fs_context, buckets_dir_path, bucket_conf_path, data_json);
212-
write_stdout_response(ManageCLIResponse.BucketCreated, data_json, {bucket: data.name});
213+
write_stdout_response(ManageCLIResponse.BucketCreated, data_json, { bucket: data.name });
213214
}
214215

215216
/** verify_bucket_owner will check if the bucket_owner has an account
@@ -419,6 +420,7 @@ async function fetch_existing_account_data(target) {
419420
get_config_file_path(accounts_dir_path, target.name) :
420421
get_symlink_config_file_path(access_keys_dir_path, target.access_keys[0].access_key);
421422
source = await get_config_data(account_path, true);
423+
source.access_keys = await nc_mkm.decrypt_access_keys(source);
422424
} catch (err) {
423425
dbg.log1('NSFS Manage command: Could not find account', target, err);
424426
if (err.code === 'ENOENT') {
@@ -452,17 +454,20 @@ async function add_account(data) {
452454
throw_cli_error(err_code, event_arg, {account: event_arg});
453455
}
454456
data._id = mongo_utils.mongoObjectId();
455-
data = JSON.stringify(data);
457+
const encrypted_account = await nc_mkm.encrypt_access_keys(data);
458+
data.master_key_id = encrypted_account.master_key_id;
459+
const encrypted_data = JSON.stringify(encrypted_account);
456460
// We take an object that was stringify
457461
// (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters)
458462
// for validating against the schema we need an object, hence we parse it back to object
459-
nsfs_schema_utils.validate_account_schema(JSON.parse(data));
460-
await native_fs_utils.create_config_file(fs_context, accounts_dir_path, account_config_path, data);
463+
nsfs_schema_utils.validate_account_schema(JSON.parse(encrypted_data));
464+
await native_fs_utils.create_config_file(fs_context, accounts_dir_path, account_config_path, encrypted_data);
461465
await native_fs_utils._create_path(access_keys_dir_path, fs_context, config.BASE_MODE_CONFIG_DIR);
462466
await nb_native().fs.symlink(fs_context, account_config_relative_path, account_config_access_key_path);
463-
write_stdout_response(ManageCLIResponse.AccountCreated, data, {account: event_arg});
467+
write_stdout_response(ManageCLIResponse.AccountCreated, data, { account: event_arg });
464468
}
465469

470+
466471
async function update_account(data) {
467472
await validate_account_args(data, ACTIONS.UPDATE);
468473

@@ -475,12 +480,14 @@ async function update_account(data) {
475480

476481
if (!update_name && !update_access_key) {
477482
const account_config_path = get_config_file_path(accounts_dir_path, data.name);
478-
data = JSON.stringify(data);
483+
const encrypted_account = await nc_mkm.encrypt_access_keys(data);
484+
data.master_key_id = encrypted_account.master_key_id;
485+
const encrypted_data = JSON.stringify(encrypted_account);
479486
// We take an object that was stringify
480487
// (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters)
481488
// for validating against the schema we need an object, hence we parse it back to object
482-
nsfs_schema_utils.validate_account_schema(JSON.parse(data));
483-
await native_fs_utils.update_config_file(fs_context, accounts_dir_path, account_config_path, data);
489+
nsfs_schema_utils.validate_account_schema(JSON.parse(encrypted_data));
490+
await native_fs_utils.update_config_file(fs_context, accounts_dir_path, account_config_path, encrypted_data);
484491
write_stdout_response(ManageCLIResponse.AccountUpdated, data);
485492
return;
486493
}
@@ -499,16 +506,21 @@ async function update_account(data) {
499506
const err_code = name_exists ? ManageCLIError.AccountNameAlreadyExists : ManageCLIError.AccountAccessKeyAlreadyExists;
500507
throw_cli_error(err_code);
501508
}
502-
data = JSON.stringify(_.omit(data, ['new_name', 'new_access_key']));
509+
data = _.omit(data, ['new_name', 'new_access_key']);
510+
const encrypted_account = await nc_mkm.encrypt_access_keys(data);
511+
data.master_key_id = encrypted_account.master_key_id;
512+
const encrypted_data = JSON.stringify(encrypted_account);
513+
data = JSON.stringify(data);
514+
503515
// We take an object that was stringify
504516
// (it unwraps ths sensitive strings, creation_date to string and removes undefined parameters)
505517
// for validating against the schema we need an object, hence we parse it back to object
506-
nsfs_schema_utils.validate_account_schema(JSON.parse(data));
518+
nsfs_schema_utils.validate_account_schema(JSON.parse(encrypted_data));
507519
if (update_name) {
508-
await native_fs_utils.create_config_file(fs_context, accounts_dir_path, new_account_config_path, data);
520+
await native_fs_utils.create_config_file(fs_context, accounts_dir_path, new_account_config_path, encrypted_data);
509521
await native_fs_utils.delete_config_file(fs_context, accounts_dir_path, cur_account_config_path);
510522
} else if (update_access_key) {
511-
await native_fs_utils.update_config_file(fs_context, accounts_dir_path, cur_account_config_path, data);
523+
await native_fs_utils.update_config_file(fs_context, accounts_dir_path, cur_account_config_path, encrypted_data);
512524
}
513525
// TODO: safe_unlink can be better but the current impl causing ELOOP - Too many levels of symbolic links
514526
// need to find a better way for atomic unlinking of symbolic links
@@ -560,6 +572,7 @@ async function get_account_status(data, show_secrets) {
560572
get_symlink_config_file_path(access_keys_dir_path, data.access_keys[0].access_key) :
561573
get_config_file_path(accounts_dir_path, data.name);
562574
const config_data = await get_config_data(account_path, show_secrets);
575+
if (config_data.access_keys) config_data.access_keys = await nc_mkm.decrypt_access_keys(config_data);
563576
write_stdout_response(ManageCLIResponse.AccountStatus, config_data);
564577
} catch (err) {
565578
if (_.isUndefined(data.name)) {
@@ -656,6 +669,7 @@ async function list_config_files(type, config_path, wide, show_secrets, filters)
656669
if (wide || should_filter) {
657670
const full_path = path.join(config_path, entry.name);
658671
const data = await get_config_data(full_path, show_secrets || should_filter);
672+
if (data.access_keys) data.access_keys = await nc_mkm.decrypt_access_keys(data);
659673
if (should_filter && !filter_list_item(type, data, filters)) return undefined;
660674
// remove secrets on !show_secrets && should filter
661675
return wide ? _.omit(data, show_secrets ? [] : ['access_keys']) : { name: entry.name.slice(0, entry.name.indexOf('.json')) };

src/manage_nsfs/manage_nsfs_glacier.js

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,65 @@ async function process_expiry() {
6969
const fs_context = native_fs_utils.get_process_fs_context();
7070

7171
await lock_and_run(fs_context, SCAN_LOCK, async () => {
72-
if (!(await time_exceeded(fs_context, config.NSFS_GLACIER_EXPIRY_INTERVAL, GlacierBackend.EXPIRY_TIMESTAMP_FILE))) return;
72+
const backend = getGlacierBackend();
73+
if (
74+
await backend.low_free_space() ||
75+
await is_desired_time(
76+
fs_context,
77+
new Date(),
78+
config.NSFS_GLACIER_EXPIRY_RUN_TIME,
79+
config.NSFS_GLACIER_EXPIRY_RUN_DELAY_LIMIT_MINS,
80+
GlacierBackend.EXPIRY_TIMESTAMP_FILE,
81+
)
82+
) {
83+
await backend.expiry(fs_context);
84+
await record_current_time(fs_context, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
85+
}
86+
});
87+
}
7388

89+
/**
90+
* is_desired_time returns true if the given time matches with
91+
* the desired time or if
92+
* @param {nb.NativeFSContext} fs_context
93+
* @param {Date} current
94+
* @param {string} desire time in format 'hh:mm'
95+
* @param {number} delay_limit_mins
96+
* @param {string} timestamp_file
97+
* @returns {Promise<boolean>}
98+
*/
99+
async function is_desired_time(fs_context, current, desire, delay_limit_mins, timestamp_file) {
100+
const [desired_hour, desired_min] = desire.split(':').map(Number);
101+
if (
102+
isNaN(desired_hour) ||
103+
isNaN(desired_min) ||
104+
(desired_hour < 0 || desired_hour >= 24) ||
105+
(desired_min < 0 || desired_min >= 60)
106+
) {
107+
throw new Error('invalid desired_time - must be hh:mm');
108+
}
74109

75-
await getGlacierBackend().expiry(fs_context);
76-
await record_current_time(fs_context, GlacierBackend.EXPIRY_TIMESTAMP_FILE);
77-
});
110+
const min_time = get_tz_date(desired_hour, desired_min, 0, config.NSFS_GLACIER_EXPIRY_TZ);
111+
const max_time = get_tz_date(desired_hour, desired_min + delay_limit_mins, 0, config.NSFS_GLACIER_EXPIRY_TZ);
112+
113+
if (current >= min_time && current <= max_time) {
114+
try {
115+
const { data } = await nb_native().fs.readFile(fs_context, path.join(config.NSFS_GLACIER_LOGS_DIR, timestamp_file));
116+
const lastrun = new Date(data.toString());
117+
118+
// Last run should NOT be in this window
119+
if (lastrun >= min_time && lastrun <= max_time) return false;
120+
} catch (error) {
121+
if (error.code === 'ENOENT') return true;
122+
console.error('failed to read last run timestamp:', error, 'timestamp_file:', timestamp_file);
123+
124+
throw error;
125+
}
126+
127+
return true;
128+
}
129+
130+
return false;
78131
}
79132

80133
/**
@@ -134,6 +187,31 @@ async function run_glacier_operation(fs_context, log_namespace, cb) {
134187
}
135188
}
136189

190+
/**
191+
* @param {number} hours
192+
* @param {number} mins
193+
* @param {number} secs
194+
* @param {'UTC' | 'LOCAL'} tz
195+
* @returns {Date}
196+
*/
197+
function get_tz_date(hours, mins, secs, tz) {
198+
const date = new Date();
199+
200+
if (tz === 'UTC') {
201+
date.setUTCHours(hours);
202+
date.setUTCMinutes(hours);
203+
date.setUTCSeconds(secs);
204+
date.setUTCMilliseconds(0);
205+
} else {
206+
date.setHours(hours);
207+
date.setMinutes(mins);
208+
date.setSeconds(secs);
209+
date.setMilliseconds(0);
210+
}
211+
212+
return date;
213+
}
214+
137215
/**
138216
* lock_and_run acquires a flock and calls the given callback after
139217
* acquiring the lock

0 commit comments

Comments
 (0)