Skip to content

Commit fb22b50

Browse files
committed
NC | add bypass governance flag
Signed-off-by: nadav mizrahi <nadav.mizrahi16@gmail.com>
1 parent 5d4129c commit fb22b50

File tree

9 files changed

+112
-19
lines changed

9 files changed

+112
-19
lines changed

src/cmd/manage_nsfs.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ async function fetch_account_data(action, user_input) {
505505
new_buckets_path: user_input.new_buckets_path,
506506
fs_backend: user_input.fs_backend ? String(user_input.fs_backend) : config.NSFS_NC_STORAGE_BACKEND,
507507
custom_bucket_path_allowed_list: user_input.custom_bucket_path_allowed_list,
508+
allow_bypass_governance: user_input.allow_bypass_governance === '' ? user_input.allow_bypass_governance : get_boolean_or_string_value(user_input.allow_bypass_governance),
508509
},
509510
default_connection: user_input.default_connection === undefined ? undefined : String(user_input.default_connection)
510511
};
@@ -545,6 +546,7 @@ async function fetch_account_data(action, user_input) {
545546
}
546547
// custom_bucket_path_allowed_list deletion specified with empty string ''
547548
data.nsfs_account_config.custom_bucket_path_allowed_list = data.nsfs_account_config.custom_bucket_path_allowed_list || undefined;
549+
data.nsfs_account_config.allow_bypass_governance = data.nsfs_account_config.allow_bypass_governance === '' ? undefined : data.nsfs_account_config.allow_bypass_governance;
548550

549551
return data;
550552
}

src/manage_nsfs/manage_nsfs_constants.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ const FROM_FILE = 'from_file';
4646
const ANONYMOUS = 'anonymous';
4747

4848
const VALID_OPTIONS_ACCOUNT = {
49-
'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]),
50-
'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]),
49+
'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', 'allow_bypass_governance', FROM_FILE, ...CLI_MUTUAL_OPTIONS]),
50+
'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', 'allow_bypass_governance', ...CLI_MUTUAL_OPTIONS]),
5151
'delete': new Set(['name', ...CLI_MUTUAL_OPTIONS]),
5252
'list': new Set(['wide', 'show_secrets', 'gid', 'uid', 'user', 'name', 'access_key', ...CLI_MUTUAL_OPTIONS]),
5353
'status': new Set(['name', 'access_key', 'show_secrets', ...CLI_MUTUAL_OPTIONS]),
@@ -177,12 +177,13 @@ const OPTION_TYPE = {
177177
// bucket tagging
178178
tag: 'string',
179179
merge_tag: 'string',
180+
allow_bypass_governance: 'boolean',
180181
};
181182

182183
const BOOLEAN_STRING_VALUES = ['true', 'false'];
183184
const BOOLEAN_STRING_OPTIONS = new Set(['allow_bucket_creation', 'regenerate', 'wide', 'show_secrets', 'force',
184185
'force_md5_etag', 'iam_operate_on_root_account', 'all_account_details', 'all_bucket_details', 'anonymous',
185-
'disable_service_validation', 'disable_runtime_validation', 'short_status', 'skip_verification', 'continue']);
186+
'disable_service_validation', 'disable_runtime_validation', 'short_status', 'skip_verification', 'continue', 'allow_bypass_governance']);
186187

187188
// CLI UNSET VALUES
188189
const CLI_EMPTY_STRING = '';
@@ -199,6 +200,7 @@ const UNSETTABLE_OPTIONS_OBJ = Object.freeze({
199200
'new_buckets_path': CLI_EMPTY_STRING,
200201
'custom_bucket_path_allowed_list': CLI_EMPTY_STRING,
201202
'ips': CLI_EMPTY_STRING_ARRAY,
203+
'allow_bypass_governance': CLI_EMPTY_STRING,
202204
});
203205

204206
const LIST_ACCOUNT_FILTERS = ['uid', 'gid', 'user', 'name', 'access_key'];

src/manage_nsfs/manage_nsfs_help_utils.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ Flags:
144144
--iam_operate_on_root_account <true | false> (optional) Set the account to create root accounts instead of IAM users in IAM API requests.
145145
--from_file <string> (optional) Use details from the JSON file, there is no need to mention all the properties individually in the CLI
146146
--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/'
147+
--allow_bypass_governance <true | false> (optional) Set the account to allow bypassing governance mode object lock for object deletion and retention configuration (unset with '')
147148
`;
148149

149150
const ACCOUNT_FLAGS_UPDATE = `
@@ -172,6 +173,7 @@ Flags:
172173
--force_md5_etag <true | false> (optional) Update the account to force md5 etag calculation (unset with '') (will override default config.NSFS_NC_STORAGE_BACKEND)
173174
--iam_operate_on_root_account <true | false> (optional) Update the account to create root accounts instead of IAM users in IAM API requests.
174175
--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 '')
176+
--allow_bypass_governance <true | false> (optional) Update the account to allow bypassing governance mode object lock for object deletion and retention configuration (unset with '')
175177
`;
176178

177179
const ACCOUNT_FLAGS_DELETE = `

src/sdk/accountspace_fs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ class AccountSpaceFS {
609609
new_buckets_path: requesting_account.nsfs_account_config.new_buckets_path,
610610
fs_backend: requesting_account.nsfs_account_config.fs_backend,
611611
custom_bucket_path_allowed_list: requesting_account.nsfs_account_config.custom_bucket_path_allowed_list,
612+
allow_bypass_governance: requesting_account.nsfs_account_config.allow_bypass_governance,
612613
}
613614
};
614615
if (requesting_account.iam_operate_on_root_account) {

src/sdk/namespace_fs.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,18 +2317,22 @@ class NamespaceFS {
23172317
return { retention: { mode: default_retention.mode, retain_until_date } };
23182318
}
23192319

2320+
_check_bypass_governance_permission(fs_context) {
2321+
return fs_context?.allow_bypass_governance;
2322+
}
2323+
23202324
/**
23212325
* check if the object deletion should be blocked by retention lock. if the object is blocked will throw AccessDenied error
23222326
* @param {Object} retention - object retention lock settings
23232327
* @param {boolean} bypass_governance - if true, and user has permission to use this flag, will allow to bypass governance mode retention lock. compliance mode retention lock cannot be bypassed.
23242328
* @throws {S3Error.AccessDenied} if the object is protected by object lock and the user does not have permission to bypass the lock
23252329
*/
2326-
_check_object_retention(retention, bypass_governance) {
2330+
_check_object_retention(fs_context, retention, bypass_governance) {
23272331
if (retention) {
23282332
const retain_until_date = new Date(retention.retain_until_date);
23292333
const now = new Date();
23302334
if (now < retain_until_date) {
2331-
//TODO should check for bypass_governance permission
2335+
bypass_governance = bypass_governance && this._check_bypass_governance_permission(fs_context);
23322336
if (retention.mode === 'COMPLIANCE' ||
23332337
(retention.mode === 'GOVERNANCE' && !bypass_governance)) {
23342338
const err = new S3Error(S3Error.AccessDenied);
@@ -2349,13 +2353,13 @@ class NamespaceFS {
23492353
* @param {boolean} bypass_governance - if true, and user has permission to use this flag, will allow to bypass governance mode retention lock. compliance mode retention lock cannot be bypassed.
23502354
* @throws {S3Error.AccessDenied} if the object is protected by object lock and the user does not have permission to bypass the lock
23512355
*/
2352-
_compare_object_retention(current_retention, new_retention, bypass_governance) {
2356+
_compare_object_retention(fs_context, current_retention, new_retention, bypass_governance) {
23532357
if (current_retention) {
23542358
const retain_until_date = new Date(current_retention.retain_until_date);
23552359
const new_date = new Date(new_retention.retain_until_date);
23562360
//can always increase retention time
2357-
if (new_date >= retain_until_date && new_retention.mode === current_retention.mode) return;
2358-
//TODO should check for bypass_governance permission
2361+
if (new_date > retain_until_date && new_retention.mode === current_retention.mode) return;
2362+
bypass_governance = bypass_governance && this._check_bypass_governance_permission(fs_context);
23592363
if (current_retention.mode === 'COMPLIANCE' ||
23602364
(current_retention.mode === 'GOVERNANCE' && !bypass_governance)) {
23612365
const err = new S3Error(S3Error.AccessDenied);
@@ -2371,13 +2375,13 @@ class NamespaceFS {
23712375
* @param {Object} params - request params, contains bypass_governance flag
23722376
* @throws {S3Error.AccessDenied} if the object is protected by object lock and the user does not have permission to bypass the lock
23732377
*/
2374-
_check_object_lock(version_id, params) {
2378+
_check_object_lock(fs_context, version_id, params) {
23752379
if (version_id.legal_hold === 'ON') {
23762380
const err = new S3Error(S3Error.AccessDenied);
23772381
err.message = 'Access Denied because object protected by object lock.';
23782382
throw err;
23792383
}
2380-
this._check_object_retention(version_id.retention, params.bypass_governance);
2384+
this._check_object_retention(fs_context, version_id.retention, params.bypass_governance);
23812385
}
23822386

23832387

@@ -2468,7 +2472,7 @@ class NamespaceFS {
24682472
try {
24692473
const stat = await nb_native().fs.stat(fs_context, file_path);
24702474
const current_retention = this._get_retention_mode_from_xattr(stat.xattr);
2471-
this._compare_object_retention(current_retention, params.retention, params.bypass_governance);
2475+
this._compare_object_retention(fs_context, current_retention, params.retention, params.bypass_governance);
24722476
const fs_xattr = {};
24732477
fs_xattr[XATTR_RETENTION_MODE] = params.retention.mode;
24742478
fs_xattr[XATTR_RETENTION_DATE] = params.retention.retain_until_date.toISOString();
@@ -3420,7 +3424,7 @@ class NamespaceFS {
34203424
await this._check_path_in_bucket_boundaries(fs_context, file_path);
34213425
const version_info = await this._get_version_info(fs_context, file_path);
34223426
if (!version_info) return;
3423-
this._check_object_lock(version_info, params);
3427+
this._check_object_lock(fs_context, version_info, params);
34243428

34253429
const deleted_latest = file_path === latest_version_path;
34263430
if (deleted_latest) {

src/server/system_services/schemas/nsfs_account_schema.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ module.exports = {
8989
$ref: 'common_api#/definitions/fs_backend'
9090
},
9191
custom_bucket_path_allowed_list: { type: 'string' },
92+
allow_bypass_governance: { type: 'boolean' },
9293
}
9394
}, {
9495
type: 'object',
@@ -100,6 +101,7 @@ module.exports = {
100101
$ref: 'common_api#/definitions/fs_backend'
101102
},
102103
custom_bucket_path_allowed_list: { type: 'string' },
104+
allow_bypass_governance: { type: 'boolean' },
103105
}
104106
}]
105107
},

src/test/integration_tests/api/s3/test_s3_worm.js

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ mocha.describe('s3 worm', function() {
4444
let version_id3;
4545
let version_id4;
4646
const user_a = 'alicia';
47+
const user_a_mail = 'alicia@test.com';
4748
let s3_owner;
4849
//let s3_a;
4950
mocha.before(async function() {
@@ -70,19 +71,23 @@ mocha.describe('s3 worm', function() {
7071
account.nsfs_account_config = {
7172
uid: process.getuid(),
7273
gid: process.getgid(),
73-
new_buckets_path: tmp_fs_root
74+
new_buckets_path: tmp_fs_root,
75+
allow_bypass_governance: 'true',
7476
};
7577
}
7678
const admin_keys = (await rpc_client.account.read_account({ email: EMAIL, })).access_keys;
7779
account.name = user_a;
7880
account.email = user_a;
7981
const user_a_keys = (await rpc_client.account.create_account(account)).access_keys;
80-
s3_creds.credentials.accessKeyId = user_a_keys[0].access_key.unwrap();
81-
s3_creds.credentials.secretAccessKey = user_a_keys[0].secret_key.unwrap();
82-
//s3_a = new AWS.S3(s3_creds);
83-
s3_creds.credentials.accessKeyId = admin_keys[0].access_key.unwrap();
84-
s3_creds.credentials.secretAccessKey = admin_keys[0].secret_key.unwrap();
85-
s3_owner = new S3(s3_creds);
82+
if (is_nc_coretest) {
83+
s3_creds.credentials.accessKeyId = user_a_keys[0].access_key.unwrap();
84+
s3_creds.credentials.secretAccessKey = user_a_keys[0].secret_key.unwrap();
85+
s3_owner = new S3(s3_creds);
86+
} else {
87+
s3_creds.credentials.accessKeyId = admin_keys[0].access_key.unwrap();
88+
s3_creds.credentials.secretAccessKey = admin_keys[0].secret_key.unwrap();
89+
s3_owner = new S3(s3_creds);
90+
}
8691
});
8792
mocha.describe('buckets creation', function() {
8893
mocha.it('create bucket BKT & enable lock', async function() {
@@ -977,4 +982,43 @@ mocha.describe('s3 worm', function() {
977982
}), 'NoSuchObjectLockConfiguration', 'The specified object does not have a ObjectLock configuration');
978983
});
979984
});
985+
986+
mocha.describe('no bypass permissions for user', async function() {
987+
let version_id;
988+
mocha.before(async function() {
989+
const update_conf = {
990+
email: user_a_mail,
991+
nsfs_account_config: {allow_bypass_governance: 'false'}};
992+
await rpc_client.account.update_account_s3_access(update_conf);
993+
994+
const conf = await s3_owner.putObject({
995+
Bucket: BKT,
996+
Key: OBJ1,
997+
Body: file_body,
998+
ContentType: 'text/plain',
999+
ObjectLockMode: 'GOVERNANCE',
1000+
ObjectLockRetainUntilDate: tomorrow
1001+
});
1002+
version_id = conf.VersionId;
1003+
});
1004+
1005+
mocha.it('should fail to put retention without bypass flag', async function() {
1006+
await assert_throws_async(s3_owner.putObjectRetention({
1007+
Bucket: BKT,
1008+
Key: OBJ1,
1009+
Retention: { Mode: 'COMPLIANCE', RetainUntilDate: tomorrow },
1010+
VersionId: version_id,
1011+
BypassGovernanceRetention: true
1012+
}), 'AccessDenied', is_nc_coretest ? 'Access Denied because object protected by object lock.' : 'Access Denied');
1013+
});
1014+
1015+
mocha.it('should fail to delete object with retention without bypass flag', async function() {
1016+
await assert_throws_async(s3_owner.deleteObject({
1017+
Bucket: BKT,
1018+
Key: OBJ1,
1019+
VersionId: version_id1,
1020+
BypassGovernanceRetention: true,
1021+
}), 'AccessDenied', is_nc_coretest ? 'Access Denied because object protected by object lock.' : 'Access Denied');
1022+
});
1023+
});
9801024
});

src/test/integration_tests/nc/cli/test_nc_account_cli.test.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const quoted_type = Object.freeze({
3131

3232
// eslint-disable-next-line max-lines-per-function
3333
describe('manage nsfs cli account flow', () => {
34+
/* eslint-disable max-statements */
3435
describe('cli create account', () => {
3536
const config_root = path.join(tmp_fs_path, 'config_root_manage_nsfs');
3637
const config_fs = new ConfigFS(config_root);
@@ -671,6 +672,20 @@ describe('manage nsfs cli account flow', () => {
671672
assert_account(account, account_options, false);
672673
expect(account.nsfs_account_config.custom_bucket_path_allowed_list).toBe(custom_bucket_path_allowed_list);
673674
});
675+
676+
it('cli account add - with bypass governance lock flag', async function() {
677+
const action = ACTIONS.ADD;
678+
const { type, name, new_buckets_path, uid, gid } = defaults;
679+
const allow_bypass_governance = 'true';
680+
const account_options = { config_root, name, new_buckets_path, uid, gid, allow_bypass_governance };
681+
await fs_utils.create_fresh_path(new_buckets_path);
682+
await fs_utils.file_must_exist(new_buckets_path);
683+
await set_path_permissions_and_owner(new_buckets_path, account_options, 0o700);
684+
await exec_manage_cli(type, action, account_options);
685+
const account = await config_fs.get_account_by_name(name, config_fs_account_options);
686+
assert_account(account, account_options, false);
687+
expect(account.nsfs_account_config.allow_bypass_governance).toBe(true);
688+
});
674689
});
675690

676691
describe('cli update account', () => {
@@ -1256,6 +1271,25 @@ describe('manage nsfs cli account flow', () => {
12561271
new_account_details = await config_fs.get_account_by_name(name, config_fs_account_options);
12571272
expect(new_account_details.nsfs_account_config.custom_bucket_path_allowed_list).toBe("/some/path/:/another/path/");
12581273
});
1274+
1275+
it('cli update account with bypass governance lock flag', async function() {
1276+
const { name } = defaults;
1277+
const account_options = { config_root, name, allow_bypass_governance: 'true'};
1278+
const action = ACTIONS.UPDATE;
1279+
await exec_manage_cli(type, action, account_options);
1280+
let new_account_details = await config_fs.get_account_by_name(name, config_fs_account_options);
1281+
expect(new_account_details.nsfs_account_config.allow_bypass_governance).toBe(true);
1282+
1283+
account_options.allow_bypass_governance = 'false';
1284+
await exec_manage_cli(type, action, account_options);
1285+
new_account_details = await config_fs.get_account_by_name(name, config_fs_account_options);
1286+
expect(new_account_details.nsfs_account_config.allow_bypass_governance).toBe(false);
1287+
1288+
account_options.allow_bypass_governance = CLI_UNSET_EMPTY_STRING;
1289+
await exec_manage_cli(type, action, account_options);
1290+
new_account_details = await config_fs.get_account_by_name(name, config_fs_account_options);
1291+
expect(new_account_details.nsfs_account_config.allow_bypass_governance).toBeUndefined();
1292+
});
12591293
});
12601294

12611295
describe('cli update account (has distinguished name)', () => {

src/test/utils/coretest/nc_coretest.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,7 @@ async function create_account_manage(options) {
344344
access_key: options.access_key,
345345
secret_key: options.secret_key,
346346
custom_bucket_path_allowed_list: options.nsfs_account_config.custom_bucket_path_allowed_list,
347+
allow_bypass_governance: options.nsfs_account_config.allow_bypass_governance,
347348
};
348349
const res = await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.ADD, cli_options);
349350
const json_account = JSON.parse(res);
@@ -441,6 +442,7 @@ async function update_account_s3_access_manage(options) {
441442
uid: options.nsfs_account_config.uid,
442443
gid: options.nsfs_account_config.gid,
443444
custom_bucket_path_allowed_list: options.nsfs_account_config.custom_bucket_path_allowed_list,
445+
allow_bypass_governance: options.nsfs_account_config.allow_bypass_governance,
444446
};
445447
await exec_manage_cli(TYPES.ACCOUNT, ACTIONS.UPDATE, cli_options);
446448
}

0 commit comments

Comments
 (0)