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
137 changes: 137 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: Release

on:
push:
tags:
- 'v*.*.*'

permissions:
contents: write
packages: write
id-token: write
attestations: write

concurrency:
group: pod-pause-image-release-${{ github.ref }}
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-24.04
timeout-minutes: 60
env:
TARGET_REPOSITORY: ghcr.io/pasturestack/pod-pause-image
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
steps:
- name: Check out immutable tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false

- name: Validate release identity
shell: bash
run: |
set -euo pipefail
test "$GITHUB_REF_TYPE" = tag
[[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
test "$(git cat-file -t "refs/tags/$GITHUB_REF_NAME")" = tag
test "$(git rev-list -n 1 "$GITHUB_REF_NAME")" = "$GITHUB_SHA"
test "$(sed -n 's/^ARG IMAGE_VERSION=//p' Dockerfile)" = "$GITHUB_REF_NAME"
test -z "$(git status --porcelain)"
bash scripts/validate
git diff --check

- name: Build and smoke-test exact runtime
shell: bash
run: |
set -euo pipefail
image="$TARGET_REPOSITORY:$GITHUB_REF_NAME"
docker build --pull \
--build-arg "IMAGE_VERSION=$GITHUB_REF_NAME" \
--build-arg "SOURCE_REVISION=$GITHUB_SHA" \
--tag "$image" .
test "$(docker image inspect --format '{{.Config.User}}' "$image")" = 65532:65532
test "$(docker image inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}' "$image")" = "$GITHUB_REF_NAME"
test "$(docker image inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' "$image")" = "$GITHUB_SHA"
for signal_name in TERM INT; do
container="$(docker run --detach "$image")"
trap 'docker rm -f "$container" >/dev/null 2>&1 || true' EXIT
test "$(docker inspect --format '{{.State.Running}}' "$container")" = true
docker kill --signal "$signal_name" "$container" >/dev/null
test "$(docker wait "$container")" = 0
docker rm "$container"
trap - EXIT
done

- name: Scan runtime and generate SBOM
shell: bash
run: |
set -euo pipefail
image="$TARGET_REPOSITORY:$GITHUB_REF_NAME"
mkdir -p evidence "$RUNNER_TEMP/trivy-cache"
docker pull "$TRIVY_IMAGE" >/dev/null
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD/evidence:/evidence" \
-v "$RUNNER_TEMP/trivy-cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" image --scanners vuln,secret \
--severity CRITICAL,HIGH --exit-code 1 \
--format json --output /evidence/runtime-security.json "$image"
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD/evidence:/evidence" \
-v "$RUNNER_TEMP/trivy-cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" image --format cyclonedx \
--output /evidence/runtime.cdx.json "$image"
jq -e '.bomFormat == "CycloneDX" and (.components | length > 0)' \
evidence/runtime.cdx.json >/dev/null
sha256sum evidence/runtime-security.json evidence/runtime.cdx.json \
> evidence/SHA256SUMS

- name: Publish immutable runtime image
id: publish
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
image="$TARGET_REPOSITORY:$GITHUB_REF_NAME"
printf '%s' "$GH_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin
docker push "$image"
reference="$(docker image inspect --format '{{index .RepoDigests 0}}' "$image")"
digest="${reference#*@}"
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]]
printf 'digest=%s\n' "$digest" >> "$GITHUB_OUTPUT"

- name: Attest release evidence
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-checksums: evidence/SHA256SUMS

- name: Attest runtime image
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-name: ${{ env.TARGET_REPOSITORY }}
subject-digest: ${{ steps.publish.outputs.digest }}
push-to-registry: true

- name: Publish GitHub release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --verify-tag \
--title "Pod Pause Image $GITHUB_REF_NAME" \
--notes 'Pure numeric PastureStack pod-infrastructure image release with signal smoke tests, vulnerability scan, SBOM, checksums, and provenance.' \
evidence/runtime-security.json evidence/runtime.cdx.json evidence/SHA256SUMS

- name: Clean run-owned resources
if: always()
shell: bash
run: |
set +e
docker logout ghcr.io >/dev/null 2>&1
docker image rm -f "$TARGET_REPOSITORY:$GITHUB_REF_NAME" "$TRIVY_IMAGE" >/dev/null 2>&1
rm -rf -- evidence "$RUNNER_TEMP/trivy-cache"
3 changes: 2 additions & 1 deletion .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ jobs:
test -s evidence/manifests/builder-ubuntu-packages.tsv
test -s evidence/manifests/builder-toolchain.tsv
test -s evidence/manifests/runtime-ubuntu-packages.tsv
grep -F $'metadata\tubuntu_snapshot\t-\t20260825T000000Z' \
. ./ubuntu-apt.lock
grep -F $'metadata\tubuntu_snapshot\t-\t'"${UBUNTU_APT_LOCKED_SNAPSHOT}" \
evidence/manifests/builder-ubuntu-packages.tsv >/dev/null
grep -F $'gcc\t4:15.2.0-5ubuntu1\t15.2.0' \
evidence/manifests/builder-toolchain.tsv >/dev/null
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73
FROM ${UBUNTU_IMAGE} AS snapshot-ca-bootstrap

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

RUN set -eux; \
Expand All @@ -18,7 +18,7 @@ RUN set -eux; \

FROM ${UBUNTU_IMAGE} AS build

ARG UBUNTU_APT_SNAPSHOT=20260825T000000Z
ARG UBUNTU_APT_SNAPSHOT=20260909T000000Z

ENV DEBIAN_FRONTEND=noninteractive

Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,34 @@ PastureStack is an independent community effort to preserve, audit, and moderniz

**Origin:** This is an independent Ubuntu 26.04 compatibility implementation. No public upstream repository could be verified, so it is intentionally not represented as a GitHub fork.

## Release image
## Maintained image

The reviewed `linux/amd64` release used by the catalog is:
The reviewed `linux/amd64` runtime uses the pure numeric coordinate:

```text
ghcr.io/pasturestack/pod-pause-image:v3.0.2
```

The catalog uses this semantic version tag. Release evidence records the immutable digest separately so a long digest never appears in the user interface.
Product identity and provenance are carried by the package name, OCI labels,
SBOM, and attestations rather than a text qualifier in the tag. Existing
Catalog revisions remain immutable; maintained Catalog revisions use this
numeric successor after its release and integration gates pass.

## Build

```sh
docker build --pull \
--build-arg IMAGE_VERSION=v3.0.2 \
--build-arg SOURCE_REVISION="$(git rev-parse HEAD)" \
-t ghcr.io/pasturestack/pod-pause-image:v3.0.2 .
-t local/pasturestack/pod-pause-image:v3.0.2 .
```

The runtime base is pinned to the reviewed Ubuntu 26.04 `linux/amd64` manifest. The build stage uses the HTTPS Ubuntu snapshot and exact direct-package versions recorded in [`ubuntu-apt.lock`](ubuntu-apt.lock). The final image carries the resolved builder toolchain, builder package, and runtime package manifests under `/usr/share/pasturestack/manifests/`; CI also records builder and runtime image inspections, CycloneDX SBOMs, vulnerability reports, and signal-handling smoke tests as a 30-day review artifact. Runtime High and Critical findings are rejected. The non-shipping builder additionally rejects every High or Critical finding except Ubuntu `linux-libc-dev` kernel-header records for which the vendor has not published a fixed package; those records remain explicit review evidence and become blocking as soon as a fixed version exists.

## Smoke test

```sh
docker run -d --name pod-pause-poc ghcr.io/pasturestack/pod-pause-image:v3.0.2
docker run -d --name pod-pause-poc local/pasturestack/pod-pause-image:v3.0.2
docker inspect --format '{{.State.Running}} {{.Config.User}}' pod-pause-poc
docker stop --time 5 pod-pause-poc
docker inspect --format '{{.State.ExitCode}}' pod-pause-poc
Expand Down
8 changes: 5 additions & 3 deletions scripts/validate
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,17 @@ test -f pause.c
test -f .github/workflows/security-release-gate.yml

. ./ubuntu-apt.lock
test "$UBUNTU_APT_LOCKED_SNAPSHOT" = '20260825T000000Z'
test "$UBUNTU_APT_LOCKED_SNAPSHOT" = '20260909T000000Z'
test "$UBUNTU_APT_BUILD_ESSENTIAL_VERSION" = '12.12ubuntu2.26.04.2'
test "$UBUNTU_APT_CA_CERTIFICATES_VERSION" = '20260601~26.04.1'
test "$UBUNTU_APT_DPKG_DEV_VERSION" = '1.23.7ubuntu1'
test "$UBUNTU_APT_GPP_VERSION" = '4:15.2.0-5ubuntu1'
test "$UBUNTU_APT_GCC_VERSION" = '4:15.2.0-5ubuntu1'
test "$UBUNTU_APT_LIBC6_DEV_VERSION" = '2.43-2ubuntu2.3'
test "$UBUNTU_APT_LIBC6_DEV_VERSION" = '2.43-2ubuntu2.4'
test "$UBUNTU_APT_MAKE_VERSION" = '4.4.1-3'

grep -F 'ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:' Dockerfile >/dev/null
grep -F 'https://snapshot.ubuntu.com/ubuntu/20260825T000000Z/' Dockerfile >/dev/null
grep -F 'https://snapshot.ubuntu.com/ubuntu/20260909T000000Z/' Dockerfile >/dev/null
grep -F 'https://snapshot.ubuntu.com/ubuntu/%s' Dockerfile >/dev/null
grep -F 'build-essential="${UBUNTU_APT_BUILD_ESSENTIAL_VERSION}"' Dockerfile >/dev/null
grep -F 'gcc="${UBUNTU_APT_GCC_VERSION}"' Dockerfile >/dev/null
Expand All @@ -39,6 +39,8 @@ grep -F 'builder-toolchain.tsv' Dockerfile >/dev/null
grep -F 'runtime-ubuntu-packages.tsv' Dockerfile >/dev/null
grep -F 'rm -f /usr/bin/pebble' Dockerfile >/dev/null
grep -F 'ARG IMAGE_VERSION=v3.0.2' Dockerfile >/dev/null
grep -F '. ./ubuntu-apt.lock' .github/workflows/security-release-gate.yml >/dev/null
grep -F 'UBUNTU_APT_LOCKED_SNAPSHOT' .github/workflows/security-release-gate.yml >/dev/null
grep -F 'aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969' \
.github/workflows/security-release-gate.yml >/dev/null
grep -F 'builder-unfixed-kernel-header-findings.json' \
Expand Down
4 changes: 2 additions & 2 deletions ubuntu-apt.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
# Source indexes are the official resolute, resolute-updates, and
# resolute-security Packages files below the dated snapshot URL.

UBUNTU_APT_LOCKED_SNAPSHOT='20260825T000000Z'
UBUNTU_APT_LOCKED_SNAPSHOT='20260909T000000Z'

UBUNTU_APT_BUILD_ESSENTIAL_VERSION='12.12ubuntu2.26.04.2'
UBUNTU_APT_CA_CERTIFICATES_VERSION='20260601~26.04.1'
UBUNTU_APT_DPKG_DEV_VERSION='1.23.7ubuntu1'
UBUNTU_APT_GPP_VERSION='4:15.2.0-5ubuntu1'
UBUNTU_APT_GCC_VERSION='4:15.2.0-5ubuntu1'
UBUNTU_APT_LIBC6_DEV_VERSION='2.43-2ubuntu2.3'
UBUNTU_APT_LIBC6_DEV_VERSION='2.43-2ubuntu2.4'
UBUNTU_APT_MAKE_VERSION='4.4.1-3'