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
115 changes: 115 additions & 0 deletions .github/workflows/security-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
name: Security build verification

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
verify:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
GOTOOLCHAIN: local
GOMAXPROCS: 2
GOFLAGS: -p=2
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Install checksum-pinned Go compiler
shell: bash
run: |
set -euo pipefail
curl --proto '=https' --tlsv1.2 --fail --silent --show-error --location \
--output "$RUNNER_TEMP/go.tar.gz" https://go.dev/dl/go1.27.3.linux-amd64.tar.gz
printf '%s %s\n' 65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40 "$RUNNER_TEMP/go.tar.gz" | sha256sum -c -
tar -C "$RUNNER_TEMP" -xzf "$RUNNER_TEMP/go.tar.gz"
echo "$RUNNER_TEMP/go/bin" >> "$GITHUB_PATH"
- name: Validate source and build the updated runtime
shell: bash
run: |
set -euo pipefail
test "$(go env GOVERSION)" = go1.27.3
make validate test
make build smoke integration
- name: Retain the exact verified runtime
shell: bash
run: |
set -euo pipefail
mkdir -p dist/security-runtime
docker image inspect local/system-image-preloader:v0.3.1 > dist/security-runtime/images.json
docker save local/system-image-preloader:v0.3.1 | 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
shell: bash
run: |
set -euo pipefail
scanner=aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
docker pull "$scanner"
for runtime in local/system-image-preloader:v0.3.1; 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 --severity HIGH,CRITICAL --exit-code 1 --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"
done
(cd dist/security-runtime && sha256sum -- *.json images.tar.gz source-revision.txt > SHA256SUMS)
- name: Upload verified runtime and checksums
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: security-runtime-${{ github.sha }}
path: dist/security-runtime/
if-no-files-found: error
retention-days: 14

publish:
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main'
needs: verify
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: write
actions: read
packages: write
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: v0.3.1
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Publish only this run's verified immutable runtime
shell: bash
run: |
set -euo pipefail
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
test "$(git rev-parse "refs/tags/$RELEASE_TAG^{}")" = "$GITHUB_SHA"
gh run download "$GITHUB_RUN_ID" --repo "$GITHUB_REPOSITORY" --name "security-runtime-$GITHUB_SHA" --dir dist/security-runtime
test "$(cat dist/security-runtime/source-revision.txt)" = "$GITHUB_SHA"
(cd dist/security-runtime && sha256sum --check SHA256SUMS)
gzip -dc dist/security-runtime/images.tar.gz | docker load
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.1'
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
else
grep -Eq 'manifest unknown|no such manifest' "$RUNNER_TEMP/manifest-error"
fi
docker tag "$local_image" "$remote_image"
docker push "$remote_image" | tee "$RUNNER_TEMP/push-log"
pushed=$(awk '/digest: sha256:/{for(i=1;i<=NF;i++) if($i=="digest:") print $(i+1)}' "$RUNNER_TEMP/push-log")
readback=$(docker buildx imagetools inspect "$remote_image" | awk '$1=="Digest:"{print $2; exit}')
test "$pushed" = "$readback"
[[ "$readback" =~ ^sha256:[0-9a-f]{64}$ ]]
jq -n --arg image "$remote_image" --arg digest "$readback" --arg source "$GITHUB_SHA" '{image:$image,digest:$digest,source:$source}' >> dist/security-runtime/registry-images.jsonl
sha256sum dist/security-runtime/registry-images.jsonl > dist/security-runtime/registry-images.sha256
gh release create "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --verify-tag --title "$GITHUB_REPOSITORY $RELEASE_TAG" --notes "Official security rebuild from $GITHUB_SHA. The release contains the exact tested/scanned runtime archive, SBOMs, and registry digest readbacks. No deployment is included." dist/security-runtime/*
46 changes: 38 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b
FROM ${UBUNTU_IMAGE} AS tool-builder

ARG GO_VERSION=1.27.0
ARG GO_SHA256_amd64=675c26c449cbb18fc24b74650de1eabbae6e16f64326fd85a283fb3b58280685
ARG GO_VERSION=1.27.3
ARG GO_SHA256_amd64=65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40
ARG YQ_VERSION=v4.53.3
ARG YQ_GIT_COMMIT=1b9b4ac5187171d2e5e3129be0cfa827c7f9d53d
ARG GOMPLATE_VERSION=v5.1.0
Expand All @@ -15,8 +15,8 @@ ARG DOCKER_GIT_COMMIT=055a478ea9010a19d0d4674c0d0e87ade37a4223
ARG DOCKER_SOURCE_DATE_EPOCH=1776697064
ARG UBUNTU_SNAPSHOT=20260825T000000Z

ADD --checksum=sha256:6077d27c6b6f8b23590cb01ff877ed8c804a67a5442cc32b5a33da10d2bd0e90 \
https://snapshot.ubuntu.com/ubuntu/20260825T000000Z/pool/main/c/ca-certificates/ca-certificates_20260601~26.04.1_all.deb \
ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd \
https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb \
/tmp/ca-certificates.deb

ENV DEBIAN_FRONTEND=noninteractive \
Expand All @@ -28,6 +28,9 @@ ENV DEBIAN_FRONTEND=noninteractive \
GOTELEMETRY=off \
PATH=/usr/local/go/bin:${PATH}

COPY ubuntu-security-packages.lock /licenses/ubuntu-security-packages.lock
COPY scripts/install-ubuntu-security-updates /usr/local/libexec/pasturestack/install-ubuntu-security-updates
ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb /opt/pasturestack-ca-certificates.deb
RUN set -eux; \
mkdir -p /tmp/ca-bootstrap /etc/ssl/certs; \
dpkg-deb --extract /tmp/ca-certificates.deb /tmp/ca-bootstrap; \
Expand All @@ -45,6 +48,12 @@ RUN set -eux; \
libc6-dev \
make \
tar; \
dpkg -i /opt/pasturestack-ca-certificates.deb && \
update-ca-certificates --fresh && \
test "$(dpkg-query -W -f='${Version}' ca-certificates)" = '20260816ubuntu0.26.04.1' && \
test ! -e /usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_G3.crt && \
rm -f /opt/pasturestack-ca-certificates.deb && \
sh /usr/local/libexec/pasturestack/install-ubuntu-security-updates /licenses/ubuntu-security-packages.lock /etc/ssl/certs/ca-certificates.crt && \
rm -rf /var/lib/apt/lists/*

RUN set -eux; \
Expand All @@ -60,6 +69,8 @@ RUN set -eux; \
git clone --branch "${YQ_VERSION}" --depth 1 https://github.com/mikefarah/yq /src/yq; \
cd /src/yq; \
test "$(git rev-parse HEAD)" = "${YQ_GIT_COMMIT}"; \
go get golang.org/x/net@v0.60.0 golang.org/x/text@v0.42.0; \
go mod tidy; \
go build -trimpath -buildvcs=false -ldflags "-s -w -X github.com/mikefarah/yq/v4/cmd.GitCommit=${YQ_GIT_COMMIT}" -o /out/yq .; \
cp LICENSE /out/licenses/yq/LICENSE; \
/out/yq --version; \
Expand All @@ -72,16 +83,18 @@ RUN set -eux; \
test "$(git rev-parse HEAD)" = "${GOMPLATE_GIT_COMMIT}"; \
go get \
github.com/go-git/go-billy/v5@v5.9.0 \
github.com/go-git/go-git/v5@v5.19.0 \
golang.org/x/crypto@v0.52.0 \
golang.org/x/net@v0.55.0 \
google.golang.org/grpc@v1.82.1; \
github.com/go-git/go-git/v5@v5.19.2 \
golang.org/x/crypto@v0.57.0 \
golang.org/x/net@v0.60.0 \
google.golang.org/grpc@v1.83.2; \
go mod tidy; \
test "$(go list -m -f '{{.Version}}' golang.org/x/net)" = v0.60.0; \
go build -trimpath -buildvcs=false \
-ldflags "-s -w -X github.com/hairyhenderson/gomplate/v5/version.Version=${GOMPLATE_VERSION} -X github.com/hairyhenderson/gomplate/v5/version.GitCommit=${GOMPLATE_GIT_COMMIT}" \
-o /out/gomplate ./cmd/gomplate; \
cp LICENSE /out/licenses/gomplate/LICENSE; \
/out/gomplate --version; \
go version -m /out/gomplate | grep -F 'golang.org/x/net'; \
rm -rf /src/gomplate /tmp/go-build-cache /tmp/go-mod-cache

RUN set -eux; \
Expand All @@ -91,6 +104,14 @@ RUN set -eux; \
https://github.com/docker/cli /go/src/github.com/docker/cli; \
cd /go/src/github.com/docker/cli; \
test "$(git rev-parse HEAD)" = "${DOCKER_GIT_COMMIT}"; \
cp vendor.mod go.mod; \
cp vendor.sum go.sum; \
go get golang.org/x/net@v0.60.0 \
github.com/klauspost/compress@v1.18.7 \
github.com/moby/go-archive@v0.3.0 \
google.golang.org/grpc@v1.83.2; \
go mod vendor; \
test "$(go list -m -f '{{.Version}}' golang.org/x/net)" = v0.60.0; \
CGO_ENABLED=0 \
GOPATH=/go \
GO_STRIP=1 \
Expand All @@ -114,6 +135,9 @@ COPY --from=tool-builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-ce
ENV DEBIAN_FRONTEND=noninteractive \
SYSTEM_IMAGE_PRELOADER_VERSION=${IMAGE_VERSION}

COPY ubuntu-security-packages.lock /licenses/ubuntu-security-packages.lock
COPY scripts/install-ubuntu-security-updates /usr/local/libexec/pasturestack/install-ubuntu-security-updates
ADD --checksum=sha256:5b6326f1932cfd7be57130561dc78192f09ed9f58bfbe6606ed0c50510c3b6cd https://archive.ubuntu.com/ubuntu/pool/main/c/ca-certificates/ca-certificates_20260816ubuntu0.26.04.1_all.deb /opt/pasturestack-ca-certificates.deb
RUN set -eux; \
rm -f /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; \
printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/%s\nSuites: resolute resolute-updates resolute-backports resolute-security\nComponents: main universe restricted multiverse\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' "${UBUNTU_SNAPSHOT}" > /etc/apt/sources.list.d/pasturestack-snapshot.sources; \
Expand All @@ -130,6 +154,12 @@ RUN set -eux; \
procps \
sed; \
rm -f /usr/bin/pebble; \
dpkg -i /opt/pasturestack-ca-certificates.deb && \
update-ca-certificates --fresh && \
test "$(dpkg-query -W -f='${Version}' ca-certificates)" = '20260816ubuntu0.26.04.1' && \
test ! -e /usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_G3.crt && \
rm -f /opt/pasturestack-ca-certificates.deb && \
sh /usr/local/libexec/pasturestack/install-ubuntu-security-updates /licenses/ubuntu-security-packages.lock /etc/ssl/certs/ca-certificates.crt && \
rm -rf /var/lib/apt/lists/*

COPY --from=tool-builder /out/yq /usr/local/bin/yq
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Created by PastureStack contributors for repeatable validation.

VERSION ?= v0.3.0
VERSION ?= v0.3.1
REVISION ?= $(shell git rev-parse HEAD)
IMAGE_REPOSITORY ?= local/system-image-preloader
IMAGE ?= $(IMAGE_REPOSITORY):$(VERSION)
Expand Down
2 changes: 1 addition & 1 deletion ORIGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The container build includes separately maintained tools:
- `yq` `v4.53.3`, built from its verified upstream commit; and
- `gomplate` `v5.1.0`, built from its verified upstream commit.

The source-built Go tools use Go `1.27.0`. The `gomplate` build updates
The source-built Go tools use Go `1.27.3`. The `gomplate` build updates
dependencies with published security fixes while retaining the verified
`v5.1.0` application source.

Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This repository is a derivative maintenance project. It preserves the upstream G

## Reviewed release

The `v0.3.0` release provides:
The `v0.3.1` release provides:

- a self-contained public build based on a digest-pinned Ubuntu image;
- the `system-image-preloader` executable and image name;
Expand Down Expand Up @@ -72,7 +72,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.0
ghcr.io/pasturestack/system-image-preloader:v0.3.1
```

The deployment definition must mount `/var/run/docker.sock`, run one instance
Expand Down Expand Up @@ -115,3 +115,9 @@ The repository remains under its existing Apache License 2.0 file. The runtime i
## Localization

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

The source-built yq, gomplate, and Docker CLI helpers select `golang.org/x/net v0.60.0` for the October HTTP/2 fixes. Their scanned runtime module graph also pins official security fixes: x/text 0.42.0, go-git 5.19.2, grpc-go 1.83.2, klauspost/compress 1.18.7, and moby/go-archive 0.3.0. Upstream helper feature versions stay unchanged; CI exercises the rebuilt 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.
51 changes: 51 additions & 0 deletions scripts/install-ubuntu-security-updates
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/bin/sh
# Same-series official micro-updates. Never install a package just for a scan.
set -eu
test "$#" -eq 2 || { echo 'usage: security-updates LOCK TRUSTED_CA_BUNDLE' >&2; exit 2; }
lock=$1
trusted_ca=$2
. /etc/os-release
test "$ID:$VERSION_ID" = ubuntu:26.04
test -s "$lock"
test -s "$trusted_ca"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' EXIT HUP INT TERM
dpkg-query -W -f='${binary:Package}\t${db:Status-Status}\n' | LC_ALL=C sort > "$tmp/before"
set --
while IFS= read -r entry || test -n "$entry"; do
case "$entry" in ''|'#'*) continue;; esac
case "$entry" in *[!a-zA-Z0-9.:+~=_-]*) echo "Invalid lock entry" >&2; exit 1;; esac
package=${entry%%=*}
version=${entry#*=}
test "$package" != "$version"
status=$(dpkg-query -W -f='${db:Status-Status}' "$package" 2>/dev/null || true)
test "$status" = installed || continue
installed=$(dpkg-query -W -f='${Version}' "$package")
# A newer base may already contain the fix; never downgrade it.
dpkg --compare-versions "$installed" ge "$version" && continue
set -- "$@" "$entry"
done < "$lock"
if test "$#" -gt 0; then
printf 'Types: deb\nURIs: https://snapshot.ubuntu.com/ubuntu/20261023T000000Z\nSuites: resolute-updates resolute-security\nComponents: main\nSigned-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg\nSnapshot: no\n' > "$tmp/security.sources"
apt_security() {
apt-get -o Dir::Etc::sourcelist="$tmp/security.sources" \
-o Dir::Etc::sourceparts=- -o Acquire::https::CaInfo="$trusted_ca" \
-o Acquire::https::Verify-Peer=true -o Acquire::https::Verify-Host=true \
-o Acquire::AllowInsecureRepositories=false -o APT::Get::AllowUnauthenticated=false \
-o Acquire::Retries=5 "$@"
}
apt_security update
DEBIAN_FRONTEND=noninteractive apt_security install -y --only-upgrade --no-install-recommends --no-remove "$@"
fi
while IFS= read -r entry || test -n "$entry"; do
case "$entry" in ''|'#'*) continue;; esac
package=${entry%%=*}
version=${entry#*=}
status=$(dpkg-query -W -f='${db:Status-Status}' "$package" 2>/dev/null || true)
test "$status" = installed || continue
dpkg --compare-versions "$(dpkg-query -W -f='${Version}' "$package")" ge "$version"
done < "$lock"
dpkg-query -W -f='${binary:Package}\t${db:Status-Status}\n' | LC_ALL=C sort > "$tmp/after"
# A security micro-update must not add/remove packages or change installed status.
cmp "$tmp/before" "$tmp/after"
printf 'UBUNTU_SECURITY_MICRO_UPDATES_OK\n'
2 changes: 1 addition & 1 deletion scripts/integration-smoke
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
set -Eeuo pipefail

PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
IMAGE="${IMAGE:-local/system-image-preloader:v0.3.0}"
IMAGE="${IMAGE:-local/system-image-preloader:v0.3.1}"
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"
Expand Down
32 changes: 32 additions & 0 deletions ubuntu-security-packages.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Official Ubuntu 26.04 security/updates snapshot: 20261023T000000Z
# Existing packages only; ABI series and unrelated packages are preserved.
bsdutils=1:2.41.3-3ubuntu2.2
curl=8.18.0-1ubuntu2.8
diffutils=1:3.12-1ubuntu0.1
gnu-coreutils=9.7-3ubuntu2.1
gpgv=2.4.8-4ubuntu3.1
libattr1=1:2.5.2-4ubuntu0.1
libblkid1=2.41.3-3ubuntu2.2
libbz2-1.0=1.0.8-6ubuntu0.1
libc-bin=2.43-2ubuntu2.4
libc-gconv-modules-extra=2.43-2ubuntu2.4
libc6=2.43-2ubuntu2.4
libc6-dev=2.43-2ubuntu2.4
libcurl4t64=8.18.0-1ubuntu2.8
libgcrypt20=1.12.0-2ubuntu1.1
libmount1=2.41.3-3ubuntu2.2
libperl5.40=5.40.1-7ubuntu0.3
libsmartcols1=2.41.3-3ubuntu2.2
libssh2-1t64=1.11.1-1ubuntu0.26.04.4
libssl3t64=3.5.5-1ubuntu3.7
libuuid1=2.41.3-3ubuntu2.2
linux-libc-dev=7.0.0-38.38
login=1:4.16.0-2+really2.41.3-3ubuntu2.2
mount=2.41.3-3ubuntu2.2
openssl=3.5.5-1ubuntu3.7
openssl-provider-legacy=3.5.5-1ubuntu3.7
perl=5.40.1-7ubuntu0.3
perl-base=5.40.1-7ubuntu0.3
perl-modules-5.40=5.40.1-7ubuntu0.3
util-linux=2.41.3-3ubuntu2.2
zlib1g=1:1.3.dfsg+really1.3.1-1ubuntu3.2
Loading