Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deleting a marker must not AccessDenied when retention set #690

Open
irq0 opened this issue Aug 30, 2023 · 5 comments · May be fixed by aquarist-labs/ceph#241
Open

Deleting a marker must not AccessDenied when retention set #690

irq0 opened this issue Aug 30, 2023 · 5 comments · May be fixed by aquarist-labs/ceph#241
Labels
area/rgw-sfs RGW & SFS related kind/bug Something isn't working triage/next-candidate This could be moved to the next milestone

Comments

@irq0
Copy link
Contributor

irq0 commented Aug 30, 2023

S3 test test_object_lock_delete_object_with_retention_and_marker fails while deleting a previously created delete marker

    @pytest.mark.fails_on_dbstore
    def test_object_lock_delete_object_with_retention_and_marker():
        bucket_name = get_new_bucket_name()
        client = get_client()
        client.create_bucket(Bucket=bucket_name, ObjectLockEnabledForBucket=True)
        key = 'file1'
    
        response = client.put_object(Bucket=bucket_name, Body='abc', Key=key)
        retention = {'Mode':'GOVERNANCE', 'RetainUntilDate':datetime.datetime(2030,1,1,tzinfo=pytz.UTC)}
        client.put_object_retention(Bucket=bucket_name, Key=key, Retention=retention)
        del_response = client.delete_object(Bucket=bucket_name, Key=key)
        e = assert_raises(ClientError, client.delete_object, Bucket=bucket_name, Key=key, VersionId=response['VersionId'])
        status, error_code = _get_status_and_error_code(e.response)
        assert status == 403
        assert error_code == 'AccessDenied'
    
>       client.delete_object(Bucket=bucket_name, Key=key, VersionId=del_response['VersionId'])

del_response is the delete marker created by delete_object without a version id before.

Deleting the delete marker should work, but raises a AccessDenied

@github-project-automation github-project-automation bot moved this to Backlog in S3GW Aug 30, 2023
@github-actions github-actions bot added the triage/waiting Waiting for triage label Aug 30, 2023
@irq0 irq0 added kind/bug Something isn't working priority/1 Should be fixed for next release area/rgw-sfs RGW & SFS related and removed triage/waiting Waiting for triage labels Aug 30, 2023
@giubacc giubacc added this to the v0.22.0 milestone Oct 4, 2023
@jecluis jecluis moved this from Backlog to Scheduled in S3GW Oct 4, 2023
@giubacc giubacc moved this from Scheduled to In Progress 🏗️ in S3GW Oct 4, 2023
@giubacc
Copy link

giubacc commented Oct 6, 2023

IMO the test_object_lock_delete_object_with_retention_and_marker test is incorrect.

This block:

    del_response = client.delete_object(Bucket=bucket_name, Key=key)
    e = assert_raises(ClientError, client.delete_object, Bucket=bucket_name, Key=key, VersionId=response['VersionId'])

What is the purpose of the first deletion?
del_response is not even checked.

Then this block makes no sense:

    client.delete_object(Bucket=bucket_name, Key=key, VersionId=del_response['VersionId'])
    e = assert_raises(ClientError, client.delete_object, Bucket=bucket_name, Key=key, VersionId=response['VersionId'])
    status, error_code = _get_status_and_error_code(e.response)
    assert status == 403
    assert error_code == 'AccessDenied'

I think the test wanted to assert the raise of the exception when deleting the specific VersionID, but if so, why calling the exact same call just before the protected assert_raises function?

Indeed the test is (correctly!) raising an unchecked exception in the expected place:

>       client.delete_object(Bucket=bucket_name, Key=key, VersionId=del_response['VersionId'])

s3tests_boto3/functional/test_s3.py:11871: 

E           botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the DeleteObject operation: forbidden by object lock

To me, this test is invalid.

@giubacc giubacc added the triage/invalid This doesn't seem right label Oct 6, 2023
@giubacc giubacc moved this from In Progress 🏗️ to Blocked / On hold in S3GW Oct 6, 2023
@giubacc
Copy link

giubacc commented Oct 24, 2023

@jecluis need another pair of eyes to eval what to do here, IMO this should be closed.

@jecluis
Copy link
Contributor

jecluis commented Oct 24, 2023

IMO the test_object_lock_delete_object_with_retention_and_marker test is incorrect.

I haven't read the test, but from your description I'm not so sure that's the case.

AFAIU, the test is supposed to be deleting a delete marker. That means deleting an object that exists, which introduces a delete marker, and then deleting the latest version id of said object, hence the deleting the delete marker.

This block:

    del_response = client.delete_object(Bucket=bucket_name, Key=key)
    e = assert_raises(ClientError, client.delete_object, Bucket=bucket_name, Key=key, VersionId=response['VersionId'])

What is the purpose of the first deletion? del_response is not even checked.

It may not be checked, but is used later on. I suspect this is deleting the object, hence creating a delete marker.

Then this block makes no sense:

    client.delete_object(Bucket=bucket_name, Key=key, VersionId=del_response['VersionId'])
    e = assert_raises(ClientError, client.delete_object, Bucket=bucket_name, Key=key, VersionId=response['VersionId'])
    status, error_code = _get_status_and_error_code(e.response)
    assert status == 403
    assert error_code == 'AccessDenied'

It looks to me that it is attempting to delete the delete marker. See how it is deleting the object with a specified version id, which is the same as returned before by the first object deletion. I presume this is the version id of the delete marker that was introduced.

Only after that, the test attempts to delete the actual initial object version, and only then expects an Access Denied.

To me it seems like what the test expects is to be able to delete the delete marker even under object locking, but expects not being able to delete the actual object version (not the delete marker).

I think the only way to assess what is the right thing here is to resort to the documentation and understand whether a delete marker under object locking can be deleted. I suspect it should, although it might require special privileges (like admin, which we should have because we are running these tests with default admin credentials).

I think the test wanted to assert the raise of the exception when deleting the specific VersionID, but if so, why calling the exact same call just before the protected assert_raises function?

Indeed the test is (correctly!) raising an unchecked exception in the expected place:

>       client.delete_object(Bucket=bucket_name, Key=key, VersionId=del_response['VersionId'])

s3tests_boto3/functional/test_s3.py:11871: 

E           botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the DeleteObject operation: forbidden by object lock

To me, this test is invalid.

As I said above, I don't think that's what the test is trying to do and validate. The operation failing in this case is expected to succeed (i.e., deleting the delete marker), and only the one that comes after (i.e., deleting the object version) is expected to return Access Denied.

@jecluis jecluis added triage/needs-information Further information is requested and removed triage/invalid This doesn't seem right labels Oct 24, 2023
@giubacc
Copy link

giubacc commented Oct 24, 2023

ah yes, I could have completely misunderstood the test; will recheck this, thanks!

@jecluis jecluis modified the milestones: v0.22.0, v0.23.0 Oct 30, 2023
@giubacc
Copy link

giubacc commented Oct 31, 2023

@jecluis the test is correct, we have a bug in the rgw/sfs that makes not properly recognize a deletemarker when handling an object deletion.

@giubacc giubacc removed the triage/needs-information Further information is requested label Oct 31, 2023
@giubacc giubacc moved this from Blocked / On hold to In Progress 🏗️ in S3GW Oct 31, 2023
giubacc referenced this issue in giubacc/ceph Oct 31, 2023
RGWDeleteObj::execute() relies on the return code from
rgw::sal::Object::get_obj_state() to ascert the object being deleted is
a regular object rather than a delete-marker.
The rule is to return -ENOENT to signal a delete-marker.
get_obj_state() in rgw/sfs was returning always 0 for any object,
therefore also a delete-marker fell into a regular object in
RGWDeleteObj::execute().
This is wrong because, when dealing with object lock checks, a delete-marker
is always allowed to be deleted regardless of the object's retention mode.
Returing 0 was incorrectly preventing a delete-marker to be deleted for
object-lock protected objects.

Fixes: https://github.com/aquarist-labs/s3gw/issues/690
Signed-off-by: Giuseppe Baccini <[email protected]>
@giubacc giubacc linked a pull request Oct 31, 2023 that will close this issue
11 tasks
@giubacc giubacc moved this from In Progress 🏗️ to Blocked / On hold in S3GW Nov 17, 2023
@jecluis jecluis modified the milestones: v0.23.0, v0.24.0 Nov 26, 2023
@jecluis jecluis added triage/next-candidate This could be moved to the next milestone and removed priority/1 Should be fixed for next release labels Mar 21, 2024
@jecluis jecluis added this to s3gw Mar 21, 2024
@jecluis jecluis moved this to Backlog in s3gw Mar 21, 2024
@jecluis jecluis removed this from the v0.24.0 milestone Mar 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/rgw-sfs RGW & SFS related kind/bug Something isn't working triage/next-candidate This could be moved to the next milestone
Projects
Status: Backlog
Development

Successfully merging a pull request may close this issue.

3 participants