Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
release_tag:
description: Numeric semantic release tag, for example v0.20.11
description: Numeric semantic release tag, for example v0.20.12
required: true
type: string

Expand Down Expand Up @@ -72,6 +72,7 @@ jobs:

run_build() {
local command=$1
test -z "$(git -C source status --porcelain)"
docker run --rm \
--volume "${source_path}:/go/src/github.com/PastureStack/catalog-service" \
--env "DAPPER_UID=$(id -u)" \
Expand All @@ -97,6 +98,14 @@ jobs:
test -x artifact-check/catalog-service
test -x artifact-check/catalog-service-sqlite
test "$(find artifact-check -maxdepth 1 -type f | wc -l)" -eq 2
for binary in catalog-service catalog-service-sqlite; do
test "$(artifact-check/"$binary" --version)" = "$RELEASE_TAG"
docker run --rm --entrypoint sh \
--volume "$PWD/artifact-check:/artifacts:ro" "$image" -lc \
"go version -m /artifacts/$binary" >"artifact-check/$binary.buildinfo"
grep -Fx $'\tbuild\tvcs.revision='"$SOURCE_SHA" "artifact-check/$binary.buildinfo" >/dev/null
grep -Fx $'\tbuild\tvcs.modified=false' "artifact-check/$binary.buildinfo" >/dev/null
done
sha256sum "$artifact" |
sed "s# source/dist/artifacts/# #" \
>"${artifact}.sha256"
Expand All @@ -117,11 +126,12 @@ jobs:

{
printf '# PastureStack Catalog Service %s\n\n' "$RELEASE_TAG"
printf 'This release enforces operator-authorized catalog origins, rooted cache access, bounded Helm inputs, and plain-text readme delivery.\n\n'
printf 'This release excludes Git metadata from template traversal, validates version directories before allocating templates, omits metadata-only entries without a template definition, preserves template/version READMEs and icons, and rebuilds same-commit empty or unnamed indexes transactionally. Existing catalog origin, rooted-cache and bounded-input protections are retained.\n\n'
printf '## Immutable coordinates\n\n'
printf -- '- Source commit: `%s`\n' "$SOURCE_SHA"
printf -- '- Artifact SHA-256: `%s`\n\n' "$artifact_sha"
printf 'The full test suite passed, and two clean builds produced byte-identical archives.\n\n'
printf 'Python test dependencies are hash-locked; bootstrap installers are removed from the completed build image. Build-header VEX exceptions remain explicitly recorded and do not claim an absence of all CVEs. Server packaging and real catalog migration/UI acceptance are separate and not claimed by this component release.\n\n'
printf 'PastureStack is an independent community effort to preserve, audit, and modernize the Rancher 1.6 ecosystem. It is not affiliated with or endorsed by Rancher Labs or SUSE.\n'
} >release-notes.md

Expand Down
17 changes: 12 additions & 5 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
env:
DAPPER_IMAGE: pasturestack/catalog-service-dapper:${{ github.sha }}
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
VERSION_OVERRIDE: v0.20.11
VERSION_OVERRIDE: v0.20.12

steps:
- name: Check out candidate
Expand Down Expand Up @@ -60,6 +60,9 @@ jobs:
source_path="$GITHUB_WORKSPACE"
source_epoch="$(git show -s --format=%ct HEAD)"
run_ci() {
# Generated review artifacts are ignored; any actual source drift
# must fail before Go captures vcs.modified in the binaries.
test -z "$(git status --porcelain)"
docker run --rm \
--volume "${source_path}:/go/src/github.com/PastureStack/catalog-service" \
--env "DAPPER_UID=$(id -u)" \
Expand All @@ -71,7 +74,7 @@ jobs:
}

run_ci
artifact="dist/artifacts/catalog-service-0.20.11.tar.xz"
artifact="dist/artifacts/catalog-service-0.20.12.tar.xz"
test -s "$artifact"
cp "$artifact" /tmp/catalog-service-first.tar.xz

Expand All @@ -85,8 +88,8 @@ jobs:
test "$(find evidence/product -maxdepth 1 -type f | wc -l)" -eq 2
test -x evidence/product/catalog-service
test -x evidence/product/catalog-service-sqlite
test "$(evidence/product/catalog-service --version)" = 'v0.20.11'
test "$(evidence/product/catalog-service-sqlite --version)" = 'v0.20.11'
test "$(evidence/product/catalog-service --version)" = 'v0.20.12'
test "$(evidence/product/catalog-service-sqlite --version)" = 'v0.20.12'
sha256sum "$artifact" > evidence/catalog-service.tar.xz.sha256
docker run --rm --entrypoint sh \
--volume "$PWD:/work:ro" \
Expand All @@ -105,6 +108,10 @@ jobs:
> evidence/product-linkage.txt
grep -F $'build\tCGO_ENABLED=0' evidence/catalog-service-go-version.txt >/dev/null
grep -F $'build\tCGO_ENABLED=1' evidence/catalog-service-sqlite-go-version.txt >/dev/null
for metadata in evidence/catalog-service-go-version.txt evidence/catalog-service-sqlite-go-version.txt; do
grep -Fx $'\tbuild\tvcs.revision='"$GITHUB_SHA" "$metadata" >/dev/null
grep -Fx $'\tbuild\tvcs.modified=false' "$metadata" >/dev/null
done
grep -Eq 'catalog-service:[[:space:]]+ELF' evidence/product-linkage.txt
grep -F 'statically linked' evidence/product-linkage.txt >/dev/null
grep -Eq 'catalog-service-sqlite:[[:space:]]+ELF' evidence/product-linkage.txt
Expand All @@ -118,7 +125,7 @@ jobs:
"printf 'package\tversion\n'; dpkg-query -W -f='\${binary:Package}\t\${Version}\n' | LC_ALL=C sort" \
> evidence/dapper-dpkg.tsv
docker run --rm --entrypoint sh "$DAPPER_IMAGE" -lc \
'/opt/tox/bin/pip freeze --all | LC_ALL=C sort' \
'/opt/tox/bin/python -I -c '\''import importlib.metadata as m; print("\n".join(sorted(d.metadata["Name"] + "==" + d.version for d in m.distributions())))'\''' \
> evidence/dapper-python.txt
docker run --rm \
-v "$PWD/scripts/verify-dapper-vex:/verify-dapper-vex:ro" \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.dapper
/bin
/dist
/evidence
*.swp
/.trash-cache
/cache
Expand Down
8 changes: 8 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,11 @@ The `0.20.9` candidate replaces MD5 cache directory names with SHA-256 names. Th
The `0.20.11` candidate preserves those database table names and JSON resource shapes while moving to GORM v2 and a bounded project-owned catalog API compatibility layer. The retired Rancher client HTML renderer is intentionally not preserved; Catalog API responses are JSON. Its MySQL DSN construction preserves the current driver's compatibility defaults required by existing installations.

Release validation covers `platformVersion` precedence and legacy fallback, both legacy metadata layouts, catalog refresh, database migration, empty default configuration, icon and readme routes, version ordering, upgrade links, malformed repositories, empty-index recovery, outbound-origin and path boundaries, Helm archive limits, SQLite and non-SQLite binaries, and rollback.

The `0.20.12` source adds Git-metadata exclusion and rejects invalid version
folders before reading or allocating a template. A same-commit index containing
an empty or NULL template folder is rebuilt by the existing catalog transaction;
the check is confined to the selected catalog name and environment. The database
surrogate IDs may change on reindexing, as on an ordinary catalog refresh, while
public catalog/template identifiers and the reviewed source commit remain
unchanged. No operator SQL cleanup or catalog recreation is required.
35 changes: 27 additions & 8 deletions Dockerfile.dapper
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ RUN set -eux; \
gcc="${UBUNTU_APT_GCC_VERSION}" \
git="${UBUNTU_APT_GIT_VERSION}" \
libc6-dev="${UBUNTU_APT_LIBC6_DEV_VERSION}" \
libssl3t64="${UBUNTU_APT_OPENSSL_VERSION}" \
openssl="${UBUNTU_APT_OPENSSL_VERSION}" \
openssl-provider-legacy="${UBUNTU_APT_OPENSSL_VERSION}" \
python3="${UBUNTU_APT_PYTHON3_VERSION}" \
python3-pip="${UBUNTU_APT_PYTHON3_PIP_VERSION}" \
python3-venv="${UBUNTU_APT_PYTHON3_VENV_VERSION}" \
tar="${UBUNTU_APT_TAR_VERSION}" \
xz-utils="${UBUNTU_APT_XZ_UTILS_VERSION}"; \
{ \
printf 'snapshot\t%s\n' "${UBUNTU_APT_SNAPSHOT}"; \
dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort; \
} > /licenses/CATALOG-SERVICE-UBUNTU-APT-PACKAGES.tsv; \
apt-get clean; \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/bin/pebble; \
rm -f /bin/sh; \
Expand All @@ -69,8 +67,8 @@ RUN case "${DAPPER_HOST_ARCH}" in \
echo "${go_sha} /tmp/go.tgz" | sha256sum -c - && \
tar -C /usr/local -xzf /tmp/go.tgz && \
rm -f /tmp/go.tgz && \
python3 -m venv /opt/tox && \
/opt/tox/bin/pip install --no-cache-dir --require-hashes \
/usr/bin/python3 -m venv --without-pip /opt/tox && \
/usr/bin/python3 -m pip --python /opt/tox install --no-cache-dir --require-hashes \
--requirement /tmp/catalog-service-build-requirements.lock && \
mkdir -p /go/bin /go/src && \
go version && \
Expand All @@ -79,10 +77,31 @@ RUN case "${DAPPER_HOST_ARCH}" in \

COPY integration/requirements.lock /tmp/catalog-service-test-requirements.lock
RUN mkdir -p /opt/catalog-service-wheelhouse && \
/opt/tox/bin/pip download --require-hashes --no-deps --only-binary=:all: \
/opt/tox/bin/pip download --no-cache-dir --require-hashes --no-deps --only-binary=:all: \
--platform any --python-version 3.14 --implementation py --abi none \
--dest /opt/catalog-service-wheelhouse \
--requirement /tmp/catalog-service-test-requirements.lock && \
/opt/tox/bin/pip install --no-cache-dir --require-hashes --no-deps \
--no-index --find-links=/opt/catalog-service-wheelhouse \
--requirement /tmp/catalog-service-test-requirements.lock && \
cp /tmp/catalog-service-test-requirements.lock \
/licenses/CATALOG-SERVICE-TEST-REQUIREMENTS.lock && \
/opt/tox/bin/pip uninstall -y cachetools distlib filelock platformdirs \
pyproject-api python-discovery setuptools tomli-w tox virtualenv wheel && \
/opt/tox/bin/pip uninstall -y pip && \
for package in python3-pip python3-pip-whl; do \
if dpkg-query -W -f='${db:Status-Status}\n' "${package}" 2>/dev/null \
| grep -qx installed; then \
apt-get purge -y "${package}" || exit 1; \
fi; \
done && \
. /licenses/ubuntu-apt.lock && \
{ \
printf 'snapshot\t%s\n' "${UBUNTU_APT_SNAPSHOT}"; \
dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort; \
} > /licenses/CATALOG-SERVICE-UBUNTU-APT-PACKAGES.tsv && \
/opt/tox/bin/python -I -m flake8 --version && \
/opt/tox/bin/python -I -m pytest --version && \
rm -f /tmp/catalog-service-build-requirements.lock \
/tmp/catalog-service-test-requirements.lock

Expand Down
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,42 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

## Project status

The current numeric maintenance release is `0.20.11`, consumed by PastureStack
Server `v1.6.410`. It retains the Ubuntu 26.04, Go 1.27.0, database, dependency, version-filter, TLS, and build maintenance completed after the preserved upstream boundary. Product-owned imports, binaries, default configuration, version query, and operator messages use PastureStack naming. The default `repo.json` is intentionally empty; no unreviewed catalog is cloned. Python build and integration-test dependencies are transitively pinned with package hashes and installed from an offline wheelhouse inside the disposable build image. The historical `--track` flag is accepted only for command-line compatibility; the service does not read or transmit an installation identifier. MySQL DSNs are created from the driver's reviewed defaults so existing `mysql_native_password` installations remain compatible after the driver upgrade.
This source prepares numeric maintenance release `0.20.12`; the latest published
release is still `0.20.11`, included in PastureStack Server through `v1.6.513`.
The next server integration target is `v1.6.514`; publication and 8080 acceptance
are not yet complete. The indexing fix skips Git's `.git` metadata, validates a
numeric revision or semantic-version folder before reading a version file or
allocating a template, and rebuilds a same-commit index containing unnamed
templates through the existing catalog transaction. Other catalogs are not
included in that cache check. Native revision numbers, semantic versions,
template metadata, labels and public API identifiers remain compatible.
README, icon and version files alone do not emit templates: a successfully
parsed `config.yml` or `template.yml` must establish the folder identity.
Root README files are resolved from their containing directory rather than
mistaken for version folders. This repairs previously empty root README fields
from repository bytes; version README files, icons and valid numeric or semantic
versions retain their existing contents and interpretation.

It retains the Ubuntu 26.04, Go 1.27.0, database, dependency, version-filter, TLS, and build maintenance completed after the preserved upstream boundary. Product-owned imports, binaries, default configuration, version query, and operator messages use PastureStack naming. The default `repo.json` is intentionally empty; no unreviewed catalog is cloned. Python build and integration-test dependencies are transitively pinned with package hashes and installed from an offline wheelhouse inside the disposable build image. The historical `--track` flag is accepted only for command-line compatibility; the service does not read or transmit an installation identifier. MySQL DSNs are created from the driver's reviewed defaults so existing `mysql_native_password` installations remain compatible after the driver upgrade.

The archived `docker/libcompose` parser and the unmaintained `go-rancher` client are no longer imported or vendored. A small project-owned compatibility layer now emits only the resource, schema, link, and JSON shapes this service actually uses. Catalog metadata is decoded through YAML v3 with focused compatibility tests for top-level legacy metadata, Compose v2 service metadata, alias fields, precedence, malformed input, and empty metadata. A source gate prevents the removed parser from returning.

Database access uses GORM v2 with current MySQL and SQLite drivers. Dependencies are resolved by Go Modules, locked by `go.mod` and `go.sum`, and rebuilt into `vendor/` so release builds remain offline and reproducible.

The disposable build image is pinned by digest. Its Ubuntu package source is fixed to the `20260808T000000Z` official snapshot, and every directly installed APT package has an exact version in `ubuntu-apt.lock`. The candidate security workflow builds and packages twice, runs the complete test, race, validation, and packaging path, scans source, product binaries, and the exported build-image filesystem, and uploads review evidence without publishing or deploying anything. Both the image-metadata and exported-filesystem raw reports are retained. Findings originating from an embedded third-party SBOM require exact OpenVEX set equality plus executable checks that the affected implementation and call path are absent; installed package databases remain independent evidence and product binaries are gated separately.
The disposable build image is pinned by digest. Its Ubuntu package source is fixed to the `20261002T000000Z` official snapshot, and every directly installed APT package has an exact version in `ubuntu-apt.lock`. The candidate security workflow builds and packages twice, runs the complete test, race, validation, and packaging path, scans source, product binaries, and the exported build-image filesystem, and uploads review evidence without publishing or deploying anything. Both the image-metadata and exported-filesystem raw reports are retained. Findings originating from an embedded third-party SBOM require exact OpenVEX set equality plus executable checks that the affected implementation and call path are absent; installed package databases remain independent evidence and product binaries are gated separately.

The same hash-locked integration dependencies are preinstalled in the isolated
`/opt/tox` environment, which runs flake8 and pytest directly without seeding
another environment. Bootstrap invokes Ubuntu's `/usr/bin/python3` explicitly
so the seedless environment placed first on PATH cannot shadow the installer.
The official pip installer, tox and virtualenv are retired
with their supported uninstall commands after preparation; the system pip
packages are explicitly purged without autoremove. The developer `tox.ini` is
retained. Bootstrap dependency locks remain provenance, not a claim that the
bootstrap phase has no vulnerabilities: pip's embedded urllib3 is not fixed by
installing an unrelated top-level urllib3. The final image must prove that the
retired installer code and packages are absent and that the exact test
dependencies remain installed; no urllib3 OpenVEX exception is permitted.

Catalog sources are denied unless their exact origin is authorized by the service operator. Reviewed public GitHub origins are built in. Add private HTTPS origins as a comma-separated list in `PASTURESTACK_CATALOG_ALLOWED_EXTERNAL_ORIGINS`; each entry must contain only a scheme, hostname, and optional port. Plain HTTP is accepted only for loopback tests. Local Git catalogs are restricted to isolated tests: `PASTURESTACK_CATALOG_ALLOWED_LOCAL_ROOTS` may enable only the platform temporary root. Catalog documents, API callers, redirects, icon links, and chart links cannot expand either policy.

Expand Down
4 changes: 2 additions & 2 deletions integration/build-requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ pip==26.2.1 --hash=sha256:71138adf1f4ca900cdb7d289c21b7494329f2332b6d85f0e1c4210
platformdirs==4.11.2 --hash=sha256:7f89089b6ea71bda7962953edcf784b2e2d9d285b40ad88be2bb75c6e9d82ab4
pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
pyproject-api==1.11.0 --hash=sha256:860060c8832dce983b5eec6f41c4c43eb3ec06ff7332387a63acdf5ca27b68d8
python-discovery==1.5.2 --hash=sha256:3e338c2d0f15dfaeea57493f4c2c6caebe0e998ea815c30ae8bf8ee21f1112d3
python-discovery==1.6.0 --hash=sha256:d4e244cf17b8b29819ed78003d55fbacf86eda23425b075454fff9271b79377a
setuptools==84.0.0 --hash=sha256:51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670
tomli==2.4.1 --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe
tomli-w==1.2.0 --hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90
tox==4.59.0 --hash=sha256:fa9a1c968503302544498a98e785e7f46e85d22e9d5bf9bcce1731c8f3ffae01
typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
virtualenv==21.7.4 --hash=sha256:376ec93cd6aab3044fa395d7db226db38043b7b5748948044b2a87168525e843
virtualenv==21.7.13 --hash=sha256:1bea5af7463f59c4719db48fe739579a2a4f569c96f26c086edda85c96da9f59
wheel==0.48.0 --hash=sha256:3217dcc807155e45db462d7ef2431f5ddda0d7273b700d05a67b271ceb1287ab
2 changes: 1 addition & 1 deletion integration/requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb2
requests==2.34.2 --hash=sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0
tomli==2.4.1 --hash=sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe
typing-extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
urllib3==2.7.0 --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
urllib3==2.8.0 --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3
wait-for==2.0 --hash=sha256:b1799a8ef2060c676453497823682b80911e54aeefeb634dd1f2085348e42985
Loading
Loading