Skip to content

Implement DELETE precondition checks for If-Match header#12

Open
magicxor wants to merge 1 commit intopgsty:masterfrom
magicxor:feature/delete-object-conditional-headers
Open

Implement DELETE precondition checks for If-Match header#12
magicxor wants to merge 1 commit intopgsty:masterfrom
magicxor:feature/delete-object-conditional-headers

Conversation

@magicxor
Copy link

Community Contribution License

All community contributions in this pull request are licensed to the project maintainers
under the terms of the Apache 2 license.
By creating this pull request I represent that I have the right to license the
contributions to the project maintainers under the Apache 2 license.

Description

Add support for the If-Match conditional header on the DeleteObject API operation, as specified by AWS S3. (fixes #10)

When If-Match is provided, the object is deleted only if its ETag matches the specified value; otherwise a 412 Precondition Failed response is returned. When If-Match is specified and the object does not exist, a 404 NoSuchKey error is returned instead of the usual 204 No Content.

This follows the same two-tier precondition check pattern already used by PutObject and GetObject:

  1. HTTP handler layer (DeleteObjectHandler) — reads the If-Match header, sets opts.CheckPrecondFn closure and opts.HasIfMatch flag.
  2. Storage layer (erasureObjects.DeleteObject) — evaluates CheckPrecondFn atomically after fetching the latest ObjectInfo via getObjectInfoAndQuorum, before proceeding with the actual deletion. Non-NotFound errors (e.g. InsufficientReadQuorum) are propagated to prevent unverified deletes.

Source changes across 4 files:

  • cmd/object-handlers-common.go — new checkPreconditionsDELETE() function that checks If-Match for DELETE requests.
  • cmd/object-handlers.goDeleteObjectHandler now parses the If-Match header and sets the precondition function. Error handling updated: PreConditionFailed returns early; If-Match on a non-existent object returns NoSuchKey error instead of 204 No Content.
  • cmd/erasure-object.goDeleteObject evaluates CheckPrecondFn after getObjectInfoAndQuorum and before EvalMetadataFn, with proper quorum-error propagation.
  • cmd/object-api-interface.go — updated CheckPrecondFn field comment to be generic.

Test changes across 3 files:

  • cmd/object-handlers-common_test.goTestCheckPreconditionsDELETE: 6 unit test cases for the precondition function (non-DELETE ignored, matching/non-matching/wildcard/quoted ETags, no headers).
  • cmd/erasure-object-conditional_test.goTestDeleteObjectConditional: 3 erasure-layer integration tests (wrong ETag → fail, correct ETag → succeed, missing object → error). TestDeleteObjectConditionalWithReadQuorumFailure: 1 test verifying that conditional delete is blocked when read quorum is lost.
  • cmd/object-handlers_test.go — 2 handler-level tests added to TestAPIDeleteObjectHandler (wrong If-Match → 412 and object remains; If-Match on missing key → 404 NoSuchKey).

Motivation and Context

AWS S3 supports the If-Match conditional header on DeleteObject operations, returning 412 Precondition Failed when the condition is not met. This enables safe concurrent delete workflows where callers can ensure they only delete a specific known version of an object (by ETag), preventing accidental deletion of an object that was updated between a read and a delete.

MinIO already supports conditional headers for GetObject, HeadObject, PutObject, CopyObject, and multipart operations — but DeleteObject was missing If-Match support entirely. There were no checks, no TODOs, and no code paths for conditional headers in the delete flow.

How to test this PR?

Reproduce the bug (before the fix)

  1. Start an unpatched MinIO server:
    docker run -d -p 9000:9000 -e MINIO_ROOT_USER=minioadmin -e MINIO_ROOT_PASSWORD=minioadmin \
      minio/minio:RELEASE.2025-12-03T12-00-00Z server /data
  2. Create a bucket and upload an object.
  3. Send a DeleteObject request with If-Match: "wrong-etag" (an ETag that does not match the object).
  4. Observe that the object is deleted with 204 No Content instead of being rejected with 412 Precondition Failed.

Minimal reproduction using .NET 10 + AWS SDK (save as test-ifmatch.cs, run with dotnet run --file test-ifmatch.cs):

#!/usr/bin/env dotnet run
#:package AWSSDK.S3@4.*

using Amazon.S3;
using Amazon.S3.Model;
using Amazon.Runtime;

var config = new AmazonS3Config { ServiceURL = "http://localhost:9000", ForcePathStyle = true };
var s3 = new AmazonS3Client(new BasicAWSCredentials("minioadmin", "minioadmin"), config);

// Upload a test object
await s3.PutObjectAsync(new PutObjectRequest {
    BucketName = "testbucket", Key = "test.txt", ContentBody = "hello"
});
var etag = (await s3.GetObjectMetadataAsync("testbucket", "test.txt")).ETag;
Console.WriteLine($"ETag: {etag}");

// DELETE with wrong ETag — should return 412 Precondition Failed, but MinIO returns 204
try {
    await s3.DeleteObjectAsync(new DeleteObjectRequest {
        BucketName = "testbucket", Key = "test.txt", IfMatch = "\"wrong-etag\""
    });
    Console.WriteLine("BUG: object deleted even though ETag does not match!");
} catch (AmazonS3Exception ex) when (ex.StatusCode == System.Net.HttpStatusCode.PreconditionFailed) {
    Console.WriteLine("OK: 412 Precondition Failed (expected behavior)");
}

Before fix: BUG: object deleted even though ETag does not match!
After fix: OK: 412 Precondition Failed (expected behavior)

Unit tests

go test ./cmd/ -run "TestCheckPreconditionsDELETE|TestDeleteObjectConditional|TestAPIDeleteObjectHandler" -v -count=1

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Optimization (provides speedup with no functional changes)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • Fixes a regression (If yes, please add commit-id or PR # here)
  • Unit tests added/updated
  • Internal documentation updated
  • Create a documentation update request here

AWS S3 supports the If-Match conditional header on DeleteObject, but
MinIO silently ignored it — the object was deleted regardless of ETag
mismatch. Add precondition checks following the same two-tier pattern
used by PutObject/GetObject: handler sets CheckPrecondFn, erasure layer
evaluates it atomically after fetching fresh ObjectInfo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

DeleteObject ignores If-Match header (no conditional delete support)

1 participant