Skip to content

Commit

Permalink
cli permissions and other fixes
Browse files Browse the repository at this point in the history
Signed-off-by: Romy <[email protected]>
  • Loading branch information
romayalon committed Nov 28, 2023
1 parent b741676 commit 06d2e17
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 78 deletions.
3 changes: 3 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,9 @@ config.NSFS_VERSIONING_ENABLED = true;

config.NSFS_NC_DEFAULT_CONF_DIR = '/etc/noobaa.conf.d';
config.NSFS_TEMP_CONF_DIR_NAME = '.noobaa-config-nsfs';
// config files should allow access to the owner of the files
config.BASE_MODE_CONFIG_FILE = 0o600;
config.BASE_MODE_CONFIG_DIR = 0o700;

// NSFS_RESTORE_ENABLED can override internal autodetection and will force
// the use of restore for all objects.
Expand Down
9 changes: 7 additions & 2 deletions docs/design/NonContainerizedNSFSDesign.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NSFS FS Standalone
# NSFS Non Containerized

Running noobaa-core standalone is useful for development, testing, or deploying in Linux without depending on Kubernetes, NSFS FS is different from the simple standalone in such a way that it doesn't depend on the Noobaa postgres db. All the Global configurations, Accounts, and Bucket related schemas are saved in FS. And it gives a more lightweight flavor to the Noobaa standalone version. Permissions are handled by uid and gid, or by providing a distinguished name (LDAP/AD) that will be resolved to uid/gid by the operating system.
Running noobaa-core non containerized is useful for development, testing, or deploying in Linux without depending on Kubernetes, NSFS FS is different from the simple standalone in such a way that it doesn't depend on the Noobaa postgres db. All the Global configurations, Accounts, and Bucket related schemas are saved in FS. And it gives a more lightweight flavor to the Noobaa standalone version. Permissions are handled by uid and gid, or by providing a distinguished name (LDAP/AD) that will be resolved to uid/gid by the operating system.

Users can switch between Noobaa standalone and NSFS FS standalone by adding/removing the argument `config_dir`.

Expand Down Expand Up @@ -122,3 +122,8 @@ To simplify the flow new SDK `BucketSpaceFS` is added for the NSFS FS standalone
- Reuse the simple `BucketSpaceSimpleFS` code by extending it.
- Implements the requirements from the top.
- CLI: node nsfs --config_dir /fs-config-root/


### Config file permissions
- Configuration files created under accounts/ or buckets/ will have 600 permissions (read, write, execute) for the owner of the config file only.
- config_file created by manage_nsfs.js CLI tool will be owned by the user who ran the command.
112 changes: 69 additions & 43 deletions src/cmd/manage_nsfs.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
/* Copyright (C) 2020 NooBaa */
'use strict';

const fs_utils = require('../util/fs_utils');
const SensitiveString = require('../util/sensitive_string');
const minimist = require('minimist');
const path = require('path');
const fs = require('fs');
const P = require('../util/promise');
const _ = require('lodash');
const config = require('../../config');
const native_fs_utils = require('../util/native_fs_utils');
Expand Down Expand Up @@ -108,23 +105,24 @@ const accounts_dir_name = '/accounts';
const access_keys_dir_name = '/access_keys';

async function check_and_create_config_dirs(config_root) {
const pre_req_paths = [
const pre_req_dirs = [
config_root,
path.join(config_root, buckets_dir_name),
path.join(config_root, accounts_dir_name),
path.join(config_root, access_keys_dir_name)
];
for (const p of pre_req_paths) {
for (const dir_path of pre_req_dirs) {
try {
const dir_exists = await fs_utils.file_exists(p);
const fs_context = get_process_fs_context();
const dir_exists = await config_file_exists(fs_context, dir_path);
if (dir_exists) {
console.log('NSFS config root dir exists:', p);
console.log('nsfs.check_and_create_config_dirs: config dir exists:', dir_path);
} else {
await fs_utils.create_path(p);
console.log('NSFS config root dir was created:', p);
await native_fs_utils._create_path(dir_path, fs_context, config.BASE_MODE_CONFIG_DIR);
console.log('nsfs.check_and_create_config_dirs: config dir was created:', dir_path);
}
} catch (err) {
console.error('nsfs.check_and_create_config_dirs could not create pre requisite path', p);
console.error('nsfs.check_and_create_config_dirs: could not create pre requisite path', dir_path);
}
}
}
Expand Down Expand Up @@ -160,17 +158,18 @@ async function main(argv = minimist(process.argv.slice(2))) {
const from_file = argv.from_file ? String(argv.from_file) : '';
if (resources_type === 'account') {
await account_management(argv, config_root, from_file);
}
if (resources_type === 'bucket') {
} else if (resources_type === 'bucket') {
await bucket_management(argv, config_root, from_file);
} else {
throw new Error('Invalid config type, available config types are account/bucket');
}
} catch (err) {
console.error('NSFS Manage command: exit on error', err.stack || err);
process.exit(2);
}
}

function get_root_fs_context(config_root_backend) {
function get_process_fs_context(config_root_backend) {
return {
uid: process.getuid(),
gid: process.getgid(),
Expand All @@ -190,7 +189,8 @@ async function fetch_bucket_data(argv, config_root, from_file) {
const action = argv._[1] || '';
let data;
if (from_file) {
const raw_data = await fs.promises.readFile(from_file);
const fs_context = get_process_fs_context();
const raw_data = (await nb_native().fs.readFile(fs_context, from_file)).data;
data = JSON.parse(raw_data.toString());
}
if (!data) {
Expand Down Expand Up @@ -249,8 +249,15 @@ async function add_bucket_config_file(data, buckets_config_path, config_root_bac
print_bucket_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);
try {
native_fs_utils.validate_bucket_creation({ name: data.name.unwrap()});
} catch (err) {
console.error('Error: Invalid bucket name');
print_bucket_usage();
return;
}

const fs_context = get_process_fs_context(config_root_backend);
const full_bucket_config_path = get_config_file_path(buckets_config_path, data.name);
const exists = await config_file_exists(fs_context, full_bucket_config_path);
if (exists) {
Expand Down Expand Up @@ -283,8 +290,8 @@ async function update_bucket_config_file(data, bucket_config_path, config_root_b
print_bucket_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);

const fs_context = get_process_fs_context(config_root_backend);

const cur_name = data.name;
const update_name = data.new_name && cur_name && data.new_name.unwrap() !== cur_name.unwrap();
Expand All @@ -296,6 +303,14 @@ async function update_bucket_config_file(data, bucket_config_path, config_root_b
return;
}

try {
native_fs_utils.validate_bucket_creation({ name: data.new_name.unwrap()});
} catch (err) {
console.error('Error: Invalid bucket name');
print_bucket_usage();
return;
}

data.name = data.new_name;

const cur_bucket_config_path = get_config_file_path(bucket_config_path, cur_name.unwrap());
Expand All @@ -321,8 +336,8 @@ async function delete_bucket_config_file(data, buckets_config_path, config_root_
print_bucket_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);

const fs_context = get_process_fs_context(config_root_backend);
const full_bucket_config_path = get_config_file_path(buckets_config_path, data.name);
await native_fs_utils.delete_config_file(fs_context, buckets_config_path, full_bucket_config_path);
}
Expand All @@ -342,6 +357,7 @@ async function manage_bucket_operations(action, data, config_root, config_root_b
const bucket_names = buckets.map(item => (item.name));
console.log('Bucket list', bucket_names);
} else {
console.error('Invalid action, available actions are add, status, update, delete, list');
print_bucket_usage();
}
}
Expand All @@ -357,7 +373,8 @@ async function fetch_account_data(argv, config_root, from_file) {
let data;
const action = argv._[1] || '';
if (from_file) {
const raw_data = await fs.promises.readFile(from_file);
const fs_context = get_process_fs_context();
const raw_data = (await nb_native().fs.readFile(fs_context, from_file)).data;
data = JSON.parse(raw_data.toString());
}
if (!data) {
Expand Down Expand Up @@ -437,8 +454,8 @@ async function add_account_config_file(data, accounts_path, access_keys_path, co
print_account_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);

const fs_context = get_process_fs_context(config_root_backend);
const access_key = data.access_keys[0].access_key;
const full_account_config_path = get_config_file_path(accounts_path, data.name);
const full_account_config_access_key_path = get_symlink_config_file_path(access_keys_path, access_key);
Expand All @@ -455,7 +472,7 @@ async function add_account_config_file(data, accounts_path, access_keys_path, co

data = JSON.stringify(data);
await native_fs_utils.create_config_file(fs_context, accounts_path, full_account_config_path, data);
await native_fs_utils._create_path(access_keys_path, fs_context);
await native_fs_utils._create_path(access_keys_path, fs_context, config.BASE_MODE_CONFIG_DIR);
await nb_native().fs.symlink(fs_context, full_account_config_path, full_account_config_access_key_path);
}

Expand All @@ -465,8 +482,8 @@ async function update_account_config_file(data, accounts_path, access_keys_path,
print_account_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);

const fs_context = get_process_fs_context(config_root_backend);
const cur_name = data.name;
const cur_access_key = data.access_keys[0].access_key;
const update_name = data.new_name && cur_name && data.new_name.unwrap() !== cur_name.unwrap();
Expand Down Expand Up @@ -519,8 +536,8 @@ async function delete_account_config_file(data, accounts_path, access_keys_path,
print_account_usage();
return;
}
// TODO: support non root fs context
const fs_context = get_root_fs_context(config_root_backend);

const fs_context = get_process_fs_context(config_root_backend);
const account_config_path = get_config_file_path(accounts_path, data.name);
const access_key_config_path = get_symlink_config_file_path(access_keys_path, data.access_keys[0].access_key.unwrap());
await native_fs_utils.delete_config_file(fs_context, accounts_path, account_config_path);
Expand Down Expand Up @@ -561,8 +578,8 @@ async function manage_account_operations(action, data, config_root, config_root_
if (!data.wide) accounts = accounts.map(item => (item.name));
console.log('Account list:', accounts);
} else {
console.error('Account action not found.');
console.warn(ARGUMENTS.trimStart());
console.error('Invalid action, available actions are add, status, update, delete, list');
print_account_usage();
}
}

Expand All @@ -572,32 +589,39 @@ async function manage_account_operations(action, data, config_root, config_root_
* @param {string} config_path
*/
async function list_config_files(config_path) {
const entries = await fs.promises.readdir(config_path);
const config_files = entries.filter(entree => entree.endsWith('.json'));
const resources = await P.map(config_files, config_file_name => {
const full_path = path.join(config_path, config_file_name);
return get_view_config_data(full_path);
});
return resources;
const fs_context = get_process_fs_context();
const entries = await nb_native().fs.readdir(fs_context, config_path);

const config_files_list = [];
for (const entry of entries) {
if (entry.name.endsWith('.json')) {
const full_path = path.join(config_path, entry.name);
const data = await get_view_config_data(full_path);
config_files_list.push(data);
}
}
return config_files_list;
}


/**
* get_config_data will read a config file and return its content from it
* @param {fs.PathLike} config_file_path
* @param {string} config_file_path
*/
async function get_config_data(config_file_path) {
const data = await fs.promises.readFile(config_file_path);
const fs_context = get_process_fs_context();
const { data } = await nb_native().fs.readFile(fs_context, config_file_path);
const resources = JSON.parse(data.toString());
return resources;
}

/**
* get_view_config_data will read a config file and return its content ready to be printed
* @param {fs.PathLike} config_file_path
* @param {string} config_file_path
*/
async function get_view_config_data(config_file_path) {
const data = await fs.promises.readFile(config_file_path);
const fs_context = get_process_fs_context();
const { data } = await nb_native().fs.readFile(fs_context, config_file_path);
const resources = _.omit(JSON.parse(data.toString()), ['access_keys']);
if (resources.nsfs_account_config) {
resources.new_buckets_path = resources.nsfs_account_config.new_buckets_path;
Expand Down Expand Up @@ -629,7 +653,8 @@ async function validate_bucket_add_args(data, update) {
console.error('Error: Bucket new_name can not be used on add command, please remove the --new_name flag');
return false;
}
const bucket_dir_stat = await fs_utils.file_exists(data.path);
const fs_context = get_process_fs_context();
const bucket_dir_stat = await config_file_exists(fs_context, data.path);
if (!bucket_dir_stat) {
console.error('Error: Path should be a valid dir path', data.path);
return false;
Expand Down Expand Up @@ -668,7 +693,8 @@ async function validate_account_add_args(data, update) {
console.error('Error: Account new_access_key can not be used on add command, please remove the --new_access_key flag');
return false;
}
const bucket_dir_stat = await fs_utils.file_exists(data.nsfs_account_config.new_buckets_path);
const fs_context = get_process_fs_context();
const bucket_dir_stat = await config_file_exists(fs_context, data.nsfs_account_config.new_buckets_path);
if (!bucket_dir_stat) {
console.error('Error: new_buckets_path should be a valid dir path');
return false;
Expand Down
2 changes: 1 addition & 1 deletion src/cmd/nsfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ async function main(argv = minimist(process.argv.slice(2))) {
}
}

async function verify_gpfslib() {
function verify_gpfslib() {
if (!nb_native().fs.gpfs) {
dbg.event({
code: "noobaa_gpfslib_missing",
Expand Down
Loading

0 comments on commit 06d2e17

Please sign in to comment.