Skip to content

Commit 48d50d3

Browse files
committed
IAM | Authorize requests for IAM users according to IAM user inline policy
Signed-off-by: shirady <[email protected]>
1 parent 477d3ae commit 48d50d3

File tree

4 files changed

+66
-8
lines changed

4 files changed

+66
-8
lines changed

src/api/account_api.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,15 @@ module.exports = {
749749
},
750750
nsfs_account_config: {
751751
$ref: 'common_api#/definitions/nsfs_account_config'
752+
},
753+
iam_user_policies: {
754+
type: 'array',
755+
items: {
756+
$ref: 'common_api#/definitions/iam_user_policy',
757+
}
758+
},
759+
owner: {
760+
type: 'string'
752761
}
753762
},
754763
},

src/endpoint/s3/s3_bucket_policy_utils.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -149,20 +149,21 @@ async function _is_object_version_fit(req, predicate, value) {
149149
return res;
150150
}
151151

152-
async function has_bucket_policy_permission(policy, account, method, arn_path, req, disallow_public_access = false) {
152+
async function has_bucket_policy_permission(policy, account, method, arn_path, req, disallow_public_access = false,
153+
should_pass_principal = true) {
153154
const [allow_statements, deny_statements] = _.partition(policy.Statement, statement => statement.Effect === 'Allow');
154155

155156
// the case where the permission is an array started in op get_object_attributes
156157
const method_arr = Array.isArray(method) ? method : [method];
157158

158159
// look for explicit denies
159160
const res_arr_deny = await is_statement_fit_of_method_array(
160-
deny_statements, account, method_arr, arn_path, req); // No need to disallow in "DENY"
161+
deny_statements, account, method_arr, arn_path, req, undefined, should_pass_principal); // No need to disallow in "DENY"
161162
if (res_arr_deny.every(item => item)) return 'DENY';
162163

163164
// look for explicit allows
164165
const res_arr_allow = await is_statement_fit_of_method_array(
165-
allow_statements, account, method_arr, arn_path, req, disallow_public_access);
166+
allow_statements, account, method_arr, arn_path, req, disallow_public_access, should_pass_principal);
166167
if (res_arr_allow.every(item => item)) return 'ALLOW';
167168

168169
// implicit deny
@@ -217,15 +218,17 @@ function _is_resource_fit(arn_path, statement) {
217218
return statement.Resource ? resource_fit : !resource_fit;
218219
}
219220

220-
async function is_statement_fit_of_method_array(statements, account, method_arr, arn_path, req, disallow_public_access = false) {
221+
async function is_statement_fit_of_method_array(statements, account, method_arr, arn_path, req,
222+
disallow_public_access = false, should_pass_principal = true) {
221223
return Promise.all(method_arr.map(method_permission =>
222-
_is_statements_fit(statements, account, method_permission, arn_path, req, disallow_public_access)));
224+
_is_statements_fit(statements, account, method_permission, arn_path, req, disallow_public_access, should_pass_principal)));
223225
}
224226

225-
async function _is_statements_fit(statements, account, method, arn_path, req, disallow_public_access = false) {
227+
async function _is_statements_fit(statements, account, method, arn_path, req, disallow_public_access = false,
228+
should_pass_principal = true) {
226229
for (const statement of statements) {
227230
const action_fit = _is_action_fit(method, statement);
228-
const principal_fit = _is_principal_fit(account, statement, disallow_public_access);
231+
const principal_fit = should_pass_principal ? _is_principal_fit(account, statement, disallow_public_access) : true;
229232
const resource_fit = _is_resource_fit(arn_path, statement);
230233
const condition_fit = await _is_condition_fit(statement, req, method);
231234

src/endpoint/s3/s3_rest.js

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ async function authorize_request(req) {
224224
req.object_sdk.authorize_request_account(req),
225225
// authorize_request_policy(req) is supposed to
226226
// allow owners access unless there is an explicit DENY policy
227-
authorize_request_policy(req)
227+
authorize_request_policy(req),
228+
// authorize_request_iam_policy(req) is for users only
229+
authorize_request_iam_policy(req),
228230
]);
229231
}
230232

@@ -316,6 +318,42 @@ async function authorize_request_policy(req) {
316318
throw new S3Error(S3Error.AccessDenied);
317319
}
318320

321+
async function authorize_request_iam_policy(req) {
322+
const auth_token = req.object_sdk.get_auth_token();
323+
const is_anon = !(auth_token && auth_token.access_key);
324+
if (is_anon) return;
325+
326+
const account = req.object_sdk.requesting_account;
327+
const is_iam_user = account.owner !== undefined;
328+
if (!is_iam_user) return;
329+
330+
const resource_arn = _get_arn_from_req_path(req);
331+
const method = _get_method_from_req(req);
332+
const iam_policies = account.iam_user_policies || [];
333+
if (iam_policies.length === 0) return;
334+
335+
// parallel policy check
336+
const promises = [];
337+
for (const iam_policy of iam_policies) {
338+
// We reusing the bucket policy util function as it is checks the policy document
339+
const promise = s3_bucket_policy_utils.has_bucket_policy_permission(
340+
iam_policy.policy_document, undefined, method, resource_arn, req, undefined, false
341+
);
342+
promises.push(promise);
343+
}
344+
const permission_result = await Promise.all(promises);
345+
let has_allow_permission = false;
346+
for (const permission of permission_result) {
347+
if (permission === "DENY") throw new S3Error(S3Error.AccessDenied);
348+
if (permission === "ALLOW") {
349+
has_allow_permission = true;
350+
}
351+
}
352+
if (has_allow_permission) return;
353+
dbg.log1('authorize_request_iam_policy: user have inline policies but none of them matched the method');
354+
throw new S3Error(S3Error.AccessDenied);
355+
}
356+
319357
async function authorize_anonymous_access(s3_policy, method, arn_path, req, public_access_block) {
320358
if (!s3_policy) throw new S3Error(S3Error.AccessDenied);
321359

src/server/system_services/account_server.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,14 @@ function get_account_info(account, include_connection_cache) {
10491049
};
10501050
info.role_config = account.role_config;
10511051
info.force_md5_etag = account.force_md5_etag;
1052+
1053+
if (account.iam_user_policies) {
1054+
info.iam_user_policies = account.iam_user_policies;
1055+
}
1056+
if (account.owner) {
1057+
info.owner = account.owner._id.toString();
1058+
}
1059+
10521060
return info;
10531061
}
10541062

0 commit comments

Comments
 (0)