Skip to content

Conversation

@naveenpaul1
Copy link
Contributor

@naveenpaul1 naveenpaul1 commented Nov 24, 2025

Describe the Problem

  1. Bucket policy "Principal" validation was based on the email before and it updated with account/user ID
  2. put_bucket_policy validate principal account/user ID
  3. Bucket policy ARN related test added

Explain the Changes

Principal validation

  1. Validate account/user ID

S3 Rest authorize request policy

  1. Account/user ID is added to request policy validation replacing account email. account/user ID based validation for containerized deployments NSFS NC.
  2. Updated test cases(test_s3_bucket_policy.js), previousily these tests where using email for principal for both NSFS non-containerized and acontainerized, With latest changes containerized will use account/user ID and ARN. Enabled account/user ID based test cases also for containerized integration test

GAP: IAM User test cases for ARN and user ID test cases are missing

Testing Instructions:

Un integration test test_s3_bucket_policy.js

  1. Create a bucket(test1) from account(account1)
  2. Put bucket policy to newly created bucket()
s3api put-bucket-policy --bucket test1 --policy '{"Version": "2012-10-17","Statement": [ { "Effect": "Allow", "Principal": { "AWS": [ "${user2_id}" ] }, "Action": [ "s3:*" ], "Resource": [ "arn:aws:s3:::test1/*", "arn:aws:s3:::test1" ] } ]}'

user2_id could be ID of different account or IAM user ID of IAM user(not belongs to account1)
4. Verify the user2 have bucket1 access.

  • Doc added/updated
  • Tests added

Summary by CodeRabbit

  • Bug Fixes

    • More accurate bucket policy enforcement: principal matching accepts multiple identifier forms and evaluates denies before allows to reduce incorrect denials.
    • Owner-account access checks added so owner-based permissions are correctly considered.
  • New Features

    • Improved principal resolution across deployments so policies using root/user identifiers and ARNs are recognized.
  • Tests

    • Expanded bucket policy tests to cover ARN principals and unified account-ID usage; teardown now conditional for NC core tests.
  • Chores

    • Minor logging and formatting clarifications.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 24, 2025

Walkthrough

Normalize bucket-policy principal handling to accept arrays (ARN and ID), add ARN-based principal resolution, propagate array-based principal matching through policy evaluation (DENY before ALLOW), adjust authorization flows to check account/owner ID and ARN, and update tests to cover ARN/ID principal cases.

Changes

Cohort / File(s) Summary
Policy Evaluation Utilities
src/endpoint/s3/s3_bucket_policy_utils.js
Add exported has_bucket_policy_permission(...); normalize account to account_arr (array) and propagate it through _is_principal_fit, is_statement_fit_of_method_array, _is_statements_fit; evaluate DENY before ALLOW; use array includes for principal matching; update debug logs.
S3 REST Authorization
src/endpoint/s3/s3_rest.js
Import get_owner_account_id; always derive account_identifier_id from account._id; reorder/condition permission checks to prefer ID-based check and add owner-based ARN/ID check using get_owner_account_id.
Authorization Server
src/server/common_services/auth_server.js
Build principal as arrays (e.g., [root_ARN, account._id.toString()]); when account has owner derive owner ID via get_owner_account_id and call policy check with [owner_ARN, owner_id]; update comments.
Bucket Server / Principal Resolution
src/server/system_services/bucket_server.js
Add account_exists_by_principal_arn(principal_as_string) to validate/resolve IAM ARNs (returns account or undefined); refactor get_account_by_principal(principal) to detect ARN vs ID and delegate accordingly; update JSDoc param name.
S3 Bucket Policy Tests
src/test/integration_tests/api/s3/test_s3_bucket_policy.js
Introduce explicit account_id variables; update Sid/Principal constructions to use account_id or ARNs; add tests for ARN principals (valid/invalid); unify assertions and logging to use ID/ARN principal handling.
IAM Integration Tests
src/test/integration_tests/api/iam/test_iam_basic_integration.js
After-hook cleanup conditionally deletes config_root only when is_nc_coretest is true.
Account Utilities (formatting)
src/util/account_util.js
Remove an extraneous blank line before _check_if_requesting_account_is_root_account (formatting only).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant s3_rest as s3_rest.js
    participant auth as auth_server.js
    participant policy as s3_bucket_policy_utils.js
    participant bucket as bucket_server.js

    Note over Client,s3_rest: Incoming S3 request with account context
    Client->>s3_rest: request (account)
    s3_rest->>s3_rest: account_identifier_id = account._id

    s3_rest->>auth: check permission by account_id
    auth->>policy: has_bucket_policy_permission(principal=[root_ARN, account_id], ...)
    policy->>policy: normalize principal -> account_arr (array)
    policy->>policy: evaluate DENY statements then ALLOW statements using account_arr (includes match)

    alt owner present
        s3_rest->>auth: owner-based check with owner ARN/ID
        auth->>policy: has_bucket_policy_permission(principal=[owner_ARN, owner_id], ...)
    end

    policy->>bucket: if principal needs existence check -> account_exists_by_principal_arn(principal_as_string)
    bucket-->>policy: return account object or undefined
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • src/endpoint/s3/s3_bucket_policy_utils.js — correctness of account_arr normalization, DENY-before-ALLOW semantics, and principal matching logic.
    • src/server/system_services/bucket_server.js — ARN parsing/validation and return-type semantics (account vs boolean).
    • Authorization flow changes in src/endpoint/s3/s3_rest.js and src/server/common_services/auth_server.js for ordering and multisource principal shaping.
    • Updated tests in src/test/integration_tests/api/s3/test_s3_bucket_policy.js to ensure coverage of ARN vs ID principal paths.

Possibly related PRs

Suggested reviewers

  • aayushchouhan09
  • shirady
  • liranmauda

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: updating IAM Principal validation and S3 permission checks to use account/user IDs instead of emails.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@naveenpaul1 naveenpaul1 force-pushed the iam_permission_by_id branch 2 times, most recently from ca9a081 to 0020baa Compare November 25, 2025 14:25
@pull-request-size pull-request-size bot added size/L and removed size/M labels Nov 25, 2025
@naveenpaul1 naveenpaul1 marked this pull request as ready for review November 25, 2025 14:25
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/server/system_services/bucket_server.js (2)

528-546: Consider removing unnecessary async keyword.

This function doesn't contain any await expressions. Both system_store.data.accounts.find() and system_store.get_account_by_email() appear to be synchronous operations based on usage patterns in the codebase. Marking this function async unnecessarily wraps return values in a Promise.

If this is intentional for consistency with the caller or future changes, this is fine as-is.


548-567: Function name is misleading - returns boolean, not account.

get_account_by_principal implies the function returns an account object, but it actually returns a boolean indicating whether the principal exists. Consider renaming to better reflect the return type:

  • principal_exists
  • is_valid_principal
  • validate_principal
-async function get_account_by_principal(principal) {
+async function principal_exists(principal) {

Alternatively, if a getter pattern is preferred, the function could return the account object (or undefined) and let the caller convert to boolean, which would make the validation callback cleaner and enable future use cases that need the account:

 async function get_account_by_principal(principal) {
     const principal_as_string = principal instanceof SensitiveString ? principal.unwrap() : principal;
     const is_principal_arn = principal_as_string.startsWith('arn:aws:iam::');
     if (is_principal_arn) {
-        const principal_by_arn = await account_exists_by_principal_arn(principal_as_string);
-        dbg.log3('get_account_by_principal: principal_by_arn', principal_by_arn);
-        if (principal_by_arn) return true;
+        return account_exists_by_principal_arn(principal_as_string);
     } else {
-        const account = system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
-        const principal_by_id = account !== undefined;
-        dbg.log3('get_account_by_principal: principal_by_id', principal_by_id);
-        if (principal_by_id) return true;
+        return system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
     }
-    return false;
 }

Then update the callback to convert to boolean:

principal => Boolean(get_account_by_principal(principal))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8204d2a and 0020baa.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (4 hunks)
  • src/endpoint/s3/s3_rest.js (2 hunks)
  • src/server/common_services/auth_server.js (1 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (10 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🧰 Additional context used
📓 Path-based instructions (1)
src/test/**/*.*

⚙️ CodeRabbit configuration file

src/test/**/*.*: Ensure that the PR includes tests for the changes.

Files:

  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧠 Learnings (6)
📓 Common learnings
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/server/common_services/auth_server.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-08-08T13:08:38.361Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9182
File: src/server/system_services/bucket_server.js:1324-1327
Timestamp: 2025-08-08T13:08:38.361Z
Learning: In src/server/system_services/bucket_server.js, the update_all_buckets_default_pool(req) handler expects req.rpc_params.pool_name to be a plain string (not a SensitiveString wrapper), so calling .unwrap() is not needed there.

Applied to files:

  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (6)
src/endpoint/s3/s3_rest.js (1)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/server/common_services/auth_server.js (3)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/endpoint/s3/s3_rest.js (2)
  • account (252-252)
  • account (343-343)
src/server/system_services/bucket_server.js (1)
  • account (561-561)
src/test/integration_tests/api/iam/test_iam_basic_integration.js (1)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (2)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (303-303)
  • _ (4-4)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
src/endpoint/iam/iam_utils.js (1)
  • statement_principal (723-723)
src/server/system_services/bucket_server.js (6)
src/server/system_services/account_server.js (11)
  • SensitiveString (15-15)
  • dbg (12-12)
  • account (69-69)
  • account (117-117)
  • account (135-137)
  • account (159-159)
  • account (207-207)
  • account (330-330)
  • account (693-693)
  • account (957-957)
  • system_store (17-17)
src/api/account_api.js (1)
  • SensitiveString (4-4)
src/cmd/manage_nsfs.js (1)
  • SensitiveString (25-25)
src/api/common_api.js (1)
  • SensitiveString (4-4)
src/cmd/nsfs.js (1)
  • SensitiveString (38-38)
src/sdk/bucketspace_nb.js (1)
  • dbg (7-7)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-jest-unit-tests
  • GitHub Check: run-package-lock-validation
🔇 Additional comments (14)
src/test/integration_tests/api/iam/test_iam_basic_integration.js (1)

76-78: LGTM!

The conditional cleanup correctly ensures folder_delete only runs when is_nc_coretest is true, matching the condition under which config_root is set in the before hook. This prevents attempting to delete an undefined path in containerized deployments.

src/server/common_services/auth_server.js (1)

558-566: LGTM!

The change to pass [arn, account._id.toString()] as an array aligns with the updated has_bucket_policy_permission signature that now accepts an array of account identifiers. This enables policy principal matching against both the ARN and the account ID, supporting the PR's objective of ID-based validation.

src/endpoint/s3/s3_rest.js (3)

255-257: LGTM!

The simplification to always source account_identifier_id from account._id is correct. Both NC and containerized deployments now validate bucket policies against the account ID, aligning with the PR's objective of ID-based principal validation.


313-320: LGTM!

The name-based permission check is now correctly scoped to NSFS NC deployments only, and restricted to root accounts (account.owner === undefined). This maintains backward compatibility for NC while preventing IAM users from matching by name.


322-330: LGTM!

The ARN-based permission check is now explicitly limited to containerized deployments (!is_nc_deployment). This correctly separates the authorization paths: NC uses ID + name, while containerized uses ID + ARN.

src/endpoint/s3/s3_bucket_policy_utils.js (3)

153-168: LGTM!

The JSDoc update and account_arr normalization correctly document the function's ability to accept either a string or an array of account identifiers. The normalization pattern Array.isArray(account) ? account : [account] ensures consistent array handling downstream.


204-222: LGTM!

The refactored _is_principal_fit function correctly uses account_arr.includes(principal.unwrap()) for matching, allowing a principal to match any identifier in the array (ID, ARN, or name). The wildcard check principal.unwrap() === '*' is evaluated first, maintaining correct anonymous access handling.


239-251: LGTM!

The signature changes to is_statement_fit_of_method_array and _is_statements_fit correctly propagate account_arr through the call chain, ensuring consistent array-based principal matching throughout the policy evaluation flow.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (5)

68-70: LGTM!

The addition of user_a_account_id and user_b_account_id variables provides a consistent way to reference account IDs across tests, abstracting the difference between NC (_id) and containerized (id) response formats.


127-134: LGTM!

The account ID extraction correctly handles the different response formats: NC deployments use _id while containerized deployments use id. The added console logging will aid debugging.


156-157: LGTM!

The principal construction correctly differentiates between NC (account name) and containerized (ARN from account ID) deployments, aligning with how each deployment validates principals.


315-362: LGTM!

The test cases for principal by account ID correctly use user_a_account_id in both the policy Sid and Principal AWS fields, properly testing ID-based authorization for both NC and containerized deployments.


482-484: Tests enabled for both deployment types.

The removal of this.skip() guards for NC-only tests (// if (!is_nc_coretest) this.skip();) enables these account-ID-based policy tests to run on both NC and containerized deployments. This aligns with the PR goal of enabling ID-based validation across deployment types.

Also applies to: 517-519, 583-585

src/server/system_services/bucket_server.js (1)

572-573: No issues found - async callback handling is correct.

The callback is properly awaited by validate_s3_policy at line 303 of s3_bucket_policy_utils.js: const account = await get_account_handler(principal);. The Promise returned by get_account_by_principal is correctly handled.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/endpoint/s3/s3_rest.js (1)

252-332: Public access block is not enforced for ARN-based principal checks

In the containerized branch you call has_bucket_policy_permission for account_identifier_arn with public_access_block?.restrict_public_buckets as a bare boolean, so disallow_public_access is never set for that path. As a result, statements with Principal: "*" can still allow access via the ARN check even when restrict_public_buckets is enabled, weakening the bucket public access block semantics.

Mirror the ID-based call and pass an options object so wildcard principals are ignored consistently when public access is restricted:

-    // Check bucket policy permission against ARN only for containerized deployments.
-    // Policy permission is validated by account arn
-    if (!is_nc_deployment && permission_by_id !== "DENY") {
-        permission_by_arn = await s3_bucket_policy_utils.has_bucket_policy_permission(
-            s3_policy, account_identifier_arn, method, arn_path, req, public_access_block?.restrict_public_buckets
-        );
-        dbg.log3('authorize_request_policy: permission_by_arn', permission_by_arn);
-    }
+    // Check bucket policy permission against ARN only for containerized deployments.
+    // Policy permission is validated by account arn
+    if (!is_nc_deployment && permission_by_id !== "DENY") {
+        permission_by_arn = await s3_bucket_policy_utils.has_bucket_policy_permission(
+            s3_policy,
+            account_identifier_arn,
+            method,
+            arn_path,
+            req,
+            { disallow_public_access: public_access_block?.restrict_public_buckets }
+        );
+        dbg.log3('authorize_request_policy: permission_by_arn', permission_by_arn);
+    }
src/endpoint/s3/s3_bucket_policy_utils.js (1)

204-222: _is_principal_fit does not handle string principals anymore

_is_principal_fit now unconditionally calls principal.unwrap(), but principals in stored policies can be plain strings (e.g., "*", account IDs, ARNs) as well as SensitiveString instances (see validate_s3_policy and allows_public_access). For string principals this will throw at runtime and break policy evaluation.

You should normalize the principal value to a string in a type-safe way before comparison:

function _is_principal_fit(account_arr, statement, ignore_public_principal = false) {
    let statement_principal = statement.Principal || statement.NotPrincipal;

    let principal_fit = false;
    statement_principal = statement_principal.AWS ? statement_principal.AWS : statement_principal;
    for (const principal of _.flatten([statement_principal])) {
-        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal, account_arr);
-        if ((principal.unwrap() === '*') || account_arr.includes(principal.unwrap())) {
-            if (ignore_public_principal && principal.unwrap() === '*' && statement.Principal) {
+        const principal_val = typeof principal === 'string' ? principal : principal.unwrap();
+        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal_val, account_arr);
+        if ((principal_val === '*') || account_arr.includes(principal_val)) {
+            if (ignore_public_principal && principal_val === '*' && statement.Principal) {
                 // Ignore the "fit" if ignore_public_principal is requested
                 continue;
             }

            principal_fit = true;
            break;
        }
    }
    return statement.Principal ? principal_fit : !principal_fit;
}

This restores compatibility with existing policies/tests that use string principals while still supporting SensitiveString-wrapped values.

♻️ Duplicate comments (1)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

397-404: a_principal now correctly uses user A’s account ID in complex policy tests

Recomputing a_principal with user_a_account_id for the containerized branch fixes the earlier issue where the DENY-by-name statement was unintentionally targeting user B in these conflict-policy tests.

🧹 Nitpick comments (2)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

68-70: Account ID test setup is consistent; consider small cleanups

The introduction of user_a_account_id / user_b_account_id and using them for ID/ARN-based policies looks consistent with how IDs are used elsewhere, and it gives you a clear separation between NC (_id) and containerized (id) cases.

Two minor nits you might consider:

  • The console.log('user_*_account_details', ...) lines are useful for debugging but can be noisy in regular test runs; you may want to drop them once debugging is done.
  • To avoid ever mixing raw ObjectIDs with strings, you could normalize user_*_account_id to String(...) in both branches and then reuse that everywhere (including the ARN construction), instead of sometimes reaching back to *_account_details.id.

Also applies to: 127-135, 155-158

src/server/system_services/bucket_server.js (1)

526-567: ARN/ID principal resolution is sound; verify if name-based principals are still needed

The new helpers:

  • account_exists_by_principal_arn(principal_as_string) correctly:

    • Validates the IAM ARN format via AWS_IAM_ARN_REGEXP.
    • Resolves ...:root to an account by _id.
    • Resolves ...:user/.../username to an IAM user account via the ${username}:${account_id} email convention.
  • get_account_by_principal(principal) then:

    • Treats values starting with arn:aws:iam:: as ARNs and delegates to the above.
    • Otherwise looks up system_store.data.accounts by _id.toString().
    • Returns a boolean, which validate_s3_policy uses only for truthiness, so the change in return type is safe.

One thing to double-check: get_account_by_principal intentionally does not handle plain account names/emails. If any bucket policy entry points (especially NC/NSFS ones) still rely on Principal being an account name rather than ID/ARN, those policies will now be rejected as malformed. If that’s not intended, you may want to extend get_account_by_principal with a name/email lookup branch for those environments.

Also applies to: 569-591

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0020baa and 2a88698.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (4 hunks)
  • src/endpoint/s3/s3_rest.js (2 hunks)
  • src/server/common_services/auth_server.js (1 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (11 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
  • src/server/common_services/auth_server.js
🧰 Additional context used
📓 Path-based instructions (1)
src/test/**/*.*

⚙️ CodeRabbit configuration file

src/test/**/*.*: Ensure that the PR includes tests for the changes.

Files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧠 Learnings (5)
📓 Common learnings
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/endpoint/s3/s3_rest.js
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (4)
src/endpoint/s3/s3_rest.js (2)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (17)
  • s3_bucket_policy_utils (15-15)
  • s3_policy (1701-1711)
  • s3_policy (1728-1738)
  • s3_policy (1755-1765)
  • s3_policy (1782-1790)
  • s3_policy (1807-1815)
  • s3_policy (1832-1840)
  • s3_policy (1857-1857)
  • s3_policy (1877-1887)
  • s3_policy (1899-1908)
  • s3_policy (1920-1929)
  • s3_policy (1941-1950)
  • s3_policy (1962-1977)
  • s3_policy (2118-2127)
  • s3_policy (2144-2153)
  • s3_policy (2170-2179)
  • s3_policy (2197-2206)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (3)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (303-303)
  • _ (4-4)
src/server/common_services/auth_server.js (5)
  • account (104-104)
  • account (189-195)
  • account (318-322)
  • s3_bucket_policy_utils (19-19)
  • _ (4-4)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (17)
  • policy (183-192)
  • policy (201-209)
  • policy (218-227)
  • policy (235-249)
  • policy (265-273)
  • policy (288-296)
  • policy (316-324)
  • policy (338-346)
  • policy (411-416)
  • policy (446-451)
  • policy (487-492)
  • policy (521-526)
  • policy (552-557)
  • policy (586-591)
  • policy (617-622)
  • policy (647-656)
  • _ (13-13)
src/endpoint/iam/iam_utils.js (1)
  • statement_principal (723-723)
src/server/system_services/bucket_server.js (2)
src/server/system_services/account_server.js (10)
  • SensitiveString (15-15)
  • account (69-69)
  • account (117-117)
  • account (135-137)
  • account (159-159)
  • account (207-207)
  • account (330-330)
  • account (693-693)
  • account (957-957)
  • system_store (17-17)
src/api/account_api.js (1)
  • SensitiveString (4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run-jest-unit-tests
  • GitHub Check: run-package-lock-validation
  • GitHub Check: Build Noobaa Image
🔇 Additional comments (2)
src/endpoint/s3/s3_bucket_policy_utils.js (1)

153-189: Account array normalization in policy checks looks correct

Normalizing method to method_arr and account to account_arr, then threading account_arr through is_statement_fit_of_method_array / _is_statements_fit, matches the new use cases (e.g., [ARN, account_id] principals) and keeps deny/allow handling consistent.

No issues from my side with this structure.

Also applies to: 239-252

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

315-362: ID-based bucket policy tests cover key allow/deny combinations

The new tests that use user_a_account_id (principal by account ID, and the various mixed allow/deny-by-ID vs by-name conflict cases) provide good coverage for the updated ID-first matching semantics in authorize_request_policy. They exercise:

  • Pure ID principals (allowing user A, denying B).
  • Interactions between ID-based and name-based principals across multiple statements.
  • Precedence of DENY over ALLOW when the same identity is referenced by both name and ID.

These look consistent with the intended behavior and complement the name-based tests gated on is_nc_coretest.

Also applies to: 387-395, 406-611

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/endpoint/s3/s3_rest.js (1)

322-330: Bug: passing boolean instead of options object to has_bucket_policy_permission in ARN path

In the ARN path you call:

permission_by_arn = await s3_bucket_policy_utils.has_bucket_policy_permission(
    s3_policy, account_identifier_arn, method, arn_path, req, public_access_block?.restrict_public_buckets
);

But has_bucket_policy_permission now destructures its last parameter as an object:

async function has_bucket_policy_permission(policy, account, method, arn_path, req,
    { disallow_public_access = false, should_pass_principal = true } = {}) { ... }

Passing a boolean will throw at runtime when public_access_block?.restrict_public_buckets is true/false (cannot destructure properties from a boolean), impacting containerized deployments where this flag is set and ARN evaluation is reached.

Apply this fix to always pass an options object:

-    if (!is_nc_deployment && permission_by_id !== "DENY") {
-        permission_by_arn = await s3_bucket_policy_utils.has_bucket_policy_permission(
-            s3_policy, account_identifier_arn, method, arn_path, req, public_access_block?.restrict_public_buckets
-        );
+    if (!is_nc_deployment && permission_by_id !== "DENY") {
+        permission_by_arn = await s3_bucket_policy_utils.has_bucket_policy_permission(
+            s3_policy,
+            account_identifier_arn,
+            method,
+            arn_path,
+            req,
+            { disallow_public_access: public_access_block?.restrict_public_buckets }
+        );
     }
🧹 Nitpick comments (2)
src/test/integration_tests/api/iam/test_iam_basic_integration.js (1)

75-79: Guarding folder_delete with is_nc_coretest is correct for non-NC runs

Tying the cleanup to is_nc_coretest matches where config_root is initialized and avoids deleting with an undefined path in containerized runs. If fs_utils.folder_delete is async, consider awaiting it to ensure Mocha waits for cleanup, but that’s optional and consistent with prior behavior.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

127-135: Optional: remove or gate noisy debug logs in setup

The console.log('user_a_account_details', ...) / console.log('user_b_account_details', ...) lines are useful during development but will spam test output in normal runs. Consider removing them or guarding behind an env flag.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a88698 and 93dc942.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (5 hunks)
  • src/endpoint/s3/s3_rest.js (2 hunks)
  • src/server/common_services/auth_server.js (1 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (11 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🧰 Additional context used
📓 Path-based instructions (1)
src/test/**/*.*

⚙️ CodeRabbit configuration file

src/test/**/*.*: Ensure that the PR includes tests for the changes.

Files:

  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧠 Learnings (6)
📓 Common learnings
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/server/common_services/auth_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/common_services/auth_server.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/common_services/auth_server.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/server/common_services/auth_server.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-08-08T13:08:38.361Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9182
File: src/server/system_services/bucket_server.js:1324-1327
Timestamp: 2025-08-08T13:08:38.361Z
Learning: In src/server/system_services/bucket_server.js, the update_all_buckets_default_pool(req) handler expects req.rpc_params.pool_name to be a plain string (not a SensitiveString wrapper), so calling .unwrap() is not needed there.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (5)
src/test/integration_tests/api/iam/test_iam_basic_integration.js (1)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/endpoint/s3/s3_rest.js (1)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (2)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (303-303)
  • _ (4-4)
src/server/common_services/auth_server.js (1)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/server/system_services/bucket_server.js (3)
src/util/account_util.js (2)
  • SensitiveString (9-9)
  • system_store (12-12)
src/server/system_services/account_server.js (10)
  • SensitiveString (15-15)
  • account (69-69)
  • account (117-117)
  • account (135-137)
  • account (159-159)
  • account (207-207)
  • account (330-330)
  • account (693-693)
  • account (957-957)
  • system_store (17-17)
src/api/account_api.js (1)
  • SensitiveString (4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run-jest-unit-tests
  • GitHub Check: run-package-lock-validation
  • GitHub Check: Build Noobaa Image
🔇 Additional comments (13)
src/server/common_services/auth_server.js (1)

631-636: Principal array (ARN + ID) wiring in has_bucket_action_permission looks correct

Using account.owner._id.toString() / account._id.toString() for ARN creation and passing [arn, account._id.toString()] into has_bucket_policy_permission matches the new principal-array semantics in s3_bucket_policy_utils and system_store account shapes in this file.

src/endpoint/s3/s3_rest.js (2)

255-257: ID-based policy checks wiring is consistent

Defining account_identifier_id = account._id and using it as the primary principal for both NC and containerized flows aligns with the move to ID-based authorization and matches how principals are validated elsewhere.


301-321: NC name-based fallback logic matches documented behavior

The NC path that:

  • checks permission_by_id first, then
  • falls back to permission_by_name only when account.owner === undefined

is consistent with the comment that NC supports principals by account name for backward compatibility while preferring IDs. The allow/deny precedence (DENY short‑circuit) remains intact.

src/endpoint/s3/s3_bucket_policy_utils.js (4)

153-189: Principal array normalization in has_bucket_policy_permission is sound

Normalizing account into account_arr and threading it through deny/allow evaluation lets callers pass either a single principal or an array (e.g., [ARN, id]) without changing existing semantics. The implicit-deny vs explicit-ALLOW/DENY logic remains unchanged.


204-222: Principal matching via account_arr.includes() correctly supports multi-identifier principals

Updating _is_principal_fit to compare principal.unwrap() against account_arr allows a caller to supply multiple identifiers (ID, ARN, etc.) for the same account and still get correct matches, while preserving handling of '*' and ignore_public_principal.


239-259: Refactored statement-fit helpers correctly propagate account_arr and options

is_statement_fit_of_method_array and _is_statements_fit now consistently pass account_arr and the { disallow_public_access, should_pass_principal } options through, keeping IAM inline-policy evaluation (where should_pass_principal === false) and bucket-policy principal checks aligned.


374-377: Root ARN construction now uses stringified account ID

Switching create_arn_for_root calls in get_bucket_policy_principal_arn to account._id.toString() avoids implicit object interpolation and aligns with other ID-based principal construction in the PR.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (4)

68-70: Account ID derivation for tests matches NC vs containerized layouts

Initializing user_a_account_id / user_b_account_id from _id in NC and from .id in containerized runs aligns with how accounts are represented in each deployment mode and lets the same tests exercise ID-based principals across both.


315-362: ID-based principal tests correctly exercise bucket policy behavior

The new “principal by account ID” tests for:

  • put/get bucket policy, and
  • putObject permissions

correctly use user_a_account_id in both Sid and Principal.AWS, and validate that user A is allowed while user B is denied. This matches the intended shift to ID-based principals.


387-411: Rebinding a_principal to ID/ARN keeps complex-policy tests aligned

Reassigning a_principal for the complex “conflict statements” suite using create_arn_for_root(user_a_account_id.toString()) on containerized deployments keeps those tests consistent with the rest of the ARN-based principal logic.


2112-2306: New ARN-principal tests provide good negative and positive coverage

The “Bucket policy with ARN principal” describe block adds:

  • Multiple negative cases (invalid principal value, malformed ARN, bad account ID, non-existent IAM user ARN) that all assert MalformedPolicy.
  • Positive cases for valid account-root ARNs covering both GetObject and PutObject, plus a mismatch case ensuring other accounts are denied.

All are correctly skipped on NC (is_nc_coretest), so they focus on containerized behavior where ARN-based principals are expected. This is a solid test addition for the new validation and evaluation paths.

src/server/system_services/bucket_server.js (2)

526-546: ARN-based principal resolution helper is consistent with new IAM conventions

account_exists_by_principal_arn() correctly:

  • Validates the ARN via AWS_IAM_ARN_REGEXP.
  • Distinguishes root vs user ARNs using the suffix and segment 5.
  • Resolves root ARNs by account _id.toString() and IAM-user ARNs by the synthesized ${username}:${account_id} email.

This matches the introduced IAM-email convention and is an appropriate central place for ARN→account existence checks.


569-591: Wiring get_account_by_principal into validate_s3_policy is appropriate

Passing principal => get_account_by_principal(principal) into validate_s3_policy ensures bucket policies are validated against existing accounts (by ID or ARN, and—after adjustment—by name), and the subsequent public-access-block check remains unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/endpoint/s3/s3_bucket_policy_utils.js (1)

162-189: Fix principal type handling in _is_principal_fit to avoid runtime errors

The new array-based principal logic is good, but _is_principal_fit now assumes every principal has an .unwrap() method:

if ((principal.unwrap() === '*') || account_arr.includes(principal.unwrap())) {
    ...
}

Elsewhere in this module (e.g., validate_s3_policy), principals are explicitly handled as either plain strings or SensitiveString:

(typeof principal === 'string') ? principal !== '*' : principal.unwrap() !== '*'

With the current implementation, any policy that stores principals as plain strings (very common for JSON-based policies: "Principal": {"AWS": "..."} or "*") will cause principal.unwrap to be undefined and throw at evaluation time, breaking all authorization paths that hit these statements.

You can fix this by normalizing principals to strings and slightly tightening account_arr handling:

@@
 async function has_bucket_policy_permission(policy, account, method, arn_path, req,
     { disallow_public_access = false, should_pass_principal = true } = {}) {
@@
-    const method_arr = Array.isArray(method) ? method : [method];
-    const account_arr = Array.isArray(account) ? account : [account];
+    const method_arr = Array.isArray(method) ? method : [method];
+    // Normalize account into an array; allow empty when no principal is supplied
+    const account_arr = (account === undefined || account === null)
+        ? []
+        : (Array.isArray(account) ? account : [account]);
@@
-function _is_principal_fit(account_arr, statement, ignore_public_principal = false) {
+function _is_principal_fit(account_arr, statement, ignore_public_principal = false) {
@@
-    for (const principal of _.flatten([statement_principal])) {
-        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal, account_arr);
-        if ((principal.unwrap() === '*') || account_arr.includes(principal.unwrap())) {
-            if (ignore_public_principal && principal.unwrap() === '*' && statement.Principal) {
+    for (const principal of _.flatten([statement_principal])) {
+        const principal_str = typeof principal === 'string' ? principal : principal.unwrap();
+        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal_str, account_arr);
+        if ((principal_str === '*') || account_arr.includes(principal_str)) {
+            if (ignore_public_principal && principal_str === '*' && statement.Principal) {
                 // Ignore the "fit" if ignore_public_principal is requested
                 continue;
             }
@@
-    return statement.Principal ? principal_fit : !principal_fit;
+    return statement.Principal ? principal_fit : !principal_fit;
 }

This keeps the new multi-principal behavior (including [arn, account_id] arrays) while remaining compatible with both string and SensitiveString principals and avoiding runtime exceptions.

Also applies to: 204-222, 239-259, 374-377

♻️ Duplicate comments (1)
src/server/system_services/bucket_server.js (1)

528-567: Re‑add name‑based principal validation for NC buckets in get_account_by_principal

get_account_by_principal now only accepts principals that are:

  • A valid IAM ARN resolvable by account_exists_by_principal_arn, or
  • A string equal to some account._id.toString().

Principals specified by account name (e.g., "alice") are rejected as invalid, which conflicts with:

  • Existing NC behavior/tests such as should put/get bucket policy - principal by account name in test_s3_bucket_policy.js (NC-only).
  • The comment in s3_rest.authorize_request_policy that NC supports principals by account name for backward compatibility.

This is the same regression previously flagged in an earlier review; the issue still exists in the current implementation. To preserve NC compatibility while keeping containerized deployments ID/ARN-centric, you can add a name-based fallback after the ID lookup:

 async function get_account_by_principal(principal) {
     const principal_as_string = principal instanceof SensitiveString ? principal.unwrap() : principal;
     const is_principal_arn = principal_as_string.startsWith('arn:aws:iam::');
     if (is_principal_arn) {
         const principal_by_arn = await account_exists_by_principal_arn(principal_as_string);
         dbg.log3('get_account_by_principal: principal_by_arn', principal_by_arn);
         if (principal_by_arn) return true;
     } else {
-        const account = system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
-        const principal_by_id = account !== undefined;
-        dbg.log3('get_account_by_principal: principal_by_id', principal_by_id);
-        if (principal_by_id) return true;
+        // ID-based lookup (containerized & NC)
+        const account_by_id = system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
+        const principal_by_id = account_by_id !== undefined;
+        dbg.log3('get_account_by_principal: principal_by_id', principal_by_id);
+        if (principal_by_id) return true;
+
+        // Backward-compatible name-based lookup (primarily for NC)
+        const account_by_name = system_store.data.accounts.find(acc =>
+            acc.name && acc.name.unwrap && acc.name.unwrap() === principal_as_string
+        );
+        const principal_by_name = account_by_name !== undefined;
+        dbg.log3('get_account_by_principal: principal_by_name', principal_by_name);
+        if (principal_by_name) return true;
     }
     return false;
 }

You may also want to update the function’s docstring to reflect that it returns a boolean “exists” flag rather than an account object.

This keeps containerized flows on ID/ARNs while restoring NC’s documented/supported name-based principals.

Also applies to: 569-575

🧹 Nitpick comments (2)
src/endpoint/s3/s3_rest.js (1)

252-347: Confirm account_identifier_id is always a string for policy checks

The new flow (ID → name for NC, ID → ARN for containerized) looks correct and matches the intent to prioritize ID-based evaluation. One nuance: account_identifier_id is set to account._id and then passed to has_bucket_policy_permission, which ultimately compares principals using account_arr.includes(principal_str).

If req.object_sdk.requesting_account._id is not already a plain string in all deployments (e.g., remains an ObjectId in some paths), ID-based principals in policies will never match and only the name/ARN fallbacks will be effective.

To make this robust across NC and containerized, consider normalizing to string at the source:

-    const account_identifier_id = account._id;
+    const account_identifier_id = account._id && account._id.toString();

This keeps behavior unchanged when _id is already a string but protects you if an ObjectId ever reaches this layer.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

68-70: Clarify account ID source and consider trimming debug logs

The introduction of user_a_account_id / user_b_account_id and their use in principals/denies looks consistent with the move to ID-based principals, but there are a couple of minor cleanups you might consider:

  • In containerized mode you now rely on user_*_account_details.id to equal account._id.toString(). If that invariant ever changes, ID-based principal validation in get_account_by_principal (which compares against _id.toString()) will break silently. It may be safer (and clearer) to derive the exported IDs explicitly from _id.toString() when possible.
  • The console.log('user_*_account_details', ...) lines are useful when debugging but can be noisy in regular integration runs; consider dropping or gating them if they’re no longer needed.

Example tweak:

-    user_a_account_id = is_nc_coretest ? user_a_account_details._id : user_a_account_details.id;
+    user_a_account_id = is_nc_coretest
+        ? user_a_account_details._id
+        : user_a_account_details._id.toString();
@@
-    user_b_account_id = is_nc_coretest ? user_b_account_details._id : user_b_account_details.id;
+    user_b_account_id = is_nc_coretest
+        ? user_b_account_details._id
+        : user_b_account_details._id.toString();
@@
-    console.log('user_a_account_details', user_a_account_details);
+    // console.log('user_a_account_details', user_a_account_details);
@@
-    console.log('user_b_account_details', user_b_account_details);
+    // console.log('user_b_account_details', user_b_account_details);

This keeps tests aligned with how principals are validated while reducing noise.

Also applies to: 120-135, 155-158, 387-395

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 93dc942 and 78b78c4.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (5 hunks)
  • src/endpoint/s3/s3_rest.js (2 hunks)
  • src/server/common_services/auth_server.js (1 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (11 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
  • src/server/common_services/auth_server.js
🧰 Additional context used
📓 Path-based instructions (1)
src/test/**/*.*

⚙️ CodeRabbit configuration file

src/test/**/*.*: Ensure that the PR includes tests for the changes.

Files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧠 Learnings (5)
📓 Common learnings
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/endpoint/s3/s3_rest.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (2)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (4)
src/test/system_tests/test_utils.js (1)
  • is_nc_coretest (48-48)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (303-303)
  • _ (4-4)
src/server/system_services/bucket_server.js (3)
  • account (561-561)
  • _ (7-7)
  • _ (1803-1810)
src/util/account_util.js (7)
  • account (30-42)
  • account (196-196)
  • account (304-304)
  • account (325-325)
  • account (356-356)
  • account (622-622)
  • _ (4-4)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
src/endpoint/iam/iam_utils.js (1)
  • statement_principal (723-723)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: run-package-lock-validation
  • GitHub Check: run-jest-unit-tests
  • GitHub Check: Build Noobaa Image
🔇 Additional comments (1)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

2112-2307: New ARN principal tests thoroughly exercise validation paths

The “Bucket policy with ARN principal” suite looks solid:

  • Negative cases cover invalid principal values, malformed ARNs, wrong account IDs, and IAM user ARNs, and now explicitly assert.fail when putBucketPolicy does not throw.
  • Positive cases cover valid root account ARNs for both read and write paths, plus a cross-account negative check (valid_arn_b with s3_a.putObject).

This provides good end-to-end coverage of the new ARN-based validation in bucket_server.get_account_by_principal and account_exists_by_principal_arn.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/endpoint/s3/s3_bucket_policy_utils.js (1)

204-222: Make _is_principal_fit robust to string vs SensitiveString principals

Here you unconditionally call principal.unwrap():

for (const principal of _.flatten([statement_principal])) {
    dbg.log1(..., principal, account_arr);
    if ((principal.unwrap() === '*') || account_arr.includes(principal.unwrap())) {
        ...
    }
}

Elsewhere (validate_s3_policy, allows_public_access), principals are treated as string | SensitiveString and guarded with typeof principal === 'string' ? ... : principal.unwrap(). With the current code:

  • If a policy principal is a plain string (e.g., 'arn:aws:iam::...:root' or '*'), principal.unwrap will be undefined, leading to TypeError: principal.unwrap is not a function at evaluation time instead of a clean AccessDenied/MalformedPolicy.

To align with the rest of the file and avoid runtime errors, extract a string first:

 function _is_principal_fit(account_arr, statement, ignore_public_principal = false) {
@@
-    for (const principal of _.flatten([statement_principal])) {
-        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal, account_arr);
-        if ((principal.unwrap() === '*') || account_arr.includes(principal.unwrap())) {
+    for (const principal of _.flatten([statement_principal])) {
+        const principal_str = typeof principal === 'string' ? principal : principal.unwrap();
+        dbg.log1('bucket_policy: ', statement.Principal ? 'Principal' : 'NotPrincipal', ' fit?', principal_str, account_arr);
+        if ((principal_str === '*') || account_arr.includes(principal_str)) {
-            if (ignore_public_principal && principal.unwrap() === '*' && statement.Principal) {
+            if (ignore_public_principal && principal_str === '*' && statement.Principal) {
                 // Ignore the "fit" if ignore_public_principal is requested
                 continue;
             }

This keeps the new array-based matching while safely handling both raw strings and SensitiveStrings, consistent with validate_s3_policy and allows_public_access.

🧹 Nitpick comments (4)
src/endpoint/s3/s3_bucket_policy_utils.js (1)

162-189: Clarify return semantics of has_bucket_policy_permission in JSDoc

The new has_bucket_policy_permission implementation looks consistent: it normalizes method and account to arrays, checks DENY statements first, then ALLOW statements, and finally returns 'IMPLICIT_DENY'.

To aid future readers, consider updating the JSDoc to explicitly document the return type and values, e.g.:

/**
 * ...
 * @returns {'ALLOW' | 'DENY' | 'IMPLICIT_DENY'}
 */

This makes it clearer at call sites (like authorize_request_policy and IAM policy checks) that they must compare against string literals rather than booleans.

src/endpoint/s3/s3_rest.js (2)

252-333: Normalize account_identifier_id to a string to avoid ID/principal type mismatches

In authorize_request_policy you now use:

const account_identifier_id = account._id;
...
permission_by_id = await has_bucket_policy_permission(
    s3_policy, account_identifier_id, method, arn_path, req, ...
);

and later compare policy principals via _is_principal_fit, which does:

account_arr.includes(principal_str);

where principal_str is a string derived from the policy document. For containerized flows, account._id may still be an ObjectId when loaded into requesting_account before RPC serialization, while principals in bucket policies are always strings. Relying on implicit serialization risks silent mismatches ('id' !== ObjectId('id')).

To make this robust and self-documenting, coerce to string up front:

-    const account_identifier_id = account._id;
+    const account_identifier_id = account._id && account._id.toString();

(or String(account._id)).

That keeps the new ID-based principal support intact for both NC and containerized deployments, and ensures consistent comparison in has_bucket_policy_permission.


334-347: Owner-based principal checks for IAM users are correct; consider tiny naming/log nit

The new block:

  • Uses get_owner_account_id(account) to derive the root account ID.
  • Builds owner_account_identifier_arn via create_arn_for_root(owner_account_id).
  • Checks permissions with [owner_account_identifier_arn, owner_account_id].

This correctly mirrors the auth_server path and ensures IAM users inherit DENY/ALLOW semantics expressed against their owner root account, which aligns with S3 behavior.

Minor nit: the debug log still refers to permission_by_arn_owner:

dbg.log3('authorize_request_policy permission_by_arn_owner', permission_by_owner);

You might want to adjust the log string to permission_by_owner for clarity, but functionally the logic looks good.

src/server/system_services/bucket_server.js (1)

548-567: Align get_account_by_principal JSDoc and simplify wiring into validate_s3_policy

Two small points in get_account_by_principal:

  1. The JSDoc still says “Validate and return account by principal ARN and account id”, but the function now returns a boolean (existence) instead of an account object, which is exactly what validate_s3_policy expects. Consider updating the comment to e.g.:
/**
 * Check whether a bucket policy principal refers to an existing account.
 * @param {SensitiveString | string} principal
 * @returns {Promise<boolean>}
 */
  1. In put_bucket_policy you can pass the function directly instead of wrapping it:
- await bucket_policy_utils.validate_s3_policy(req.rpc_params.policy, bucket.name,
-     principal => get_account_by_principal(principal));
+ await bucket_policy_utils.validate_s3_policy(
+     req.rpc_params.policy,
+     bucket.name,
+     get_account_by_principal
+ );

Functionally equivalent, slightly clearer.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78b78c4 and 19dbd17.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (5 hunks)
  • src/endpoint/s3/s3_rest.js (5 hunks)
  • src/server/common_services/auth_server.js (2 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (11 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/endpoint/s3/s3_rest.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/endpoint/s3/s3_rest.js
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (4)
src/server/common_services/auth_server.js (3)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/endpoint/s3/s3_rest.js (5)
  • account (252-252)
  • account (358-358)
  • s3_bucket_policy_utils (10-10)
  • owner_account_id (337-337)
  • owner_account_identifier_arn (338-338)
src/endpoint/iam/iam_utils.js (2)
  • owner_account_id (78-78)
  • owner_account_id (851-851)
src/endpoint/s3/s3_rest.js (2)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
  • account (303-303)
  • dbg (5-5)
src/endpoint/iam/iam_utils.js (2)
  • owner_account_id (78-78)
  • owner_account_id (851-851)
src/endpoint/s3/s3_bucket_policy_utils.js (2)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (17)
  • policy (183-192)
  • policy (201-209)
  • policy (218-227)
  • policy (235-249)
  • policy (265-273)
  • policy (288-296)
  • policy (316-324)
  • policy (338-346)
  • policy (411-416)
  • policy (446-451)
  • policy (487-492)
  • policy (521-526)
  • policy (552-557)
  • policy (586-591)
  • policy (617-622)
  • policy (647-656)
  • _ (13-13)
src/endpoint/iam/iam_utils.js (1)
  • statement_principal (723-723)
src/server/system_services/bucket_server.js (2)
src/server/system_services/account_server.js (10)
  • SensitiveString (15-15)
  • account (69-69)
  • account (117-117)
  • account (135-137)
  • account (159-159)
  • account (207-207)
  • account (330-330)
  • account (693-693)
  • account (957-957)
  • system_store (17-17)
src/api/account_api.js (1)
  • SensitiveString (4-4)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-package-lock-validation
  • GitHub Check: run-jest-unit-tests
🔇 Additional comments (5)
src/endpoint/s3/s3_bucket_policy_utils.js (2)

239-252: Account-array threading through statement evaluation looks correct

is_statement_fit_of_method_array and _is_statements_fit now accept account_arr and forward it into _is_principal_fit, while still honoring should_pass_principal for IAM inline policies:

  • For bucket policies and anonymous access, principals are evaluated against the provided array (e.g. [ARN, id]).
  • For IAM user inline policies (should_pass_principal === false), principal_fit is forced to true, so only action/resource/condition are checked, which matches AWS semantics.

This change correctly generalizes the principal matching logic without altering existing behavior for IAM policies.


374-377: Root-account ARN now consistently uses stringified account_id

Using account._id.toString() when building the root-account ARN:

create_arn_for_root(account._id.toString());

is the right choice and avoids subtle mismatches when policy principals store IDs as strings. This is consistent with the S3 REST path, where IDs are already strings due to RPC serialization.

src/server/system_services/bucket_server.js (1)

528-546: ARN principal resolution logic is sound; ensure AWS_IAM_ARN_REGEXP covers both root and user cases

account_exists_by_principal_arn validates principal ARNs and maps:

  • ...:root → account by _id.
  • ...:user[/path/]<username> → account by email ${username}:${account_id}.

The structure matches your forward create_arn_for_user/create_arn_for_root helpers and should correctly resolve both root and IAM-user principals as long as AWS_IAM_ARN_REGEXP accepts both forms, which it appears intended to do.

No functional issues spotted here.

src/server/common_services/auth_server.js (2)

643-660: I'll help you verify the review comment by checking the implementation of get_owner_account_id and whether it handles both account.owner shapes correctly. Let me start by locating and examining this function.
<function_calls>

rg -n "get_owner_account_id" src --type=js -B 2 -A 10

</function_calls>
<function_calls>

fd -name "iam_utils*" src

</function_calls>


631-637: I'm encountering persistent cloning failures that prevent me from accessing the repository to verify the concerns in the review comment. Since I cannot run shell scripts to:

  1. Confirm whether iam_utils actually exports create_arn_for_user and create_arn_for_root
  2. Compare the implementations and output semantics with s3_bucket_policy_utils helpers
  3. Check for other usages of these functions in the codebase

I need to mark this review comment for manual verification:

Verify that iam_utils exports create_arn_for_user and create_arn_for_root with consistent semantics

The review comment raises two valid structural concerns:

  1. API existence: Confirm that iam_utils actually exports create_arn_for_user() and create_arn_for_root() functions. If these don't exist, the code will fail at runtime.

  2. Consistency and duplication: Verify whether s3_bucket_policy_utils.get_bucket_policy_principal_arn() (already used in s3_rest.js) produces identical output to the current approach. If so, consolidating to use the shared utility would prevent future divergence.

A developer should:

  • Examine src/server/common_services/iam_utils.js to confirm the function exports
  • Compare implementations with s3_bucket_policy_utils helpers
  • Consider consolidating to a single canonical implementation if feasible

const is_nc_deployment = Boolean(req.object_sdk.nsfs_config_root);
const account_identifier_name = is_nc_deployment ? account.name.unwrap() : account.email.unwrap();
const account_identifier_id = is_nc_deployment ? account._id : undefined;
// Both NSFS NC and containerized will validate bucket policy against acccount id.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In NC, the account identifier by ID served both accounts and users, as we didn't have the ARN yet.

Copy link
Contributor Author

@naveenpaul1 naveenpaul1 Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shirady started a thread in slack to discuss this, If we decide to ID only for account, then that flow will be taken care in different PR

Signed-off-by: Naveen Paul <napaul@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

151-158: Principal construction for NC vs containerized and re-assignment of a_principal now match intent

  • admin_principal, a_principal, and b_principal now:

    • Use plain names (EMAIL, user_a, user_b) for NC coretest.
    • Use create_arn_for_root(<account_id>) for containerized runs.
  • Before the “complex policies (conflict statements)” tests, a_principal is re-initialized as:

    • user_a for NC.
    • create_arn_for_root(user_a_account_id.toString()) for containerized.

This fixes the earlier issue where a_principal in containerized tests could be built from the wrong account ID, and aligns the deny-by-name / deny-by-ID tests with the intended actor (user_a).

Also applies to: 397-404

src/server/system_services/bucket_server.js (1)

548-567: get_account_by_principal drops name-based validation again; likely breaks NC “principal by account name” policies

Current implementation only treats a principal as valid if:

  • It’s an IAM ARN resolvable by account_exists_by_principal_arn, or
  • It matches some account._id.toString() (ID-based principal).

It no longer checks for principals specified by account name, even though:

  • NC-specific tests like should put/get bucket policy - principal by account name (gated by is_nc_coretest) still construct bucket policies with Principal: { AWS: user_a_account_details.name }.
  • s3_rest.authorize_request_policy still has explicit support for NC name principals (via permission_by_name checks).

With this implementation, a new or updated policy that uses "alice" (or any account name) as a principal will be rejected as invalid during validate_s3_policy, which appears to reintroduce the regression called out in the earlier review. This risks breaking NC scenarios and those NC-only tests.

I recommend restoring a name-based lookup fallback in the non-ARN branch so that NC name principals remain valid alongside ID/ARN principals.

A concrete patch:

 async function get_account_by_principal(principal) {
     const principal_as_string = principal instanceof SensitiveString ? principal.unwrap() : principal;
     const is_principal_arn = principal_as_string.startsWith('arn:aws:iam::');
     if (is_principal_arn) {
         const principal_by_arn = await account_exists_by_principal_arn(principal_as_string);
         dbg.log3('get_account_by_principal: principal_by_arn', principal_by_arn);
         if (principal_by_arn) return true;
     } else {
-        const account = system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
-        const principal_by_id = account !== undefined;
-        dbg.log3('get_account_by_principal: principal_by_id', principal_by_id);
-        if (principal_by_id) return true;
+        // ID-based lookup
+        const account_by_id = system_store.data.accounts.find(acc => acc._id.toString() === principal_as_string);
+        const principal_by_id = account_by_id !== undefined;
+        dbg.log3('get_account_by_principal: principal_by_id', principal_by_id);
+        if (principal_by_id) return true;
+
+        // Backward-compatible name-based lookup (primarily for NC)
+        const account_by_name = system_store.data.accounts.find(acc =>
+            acc.name && acc.name.unwrap && acc.name.unwrap() === principal_as_string
+        );
+        const principal_by_name = account_by_name !== undefined;
+        dbg.log3('get_account_by_principal: principal_by_name', principal_by_name);
+        if (principal_by_name) return true;
     }
     return false;
 }

This keeps containerized behavior focused on IDs/ARNs while maintaining NC compatibility for existing name-based policies and tests.

🧹 Nitpick comments (5)
src/server/common_services/auth_server.js (1)

631-661: Owner-based ARN/ID checks look correct; just ensure get_owner_account_id semantics match auth_server account shape

Logic here correctly:

  • Builds principal array [arn, account._id.toString()] so bucket policies can match by ARN or ID.
  • Adds an owner-account check using const owner_account_id = iam_utils.get_owner_account_id(account); and passes [owner_account_identifier_arn, owner_account_id], while preserving DENY precedence and owner-allow semantics.

Given that in auth_server accounts come directly from system_store and account.owner is an ObjectId reference (with ._id), please double-check that iam_utils.get_owner_account_id(account) is implemented to handle this object form (as opposed to the string form used in s3_rest) so both IAM and root accounts resolve consistently. Based on learnings, this is the main cross-context gotcha to verify.

If you want to make this more obviously robust, you could add a brief comment near the get_owner_account_id(account) call noting that this path receives a system_store account where owner is an ObjectId, to avoid future regressions.

src/endpoint/s3/s3_rest.js (1)

252-333: ID/name/ARN principal ordering is sensible; verify account._id is correct ID in all deployments

This block now:

  • Uses account_identifier_id = account._id for both NC and containerized deployments.
  • Keeps NC-only name-based checking (permission_by_name) gated by is_nc_deployment && account.owner === undefined.
  • Restricts ARN-based checking (permission_by_arn) to non-NC deployments.

This preserves:

  • Backward compatibility for NC name principals.
  • ID-first evaluation with DENY precedence (permission_by_id === "DENY" short-circuits).
  • Environment-specific principal types (name vs ARN).

Please confirm that in both NC and containerized flows req.object_sdk.requesting_account._id indeed matches the identifier used in bucket policy principals (for NC, _id; for containerized, the string account ID you validate in bucket_server). If any flow still relies on account.id, you may need to adjust this line or normalize in the account serialization.

If you confirm _id and the validated ID are always the same string, consider updating the nearby comment to make that guarantee explicit.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (2)

68-70: Account ID derivation looks right; consider dropping debug logs

  • user_a_account_id / user_b_account_id correctly select _id for NC and id for containerized, matching how principals are formed in policies elsewhere in this file.
  • The two console.log calls for user_a_account_details and user_b_account_details are useful for debugging but will add noise to regular test runs; consider removing them or guarding behind an env flag if you no longer need them.

Also applies to: 127-135


2112-2305: ARN principal negative/positive tests look solid; clarify IAM-user-ARN “invalid” comment

The new “Bucket policy with ARN principal” suite:

  • Negative tests:

    • Cover non-ARN values, structurally invalid ARNs, ARNs with non-existent account IDs, and IAM user ARNs that don’t correspond to any account.
    • Correctly call assert.fail after putBucketPolicy when no error is thrown, ensuring the test fails if validation regresses.
  • Positive tests:

    • Verify that policies with valid root ARNs for user_b_account_id allow the expected operations for s3_b and deny for s3_a.

Minor nit at Line 2197: the comment // IAM user ARN formate for account ID(user_b_account_id) has a typo and could more clearly explain why that IAM user ARN is considered invalid (e.g., “no such IAM user exists for this account ID”).

You could tweak the comment to something like:

-            // IAM user ARN formate for account ID(user_b_account_id)
+            // Example IAM user ARN for this account ID; no such user exists, so principal must be rejected
src/server/system_services/bucket_server.js (1)

528-546: ARN parsing and account lookup are correct; consider minor naming/doc tweaks

The account_exists_by_principal_arn helper:

  • Correctly:
    • Validates the ARN against AWS_IAM_ARN_REGEXP.
    • Splits into parts and distinguishes:
      • Root ARNs like arn:aws:iam::<account_id>:root (resolving to account by _id).
      • User ARNs like arn:aws:iam::<account_id>:user/.../<user_name> (resolving via get_account_by_email with user_name:account_id).

Minor nits:

  • Variable names root_sufix and user_sufix are misspelled; renaming to root_suffix / user_suffix would improve clarity.
  • The JSDoc says “Validate and return account by principal ARN” but the function returns either an account object or undefined; that’s fine, but you might want to explicitly mention the return type in the doc for readability.

These are cosmetic and don’t affect behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19dbd17 and dc3f318.

📒 Files selected for processing (7)
  • src/endpoint/s3/s3_bucket_policy_utils.js (4 hunks)
  • src/endpoint/s3/s3_rest.js (5 hunks)
  • src/server/common_services/auth_server.js (2 hunks)
  • src/server/system_services/bucket_server.js (2 hunks)
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js (1 hunks)
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js (11 hunks)
  • src/util/account_util.js (0 hunks)
💤 Files with no reviewable changes (1)
  • src/util/account_util.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/endpoint/s3/s3_bucket_policy_utils.js
  • src/test/integration_tests/api/iam/test_iam_basic_integration.js
🧰 Additional context used
📓 Path-based instructions (1)
src/test/**/*.*

⚙️ CodeRabbit configuration file

src/test/**/*.*: Ensure that the PR includes tests for the changes.

Files:

  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
🧠 Learnings (4)
📚 Learning: 2025-11-19T15:03:42.260Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9291
File: src/server/common_services/auth_server.js:548-554
Timestamp: 2025-11-19T15:03:42.260Z
Learning: In src/server/common_services/auth_server.js, account objects are loaded directly from system_store (e.g., system_store.data.get_by_id()), so account.owner is an object ID reference with an ._id property, not a string. This differs from s3_rest.js where account.owner is a string due to RPC serialization.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-12T04:55:42.193Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_rest.js:258-261
Timestamp: 2025-11-12T04:55:42.193Z
Learning: In the context of S3 REST requests (src/endpoint/s3/s3_rest.js), the account.owner field from req.object_sdk.requesting_account is already a string (account ID) because it comes from RPC serialization where owner._id.toString() is applied in account_server.js. No additional .toString() or ._id extraction is needed when passing account.owner to IAM utility functions.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-18T07:00:17.653Z
Learnt from: naveenpaul1
Repo: noobaa/noobaa-core PR: 9277
File: src/endpoint/s3/s3_bucket_policy_utils.js:357-368
Timestamp: 2025-11-18T07:00:17.653Z
Learning: In NooBaa codebase, account.name is always a SensitiveString instance, so calling account.name.unwrap() is safe without defensive type checks in functions like get_bucket_policy_principal_arn in src/endpoint/s3/s3_bucket_policy_utils.js.

Applied to files:

  • src/server/common_services/auth_server.js
  • src/test/integration_tests/api/s3/test_s3_bucket_policy.js
  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
📚 Learning: 2025-11-13T07:56:23.620Z
Learnt from: shirady
Repo: noobaa/noobaa-core PR: 9281
File: src/server/system_services/account_server.js:1053-1058
Timestamp: 2025-11-13T07:56:23.620Z
Learning: In noobaa-core, account_server.js is only used in containerized deployments, not in NSFS/NC deployments. NSFS/NC deployments have separate account management code in src/manage_nsfs/ directory. Therefore, account_server.js only processes accounts from account_schema.js where owner is an objectid reference, never from nsfs_account_schema.js where owner is a string.

Applied to files:

  • src/endpoint/s3/s3_rest.js
  • src/server/system_services/bucket_server.js
🧬 Code graph analysis (2)
src/server/common_services/auth_server.js (2)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/endpoint/iam/iam_utils.js (3)
  • result (757-757)
  • owner_account_id (78-78)
  • owner_account_id (851-851)
src/endpoint/s3/s3_rest.js (2)
src/endpoint/s3/s3_bucket_policy_utils.js (1)
  • account (303-303)
src/endpoint/iam/iam_utils.js (2)
  • owner_account_id (78-78)
  • owner_account_id (851-851)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Build Noobaa Image
  • GitHub Check: run-package-lock-validation
  • GitHub Check: run-jest-unit-tests
🔇 Additional comments (3)
src/endpoint/s3/s3_rest.js (1)

334-347: Owner-based ARN/ID check for IAM users aligns with bucket-policy semantics

For IAM users on non-NC deployments, this new block:

  • Derives owner_account_id = get_owner_account_id(account) and a root ARN for the owner.
  • Checks the bucket policy against [owner_account_identifier_arn, owner_account_id].
  • Throws immediately on permission_by_owner === "DENY" and only treats it as ALLOW in the final return condition.

This achieves the intended behavior:

  • IAM users inherit bucket access from their owner account when the policy targets the owner by ARN or ID.
  • Explicit DENY on the owner also denies the IAM user, preserving AWS-like semantics where DENY overrides ALLOW.

No functional issues here; the change looks good.

src/test/integration_tests/api/s3/test_s3_bucket_policy.js (1)

315-362: ID-based principal tests correctly exercise ALLOW semantics

The “principal by account ID” tests now:

  • Use Sid strings interpolated with user_a_account_id for clarity.
  • Set Principal: { AWS: user_a_account_id } and exercise both PutBucketPolicy and subsequent PutObject flows.

These test cases are well-aligned with the new ID-based validation in bucket_server.put_bucket_policy and S3 authorization, and they complement the NC name-based tests above.

src/server/system_services/bucket_server.js (1)

569-590: Async principal validation integration with validate_s3_policy looks good

put_bucket_policy now:

  • Calls validate_s3_policy with a principal-check callback: principal => get_account_by_principal(principal).
  • Enforces block_public_policy vs allows_public_access before persisting the policy.

Assuming validate_s3_policy is already prepared to await an async validator (or treat a returned Promise correctly), this wiring is appropriate and keeps all principal-format knowledge localized in get_account_by_principal.

If not already covered by tests, it’s worth confirming that validate_s3_policy awaits (or thens) the callback result, rather than assuming a synchronous boolean.

@naveenpaul1 naveenpaul1 merged commit df4c61b into noobaa:master Nov 26, 2025
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants