Skip to content

Commit 8204d2a

Browse files
authored
Merge pull request #9307 from shirady/iam-user-policy-error-message
IAM | User Without IAM User Policy Improve Error Message
2 parents c0ac4c1 + 15a0277 commit 8204d2a

File tree

4 files changed

+51
-8
lines changed

4 files changed

+51
-8
lines changed

src/endpoint/iam/iam_utils.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,33 @@ function get_iam_username(requested_account_name) {
6666
return requested_account_name.split(iam_constants.IAM_SPLIT_CHARACTERS)[0];
6767
}
6868

69+
/**
70+
* _create_detailed_message_for_iam_user_access_in_s3 returns a detailed message with details needed for user who
71+
* tried to perform S3 operation
72+
* - resource_arn is only relevant for operations related to a bucket
73+
* @param {object} user_account
74+
* @param {string|string[]} method
75+
* @param {string} resource_arn
76+
*/
77+
function _create_detailed_message_for_iam_user_access_in_s3(user_account, method, resource_arn) {
78+
const owner_account_id = get_owner_account_id(user_account);
79+
const arn_for_requesting_account = create_arn_for_user(owner_account_id,
80+
get_iam_username(user_account.name.unwrap()), user_account.iam_path);
81+
const full_action_name = Array.isArray(method) && method.length > 1 ? method[1] : method; // special case for get_object_attributes
82+
83+
const message_start = `User: ${arn_for_requesting_account} is not authorized to perform: ${full_action_name} `;
84+
const message_resource = `on resource: ${resource_arn} `;
85+
const message_end = `because no identity-based policy allows the ${full_action_name} action`;
86+
87+
let message_with_details;
88+
if (full_action_name === 's3:ListAllMyBuckets') {
89+
message_with_details = message_start + message_end;
90+
} else {
91+
message_with_details = message_start + message_resource + message_end;
92+
}
93+
return message_with_details;
94+
}
95+
6996
/**
7097
* parse_max_items converts the input to the needed type
7198
* assuming that we've got only sting type
@@ -852,4 +879,5 @@ exports.validate_tag_user_params = validate_tag_user_params;
852879
exports.validate_untag_user_params = validate_untag_user_params;
853880
exports.validate_list_user_tags_params = validate_list_user_tags_params;
854881
exports.get_owner_account_id = get_owner_account_id;
882+
exports._create_detailed_message_for_iam_user_access_in_s3 = _create_detailed_message_for_iam_user_access_in_s3;
855883

src/endpoint/s3/s3_rest.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const http_utils = require('../../util/http_utils');
1414
const signature_utils = require('../../util/signature_utils');
1515
const config = require('../../../config');
1616
const s3_utils = require('./s3_utils');
17+
const { _create_detailed_message_for_iam_user_access_in_s3 } = require('../iam/iam_utils'); // for IAM policy
1718

1819
const S3_MAX_BODY_LEN = 4 * 1024 * 1024;
1920

@@ -331,7 +332,7 @@ async function authorize_request_policy(req) {
331332
throw new S3Error(S3Error.AccessDenied);
332333
}
333334

334-
// TODO - move the function and throw message error with details
335+
// TODO - move the function
335336
async function authorize_request_iam_policy(req) {
336337
const auth_token = req.object_sdk.get_auth_token();
337338
const is_anonymous = !(auth_token && auth_token.access_key);
@@ -341,10 +342,14 @@ async function authorize_request_iam_policy(req) {
341342
const is_iam_user = account.owner !== undefined;
342343
if (!is_iam_user) return; // IAM policy is only on IAM users (account root user is authorized here)
343344

344-
const resource_arn = _get_arn_from_req_path(req);
345+
const resource_arn = _get_arn_from_req_path(req) || '*'; // special case for list all buckets in an account
345346
const method = _get_method_from_req(req);
346347
const iam_policies = account.iam_user_policies || [];
347-
if (iam_policies.length === 0 && req.object_sdk.nsfs_config_root) return; // We do not have IAM policies in NC yet
348+
if (iam_policies.length === 0) {
349+
if (req.object_sdk.nsfs_config_root) return; // We do not have IAM policies in NC yet
350+
dbg.error('authorize_request_iam_policy: IAM user has no inline policies configured');
351+
_throw_iam_access_denied_error_for_s3_operation(account, method, resource_arn);
352+
}
348353

349354
// parallel policy check
350355
const promises = [];
@@ -359,14 +364,23 @@ async function authorize_request_iam_policy(req) {
359364
const permission_result = await Promise.all(promises);
360365
let has_allow_permission = false;
361366
for (const permission of permission_result) {
362-
if (permission === "DENY") throw new S3Error(S3Error.AccessDenied);
367+
if (permission === "DENY") {
368+
dbg.error('authorize_request_iam_policy: user has explicit DENY inline policy');
369+
_throw_iam_access_denied_error_for_s3_operation(account, method, resource_arn);
370+
}
363371
if (permission === "ALLOW") {
364372
has_allow_permission = true;
365373
}
366374
}
367375
if (has_allow_permission) return;
368-
dbg.log1('authorize_request_iam_policy: user have inline policies but none of them matched the method');
369-
throw new S3Error(S3Error.AccessDenied);
376+
dbg.error('authorize_request_iam_policy: user has inline policies but none of them matched the method');
377+
_throw_iam_access_denied_error_for_s3_operation(account, method, resource_arn);
378+
}
379+
380+
function _throw_iam_access_denied_error_for_s3_operation(requesting_account, method, resource_arn) {
381+
const message_with_details = _create_detailed_message_for_iam_user_access_in_s3(requesting_account, method, resource_arn);
382+
const { code, http_code } = S3Error.AccessDenied;
383+
throw new S3Error({ code, message: message_with_details, http_code});
370384
}
371385

372386
async function authorize_anonymous_access(s3_policy, method, arn_path, req, public_access_block) {
@@ -399,6 +413,7 @@ function _get_method_from_req(req) {
399413
}
400414

401415
function _get_arn_from_req_path(req) {
416+
if (!req.params.bucket) return;
402417
const bucket = req.params.bucket;
403418
const key = req.params.key;
404419
let arn_path = `arn:aws:s3:::${bucket}`;

src/sdk/accountspace_fs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ class AccountSpaceFS {
674674
const full_action_name = get_action_message_title(action);
675675
const account_id_for_arn = this._get_account_owner_id_for_arn(requesting_account);
676676
const arn_for_requesting_account = create_arn_for_user(account_id_for_arn,
677-
requesting_account.name.unwrap(), requesting_account.path);
677+
requesting_account.name.unwrap(), requesting_account.iam_path);
678678
const basic_message = `User: ${arn_for_requesting_account} is not authorized to perform:` +
679679
`${full_action_name} on resource: `;
680680
let message_with_details;

src/util/account_util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,7 @@ function _throw_access_denied_error(action, requesting_account, details, entity)
441441
const full_action_name = get_action_message_title(action);
442442
const account_id_for_arn = _get_account_owner_id_for_arn(requesting_account).toString();
443443
const arn_for_requesting_account = create_arn_for_user(account_id_for_arn, get_iam_username(requesting_account.name.unwrap()),
444-
requesting_account.path || IAM_DEFAULT_PATH);
444+
requesting_account.iam_path || IAM_DEFAULT_PATH);
445445
const basic_message = `User: ${arn_for_requesting_account} is not authorized to perform:` +
446446
`${full_action_name} on resource: `;
447447
let message_with_details;

0 commit comments

Comments
 (0)