Skip to content

Fix REST signing for S3 bulk deletes - #4039

Draft
smaheshwar-pltr wants to merge 1 commit into
apache:mainfrom
smaheshwar-pltr:fix/s3-remote-signing-delete-body
Draft

smaheshwar-pltr wants to merge 1 commit into
apache:mainfrom
smaheshwar-pltr:fix/s3-remote-signing-delete-body

Conversation

@smaheshwar-pltr

@smaheshwar-pltr smaheshwar-pltr commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Rationale for this change

Fsspec uses S3 DeleteObjects when deleting files, including a single file. The REST signer currently omits the XML body, so the signing service cannot authorize the object keys in the request.

Include the UTF-8 body for POST ?delete, matching the Java client's behavior. Other request bodies remain untouched.

Are these changes tested?

Yes. The three new DeleteObjects cases failed before the fix because the signing request had no body. Six more cases check that other methods and queries, including ?undelete and ?key=delete, neither forward nor read the request body.

Are there any user-facing changes?

REST signing requests for S3 bulk deletion now include the object list required for authorization.

@smaheshwar-pltr smaheshwar-pltr left a comment

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 change follows the Java signer's DeleteObjects handling. The inline notes link the protocol contract, the Python serialization path, and the caller that makes this necessary.

Comment thread pyiceberg/io/fsspec.py
"uri": request.url,
"headers": {key: [val] for key, val in request.headers.items()},
}
if request.method == "POST" and "delete" in parse_qs(urlparse(request.url).query, keep_blank_values=True):

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.

This matches the Java client's existing predicate and body handling: POST with a delete query key. The REST contract calls out DeleteObjects because its body contains the keys that the signing service must validate; the URL identifies only the bucket. keep_blank_values=True makes both ?delete and ?delete= recognizable. Other POST operations, including multipart completion, keep their existing behavior.

Comment thread pyiceberg/io/fsspec.py
}
if request.method == "POST" and "delete" in parse_qs(urlparse(request.url).query, keep_blank_values=True):
if body := request.body:
signer_body["body"] = body.decode("utf-8")

@smaheshwar-pltr smaheshwar-pltr Oct 1, 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.

The bytes assumption comes from Botocore's REST XML serializer, which serializes the request using its UTF-8 default. AWSRequest.body exposes that prepared body. Decoding creates the string expected by the signing API; it does not replace the original XML bytes or checksum headers sent to S3. The guards above prevent accessing unrelated upload streams.

Comment thread tests/io/test_fsspec.py


@pytest.mark.parametrize("query", ["delete", "delete=", "delete=&x-id=DeleteObjects"])
def test_s3v4_rest_signer_sends_delete_objects_body(requests_mock: Mocker, query: str) -> None:

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.

This is reachable even when deleting one file: FsspecFileIO.delete calls fs.rm, and s3fs batches those paths through _bulk_delete, which calls DeleteObjects. All three cases here failed before the fix because the captured signing JSON had no body. The six cases below separately guard against forwarding or reading unrelated request bodies.

@Fokko

Fokko commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Thanks for raising this @smaheshwar-pltr. I know pre-signed URLs conceptually, but not the details. Maybe @danielcweeks has a free cycle to take a look at this. Can I ask if you have tested this against a S3 instance? One issue we have with the pre-signed url code is that it doesn't run any integration tests again MinIO.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants