diff --git a/.github/workflows/security-build.yml b/.github/workflows/security-build.yml index 1a6a70c..a78173f 100644 --- a/.github/workflows/security-build.yml +++ b/.github/workflows/security-build.yml @@ -43,20 +43,20 @@ jobs: run: | set -euo pipefail mkdir -p dist/security-runtime - docker image inspect local/system-image-preloader:v0.3.2 > dist/security-runtime/images.json - container=$(docker create --entrypoint /bin/true local/system-image-preloader:v0.3.2) + docker image inspect local/system-image-preloader:v0.3.3 > dist/security-runtime/images.json + container=$(docker create --entrypoint /bin/true local/system-image-preloader:v0.3.3) trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT docker cp "$container:/licenses/third-party/security/." dist/security-runtime/ docker cp "$container:/usr/local/bin/docker" dist/security-runtime/docker-cli docker run --rm --network none --entrypoint sha256sum \ - local/system-image-preloader:v0.3.2 /usr/local/bin/docker \ + local/system-image-preloader:v0.3.3 /usr/local/bin/docker \ > dist/security-runtime/docker-sha256.txt python3 -B tests/docker_manifest_digest_proof.py \ > dist/security-runtime/docker-manifest-digest-proof.json rm dist/security-runtime/docker-cli docker rm "$container" >/dev/null trap - EXIT - docker save local/system-image-preloader:v0.3.2 | gzip -n > dist/security-runtime/images.tar.gz + docker save local/system-image-preloader:v0.3.3 | gzip -n > dist/security-runtime/images.tar.gz printf '%s\n' "$GITHUB_SHA" > dist/security-runtime/source-revision.txt (cd dist/security-runtime && sha256sum images.json images.tar.gz source-revision.txt > SHA256SUMS) - name: Scan the exact runtime and generate its SBOM @@ -65,15 +65,46 @@ jobs: set -euo pipefail scanner=aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 docker pull "$scanner" - for runtime in local/system-image-preloader:v0.3.2; do + cache="$RUNNER_TEMP/ubuntu-backports-trivy-cache" + mkdir -p "$cache" + docker run --rm -v "$cache:/root/.cache/trivy" "$scanner" image --download-db-only + for runtime in local/system-image-preloader:v0.3.3; do name=${runtime##*/}; name=${name%%:*} docker save "$runtime" --output "$RUNNER_TEMP/$name.tar" - docker run --rm -v "$RUNNER_TEMP/$name.tar:/image.tar:ro" -v "$PWD/dist/security-runtime:/evidence" "$scanner" image --input /image.tar --scanners vuln,secret --list-all-pkgs --format json --output "/evidence/$name-security.json" - docker run --rm -v "$RUNNER_TEMP/$name.tar:/image.tar:ro" -v "$PWD/dist/security-runtime:/evidence" "$scanner" image --input /image.tar --format cyclonedx --output "/evidence/$name-sbom.cdx.json" + docker run --rm -v "$cache:/root/.cache/trivy" -v "$RUNNER_TEMP/$name.tar:/image.tar:ro" -v "$PWD/dist/security-runtime:/evidence" "$scanner" image --skip-db-update --input /image.tar --scanners vuln,secret --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --list-all-pkgs --format json --output "/evidence/$name-security.json" + docker run --rm -v "$cache:/root/.cache/trivy" -v "$RUNNER_TEMP/$name.tar:/image.tar:ro" -v "$PWD/dist/security-runtime:/evidence" "$scanner" image --skip-db-update --input /image.tar --format cyclonedx --output "/evidence/$name-sbom.cdx.json" + mkdir -p dist/security-runtime + consumer_container=$(docker create "$runtime" /bin/true) + if docker cp "$consumer_container:/usr/share/pasturestack/security/ubuntu-backports" dist/security-runtime/ubuntu-backports; then + docker rm "$consumer_container" >/dev/null + else + docker rm "$consumer_container" >/dev/null + exit 1 + fi + (cd dist/security-runtime/ubuntu-backports && sha256sum --check SHA256SUMS) + bundle_image='ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' + mkdir -p dist/security-runtime/bundle-tools + bundle_container=$(docker create "$bundle_image" unused) + if docker cp "$bundle_container:/out/verify-consumer-evidence.py" dist/security-runtime/bundle-tools/verify-consumer-evidence.py && \ + docker cp "$bundle_container:/out/SHA256SUMS" dist/security-runtime/bundle-tools/SHA256SUMS; then + docker rm "$bundle_container" >/dev/null + else + docker rm "$bundle_container" >/dev/null + exit 1 + fi + (cd dist/security-runtime/bundle-tools && \ + test "$(grep -Ec '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS)" -eq 1 && \ + grep -E '^[0-9a-f]{64} verify-consumer-evidence\.py$' SHA256SUMS | sha256sum --check -) + python3 dist/security-runtime/bundle-tools/verify-consumer-evidence.py \ + --evidence-dir dist/security-runtime/ubuntu-backports --sbom "dist/security-runtime/$name-sbom.cdx.json" \ + --bundle-checksums dist/security-runtime/bundle-tools/SHA256SUMS \ + --expected-bundle-digest 'sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > dist/security-runtime/ubuntu-backports-validation.json + docker run --rm -v "$cache:/root/.cache/trivy" -v "$RUNNER_TEMP/$name.tar:/image.tar:ro" -v "$PWD/dist/security-runtime:/evidence" "$scanner" image --skip-db-update --input /image.tar --scanners vuln,secret --severity UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL --list-all-pkgs --vex /evidence/ubuntu-backports/openvex.json --format json --output "/evidence/$name-security-backports.json" + tar -cf dist/security-runtime/ubuntu-backports-evidence.tar -C dist/security-runtime ubuntu-backports ubuntu-backports-validation.json done python3 -B scripts/review-runtime-security.py python3 -B scripts/enforce-runtime-vulnerabilities.py --phase runtime - (cd dist/security-runtime && sha256sum -- *.json *.txt images.tar.gz > SHA256SUMS) + (cd dist/security-runtime && sha256sum -- *.json *.txt *.tar images.tar.gz > SHA256SUMS) - name: Upload verified runtime and checksums if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -94,7 +125,7 @@ jobs: packages: write env: GH_TOKEN: ${{ github.token }} - RELEASE_TAG: v0.3.2 + RELEASE_TAG: v0.3.3 steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: @@ -113,7 +144,7 @@ jobs: printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin trap 'docker logout ghcr.io >/dev/null 2>&1 || true' EXIT : > dist/security-runtime/registry-images.jsonl - local_image='local/system-image-preloader:v0.3.2' + local_image='local/system-image-preloader:v0.3.3' remote_image="ghcr.io/pasturestack/system-image-preloader:$RELEASE_TAG" if docker manifest inspect "$remote_image" >/dev/null 2>"$RUNNER_TEMP/manifest-error"; then echo "Refusing to replace an existing immutable image tag: $remote_image" >&2; exit 1 diff --git a/Dockerfile b/Dockerfile index 2684d88..622d73e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ # Modified by PastureStack contributors for independent maintenance and rebranding. ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b +FROM ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a AS ubuntu_security_backports FROM ${UBUNTU_IMAGE} AS tool-builder ARG GO_VERSION=1.27.3 @@ -182,4 +183,20 @@ LABEL org.opencontainers.image.title="system-image-preloader" \ org.opencontainers.image.version="${IMAGE_VERSION}" \ org.opencontainers.image.revision="${IMAGE_REVISION}" +# Install qualified backports only for packages already in this runtime. +# The immutable read-only build mount adds no installer or interpreter to it. +RUN --mount=from=ubuntu_security_backports,source=/out,target=/opt/pasturestack/ubuntu-security-backports,ro \ + set -eux; \ + evidence=/usr/share/pasturestack/security/ubuntu-backports; \ + mkdir -p "${evidence}"; \ + /opt/pasturestack/ubuntu-security-backports/install-existing-backports --install-in-docker-build > "${evidence}/install.json"; \ + /opt/pasturestack/ubuntu-security-backports/install-existing-backports --verify-installed > "${evidence}/verify.json"; \ + for document in manifest.json qualification.json openvex.json source-inputs.lock.json; do \ + cp "/opt/pasturestack/ubuntu-security-backports/${document}" "${evidence}/${document}"; \ + done; \ + printf '%s\n' 'ghcr.io/pasturestack/ubuntu-security-backports:v0.1.0@sha256:2742a3d429e4e14bc587f6dedc51ded4246cf90bbaedcd86c67d0885e32a6f2a' > "${evidence}/bundle-image.txt"; \ + (cd "${evidence}" && sha256sum manifest.json qualification.json openvex.json source-inputs.lock.json install.json verify.json bundle-image.txt > SHA256SUMS); \ + dpkg-query -W -f='${binary:Package}\t${Version}\n' | LC_ALL=C sort > /usr/share/pasturestack/ubuntu-security-packages.tsv; \ + rm -f /var/cache/ldconfig/aux-cache + ENTRYPOINT ["/usr/local/bin/system-image-preloader"] diff --git a/Makefile b/Makefile index 67b4048..74e32c7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Created by PastureStack contributors for repeatable validation. -VERSION ?= v0.3.2 +VERSION ?= v0.3.3 REVISION ?= $(shell git rev-parse HEAD) IMAGE_REPOSITORY ?= local/system-image-preloader IMAGE ?= $(IMAGE_REPOSITORY):$(VERSION) diff --git a/README.md b/README.md index f83939b..90d7ae4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # PastureStack System Image Preloader +Linux runtime **`v0.3.3`** integrates shared Ubuntu 26.04 security backports for already-installed packages while preserving the selected coreutils provider. + +The image retains bundle manifests, source inputs and installation receipts under `/usr/share/pasturestack/security/ubuntu-backports/`. + PastureStack System Image Preloader discovers the images required by legacy-compatible system workloads and asks the local Docker daemon to pull images that are not already present. The runtime is a Bash executable rather than a Go module. Its public package identity is the repository, executable, container image, and OCI metadata name `system-image-preloader`. @@ -10,9 +14,9 @@ PastureStack is an independent community effort to preserve, audit, and moderniz This repository is a derivative maintenance project. It preserves the upstream Git history, authorship, and Apache License 2.0 terms. See [ORIGIN.md](ORIGIN.md) for source attribution and [COMPATIBILITY.md](COMPATIBILITY.md) for the legacy contracts that intentionally retain upstream identifiers. -## Reviewed release +## Runtime and build contract -The `v0.3.2` release provides: +The maintained recipe provides: - a self-contained public build based on a digest-pinned Ubuntu image; - the `system-image-preloader` executable and image name; @@ -73,7 +77,7 @@ restores the image-presence state. It never performs a broad image prune. Use the immutable semantic tag: ```text -ghcr.io/pasturestack/system-image-preloader:v0.3.2 +ghcr.io/pasturestack/system-image-preloader:v0.3.3 ``` The deployment definition must mount `/var/run/docker.sock`, run one instance @@ -117,13 +121,13 @@ The repository remains under its existing Apache License 2.0 file. The runtime i This project is a background service with no user interface or runtime localization subsystem. It therefore does not add locale files. User-facing dashboards that manage this service must provide Traditional Chinese through their own locale framework. -## October 2026 security maintenance +## Security inputs Source-built yq and gomplate select `golang.org/x/net v0.60.0` for the October HTTP/2 fixes, with x/text 0.42.0, go-git 5.19.2 and grpc-go 1.83.2 where applicable. Their upstream feature versions stay unchanged. The official Docker CLI 29.9.2 is pinned by archive and executable SHA-256 and carries the vendor's patched Go 1.26.10 compiler; its exact source is commit `a82e528901260c8df6073311379eac65f92db204`. CI exercises these exact helpers and scans the actual final runtime. Maintained Go builds select the patched Go 1.27.3 compiler without raising the existing Go language requirement. Ubuntu build/runtime stages apply the checksum-pinned `ca-certificates 20260816ubuntu0.26.04.1` security package after their frozen APT baseline; the final trust store and installed version are verified. Release artifacts must pass the repository's existing checks before publication. -The 0.3.2 rebuild pins gomplate's affected OpenTelemetry SDK to official 1.45.0 +Gomplate selects official OpenTelemetry SDK 1.45.0 (CVE-2026-81870) and adopts the checksum-pinned official Docker CLI 29.9.2, within the existing 29.x series. The daemon and gomplate 5.1.0 are unchanged. Every scan severity is retained. Any available fix or unreviewed diff --git a/scripts/integration-smoke b/scripts/integration-smoke index a0bb7d2..c9854d5 100755 --- a/scripts/integration-smoke +++ b/scripts/integration-smoke @@ -4,7 +4,7 @@ set -Eeuo pipefail PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -IMAGE="${IMAGE:-local/system-image-preloader:v0.3.2}" +IMAGE="${IMAGE:-local/system-image-preloader:v0.3.3}" TEST_PULL_IMAGE="${TEST_PULL_IMAGE:-ghcr.io/pasturestack/container-cron:v0.6.0}" PLATFORM_VERSION="${PLATFORM_VERSION:-v1.6.281}" ACCESS_KEY="integration-access" diff --git a/scripts/review-runtime-security.py b/scripts/review-runtime-security.py index 5fb6105..48b7fe3 100644 --- a/scripts/review-runtime-security.py +++ b/scripts/review-runtime-security.py @@ -98,7 +98,7 @@ def review(report, dependencies, gomplate_info, docker_sha256, image_id): def main(): - report = json.loads((EVIDENCE / 'system-image-preloader-security.json').read_text()) + report = json.loads((EVIDENCE / 'system-image-preloader-security-backports.json').read_text()) images = json.loads((EVIDENCE / 'images.json').read_text()) if len(images) != 1 or report.get('Metadata', {}).get('ImageID') != images[0]['Id']: raise ValueError('scan does not identify the exact built image')