0003-owner-scoped-access.patch changes authorization, applied after the de-brand and dark-theme patches: - a bucket's ACL and policy may name only the owner's keys (accounts on the owner's UserID), whoever writes them, root included; - public access is read-only: all-users READ, and "*" allowed only s3:GetObject and s3:ListBucket; - the bucket policy never denies the owner's key; - DeleteBucket, PutBucketVersioning, PutObjectLockConfiguration and PutBucketOwnershipControls are reserved to root and admins; - an ACL with the all-users READ grant can be written back unchanged. docs/owner-scoped-access.md has the rules, what v1.11.0 already did, and the leil-rig proof procedure with its 2026-11-10 result: 63 of 63 on the patched build, 29 of 63 failing on stock v1.11.0. tests/owner-scope/ holds the proof script and a stdlib-only SigV4 request helper. README and the pipeline doc list the patch and how to re-verify it after a rebase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.9 KiB
s3-gateway
Downstream build pipeline that produces de-branded versitygw releases — the upstream Versity S3 Gateway with Versity trademarks and logos removed from the admin web UI.
Same pattern as scoutfs-build: pull an upstream
release tag, apply our patch series scoped to that tag, build
with upstream's GoReleaser, and publish to Gitea Releases.
Artifact names are intentionally unchanged — the binary stays
versitygw and the deb/rpm packages stay versitygw, so builds are
drop-in compatible with existing configs, systemd units, and the
versitygw CLI. Only the embedded web UI is rebranded to "S3 Gateway".
The v1.11.0 series also carries one behavioural patch, owner-scoped bucket access: a bucket's ACL and policy may name only the owner's own keys, public access is read-only, and deleting a bucket or changing its versioning, object lock or ownership controls is left to the gateway's root and admins. See docs/owner-scoped-access.md.
upstream tag (vX.Y.Z) this repo
github.com/versity/versitygw patches/<tag>/*.patch
│ │
▼ ▼
ci/clone-source.sh ───► ci/apply-patches.sh ───► ci/build.sh ───► ci/publish-release.sh
(shallow clone tag) (git am --3way) (goreleaser) (Gitea release assets)
ci/support-matrix.yaml records which upstream versions have a curated
patch set and maps each to its patch directory.
Layout
s3-gateway/
├── ci/
│ ├── support-matrix.yaml # upstream tag -> patch directory (registry)
│ ├── clone-source.sh # shallow-clone upstream versitygw at a tag
│ ├── apply-patches.sh # git am --3way patches/<tag>/*.patch
│ ├── build.sh # goreleaser release (binaries + deb/rpm)
│ ├── publish-release.sh # upload dist/* to a Gitea release
│ └── run.sh # local end-to-end: clone + patch + build one tag
├── patches/
│ ├── v1.11.0/ # the supported series
│ │ ├── 0001-webui-debrand.patch
│ │ ├── 0002-webui-dark-theme.patch
│ │ └── 0003-owner-scoped-access.patch
│ └── archive/ # superseded; kept, not built
│ ├── v1.10.0/
│ ├── v1.8.0/
│ ├── v1.7.0/
│ ├── v1.6.0/
│ └── v1.5.0/
├── tests/owner-scope/ # leil-rig proof test for 0003 (proof.sh, s3req.py)
├── .gitea/workflows/build.yml
└── docs/
├── multi-version-pipeline.md
└── owner-scoped-access.md # what 0003 does, and its proof test
Build locally
Prerequisites: git, go, and goreleaser.
# Build one upstream tag end-to-end → artifacts in src/dist/
VGW_TAG=v1.11.0 bash ci/run.sh
# Fast offline build against a local upstream clone:
VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.11.0 bash ci/run.sh
Or run the steps individually:
VGW_TAG=v1.11.0 bash ci/clone-source.sh # → src/
VGW_TAG=v1.11.0 bash ci/apply-patches.sh # git am the series onto src/
VGW_TAG=v1.11.0 bash ci/build.sh # goreleaser → src/dist/
src/ and src/dist/ are gitignored.
Patches
Patch sets live in patches/<dir>/ as git format-patch files and are
applied with git am --3way, in filename order. The directory for a
tag comes from the patches: field in ci/support-matrix.yaml
(defaults to the tag name).
That matrix is an allowlist: ci/apply-patches.sh refuses any tag
it does not list, so a stray tag push or a mistyped dispatch can't
publish a build of a superseded release.
Currently maintained:
| Upstream tag | Patch set | Contents |
|---|---|---|
v1.11.0 |
v1.11.0/ |
0001-webui-debrand.patch, 0002-webui-dark-theme.patch, 0003-owner-scoped-access.patch |
v1.11.0 is the only supported version. It supersedes v1.10.0
with 42 further upstream fixes, so the older series — v1.10.0,
v1.8.0, v1.7.0, v1.6.0 and v1.5.0 — are no longer built.
Their patches are kept under
patches/archive/ for reference and for a
deliberate rebuild; see that directory's README.
Note that protection against CVE-2026-111943 (the Go crypto/x509
eMudhra distrust, announced in upstream's v1.10.0 release notes)
comes from the Go toolchain these binaries are built with, not
from the upstream tag. Build with Go 1.26.10 / 1.27.3 or later.
What the de-brand patch does
- Sidebar/login logos → a plain "S3 Gateway" wordmark.
- Page titles: "VersityGW Dashboard" → "Dashboard" (Buckets, Users, Explorer and the v1.8.0 IAM pages too).
- Browser tab titles → "S3 Gateway – …".
- Removes the sidebar Resources section (external
github.com/versitylinks). - Replaces the Versity favicon with a neutral SVG; deletes the Versity logo PNGs.
- Removes the visible "© Versity Software Inc." footer from the login page.
Deliberately kept (license compliance): the per-file Apache-2.0
license headers — which carry the upstream copyright notice as an HTML
comment at the top of each file — and the NOTICE file. Upstream is
Apache-2.0; the de-brand strips the visible Versity trademarks (logos,
wordmarks, and the login footer) while preserving the copyright notice
in the source headers.
What the dark-theme patch does
0002-webui-dark-theme.patch re-skins the admin UI to match the
kanrisha web app — a near-black palette with indigo/blue accents —
without touching the page markup. It works entirely through the two
shared asset files plus bundled fonts:
tailwind-config.js— remaps the named color tokens (surface,charcoal,primary,accent) onto the dark palette and switches the sans/mono fonts to Inter / JetBrains Mono.theme.css— sets the dark body, restyles the bespoke components (sidebar active state, dropdowns, toggles, checkboxes, file rows, login) in indigo, and adds an override layer that flips the built-in Tailwind white/gray/status utilities the markup uses.fonts.css+assets/fonts/*.woff2— self-hosts Inter and JetBrains Mono (variable, latin + latin-ext) so the UI stays fully offline, matching the existing self-hosted Roboto.
The only markup change is the login page's light background gradient,
which becomes a solid dark surface. It is part of the supported
v1.11.0 series, and of every archived set except v1.5.0.
What the owner-scoped access patch does
0003-owner-scoped-access.patch changes authorization in auth/ (plus
one call in s3api/controllers/bucket-put.go). A bucket's owner is the
account its ACL names; the owner's keys are every account with that
account's UserID.
- Only the owner's keys.
PutBucketPolicy,PutBucketAcl(body and grant headers) andCreateBucket's grant headers — so alsoversitygw admin create-bucket --grant-*— refuse any account whoseUserIDis not the owner's. Root is refused too. - Public means read.
all-usersgetsREADonly; a policy may allow"*"onlys3:GetObjectands3:ListBucket.public-read-writeand every other public grant are refused. - A policy adds to the ACL. An
Allowfrom either is enough and a policyDenywins — except that the owner's key is never denied. - Root/admin only:
DeleteBucket,PutBucketVersioning,PutObjectLockConfiguration,PutBucketOwnershipControls(andDeleteBucketOwnershipControls). - An ACL holding the
all-usersREADgrant can be written back unchanged (upstream answers500).
It is the series' only patch outside webui/, and it has unit tests:
after a rebase run go test ./auth/ ./s3api/... (on Linux — on Windows
upstream's standalone-IAM, posix and CLI tests fail with or without the
series) and
the leil-rig proof test in
docs/owner-scoped-access.md.
Adding / refreshing a version
When upstream cuts a new release you want a build of, you rebase the patch series onto it and add a patch directory. See docs/multi-version-pipeline.md for the full recipe. In short:
# Rebase the series onto the new tag, re-export into patches/vX.Y.Z/
git clone https://github.com/versity/versitygw && cd versitygw
git -c core.autocrlf=false checkout -b debrand vX.Y.Z
git am --3way /path/to/s3-gateway/patches/<prev>/*.patch # resolve any conflicts
git format-patch --binary vX.Y.Z..HEAD -o /path/to/s3-gateway/patches/vX.Y.Z/
Then add the tag to ci/support-matrix.yaml, commit, and build it (see
below).
CI
.gitea/workflows/build.yml builds one version per run and
publishes it to a Gitea release in this repo. It triggers two ways — no
scheduled/cron builds:
- By new tag — push a
vX.Y.Ztag to this repo; CI builds upstream versitygwvX.Y.Zwithpatches/vX.Y.Z/. - One-shot — run the build workflow manually (workflow_dispatch) and enter the upstream tag to build.
Either way the tag must appear in ci/support-matrix.yaml; if it does
not, the patch step fails the run rather than publishing an
un-de-branded or superseded build.
Configuration:
- Runner: any Linux runner (
runs-on: ubuntu-latest). The Go toolchain comes from thegolangcontainer, so the runner only needs Docker. - Secret:
TOKEN_GITEA— a Gitea PAT with write:repository (clones this repo + creates releases / uploads assets). - Variable (optional):
VGW_REPO_URLto override the upstream URL.
License
Apache-2.0, matching versitygw upstream. Upstream copyright and
NOTICE are retained in every build.