Skip to content

block COMPLIANCE to GOVERNANCE retention mode downgrade - #9823

Merged
kajalpareek-lab merged 1 commit into
noobaa:masterfrom
kajalpareek-lab:compliance-downgrade
Aug 18, 2026
Merged

block COMPLIANCE to GOVERNANCE retention mode downgrade#9823
kajalpareek-lab merged 1 commit into
noobaa:masterfrom
kajalpareek-lab:compliance-downgrade

Conversation

@kajalpareek-lab

@kajalpareek-lab kajalpareek-lab commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Reject PutObjectRetention when an object has active COMPLIANCE retention and the request tries to change mode away from COMPLIANCE (e.g. to GOVERNANCE).
  • Keep allowing COMPLIANCE date extension, GOVERNANCE → COMPLIANCE upgrade, and new retention after the retain-until date has passed.
  • Fix the existing S3 Object Lock test so it uses a non-shorter retain date (otherwise it only exercised "cannot shorten", not mode downgrade), and add unit tests for the retention mode rules.

Test plan

  • Unit tests: put_object_retention cases in test_object_server.test.js (downgrade blocked, upgrade allowed, extend allowed, expired allowed, shorten blocked)
  • Integration: test_s3_worm.jsshould fail to change compliance mode to governance
  • Manual: PutObjectRetention COMPLIANCE → GOVERNANCE with same/longer date returns AccessDenied; GOVERNANCE → COMPLIANCE still succeeds

Summary by CodeRabbit

  • Bug Fixes

    • Prevented active COMPLIANCE retention modes from being changed.
    • Preserved protections against shortening or clearing locked retention periods.
    • Improved handling of missing retention requests to avoid errors.
    • Added clearer validation for retention-mode changes.
  • Tests

    • Added coverage for retention-mode transitions, expiration, date changes, and authorization failures.
    • Verified that governance-locked objects can be upgraded to COMPLIANCE mode successfully.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The object retention API now blocks active COMPLIANCE mode changes and disallowed date reductions. Unit and S3 integration tests cover rejected and accepted retention transitions.

Changes

Retention enforcement

Layer / File(s) Summary
Retention validation logic
src/server/object_services/object_server.js
put_object_retention documents and enforces active COMPLIANCE mode restrictions. It preserves shortening and clearing restrictions, adds retention fields to rejection logs, and handles missing requested retention safely.
Unit retention validation coverage
src/test/unit_tests/internal/test_object_lock_retention.test.js
Unit tests cover forbidden downgrades and shortening, allowed governance-to-compliance changes, extensions, and expired compliance retention.
S3 retention integration coverage
src/test/integration_tests/api/s3/test_s3_worm.js
Integration tests cover compliance-to-governance rejection and successful governance-to-compliance updates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: alphaprinz, naveenpaul1, nadavmiz

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: blocking COMPLIANCE-to-GOVERNANCE retention mode downgrades.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/server/object_services/object_server.js (1)

366-372: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

Pre-existing TOCTOU: read-validate-write without optimistic concurrency control.

Between find_object_md (line 342) and update_object_by_id (line 378), a concurrent request can change the object's retention, making the validation stale. For example, request A reads GOVERNANCE and passes the new COMPLIANCE check, but before A writes, request B upgrades to COMPLIANCE — A then writes GOVERNANCE, effectively downgrading active COMPLIANCE.

Consider a conditional update (e.g., check lock_settings hasn't changed) or a version-based optimistic lock in update_object_by_id as a follow-up.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/object_services/object_server.js` around lines 366 - 372, The
retention read-validation-write flow around find_object_md and
update_object_by_id lacks optimistic concurrency control. Update
update_object_by_id usage to condition the write on the retention/lock state or
object version read by find_object_md, and handle a failed conditional update as
a conflict without overwriting concurrent retention changes.

Source: Coding guidelines

src/test/unit_tests/internal/test_object_server.test.js (1)

624-716: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding tests for GOVERNANCE bypass scenarios.

The suite covers COMPLIANCE transitions thoroughly but doesn't exercise the GOVERNANCE-specific bypass path at line 367 of object_server.js: shortening GOVERNANCE retention with bypass_governance: true + admin (should succeed) and without bypass_governance (should fail with UNAUTHORIZED). These are important existing paths that interact with the refactored validation logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/unit_tests/internal/test_object_server.test.js` around lines 624 -
716, Add tests covering the GOVERNANCE shortening path in
object_server.put_object_retention: verify an admin request with
bypass_governance: true succeeds and calls update_object_by_id_stub, while the
same shortening request without bypass_governance rejects with rpc_code
UNAUTHORIZED and does not update. Use active GOVERNANCE retention dates and the
existing request/object helpers.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/server/object_services/object_server.js`:
- Around line 366-372: The retention read-validation-write flow around
find_object_md and update_object_by_id lacks optimistic concurrency control.
Update update_object_by_id usage to condition the write on the retention/lock
state or object version read by find_object_md, and handle a failed conditional
update as a conflict without overwriting concurrent retention changes.

In `@src/test/unit_tests/internal/test_object_server.test.js`:
- Around line 624-716: Add tests covering the GOVERNANCE shortening path in
object_server.put_object_retention: verify an admin request with
bypass_governance: true succeeds and calls update_object_by_id_stub, while the
same shortening request without bypass_governance rejects with rpc_code
UNAUTHORIZED and does not update. Use active GOVERNANCE retention dates and the
existing request/object helpers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7e52bbfe-8909-4a1e-a99f-16ff5132d1bd

📥 Commits

Reviewing files that changed from the base of the PR and between c7a381d and 6222569.

📒 Files selected for processing (3)
  • src/server/object_services/object_server.js
  • src/test/integration_tests/api/s3/test_s3_worm.js
  • src/test/unit_tests/internal/test_object_server.test.js

Comment on lines +357 to +358
// AWS: while COMPLIANCE retention is active, mode cannot change (e.g. to GOVERNANCE).
// Must run outside the shorten-date check — same/longer dates previously bypassed it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

may be personal, but I feel like this comment is not very understandable. what does it mean: "Must run outside the shorten-date check — same/longer dates previously bypassed it."?
I feel like it should be more intuitive to someone that doesn't necessarily understand object lock semantics. something like:
"Cannot change the COMPLIANCE mode when replacing an existing policy with a new one"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated. Changed it to:
"Cannot change COMPLIANCE mode when replacing an existing retention policy (e.g. COMPLIANCE -> GOVERNANCE) while retention is still active."
Also kept a short note that this check is separate from date shortening so a longer retain-until cannot bypass the mode restriction.

}
/**
*
* put_object_retention

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think its worth adding a small comment explaining the function. and generally what it allows and disallows

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added a short JSDoc on put_object_retention describing what it allows/disallows for COMPLIANCE vs GOVERNANCE while retention is active.

// Use a date >= current retain-until (extended to +60 above).
// A shorter date would fail for the wrong reason (cannot shorten COMPLIANCE).
const longerOrEqualDate = new Date();
longerOrEqualDate.setDate(longerOrEqualDate.getDate() + 90);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why is this change needed? why does it matters if we are 30 or 90 days in the future?
also its never an equal date

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ahh its because of the previous test. then its a good fix. you should just change the name of the variable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@kajalpareek-lab could you add a comment about the days so it would be clear for others? (the dependency between the tests).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@nadavMiz Yes — needed because the previous test extends retain-until to +60 days, so this one must use a later date (+90) so the failure is on COMPLIANCE -> GOVERNANCE mode change, not on shortening.
Renamed the variable to longerRetainUntilDate and added a comment explaining the dependency between the two tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@shirady Added a comment explaining the dependency between the tests: previous test sets +60, so this one uses +90 to assert mode-change denial rather than date shortening.

});
});

describe('object_server - put_object_retention', () => {

@nadavMiz nadavMiz Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't dislike the idea of adding unit tests. but we are already using integration tests for object lock. I think it would be good to have this coverage in test_s3_worm.js. the full path doesn't add much complexity, and I feel like its worth testing. we would also get the added benefit of testing NC as well.

having said that if you still feel like unit tests are needed we should probably move them to their own file for object_lock. no one would expect object lock tests to be here, and I fear they would be missed and potentially recreated elsewhere

@kajalpareek-lab kajalpareek-lab Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed. Moved the unit tests out of test_object_server.test.js. I’ll also add the COMPLIANCE -> GOVERNANCE / allowed transition coverage to test_s3_worm.js so we exercise the full S3 path (including NC).

}

if (!new_retention || new Date(new_retention.retain_until_date) < current_retain_until) {
if ((current_retention.mode === 'GOVERNANCE' && (!req.rpc_params.bypass_governance || req.role !== 'admin')) ||

@nadavMiz nadavMiz Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

worth adding a TODO that bypass_governance should be allowed to users with IAM permission and not specificly to admin

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nadavMiz I think the req.role admin is not related to the IAM users.
@kajalpareek-lab I don't think you need to check the req.role again, as it is already checked in the function before:

   if (req.role !== 'admin') {
        throw new RpcError('UNAUTHORIZED');
    }

and anyway I think that you can test the feature of object lock with IAM users vs root users to see you have the expected results according to AWS and if not fix accordingly.

@kajalpareek-lab kajalpareek-lab Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@nadavMiz Added a TODO that BypassGovernanceRetention should be allowed based on IAM / bucket-policy permission, not specifically the admin role.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@shirady Removed the redundant admin check in the shorten/bypass path. Admin is already enforced earlier in the function. Agree that IAM-user vs root-user Object Lock coverage is useful; I’d prefer to handle that as a follow-up outside this COMPLIANCE downgrade fix.

@shirady shirady left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree with @nadavMiz comments and added a few comments

}

if (!new_retention || new Date(new_retention.retain_until_date) < current_retain_until) {
if ((current_retention.mode === 'GOVERNANCE' && (!req.rpc_params.bypass_governance || req.role !== 'admin')) ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nadavMiz I think the req.role admin is not related to the IAM users.
@kajalpareek-lab I don't think you need to check the req.role again, as it is already checked in the function before:

   if (req.role !== 'admin') {
        throw new RpcError('UNAUTHORIZED');
    }

and anyway I think that you can test the feature of object lock with IAM users vs root users to see you have the expected results according to AWS and if not fix accordingly.

Comment thread src/server/object_services/object_server.js Outdated
// Use a date >= current retain-until (extended to +60 above).
// A shorter date would fail for the wrong reason (cannot shorten COMPLIANCE).
const longerOrEqualDate = new Date();
longerOrEqualDate.setDate(longerOrEqualDate.getDate() + 90);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@kajalpareek-lab could you add a comment about the days so it would be clear for others? (the dependency between the tests).

Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js Outdated
current_retention,
new_retention,
});
throw new RpcError('UNAUTHORIZED');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you check the error message in AWS for those cases to see if we have the same error message?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Checked on a live hosted cluster with this change: PutObjectRetention denials (COMPLIANCE → GOVERNANCE and shortening COMPLIANCE) return AccessDenied / Access Denied, same as other Object Lock denials and what our worm tests already expect for hosted. That matches AWS’s AccessDenied behavior for these cases. I’d leave the message as-is in this PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I meant the error message, not just the error code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You’re right — I was looking at the error code only.
Hosted today still returns "Access Denied" for these PutObjectRetention denials (UNAUTHORIZED). Aligning the message to "Access Denied because object protected by object lock." (and mapping via OBJECT_LOCKED / AccessDeniedObjectLocked) is already covered in #9868, including the worm test updates for both hosted and NC.
I’ll leave the message as-is in this PR and rebase onto #9868 after it merges.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What are this new tests add that we do not cover in src/test/unit_tests/internal/test_object_lock_retention.test.js?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The unit file keeps the edge cases that are awkward or slow over S3: same-date downgrade (proves mode check isn’t only hit via shorten), longer-date downgrade, expired COMPLIANCE then GOVERNANCE, and isolated put_object_retention without a cluster. Per Nadav’s earlier note we moved them out of test_object_server.test.js into this dedicated file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nadavMiz by using this file it would not be tested in NC, could you explain why not adding the test cases in src/test/integration_tests/api/s3/test_s3_worm.js?

I'm trying to look at the test cases, and I think it is good coverage for both environments:

  • rejects COMPLIANCE to GOVERNANCE downgrade with same retain date
  • rejects COMPLIANCE to GOVERNANCE downgrade with longer retain date
  • 'allows GOVERNANCE to COMPLIANCE upgrade with same retain date
  • allows extending COMPLIANCE retention while keeping COMPLIANCE mode
  • allows setting GOVERNANCE after COMPLIANCE retention has expired
  • rejects shortening active COMPLIANCE retention

@kajalpareek-lab
kajalpareek-lab force-pushed the compliance-downgrade branch 2 times, most recently from 8ba2527 to 8404ed8 Compare August 8, 2026 18:31
Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/test/unit_tests/internal/test_object_lock_retention.test.js Outdated
Comment thread src/test/unit_tests/internal/test_object_lock_retention.test.js Outdated
Comment thread src/server/object_services/object_server.js
Comment thread src/server/object_services/object_server.js Outdated
current_retention,
new_retention,
});
throw new RpcError('UNAUTHORIZED');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I meant the error message, not just the error code.

@nadavMiz

Copy link
Copy Markdown
Contributor

looks like error message is still wrong. after that is fixed. its fine by me

@shirady

shirady commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

looks like error message is still wrong. after that is fixed. its fine by me

@nadavMiz @kajalpareek-lab plans to fix it in another PR - see comment.

Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js Outdated
Comment thread src/server/object_services/object_server.js
Comment thread src/test/integration_tests/api/s3/test_s3_worm.js Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@nadavMiz by using this file it would not be tested in NC, could you explain why not adding the test cases in src/test/integration_tests/api/s3/test_s3_worm.js?

I'm trying to look at the test cases, and I think it is good coverage for both environments:

  • rejects COMPLIANCE to GOVERNANCE downgrade with same retain date
  • rejects COMPLIANCE to GOVERNANCE downgrade with longer retain date
  • 'allows GOVERNANCE to COMPLIANCE upgrade with same retain date
  • allows extending COMPLIANCE retention while keeping COMPLIANCE mode
  • allows setting GOVERNANCE after COMPLIANCE retention has expired
  • rejects shortening active COMPLIANCE retention

@kajalpareek-lab
kajalpareek-lab force-pushed the compliance-downgrade branch 4 times, most recently from 80e3ca9 to 1079cc4 Compare August 17, 2026 07:39
Reject PutObjectRetention mode changes away from COMPLIANCE while retention is still active, matching AWS Object Lock behavior. Retention denials use OBJECT_LOCKED so S3 clients get the Object Lock Access Denied message.

Signed-off-by: kajalpareek-lab <pareekkajal97@gmail.com>
@kajalpareek-lab
kajalpareek-lab merged commit 3eefddf into noobaa:master Aug 18, 2026
19 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.

3 participants