mirror of
https://github.com/pgsty/minio.git
synced 2026-08-09 15:53:28 +03:00
2ff594f4bb03df131f12cf61bfa4aee2ca657d68
12660 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2ff594f4bb |
feat: add the native silo healthcheck subcommand
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> |
||
|
|
219670d317 |
build: ship LICENSE and NOTICE in every package and image
The RPM, DEB and APK payloads carried no license material at all, and
the OCI image copied LICENSE and CREDITS but not NOTICE. For an AGPL
distribution that is not defensible packaging. Every delivery channel
now carries the same license set:
- nfpm packages install LICENSE and NOTICE under /usr/share/doc/silo/.
The entries are declared once per packager because nfpm only honors
type: license on rpm and silently drops such entries from deb and apk
-- the rpm keeps its %license flag, deb and apk carry plain files at
the same path. CREDITS stays out of the OS packages deliberately: at
1.8MB it would dominate an otherwise ~40MB payload.
- The OCI image gains /licenses/NOTICE next to the existing LICENSE and
CREDITS, in the Dockerfile and in both context-staging paths (the
make docker target and docker-release.yml).
- The GoReleaser archives list their files explicitly; the default
globs would keep missing NOTICE.
NOTICE's first line now reads 2015-2025 to match the banner constants
fixed in
|
||
|
|
6bd9cf77ef |
build: regenerate CREDITS from the linked module set and guard it in CI
The shipped CREDITS predated the fork's dependency work: 38 of the modules in the current go.mod, among them lestrrat-go/jwx, go-tpm, go-spiffe and the charmbracelet family, had no entry at all, while entries lingered for modules no longer in the build. An attribution file that silently drifts from go.mod is worse than none, so it is now generated, not curated. buildscripts/gen-credits.sh (make credits) rebuilds the file from the licenses of every module go list -deps reports for the main package -- the set actually linked into the silo binary, so test-only and tool dependencies stay out. Entries keep the established name / URL / license-text layout. New over the old file: - Modules replaced in go.mod are annotated with the repository that actually serves them, so the pgsty/silo-console, pgsty/mc and pgsty/silo-pkg forks are named next to their upstream import paths. - Bundled NOTICE files are reproduced after the license text, which Apache License 2.0 section 4(d) requires when redistributing; 23 modules carry one. - minio/colorjson, minio/csvparser and minio/filepath publish no license file at all; they repackage Go standard library code and their sources carry the Go Authors' BSD-style header, so the Go project license is reproduced for them with a note saying why. - The Go license text itself comes from the pinned golang.org/x/sys module rather than GOROOT, because Homebrew's Go omits GOROOT/LICENSE and the module copy is version-locked. check-gen now runs the generator and fails on a CREDITS diff, the same treatment go.mod and go.sum already get, so dependency changes cannot leave stale attributions behind. Output is deterministic: two runs are byte-identical, and LC_ALL=C sorting plus version-pinned inputs keep it that way across machines. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Feng Ruohang <rh@vonng.com> |
||
|
|
a6d6d9b028 |
build: update the embedded Console to SILO Console v2.1.1
Pins pgsty/silo-console at 72fc0a5e, the v2.1.1 tag, replacing the v2.1.0 commit from earlier today. The pseudo-version form stays because the compatibility module path github.com/minio/console carries no /v2 suffix, so a v2.x semver tag is not directly selectable. v2.1.1 is 5 commits over v2.1.0. The one substantive change for the server is a fix that keeps unresolved i18n placeholders out of the metrics dashboard legends; the rest is the v2.1.1 changelog, dropping the container-image advertising from the Console docs, and regenerated embedded web assets. Verified: go mod tidy -compat=1.26 is a clean no-op afterwards, which is what make check-gen asserts in CI, and go build ./... and go vet ./... pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Feng Ruohang <rh@vonng.com> |
||
|
|
05be686b84 | docs: update README and svg log | ||
|
|
b57275be34 |
docs: adopt the no-CLA plus DCO policy and fix copyright terms
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> |
||
|
|
6740e6978f |
ci: move the workflow actions onto the Node 24 runtime
actions/checkout@v4 and actions/setup-go@v5 declare runs.using: node20, so every Go CI job printed the runner's Node 20 deprecation warning. The release, test-release and docker-release workflows carried six more node20 actions that never appeared in that warning only because those workflows run on a tag or a dispatch rather than on every push: upload-artifact v4, goreleaser-action v6, build-push v6 and the docker qemu/buildx/login v3 line. All of them move to the current majors, which declare node24. The SHA-pinned rest already qualified -- attest and sbom-action are node24, cosign-installer is composite. Checked against what those majors removed. build-push v7 dropped DOCKER_BUILD_NO_SUMMARY and DOCKER_BUILD_EXPORT_RETENTION_DAYS and setup-buildx v4 dropped its deprecated inputs, none of which are used here. checkout v7 refuses to check out fork PRs under pull_request_target and workflow_run, and no workflow here triggers on either. setup-go v6 exports GOTOOLCHAIN=local, the one change with teeth. go.mod declares `go 1.26.5` and carries no toolchain directive, every job resolves that same version through go-version-file, and nothing in the module graph asks for more, so there is nothing left for the go command to fetch: `go build ./...` passes under GOTOOLCHAIN=local. The tools installed from outside the module are in range too -- nfpm v2.47.0 requires 1.26.4 and govulncheck v1.6.0 requires 1.25.0 -- and golangci-lint arrives as a prebuilt binary from upstream's install script, not through the toolchain. The runner floor that comes with node24 (v2.327.1) is met by GitHub-hosted runners, and every job here is ubuntu-latest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
62717d7bf6 |
build: update the embedded Console to SILO Console v2.1.0
Pins pgsty/silo-console at 343d9442, the v2.1.0 tag, replacing the v2.0.0 commit from 2026-08-04. The pseudo-version is still the form used here because the compatibility module path github.com/minio/console carries no /v2 suffix, so a v2.x semver tag is not directly selectable; the comment above the directive now names the release the commit actually corresponds to. v2.1.0 is 23 commits over v2.0.0. The substantive ones for the server are a hand-rolled EN/ZH i18n layer with a language toggle covering login, navigation, dashboard, buckets and object browser, identity and configuration, event destinations, observability and admin tools; a dashboard migration to MinIO Metrics V3; and removal of the remaining SUBNET references from health reporting, which matches this repository's own decision to close the upstream telemetry surfaces. The rest is UI polish and regenerated embedded web assets. Verified: go mod tidy -compat=1.26 is a clean no-op afterwards, which is what make check-gen asserts in CI, and go build ./... and go vet ./... pass. Not included here: the GitHub Action version bumps and the .gitignore entry sitting in the working tree are someone else's change and stay uncommitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
f1c77d5a2b |
ci: let the READMEs name the old repository without pointing at it
The previous commit added a rename note to both READMEs and broke the gate I added one commit earlier. That gate rejected the string pgsty/minio anywhere outside three guard files, which was the wrong invariant: the point is that the old name must never be a live target, not that it must never be spoken. A README explaining where the MinIO-named artifacts went has to name them, and doing so is the opposite of stranding a reader on a dead repository. Split into two rules that say what is actually meant: - No live URL may resolve to the old repository anywhere, READMEs included - github.com/pgsty/minio and hub.docker.com/r/pgsty/minio are rejected outright. This is the rule that protects users. - The bare name is allowed only in five files: the pinned pre-rebrand image digest in the upgrade test, the two guards that refuse a legacy image, and the two READMEs. Both halves are negative-tested: adding a github.com/pgsty/minio link to README.md fails rule one, and adding a bare pgsty/minio to docs/docker/README.md fails rule two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c47733abc4 |
docs: point readers at the archived minio branch
Someone arriving at main after the rename has one question the README did not answer: where did the MinIO-named builds go. Both READMEs now say it directly — the archived minio branch and the releases up to RELEASE.2026-08-04T00-00-00Z hold the artifacts maintained under the original identity, and those assets and the docker.io/pgsty/minio image stay published and unmodified. The note also records the rename itself with its date, and repeats the one fact that determines whether an operator has any work to do: only product and artifact names changed. MINIO_* variables, minio_* metrics, x-minio-* headers, /minio/* routes and the on-disk format are the same. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
c46b16ec62 |
chore: cut over to pgsty/silo and main
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> |
||
|
|
fd2ca1c6d2 |
docs: rebrand the repository documentation, templates and dashboards
README, README_ZH, SECURITY, COMPLIANCE, CONTRIBUTING, NOTICE, code_of_conduct, the vulnerability and PR-etiquette documents, the GitHub issue and pull request templates, and the docs/ tree all present Silo as the product. The Grafana dashboards under docs/metrics/prometheus/grafana/ have their panel titles and descriptions rebranded while every minio_* query, label and expression is left alone, so existing alerts and recording rules keep matching. The distinction the review demanded is applied per hit rather than by search-and-replace: - Product and command text becomes Silo and silo: install and run instructions, systemd examples, compose services, download links, badges. - Protocol and interface text keeps MinIO: MINIO_* variables, minio_* metrics, x-minio-* headers, /minio/* routes, .minio.sys, arn:minio, and API field and error names. - Attribution keeps MinIO and gains the fork's own: the AGPL obligations, original copyright, CREDITS and NOTICE stay, with the modification notice added alongside rather than replacing them. - Historical and third-party references are left as facts, not rewritten for brand tidiness. README and README_ZH each carry an explicit non-affiliation notice, document the side-by-side package migration including the /etc/systemd/system/silo.service.d/10-legacy-user.conf drop-in for keeping a legacy UID/GID, and state that recursive chown is never performed. The trademark attribution uses the policy's approved "based on MinIO technology" wording, not the shortened form the policy rejects. github.com/pgsty/minio links are left in place and labelled transitional. The repository has not been renamed, and rewriting them now would produce documented URLs that 404 until the cutover; they change in the cutover commit together with the goreleaser release target, the OCI source label and the raw-content branch. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
6613c2a3cb |
test: pin the external fixtures and run the suites against the silo binary
The test and verification scripts invoked ./minio and pulled their tooling from upstream infrastructure with no integrity check. Every `curl | tar` of a client or an old server binary was an unverified execution path in a script that regularly runs as a privileged user, and several fetched a floating "latest". Two installers replace all of it: - install-mcli.sh resolves a pinned pgsty/mc release, downloads the archive and its checksum manifest, requires exactly one valid manifest entry for the asset, verifies it, and installs. MCLI_BIN with a mandatory MCLI_SHA256 lets an offline or air-gapped run supply its own binary, still checksum-checked. - install-verified-fixture.sh takes source, expected SHA-256 and target, and refuses anything that does not match. Sources may be a URL or a local file. Every script that previously downloaded mc now calls install-mcli.sh. The three places that genuinely need an upstream artifact - the old MinIO server binary for the LDAP IAM upgrade-import test, the 2021 mc for the three-site replication test, and the functional-tests.sh fixture - go through install-verified-fixture.sh with the digest recorded inline. Those dl.min.io URLs remain on purpose: they are historical upstream artifacts needed to prove upgrade compatibility, and they are now pinned and verified rather than trusted. The scripts otherwise switch to ./silo, silo.service, the silo container and compose service names, and SILO_CONFIG_DIR. run-multi-site-minio-idp.sh is renamed to run-multi-site-silo-idp.sh with the Makefile target following. buildscripts/minio-upgrade.sh keeps its name and its `minio server` argv - it exists to test the MinIO-to-Silo upgrade, so the old side must stay old - but it is now pinned to an image digest rather than a tag, and its `docker system prune` and `docker volume prune` calls are removed. Those ran unfiltered against the developer's whole Docker installation; the resiliency tests had the same problem and lose their prune and `docker ps -q` sweeps too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bd8df51665 |
ci: gate the rebrand on compatibility, packaging and provenance evidence
A rebrand of this size cannot be reviewed by reading the diff. The risk is not what was renamed but what was renamed by accident: an environment variable, a metric, a header, a route, a storage marker or an exported symbol that some deployment depends on. So the invariants get a machine check. rebrand-guard extracts the identifiers a product rename must not touch - the go.mod module path, github.com/minio/* imports, MINIO_* variables, minio_* metric literals, x-minio-* headers, route string literals, the hardcoded grid routes in internal/grid/manager.go, the reserved bucket root, .minio.sys markers, arn:minio and minio:s3 policy values, every exported symbol in cmd/ and internal/, and every branded string literal - and compares them to a checked-in baseline. Any removal or addition fails the build, in both directions, so a future change cannot quietly drop one either. The recorded delta against the pre-rebrand tree is: module path, 137 imports, 19 metrics, 15 storage markers, 58 policy values and all 9014 exported symbols byte-identical; environment 437 -> 436, headers 85 -> 84, routes 331 -> 330. Each of those three is accounted for. MINIO_CONFIG_DIR and MINIO_VERSION were a shell local and a compose variable in test scripts, never server settings. x-minio-common was a docker-compose extension key, never an HTTP header. /api/health/upload was the SUBNET upload path in the deleted callhome.go. verify-rebrand.sh covers what the identifier guard cannot see: that the retired upstream delivery paths stay deleted, that the packaging and container files name silo, that silo.service keeps its Conflicts and both EnvironmentFile lines, that silo.env contains no active assignment that would shadow the legacy file, that no active delivery surface still publishes pgsty/minio, and that no non-test Go source contains a dl.min.io, subnet.min.io, api.min.io, slack.min.io or play.min.io URL. Both, plus the entrypoint argv test, run in go.yml, release.yml and test-release.yml. test-release.yml also gets the path filters right: it was watching minio.service, which no longer exists, so the packaging gate would have gone silently dormant. It now watches the unit, defaults, sysusers, lifecycle scripts, helm chart and both guards, and asserts the four-file payload, the absence of any cross-name Provides/Obsoletes/Conflicts, and that a container started with the legacy `minio server` argv really runs silo as PID 1. release.yml and docker-release.yml additionally verify that the checked-out revision matches GITHUB_SHA before building. GitHub's OIDC certificate records GITHUB_SHA, not the ref passed to actions/checkout, so a manual dispatch from the wrong ref would have produced provenance describing different source from the bytes being published. They now emit SBOMs and attestations for archives, packages and images, and docker-release verifies the release workflow's attestation on every artifact it consumes. Images publish to pgsty/silo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
e071bb77e4 |
build(helm): replace the minio chart with a silo chart that preserves identity
helm/minio becomes helm/silo: chart name silo, version 6.0.0 -> 7.0.0, the MinIO wordmark icon replaced with the project's own, image.repository and mcImage.repository pointing at pgsty/silo, and the container command changed to silo. User-visible titles, comments and documentation links are rebranded. The MINIO_* environment variables and every existing values key are kept - the first Silo chart is a rename, not a values-schema migration. The hard problem is that a chart rename normally rewrites Kubernetes resource identity, and a StatefulSet's selector and volumeClaimTemplate are immutable. An existing release upgraded carelessly would either fail or orphan its PVCs. Two things address that: - Templates no longer derive the container name from .Chart.Name. It comes from a helper, so nameOverride can pin it, which means an existing release can be upgraded with nameOverride=minio, fullnameOverride=<existing-fullname> and serviceAccount.name=minio-sa and render byte-stable identity while switching chart and image. - helm-migration-guard and verify-helm-migration.sh make that a gate rather than a documented hope. The script lints the chart, renders it in distributed and standalone modes plus the optional templates, then renders the legacy chart from a pinned commit and the new chart with those three overrides and compares resource identity. The guard additionally rejects any rendered container still pulling pgsty/minio or invoking /usr/bin/minio. It runs through a pinned alpine/helm image when helm is not installed locally, so the gate does not depend on the developer's machine. Currently green over 7 compared resources. Rollback is asymmetric and the README says so: the old chart with the new image survives via the entrypoint argv shim, but the new chart with an old MinIO image does not, because `silo server` is not a command that binary knows. Only `helm rollback` is supported, never an image-only downgrade. Not addressed here: the default image tag is pgsty/silo:RELEASE.2026-08-04T00-00-00Z, which does not exist yet. The chart must not be published until the first Silo image is pushed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
30749911bd |
build(docker): ship the silo binary and translate the legacy argv command
Dockerfile.goreleaser now copies silo to /usr/bin/silo, defaults to CMD ["silo"], and labels the image as Silo. MINIO_UPDATE_MINISIGN_PUBKEY is removed from the image environment: with the updater permanently disabled it was dead weight, and leaving upstream's verification key in a Silo image implied a trust relationship that does not exist. The MINIO_* runtime environment variables, ports, volume and health endpoints are unchanged. The image keeps shipping mcli with an /usr/bin/mc symlink. That is the client, not a MinIO-branded alias for the server binary, and the Helm post-install job and existing container scripts call it by name. docker-entrypoint.sh translates a legacy first argument: `minio server /data` becomes `silo server /data`, so an existing `command: minio server ...` in compose or a Pod spec keeps working across the image swap. The translation is argv-level only - no file named minio is installed, and an explicitly overridden `entrypoint: /usr/bin/minio` still fails, which is the honest outcome since that path genuinely no longer exists. The entrypoint also fixes an unrelated startup hazard it was already carrying: when the image runs under an arbitrary UID, HOME points at an unreadable /root and the server probes its default config directory during initialization. It now falls back to /tmp when HOME is unset, /root, missing or unwritable. docker-entrypoint_test.sh pins all of it - empty argv, legacy minio, native silo, bare flags, and an explicit shell command that must stay explicit. It is wired into make rebrand-guard and into the go.yml, release.yml and test-release.yml gates, so removing the shim breaks CI rather than breaking users. Passing locally. download-static-curl.sh gains checksum verification for the curl it fetches into the build stage. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
15ab10833b |
build: rename the delivery artifacts to silo and complete the package payload
Everything a user installs is renamed, and the package finally installs enough to be startable on a clean host. Artifact names - goreleaser.yml: build id, binary, archive and checksum manifest become silo_*. release.github.name stays "minio" with a comment - the GitHub repository has not been renamed yet, and pointing at pgsty/silo before the rename would 404. Also adds per-archive SPDX SBOMs and a keyless cosign signature over the checksum manifest, so the signed manifest covers archives and SBOMs together. - nfpm.yml: package name silo, and the binary moves from /usr/local/bin/minio to /usr/bin/silo. /usr/local is not on the default PATH of a systemd unit and is not FHS-correct for a distribution package. - package-release.sh, sign-release-rpms.sh and verify-build-provenance.sh follow the new names; the RPM signing script asserts NAME=silo and the new four-file payload. nfpm is now invoked from the repository root so relative script paths in the config resolve regardless of the caller's directory. Package relationships are deliberately empty No Provides, Obsoletes, Replaces or package-level Conflicts. Obsoletes: minio cannot distinguish a pgsty package from upstream's own identically named one, so an unattended dnf upgrade could silently swap a different vendor's product for this one. With no relationships, both packages coexist, their file sets do not overlap, and migration and rollback are single explicit commands. The mutual exclusion lives in the unit instead: silo.service carries Conflicts=minio.service plus After=minio.service. Payload, from two files to four - /usr/bin/silo - /usr/lib/systemd/system/silo.service - /etc/default/silo, installed config|noreplace - /usr/lib/sysusers.d/silo.conf The old package shipped a unit referencing an account nothing created, so a clean install could not start. postinstall.sh now creates the silo system account through systemd-sysusers, useradd or BusyBox adduser in that order and runs daemon-reload. It never stops a service, never chowns data and never touches /etc/default/minio. preremove.sh disables silo.service only on a real removal - Debian "remove", RPM 0, Alpine's dotted version - so upgrades leave the running service alone. lifecycle_test.sh exercises both against a stubbed PATH, so a green run cannot create an account or touch the host. silo.service reads /etc/default/minio then /etc/default/silo, in that order, so an existing node's MINIO_* values keep working and the new file overrides them. The packaged silo.env therefore ships comments only: any active assignment would shadow the legacy file with an empty value. Makefile: build/install/install-race produce ./silo, and the docker target now assembles a context from a locally built linux binary plus Dockerfile.goreleaser instead of the deleted Dockerfile. The hotfix, hotfix-push, docker-hotfix and docker-hotfix-push targets are gone - they downloaded upstream's pkger, signed with upstream's minisign key and scp'd to dl-N.minio.io. verifiers now depends on a new rebrand-guard target. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
77bdc4c0cd |
feat(server): present Silo identity and close the inherited upstream services
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> |
||
|
|
15def34dce |
chore: drop the unpublished upstream delivery and branding residue
The tree still carried a complete second publishing pipeline inherited from upstream that pgsty has never operated, plus MinIO-branded artwork the fork has no right to redistribute. None of it is a pgsty release artifact, so the history-is-immutable rule does not protect it: nothing here was ever published under this repository's name, and git history plus the pre-cutover tag keep the provenance. Removed, by category: - charts.min.io machinery: CNAME (charts.min.io), _config.yml, index.yaml, helm-reindex.sh and 90 helm-releases/minio-*.tgz. GitHub Pages is not enabled on this repository, so this was dormant - but enabling Pages for any reason would have stood up a convincing impostor of the official MinIO chart repository from our own domain. - Dockerfiles that consume upstream binaries: Dockerfile, Dockerfile.cicd, Dockerfile.hotfix, Dockerfile.release, Dockerfile.release.old_cpu, Dockerfile.scratch and docker-buildx.sh. These fetch or expect a MinIO-built binary; a Silo image must only ever contain an artifact this repository built from this commit. Dockerfile.goreleaser, the one the release workflow actually uses, stays. - buildscripts/upgrade-tests/ (compose.yml, minio.env, nginx.conf), which pinned minio/minio images through a MINIO_VERSION compose variable. The upgrade test is rebuilt on image digests in a later commit. - docs/hotfixes.md, describing an upstream hotfix distribution channel that does not exist for this fork. - MinIO trademark artwork: .github/logo.svg (a recoloured MinIO wordmark), the docs/screenshots/ set, the Grafana dashboard screenshots and docs/federation/lookup/bucket-lookup.png. The dashboards themselves are kept and rebranded separately; only the images carrying MinIO chrome go. Deliberately not touched: published releases, tags, image digests and package signatures remain byte-identical. This commit only removes paths that were never part of a pgsty release. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
d88f46ccee |
ci: build the release runtime image and assert graceful shutdown
The release image (Dockerfile.goreleaser) was only ever built by docker-release.yml, which is workflow_dispatch only - so the runtime layer and entrypoint were never exercised by CI until an actual publish, where a broken COPY path or a signal-handling regression would surface at the worst possible moment. MC already covers this in its test-release; the server did not. Add an offline, deterministic smoke: assemble a minimal image from the linux/amd64 binary goreleaser already built and the real entrypoint, then start the server and `docker stop` it on both the default and the MINIO_USERNAME drop-privilege paths, asserting PID 1 is minio, the exit is 0, the shutdown is sub-timeout, and the "Exiting on signal" log is present. The mcli-download build stage is skipped deliberately - it hits the GitHub API and would make this gate flaky and non-reproducible; its simple, checksum-guarded logic still runs at publish time. This is the regression guard for the exec-into-chroot entrypoint fix. Also add dockerscripts/docker-entrypoint.sh to the pull_request paths so an entrypoint change actually triggers this pipeline. Verified locally end to end: the fixed entrypoint passes both paths; reverting the exec makes the drop-privilege path time out to exit 137 and the step fails. Co-authored-by: Claude <noreply@anthropic.com>RELEASE.2026-08-04T00-00-00Z |
||
|
|
021110b451 |
ci: make the lint, getdeps, and release gates fail honestly
Three gate-correctness fixes: - lint: `command typos && typos ./ || echo skipping` ran typos twice (POSIX `command` executes it) and, via `&& ... || echo`, turned a real typo finding (nonzero exit) into the "not installed" message with exit 0 - so spelling issues could never fail the gate. Use `command -v` to test presence and run typos once, letting its findings surface. - getdeps: `curl -sSfL ... | sh` took the pipeline's exit from sh, and make's /bin/sh has no pipefail, so a failed or partial download of the golangci-lint installer was reported as success and a truncated script could run. Download to a temp file under `set -e` (with a trap to clean it up) and execute that, so a curl failure aborts the target. Verified: a 404 now exits nonzero instead of silently continuing. - release: workflow_dispatch checked out github.ref (the branch it ran from) while naming artifacts after the input tag, so a release could be built from one ref and published under another. Pin checkout to the requested tag. Not addressed here: the server's release container image is still only built by the publish-time docker-release workflow, never in CI (MC covers this in its test-release). Flagged for a separate decision. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
aa51393694 |
build: install the systemd unit under /usr/lib, drop the template residue
nFPM placed minio.service at /lib/systemd/system. On merged-usr EL/Fedora the
file lands correctly through the symlink, but the RPM database records /lib/...,
so `rpm -qf` disowns it and - more importantly - systemd's
%transfiletriggerin watches /usr/lib/systemd/system, so the path recorded as
/lib/... never fires the automatic `daemon-reload` on install or removal. Move
the unit to the canonical /usr/lib/systemd/system, which both restores that
trigger and makes the package own the path it ships.
The destination is asserted verbatim in several places, all updated in lockstep
so the packaging gate still passes: the six name/payload/sha checks in
test-release.yml (rpm/deb/apk) and expected_payload in sign-release-rpms.sh.
Verified by building a real deb with the new config: the unit is at
./usr/lib/systemd/system/minio.service.
Also drop the `# Built for ${project.name}...` line from minio.service. nFPM
expands variables in content src paths, not file bodies, so that pkger-era
placeholder was being written verbatim into every installed unit.
Not addressed here, deliberately: the package still does not create the
minio-user account the unit references. That matches upstream MinIO's own
packages (both rely on the documented manual useradd) and is not a fork
regression, so it stays a documentation step rather than a scriptlet.
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
e064b5555f |
build: remove the untracked shadow .goreleaser.yml
A gitignored .goreleaser.yml sat in the repo root, so a bare `goreleaser` invocation - the default discovery path - picked it up instead of the .github/goreleaser.yml that CI passes with --config. The two had diverged completely: the shadow published the GitHub release directly (draft: false), pushed pgsty/minio:latest from a dockers section, and stamped a different vendor and artifact naming. Since RPM signing runs on the maintainer's own machine, that shadow config was one stray `goreleaser release` away from publishing a mislabelled release and overwriting the Docker latest tag. Delete the file and drop its .gitignore entry so any future .goreleaser.yml shows up as untracked and is caught by the release workflow's clean-checkout check, rather than silently steering local builds. CI is unaffected: it names its config explicitly. (A copy of the removed file is preserved out of tree for reference.) Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2ca4971d91 |
build: stop stamping the build machine's GOPATH/GOROOT into the binary
gen-ldflags injected -X cmd.GOPATH / cmd.GOROOT from the builder's environment, baking absolute paths like /Users/<user>/go into every released binary. That defeats -trimpath and makes the build unreproducible: a third party rebuilding the same tag gets different bytes and cannot verify checksums.txt. The values only seed logger.Init's source-path trim list, and under -trimpath the binary's paths are already relative, so there is no build-machine prefix left to trim - the trim list also still gets runtime.GOROOT() and build.Default.GOPATH at run time. Dropping the two stamps changes no observable logging behaviour; cmd.GOPATH/GOROOT keep the empty defaults a plain go build leaves. Verified: gen-ldflags output no longer contains cmd.GOPATH/GOROOT; a -trimpath release build has zero occurrences of the builder path (was 1); Version, ReleaseTag and CommitID stamps are intact. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4c185d5a66 |
ci: stop interpolating the dispatch tag into the release script
The compute step spliced ${{ github.event.inputs.tag }} straight into the
run: body, so a dispatch tag containing shell metacharacters was parsed as
script - arbitrary code in a job holding a contents:write token. Pass the
input through the environment instead (INPUT_TAG), the same shape
docker-release.yml already uses, so the value reaches bash as data.
Second vector: the old format check compared the sed output against the input
to decide validity, and sed anchors ^...$ per line. A tag carrying a newline
passed the check on its first line and the remaining lines flowed into
$GITHUB_ENV, setting arbitrary variables (PATH, LD_PRELOAD, ...) for every
later step. Replace it with a bash =~ whitelist anchored to the whole string,
which rejects any multi-line value up front.
Verified: a legal RELEASE.* tag is accepted and yields the right PKG_VERSION;
a `"; touch ...; #` injection and a newline-carrying PATH/LD_PRELOAD payload
are both rejected; an empty input falls back to the git ref name.
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
ca674a6967 |
build: cross-compile only the targets we publish
The crosscompile gate built 15 OS/arch combinations, ten of which we never ship (ppc64le, mips64, s390x, mips, riscv64, 386, arm, freebsd, netbsd, openbsd). On a cold CI cache each target costs ~80s, so the full set ran ~21 min and tripped the job's 20-min timeout, cancelling the only CI run for the release HEAD. Trim the list to the exact goos/goarch matrix the release actually produces (see .github/goreleaser.yml): linux, darwin, windows on amd64 and arm64. This also closes a coverage gap: windows/arm64 is published but was not being compile-checked. The trimmed set builds all six in well under the timeout. Note: netbsd is no longer compile-checked here, so the go-systemd v22.6.0 pin (kept because v22.7.0 does not build on netbsd) loses its CI guard. The pin is retained deliberately - it is harmless on the platforms we ship and upgrading go-systemd is a separate decision - but a netbsd regression in a dependency would now surface only if that build is exercised out of band. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
3b8a55deef |
fix: exec into the dropped-privilege process so signals reach MinIO
The two chroot branches that drop privileges when MINIO_USERNAME/GROUPNAME (and optionally MINIO_UID/GID) are set ran chroot as a child of the entry shell, leaving the shell as PID 1. A SIGTERM from `docker stop` or an orchestrator then went to the shell, which does not forward it, so MinIO was never asked to shut down and was killed after the stop timeout (exit 137) with no "Exiting on signal" log - risking in-flight requests and data at the flush boundary. The default branch already exec's; these two now do too, so MinIO runs as PID 1 and receives the signal directly. Verified in a faithful reproduction of the release runtime layer: all three paths (default, USERNAME only, USERNAME+UID/GID) now stop in ~0.2s with exit 0 and log the graceful shutdown, where the two drop-privilege paths previously timed out to exit 137. Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
11d79fddc3 |
ci: gate build, vet, tests, lint, generation, race, and crosscompile
Make the repository's actual quality contract visible in CI. Check formatting, build and vet the tree, run cmd and internal tests, enforce pinned lint and generated-file cleanliness, exercise S3 Select under the race detector, and cross-compile every supported target. Keep each concern in a separate job so failures identify the missing guarantee instead of hiding behind a single aggregate test result. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
475236c79c |
test(s3select): stop racing minio-go's parser for the response body
SelectResults spawns a parser goroutine that drains and closes the response body when the stream ends; deferring res.Close() had the test drain and close the same bytes.Reader concurrently, which the race detector catches reliably. Give the test body a close signal and wait for the parser to finish instead of competing with it. The double-close lives in minio-go's client parser, which no server code path uses; it remains worth an upstream report. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1814ae52f4 |
build: regenerate and verify all generated outputs
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> |
||
|
|
632ade111b |
build: pin golangci-lint installation
Installing golangci-lint from the moving master branch made local and CI results depend on the day they ran. Pin v2.11.3 and install it into a versioned path so version changes cannot accidentally reuse a stale binary. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
32863c8523 |
ci: publish container images from a published release, on demand
The release workflow built and pushed the container images in the same run that created the draft, so pgsty/minio:latest moved to a build nobody had signed or published yet. Abandoning that draft left latest pointing at it with no way to notice, and the images were the one artifact of a release that escaped the draft gate entirely. Image publishing moves to its own dispatch-triggered workflow that takes a tag and refuses to touch anything that is not a published, non-prerelease release, and that is not the latest one - since it moves the latest image tag, running it for an older release would silently roll users back. It builds from the archives attached to that release, checked against the published checksums, rather than rebuilding from source: the image then contains the same binary the tarball does, by construction rather than by assumption. GoReleaser loses its dockers and docker_manifests sections along with the QEMU, buildx and registry-login steps that only existed to serve them, and the release job drops packages: write, which was granted for a GHCR push that never happened. Releasing now has one more manual step. That is the point: tagging no longer moves docker latest, so the tag can be cut, inspected, signed and published before anything reaches users who pull by tag. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
cf7df097b2 |
ci: verify release provenance, names, checksums, and payloads
Refuse dirty release checkouts and verify that every GoReleaser binary records the tagged revision with vcs.modified=false before packaging or publication. Exercise the complete nFPM output in the release test pipeline: assert the six public names, validate every checksum and package identity field, and prove that RPM, DEB, and APK payloads contain the exact source binary and systemd unit. Validate release scripts and make their identity expectations the single source of truth. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
10c7670b80 |
build: package releases with nFPM under the PGSTY identity
Replace minio/pkger's hard-coded upstream identity with an in-tree nFPM configuration. Packages now name PGSTY as vendor and maintainer, use the SILO homepage and SPDX license, and preserve the established package names, versions, payload paths, modes, and checksum format. Resolve both the release binary and systemd unit independently of the caller's working directory. The release and test workflows share the same package script, while the signing script consumes the same final metadata contract. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9c799f42d5 |
build: stage draft releases for local RPM signing
GitHub Actions cannot hold the Pigsty RPM signing key, so the release workflow must stop before publication. Create releases as append-only drafts, refuse to overwrite existing assets, and make publication an explicit action after local signing and review. Add a maintainer-side signing command that downloads the two RPMs, verifies checksums and package identity, signs them with the Pigsty key, regenerates checksums, and only replaces draft assets when --upload is explicitly requested. Published releases are never modified. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
8eae745ab2 |
docs(security): record the 2026-08-04 hardening advisories
Consolidate the release-cycle security ledger after the implementation commits are stable. Document internode containment, policy-condition source hardening, the opt-in trusted-proxy boundary, and the bucket/object authorization tightening with fork-local identifiers where no CVE exists. Use silo-pkg v3.11.0 as the maintained dependency reference, explain compatibility and migration behavior, and link each entry to the rewritten commit that actually carries the fix. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b42ee4e8ac |
chore: ignore maintainer-only workspace files
Keep unpublished security working notes, local agent instructions, and maintainer scratch material out of the public source tree. Already tracked security documentation remains tracked; the ignore rules only prevent accidental additions. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5f4513fd40 |
docs: migrate Silo resources to unified portal
Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
dfe6698627 |
helm: harden defaults for RELEASE.2026-08-04
Point both the server and bundled client jobs at the release tag being cut, keep chart appVersion aligned, and retain the maintained pgsty/minio image selected by the public chart baseline. Stop creating the console/console123 administrator by default. Leave an explicit change-me example while rendering no user-creation job unless an operator opts in. Chart packaging and repository indexing remain separate release steps. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
2602177ef6 |
fix: guard the ReadParts trace path against an empty part list
ReadParts built its disk-health trace path from partMetaPaths[0] with no length check, so a caller passing no paths at all indexed an empty slice. xlStorage.ReadParts handles an empty list perfectly well - it returns an empty result - so the panic came entirely from the metrics bookkeeping wrapped around it. The reachable caller is ReadPartsHandler, which decodes its path list from a msgpack request body that an authenticated peer controls, and neither the handler, the storage-REST client, nor the path guard rejects an empty one: guardPaths ranges over the slice, so an empty slice passes vacuously. net/http recovers a panicking handler, so this is not a crash - which is what makes it worth fixing rather than merely tidy. ReadPartsHandler calls keepHTTPResponseAlive before it calls ReadParts, and that helper spawns a goroutine whose only exit is receiving from the channel done() writes. Panicking in between skips both done(err) and done(nil), so the process survives and the keep-alive goroutine and its ten-second ticker stay parked forever - one per request, driven by a request body the caller chooses. Repeating one malformed frame exhausts the node. The fix follows DeleteVersions in the same decorator, which already guards the identical "merely for tracing" lookup; ReadParts was the one method missing the pattern. It also covers the second entry point, the per-disk errgroup in readParts, where a panic has no recover at all and would take the process down. That path is screened at the S3 boundary today, so this is defence in depth there. No error is returned for the empty case. The storage layer's answer to an empty list is an empty result, and turning that into an error would be a behaviour change on a path that is merely degenerate. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9dd1dc172d |
fix: restore the merrs tag on dangling-object deletion audit records
joinErrs ranged over its own empty accumulator string instead of the errs slice, so the loop body never executed and the function unconditionally returned "". Its only caller feeds the merrs tag of the DeleteDanglingObject audit event, so every dangling deletion was recorded without the per-drive metadata errors: the record showed what was deleted but not which drives errored or why quorum was lost. Range over errs instead. The existing separator logic is already right once the loop runs, since a leading nil error appends "<nil>" and every later element gets its comma. Upstream's open minio/minio#21580 fixes the same bug with a strings.Builder rewrite, not taken here: the function runs once per dangling deletion over a drive-count-sized slice, and the one-word change is the entire defect. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
0c14d81510 |
fix(notify): quote libpq connection parameters
The discrete PostgreSQL connection path concatenated raw values into a libpq keyword/value string. Whitespace, quotes, or backslashes could split a value into additional parameters or make an otherwise valid configuration fail to parse; the path also used the unsupported keyword username instead of user. Render every generated value as a single-quoted libpq parameter, escape quotes and backslashes, and use the correct user key. Keep the existing connection_string form untouched. The earlier attempt to register migrated PostgreSQL and MySQL fields is deliberately absent because those key names collide with the legacy connection-string tokenizer. Focused tests cover ordinary values, whitespace, quotes, backslashes, and parameter-shaped input. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
162ded3438 |
fix: register NATS/AMQP notify config keys read by parsers
GetNotifyNATS reads user_credentials, nkey_seed and tls_handshake_first and GetNotifyAMQP reads immediate, but none of them were registered in DefaultNATSKVS/DefaultAMQPKVS or the help schema, so CheckValidKeys rejected any enable=on target carrying them. Worse, the legacy config migration wrote exactly these keys - including the env var name MINIO_NOTIFY_NATS_USER_CREDENTIALS used as a config key, because the NATSUserCredentials constant doubled as both - so a migrated NATS config failed validation on every load, and the FetchEnabledTargets fail-fast then silently disabled all bucket notification targets. - Register user_credentials/nkey_seed/tls_handshake_first (NATS) and immediate (AMQP) in the default KVS and help schema; split NATSUserCredentials into a real config key plus EnvNATSUserCredentials (all env var names byte-stable) - Fix legacy migration: SetNotifyNATS writes the proper key; SetNotifyAMQP no longer writes cfg.Immediate under the internal key and now carries both immediate and internal - Tolerate the legacy MINIO_NOTIFY_NATS_USER_CREDENTIALS key written by pre-fix migrations (NATS-scoped, load path only) with fallback read; env > user_credentials > legacy key - Print key names only, never values, in the invalid-keys error of both CheckValidKeys forms; rejected values can carry credentials - Add an AST-based audit test asserting parser reads, migration writes and help entries stay within the registered key set for all ten notify subsystems, with floor assertions so collector drift fails loudly - Document (unchanged) FetchEnabledTargets fail-fast and pin it with a characterization test Known same-class gap left in place and pinned by the audit's allowlist: SetNotifyPostgres/SetNotifyMySQL write five unregistered DSN-era keys; tracked for a follow-up issue. Closes #39 Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
fe6dc47804 |
feat: add a trusted-proxy boundary for the client source address
The address MinIO attributes a request to is read from X-Forwarded-For,
X-Real-IP or RFC 7239 Forwarded, and never from the connection unless all
three are absent. It becomes aws:SourceIp and the audit remotehost field,
so any client that can reach the API port chooses the value an IpAddress
condition is evaluated against and the address every logged action is
attributed to.
MINIO_API_TRUSTED_PROXIES now selects who may make that claim:
unset the historical behaviour, unchanged
none no forwarded header is believed; the TCP peer wins
<CIDRs> believed only from listed peers, chains read right-to-left
Reading right-to-left is what makes an appending proxy safe: each hop
appends the peer it actually saw, so an entry a client injected can only
sit to the left of one a proxy wrote. The stock nginx recipe
$proxy_add_x_forwarded_for appends, which leaves the client's entry
left-most - exactly where the untrusted path reads - so a deployment with
no direct route to the API port was forgeable too.
_MINIO_API_XFF_HEADER is deliberately untouched, in semantics and in read
timing. Widening it to mean "trust nothing" was implemented and reverted:
it is the only part of this change that could alter a deployed
configuration, and the new variable expresses the same guarantee at no
compatibility cost. Upstream's TestXFFDisabled is retained verbatim.
Notes on the allow-list mode, all covered by tests:
- it must name proxies, not the subnet they sit in; listed entries are
skipped while walking, so a range covering clients lets them forge
- a cluster must list its own nodes, because MinIO forwards between
them and a client can force a hop via the ListObjectsV2 token
- loopback is trusted as a peer, not as a chain entry, so FTP and SFTP
keep attributing their sessions
- the node-to-node forwarder drops X-Real-IP and Forwarded from a peer
not entitled to have set them
- the walk scans the header in place and stops after 100 hops, so a
long chain costs neither allocation nor unbounded work
No behaviour change for any deployment that does not set the new
variable: the untrusted path is a verbatim copy of the previous function
body, differentially verified against it over ~5.1M header combinations.
The LDAP STS allow-list now shares the list parser as pure code motion,
verified identical across every combination of 37 allow-list values and
21 peer addresses.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
744a9dcd71 |
fix: bind s3:versionid conditions to the effective object version
A bucket policy that allows s3:DeleteObject only when s3:versionid is null
-- Condition {"Null": {"s3:versionid": "true"}}, the idiom for "let clients
delete current objects but not roll back versions" -- denied every delete,
including the version-less ones it was meant to permit (upstream issue
minio/minio#21735).
getConditionValues wrote "versionid": {""} unconditionally. The condition
engine decides Null by slice length (nullfunc.evaluate), so a present-but-
empty value reads as "key present": Null:true never matched and Null:false
always did. Absent and empty were indistinguishable.
Writing the key only when the request names a version fixes the reported
case but, alone, opens a worse one. DeleteObjects carries each object's
version in the XML body, which getConditionValues -- reading only r.Form --
never sees. A body version would then vanish from the map, read as null,
and a policy meant to protect old versions would authorize deleting a
specific one. So authorization also rebinds versionid to the effective,
server-resolved reqInfo.VersionID for DeleteObjectAction: the per-entry
body value that checkRequestAuthTypeWithVID already sets in the
DeleteObjects loop, deleting the key when that value is empty. A
query-level ?versionId on a DeleteObjects POST no longer leaks into any
entry's decision.
Finally, trim the version the condition builder reads. newContext and
getOpts both TrimSpace it before the object layer acts, so an untrimmed
value here let a padded ?versionId=V%20 present a different s3:versionid
than the version actually operated on, sidestepping a Deny keyed on
StringEquals s3:versionid. DeleteObjectAction was already immune via the
trimmed reqInfo value; this covers GetObject, tagging, retention, and the
copy-source read.
Tests: an end-to-end DeleteObjects against a Null:{s3:versionid:true}
policy over versioned objects (with a decoy query versionId proving the
per-entry body value wins), and a unit test asserting key presence,
trimming, and the copy-source fallback.
Co-authored-by: ChatGPT <noreply@openai.com>
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
2f55347f78 |
fix(iam): bind policy conditions to effective request values
Policy evaluation mixed server-derived identity and transport values with raw headers and query parameters. A client could therefore shadow internal condition keys, synthesize LDAP or JWT resource variables, substitute request tags for stored tags, or make a condition observe a value different from the one the handler actually used. Partition condition sources, reserve internal names, adopt exact-name lookup from silo-pkg, and bind authorization to the effective request state. Preserve compatible query forms for storage class and upload tags with explicit header precedence, while restricting signature age and existing-object tags to authenticated or server-resolved values. Tests sweep every supported key across header and query routes and exercise LDAP/OIDC variables, object-lock spelling, STS tags, metadata extraction, and end-to-end policy decisions. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
97b7d28040 |
fix(iam): enforce the bucket/object resource boundary
Upgrade directly to silo-pkg v3.11.0, the fork's first non-colliding release, and carry the completed minio/minio#20449 hardening without exposing the retired v3.7.0, v3.8.0, or v3.8.1 tags in the rewritten history. Twelve sensitive bucket-level writes now require the bare bucket resource in addition to the historical bucket/ form, so an object-only bucket/* grant cannot delete a bucket or change protections. Read/list behavior, ordinary tenant self-service, Deny statements, and NotResource exclusions retain their compatible behavior; MINIO_API_LEGACY_BUCKET_RESOURCE_MATCH restores the old matcher when migration requires it. End-to-end tests cover direct clients, session policies, service accounts, wildcard edge cases, compatible resource pairs, and real bucket deletion. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
22c1e41fd2 |
fix: reject duplicate part numbers in CompleteMultipartUpload
sort.SliceIsSorted with a strict '<' predicate treats equal neighbours as sorted, so a completion list like [1,1] passed the order check and the same part was assembled into the object twice: a single uploaded 5 MiB part produced a 10 MiB object. Replace the check with an explicit strictly-increasing scan that rejects repeats with InvalidPartOrder before anything is assembled. Gaps and lists not starting at part 1 remain legal, matching AWS semantics. The regression test drives the real CompleteMultipartUpload handler on both Erasure backends and asserts that rejected completions leave no object behind and keep the upload retryable. Closes #49 Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
38366f6543 |
cleanup: drop the HTTP stream helpers orphaned by the ReadMultiple removal
httpStreamResponse, streamHTTPResponse, waitForHTTPStream, and their
8k buffer pool lost their last caller when ReadMultipleHandler went
away (
|
||
|
|
1af351a702 |
fix(storage): preserve ReadParts errors across keepalive responses
ReadPartsHandler completed its keepalive stream before reporting storage failures, then tried to write an ordinary error response after the body was already owned. Clients consequently decoded the error text as msgpack and lost the real failure. Send failures through the keepalive completion channel and mark success only after ReadParts returns cleanly, preserving the existing wire framing and error identity. Co-authored-by: ChatGPT <noreply@openai.com> Co-authored-by: Claude <noreply@anthropic.com> |