Skip to content

fix(s3): apply and validate Tags from CreateBucket request body#1103

Merged
Nahuel990 merged 2 commits into
ministackorg:mainfrom
nirajsapkota:fix/s3-createbucket-tags
Jul 18, 2026
Merged

fix(s3): apply and validate Tags from CreateBucket request body#1103
Nahuel990 merged 2 commits into
ministackorg:mainfrom
nirajsapkota:fix/s3-createbucket-tags

Conversation

@nirajsapkota

Copy link
Copy Markdown
Contributor

Summary

CreateBucket accepts a <Tags> container in its request body, but
_create_bucket parsed only LocationConstraint and silently dropped any
tags. As a result a follow-up GetBucketTagging returned NoSuchTagSet, and
S3 Control ListTagsForResource returned an empty list, instead of the tags
supplied at creation time.

AWS request body reference:
https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html#API_CreateBucket_RequestBody

This fix parses the <Tag> set at creation time and stores it in
_bucket_tags, so both GetBucketTagging and S3 Control
ListTagsForResource return the tags. Invalid tags are rejected before the
bucket is created, so no partial state is left behind.

Validation behavior

Each rule's error code, HTTP status, and message were verified against real
AWS S3 (a live account), and the emulator now matches on every case.

Rule Behavior AWS reference
Key length 1 to 128 chars InvalidTag / 400 / "The TagKey you have provided is invalid" Key min length 1 and required: https://docs.aws.amazon.com/AmazonS3/latest/API/API_Tag.html . Key 1 to 128 chars: https://docs.aws.amazon.com/AmazonS3/latest/userguide/CostAllocTagging.html
Value length 0 to 256 chars (empty allowed) InvalidTag / 400 / "The TagValue you have provided is invalid" Value 0 to 256 chars: https://docs.aws.amazon.com/AmazonS3/latest/userguide/CostAllocTagging.html . 256 char limit also at https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-tagging.html
Reserved aws: key prefix InvalidTag / 400 / User-defined tag keys can't start with "aws:". This prefix is reserved for system tags. Remove "aws:" from your tag keys and try again. Reserved aws: prefix: https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/custom-tags.html#allocation-tag-restrictions
At most 50 tags per bucket BadRequest / 400 / "Bucket tag count cannot be greater than 50" A tag set can contain as many as 50 tags: https://docs.aws.amazon.com/AmazonS3/latest/userguide/CostAllocTagging.html
Duplicate tag keys InternalError / 500 / "We encountered an internal error. Please try again." Keys must be unique within a tag set: https://docs.aws.amazon.com/AmazonS3/latest/userguide/CostAllocTagging.html . On the CreateBucket path, real AWS returns a 500 for a repeated key (verified against the cloud)

Out of scope

PutBucketTagging also validates duplicate keys on real AWS, but it returns
InvalidTag / 400 / "Cannot provide multiple Tags with the same key" (a
different response from the CreateBucket 500). I have left PutBucketTagging
unchanged in this PR to keep the change focused on the CreateBucket path; its
handler still uses the existing last-writer-wins parse. This can be addressed
in a follow-up.

Implementation notes

Parsing is factored into a shared _iter_tag_pairs helper. The lenient
_parse_tags_xml (last-writer-wins on duplicate keys) is unchanged and still
used by the PutBucketTagging and PutObjectTagging handlers.

Tests

Added integration tests covering: tags applied on create, tags plus
LocationConstraint, no tags leaving no tag set, empty tag values, all four
validation failures above, the duplicate-key 500, and readability of
create-time tags through S3 Control ListTagsForResource. Every rejection
test also asserts the bucket was not created.

CreateBucket accepts a <Tags> container in its request body
(API_CreateBucket_RequestBody), but _create_bucket parsed only
LocationConstraint and silently dropped any tags. A follow-up
GetBucketTagging then returned NoSuchTagSet, and S3 Control
ListTagsForResource returned an empty list, instead of the tags
supplied at creation time.

Parse the <Tag> set at creation time and store it in _bucket_tags so
both GetBucketTagging and S3 Control ListTagsForResource return it.
Validate the tag set against the S3 constraints, with codes, HTTP
statuses and messages verified against real AWS S3:

- key length 1-128, value length 0-256 (empty value allowed) -> InvalidTag/400
- reserved "aws:" key prefix -> InvalidTag/400
- more than 50 tags -> BadRequest/400
- duplicate keys -> InternalError/500 (matches real AWS on this path)

Invalid tags are rejected before the bucket is created, so no partial
state is left behind.

Parsing is factored into a shared _iter_tag_pairs; the lenient
_parse_tags_xml (last-writer-wins) is unchanged and still used by the
Put(Bucket|Object)Tagging handlers.
@github-actions

Copy link
Copy Markdown

Docker image for this PR has been published: ministackorg/ministack-preview-build:pr-1103-1f41dd56

Comment thread ministack/services/s3.py
return tags
yield key_text, val_text or ""

def _parse_tags_xml(body: bytes) -> dict:

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.

As mentioned in the PR description, I've left paths outside of _create_bucket unchanged to limit the scope of this pull-request so preserving the last-tag wins parsing in this method.

@github-actions

Copy link
Copy Markdown

Docker image for this PR has been published: ministackorg/ministack-preview-build:pr-1103-e860b2b6

@Nahuel990
Nahuel990 merged commit f556487 into ministackorg:main Jul 18, 2026
2 checks passed
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