Context
PR #71 adds the standard S3 per-bucket CORS control plane and enforcement path. The core single-site implementation has been accepted: PutBucketCors, GetBucketCors, and DeleteBucketCors work with a standard minio-go client; matching and rejected preflights behave as expected; configured response headers are applied; and buckets without a CORS configuration retain the existing global fallback.
The maintainer is accepting the contribution and taking ownership of the remaining integration and compatibility hardening in this issue. This issue is a release gate for shipping per-bucket CORS; merging #71 does not by itself make the feature release-ready.
CORS is a browser response policy, not an IAM authorization mechanism. The replication bugs below do not bypass S3 permissions, but they can leave an origin enabled on one site after an administrator intended to revoke it.
Release-blocking correctness work
1. Preserve CORS tombstones and source ordering across site replication
The current peer handler and heal path do not preserve last-writer-wins semantics across PUT and DELETE:
SiteReplicationMetaInfo only exports CorsConfigUpdatedAt when CorsConfigXML is non-empty. A DELETE therefore loses its tombstone timestamp in the status/heal snapshot.
PeerBucketCorsConfigHandler checks staleness through GetCorsConfig. Once the local config is deleted, that getter returns not-found and the newer deletion timestamp is unavailable, so an older PUT can resurrect the config.
- Peer PUT/DELETE calls use
BucketMetadataSys.Update/Delete, which generate a new local timestamp instead of persisting the source UpdatedAt. Delayed or reordered events can therefore be accepted or rejected using arrival time rather than source order.
- When one site misses a DELETE, heal can currently select the remaining old non-nil config as the newest state and restore it to the site where it was deleted.
Required behavior:
2. Add tests against the real apply/status/heal seams
The tests added in #71 cover JSON/base64 transport and pointer equality, but not metadata application or convergence. Add regression coverage for:
3. Do not fail open on metadata errors
The middleware should use the global CORS fallback only when the bucket genuinely has no per-bucket configuration. A parse, cache, or operational metadata error must not silently widen the bucket to the global policy.
Compatibility and operational cleanup
4. Complete the S3 preflight response
5. Correct site-replication status accounting
TotalCorsConfigCount currently uses the cumulative corsCfgCount > 0 condition while iterating sites. Once an earlier site has a config, a later site without one can be counted as configured.
6. Finish validation edge cases
7. Document upgrade, fallback, and rollback semantics accurately
CORS data is stored in the bucket metadata record under the logical cors.xml selector; it is not an independent dormant file that is guaranteed to survive all downgrade writes.
Inherited site-replication pattern audit
The CORS implementation mirrors existing policy/tag/SSE/quota handlers. Some of the timestamp and cumulative-counter behavior may therefore predate #71.
Verification and release gate
Out of scope
- Silo Console UI for editing bucket CORS is a separate follow-up.
- A broad redesign of site replication is not required unless the audit produces a concrete shared bug and a narrowly reviewable fix.
Acceptance criteria
This issue can close when per-bucket CORS converges correctly across normal delivery, retry, reordering, missed DELETE, and heal; errors cannot widen a configured bucket to the global policy; the protocol/status cleanup is tested; documentation describes the operational boundary; and all local and remote release gates are green.
Context
PR #71 adds the standard S3 per-bucket CORS control plane and enforcement path. The core single-site implementation has been accepted:
PutBucketCors,GetBucketCors, andDeleteBucketCorswork with a standardminio-goclient; matching and rejected preflights behave as expected; configured response headers are applied; and buckets without a CORS configuration retain the existing global fallback.The maintainer is accepting the contribution and taking ownership of the remaining integration and compatibility hardening in this issue. This issue is a release gate for shipping per-bucket CORS; merging #71 does not by itself make the feature release-ready.
CORS is a browser response policy, not an IAM authorization mechanism. The replication bugs below do not bypass S3 permissions, but they can leave an origin enabled on one site after an administrator intended to revoke it.
Release-blocking correctness work
1. Preserve CORS tombstones and source ordering across site replication
The current peer handler and heal path do not preserve last-writer-wins semantics across PUT and DELETE:
SiteReplicationMetaInfoonly exportsCorsConfigUpdatedAtwhenCorsConfigXMLis non-empty. A DELETE therefore loses its tombstone timestamp in the status/heal snapshot.PeerBucketCorsConfigHandlerchecks staleness throughGetCorsConfig. Once the local config is deleted, that getter returns not-found and the newer deletion timestamp is unavailable, so an older PUT can resurrect the config.BucketMetadataSys.Update/Delete, which generate a new local timestamp instead of persisting the sourceUpdatedAt. Delayed or reordered events can therefore be accepted or rejected using arrival time rather than source order.Required behavior:
CorsConfigUpdatedAtconsistently for metadata that predates this field.CorsConfigUpdatedAteven whenCorsConfigis nil, so a deletion remains an observable tombstone.UpdatedAtfor both PUT and DELETE instead of replacing it with local arrival time.2. Add tests against the real apply/status/heal seams
The tests added in #71 cover JSON/base64 transport and pointer equality, but not metadata application or convergence. Add regression coverage for:
SiteReplicationMetaInfowithCorsConfig == nil.3. Do not fail open on metadata errors
The middleware should use the global CORS fallback only when the bucket genuinely has no per-bucket configuration. A parse, cache, or operational metadata error must not silently widen the bucket to the global policy.
no CORS configfrom metadata/parse failures.Compatibility and operational cleanup
4. Complete the S3 preflight response
Access-Control-Expose-Headerson a successful OPTIONS response.Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headerscoverage.5. Correct site-replication status accounting
TotalCorsConfigCountcurrently uses the cumulativecorsCfgCount > 0condition while iterating sites. Once an earlier site has a config, a later site without one can be counted as configured.s.CorsConfig != nilstate.6. Finish validation edge cases
AllowedOriginvalues rather than only checking that an element exists.*wildcard semantics are accepted; do not inherit?wildcard behavior from a generic matcher.7. Document upgrade, fallback, and rollback semantics accurately
CORS data is stored in the bucket metadata record under the logical
cors.xmlselector; it is not an independent dormant file that is guaranteed to survive all downgrade writes.pgsty/silo.pgsty.comas a separate repository deliverable.Inherited site-replication pattern audit
The CORS implementation mirrors existing policy/tag/SSE/quota handlers. Some of the timestamp and cumulative-counter behavior may therefore predate #71.
Verification and release gate
go build ./...cmdpackage testsgolangci-lintwith the repository-pinned versionminio-goend-to-end PUT/GET/DELETE and preflight smoke testmainOut of scope
Acceptance criteria
This issue can close when per-bucket CORS converges correctly across normal delivery, retry, reordering, missed DELETE, and heal; errors cannot widen a configured bucket to the global policy; the protocol/status cleanup is tested; documentation describes the operational boundary; and all local and remote release gates are green.