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
11 changes: 11 additions & 0 deletions COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Compatibility Contract

## Server v1.6.550 prepared, not published

The source candidate preserves all component versions and operator settings from
549 while rebuilding the two PCRE2 runtime packages with the upstream's official
10.46 backports. Original Ubuntu packaging, SONAMEs, JIT and POSIX interfaces are
retained. First-party package version `10.46-1build1+pasturestack.1` distinguishes
this rebuild from Canonical binaries. Native upstream tests, exact package
integrity, ELF hashes and actual consumers are mandatory gates. The published
installation remains 549 until a new immutable image passes publication.
See [the candidate note](docs/releases/server-1.6.550.md).

<a id="server-v16549-web-console-sbom"></a>

## Server v1.6.549 published
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ See the [549 release note](docs/releases/server-1.6.549.md) and
[compatibility contract](COMPATIBILITY.md#server-v16549-web-console-sbom)
for exact identities, attachment recovery and acceptance boundaries.

Source candidate `v1.6.550` adds the official PCRE2 10.46 security backports with
original Ubuntu packaging, ABI checks and native regression tests. It is not yet
published; installation examples continue to use the verified release above.
See the [candidate note](docs/releases/server-1.6.550.md).

Personal API Keys remain available without an accessible environment. See the [Host guide](docs/hosts/README.md) before enrolling a host.

## API Keys
Expand Down
48 changes: 48 additions & 0 deletions docs/releases/server-1.6.550.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Server v1.6.550 candidate

Prepared source only; no Server image or release has been published.

## Change

Backport the twelve official PCRE2 `release/pcre2-10.46` commits through
`947809f11e9c9744aa0974b4b630a1ae461733dd` onto the original Ubuntu 26.04
`10.46-1build1` source package. The source, original packaging and every patch
are SHA-256 pinned. This is a first-party rebuild, not a Canonical release.
It avoids unrelated regex, Unicode, JIT or packaging changes.

The two packages `libpcre2-8-0` and `libpcre2-posix3` use version
`10.46-1build1+pasturestack.1`. The eight fixed vulnerabilities are
CVE-2026-103111, CVE-2026-86145, CVE-2026-89156, CVE-2026-89157,
CVE-2026-89158, CVE-2026-89160, CVE-2026-89161 and CVE-2026-89162.
The [official supported backport list](https://github.com/PCRE2Project/pcre2/blob/pcre2-10.49/SUPPORT-LIFECYCLE.md)
identifies this exact series. Ubuntu having no updated binary package does not
mean upstream has no accepted fix.

## Verification and boundaries

The build runs the unchanged Ubuntu packaging tests: `pcre2_jit_test`, `RunTest`
and `RunGrepTest`, and requires all three PASS records. The output retains source
locks, build logs, native test logs, compiler flags, package identity and ABI /
hardening evidence. Runtime checks require exact package versions, zero dpkg
integrity differences, both library hashes, resolved linkage from grep, Git,
MariaDB, libselinux and the POSIX wrapper, and C / C.utf8 regex controls.

The inherited image's documentation exclusion is overridden only while installing
the package-owned `README.Debian`; no integrity exception is added. Raw scanner
results remain intact. The fixed VEX entries apply only to the exact rebuilt
package PURLs; the other ten pending Server CVEs remain tracked.

Native library and layered-candidate checks have passed on the QA host. These do
not establish publication or startup/restart acceptance of the final image.
Those results and the immutable digest will be recorded after the release gates.
Web Console 1.6.195, its runtime SBOM and all other component pins are unchanged.
Unrelated role, UI, OIDC, MFA and feature-matrix PASS evidence is carried forward.

## Upgrade and rollback

Until publication, continue using the numeric `v1.6.549` installation examples.
Do not overwrite an existing image tag or apply library patches to a running
container. Upgrade with a new immutable image after verified backups, preserving
all existing volumes, configuration and the previous container/image for rollback.
No database migration or operator setting change is introduced by this patch.
No production deployment is part of this release task.
20 changes: 10 additions & 10 deletions scripts/check-server-api-explorer-patch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ python3 scripts/test-web-console-sbom.py
python3 server/security/test_web_console_sbom_scan.py

require_marker "$release_dockerfile" \
'ARG SERVER_RELEASE_TAG=v1.6.549' \
'ARG SERVER_RELEASE_TAG=v1.6.550' \
SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING
require_marker "$release_dockerfile" \
'org.opencontainers.image.version="${SERVER_RELEASE_TAG}"' \
Expand Down Expand Up @@ -1138,10 +1138,10 @@ require_marker "$build_script" \
'--file server/Dockerfile.web-compose-release' \
SERVER_INCREMENTAL_RELEASE_BUILD_PATH_MISSING
require_marker "$build_script" \
'image=${IMAGE:-pasturestack-validation/server:v1.6.549}' \
'image=${IMAGE:-pasturestack-validation/server:v1.6.550}' \
SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING
require_marker "$build_script" \
'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.549}' \
'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}' \
SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING
require_marker "$build_script" \
'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"' \
Expand Down Expand Up @@ -1691,10 +1691,10 @@ fi
if grep -Fxq '## Current source candidate' README.md; then
for candidate_readme_marker in \
'<a id="current-release"></a>' \
'Server `v1.6.549` is a prepared source candidate, not a published image.' \
'Server `v1.6.550` is a prepared source candidate, not a published image.' \
'run them only after its official release' \
'This candidate is not yet available' \
'ghcr.io/pasturestack/server:v1.6.549'; do
'ghcr.io/pasturestack/server:v1.6.550'; do
require_marker README.md "$candidate_readme_marker" \
SERVER_CURRENT_CANDIDATE_README_MISSING
done
Expand Down Expand Up @@ -2300,11 +2300,11 @@ done
jq -e '
["CVE-2026-107928","CVE-2026-107929","CVE-2026-86409","CVE-2026-86410","CVE-2026-86412","CVE-2026-86414","CVE-2026-87862","CVE-2026-87863","CVE-2026-87864","CVE-2026-87865","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"] as $curl_fixed_cves
| .["@context"] == "https://openvex.dev/ns/v0.2.0"
and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.549"
and (.statements | length) == 48
and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"
and (.statements | length) == 56
and ([.statements[].vulnerability.name] | length == (unique | length))
and ([.statements[] | select(.status == "fixed") | .vulnerability.name] | sort)
== ["CVE-2024-52005","CVE-2026-107928","CVE-2026-107929","CVE-2026-12087","CVE-2026-13221","CVE-2026-18798","CVE-2026-27171","CVE-2026-56391","CVE-2026-57432","CVE-2026-57433","CVE-2026-75803","CVE-2026-85091","CVE-2026-86409","CVE-2026-86410","CVE-2026-86412","CVE-2026-86414","CVE-2026-87862","CVE-2026-87863","CVE-2026-87864","CVE-2026-87865","CVE-2026-8932","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"]
== ["CVE-2024-52005","CVE-2026-103111","CVE-2026-107928","CVE-2026-107929","CVE-2026-12087","CVE-2026-13221","CVE-2026-18798","CVE-2026-27171","CVE-2026-56391","CVE-2026-57432","CVE-2026-57433","CVE-2026-75803","CVE-2026-85091","CVE-2026-86145","CVE-2026-86409","CVE-2026-86410","CVE-2026-86412","CVE-2026-86414","CVE-2026-87862","CVE-2026-87863","CVE-2026-87864","CVE-2026-87865","CVE-2026-89156","CVE-2026-89157","CVE-2026-89158","CVE-2026-89160","CVE-2026-89161","CVE-2026-89162","CVE-2026-8932","CVE-2026-92386","CVE-2026-92387","CVE-2026-92388","CVE-2026-92389","CVE-2026-92390","CVE-2026-92391","CVE-2026-92392","CVE-2026-94266"]
and ([.statements[] | select(.status == "not_affected") | .vulnerability.name] | sort)
== ["CVE-2024-2236","CVE-2024-56433","CVE-2025-1352","CVE-2025-1376","CVE-2025-66382","CVE-2026-13757","CVE-2026-18374","CVE-2026-18477","CVE-2026-18508","CVE-2026-27456","CVE-2026-3184","CVE-2026-40228","CVE-2026-53910","CVE-2026-54371","CVE-2026-56392","CVE-2026-56855","CVE-2026-57062","CVE-2026-78662","GO-2026-5932"]
and ([.statements[] | select(.status == "under_investigation") | .vulnerability.name] | sort)
Expand Down Expand Up @@ -2338,7 +2338,7 @@ jq -r '
| @tsv
' "$runtime_vendor_pending" | LC_ALL=C sort -u >"$vendor_pending_fixture"
bash "$vendor_pending_validator" "$runtime_vendor_pending" \
"$vendor_pending_fixture" v1.6.549 >/dev/null
"$vendor_pending_fixture" v1.6.550 >/dev/null
bash scripts/test-vendor-pending-findings.sh >/dev/null
rm -f "$vendor_pending_fixture"
trap - EXIT
Expand Down Expand Up @@ -2427,4 +2427,4 @@ for release_readback_contract in \
fi
done

printf 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.549 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 webhook_automation_service=0.10.5 authentication_service=0.4.46 curl=8.18.0-1ubuntu2.8 freemarker=2.3.35 artifact_scan=required vendor_pending=exact-set role_matrix=qa-required locale_layout=qa-required\n'
printf 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.550 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 webhook_automation_service=0.10.5 authentication_service=0.4.46 curl=8.18.0-1ubuntu2.8 freemarker=2.3.35 artifact_scan=required vendor_pending=exact-set role_matrix=qa-required locale_layout=qa-required\n'
2 changes: 2 additions & 0 deletions scripts/check-server-source-gates.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ run_gate catalog_card_brand scripts/check-server-catalog-card-brand-patch.sh
run_gate dockerfile_artifact_downloads scripts/check-server-dockerfile-artifact-downloads.sh
run_gate production_build_context python3 scripts/test-server-build-context.py
run_gate offline_ubuntu_security_updates python3 scripts/test-offline-ubuntu-security-updates.py
run_gate pcre2_official_backports python3 server/security/ubuntu-backports/test_pcre2_backport.py
run_gate pcre2_runtime_verifier python3 scripts/test-pcre2-runtime-verifier.py
run_gate engine_archive_contract python3 scripts/test-engine-archive-contract.py
run_gate runtime_contract_readback python3 scripts/test-runtime-contract-readback.py
run_gate server_dev_dockerfile_downloads scripts/check-server-dev-dockerfile-downloads.sh
Expand Down
30 changes: 15 additions & 15 deletions scripts/test-current-server-packaging-contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
ENGINE_SHA = 'f4889bfb8759a7664f07524bb129d0cda19f35604a06c877b6ca7033246df6fa'
ENGINE_SOURCE = 'dc5a8d7bc44131f609f72a6504483b692d47ce16'
OLD_COORDINATES = {
'v1.6.549': 'v1.6.521',
'v1.6.550': 'v1.6.521',
'1.6.195': '1.6.181',
WEB_SHA: '9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03',
WEB_SOURCE: '7f2bdad0c90a3241693c2ddd4475202ababba550',
Expand All @@ -66,7 +66,7 @@
}
EXPECTED = {
COMPATIBILITY_CODE: (COMPATIBILITY, PUBLISHED_508),
'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.549'),
'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.550'),
'SERVER_INCREMENTAL_RELEASE_VERSION_MISSING': (DOCKER, 'org.opencontainers.image.version="${SERVER_RELEASE_TAG}"'),
'SERVER_INCREMENTAL_RELEASE_RUNTIME_VERSION_MISSING': (DOCKER, 'ENV CATTLE_RANCHER_SERVER_VERSION=${SERVER_RELEASE_TAG}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_VERSION_MISSING': (DOCKER, 'ARG WEB_CONSOLE_RELEASE_TAG=1.6.195'),
Expand All @@ -81,8 +81,8 @@
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_HASH_MISSING': (BUILD, 'web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-' + WEB_SHA + '}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_LOCK_HASH_MISSING': (BUILD, 'web_console_lock_sha256=${WEB_CONSOLE_LOCK_SHA256:-' + WEB_LOCK_SHA + '}'),
'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_SBOM_HASH_MISSING': (BUILD, 'web_console_runtime_sbom_sha256=${WEB_CONSOLE_RUNTIME_SBOM_SHA256:-' + WEB_SBOM_SHA + '}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.549}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.549}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.550}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.550}'),
'SERVER_INCREMENTAL_RELEASE_BUILD_RUNTIME_VERSION_MISSING': (BUILD, 'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"'),
'SERVER_WEB_CONSOLE_RUNTIME_VERSION_GATE_MISSING': (BUILD, 'test "$(cat "${web_root}/VERSION.txt")" = "${WEB_CONSOLE_RELEASE_TAG}"'),
'SERVER_VOLUME_NATIVE_READ_ONLY_BUILD_GATE_MISSING': (DOCKER, 'grep -F \'"volume.isNative" : "r"\' >/dev/null'),
Expand Down Expand Up @@ -600,15 +600,15 @@ def verify(files, gate=GATE):
pipeline = r'(?:^|[;\n])\s*' + re.escape(marker) + r'(?=\s*(?:;|\n|$))'
if len(re.findall(pipeline, source)) != 1:
raise AssertionError('CURRENT_CATALOG_HASH_BYPASS')
if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.549"' not in gate:
if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"' not in gate:
raise AssertionError('CURRENT_VEX_GATE_PIN_MISMATCH')
if '"$vendor_pending_fixture" v1.6.549 >/dev/null' not in gate:
if '"$vendor_pending_fixture" v1.6.550 >/dev/null' not in gate:
raise AssertionError('CURRENT_VENDOR_GATE_PIN_MISMATCH')
if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.549':
if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.550':
raise AssertionError('CURRENT_VEX_RELEASE_MISMATCH')
if json.loads(files[VENDOR]).get('release') != 'v1.6.549':
if json.loads(files[VENDOR]).get('release') != 'v1.6.550':
raise AssertionError('CURRENT_VENDOR_RELEASE_MISMATCH')
if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.549 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ' not in gate:
if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.550 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ' not in gate:
raise AssertionError('CURRENT_SUMMARY_MISMATCH')
verify_incremental_contract(files, gate)
verify_load_balancer_contract(files, gate)
Expand All @@ -623,11 +623,11 @@ def previous_gate(gate=GATE):
if stale(marker) != marker:
replacements["'" + marker + "' "] = "'" + stale(marker) + "' "
replacements.update({
'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.549"':
'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.550"':
'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.521"',
'"$vendor_pending_fixture" v1.6.549 >/dev/null':
'"$vendor_pending_fixture" v1.6.550 >/dev/null':
'"$vendor_pending_fixture" v1.6.521 >/dev/null',
'SERVER_API_EXPLORER_PATCH_OK release=v1.6.549 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ':
'SERVER_API_EXPLORER_PATCH_OK release=v1.6.550 base=v1.6.460 engine=0.183.354 web_console=1.6.195 catalog_service=0.20.15 ':
'SERVER_API_EXPLORER_PATCH_OK release=v1.6.521 base=v1.6.460 engine=0.183.340 web_console=1.6.181 catalog_service=0.20.12 ',
})
for marker, stale_marker in replacements.items():
Expand Down Expand Up @@ -1311,7 +1311,7 @@ def test_historical_engine340_cannot_be_packaged(self):

def test_engine352_actual_war_producer_and_previous351_350_boundary(self):
candidate = (REPO / 'server/build-component-candidate.sh').read_text(encoding='utf-8')
self.assertIn('export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.549}', candidate)
self.assertIn('export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.550}', candidate)
self.assertIn('export ORCHESTRATION_ENGINE_RELEASE_TAG=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.354}', candidate)
for old in (
{'0.183.354': '0.183.351',
Expand Down Expand Up @@ -1619,7 +1619,7 @@ def test_machine_actual_binary_package_and_retired_boundary_are_verified(self):
def test_current_readme_status_specific_prose_does_not_block_publication_transition(self):
common = GATE.split('for current_readme_marker in ', 1)[1].split('; do', 1)[0]
self.assertNotIn('candidate', common)
self.assertNotIn('v1.6.549', common)
self.assertNotIn('v1.6.550', common)
self.assertIn("grep -Fqi 'keep all three named volumes' README.md", GATE)
status = GATE.split("if grep -Fxq '## Current source candidate' README.md; then", 1)[1]
candidate, published = status.split("elif grep -Fxq '## Current release' README.md; then", 1)
Expand All @@ -1628,7 +1628,7 @@ def test_current_readme_status_specific_prose_does_not_block_publication_transit
self.assertIn('This candidate is not yet available', candidate)
self.assertIn('SERVER_CURRENT_PUBLISHED_README_MISSING', published)
self.assertNotIn('candidate', published)
self.assertNotIn('v1.6.549', published)
self.assertNotIn('v1.6.550', published)


if __name__ == '__main__':
Expand Down
Loading