From 7faf7e61f4f24685b7266049a6e276a3d44e8552 Mon Sep 17 00:00:00 2001 From: "Cheng-Chen, Chen" Date: Mon, 12 Oct 2026 19:12:01 +0800 Subject: [PATCH] fix(server): publish native host onboarding repair in 1.6.522 --- .github/workflows/publish-current-server.yml | 6 +- COMPATIBILITY.md | 18 ++ README.md | 80 +++---- docs/README.md | 6 + docs/performance/README.md | 6 +- docs/releases/server-1.6.522.md | 76 ++++++ scripts/check-server-api-explorer-patch.sh | 107 ++++++--- .../test-current-server-packaging-contract.py | 88 ++++--- scripts/test-mfa-policy-api.py | 14 ++ scripts/test-published-install-gate.py | 65 ++++- scripts/test-registration-basic-api-unit.py | 77 ++++++ scripts/test-registration-basic-api.py | 223 ++++++++++++++++++ server/Dockerfile.web-compose-release | 26 +- server/build-api-explorer-patch-image.sh | 18 +- server/build-component-candidate.sh | 6 +- server/security/openvex.json | 2 +- server/security/vendor-pending.json | 2 +- 17 files changed, 682 insertions(+), 138 deletions(-) create mode 100644 docs/releases/server-1.6.522.md create mode 100644 scripts/test-registration-basic-api-unit.py create mode 100644 scripts/test-registration-basic-api.py diff --git a/.github/workflows/publish-current-server.yml b/.github/workflows/publish-current-server.yml index 700314f814b..4ebfb40d52d 100644 --- a/.github/workflows/publish-current-server.yml +++ b/.github/workflows/publish-current-server.yml @@ -336,8 +336,10 @@ jobs: # Exercise the published API and real auth overlay on this fresh, # disposable database before the image is allowed to be released. python3 source/scripts/test-mfa-policy-api.py \ - --port 18082 --disposable | + --port 18082 --disposable --registration-basic | tee "$evidence/mfa-policy-api-smoke.txt" + grep -E '^REGISTRATION_BASIC_API_OK checks=7 ordinary_user=true entry_only=true node_active_verified=false script_sha256=[0-9a-f]{64}$' \ + "$evidence/mfa-policy-api-smoke.txt" >"$evidence/registration-basic-api-smoke.txt" docker stop "$CANDIDATE_NAME" >/dev/null mkdir -p "$rootfs" @@ -585,6 +587,7 @@ jobs: host-api-package-check.txt \ image-inspect.txt \ mfa-policy-api-smoke.txt \ + registration-basic-api-smoke.txt \ published.txt \ push.log \ release-notes.md \ @@ -615,6 +618,7 @@ jobs: "$evidence/host-api-package-check.txt" \ "$evidence/image-inspect.txt" \ "$evidence/mfa-policy-api-smoke.txt" \ + "$evidence/registration-basic-api-smoke.txt" \ "$evidence/published.txt" \ "$evidence/push.log" \ "$evidence/release-notes.md" \ diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index de306769f08..bceafd664e4 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -1,5 +1,23 @@ # Compatibility Contract +## Server v1.6.522 prepared, not published + +The minimal onboarding candidate uses released Engine `v0.183.339` and Web Console +`1.6.183`. It restores dedicated registration-token Basic authentication. +Custom add-host requires RegistrationToken POST; built-in and Other drivers +require Host POST, using the current project's loaded native schemas. +Cancel / Go Back preserves the external return target across driver changes and +API-host settings refresh; missing or self-referencing targets return to Hosts. +Backend roles, resource authorization, other Server components and the console +broker are unchanged. + +Clean merged component source and official asset checksums have been read back; +the original component packagers produced those assets. Focused component and +native UI validation passed, but the final Server image, its seven-check native +registration gate and active-node lifecycle acceptance remain pending. +Integration candidates are not this published release. See the +[candidate note](docs/releases/server-1.6.522.md) for exact component identities. + ## Server v1.6.521 published The [official release](https://github.com/PastureStack/server/releases/tag/v1.6.521) diff --git a/README.md b/README.md index b1b22d33281..77be1e24aae 100644 --- a/README.md +++ b/README.md @@ -9,72 +9,68 @@ Rancher 1.6 ecosystem. It is not affiliated with Rancher Labs or SUSE. This fork of [`rancher/rancher`](https://github.com/rancher/rancher) preserves upstream history, authorship, licenses, and notices. -## Current release - -Server `v1.6.521` packages Web Console `1.6.181` -and Orchestration Engine `v0.183.337`. API Keys support full, custom and closed -policies, optional expiry, and key-scoped durable audit records. Their permissions -remain bounded by the owner's current role and environment access. Delegated -terminal/log access requires the verified Host API audit capability and the -compatible Linux Node Agent lifecycle. -Custom Key Project targets share the normal API's live membership checks; -membership never bypasses Key rules or unrelated resource authorization. + + +## Current source candidate + +Server `v1.6.522` is a prepared source candidate, not a published image. It targets +released Web Console `1.6.183` and Orchestration Engine `v0.183.339`. Registration +tokens use their dedicated Basic authentication route. Add-host access follows +the form's actual operation: Custom requires RegistrationToken POST; built-in +and Other drivers require Host POST. Cancel / Go Back retains the return route +across driver changes and API-host settings refresh, with Hosts as the fallback +for a missing or self-referencing target. +Component validation and official asset readback have passed; final Server image +publication, its native registration gate and active-node acceptance remain pending. +The published Server remains +[`v1.6.521`](https://github.com/PastureStack/server/releases/tag/v1.6.521); its +registration-token Basic route has a known authentication regression. Its +[release note](docs/releases/server-1.6.521.md) retains the published identity. See the +[candidate release note](docs/releases/server-1.6.522.md) for the release boundary. The console supports OIDC, TOTP and passkeys, role-aware resource operations, container resource/hardware settings, and movable terminal/log windows. Hardware options require compatible node software and actual host capabilities; -GPU device access is not exclusive GPU allocation. Audit queries and exports -cover retained records with environment-aware access and bounded pagination. -Explicit date bounds remain attached to every audit page, so an older interval -does not scan newer records first. Oversized queries retain the console filters -and offer explicit narrowing or retry. Export failures are translated on the -page instead of downloaded as error files. An export failure keeps successful -query results intact; scan and export limits are retained. -The Key modal owns initial focus and yields to keyboard or pointer interaction. -Audit export closes its own menu before locking the request, allowing consecutive -JSON, CSV and XLSX downloads without leaving a disabled menu open. -Key scope choices use the viewer's authorized environment list. Environment, -stack and resource selectors close before dependent loading or verification. -Failed Key edits use a consistent public Key ID in audit records. Per-Key audit -also reads historical numeric-ID records within the same ownership and environment -authorization checks; existing records are not rewritten. -See the [published v1.6.521 release](https://github.com/PastureStack/server/releases/tag/v1.6.521) -and [release note](docs/releases/server-1.6.521.md) for exact -component identities and verification boundaries. A version or packaged feature -does not establish that every resource/role/hardware combination has been tested. +GPU device access is not exclusive GPU allocation. Audit queries and JSON, CSV +and XLSX exports preserve environment authorization, explicit date bounds and +bounded pagination. Query/export errors retain filters and successful query +results. See the [Host compatibility guide](docs/hosts/README.md) before enrolling +a host and the [API Key guide](docs/api-keys.md) for policy and audit behavior. +A packaged feature does not establish that every resource, role or hardware +combination has been tested. ## API Keys Choose allow-by-default with deny exceptions, or deny-by-default with allow exceptions, with optional expiry. Without exceptions these retain full or closed access. Search for resource names; the editor and reviewed changes include a -resource-by-operation policy matrix, not an extra grant of authority. The maximum access -is always the Key owner's current account/environment RBAC; a Key never grants -an additional role. Existing legacy Keys are not automatically narrowed on upgrade. +resource-by-operation policy matrix, not an extra grant of authority. The maximum +access is the Key owner's current account/environment RBAC, including live +membership checks; a Key never grants an additional role. Existing legacy Keys +are not automatically narrowed on upgrade. New Key secrets are shown once at creation and are not returned by later lists or details. Per-Key audit separates authorization, the HTTP response and any later job/stream completion. A successful WebSocket upgrade is recorded as a successful HTTP `101` handshake, not as proof of the stream's later completion. Environment-Key audit rechecks live Engine access in Engine-verified project contexts; a caller-supplied project header is not a grant. -Open a Key's audit from its action menu; the close control follows the audit -filters, results and details, below the audit block. -Scope options and unselected hints use readable light/dark theme text, including -keyboard highlighting; narrow layouts keep the matrix in its own scroll region. -Errors use the existing API status/code and console error -handling. See the [API Key guide](docs/api-keys.md) for policy and audit contracts. +Delegated terminal/log access requires the verified Host API audit capability +and a compatible Linux Node Agent lifecycle. Open a Key's audit from its action +menu. See the [API Key guide](docs/api-keys.md) for policy and audit contracts. ## Quick start -Public GHCR downloads do not require a registry login. Use the published numeric -version tag and keep all three named volumes: +Public GHCR downloads do not require a registry login. The examples below target +the prepared numeric `v1.6.522` tag: run them only after its official release and +image have been published and read back. This candidate is not yet available +from GHCR. Keep all three named volumes: ```sh docker run -d --name pasturestack-server --restart unless-stopped -p 8080:8080 \ -v pasturestack-cattle:/var/lib/cattle \ -v pasturestack-mysql:/var/lib/mysql \ -v pasturestack-mysqllog:/var/log/mysql \ - ghcr.io/pasturestack/server:v1.6.521 + ghcr.io/pasturestack/server:v1.6.522 ``` For HTTPS termination at a reverse proxy, set the exact public origin: @@ -82,7 +78,7 @@ For HTTPS termination at a reverse proxy, set the exact public origin: ```yaml services: pasturestack-server: - image: ghcr.io/pasturestack/server:v1.6.521 + image: ghcr.io/pasturestack/server:v1.6.522 restart: unless-stopped ports: - "8080:8080" diff --git a/docs/README.md b/docs/README.md index 2e0ccfe4d2b..ed16cb39645 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,12 @@ runtime gates have passed. ## Release notes +- [Server v1.6.522 candidate](releases/server-1.6.522.md) — prepared source using + released Engine `0.183.339` and Web Console `1.6.183`; dedicated registration + authentication and schema-bound add-host access/navigation. Component checks + passed; the final Server image, native registration gate and active-node + acceptance remain pending. It is not a published image. + - [Server v1.6.521](releases/server-1.6.521.md) — officially published with Engine `0.183.337` and Web Console `1.6.181`; canonical API Key audit identifiers and historical numeric-ID reads retain ownership and environment checks. diff --git a/docs/performance/README.md b/docs/performance/README.md index 9c36c507f9f..f8988e27654 100644 --- a/docs/performance/README.md +++ b/docs/performance/README.md @@ -8,10 +8,14 @@ The following example is appropriate as a starting point for a dedicated 16-vCPU host with about 32 GiB of RAM. It is not a universal performance profile; measure the real workload before increasing it further. +The example matches the prepared `v1.6.522` source candidate: run it only after +its official release and image have been published and read back. This candidate +is not yet available from GHCR; see the [candidate release note](../releases/server-1.6.522.md). + ```yaml services: pasturestack-server: - image: ghcr.io/pasturestack/server:v1.6.521 + image: ghcr.io/pasturestack/server:v1.6.522 restart: unless-stopped ports: - "8080:8080" diff --git a/docs/releases/server-1.6.522.md b/docs/releases/server-1.6.522.md new file mode 100644 index 00000000000..79372a71a7b --- /dev/null +++ b/docs/releases/server-1.6.522.md @@ -0,0 +1,76 @@ +# Server v1.6.522 candidate + +Prepared source only; no Server image or release has been published. +The candidate uses released Engine `v0.183.339` and Web Console `1.6.183`. +The published Server remains `v1.6.521`. + +## Minimal onboarding changes + +Engine gives the reserved `registrationToken` Basic username to its dedicated +registration lookup before ordinary API Key authentication. A rejected native +token does not fall through to an API Key or browser session. This does not +broaden the register schema, API Key permissions or account RBAC. + +Web Console gates each add-host form by its actual submission path: Custom +creates a RegistrationToken and requires RegistrationToken POST; built-in and +Other driver forms create a Host and require Host POST. Machine POST alone does +not grant either path. These checks use the current project's loaded native +schemas; absent, readonly or stale schemas grant no onboarding access. + +Cancel / Go Back explicitly dispatches to the active route. The external return +target is saved before the initial driver redirect and preserved across driver +changes and API-host settings refresh. Missing, loading or self-referencing +add-host targets fall back to Hosts; valid external targets and the existing +waiting destination remain. Other Server components and the console broker are +unchanged; this is not the broader API Key integration candidate. + +## Released component inputs + +Engine `v0.183.339` uses source +`436a37215bc5702e516ecf1feb6753b4be5bf602`; official `cattle.jar` SHA-256 is +`bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d`. +Web Console `1.6.183` uses normally merged source +`e056a0111d20816742eff17a65dfabb5d003dae2`; official +`web-console-1.6.183.tar.gz` SHA-256 is +`8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217`. + +These components use clean merged source and the original component packagers; +the official versioned release assets and checksums were independently read back. +They are not CI-produced component artifacts. The Server image is a separate +normal publication CI output and is still pending. + +## Publication prerequisites + +The formal recipe, builder defaults and coordinate assertions must use the +released component identities above and the verified Engine schema hashes. +An uncommitted local build checksum or integration image is not an official +release identity. + +The normal Server publication workflow still requires a clean exact source, +source gates, formal image build and flatten comparison, isolated startup and +restart, proxy/cache contracts, native MFA/API smoke, final merged-rootfs +security and SBOM gates, provenance, immutable numeric publication, and release +asset readback. Existing vulnerability decisions and review deadlines remain; +the new version is not permission to widen or reset them. + +## Native acceptance boundary + +Eleven focused native-schema and return-history component cases and the native +add-host UI checks passed on the component source/tree used for the released +Web bundle. They validate the component changes only, not a final Server image. + +The final Server's seven-check native registration gate remains pending. It must +verify native token creation and readiness, generated script retrieval, +`GET /v1/register`, the restricted registration schema, ordinary API Key Basic +authentication and invalid-token rejection even with a valid cookie. +Successful HTTP entry alone does not prove an agent connection or active node. +The real agent lifecycle and active-node acceptance also remain pending. + +## Install, upgrade and rollback + +After publication and readback, use the numeric image tag +`ghcr.io/pasturestack/server:v1.6.522` shown in the README. Keep all three existing +volumes, environment variables, networking, restart policy and security options. +The intended changes do not add a database migration or authentication setting. +Rollback to `v1.6.521` restores the known registration-token authentication defect; +keep a matching database backup and follow the existing API Key rollback guide. diff --git a/scripts/check-server-api-explorer-patch.sh b/scripts/check-server-api-explorer-patch.sh index a6c5bd05107..2bc067e24d2 100755 --- a/scripts/check-server-api-explorer-patch.sh +++ b/scripts/check-server-api-explorer-patch.sh @@ -323,6 +323,7 @@ check_server497_published_docs # Install examples must follow the highest actually published numeric release; # Install commands use numeric tags; digest identity stays in publication evidence. +# An explicitly guarded522 source candidate may show numeric examples, not claim publication. python3 - <<'PY' import pathlib import re @@ -332,7 +333,11 @@ install_pattern = r'ghcr\.io/pasturestack/server:v[0-9]+\.[0-9]+\.[0-9]+' image_pattern = install_pattern + r'@sha256:[0-9a-f]{64}' reference_pattern = r'''ghcr\.io/pasturestack/server:[^\s`"'<>()\[\]{},;]+''' current = re.search(r'(?ms)^## Current release\n(.*?)(?=^## |\Z)', readme) -tags = re.findall(r'https://github\.com/PastureStack/server/releases/tag/(v[0-9]+\.[0-9]+\.[0-9]+)(?=[)\s])', current[1] if current else '') +candidate = re.search(r'(?ms)^## Current source candidate\n(.*?)(?=^## |\Z)', readme) +if bool(current) == bool(candidate): + raise SystemExit('SERVER_LATEST_PUBLISHED_IDENTITY_MISSING') +context = current if current else candidate +tags = re.findall(r'https://github\.com/PastureStack/server/releases/tag/(v[0-9]+\.[0-9]+\.[0-9]+)(?=[)\s])', context[1]) if len(tags) != 1: raise SystemExit('SERVER_LATEST_PUBLISHED_IDENTITY_MISSING') tag = tags[0] @@ -342,12 +347,34 @@ if len(references) != 2 or references[0] != references[1] or not re.fullmatch(in raise SystemExit('SERVER_LATEST_PUBLISHED_QUICK_START_MISMATCH') image = references[0] notes_path = 'docs/releases/server-' + tag[1:] + '.md' -if image != 'ghcr.io/pasturestack/server:' + tag or '(' + notes_path + ')' not in current[1]: +expected_install_tag = 'v1.6.522' if candidate else tag +if image != 'ghcr.io/pasturestack/server:' + expected_install_tag or '(' + notes_path + ')' not in context[1]: raise SystemExit('SERVER_LATEST_PUBLISHED_QUICK_START_MISMATCH') performance = pathlib.Path('docs/performance/README.md').read_text(encoding='utf-8') performance_references = re.findall(r'ghcr\.io/pasturestack/server:[^\s`"<>]+', performance) if performance_references != [image]: raise SystemExit('SERVER_LATEST_PUBLISHED_PERFORMANCE_MISMATCH') +if candidate: + candidate_path = 'docs/releases/server-1.6.522.md' + candidate_notes = pathlib.Path(candidate_path).read_text(encoding='utf-8') + required = ( + 'Server `v1.6.522` is a prepared source candidate, not a published image.', + '(' + candidate_path + ')', + ) + if '' not in readme or \ + any(marker not in context[1] for marker in required) or not all(marker in quick[1] for marker in ( + 'run them only after its official release', + 'This candidate is not yet available')): + raise SystemExit('SERVER_SOURCE_CANDIDATE_DISCLOSURE_MISSING') + normalized_performance = ' '.join(performance.split()) + if not all(marker in normalized_performance for marker in ( + 'run it only after its official release and image have been published and read back.', + 'is not yet available from GHCR', + '(../releases/server-1.6.522.md)')): + raise SystemExit('SERVER_SOURCE_CANDIDATE_PERFORMANCE_DISCLOSURE_MISSING') + if not re.search(r'(?m)^# Server v1\.6\.522 candidate[ \t]*$', candidate_notes) or \ + 'Prepared source only; no Server image or release has been published.' not in candidate_notes: + raise SystemExit('SERVER_SOURCE_CANDIDATE_NOTES_MISMATCH') notes = pathlib.Path(notes_path).read_text(encoding='utf-8') compat = pathlib.Path('COMPATIBILITY.md').read_text(encoding='utf-8') published = [reference for reference in re.findall(reference_pattern, compat) @@ -356,6 +383,11 @@ if not published: raise SystemExit('SERVER_LATEST_PUBLISHED_IDENTITY_MISSING') latest_tag = max((reference.split('@')[0].rsplit(':', 1)[1] for reference in published), key=lambda value: tuple(map(int, value[1:].split('.')))) +if candidate: + if tuple(map(int, expected_install_tag[1:].split('.'))) <= tuple(map(int, latest_tag[1:].split('.'))) or \ + '## Server v1.6.522 prepared, not published' not in compat or \ + 'ghcr.io/pasturestack/server:v1.6.522@' in readme + performance + compat + candidate_notes: + raise SystemExit('SERVER_SOURCE_CANDIDATE_PUBLICATION_MISMATCH') if tag != latest_tag or not re.search(r'(?m)^# Server ' + re.escape(tag) + r'[ \t]*$', notes): raise SystemExit('SERVER_LATEST_PUBLISHED_QUICK_START_MISMATCH') publication_identity = None @@ -368,7 +400,10 @@ for text in (notes, compat): publication_identity = identities[0] if any(reference != publication_identity for reference in identities): raise SystemExit('SERVER_LATEST_PUBLISHED_QUICK_START_MISMATCH') -print('SERVER_LATEST_PUBLISHED_QUICK_START_OK release=' + tag) +if candidate: + print('SERVER_SOURCE_CANDIDATE_INSTALL_OK release=' + expected_install_tag + ' published=' + tag) +else: + print('SERVER_LATEST_PUBLISHED_QUICK_START_OK release=' + tag) PY python3 scripts/test-published-install-gate.py python3 scripts/test-current-server-packaging-contract.py @@ -491,7 +526,7 @@ require_marker "$release_dockerfile" \ 'ARG BASE_IMAGE=ghcr.io/pasturestack/server:v1.6.460@sha256:c855af8aea232dacc5bb6df68e2271d482c68b53c43ab0c108ec19118f5ab403' \ SERVER_INCREMENTAL_RELEASE_BASE_MISSING require_marker "$release_dockerfile" \ - 'ARG SERVER_RELEASE_TAG=v1.6.519' \ + 'ARG SERVER_RELEASE_TAG=v1.6.522' \ SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING require_marker "$release_dockerfile" \ 'org.opencontainers.image.version="${SERVER_RELEASE_TAG}"' \ @@ -532,16 +567,16 @@ require_marker "$publish_workflow" \ 'bash source/server/artifacts/verify-host-api-package.sh' \ SERVER_HOST_API_PRODUCER_RELEASE_CHECK_MISSING require_marker "$release_dockerfile" \ - 'ARG WEB_CONSOLE_RELEASE_TAG=1.6.181' \ + 'ARG WEB_CONSOLE_RELEASE_TAG=1.6.183' \ SERVER_INCREMENTAL_WEB_CONSOLE_VERSION_MISSING require_marker "$release_dockerfile" \ - 'ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.181.tar.gz' \ + 'ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.183.tar.gz' \ SERVER_INCREMENTAL_WEB_CONSOLE_ARTIFACT_MISSING require_marker "$release_dockerfile" \ - 'ARG WEB_CONSOLE_ARTIFACT_SHA256=9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03' \ + 'ARG WEB_CONSOLE_ARTIFACT_SHA256=8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217' \ SERVER_INCREMENTAL_WEB_CONSOLE_HASH_MISSING require_marker "$release_dockerfile" \ - 'ARG WEB_CONSOLE_COMMIT=7f2bdad0c90a3241693c2ddd4475202ababba550' \ + 'ARG WEB_CONSOLE_COMMIT=e056a0111d20816742eff17a65dfabb5d003dae2' \ SERVER_INCREMENTAL_WEB_CONSOLE_COMMIT_MISSING require_marker "$release_dockerfile" \ "grep -aF 'hostsPage.permissionDenied'" \ @@ -550,16 +585,16 @@ require_marker "$release_dockerfile" \ '"hostsPage.permissionDenied":"您沒有權限在此環境中新增主機。"' \ SERVER_INCREMENTAL_WEB_CONSOLE_ZH_TW_PERMISSION_MESSAGE_MISSING require_marker "$build_script" \ - 'web_console_commit=${WEB_CONSOLE_COMMIT:-7f2bdad0c90a3241693c2ddd4475202ababba550}' \ + 'web_console_commit=${WEB_CONSOLE_COMMIT:-e056a0111d20816742eff17a65dfabb5d003dae2}' \ SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_COMMIT_MISSING require_marker "$build_script" \ - 'web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.181}' \ + 'web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.183}' \ SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_VERSION_MISSING require_marker "$build_script" \ - 'web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.181.tar.gz}' \ + 'web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.183.tar.gz}' \ SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_ARTIFACT_MISSING require_marker "$build_script" \ - 'web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03}' \ + 'web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217}' \ SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_HASH_MISSING require_marker "$release_dockerfile" \ 'ARG CATALOG_SERVICE_VERSION=0.20.13' \ @@ -1024,21 +1059,21 @@ 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.519}' \ + 'image=${IMAGE:-pasturestack-validation/server:v1.6.522}' \ SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING require_marker "$build_script" \ - 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.519}' \ + 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.522}' \ SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING require_marker "$build_script" \ 'CATTLE_RANCHER_SERVER_VERSION="${server_release_tag}"' \ SERVER_INCREMENTAL_RELEASE_BUILD_RUNTIME_VERSION_MISSING for release_engine_marker in \ - 'ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.334' \ + 'ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.339' \ 'ARG ORCHESTRATION_ENGINE_ARTIFACT=cattle.jar' \ - 'ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=a62d8f6ddd61c850e71bbb6fd30352b3a73c7c09e605bb5c45d549074e8824e8' \ - 'ARG ORCHESTRATION_ENGINE_COMMIT=66a87ea6901296fa11ada5df58b0618a4ca96975' \ + 'ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d' \ + 'ARG ORCHESTRATION_ENGINE_COMMIT=436a37215bc5702e516ecf1feb6753b4be5bf602' \ 'COPY --from=release_artifacts /out/orchestration-engine.jar /tmp/orchestration-engine.jar' \ - 'ARG ORCHESTRATION_ENGINE_VERSION=0.183.334' \ + 'ARG ORCHESTRATION_ENGINE_VERSION=0.183.339' \ 'grep -Fx "Implementation-Version: ${ORCHESTRATION_ENGINE_VERSION}"' \ 'cattle-resources-${ORCHESTRATION_ENGINE_VERSION}.jar' \ 'cattle-app-config-${ORCHESTRATION_ENGINE_VERSION}.jar' \ @@ -1059,10 +1094,10 @@ for release_engine_marker in \ SERVER_INCREMENTAL_ENGINE_REPLACEMENT_MISSING done for release_engine_build_marker in \ - 'orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.334}' \ + 'orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.339}' \ 'orchestration_engine_artifact=${ORCHESTRATION_ENGINE_ARTIFACT:-cattle.jar}' \ - 'orchestration_engine_artifact_sha256=${ORCHESTRATION_ENGINE_ARTIFACT_SHA256:-a62d8f6ddd61c850e71bbb6fd30352b3a73c7c09e605bb5c45d549074e8824e8}' \ - 'orchestration_engine_commit=${ORCHESTRATION_ENGINE_COMMIT:-66a87ea6901296fa11ada5df58b0618a4ca96975}' \ + 'orchestration_engine_artifact_sha256=${ORCHESTRATION_ENGINE_ARTIFACT_SHA256:-bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d}' \ + 'orchestration_engine_commit=${ORCHESTRATION_ENGINE_COMMIT:-436a37215bc5702e516ecf1feb6753b4be5bf602}' \ 'CATTLE_CATTLE_VERSION="${orchestration_engine_release_tag}"' \ 'cattle-resources-${ORCHESTRATION_ENGINE_VERSION}.jar' \ 'test "${hazelcast_entry}" = "WEB-INF/lib/hazelcast-5.7.5.jar"'; do @@ -1505,10 +1540,7 @@ for current_release_marker in \ SERVER_CURRENT_RELEASE_NOTES_IDENTITY_MISSING done for current_readme_marker in \ - '## Current release' \ '## Quick start' \ - 'Use the published numeric' \ - 'keep all three named volumes' \ '[performance settings](docs/performance/README.md)' \ '## Upgrade and rollback' \ 'Back up the database and volumes, test a restore' \ @@ -1520,6 +1552,27 @@ for current_readme_marker in \ require_marker README.md "$current_readme_marker" \ SERVER_CURRENT_README_IDENTITY_MISSING done +if ! grep -Fqi 'keep all three named volumes' README.md; then + echo SERVER_CURRENT_README_VOLUME_CONTRACT_MISSING >&2 + exit 1 +fi +# The earlier install proof validates the actual candidate/published body and identities. +if grep -Fxq '## Current source candidate' README.md; then + for candidate_readme_marker in \ + '' \ + 'Server `v1.6.522` 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.522'; do + require_marker README.md "$candidate_readme_marker" \ + SERVER_CURRENT_CANDIDATE_README_MISSING + done +elif grep -Fxq '## Current release' README.md; then + require_marker README.md '## Current release' SERVER_CURRENT_PUBLISHED_README_MISSING +else + echo SERVER_CURRENT_README_STATUS_MISSING >&2 + exit 1 +fi for historical_v481_marker in \ '# Server v1.6.481' \ 'This release assembles Web Console `1.6.146`' \ @@ -2091,7 +2144,7 @@ done jq -e ' .["@context"] == "https://openvex.dev/ns/v0.2.0" - and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.519" + and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.522" and (.statements | length) == 51 and ([.statements[].vulnerability.name] | length == (unique | length)) and ([.statements[] | select(.status == "fixed") | .vulnerability.name] | sort) @@ -2117,7 +2170,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.519 >/dev/null + "$vendor_pending_fixture" v1.6.522 >/dev/null bash scripts/test-vendor-pending-findings.sh >/dev/null rm -f "$vendor_pending_fixture" trap - EXIT @@ -2206,4 +2259,4 @@ for release_readback_contract in \ fi done -printf 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.519 base=v1.6.460 engine=0.183.334 web_console=1.6.181 catalog_service=0.20.13 webhook_automation_service=0.10.4 authentication_service=0.4.43 curl=8.18.0-1ubuntu2.7 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.522 base=v1.6.460 engine=0.183.339 web_console=1.6.183 catalog_service=0.20.13 webhook_automation_service=0.10.4 authentication_service=0.4.43 curl=8.18.0-1ubuntu2.7 freemarker=2.3.35 artifact_scan=required vendor_pending=exact-set role_matrix=qa-required locale_layout=qa-required\n' diff --git a/scripts/test-current-server-packaging-contract.py b/scripts/test-current-server-packaging-contract.py index 99671579d51..b26818701e1 100644 --- a/scripts/test-current-server-packaging-contract.py +++ b/scripts/test-current-server-packaging-contract.py @@ -17,21 +17,21 @@ PUBLISHED_508 = 'Server `v1.6.508` 已正式發布,封裝 Web Console `1.6.171`。' STALE_508_CANDIDATE = 'Server `v1.6.508` candidate packages Web Console `1.6.171`' COMPATIBILITY_CODE = 'SERVER_CREATE_RESPONSE_ORDER_COMPATIBILITY_MISSING' -WEB_SHA = '9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03' -WEB_SOURCE = '7f2bdad0c90a3241693c2ddd4475202ababba550' +WEB_SHA = '8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217' +WEB_SOURCE = 'e056a0111d20816742eff17a65dfabb5d003dae2' CATALOG_COMMIT = 'b6b658888fce50d3ec217eb4eba0f26ab0113baf' -ENGINE_SHA = 'a62d8f6ddd61c850e71bbb6fd30352b3a73c7c09e605bb5c45d549074e8824e8' -ENGINE_SOURCE = '66a87ea6901296fa11ada5df58b0618a4ca96975' +ENGINE_SHA = 'bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d' +ENGINE_SOURCE = '436a37215bc5702e516ecf1feb6753b4be5bf602' OLD_COORDINATES = { - 'v1.6.519': 'v1.6.518', - '1.6.181': '1.6.180', - WEB_SHA: 'a367bd6907281298a8e2bf0f3ad0444083db06062f3a1521db573cc5606d274e', - WEB_SOURCE: '637604b38401b19d2c9ef73d5729d356bb80c8e6', + 'v1.6.522': 'v1.6.521', + '1.6.183': '1.6.181', + WEB_SHA: '9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03', + WEB_SOURCE: '7f2bdad0c90a3241693c2ddd4475202ababba550', CATALOG_COMMIT: '7670ffd81d5f0b5570197fb03c7e55b46da45bf3', } -# Engine334 version stays pinned; its verified-frame WAR/source are current. -# The previous518 Web180 values remain mutation controls. -# Component coordinates do not establish Server519 artifact/runtime PASS. +# Engine339 and Web183 use independently read-back official release identities. +# Published521 component values remain mutation controls. +# Component coordinates do not establish Server522 artifact/runtime PASS. CATALOG_FIELDS = ( ('VERSION', 'version', '0.20.13'), ('COMMIT', 'commit', '4c39c73a8131ba06e9ff0aaec3b95cc27e049324'), @@ -41,25 +41,25 @@ ('LICENSE_SHA256', 'license_sha256', '0d542e0c8804e39aa7f37eb00da5a762149dc682d7829451287e11b938e94594'), ) LEGACY_ENGINE_COORDINATES = { - '0.183.334': '0.183.333', - ENGINE_SHA: '8c42c0982cbc2f4569fa265ad320b341551758cb4fc0bc6d79ba06d70e20d328', - ENGINE_SOURCE: '0d94f7d879d314235e582a7f4062914a27b82709', + '0.183.339': '0.183.337', + ENGINE_SHA: '7e45189f2b57097efc7929c04ddf7767aebbe027d1b9f90023aa6711522c5c80', + ENGINE_SOURCE: 'ba041fe312dfe97fc1e5144af576ddcf1b573d8e', } EXPECTED = { COMPATIBILITY_CODE: (COMPATIBILITY, PUBLISHED_508), - 'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.519'), + 'SERVER_INCREMENTAL_RELEASE_DEFAULT_MISSING': (DOCKER, 'ARG SERVER_RELEASE_TAG=v1.6.522'), '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.181'), - 'SERVER_INCREMENTAL_WEB_CONSOLE_ARTIFACT_MISSING': (DOCKER, 'ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.181.tar.gz'), + 'SERVER_INCREMENTAL_WEB_CONSOLE_VERSION_MISSING': (DOCKER, 'ARG WEB_CONSOLE_RELEASE_TAG=1.6.183'), + 'SERVER_INCREMENTAL_WEB_CONSOLE_ARTIFACT_MISSING': (DOCKER, 'ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.183.tar.gz'), 'SERVER_INCREMENTAL_WEB_CONSOLE_HASH_MISSING': (DOCKER, 'ARG WEB_CONSOLE_ARTIFACT_SHA256=' + WEB_SHA), 'SERVER_INCREMENTAL_WEB_CONSOLE_COMMIT_MISSING': (DOCKER, 'ARG WEB_CONSOLE_COMMIT=' + WEB_SOURCE), 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_COMMIT_MISSING': (BUILD, 'web_console_commit=${WEB_CONSOLE_COMMIT:-' + WEB_SOURCE + '}'), - 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_VERSION_MISSING': (BUILD, 'web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.181}'), - 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_ARTIFACT_MISSING': (BUILD, 'web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.181.tar.gz}'), + 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_VERSION_MISSING': (BUILD, 'web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.183}'), + 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_ARTIFACT_MISSING': (BUILD, 'web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.183.tar.gz}'), 'SERVER_INCREMENTAL_WEB_CONSOLE_BUILD_HASH_MISSING': (BUILD, 'web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-' + WEB_SHA + '}'), - 'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.519}'), - 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.519}'), + 'SERVER_INCREMENTAL_RELEASE_BUILD_VERSION_MISSING': (BUILD, 'image=${IMAGE:-pasturestack-validation/server:v1.6.522}'), + 'SERVER_INCREMENTAL_RELEASE_BUILD_DEFAULT_MISSING': (BUILD, 'server_release_tag=${SERVER_RELEASE_TAG:-v1.6.522}'), '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'), @@ -80,18 +80,18 @@ CREATE_RESPONSE_CODES = ['SERVER_WEB_CREATE_IDENTITY_BUILD_GATE_MISSING','SERVER_WEB_CANONICAL_RECORD_BUILD_GATE_MISSING','SERVER_WEB_CREATE_IDENTITY_RUNTIME_GATE_MISSING','SERVER_WEB_CANONICAL_RECORD_RUNTIME_GATE_MISSING'] ENGINE_EXPECTED = { 'SERVER_INCREMENTAL_ENGINE_REPLACEMENT_MISSING': (DOCKER, 'release_engine_marker', ( - 'ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.334', + 'ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.339', 'ARG ORCHESTRATION_ENGINE_ARTIFACT=cattle.jar', 'ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=' + ENGINE_SHA, 'ARG ORCHESTRATION_ENGINE_COMMIT=' + ENGINE_SOURCE, - 'ARG ORCHESTRATION_ENGINE_VERSION=0.183.334', + 'ARG ORCHESTRATION_ENGINE_VERSION=0.183.339', 'grep -Fx "Implementation-Version: ${ORCHESTRATION_ENGINE_VERSION}"', 'cattle-resources-${ORCHESTRATION_ENGINE_VERSION}.jar', 'cattle-app-config-${ORCHESTRATION_ENGINE_VERSION}.jar', 'ENV CATTLE_CATTLE_VERSION=${ORCHESTRATION_ENGINE_RELEASE_TAG}', )), 'SERVER_INCREMENTAL_ENGINE_BUILD_COORDINATE_MISSING': (BUILD, 'release_engine_build_marker', ( - 'orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.334}', + 'orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.339}', 'orchestration_engine_artifact=${ORCHESTRATION_ENGINE_ARTIFACT:-cattle.jar}', 'orchestration_engine_artifact_sha256=${ORCHESTRATION_ENGINE_ARTIFACT_SHA256:-' + ENGINE_SHA + '}', 'orchestration_engine_commit=${ORCHESTRATION_ENGINE_COMMIT:-' + ENGINE_SOURCE + '}', @@ -470,15 +470,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.519"' not in gate: + if 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.522"' not in gate: raise AssertionError('CURRENT_VEX_GATE_PIN_MISMATCH') - if '"$vendor_pending_fixture" v1.6.519 >/dev/null' not in gate: + if '"$vendor_pending_fixture" v1.6.522 >/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.519': + if json.loads(files[VEX]).get('@id') != 'https://github.com/PastureStack/server/security/openvex/v1.6.522': raise AssertionError('CURRENT_VEX_RELEASE_MISMATCH') - if json.loads(files[VENDOR]).get('release') != 'v1.6.519': + if json.loads(files[VENDOR]).get('release') != 'v1.6.522': raise AssertionError('CURRENT_VENDOR_RELEASE_MISMATCH') - if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.519 base=v1.6.460 engine=0.183.334 web_console=1.6.181 catalog_service=0.20.13 ' not in gate: + if 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.522 base=v1.6.460 engine=0.183.339 web_console=1.6.183 catalog_service=0.20.13 ' not in gate: raise AssertionError('CURRENT_SUMMARY_MISMATCH') verify_incremental_contract(files, gate) @@ -492,18 +492,18 @@ 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.519"': - 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.518"', - '"$vendor_pending_fixture" v1.6.519 >/dev/null': - '"$vendor_pending_fixture" v1.6.518 >/dev/null', - 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.519 base=v1.6.460 engine=0.183.334 web_console=1.6.181 catalog_service=0.20.13 ': - 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.518 base=v1.6.460 engine=0.183.334 web_console=1.6.180 catalog_service=0.20.12 ', + 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.522"': + 'and .["@id"] == "https://github.com/PastureStack/server/security/openvex/v1.6.521"', + '"$vendor_pending_fixture" v1.6.522 >/dev/null': + '"$vendor_pending_fixture" v1.6.521 >/dev/null', + 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.522 base=v1.6.460 engine=0.183.339 web_console=1.6.183 catalog_service=0.20.13 ': + 'SERVER_API_EXPLORER_PATCH_OK release=v1.6.521 base=v1.6.460 engine=0.183.339 web_console=1.6.181 catalog_service=0.20.12 ', }) for marker, stale_marker in replacements.items(): if gate.count(marker) != 1: raise AssertionError('UNEXPECTED_CURRENT_MARKER_SHAPE') gate = gate.replace(marker, stale_marker) - # Synthetic mutation leaves current engine pins; separate tests reject333. + # Synthetic mutation leaves current engine pins; separate tests reject337. return gate @@ -1043,7 +1043,7 @@ def test_govc_fixed_dependency_and_binary_readback_use_formal_pins(self): def test_current_readme_keeps_install_and_upgrade_contract(self): block = GATE.split('for current_readme_marker in ', 1)[1].split('; do', 1)[0] markers = shlex.split(block.replace('\\\n', ' ')) - for marker in ('## Current release', '## Quick start', '## Upgrade and rollback', + for marker in ('## Quick start', '## Upgrade and rollback', '[upgrade guide](docs/upgrades/README.md)', 'Do not replace existing volumes with new empty ones or run `docker compose down -v`.', 'rollback may require restoring matching data', '[release notes](docs/releases)'): @@ -1052,6 +1052,20 @@ def test_current_readme_keeps_install_and_upgrade_contract(self): for marker in markers: self.assertIn(marker, readme) + 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.522', 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) + published = published.split('else\n', 1)[0] + self.assertIn('run them only after its official release', candidate) + 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.522', published) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/scripts/test-mfa-policy-api.py b/scripts/test-mfa-policy-api.py index 9f33a432729..5d71c6fbf5e 100644 --- a/scripts/test-mfa-policy-api.py +++ b/scripts/test-mfa-policy-api.py @@ -10,7 +10,9 @@ import hashlib import hmac import http.client +import importlib.util import json +from pathlib import Path import secrets import struct import time @@ -21,6 +23,8 @@ def main(): parser.add_argument('--port', required=True, type=int) parser.add_argument('--disposable', action='store_true', required=True) parser.add_argument('--api-version', choices=('v1', 'v2-beta'), default='v2-beta') + parser.add_argument('--registration-basic', action='store_true', + help='Run native registration entry checks using this same fresh administrator session.') args = parser.parse_args() assert 1 <= args.port <= 65535 # Fixed loopback authority: callers cannot supply a remote URL, and the @@ -94,6 +98,16 @@ def verify_session_bound_token(api_root, issued_token, generation, label): call('POST', '/v1/localauthconfigs', local) check('authenticated-administrator', call('GET', api + '/accounts').get('type') == 'collection') + if args.registration_basic: + # Share only RAM session material; do not enable local auth a second time + # or create another bootstrap administrator. Run before MFA enrollment. + spec = importlib.util.spec_from_file_location('registration_basic_smoke', + Path(__file__).with_name('test-registration-basic-api.py')) + registration = importlib.util.module_from_spec(spec) + spec.loader.exec_module(registration) + registration.run(port=args.port, administrator_jwt=token, + administrator_account_id=login['accountId'], disposable=args.disposable) + # The token resource intentionally has no public schema entry. Its # clientSessionId contract is therefore verified below through observable # issue/delete semantics: a mismatched generation must preserve the token, diff --git a/scripts/test-published-install-gate.py b/scripts/test-published-install-gate.py index d3a62ee1db3..dbda06b9a5e 100644 --- a/scripts/test-published-install-gate.py +++ b/scripts/test-published-install-gate.py @@ -20,6 +20,25 @@ 'COMPATIBILITY.md': 'Server v1.6.499 preparing; no published digest.\nPublished Server v1.6.498\n' + PUBLISHED_IMAGE, 'docs/releases/server-1.6.498.md': '# Server v1.6.498\n\nOfficially published: ' + PUBLISHED_IMAGE, } +CANDIDATE_IMAGE = 'ghcr.io/pasturestack/server:v1.6.522' +CANDIDATE_FILES = dict(FILES) +intro, quick = FILES['README.md'].split('## Quick start', 1) +CANDIDATE_FILES['README.md'] = ( + '\n\n' + + intro.replace('## Current release', '## Current source candidate') + + 'Server `v1.6.522` is a prepared source candidate, not a published image.\n' + '[candidate note](docs/releases/server-1.6.522.md)\n\n## Quick start\n\n' + 'run them only after its official release\nThis candidate is not yet available\n' + + quick.replace(INSTALL_IMAGE, CANDIDATE_IMAGE)) +CANDIDATE_FILES[PERFORMANCE] = ( + 'run it only after its official release and image have been published and read back.\n' + 'is not yet available from GHCR\n[candidate note](../releases/server-1.6.522.md)\n' + + FILES[PERFORMANCE].replace(INSTALL_IMAGE, CANDIDATE_IMAGE)) +CANDIDATE_FILES['COMPATIBILITY.md'] = ( + '## Server v1.6.522 prepared, not published\n\n' + FILES['COMPATIBILITY.md']) +CANDIDATE_FILES['docs/releases/server-1.6.522.md'] = ( + '# Server v1.6.522 candidate\n\n' + 'Prepared source only; no Server image or release has been published.\n') def verify(files): @@ -35,12 +54,52 @@ def test_checked_out_install_documents_match_actual_latest_publication(self): tags = set(re.findall(r'https://github\.com/PastureStack/server/releases/tag/(v[0-9]+\.[0-9]+\.[0-9]+)', readme)) files = {'README.md': readme, 'COMPATIBILITY.md': (REPO / 'COMPATIBILITY.md').read_text(encoding='utf-8'), PERFORMANCE: (REPO / PERFORMANCE).read_text(encoding='utf-8')} - for tag in tags: - notes = REPO / ('docs/releases/server-' + tag[1:] + '.md') + note_paths = {'docs/releases/server-' + tag[1:] + '.md' for tag in tags} + note_paths.update(re.findall(r'\((docs/releases/server-[0-9.]+\.md)\)', readme)) + for name in note_paths: + notes = REPO / name if notes.is_file(): - files['docs/releases/server-' + tag[1:] + '.md'] = notes.read_text(encoding='utf-8') + files[name] = notes.read_text(encoding='utf-8') verify(files) + def test_explicit_522_candidate_keeps_publication_evidence_and_numeric_examples(self): + verify(CANDIDATE_FILES) + files = dict(CANDIDATE_FILES) + files['COMPATIBILITY.md'] = files['COMPATIBILITY.md'].replace(PUBLISHED_IMAGE, '') + with self.assertRaisesRegex(SystemExit, 'IDENTITY_MISSING'): + verify(files) + + def test_candidate_cannot_hide_warning_mismatch_or_fabricate_publication(self): + for name, old, new, error in ( + ('README.md', '', '', 'DISCLOSURE_MISSING'), + ('README.md', 'run them only after its official release', '', 'DISCLOSURE_MISSING'), + (PERFORMANCE, 'is not yet available from GHCR', '', 'DISCLOSURE_MISSING'), + (PERFORMANCE, CANDIDATE_IMAGE, INSTALL_IMAGE, 'PERFORMANCE_MISMATCH'), + ('docs/releases/server-1.6.522.md', '# Server v1.6.522 candidate', '# Server v1.6.522', 'NOTES_MISMATCH'), + ('README.md', 'Server `v1.6.522` is a prepared source candidate, not a published image.', + 'Server `v1.6.522` is a prepared source candidate, not a published image.\n' + + CANDIDATE_IMAGE + '@sha256:' + 'a' * 64, 'PUBLICATION_MISMATCH'), + ('COMPATIBILITY.md', '## Server v1.6.522 prepared, not published', + '## Server v1.6.522 published\n' + CANDIDATE_IMAGE + '@sha256:' + 'a' * 64, + 'PUBLICATION_MISMATCH')): + with self.subTest(name=name, old=old): + files = dict(CANDIDATE_FILES) + files[name] = files[name].replace(old, new) + with self.assertRaisesRegex(SystemExit, error): + verify(files) + + def test_actual_522_publication_needs_correct_docs_not_a_gate_code_change(self): + files = dict(FILES) + files['README.md'] = files['README.md'].replace('v1.6.498', 'v1.6.522').replace('server-1.6.498.md', 'server-1.6.522.md') + files[PERFORMANCE] = files[PERFORMANCE].replace('v1.6.498', 'v1.6.522') + image = CANDIDATE_IMAGE + '@sha256:' + 'a' * 64 + files['COMPATIBILITY.md'] += '\n' + image + files['docs/releases/server-1.6.522.md'] = '# Server v1.6.522\n\nOfficially published: ' + image + verify(files) + files['docs/releases/server-1.6.522.md'] = CANDIDATE_FILES['docs/releases/server-1.6.522.md'] + with self.assertRaisesRegex(SystemExit, 'QUICK_START_MISMATCH'): + verify(files) + def test_current_release_and_quick_start_need_no_readme_history(self): self.assertNotRegex(FILES['README.md'], r'(?m)^## v[0-9]') verify(FILES) diff --git a/scripts/test-registration-basic-api-unit.py b/scripts/test-registration-basic-api-unit.py new file mode 100644 index 00000000000..35c2672903a --- /dev/null +++ b/scripts/test-registration-basic-api-unit.py @@ -0,0 +1,77 @@ +"""Focused pure guard/integration checks; no Server or old smoke execution.""" +import contextlib +import importlib.util +import io +from pathlib import Path +import unittest +from unittest.mock import Mock, patch + +SCRIPT = Path(__file__).with_name('test-registration-basic-api.py') +spec = importlib.util.spec_from_file_location('registration_tested', SCRIPT) +r = importlib.util.module_from_spec(spec); spec.loader.exec_module(r) + + +class RegistrationSmokeTests(unittest.TestCase): + def test_creatable_string_kind_without_options_is_native_legal(self): + self.assertTrue(r.ordinary_kind_creatable({'create': True, 'type': 'string'})) + self.assertFalse(r.ordinary_kind_creatable({'create': False, 'type': 'string'})) + self.assertFalse(r.ordinary_kind_creatable({'create': True, 'type': 'unknown'})) + + def test_enum_or_explicit_options_must_allow_user(self): + for field in ({'create': True, 'type': 'enum'}, + {'create': True, 'type': 'enum', 'options': []}, + {'create': True, 'type': 'enum', 'options': ['admin', 'service']}, + {'create': True, 'type': 'string', 'options': ['admin']}, + {'create': True, 'type': 'string', 'options': 'user'}): + with self.subTest(field=field): + self.assertFalse(r.ordinary_kind_creatable(field)) + for kind in ('string', 'enum'): + self.assertTrue(r.ordinary_kind_creatable({'create': True, 'type': kind, 'options': ['user', 'admin']})) + + def test_schema_link_is_native_and_transport_cannot_target_external_or_existing_port(self): + path = '/v2-beta/projects/1a8/registrationtokens' + schema = {'id': 'registrationToken', 'collectionMethods': ['GET', 'POST'], + 'links': {'collection': r.ORIGIN + path}} + self.assertEqual(r.collection(schema, 'registrationToken', path), path) + self.assertNotIn('_collection', schema) + for link in ('https://company.example' + path, r.ORIGIN + path + '?secret=x'): + with self.assertRaises(r.SmokeFailure): r.native_path(link, path) + for port in (8080, 18193, 18194): + with patch.object(r.http.client, 'HTTPConnection', side_effect=AssertionError('network')): + with self.assertRaisesRegex(r.SmokeFailure, 'LOOPBACK_PORT'): r.Client(port) + client = r.Client(r.PORT) + client.request = Mock(side_effect=[(404, b'{"code":"Not Found"}', (None, None)), + (200, b'{"id":"1a8","state":"active"}', ('1a7', '1a7'))]) + with patch.object(r.time, 'sleep'): + self.assertEqual(client.active('/v2-beta/projects/1a8', 'Basic RAM', project=True)['id'], '1a8') + client.request = Mock(return_value=(403, b'{"code":"Forbidden"}', (None, None))) + with self.assertRaisesRegex(r.SmokeFailure, 'ACTIVE_HTTP_STATUS'): + client.active('/v2-beta/projects/1a8', 'Basic RAM', project=True) + + def test_native_post_is_once_even_if_first_response_fails(self): + client = r.Client(r.PORT) + client.request = Mock(return_value=(503, b'{}', (None, None))) + for _ in range(2): + with self.assertRaises(r.SmokeFailure): + client.post('/v2-beta/accounts', {'kind': 'user', 'name': 'fresh'}, 'Bearer RAM') + self.assertEqual(client.request.call_count, 1) + + def test_failure_cannot_echo_secret_and_workflow_reuses_existing_bootstrap(self): + secret = 'RAM-secret-marker-1234567890' + output = io.StringIO() + with patch.object(r.Client, 'request', side_effect=RuntimeError(secret)), contextlib.redirect_stdout(output): + with self.assertRaises(r.SmokeFailure) as failure: + r.run(port=r.PORT, administrator_jwt=secret, administrator_account_id='1a1', disposable=True) + self.assertNotIn(secret, str(failure.exception)); self.assertNotIn(secret, output.getvalue()) + mfa = SCRIPT.with_name('test-mfa-policy-api.py').read_text() + self.assertLess(mfa.index("check('authenticated-administrator'"), mfa.index('registration.run(')) + self.assertLess(mfa.index('registration.run('), mfa.index('recovery_codes = enroll()')) + self.assertNotIn('/localauthconfigs', SCRIPT.read_text()) + workflow = SCRIPT.parents[1].joinpath('.github/workflows/publish-current-server.yml').read_text() + self.assertIn('--port 18082 --disposable --registration-basic', workflow) + self.assertEqual(workflow.count('python3 source/scripts/test-mfa-policy-api.py'), 1) + self.assertEqual(workflow.count('registration-basic-api-smoke.txt'), 3) + + +if __name__ == '__main__': + unittest.main() diff --git a/scripts/test-registration-basic-api.py b/scripts/test-registration-basic-api.py new file mode 100644 index 00000000000..2fdc8e002e7 --- /dev/null +++ b/scripts/test-registration-basic-api.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +"""Focused native registration entry smoke sharing the disposable MFA bootstrap. + +Call run() with its already-issued administrator session, before MFA enrollment. +No auth bootstrap, SQL, agent launch, redirects, secret files or response logs. +The publication workflow owns the candidate container and all three volumes. +""" +import base64 +import hashlib +import http.client +import json +import re +import secrets +import time +from urllib.parse import urlsplit + +PORT = 18082 +ORIGIN = 'https://stack.example.test' +ORIGINS = frozenset((ORIGIN, 'http://127.0.0.1:18082')) +BOUND = 2 * 1024 * 1024 + + +class SmokeFailure(RuntimeError): + pass + + +def require(condition, code): + if not condition: + raise SmokeFailure('REGISTRATION_BASIC_API_' + code) + + +def native_path(link, expected): + require(isinstance(link, str) and len(link) <= 8192, 'NATIVE_LINK') + parsed = urlsplit(link) + authority = parsed.scheme + '://' + parsed.netloc + require((not parsed.scheme and not parsed.netloc or authority in ORIGINS) + and not parsed.query and not parsed.fragment and '\\' not in link + and '..' not in parsed.path and parsed.path == expected, 'OWNED_NATIVE_LINK') + return parsed.path + + +def collection(schema, kind, path): + require(schema.get('id') == kind and 'POST' in schema.get('collectionMethods', []), 'NATIVE_POST_SCHEMA') + return native_path(schema.get('links', {}).get('collection'), path) + + +def ordinary_kind_creatable(field): + if not isinstance(field, dict) or field.get('create') is not True: + return False + options = field.get('options') + if options is None: + return field.get('type') == 'string' + return field.get('type') in ('string', 'enum') and isinstance(options, list) and 'user' in options + + +def private_absent(raw, value, private): + require(not any(secret.encode() in raw for secret in private if secret), 'PRIVATE_RESPONSE') + def walk(node): + if isinstance(node, str): + require(not any(secret in node for secret in private if secret), 'PRIVATE_RESPONSE') + elif isinstance(node, dict): + for key, item in node.items(): walk(key); walk(item) + elif isinstance(node, list): + for item in node: walk(item) + walk(value) + + +class Client: + def __init__(self, port): + require(type(port) is int and port == PORT, 'DISPOSABLE_LOOPBACK_PORT') + self.port, self.private, self.used_posts = port, [], set() + self.post_paths = {'/v2-beta/accounts', '/v2-beta/apikeys', '/v2-beta/projects'} + + def request(self, method, path, data=None, auth=None, cookie=None): + parsed = urlsplit(path) + require(method in ('GET', 'POST') and path.startswith(('/v1/', '/v2-beta/')) + and not parsed.scheme and not parsed.netloc and not parsed.query and not parsed.fragment + and '..' not in path and '\\' not in path, 'OWNED_REQUEST') + headers = {'Accept': 'application/json', 'Content-Type': 'application/json', + 'Host': urlsplit(ORIGIN).netloc, 'Origin': ORIGIN} + if auth: headers['Authorization'] = auth + if cookie: headers['Cookie'] = cookie + connection = http.client.HTTPConnection('127.0.0.1', port=self.port, timeout=20) + try: + connection.request(method, path, headers=headers, + body=None if data is None else json.dumps(data).encode()) + response = connection.getresponse() + raw = response.read(BOUND + 1); require(len(raw) <= BOUND, 'RESPONSE_BOUND') + principal = (response.getheader('X-API-USER-ID'), response.getheader('X-API-ACCOUNT-ID')) + return response.status, raw, principal + finally: + connection.close() + + def json(self, method, path, data=None, auth=None, cookie=None, expected=200): + status, raw, principal = self.request(method, path, data, auth, cookie) + require(status == expected, 'HTTP_STATUS') + value = json.loads(raw); require(isinstance(value, dict), 'JSON_OBJECT') + return value, raw, principal + + def post(self, path, data, auth): + require(path in self.post_paths and path not in self.used_posts, 'NATIVE_POST_ONCE') + self.used_posts.add(path) # A failed request is never retried as a POST. + return self.json('POST', path, data, auth, expected=201)[0] + + def active(self, path, auth, project=False): + require(not project or re.fullmatch(r'/v2-beta/projects/1a[1-9][0-9]*', path), 'PROJECT_ACTIVE_PATH') + deadline = time.monotonic() + 60 + while True: + status, raw, _ = self.request('GET', path, auth=auth) + value = json.loads(raw); require(isinstance(value, dict), 'JSON_OBJECT') + # The ordinary creator's native membership must become visible; + # only this exact new-project Not Found can remain a waiting state. + waiting = project and status == 404 and value.get('code') == 'Not Found' + require(status == 200 or waiting, 'ACTIVE_HTTP_STATUS') + if status == 200 and value.get('state') == 'active': + require(not project or value.get('id') == path.rsplit('/', 1)[-1], 'PROJECT_ACTIVE_ID') + return value + require(time.monotonic() < deadline, 'NATIVE_ACTIVE_TIMEOUT') + time.sleep(.2) + + +def run(*, port, administrator_jwt, administrator_account_id, disposable): + require(disposable is True, 'DISPOSABLE_REQUIRED') + require(isinstance(administrator_jwt, str) and len(administrator_jwt) >= 20 + and '\n' not in administrator_jwt and '\r' not in administrator_jwt, 'SHARED_ADMIN_SESSION') + require(re.fullmatch(r'1a[1-9][0-9]*', administrator_account_id or ''), 'SHARED_ADMIN_ACCOUNT') + client = Client(port) + admin = 'Bearer ' + administrator_jwt + client.private.extend((administrator_jwt, admin)) + try: + schemas = {kind: client.json('GET', '/v2-beta/schemas/' + kind, auth=admin)[0] + for kind in ('account', 'apiKey', 'project')} + account_path = collection(schemas['account'], 'account', '/v2-beta/accounts') + key_path = collection(schemas['apiKey'], 'apiKey', '/v2-beta/apikeys') + project_path = collection(schemas['project'], 'project', '/v2-beta/projects') + kind_field = schemas['account'].get('resourceFields', {}).get('kind', {}) + require(ordinary_kind_creatable(kind_field) + and schemas['apiKey']['resourceFields']['accountId'].get('create') is True, 'ORDINARY_USER_SCHEMA') + name = 'registration-smoke-' + secrets.token_hex(8) + actor = client.post(account_path, {'kind': 'user', 'name': name}, admin) + actor_id = actor.get('id') + require(re.fullmatch(r'1a[1-9][0-9]*', actor_id or '') and actor_id != administrator_account_id + and actor.get('kind') == 'user' and actor.get('name') == name, 'ORDINARY_USER') + actor = client.active(account_path + '/' + actor_id, admin) + require(actor.get('kind') == 'user', 'ORDINARY_USER') + # Missing policy is a current native legacy/full control, not an upgrade claim. + key = client.post(key_path, {'type': 'apiKey', 'name': name + '-key', 'accountId': actor_id}, admin) + require(key.get('accountId') == actor_id and isinstance(key.get('publicValue'), str) + and isinstance(key.get('secretValue'), str) and len(key['secretValue']) >= 16, 'NATIVE_OWNER_KEY') + basic = 'Basic ' + base64.b64encode((key['publicValue'] + ':' + key['secretValue']).encode()).decode() + client.private.extend((key['publicValue'], key['secretValue'], basic)) + key_id = key.get('id'); require(re.fullmatch(r'1c[1-9][0-9]*', key_id or ''), 'KEY_ID') + self_path = urlsplit(key.get('links', {}).get('self', '')).path + require(self_path in (key_path + '/' + key_id, '/v2-beta/apikeyrestricteds/' + key_id), 'KEY_SELF') + native_path(key['links']['self'], self_path) + ready = client.active(self_path, admin) + require(ready.get('accountId') == actor_id and type(ready.get('apiKeyPolicyRevision')) is int + and ready['apiKeyPolicyRevision'] == 0, 'LEGACY_KEY_OWNER') + own, raw, principal = client.json('GET', account_path + '/' + actor_id, auth=basic) + private_absent(raw, own, client.private) + require(own.get('kind') == 'user' and principal == (actor_id, actor_id), 'ORDINARY_BASIC_PRINCIPAL') + project = client.post(project_path, {'name': name + '-project'}, basic) + project_id = project.get('id'); require(re.fullmatch(r'1a[1-9][0-9]*', project_id or '') + and project_id not in (actor_id, administrator_account_id) + and project.get('kind') == 'project' + and project.get('name') == name + '-project', 'NATIVE_PROJECT') + client.active(project_path + '/' + project_id, basic, project=True) + own, raw, principal = client.json('GET', '/v1/projects/' + project_id, auth=basic) + private_absent(raw, own, client.private) + require(own.get('id') == project_id and principal == (actor_id, actor_id), 'LEGACY_BASIC_CONTROL') + prefix = '/v2-beta/projects/' + project_id + schema = client.json('GET', prefix + '/schemas/registrationToken', auth=basic)[0] + token_path = collection(schema, 'registrationToken', prefix + '/registrationtokens') + client.post_paths.add(token_path) + token = client.post(token_path, {'type': 'registrationToken'}, basic) + token_id = token.get('id'); require(re.fullmatch(r'1c[1-9][0-9]*', token_id or ''), 'TOKEN_ID') + token = client.active(token_path + '/' + token_id, basic) + secret = token.get('token') + require(token.get('accountId') == project_id and isinstance(secret, str) + and len(secret.split(':')) == 3, 'NATIVE_REGISTRATION_TOKEN') + url = token.get('registrationUrl'); require(isinstance(url, str), 'NATIVE_SCRIPT_URL') + script = urlsplit(url).path + require(script.startswith(prefix + '/scripts/') and len(script.rsplit('/', 1)[-1]) > 20, 'PROJECT_SCRIPT') + native_path(url, script) + registration = 'Basic ' + base64.b64encode(('registrationToken:' + secret).encode()).decode() + wrong = secret[:-1] + ('A' if secret[-1] != 'A' else 'B') + invalid = 'Basic ' + base64.b64encode(('registrationToken:' + wrong).encode()).decode() + cookie = 'token=' + administrator_jwt + client.private.extend((secret, url, script, registration, wrong, invalid, cookie)) + status, body, _ = client.request('GET', script) + require(status == 200 and body.startswith(b'#!') and secret.encode() in body + and b'registrationToken' in body, 'GENERATED_SCRIPT') + script_hash = hashlib.sha256(body).hexdigest() + value, raw, principal = client.json('GET', '/v1/register', auth=registration) + private_absent(raw, value, client.private) + require(value.get('type') == 'collection' and value.get('resourceType') == 'register' + and isinstance(value.get('data'), list) and principal == (project_id, project_id), 'NATIVE_REGISTER_GET') + value, raw, principal = client.json('GET', '/v1/schemas/register', auth=registration) + private_absent(raw, value, client.private) + require(value.get('id') == 'register' and 'GET' in value.get('collectionMethods', []) + and principal == (project_id, project_id), 'RESTRICTED_REGISTER_SCHEMA') + value, raw, principal = client.json('GET', '/v1/accounts', auth=registration, expected=404) + private_absent(raw, value, client.private) + require(value.get('code') == 'Not Found' and principal == (project_id, project_id), 'REGISTRATION_CANNOT_ACCOUNTS') + value, raw, principal = client.json('GET', '/v2-beta/projects/' + project_id, cookie=cookie) + private_absent(raw, value, client.private) + require(value.get('id') == project_id and principal == (administrator_account_id, administrator_account_id), 'VALID_COOKIE_CONTROL') + value, raw, principal = client.json('GET', '/v1/register', auth=invalid, cookie=cookie, expected=401) + private_absent(raw, value, client.private) + require(value.get('code') == 'Unauthorized' and principal == (None, None), 'INVALID_TOKEN_NO_COOKIE_FALLTHROUGH') + print('REGISTRATION_BASIC_API_OK checks=7 ordinary_user=true entry_only=true node_active_verified=false script_sha256=' + script_hash, + flush=True) + return {'checks': 7, 'ordinaryUser': True, 'nodeActiveVerified': False, 'scriptSha256': script_hash} + except SmokeFailure: + raise + except Exception: + raise SmokeFailure('REGISTRATION_BASIC_API_FAILURE') from None + finally: + client.private.clear() + + +if __name__ == '__main__': + print('PLAN_ONLY: shared disposable test-mfa-policy-api.py --registration-basic bootstrap required; no runtime executed.') diff --git a/server/Dockerfile.web-compose-release b/server/Dockerfile.web-compose-release index 04fa467dd0f..4cb470da1dd 100644 --- a/server/Dockerfile.web-compose-release +++ b/server/Dockerfile.web-compose-release @@ -55,9 +55,9 @@ ARG NODE_AGENT_BINARY_SHA256=ce8342e10da07c50cc414b471cadc67db4dd390e6e2dcb632b3 ARG NODE_AGENT_APPLY_SHA256=dd8cb342518a43e7468cc121db5c4e44b16731f2620b77ea0fd03c02c03766a9 ARG WEB_CONSOLE_RELEASE_BASE_URL=https://github.com/PastureStack/web-console/releases/download -ARG WEB_CONSOLE_RELEASE_TAG=1.6.181 -ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.181.tar.gz -ARG WEB_CONSOLE_ARTIFACT_SHA256=9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03 +ARG WEB_CONSOLE_RELEASE_TAG=1.6.183 +ARG WEB_CONSOLE_ARTIFACT=web-console-1.6.183.tar.gz +ARG WEB_CONSOLE_ARTIFACT_SHA256=8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217 ARG CATALOG_SERVICE_RELEASE_BASE_URL=https://github.com/PastureStack/catalog-service/releases/download ARG CATALOG_SERVICE_VERSION=0.20.13 ARG CATALOG_SERVICE_COMMIT=4c39c73a8131ba06e9ff0aaec3b95cc27e049324 @@ -79,9 +79,9 @@ ARG WEBHOOK_AUTOMATION_SERVICE_VERSION=0.10.4 ARG WEBHOOK_AUTOMATION_SERVICE_ARCHIVE_SHA256=49c4579829a04e758045fae02a5a9fca12bb0ba3af0d5e979cf9eb97f23a88a9 ARG WEBHOOK_AUTOMATION_SERVICE_BINARY_SHA256=98c7faea665b7eb95206b8c73a6f47d644c5d2d0eae53f274f6a15faf5205744 ARG ORCHESTRATION_ENGINE_RELEASE_BASE_URL=https://github.com/PastureStack/orchestration-engine/releases/download -ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.337 +ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.339 ARG ORCHESTRATION_ENGINE_ARTIFACT=cattle.jar -ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=7e45189f2b57097efc7929c04ddf7767aebbe027d1b9f90023aa6711522c5c80 +ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d ARG COMPOSE_EXECUTOR_RELEASE_BASE_URL=https://github.com/PastureStack/compose-cli/releases/download ARG COMPOSE_EXECUTOR_VERSION=0.14.37 ARG COMPOSE_EXECUTOR_ARCHIVE_SHA256=5ff465601930218f531885a384f231d969033e4cef794ce33d16bfe24e5c1c81 @@ -565,9 +565,9 @@ FROM ${BASE_IMAGE} ARG SOURCE_DATE_EPOCH ARG PASTURESTACK_SERVER_REVISION -ARG SERVER_RELEASE_TAG=v1.6.521 -ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.337 -ARG ORCHESTRATION_ENGINE_VERSION=0.183.337 +ARG SERVER_RELEASE_TAG=v1.6.522 +ARG ORCHESTRATION_ENGINE_RELEASE_TAG=v0.183.339 +ARG ORCHESTRATION_ENGINE_VERSION=0.183.339 ARG ENGINE_READONLY_SCHEMA_SHA256=7f274219e8dd9c6d750a408a6edec1448b564f4b16ba2204c425c5b45cec2233 ARG ENGINE_RESTRICTED_SCHEMA_SHA256=f854ba99260f29e324ab7446a8592996946038920ac9bcf17689d98d1ebc5e51 ARG HOST_API_VERSION=0.38.5 @@ -585,9 +585,9 @@ ARG FREETYPE_PACKAGE_VERSION=2.14.2+dfsg-1ubuntu0.2 ARG LIBPNG_PACKAGE_VERSION=1.6.57-1ubuntu0.1 ARG GLIBC_PACKAGE_VERSION=2.43-2ubuntu2.4 ARG PERL_PACKAGE_VERSION=5.40.1-7ubuntu0.3 -ARG WEB_CONSOLE_RELEASE_TAG=1.6.181 -ARG WEB_CONSOLE_ARTIFACT_SHA256=9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03 -ARG WEB_CONSOLE_COMMIT=7f2bdad0c90a3241693c2ddd4475202ababba550 +ARG WEB_CONSOLE_RELEASE_TAG=1.6.183 +ARG WEB_CONSOLE_ARTIFACT_SHA256=8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217 +ARG WEB_CONSOLE_COMMIT=e056a0111d20816742eff17a65dfabb5d003dae2 ARG CATALOG_SERVICE_VERSION=0.20.13 ARG CATALOG_SERVICE_COMMIT=4c39c73a8131ba06e9ff0aaec3b95cc27e049324 ARG CATALOG_SERVICE_ARCHIVE_SHA256=29626181cb8489b016e5975ffaddc00b2a32d290b1c0066e833d27fa7a5edf83 @@ -606,8 +606,8 @@ ARG WEBHOOK_AUTOMATION_SERVICE_VERSION=0.10.4 ARG WEBHOOK_AUTOMATION_SERVICE_COMMIT=400118b893843d2a7d7c65cc70c3449d76c4a8d8 ARG WEBHOOK_AUTOMATION_SERVICE_ARCHIVE_SHA256=49c4579829a04e758045fae02a5a9fca12bb0ba3af0d5e979cf9eb97f23a88a9 ARG WEBHOOK_AUTOMATION_SERVICE_BINARY_SHA256=98c7faea665b7eb95206b8c73a6f47d644c5d2d0eae53f274f6a15faf5205744 -ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=7e45189f2b57097efc7929c04ddf7767aebbe027d1b9f90023aa6711522c5c80 -ARG ORCHESTRATION_ENGINE_COMMIT=ba041fe312dfe97fc1e5144af576ddcf1b573d8e +ARG ORCHESTRATION_ENGINE_ARTIFACT_SHA256=bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d +ARG ORCHESTRATION_ENGINE_COMMIT=436a37215bc5702e516ecf1feb6753b4be5bf602 ARG HOST_PROVISIONER_VERSION=0.39.8 ARG HOST_PROVISIONER_COMMIT=385e5b536c108f17fdfcdec84c01750a5be5ab1c ARG HOST_PROVISIONER_ARCHIVE_SHA256=d775f36a613b1a486a5e60d6ad61fdbd1ebb4bd22422cbb6dcb70fdd7c4abf0c diff --git a/server/build-api-explorer-patch-image.sh b/server/build-api-explorer-patch-image.sh index 3e045369e89..5f15a37c0a4 100755 --- a/server/build-api-explorer-patch-image.sh +++ b/server/build-api-explorer-patch-image.sh @@ -14,14 +14,14 @@ 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.521} +server_release_tag=${SERVER_RELEASE_TAG:-v1.6.522} 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} -orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.337} +orchestration_engine_release_tag=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.339} orchestration_engine_artifact=${ORCHESTRATION_ENGINE_ARTIFACT:-cattle.jar} -orchestration_engine_artifact_sha256=${ORCHESTRATION_ENGINE_ARTIFACT_SHA256:-7e45189f2b57097efc7929c04ddf7767aebbe027d1b9f90023aa6711522c5c80} -orchestration_engine_commit=${ORCHESTRATION_ENGINE_COMMIT:-ba041fe312dfe97fc1e5144af576ddcf1b573d8e} +orchestration_engine_artifact_sha256=${ORCHESTRATION_ENGINE_ARTIFACT_SHA256:-bce020a298ebb89ef3d14ba0666f040e4a732288d1b100da9859dcf957a7981d} +orchestration_engine_commit=${ORCHESTRATION_ENGINE_COMMIT:-436a37215bc5702e516ecf1feb6753b4be5bf602} orchestration_engine_version=${orchestration_engine_release_tag#v} engine_readonly_schema_sha256=${ENGINE_READONLY_SCHEMA_SHA256:-7f274219e8dd9c6d750a408a6edec1448b564f4b16ba2204c425c5b45cec2233} engine_restricted_schema_sha256=${ENGINE_RESTRICTED_SCHEMA_SHA256:-f854ba99260f29e324ab7446a8592996946038920ac9bcf17689d98d1ebc5e51} @@ -48,10 +48,10 @@ api_explorer_artifact=${API_EXPLORER_ARTIFACT:-api-explorer-1.1.18.tar.gz} api_explorer_artifact_sha256=${API_EXPLORER_ARTIFACT_SHA256:-92b718c46163018ea40c008ac552911f0eb610647377725405f4046dcd411f2c} api_explorer_commit=${API_EXPLORER_COMMIT:-3b1c39e8a116f58649d94233a384a0362c02b43e} web_console_release_base_url=${WEB_CONSOLE_RELEASE_BASE_URL:-https://github.com/PastureStack/web-console/releases/download} -web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.181} -web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.181.tar.gz} -web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-9199681f2c4311f3fbe8727fcf0f6fa136c9bafe9fbe9d13878c55df442a7a03} -web_console_commit=${WEB_CONSOLE_COMMIT:-7f2bdad0c90a3241693c2ddd4475202ababba550} +web_console_release_tag=${WEB_CONSOLE_RELEASE_TAG:-1.6.183} +web_console_artifact=${WEB_CONSOLE_ARTIFACT:-web-console-1.6.183.tar.gz} +web_console_artifact_sha256=${WEB_CONSOLE_ARTIFACT_SHA256:-8540758ad4521f901f53fc79473570cf98862add2addcb49271d8dbf125b4217} +web_console_commit=${WEB_CONSOLE_COMMIT:-e056a0111d20816742eff17a65dfabb5d003dae2} catalog_service_release_base_url=${CATALOG_SERVICE_RELEASE_BASE_URL:-https://github.com/PastureStack/catalog-service/releases/download} catalog_service_version=${CATALOG_SERVICE_VERSION:-0.20.13} catalog_service_commit=${CATALOG_SERVICE_COMMIT:-4c39c73a8131ba06e9ff0aaec3b95cc27e049324} @@ -101,7 +101,7 @@ usage_telemetry_agent_archive_sha256=${USAGE_TELEMETRY_AGENT_ARCHIVE_SHA256:-5ce usage_telemetry_agent_binary_sha256=${USAGE_TELEMETRY_AGENT_BINARY_SHA256:-e62a21270142181315293d7e11482288ffe8fc4d91cc8fe07dacc90b461f3709} 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.521} +image=${IMAGE:-pasturestack-validation/server:v1.6.522} build_options=() [[ "$revision" =~ ^[0-9a-f]{40}$ ]] diff --git a/server/build-component-candidate.sh b/server/build-component-candidate.sh index 0698ba770d5..92a70f3d01b 100755 --- a/server/build-component-candidate.sh +++ b/server/build-component-candidate.sh @@ -14,10 +14,10 @@ for name in "${required[@]}"; do case "$name" in *_COMMIT) [[ ${!name} =~ ^[0-9a-f]{40}$ ]] ;; *) [[ ${!name} =~ ^[0-9a-f]{64}$ ]] ;; esac export "$name" done -export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.521} -export ORCHESTRATION_ENGINE_RELEASE_TAG=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.337} +export SERVER_RELEASE_TAG=${SERVER_RELEASE_TAG:-v1.6.522} +export ORCHESTRATION_ENGINE_RELEASE_TAG=${ORCHESTRATION_ENGINE_RELEASE_TAG:-v0.183.339} export ORCHESTRATION_ENGINE_ARTIFACT=cattle.jar -export WEB_CONSOLE_RELEASE_TAG=${WEB_CONSOLE_RELEASE_TAG:-1.6.181} +export WEB_CONSOLE_RELEASE_TAG=${WEB_CONSOLE_RELEASE_TAG:-1.6.183} export WEB_CONSOLE_ARTIFACT="web-console-${WEB_CONSOLE_RELEASE_TAG}.tar.gz" export WEBSOCKET_PROXY_VERSION=${WEBSOCKET_PROXY_VERSION:-0.23.15} export HOST_API_VERSION=${HOST_API_VERSION:-0.38.5} diff --git a/server/security/openvex.json b/server/security/openvex.json index 28391fe2129..a2a56cc8064 100644 --- a/server/security/openvex.json +++ b/server/security/openvex.json @@ -1,6 +1,6 @@ { "@context": "https://openvex.dev/ns/v0.2.0", - "@id": "https://github.com/PastureStack/server/security/openvex/v1.6.521", + "@id": "https://github.com/PastureStack/server/security/openvex/v1.6.522", "author": "PastureStack Security", "timestamp": "2026-10-02T22:05:00Z", "version": 51, diff --git a/server/security/vendor-pending.json b/server/security/vendor-pending.json index 30088958895..9b9a78b0e87 100644 --- a/server/security/vendor-pending.json +++ b/server/security/vendor-pending.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "release": "v1.6.521", + "release": "v1.6.522", "generatedAt": "2026-10-10T00:00:00Z", "policy": { "allowedSeverities": [