Skip to content

Commit 3836ab1

Browse files
committed
IAM_NOOBAA | IAM Create account
1 parent 69d598a commit 3836ab1

File tree

9 files changed

+866
-285
lines changed

9 files changed

+866
-285
lines changed

config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,7 @@ config.ENDPOINT_PORT = Number(process.env.ENDPOINT_PORT) || 6001;
10221022
config.ENDPOINT_SSL_PORT = Number(process.env.ENDPOINT_SSL_PORT) || 6443;
10231023
// Remove the NSFS condition when NSFS starts to support STS.
10241024
config.ENDPOINT_SSL_STS_PORT = Number(process.env.ENDPOINT_SSL_STS_PORT) || (process.env.NC_NSFS_NO_DB_ENV === 'true' ? -1 : 7443);
1025-
config.ENDPOINT_SSL_IAM_PORT = Number(process.env.ENDPOINT_SSL_IAM_PORT) || -1;
1025+
config.ENDPOINT_SSL_IAM_PORT = Number(process.env.ENDPOINT_SSL_IAM_PORT) || 7444;
10261026
// each fork will get port in range [ENDPOINT_FORK_PORT_BASE, ENDPOINT_FORK_PORT_BASE + number of forks - 1)]
10271027
config.ENDPOINT_FORK_PORT_BASE = Number(process.env.ENDPOINT_FORK_PORT_BASE) || 6002;
10281028
config.ALLOW_HTTP = false;

src/endpoint/endpoint.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ const { get_notification_logger } = require('../util/notifications_util');
4444
const ldap_client = require('../util/ldap_client');
4545
const { is_nc_environment } = require('../nc/nc_utils');
4646
const NoobaaEvent = require('../manage_nsfs/manage_nsfs_events_utils').NoobaaEvent;
47+
const BucketSpaceNB = require('../sdk/bucketspace_nb');
48+
const AccountSDK = require('../sdk/account_sdk');
49+
const AccountSpaceNB = require('../sdk/accountspace_nb');
50+
4751
const cluster = /** @type {import('node:cluster').Cluster} */ (
4852
/** @type {unknown} */
4953
(require('node:cluster'))
@@ -67,6 +71,29 @@ const old_umask = process.umask(new_umask);
6771
let fork_count;
6872
dbg.log0('endpoint: replacing old umask: ', old_umask.toString(8), 'with new umask: ', new_umask.toString(8));
6973

74+
75+
// NsfsAccountSDK was based on NsfsObjectSDK
76+
// simple flow was not implemented
77+
class NBAccountSDK extends AccountSDK {
78+
/**
79+
* @param {{
80+
* rpc_client: nb.APIClient;
81+
* internal_rpc_client: nb.APIClient;
82+
* }} args
83+
*/
84+
constructor({rpc_client, internal_rpc_client}) {
85+
const bucketspace = new BucketSpaceNB({ rpc_client, internal_rpc_client });
86+
const accountspace = new AccountSpaceNB({ rpc_client, internal_rpc_client });
87+
88+
super({
89+
rpc_client: rpc_client,
90+
internal_rpc_client: internal_rpc_client,
91+
bucketspace: bucketspace,
92+
accountspace: accountspace,
93+
});
94+
}
95+
}
96+
7097
/**
7198
* @typedef {import('http').IncomingMessage & {
7299
* object_sdk?: ObjectSDK;
@@ -431,6 +458,11 @@ function create_init_request_sdk(rpc, internal_rpc_client, object_io) {
431458
object_io,
432459
stats: endpoint_stats_collector.instance(),
433460
});
461+
req.account_sdk = new NBAccountSDK({
462+
rpc_client,
463+
internal_rpc_client,
464+
//stats: endpoint_stats_collector.instance(),
465+
});
434466
};
435467
return init_request_sdk;
436468
}

src/endpoint/iam/iam_constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const IAM_PARAMETER_NAME = Object.freeze({
5858
NEW_USERNAME: 'NewUserName',
5959
});
6060

61+
const IAM_SPLIT_CHARACTERS = ':';
6162

6263
// EXPORTS
6364
exports.IAM_ACTIONS = IAM_ACTIONS;
@@ -70,3 +71,4 @@ exports.IAM_DEFAULT_PATH = IAM_DEFAULT_PATH;
7071
exports.AWS_NOT_USED = AWS_NOT_USED;
7172
exports.IAM_SERVICE_SMALL_LETTERS = IAM_SERVICE_SMALL_LETTERS;
7273
exports.IAM_PARAMETER_NAME = IAM_PARAMETER_NAME;
74+
exports.IAM_SPLIT_CHARACTERS = IAM_SPLIT_CHARACTERS;

src/sdk/accountspace_nb.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
/* Copyright (C) 2024 NooBaa */
2+
'use strict';
3+
4+
const _ = require('lodash');
5+
const SensitiveString = require('../util/sensitive_string');
6+
const account_util = require('../util/account_util');
7+
const iam_utils = require('../endpoint/iam/iam_utils');
8+
const dbg = require('../util/debug_module')(__filename);
9+
const system_store = require('..//server/system_services/system_store').get_instance();
10+
// const { account_cache } = require('./object_sdk');
11+
const IamError = require('../endpoint/iam/iam_errors').IamError;
12+
const { IAM_ACTIONS, IAM_DEFAULT_PATH, IAM_SPLIT_CHARACTERS } = require('../endpoint/iam/iam_constants');
13+
14+
15+
//const dummy_region = 'us-west-2';
16+
//const dummy_service_name = 's3';
17+
/*
18+
TODO: DISCUSS:
19+
1. IAM API only for account created using IAM API and OBC accounts not from admin, support,
20+
operator and account created using noobaa.
21+
2. Do we need to have two access keys
22+
3. get_access_key_last_used() API call could return dummy values?
23+
*/
24+
25+
/**
26+
* @implements {nb.AccountSpace}
27+
*/
28+
class AccountSpaceNB {
29+
/**
30+
* @param {{
31+
* rpc_client: nb.APIClient;
32+
* internal_rpc_client: nb.APIClient;
33+
* stats?: import('./endpoint_stats_collector').EndpointStatsCollector;
34+
* }} params
35+
*/
36+
constructor({ rpc_client, internal_rpc_client, stats }) {
37+
this.rpc_client = rpc_client;
38+
this.internal_rpc_client = internal_rpc_client;
39+
this.stats = stats;
40+
}
41+
42+
//////////////////////
43+
// ACCOUNT METHODS //
44+
//////////////////////
45+
46+
async create_user(params, account_sdk) {
47+
48+
const action = IAM_ACTIONS.CREATE_USER;
49+
//const requesting_account = account_sdk.requesting_account;
50+
//const root_account = _.find(system_store.data.accounts, account => account.name.unwrap() === requesting_account.name.unwrap());
51+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
52+
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
53+
{ username: params.username, iam_path: params.iam_path });
54+
await account_util._check_username_already_exists(action, params, requesting_account);
55+
const iam_arn = iam_utils.create_arn_for_user(requesting_account._id.toString(), params.username, params.iam_path);
56+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
57+
const req = {
58+
rpc_params: {
59+
name: account_name,
60+
email: account_name,
61+
has_login: false,
62+
s3_access: true,
63+
allow_bucket_creation: true,
64+
owner: requesting_account._id.toString(),
65+
is_iam: true,
66+
iam_arn: iam_arn,
67+
role: 'iam_user',
68+
// TODO: default_resource remove
69+
default_resource: 'noobaa-default-backing-store',
70+
},
71+
account: requesting_account,
72+
};
73+
// CORE CHANGES PENDING - START
74+
const iam_account = await account_util.create_account(req);
75+
// CORE CHANGES PENDING - END
76+
77+
// TODO : Clean account cache
78+
// TODO : Send Event
79+
return {
80+
// TODO : PATH Missing
81+
iam_path: requesting_account.iam_path || IAM_DEFAULT_PATH,
82+
username: params.username,
83+
user_id: iam_account.id,
84+
arn: iam_arn,
85+
create_date: iam_account.creation_date,
86+
};
87+
88+
}
89+
90+
async get_user(params, account_sdk) {
91+
const action = IAM_ACTIONS.GET_USER;
92+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
93+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
94+
const requested_account = system_store.get_account_by_email(account_name);
95+
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
96+
{ username: params.username, iam_path: params.iam_path });
97+
await account_util._check_if_account_exists(action, params.username, requesting_account);
98+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
99+
// CORE CHANGES PENDING - START
100+
//const root_account = system_store.get_account_by_email(requesting_account.email);
101+
// CORE CHANGES PENDING - END
102+
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
103+
const reply = {
104+
user_id: requested_account._id.toString(),
105+
// TODO : IAM PATH
106+
iam_path: requested_account.iam_path || IAM_DEFAULT_PATH,
107+
username: requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0],
108+
arn: requested_account.iam_arn,
109+
// TODO: Dates missing : GAP
110+
create_date: new Date(),
111+
password_last_used: new Date(),
112+
};
113+
return reply;
114+
}
115+
116+
async update_user(params, account_sdk) {
117+
const action = IAM_ACTIONS.UPDATE_USER;
118+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
119+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
120+
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
121+
{ username: params.username, iam_path: params.iam_path });
122+
account_util._check_if_account_exists(action, params.username, requesting_account);
123+
const requested_account = system_store.get_account_by_email(account_name);
124+
let new_iam_path = requested_account.iam_path;
125+
let new_user_name = requested_account.name.unwrap();
126+
const root_account = _.find(system_store.data.accounts, account => account.name.unwrap() === requesting_account.name.unwrap());
127+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
128+
account_util._check_if_requested_is_owned_by_root_account(action, root_account, requested_account);
129+
if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
130+
if (params.new_username !== undefined) new_user_name = params.new_username;
131+
const iam_arn = iam_utils.create_arn_for_user(root_account._id.toString(), new_user_name, new_iam_path);
132+
const new_account_name = new SensitiveString(`${params.new_username}:${requesting_account.name.unwrap()}`);
133+
const updates = {
134+
name: new_account_name,
135+
email: new_account_name,
136+
iam_arn: iam_arn,
137+
};
138+
// CORE CHANGES PENDING - START
139+
system_store.make_changes({
140+
update: {
141+
accounts: [{
142+
_id: requested_account._id,
143+
$set: _.omitBy(updates, _.isUndefined),
144+
}]
145+
}
146+
});
147+
// CORE CHANGES PENDING - END
148+
// TODO : Clean account cache
149+
// TODO : Send Event
150+
return {
151+
// TODO: IAM path needs to be saved
152+
iam_path: requested_account.iam_path || IAM_DEFAULT_PATH,
153+
username: requested_account.name.unwrap(),
154+
user_id: requested_account._id.toString(),
155+
arn: iam_arn
156+
};
157+
158+
}
159+
160+
async delete_user(params, account_sdk) {
161+
const action = IAM_ACTIONS.DELETE_USER;
162+
// GAP - we do not have the user iam_path at this point (error message)
163+
//const requesting_account = account_sdk.requesting_account;
164+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
165+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
166+
const requested_account = system_store.get_account_by_email(account_name);
167+
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
168+
await account_util._check_if_account_exists(action, params.username, requesting_account);
169+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
170+
//const root_account = system_store.get_account_by_email(requesting_account.email);
171+
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
172+
// TODO: DELETE INLINE POLICY : Manually
173+
// TODO: DELETE ACCESS KEY : manually
174+
const req = {
175+
system: system_store.data.systems[0],
176+
account: requested_account,
177+
};
178+
// CORE CHANGES PENDING - START
179+
return account_util.delete_user(req, requested_account);
180+
// CORE CHANGES PENDING - END
181+
// TODO : clean account cache
182+
183+
}
184+
185+
async list_users(params, account_sdk) {
186+
const action = IAM_ACTIONS.LIST_USERS;
187+
//const requesting_account = account_sdk.requesting_account;
188+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
189+
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { });
190+
const is_truncated = false; // GAP - no pagination at this point
191+
192+
const root_name = requesting_account.name.unwrap();
193+
// CORE CHANGES PENDING - START
194+
const requesting_account_iam_users = _.filter(system_store.data.accounts, function(acc) {
195+
if (!acc.name.unwrap().includes(IAM_SPLIT_CHARACTERS)) {
196+
return false;
197+
}
198+
return acc.name.unwrap().split(IAM_SPLIT_CHARACTERS)[1] === root_name;
199+
});
200+
let members = _.map(requesting_account_iam_users, function(iam_user) {
201+
const member = {
202+
user_id: iam_user._id.toString(),
203+
iam_path: iam_user.iam_path || IAM_DEFAULT_PATH,
204+
username: iam_user.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0],
205+
arn: iam_user.iam_arn,
206+
create_date: iam_user.creation_date,
207+
password_last_used: Date.now(), // GAP
208+
};
209+
return member;
210+
});
211+
// CORE CHANGES PENDING - END
212+
members = members.sort((a, b) => a.username.localeCompare(b.username));
213+
return { members, is_truncated };
214+
}
215+
216+
/////////////////////////////////
217+
// ACCOUNT ACCESS KEY METHODS //
218+
/////////////////////////////////
219+
220+
async create_access_key(params, account_sdk) {
221+
// TODO
222+
dbg.log0('AccountSpaceNB.create_access_key:', params);
223+
const { code, http_code, type } = IamError.NotImplemented;
224+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
225+
}
226+
227+
async get_access_key_last_used(params, account_sdk) {
228+
dbg.log0('AccountSpaceNB.get_access_key_last_used:', params);
229+
const { code, http_code, type } = IamError.NotImplemented;
230+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
231+
}
232+
233+
async update_access_key(params, account_sdk) {
234+
dbg.log0('AccountSpaceNB.update_access_key:', params);
235+
const { code, http_code, type } = IamError.NotImplemented;
236+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
237+
}
238+
239+
async delete_access_key(params, account_sdk) {
240+
dbg.log0('AccountSpaceNB.delete_access_key:', params);
241+
const { code, http_code, type } = IamError.NotImplemented;
242+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
243+
}
244+
245+
async list_access_keys(params, account_sdk) {
246+
dbg.log0('AccountSpaceNB.list_access_keys:', params);
247+
const { code, http_code, type } = IamError.NotImplemented;
248+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
249+
}
250+
251+
////////////////////
252+
// POLICY METHODS //
253+
////////////////////
254+
}
255+
256+
// EXPORTS
257+
module.exports = AccountSpaceNB;

0 commit comments

Comments
 (0)