Remove the remaining type-only canonicalization at CompleteMultipartUpload while preserving legacy uploads stored as FULL_OBJECT.
Signed-off-by: Feng Ruohang <rh@vonng.com>
After zero-byte reads authenticate SSE-C at the object layer, equal invalid rotation keys match non-empty reads and return AccessDenied before the rotation-specific distinction.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Unseal supplied SSE-C and copy-source keys after range and request preconditions when a zero-byte read cannot build a decryptor. Preserve internal no-decryption, replication, restore, and absent-header reads.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Unseal the supplied customer key after request preconditions before returning object size, ETag, checksum, or part attributes. Cover zero-byte and non-empty objects with correct, wrong, and missing keys.\n\nRefs #84.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Reject unimplemented x-amz-checksum value and trailer names instead of accepting uploads without verification. Apply the same contract to PutObject, multipart initiation and parts, CopyObject, and UploadPartCopy while preserving the five supported algorithms.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Exercise the silent compression-on-copy path, compressed SSE-C re-encryption, and the equal-invalid-key error contract on both object-layer backends.
Signed-off-by: Feng Ruohang <rh@vonng.com>
A key rotation rewraps the object key held in metadata; it never re-encrypts
the stored bytes. CopyObjectHandler took that shortcut whenever the request
looked like a same-object SSE-C rotation, on the assumption that the object
layer would then leave the stored bytes alone. That is the same assumption
copyRewritesObjectData() was added to stop making.
When the source is a null version on a bucket that gained versioning after the
object was written, the object layer cannot reference that version and falls
back to PutObject. The reader at that point holds plaintext decrypted with the
old key and no EncryptFn is set, so the destination ends up storing plaintext
under metadata that claims the object is SSE-C encrypted. A subsequent GET
failed with "sio: unsupported version".
Gate the rotation shortcut on the same prediction the compression metadata
already uses. When the object layer stores new object data the rotation falls
through to the regular re-encrypting copy, which decrypts with the old key and
re-encrypts with the new one. The source version selection moves next to the
gate because both decisions need it.
That fallback authenticates the source key through the source decryptor, which
GetObjectNInfo does not build for a zero byte object. Check the key explicitly
before the destination is written, so the gate cannot turn a rotation that the
shortcut rejected with AccessDenied into one that succeeds. The re-encrypting
copy regenerates the encrypted ETag, unlike an in-place rotation; the test
records that difference.
The other three object layer CopyObject callers that set metadataOnly -
PostRestoreObjectHandler, updateRestoreMetadata and batchKeyRotate - address
the same version on both sides and never set Versioned, so they only reach the
two in-place cases already covered by the copyRewritesObjectData table.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fk3PAD7DHCYzcyegYWqAmt
CopyObjectHandler recorded the source compression metadata whenever the copy
was metadata-only, on the assumption that the object layer would then leave
the stored bytes alone. That assumption does not hold. Both
erasureServerPools.CopyObject and erasureSets.CopyObject only skip a data
rewrite in three cases, and otherwise fall back to a full PutObject.
The reachable gap is a copy whose source is a null version on a bucket that
gained versioning after the object was written. Neither version ID is set, so
the self-referential version branch is skipped, the data is rewritten as
plaintext, and the preserved compression metadata then described bytes that
no longer exist. A subsequent GET failed with "s2: corrupt input".
Mirror the object layer's decision in copyRewritesObjectData and record the
compression metadata from it, so the metadata always describes whichever
bytes are finally stored. The source version selection that lets a versioned
metadata-only copy add a self-referential version moves next to the same
decision, since both depend on the effective metadata-only value.
Signed-off-by: Feng Ruohang <rh@vonng.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Exercise no-site, local-only, remote-only, and both-site CORS summary accounting through the real siteReplicationStatus seam.
Refs #75
Signed-off-by: Feng Ruohang <rh@vonng.com>
Integrate the strict B3 XML, validation, checksum, wildcard, MaxAge, and Origin-null response contract with the C-prime site-replication register from #75.
Preserve fail-closed metadata behavior and rejected-preflight cache variation while keeping legacy-invalid development metadata readable and repairable through a valid CORS PUT or DELETE.
Add combined parser, handler, browser-response, namespace, replication, restart, and legacy-repair regressions, and update the internal design contract.
Refs #75
Signed-off-by: Feng Ruohang <rh@vonng.com>
Define a deterministic CORS replication register with durable tombstones, strict source timestamps, equal-time conflict ordering, full-state status, and heal convergence.
Serialize local and peer CORS transitions with a distributed namespace lock, validate canonical transport payloads, preserve initial-sync deletes, and fail closed on metadata errors.
Add adversarial, concurrent, restart, status, heal, signed admin-dispatch, protocol, and middleware coverage together with the reviewed site-replication design record.
Refs #75
Signed-off-by: Feng Ruohang <rh@vonng.com>
Site replication emitted SRBucketMetaTypeCorsConfig on PutBucketCors, but
the peer receive/apply, initial-sync, status, and heal paths did not carry
the CORS metadata. Replicated sites could therefore diverge on CORS config
even though the originating request succeeded.
Complete every site-replication path for CORS, mirroring the SSEConfig
pattern:
- peer apply: PeerBucketCorsConfigHandler + item.Cors handling in
PeerBucketMetadataUpdateHandler, with an updatedAt staleness guard
- initial sync: push existing CorsConfigXML via BucketMetaHook
- status: parse per-site CorsConfig, count/compare, surface
CorsCfgMismatch/HasCorsCfgSet/ReplicatedCorsConfig, and include CORS in
the bucket-stats aggregation filter
- heal: healCORSMetadata, including nil -> delete propagation
Also harden the request/config path:
- PutBucketCors validates the supplied Content-MD5/checksum via
validateLengthAndChecksum
- CORS validation rejects more than one wildcard per AllowedOrigin/
AllowedHeader and enforces the 255-char rule ID limit
- preflight responses Vary on Origin, Access-Control-Request-Method, and
Access-Control-Request-Headers
Add focused tests for the CORS SR transport round-trip, the metadata
equality helper, and the new validation constraints.
Signed-off-by: h5vx <h5v@protonmail.com>
Return AWS-compatible errors for CompleteMultipartUpload checksum failures without changing the global streaming checksum mapping. Compare explicit multipart checksum types symmetrically, distinguish missing composite part checksums, and preserve the CRC64NVME canonicalization pending a direct AWS probe.
Signed-off-by: Feng Ruohang <rh@vonng.com>
ListObjects shortcuts can return EOF before consulting storage, causing missing buckets to appear as empty listings. Verify bucket existence only on those shortcuts so the normal listing path retains the upstream fan-out optimization.
Cover ListObjects, ListObjectsV2, and ListObjectVersions at the object layer and verify HTTP 404 NoSuchBucket responses.
Fixes#32
Co-authored-by: Jason Lin <jason@JasondeMacBook-Air.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
The legacy etcd federation branch of CopyObjectPartHandler forwards copied
bytes with minio-go Core.PutObjectPart, which can only recover a checksum
from response headers. After the server-side part checksum work, the remote
computes and persists the checksum, but an AWS-compatible UploadPart response
correctly omits a checksum the request did not supply, so the proxy had
nothing to put in CopyPartResult.
The destination now returns the non-empty checksum fields of the PartInfo
produced by that exact write, but only when the request carries the
minio-federated application token that getRemoteInstanceClient already
attaches. Ordinary UploadPart responses are unchanged, and the checksum type
is deliberately not returned because UploadPart does not carry it. The
User-Agent is a response-shape hint only: it never gates authorization,
visibility or validation, and it can expose nothing beyond the checksum of
the body the caller just uploaded.
Reading the checksum from the same PartInfo that produced the response ETag
also keeps the pair bound to one write, so a concurrent overwrite of the same
part number cannot publish another writer's checksum.
Tests cover the application token gating matrix including lookalike tokens,
the real minio-go response parser, concurrent overwrites of one part number,
and an in-process two-deployment probe that drives the federation branch
through the real getRemoteInstanceClient into a real PutObjectPartHandler for
both FULL_OBJECT and COMPOSITE uploads.
Fixes#64
Signed-off-by: Feng Ruohang <rh@vonng.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Use silo-pkg v3.12 strict validation when creating named policies and when creating or updating service-account session policies. Keep stored policy loads, IAM import, site replication, and STS inline policies on the permissive compatibility path.
Accept checksum-enabled UploadPart and UploadPartCopy requests when clients omit optional per-part checksum headers. Compute over the logical plaintext stream, persist the result, and return it from CopyPartResult while preserving client checksum validation.\n\nRefs #46; follow-ups #63 and #64 remain out of scope.
Reject pre-KV PostgreSQL and MySQL targets that lack a canonical connection string, propagate the typed migration error to the fatal startup boundary, and stop emitting unregistered discrete connection keys.\n\nCloses the implementation for #53; release and issue closure remain separate gates.
Standardize the related SILO components on Go 1.27 tooling, etcd 3.7.1, current Go-maintained modules, shared runtime versions, and explicit security and portability pins.
Keep the shared package Go 1.26 consumer floor, isolate lint tooling from product dependency selection, and preserve upstream-compatible import paths.
gocritic's ifElseChain check fails make lint on the --json/--quiet
branch introduced by b6d47b739; the quality job runs make lint on
every push to main. Behavior is unchanged (verified by re-running the
json/quiet success and failure cases byte-for-byte).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Findings from an adversarial review (Codex, gpt-5.6-sol at max effort)
of 2ff594f4b and 4c34d2309, each independently verified before fixing:
- SBOM generation: buildx attaches a provenance attestation, so every
per-arch digest names an OCI index; Syft's platform default on an
amd64 runner cannot resolve an arm64-only index and the step dies.
Pass --platform explicitly on all four Syft calls (the two classic
lanes had the same latent defect - the renamed workflow has not run
yet, which is why it never fired).
- Release ordering: the HEALTHCHECK survival check now runs against
the pushed architecture image before the versioned and rolling
multi-arch manifests are created, so a broken health config blocks
their promotion; the comment now states honestly that the
arch-suffixed tags are already public at that point.
- Gate assertions: tar's member-argument mode exits non-zero on any
missing name, which under pipefail masked a found forbidden file
when exactly one of them existed; -tv prints symlinks as
'name -> target', defeating $-anchored greps; and the licenses
check proved only one-of-three. Export the rootfs once and assert
every required and forbidden entry individually (busybox/sh and
usr/bin/mc[li] now covered), and match the image healthcheck as an
exact array instead of a substring.
- Probe target vs CLI-configured servers: a probe process cannot see
PID 1's argv, so --url gains EnvVar MINIO_HEALTHCHECK_URL as the
documented way to point the baked-in HEALTHCHECK at a server whose
address/TLS comes from command-line arguments (verified end to end:
server on --address :9010, env var alone turns the container
healthy). Baseline regenerated for the new env token.
- IPv6 zone identifiers: serialize probe URLs via url.URL.String()
so [fe80::1%eth0]:9000 becomes a valid %25-escaped URL (tests added).
- Boolean flags: read --json/--quiet via Bool() so --json=false is
false, instead of IsSet() which treats any occurrence as true.
- Docker's HEALTHCHECK timeout raised to 10s: an outer deadline equal
to the probe's own 5s always SIGKILLed the probe before it could
print its diagnostic line.
- test-release path filter now also triggers on cmd/healthcheck-main.go
and cmd/main.go, so subcommand regressions run the image gate.
Not adopted: require_text's comment-insensitivity in verify-rebrand.sh
(snapshot-tripwire by design, consistent with its other assertions -
the semantic check lives in the CI gate now), and full
staging-then-promote tag publishing (a workflow-wide redesign shared
with the classic lanes, tracked as follow-up).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add 'silo healthcheck [live|ready|cluster|cluster-read]', a thin
anonymous HTTP client for the server's own /minio/health/* endpoints,
so containers without a shell, curl, or mc can still run health
checks. Design: silo.pgsty.com/compatibility/feature/healthcheck/
The check vocabulary maps 1:1 onto the health API paths; the probe
target is derived from the server's own --address/MINIO_ADDRESS
contract with HTTPS auto-detected from the certs directory, and can
be overridden with --url. Exit codes are 0/1 only (Docker reserves 2);
diagnostics (x-minio-server-status, quorum headers) go into a single
output line for docker inspect. The request is strictly anonymous (a
credentialed request would be rejected by the reserved-path guard),
the transport bypasses HTTP_PROXY, and certificate verification is
skipped to match kubelet HTTPS probe behavior. Cluster checks default
to a 15s deadline so the server's 10s cluster_deadline can elapse.
Compatibility notes: the preserved /minio/health/* path literals and
the MINIO_ADDRESS env var are upstream wire/config surface, reused on
purpose; the rebrand-guard baseline is regenerated for the new route
literals (tests included) with zero new exported symbols. The docker
entrypoint argv translation learns the new command name.
Verified: unit tests, entrypoint tests, go vet, plus an end-to-end
run against a live server covering all four checks, --maintenance
(412), --json, usage errors, unreachable and timeout paths.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings the server in line with the contribution and copyright policy
already adopted in pgsty/mc.
Copyright terms were derived from the clock: startupBanner overwrote the
ldflags-injected CopyrightYear with time.Now().Year() and printed it as
the end of MinIO, Inc.'s term, so every January would have extended the
upstream copyright claim past the 2025 end of upstream development, and
release builds silently discarded the injected year. Both banners now
credit MinIO, Inc. for 2015-2025 and PGSTY from 2025 through the
release-stamped year, falling back to the current year in source builds.
NOTICE names PGSTY as the holder of the fork's modifications, matching
the banners and the packaging vendor field.
Contributions are accepted inbound=outbound under AGPL-3.0-or-later with
no CLA - the core is Copyright MinIO, Inc., so the combined work can
never be relicensed and a CLA would buy nothing. What a fork carrying a
downstream delta does need is provenance, so DCO 1.1 sign-off is now
mandatory and enforced by a workflow that rejects unsigned non-bot
commits. CONTRIBUTING documents sign-off, repair, cherry-pick provenance,
dual copyright headers, trailer preservation across squash merges, and
that assistive-tooling trailers carry no authorship or copyright claim.
verify-rebrand.sh pins the copyright split and the policy files so
neither can regress.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Feng Ruohang <rh@vonng.com>
The transitional references land in one commit, because they are only correct
together: the repository is pgsty/silo, its default branch is main, and nothing
in the tree should point a user at the old names.
Changed:
- Workflow branch filters. go.yml and vulncheck.yml gated on `branches: master`
for both push and pull_request, so renaming the default branch would have
taken automatic CI offline with no error and no signal - the workflows would
simply never trigger again. They now name main.
- Release target. goreleaser's `release.github.name` becomes silo, which is
what actually decides where a tagged build publishes. sign-release-rpms.sh's
GH_REPO default follows.
- The OCI `image.source` label, the Helm chart `sources` entry, the security
advisory link in the issue-template config, and the go.mod comment citing the
LDAP TLS fix.
- 115 occurrences across README, README_ZH, SECURITY, CONTRIBUTING and 30 docs
pages, including 72 links that also carried the master branch in their path.
Those matter most: GitHub redirects clone, fetch, push and web URLs after a
rename, but raw.githubusercontent.com does not, and neither follows a branch
rename - every one of those links would 404 twice over.
- Three error strings in cmd/erasure-sets.go, cmd/storage-errors.go and
internal/config/errors.go that print an issue URL to operators. These are Go
string literals inside rebrand-guard's brand allowlist, so the baseline is
regenerated. The regeneration removes exactly those three entries and adds
none; all twelve other protected sets, including the 9014 exported symbols,
are byte-identical.
- The transitional-naming disclaimers in README, README_ZH, SECURITY and
CONTRIBUTING are dropped, since they no longer describe anything.
Deliberately unchanged, all three because they exist to reject or freeze the old
name rather than to point at it:
- buildscripts/minio-upgrade.sh pins pgsty/minio@sha256:b6bfe72... - the frozen
pre-rebrand image is the control group for the MinIO-to-Silo upgrade test.
- helm-migration-guard rejects any rendered container still pulling pgsty/minio.
- verify-rebrand.sh rejects the same in the delivery surfaces.
Also unchanged: docs/config/README.md links to pgsty/mc/blob/master, and that
repository's default branch really is still master. It moves when mc does.
verify-rebrand.sh gains three assertions so this cannot silently regress: no
source reference may name pgsty/minio outside the three allowlisted guards, no
link may target pgsty/silo's master branch, and go.yml and vulncheck.yml must
filter on main. Both new rejections were negative-tested - reintroducing a
master branch filter and adding a pgsty/minio URL each fail the gate with the
specific message.
This commit assumes the rename actually happens. Until the GitHub branch and
repository renames are executed, the links it introduces do not resolve.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two coupled changes that must land together, because the same files carry both:
the server now identifies itself as Silo, and every path that would have called
home to a MinIO-operated service is closed.
Product identity
- build-constants.go: store name, UA name and startup banner become Silo. The
Go identifiers (MinioStoreName, MinioBannerName, ...) keep their names on
purpose - renaming exported symbols would churn the compatibility surface for
a cosmetic gain, and the rebrand guard freezes that surface.
- main.go, server-startup-msg.go, ftp-server.go and the user-visible log, help
and error strings across cmd/ and internal/ switch to Silo. Original MinIO
copyright, LICENSE, NOTICE and CREDITS are untouched; --version now prints
the upstream copyright, the pgsty modification notice, and the trademark
policy's approved "based on MinIO technology" attribution.
- api-headers.go: the HTTP Server header becomes "Silo". This is the one
externally observable identity change, so TestCommonHeadersUseSiloProductName
pins it - probes that sniff for "MinIO" must move to capability detection.
- Prometheus metric HELP strings keep their MinIO wording. They are part of the
metrics contract the guard protects, not product copy.
Configuration directory
- config-dir.go: new installs use ~/.silo. If only ~/.minio exists it is still
read, with a one-time notice and no files moved. If both exist ~/.silo wins
and an ambiguity warning is emitted; an explicit --config-dir always wins.
Covered by TestSelectDefaultConfigDir. The internal .minio.sys layout is
never renamed - this rule applies to the user config directory only.
Upstream service lockdown
- globalInplaceUpdateDisabled is now true at initialization rather than being
set from MINIO_UPDATE. common-main.go still parses MINIO_UPDATE so upgrading
nodes do not fail on an unknown key, but warns that the value is ignored;
there is no way to re-enable the updater. TestInplaceUpdateCannotBeEnabled
guards that. Without this, an admin with mc could have overwritten
/usr/bin/silo with an upstream MinIO binary.
- verifyBinary and commitBinary refuse early; the ServerUpdate v1/v2 admin
routes and the peer-rest update endpoints stay registered and keep returning
the existing programmatic error, so clients see a stable failure rather than
a 404.
- MinioReleaseBaseURL and defaultMinisignPubkey are emptied: no dl.min.io
download root, and upstream's minisign key is no longer a trust root for
anything this fork ships.
- cmd/callhome.go is deleted and internal/config/subnet/ is reduced to parsing
its old keys and reporting that the integration is disabled. config-current.go
warns instead of failing when callhome or SUBNET settings are present, so an
upgraded node with those keys still starts.
- internal/config/errors.go replaces the MinIO Slack and support entry points
with Silo documentation and issue links. Error codes and programmatic fields
are unchanged.
Verified: the compatibility baseline is unchanged except for the deliberate
removal of the /api/health/upload SUBNET route; go build, go vet and the full
cmd/ and internal/ unit suites pass; a locally built binary starts, serves
S3/Admin/metrics on the unchanged /minio/* routes, answers with Server: Silo,
and falls back to a pre-existing ~/.minio with the expected notice.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expand the generated-file gate beyond *_gen.go and go.sum to cover generated tests, msgp output, stringer files, go.mod, and untracked generated artifacts.
Regenerate the 19 stale stringer outputs with the go.mod-pinned x/tools version. Runtime String output is unchanged, and a repeated generation pass now leaves the tree byte-identical.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>