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
8 changes: 8 additions & 0 deletions .github/workflows/publish-current-server.yml
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,14 @@ jobs:
gzip -n -c "$evidence/web-console-runtime-vulnerabilities.tsv" \
>"$evidence/web-console-runtime-vulnerabilities.tsv.gz"
test -s "$evidence/web-console-runtime-vulnerabilities.tsv.gz"
# Recheck immutable producer bytes after report conversion. A valid
# gzip stream is not proof of a valid host package (an empty gzip is
# valid too). Never transform or re-compress this producer archive.
host_archive_sha="$(sed -n 's/^ARG HOST_API_ARCHIVE_SHA256=//p' \
source/server/Dockerfile.web-compose-release | sort -u)"
[[ "$host_archive_sha" =~ ^[0-9a-f]{64}$ ]]
printf '%s %s\n' "$host_archive_sha" \
"$evidence/host-api-${HOST_API_VERSION}.tar.gz" | sha256sum -c -
(
cd "$evidence"
sha256sum \
Expand Down
13 changes: 11 additions & 2 deletions docs/releases/server-1.6.550.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Server v1.6.550

**Standalone attachment correction:** The manual completion script mistakenly
replaced `host-api-0.38.8.tar.gz` with an empty gzip stream. Do not use that
attachment. The Server image contains the correct archive, and the
[original Host API producer release](https://github.com/PastureStack/host-api/releases/tag/v0.38.8)
is unchanged. Matching upload checksums did not detect the substitution. The
original immutable bytes remain available as failure evidence; 551 adds a
producer-identity recheck after report conversion and publishes a fresh release.

Published image:
`ghcr.io/pasturestack/server:v1.6.550@sha256:03542028c3ce4be11d7ff87f96beddf5179c67caf9f0d28ad57043acf3cf52c5`.
Producer: `54deb98ee33ffab78437070d61179b602844182e`.
Expand Down Expand Up @@ -49,8 +57,9 @@ were not changed.
[Run 40116218929](https://github.com/PastureStack/server/actions/runs/40116218929)
remains failed because the final renderer required a missing candidate-note
heading. Its tested, signed image was not rebuilt or overwritten. Original CI
bytes completed publication, all 34 immutable assets passed anonymous SHA-256
readback, and both attestations were verified. The
bytes completed image publication and both attestations were verified. The
34 assets passed upload-to-download checksum readback, which did not establish
that the Host API attachment still matched its producer; see the correction above. The
[completion receipt](https://github.com/PastureStack/server/releases/download/v1.6.550/publication-recovery.json)
records the original archive and note-only correction. A permanent pre-build
test now validates the actual current note as well as fixtures.
Expand Down
39 changes: 39 additions & 0 deletions docs/releases/server-1.6.551.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Server v1.6.551 candidate

Not published.

## Verified component inputs

This release retains the PCRE2 security backports and all component inputs from
550, including Web Console 1.6.195, Engine v0.183.354 and Host API 0.38.8.
No application behavior or database migration is introduced.

The manual completion of 550 accidentally replaced its standalone Host API
Release attachment with an empty gzip stream while converting an empty browser
findings table. The image itself contains the correct producer archive. Do not
use the 550 Host API attachment; the original official Host API 0.38.8 release
remains valid. The faulty immutable attachment is retained as evidence.

The normal publisher now checks the exact producer archive SHA-256 again after
report conversion and before upload. Its regression test rejects an empty gzip
substitution even though that stream is syntactically valid. All downloadable
archive bytes must match the producer pin; a self-consistent checksum manifest
alone does not establish that identity.

## Acceptance and remaining risk

Only the release-asset guards and version metadata changed. Original unaffected
PCRE2 native, consumer, UI and authorization evidence is retained. Normal release
source, image/security, startup/restart and exact attachment checks still apply.
8080 deployment and attachment readback are separate acceptance steps. No
production site, HAProxy, firewall or production database is modified.

The prior scan retains 13 pending LOW/MEDIUM occurrences across 10 CVEs. Other
product candidates and the Ruby/Onigmo build-host coverage gap remain tracked;
this release does not claim they have been remediated.

## Upgrade and rollback

Use the new numeric tag after publication and preserve existing configuration,
named volumes, restart policy and security options. Keep the prior image and
matching backup for rollback. Do not patch running containers or overwrite tags.
26 changes: 26 additions & 0 deletions scripts/test-publisher-safety-guards.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ def test_browser_report_upload_is_nonempty_lossless_and_keeps_internal_gate_byte
evidence = Path(temporary)
internal = evidence / 'web-console-runtime-vulnerabilities.tsv'
internal.write_bytes(report)
archive = evidence / 'host-api-0.38.8.tar.gz'
producer_bytes = gzip.compress(b'unchanged producer package', mtime=0)
archive.write_bytes(producer_bytes)
result = subprocess.run(['bash', '-c', 'set -euo pipefail; evidence="$1"; ' + command,
'upload-fixture', temporary], capture_output=True, text=True)
self.assertEqual(result.returncode, 0, result.stderr)
Expand All @@ -37,6 +40,29 @@ def test_browser_report_upload_is_nonempty_lossless_and_keeps_internal_gate_byte
self.assertEqual(gzip.decompress(public.read_bytes()), report)
self.assertEqual(gzip.decompress(gzip.compress(report, mtime=0)), report)
self.assertEqual(internal.read_bytes(), report)
self.assertEqual(archive.read_bytes(), producer_bytes)

def test_empty_gzip_cannot_replace_the_producer_host_package(self):
source = WORKFLOW.read_text()
release = source.split(' - name: Create immutable GitHub Release', 1)[1]
start = release.index(' host_archive_sha=')
end = release.index('\n (', start)
guard = textwrap.dedent(release[start:end])
self.assertLess(start, release.index(' gh release create'))
import hashlib
for replace in (False, True):
with self.subTest(empty_gzip=replace), tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
(root / 'source/server').mkdir(parents=True)
original = gzip.compress(b'unchanged producer package', mtime=0)
(root / 'source/server/Dockerfile.web-compose-release').write_text(
'ARG HOST_API_ARCHIVE_SHA256=' + hashlib.sha256(original).hexdigest() + '\n')
archive = root / 'host-api-0.38.8.tar.gz'
archive.write_bytes(gzip.compress(b'', mtime=0) if replace else original)
result = subprocess.run(['bash', '-c',
'set -euo pipefail; evidence="$1"; HOST_API_VERSION=0.38.8; ' + guard,
'asset-fixture', temporary], cwd=temporary, capture_output=True, text=True)
self.assertEqual(result.returncode, int(replace), result.stderr)

def guard(self, prefix):
lines = [line.strip() for line in WORKFLOW.read_text().splitlines()
Expand Down
2 changes: 1 addition & 1 deletion server/Dockerfile.web-compose-release
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ ARG PER_HOST_SUBNET_VERSION=0.2.5
ARG PER_HOST_SUBNET_PRODUCER_COMMIT=e102e0bb38d84b0a9cbf87e5458cf1bc74d01904
ARG PER_HOST_SUBNET_ARCHIVE_SHA256=7237e83e96a64e7f3de64eb86bff9721c4e879c35cda9e9de1e4cb1ba225f298
ARG PER_HOST_SUBNET_PROVENANCE_SHA256=afa2db0e0755c6bd3ee28e111937c521a6d84e6d71d563e99202cf4693000eaf
ARG SERVER_RELEASE_TAG=v1.6.550
ARG SERVER_RELEASE_TAG=v1.6.551
ARG API_EXPLORER_RELEASE_TAG=v1.1.20
ARG API_EXPLORER_ARTIFACT_SHA256=c989ee12a518f6d9d2b23b1f17f48d781aacf6ec31fb65291dd7de9b1661badb
ARG API_EXPLORER_COMMIT=a586f0dc0aa50df579049a8d4bf26f04400fdf75
Expand Down
4 changes: 2 additions & 2 deletions server/build-api-explorer-patch-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if [[ -n "$(git status --porcelain --untracked-files=normal)" ]]; then
fi

revision=${PASTURESTACK_SERVER_REVISION:-$(git rev-parse HEAD)}
server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}
server_release_tag=${SERVER_RELEASE_TAG:-v1.6.551}
source_date_epoch=${SOURCE_DATE_EPOCH:-$(git show -s --format=%ct HEAD)}
base_image=${BASE_IMAGE:-ghcr.io/pasturestack/server:v1.6.460@sha256:c855af8aea232dacc5bb6df68e2271d482c68b53c43ab0c108ec19118f5ab403}
orchestration_engine_release_base_url=${ORCHESTRATION_ENGINE_RELEASE_BASE_URL:-https://github.com/PastureStack/orchestration-engine/releases/download}
Expand Down Expand Up @@ -130,7 +130,7 @@ usage_telemetry_agent_archive_sha256=${USAGE_TELEMETRY_AGENT_ARCHIVE_SHA256:-6d4
usage_telemetry_agent_binary_sha256=${USAGE_TELEMETRY_AGENT_BINARY_SHA256:-90ed65f689c91801abd0cf482211c96a01e7c63ef695e4faecdf4713eff730be}
supported_docker_range='~v1.12.3 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v17.12.0 || ~v18.03.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.2 || v24.0.9 || >=v29.4.1 <=v29.7.2 || v29.8.0'
newest_docker_version=v29.8.0
image=${IMAGE:-pasturestack-validation/server:v1.6.550}
image=${IMAGE:-pasturestack-validation/server:v1.6.551}
build_options=()

[[ "$revision" =~ ^[0-9a-f]{40}$ ]]
Expand Down