From 73d5d2f3f1bb2ef8cfd39a9ff9bf514a64ff6e5e Mon Sep 17 00:00:00 2001 From: William Gill Date: Tue, 27 Oct 2026 10:54:05 -0500 Subject: [PATCH] patches: retire pre-v1.10.0 series and make the matrix an allowlist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1.10.0 carries upstream CVE fixes that the older releases predate, so stop building them. Their series move to patches/archive// rather than being deleted — still available for a deliberate rebuild, just not shipped — and ci/support-matrix.yaml now lists v1.10.0 alone. Dropping the matrix entries was not enough on its own. Two gaps made "not built" untrue: * apply-patches.sh only consulted the matrix `if command -v yq`, and the stock golang CI container has no yq — so in CI the registry was never actually read. The tag fell through to patches/ every time. * An empty or missing patch directory was a warning and exit 0, i.e. "building vanilla upstream". Archiving the directories would therefore have made a dispatch for v1.8.0 publish an un-de-branded, unpatched, CVE-vulnerable build under this project's release name — the exact outcome we are trying to prevent. So the matrix is now an enforced allowlist: a tag it does not list exits 1, as does a patch directory with no patches in it. It is parsed with yq when present and with a small awk parse otherwise, so the allowlist holds in the CI container too. If the parse ever broke, an unmatched tag is refused rather than built, so the failure mode stays closed. PATCH_DIR remains the one bypass, and is now the documented way to rebuild an archived series on purpose. That path also needed a fix: the patch list is globbed before we cd into the source tree, so a relative PATCH_DIR — the form the new error message suggests — globbed fine and then failed in git am. PATCH_DIR is now resolved to an absolute path, with a missing directory reported as such. Also re-pointed the runnable examples in README.md, ci/run.sh and the workflow's dispatch hint from v1.5.0 to v1.10.0; following them verbatim would now hit the refusal. Verified by exercising every branch: v1.10.0 applies; v1.8.0 is refused with exit 1; PATCH_DIR=patches/archive/v1.8.0 rebuilds the archived series; an empty directory and a nonexistent one are both refused. The awk parser was checked against defaulted `patches:` fields, several tags sharing one directory, and comment lines containing decoy `- tag:` text. All seven archived patch files are recorded as pure renames. Note: existing published releases for the retired versions stay downloadable until deleted by hand in Gitea. Co-Authored-By: Claude Opus 5 --- .gitea/workflows/build.yml | 2 +- README.md | 41 ++++++++---- ci/apply-patches.sh | 67 ++++++++++++++++--- ci/run.sh | 4 +- ci/support-matrix.yaml | 15 ++--- docs/multi-version-pipeline.md | 56 +++++++++++++--- patches/archive/README.md | 47 +++++++++++++ .../v1.5.0/0001-webui-debrand.patch | 0 .../v1.6.0/0001-webui-debrand.patch | 0 .../v1.6.0/0002-webui-dark-theme.patch | 0 .../v1.7.0/0001-webui-debrand.patch | 0 .../v1.7.0/0002-webui-dark-theme.patch | 0 .../v1.8.0/0001-webui-debrand.patch | 0 .../v1.8.0/0002-webui-dark-theme.patch | 0 14 files changed, 185 insertions(+), 47 deletions(-) create mode 100644 patches/archive/README.md rename patches/{ => archive}/v1.5.0/0001-webui-debrand.patch (100%) rename patches/{ => archive}/v1.6.0/0001-webui-debrand.patch (100%) rename patches/{ => archive}/v1.6.0/0002-webui-dark-theme.patch (100%) rename patches/{ => archive}/v1.7.0/0001-webui-debrand.patch (100%) rename patches/{ => archive}/v1.7.0/0002-webui-dark-theme.patch (100%) rename patches/{ => archive}/v1.8.0/0001-webui-debrand.patch (100%) rename patches/{ => archive}/v1.8.0/0002-webui-dark-theme.patch (100%) diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index b62fe3a..fd81376 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: inputs: tag: - description: "Upstream versitygw tag to build (e.g. v1.5.0)" + description: "Upstream versitygw tag to build (must be listed in ci/support-matrix.yaml, e.g. v1.10.0)" required: true jobs: diff --git a/README.md b/README.md index 4755e35..30a09b4 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,14 @@ s3-gateway/ │ ├── publish-release.sh # upload dist/* to a Gitea release │ └── run.sh # local end-to-end: clone + patch + build one tag ├── patches/ -│ ├── v1.6.0/ +│ ├── v1.10.0/ # the supported series │ │ ├── 0001-webui-debrand.patch │ │ └── 0002-webui-dark-theme.patch -│ └── v1.5.0/ -│ └── 0001-webui-debrand.patch +│ └── archive/ # superseded; kept, not built +│ ├── v1.8.0/ +│ ├── v1.7.0/ +│ ├── v1.6.0/ +│ └── v1.5.0/ ├── .gitea/workflows/build.yml └── docs/multi-version-pipeline.md ``` @@ -53,18 +56,18 @@ Prerequisites: `git`, `go`, and [`goreleaser`](https://goreleaser.com). ```bash # Build one upstream tag end-to-end → artifacts in src/dist/ -VGW_TAG=v1.5.0 bash ci/run.sh +VGW_TAG=v1.10.0 bash ci/run.sh # Fast offline build against a local upstream clone: -VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.5.0 bash ci/run.sh +VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.10.0 bash ci/run.sh ``` Or run the steps individually: ```bash -VGW_TAG=v1.5.0 bash ci/clone-source.sh # → src/ -VGW_TAG=v1.5.0 bash ci/apply-patches.sh # git am the series onto src/ -VGW_TAG=v1.5.0 bash ci/build.sh # goreleaser → src/dist/ +VGW_TAG=v1.10.0 bash ci/clone-source.sh # → src/ +VGW_TAG=v1.10.0 bash ci/apply-patches.sh # git am the series onto src/ +VGW_TAG=v1.10.0 bash ci/build.sh # goreleaser → src/dist/ ``` `src/` and `src/dist/` are gitignored. @@ -76,15 +79,21 @@ 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.10.0` | `v1.10.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.8.0` | `v1.8.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.7.0` | `v1.7.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.6.0` | `v1.6.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.5.0` | `v1.5.0/` | `0001-webui-debrand.patch` | + +**v1.10.0 is the only supported version.** It carries upstream CVE +fixes, so the older series — `v1.8.0`, `v1.7.0`, `v1.6.0` and `v1.5.0` +— are no longer built. Their patches are kept under +[`patches/archive/`](patches/archive/) for reference and for a +deliberate rebuild; see that directory's README. ### What the de-brand patch does @@ -122,8 +131,8 @@ shared asset files plus bundled fonts: matching the existing self-hosted Roboto. The only markup change is the login page's light background gradient, -which becomes a solid dark surface. Applied to every maintained patch -set except `v1.5.0`. +which becomes a solid dark surface. It is part of the supported +`v1.10.0` series, and of every archived set except `v1.5.0`. ## Adding / refreshing a version @@ -154,6 +163,10 @@ scheduled/cron builds: - **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 diff --git a/ci/apply-patches.sh b/ci/apply-patches.sh index 70c249d..3ab29fe 100644 --- a/ci/apply-patches.sh +++ b/ci/apply-patches.sh @@ -7,10 +7,17 @@ # to the tag name). Patches are git-format-patch files applied with # `git am --3way` in filename order — same model as scoutfs-notify. # +# The matrix is an ALLOWLIST: a tag that is not listed is refused, so a +# stray tag push or a mistyped dispatch cannot publish a build of a +# superseded (and possibly vulnerable) upstream release. Superseded +# series stay on disk under patches/archive// and can be rebuilt +# deliberately by passing PATCH_DIR. +# # Environment: # VGW_SRC_DIR source tree (default: src; set by clone-source.sh) # VGW_TAG upstream tag being built (required) -# PATCH_DIR override the patch directory (optional) +# PATCH_DIR build this directory instead of consulting the matrix; +# the escape hatch for patches/archive/ (optional) set -euo pipefail VGW_SRC_DIR="${VGW_SRC_DIR:-src}" @@ -19,19 +26,55 @@ VGW_SRC_DIR="${VGW_SRC_DIR:-src}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC="$(cd "${VGW_SRC_DIR}" && pwd)" -# Resolve the patch directory: explicit override, else matrix lookup, -# else the tag name. -if [[ -z "${PATCH_DIR:-}" ]]; then - PATCH_NAME="" +MATRIX="${REPO_ROOT}/ci/support-matrix.yaml" + +# Emit " " for every row of the support matrix. Prefer +# yq, but fall back to a small awk parse of the fixed shape this repo +# writes: the stock golang CI container ships no yq, and the matrix is +# the allowlist, so it has to be readable either way. +matrix_entries() { if command -v yq >/dev/null 2>&1; then - PATCH_NAME="$(yq -r \ - ".versitygw[] | select(.tag == \"${VGW_TAG}\") | .patches // \"\"" \ - "${REPO_ROOT}/ci/support-matrix.yaml" 2>/dev/null || true)" + yq -r '.versitygw[] | "\(.tag) \(.patches // .tag)"' "${MATRIX}" 2>/dev/null && return + fi + sed 's/#.*//' "${MATRIX}" | awk ' + /^[[:space:]]*-[[:space:]]*tag:[[:space:]]*[^[:space:]]/ { + if (tag != "") print tag, (pat != "" ? pat : tag) + tag = $NF; pat = ""; next + } + /^[[:space:]]*patches:[[:space:]]*[^[:space:]]/ { if (tag != "") pat = $NF } + END { if (tag != "") print tag, (pat != "" ? pat : tag) } + ' +} + +# An explicit PATCH_DIR is a deliberate override (rebuilding something +# out of patches/archive/). Otherwise the matrix decides, and a tag it +# does not list is refused — never silently built as vanilla upstream. +if [[ -z "${PATCH_DIR:-}" ]]; then + PATCH_NAME="$(matrix_entries | awk -v t="${VGW_TAG}" '$1 == t { print $2; exit }')" + if [[ -z "${PATCH_NAME}" ]]; then + { + echo "ERROR: ${VGW_TAG} is not listed in ci/support-matrix.yaml." + echo " Supported: $(matrix_entries | awk '{print $1}' | tr '\n' ' ')" + echo " Superseded series are archived under patches/archive/ and" + echo " are deliberately not rebuilt — they predate the fixes in" + echo " the supported release. To build one anyway, pass it" + echo " explicitly:" + echo " PATCH_DIR=patches/archive/${VGW_TAG} bash ci/apply-patches.sh" + } >&2 + exit 1 fi - [[ -n "${PATCH_NAME}" && "${PATCH_NAME}" != "null" ]] || PATCH_NAME="${VGW_TAG}" PATCH_DIR="${REPO_ROOT}/patches/${PATCH_NAME}" fi +# git am runs from inside the source tree, so the patch paths have to be +# absolute: a relative PATCH_DIR (the documented archive invocation is +# one) would still glob here but no longer resolve after the cd below. +if [[ ! -d "${PATCH_DIR}" ]]; then + echo "ERROR: patch directory ${PATCH_DIR} does not exist." >&2 + exit 1 +fi +PATCH_DIR="$(cd "${PATCH_DIR}" && pwd)" + echo ">>> target versitygw tag: ${VGW_TAG}" echo ">>> patch directory: ${PATCH_DIR}" @@ -40,8 +83,10 @@ patches=( "${PATCH_DIR}"/*.patch ) shopt -u nullglob if (( ${#patches[@]} == 0 )); then - echo ">>> WARNING: no patches in ${PATCH_DIR} — building vanilla upstream" - exit 0 + echo "ERROR: no *.patch files in ${PATCH_DIR}." >&2 + echo " Refusing to continue — that would publish an un-de-branded" >&2 + echo " vanilla upstream build under this project's release name." >&2 + exit 1 fi echo ">>> applying ${#patches[@]} patch(es) with git am --3way:" diff --git a/ci/run.sh b/ci/run.sh index 84abd00..c544f4a 100644 --- a/ci/run.sh +++ b/ci/run.sh @@ -3,12 +3,12 @@ # Runs the same ci/ steps CI does, in a single shell so VGW_* env flows # between them. # -# VGW_TAG=v1.5.0 bash ci/run.sh +# VGW_TAG=v1.10.0 bash ci/run.sh # # Optional: point VGW_REPO_URL at a local clone for fast offline builds. set -euo pipefail -: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.5.0 bash ci/run.sh}" +: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.10.0 bash ci/run.sh}" export VGW_TAG export VGW_SRC_DIR="${VGW_SRC_DIR:-src}" diff --git a/ci/support-matrix.yaml b/ci/support-matrix.yaml index f524121..dd5288c 100644 --- a/ci/support-matrix.yaml +++ b/ci/support-matrix.yaml @@ -1,5 +1,9 @@ # s3-gateway build support matrix — single source of truth for CI. # +# This is an ALLOWLIST. ci/apply-patches.sh refuses to build any tag +# that is not listed here, so a stale or vulnerable upstream release +# cannot be published by a stray tag push or a mistyped dispatch. +# # Each entry maps an upstream versitygw release tag to the patch set # (a directory under patches/) applied on top of that tag before # building. Add a row when upstream cuts a release you want a @@ -8,17 +12,12 @@ # # The highest-version entry is treated as "latest". # +# Superseded series are kept under patches/archive// rather than +# deleted — see that directory's README to rebuild one deliberately. +# # tag upstream versitygw git tag (github.com/versity/versitygw) # patches directory under patches/ to apply (defaults to `tag`) versitygw: - tag: v1.10.0 patches: v1.10.0 - - tag: v1.8.0 - patches: v1.8.0 - - tag: v1.7.0 - patches: v1.7.0 - - tag: v1.6.0 - patches: v1.6.0 - - tag: v1.5.0 - patches: v1.5.0 diff --git a/docs/multi-version-pipeline.md b/docs/multi-version-pipeline.md index d4400cd..eb57d9a 100644 --- a/docs/multi-version-pipeline.md +++ b/docs/multi-version-pipeline.md @@ -24,7 +24,10 @@ Each build is four steps, run for a single upstream tag: from `ci/support-matrix.yaml` (`patches:` field, default = tag name) and applies `patches//*.patch` with `git am --whitespace=nowarn --3way`, in filename order. The `--3way` fallback is what lets a - series rebase across minor upstream drift. + series rebase across minor upstream drift. A tag the matrix does not + list is **refused** (exit 1), as is a patch directory that turns out + to be empty — either would otherwise publish an un-de-branded or + superseded build. 3. **`ci/build.sh`** — runs `goreleaser release --clean --skip=validate,publish` in `src/`, producing binaries + deb/rpm in @@ -42,8 +45,8 @@ Each build is four steps, run for a single upstream tag: ```yaml versitygw: - - tag: v1.5.0 - patches: v1.5.0 + - tag: v1.10.0 + patches: v1.10.0 ``` - `tag` — upstream git tag. @@ -55,6 +58,15 @@ versitygw: being built; with the default `patches//` naming it doubles as a human-readable record of which versions are maintained. +It is also an **allowlist**, which is the mechanism that keeps +superseded versions from being rebuilt: a tag absent from the matrix +makes `apply-patches.sh` exit 1. Only `PATCH_DIR` bypasses it, and only +explicitly — that is how an archived series is rebuilt on purpose. + +The matrix is read with `yq` when it is installed and with a small +`awk` parse otherwise, so the allowlist still applies in the stock +`golang` CI container, which has no `yq`. + ## Adding a new upstream version When upstream releases `vX.Y.Z`: @@ -80,10 +92,12 @@ When upstream releases `vX.Y.Z`: versitygw: - tag: vX.Y.Z patches: vX.Y.Z - - tag: v1.5.0 - patches: v1.5.0 ``` + Leave the previous entry in place only if you still want builds of + it. If the new release supersedes it — a security fix, say — retire + the old one at the same time (see *Retiring a version* below). + 3. **Commit and push** `ci/support-matrix.yaml` + `patches/vX.Y.Z/`. 4. **Build it** — either push a `vX.Y.Z` tag to this repo, or run the @@ -98,11 +112,31 @@ VGW_REPO_URL="/path/to/local/versitygw" VGW_TAG=vX.Y.Z bash ci/run.sh A clean `git am` plus a populated `src/dist/` means the series is good. -## Removing a version +## Retiring a version -Delete its `patches//` directory and its `ci/support-matrix.yaml` -entry. Releases already published in this repo stay until you delete -them by hand. +Drop its `ci/support-matrix.yaml` entry — that alone stops it being +built, since the matrix is the allowlist — and `git mv` its directory +into `patches/archive//` rather than deleting it, so the series +stays available for a deliberate rebuild: + +```bash +git mv patches/v1.8.0 patches/archive/v1.8.0 +# then remove the v1.8.0 entry from ci/support-matrix.yaml +``` + +Update the table in [`patches/archive/README.md`](../patches/archive/README.md) +and the supported-versions table in the top-level README to match. + +Releases already published in this repo are **not** affected: retiring +the patches stops future builds but leaves existing release assets +downloadable until you delete them by hand in Gitea. + +To rebuild an archived version deliberately, pass `PATCH_DIR` instead +of re-adding it to the matrix: + +```bash +PATCH_DIR=patches/archive/v1.8.0 VGW_TAG=v1.8.0 bash ci/apply-patches.sh +``` ## CI configuration @@ -122,6 +156,6 @@ them by hand. ## Release tagging Releases are created in this repo with the **upstream** tag name -(`v1.5.0`). If you need to ship a rebuild of the same upstream version +(`v1.10.0`). If you need to ship a rebuild of the same upstream version (e.g. a patch fix), set `RELEASE_TAG` in `ci/publish-release.sh`'s -environment to something like `v1.5.0-acg2` to avoid clobbering. +environment to something like `v1.10.0-acg2` to avoid clobbering. diff --git a/patches/archive/README.md b/patches/archive/README.md new file mode 100644 index 0000000..807f8a3 --- /dev/null +++ b/patches/archive/README.md @@ -0,0 +1,47 @@ +# Archived patch series + +Patch sets for upstream versitygw releases this project **no longer +builds**. They are kept for reference and reproducibility, not because +they are safe to ship. + +| Patch set | Contents | +|-----------|-----------------------------------------------------------| +| `v1.8.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.7.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.6.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.5.0/` | `0001-webui-debrand.patch` | + +## Why these are not built + +`v1.10.0` carries upstream CVE fixes that these releases predate. +Building them would publish known-vulnerable gateways under this +project's release name, so their tags were removed from +`ci/support-matrix.yaml`. That matrix is an allowlist — +`ci/apply-patches.sh` refuses a tag it does not list, so neither a tag +push nor a workflow dispatch can rebuild one by accident. + +Releases already published for these versions are **not** removed by +archiving the patches. Delete them by hand in Gitea if they should no +longer be downloadable. + +## Rebuilding one anyway + +If you genuinely need an archived build — reproducing an old report, +bisecting a regression — point `PATCH_DIR` at it instead of re-adding +it to the matrix: + +```bash +VGW_TAG=v1.8.0 bash ci/clone-source.sh +PATCH_DIR=patches/archive/v1.8.0 VGW_TAG=v1.8.0 bash ci/apply-patches.sh +VGW_TAG=v1.8.0 bash ci/build.sh +``` + +Don't publish the result to a release unless you mean to ship a +vulnerable build. + +## Un-archiving + +To return a version to the supported set, `git mv` its directory back +to `patches//` and add its row to `ci/support-matrix.yaml`. Rebase +the series onto the tag first if upstream has moved — see +[../../docs/multi-version-pipeline.md](../../docs/multi-version-pipeline.md). diff --git a/patches/v1.5.0/0001-webui-debrand.patch b/patches/archive/v1.5.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.5.0/0001-webui-debrand.patch rename to patches/archive/v1.5.0/0001-webui-debrand.patch diff --git a/patches/v1.6.0/0001-webui-debrand.patch b/patches/archive/v1.6.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.6.0/0001-webui-debrand.patch rename to patches/archive/v1.6.0/0001-webui-debrand.patch diff --git a/patches/v1.6.0/0002-webui-dark-theme.patch b/patches/archive/v1.6.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.6.0/0002-webui-dark-theme.patch rename to patches/archive/v1.6.0/0002-webui-dark-theme.patch diff --git a/patches/v1.7.0/0001-webui-debrand.patch b/patches/archive/v1.7.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.7.0/0001-webui-debrand.patch rename to patches/archive/v1.7.0/0001-webui-debrand.patch diff --git a/patches/v1.7.0/0002-webui-dark-theme.patch b/patches/archive/v1.7.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.7.0/0002-webui-dark-theme.patch rename to patches/archive/v1.7.0/0002-webui-dark-theme.patch diff --git a/patches/v1.8.0/0001-webui-debrand.patch b/patches/archive/v1.8.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.8.0/0001-webui-debrand.patch rename to patches/archive/v1.8.0/0001-webui-debrand.patch diff --git a/patches/v1.8.0/0002-webui-dark-theme.patch b/patches/archive/v1.8.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.8.0/0002-webui-dark-theme.patch rename to patches/archive/v1.8.0/0002-webui-dark-theme.patch