Skip to content

Commit ddbfc2a

Browse files
committed
IAM_NOOBAA | IAM Create account
Signed-off-by: Naveen Paul <[email protected]>
1 parent bfc97e5 commit ddbfc2a

File tree

8 files changed

+862
-283
lines changed

8 files changed

+862
-283
lines changed

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: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
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, path: params.iam_path });
54+
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+
iam_path: params.iam_path,
68+
role: 'iam_user',
69+
70+
// TODO: default_resource remove
71+
default_resource: 'noobaa-default-backing-store',
72+
},
73+
account: requesting_account,
74+
};
75+
// CORE CHANGES PENDING - START
76+
const iam_account = await account_util.create_account(req);
77+
// CORE CHANGES PENDING - END
78+
79+
// TODO : Clean account cache
80+
// TODO : Send Event
81+
return {
82+
// TODO : PATH Missing
83+
iam_path: requesting_account.iam_path || IAM_DEFAULT_PATH,
84+
username: params.username,
85+
user_id: iam_account.id,
86+
arn: iam_arn,
87+
create_date: iam_account.create_date,
88+
};
89+
90+
}
91+
92+
async get_user(params, account_sdk) {
93+
const action = IAM_ACTIONS.GET_USER;
94+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
95+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
96+
const requested_account = system_store.get_account_by_email(account_name);
97+
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
98+
{ username: params.username, iam_path: params.iam_path });
99+
account_util._check_if_account_exists(action, params.username, requesting_account);
100+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
101+
// CORE CHANGES PENDING - START
102+
//const root_account = system_store.get_account_by_email(requesting_account.email);
103+
// CORE CHANGES PENDING - END
104+
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
105+
const reply = {
106+
user_id: requested_account._id.toString(),
107+
// TODO : IAM PATH
108+
iam_path: requested_account.iam_path || IAM_DEFAULT_PATH,
109+
username: requested_account.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0],
110+
arn: requested_account.iam_arn,
111+
create_date: new Date(requested_account.last_update),
112+
// TODO: Dates missing : GAP
113+
password_last_used: new Date(),
114+
};
115+
return reply;
116+
}
117+
118+
async update_user(params, account_sdk) {
119+
const action = IAM_ACTIONS.UPDATE_USER;
120+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
121+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
122+
account_util._check_if_requesting_account_is_root_account(action, requesting_account,
123+
{ username: params.username, iam_path: params.iam_path });
124+
account_util._check_if_account_exists(action, params.username, requesting_account);
125+
const requested_account = system_store.get_account_by_email(account_name);
126+
let new_iam_path = requested_account.iam_path;
127+
let new_user_name = requested_account.name.unwrap();
128+
account_util._check_username_already_exists(action, { username: params.new_username }, requesting_account);
129+
const root_account = _.find(system_store.data.accounts, account => account.name.unwrap() === requesting_account.name.unwrap());
130+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
131+
account_util._check_if_requested_is_owned_by_root_account(action, root_account, requested_account);
132+
if (params.new_iam_path !== undefined) new_iam_path = params.new_iam_path;
133+
if (params.new_username !== undefined) new_user_name = params.new_username;
134+
const iam_arn = iam_utils.create_arn_for_user(root_account._id.toString(), new_user_name, new_iam_path);
135+
const new_account_name = new SensitiveString(`${params.new_username}:${requesting_account.name.unwrap()}`);
136+
const updates = {
137+
name: new_account_name,
138+
email: new_account_name,
139+
iam_arn: iam_arn,
140+
};
141+
// CORE CHANGES PENDING - START
142+
await system_store.make_changes({
143+
update: {
144+
accounts: [{
145+
_id: requested_account._id,
146+
$set: _.omitBy(updates, _.isUndefined),
147+
}]
148+
}
149+
});
150+
// CORE CHANGES PENDING - END
151+
// TODO : Clean account cache
152+
// TODO : Send Event
153+
return {
154+
// TODO: IAM path needs to be saved
155+
iam_path: new_iam_path || IAM_DEFAULT_PATH,
156+
username: new_user_name,
157+
user_id: requested_account._id.toString(),
158+
arn: iam_arn
159+
};
160+
161+
}
162+
163+
async delete_user(params, account_sdk) {
164+
const action = IAM_ACTIONS.DELETE_USER;
165+
// GAP - we do not have the user iam_path at this point (error message)
166+
//const requesting_account = account_sdk.requesting_account;
167+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
168+
const account_name = new SensitiveString(`${params.username}:${requesting_account.name.unwrap()}`);
169+
const requested_account = system_store.get_account_by_email(account_name);
170+
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { username: params.username });
171+
account_util._check_if_account_exists(action, params.username, requesting_account);
172+
account_util._check_if_requested_account_is_root_account_or_IAM_user(action, requesting_account, requested_account);
173+
//const root_account = system_store.get_account_by_email(requesting_account.email);
174+
account_util._check_if_requested_is_owned_by_root_account(action, requesting_account, requested_account);
175+
// TODO: DELETE INLINE POLICY : Manually
176+
// TODO: DELETE ACCESS KEY : manually
177+
const req = {
178+
system: system_store.data.systems[0],
179+
account: requested_account,
180+
};
181+
// CORE CHANGES PENDING - START
182+
return account_util.delete_user(req, requested_account);
183+
// CORE CHANGES PENDING - END
184+
// TODO : clean account cache
185+
186+
}
187+
188+
async list_users(params, account_sdk) {
189+
const action = IAM_ACTIONS.LIST_USERS;
190+
//const requesting_account = account_sdk.requesting_account;
191+
const requesting_account = system_store.get_account_by_email(account_sdk.requesting_account.email);
192+
account_util._check_if_requesting_account_is_root_account(action, requesting_account, { });
193+
const is_truncated = false; // GAP - no pagination at this point
194+
195+
const root_name = requesting_account.name.unwrap();
196+
// CORE CHANGES PENDING - START
197+
const requesting_account_iam_users = _.filter(system_store.data.accounts, function(acc) {
198+
if (!acc.name.unwrap().includes(IAM_SPLIT_CHARACTERS)) {
199+
return false;
200+
}
201+
return acc.name.unwrap().split(IAM_SPLIT_CHARACTERS)[1] === root_name;
202+
});
203+
let members = _.map(requesting_account_iam_users, function(iam_user) {
204+
const member = {
205+
user_id: iam_user._id.toString(),
206+
iam_path: iam_user.iam_path || IAM_DEFAULT_PATH,
207+
username: iam_user.name.unwrap().split(IAM_SPLIT_CHARACTERS)[0],
208+
arn: iam_user.iam_arn,
209+
create_date: new Date(iam_user.last_update),
210+
// TODO: Miising password_last_used
211+
password_last_used: Date.now(), // GAP
212+
};
213+
return member;
214+
});
215+
// CORE CHANGES PENDING - END
216+
members = members.sort((a, b) => a.username.localeCompare(b.username));
217+
return { members, is_truncated };
218+
}
219+
220+
/////////////////////////////////
221+
// ACCOUNT ACCESS KEY METHODS //
222+
/////////////////////////////////
223+
224+
async create_access_key(params, account_sdk) {
225+
// TODO
226+
dbg.log0('AccountSpaceNB.create_access_key:', params);
227+
const { code, http_code, type } = IamError.NotImplemented;
228+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
229+
}
230+
231+
async get_access_key_last_used(params, account_sdk) {
232+
dbg.log0('AccountSpaceNB.get_access_key_last_used:', params);
233+
const { code, http_code, type } = IamError.NotImplemented;
234+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
235+
}
236+
237+
async update_access_key(params, account_sdk) {
238+
dbg.log0('AccountSpaceNB.update_access_key:', params);
239+
const { code, http_code, type } = IamError.NotImplemented;
240+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
241+
}
242+
243+
async delete_access_key(params, account_sdk) {
244+
dbg.log0('AccountSpaceNB.delete_access_key:', params);
245+
const { code, http_code, type } = IamError.NotImplemented;
246+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
247+
}
248+
249+
async list_access_keys(params, account_sdk) {
250+
dbg.log0('AccountSpaceNB.list_access_keys:', params);
251+
const { code, http_code, type } = IamError.NotImplemented;
252+
throw new IamError({ code, message: 'NotImplemented', http_code, type });
253+
}
254+
255+
////////////////////
256+
// POLICY METHODS //
257+
////////////////////
258+
}
259+
260+
// EXPORTS
261+
module.exports = AccountSpaceNB;

0 commit comments

Comments
 (0)