57_WolveandClaude Opus 5.5 a52f92c100 patches: add v1.11.0 atomic Vault IAM update patch and its leil-rig proof
0004-vault-atomic-update-user.patch makes the Vault IAM backend's
UpdateUserAccount -- what admin update-user, and so every key rotation,
runs -- one KV v2 write with cas set to the version it read, retried
from a fresh read on a check-and-set conflict (up to 5 attempts).
Upstream deleted the entry and created it again: in between, another
instance on the same KV path whose IAM cache missed answered
InvalidAccessKeyId, a failure between the two calls lost the account,
and two updates at once lost one of them. Touches only
auth/iam_vault.go and a new auth/iam_vault_update_test.go.

tests/vault-update/proof.sh proves it on leil-rig against an OpenBao
dev server, with two gateways on one KV path and gateway A's OpenBao
calls held 3 s by a proxy so upstream's gap can be observed. On
leil-test-1: patched 20 passed, 0 failed; 0001-0003 control 13 passed,
7 failed (entry missing 3.00 s, 28 InvalidAccessKeyId on the second
gateway, concurrent rotation lost, account gone after a refused
write). go test on the node: 35 of 35 packages ok.

docs/vault-atomic-update.md has the problem, the change, the proof and
its result; README and the multi-version pipeline doc point at it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 02:04:31 -06:00

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 two behavioural patches:

  • 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.
  • Atomic Vault IAM update: with the Vault IAM backend, update-user (a key rotation) is one check-and-set write of the account's KV entry instead of a delete and a create, so the account never goes missing and a failed update leaves it as it was. See docs/vault-atomic-update.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
│   │   └── 0004-vault-atomic-update-user.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)
│   └── vault-update/         # leil-rig proof test for 0004 (proof.sh, watch.py, vaultproxy.py)
├── .gitea/workflows/build.yml
└── docs/
    ├── multi-version-pipeline.md
    ├── owner-scoped-access.md  # what 0003 does, and its proof test
    └── vault-atomic-update.md  # what 0004 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, 0004-vault-atomic-update-user.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/versity links).
  • 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) and CreateBucket's grant headers — so also versitygw admin create-bucket --grant-* — refuse any account whose UserID is not the owner's. Root is refused too.
  • Public means read. all-users gets READ only; a policy may allow "*" only s3:GetObject and s3:ListBucket. public-read-write and every other public grant are refused.
  • A policy adds to the ACL. An Allow from either is enough and a policy Deny wins — except that the owner's key is never denied.
  • Root/admin only: DeleteBucket, PutBucketVersioning, PutObjectLockConfiguration, PutBucketOwnershipControls (and DeleteBucketOwnershipControls).
  • An ACL holding the all-users READ grant can be written back unchanged (upstream answers 500).

It and 0004 are the series' only patches outside webui/, and both have 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.

What the atomic Vault update patch does

0004-vault-atomic-update-user.patch changes one call in auth/iam_vault.go, the Vault IAM backend's UpdateUserAccount — what versitygw admin update-user, and so every key rotation, runs. Upstream deletes the account's KV entry and creates it again; in between the account does not exist, so another gateway instance on the same KV path whose IAM cache misses answers InvalidAccessKeyId, and a failure between the two calls loses the account for good.

  • One write. The entry is read with its KV version, changed, and written back with cas set to that version.
  • Conflicts retry. If another writer got there first, the update starts again from a fresh read, up to 5 times.
  • Unchanged: "user not found" for a missing account (also one deleted mid-update, which is never re-created), the re-login on a 403, and every other Vault call.
  • KV v2 now keeps the account's earlier versions, old secrets included, up to the mount's max_versions.

Its unit tests run with the ones above (go test ./auth/); its leil-rig proof test is in docs/vault-atomic-update.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.Z tag to this repo; CI builds upstream versitygw vX.Y.Z with patches/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 the golang container, 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_URL to override the upstream URL.

License

Apache-2.0, matching versitygw upstream. Upstream copyright and NOTICE are retained in every build.

S
Description
No description provided
Readme
2.7 MiB
0 Stars 1 Watchers 0 Forks
v1.11.0
Latest
2026-11-08 17:56:42 +00:00
Languages
Shell 74.5%
Python 25.5%