From 0290bc6bfd0f8640ba9d9c072911610b6487d99e Mon Sep 17 00:00:00 2001 From: chen21019 Date: Wed, 9 Sep 2026 18:52:32 +0800 Subject: [PATCH 1/2] Publish numeric data helper release pipeline --- .github/workflows/release.yml | 135 ++++++++++++++++++++ .github/workflows/security-release-gate.yml | 110 ++++++++++++++++ COMPATIBILITY.md | 2 +- Dockerfile | 2 +- ORIGIN.md | 7 +- README.md | 14 +- scripts/validate | 34 +++++ 7 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/security-release-gate.yml create mode 100644 scripts/validate diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d110e30 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,135 @@ +name: Release + +on: + push: + tags: + - 'v*.*.*' + +permissions: + contents: write + packages: write + id-token: write + attestations: write + +concurrency: + group: kubernetes-data-helper-release-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-24.04 + timeout-minutes: 60 + env: + TARGET_REPOSITORY: ghcr.io/pasturestack/kubernetes-data-helper-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 '{{json .Config.Entrypoint}}' "$image")" = '["/bin/true"]' + 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" + volume="pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + docker volume create "$volume" >/dev/null + trap 'docker volume rm -f "$volume" >/dev/null 2>&1 || true' EXIT + docker run --rm --network none --read-only --cap-drop ALL \ + --security-opt no-new-privileges:true --volume "$volume:/data" "$image" + docker volume inspect "$volume" >/dev/null + docker volume rm "$volume" >/dev/null + trap - EXIT + + - 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" + docker run --rm --entrypoint /usr/bin/dpkg-query "$image" \ + -W '-f=${binary:Package}\t${Version}\n' | LC_ALL=C sort \ + > evidence/runtime-ubuntu-packages.tsv + jq -e '.bomFormat == "CycloneDX" and (.components | length > 0)' evidence/runtime.cdx.json >/dev/null + sha256sum evidence/runtime-security.json evidence/runtime.cdx.json \ + evidence/runtime-ubuntu-packages.tsv | LC_ALL=C sort -k2 > evidence/SHA256SUMS + + - name: Publish immutable runtime image + id: publish + shell: bash + env: + GHCR_TOKEN: ${{ secrets.GHCR_PUBLISH_TOKEN || github.token }} + run: | + set -euo pipefail + image="$TARGET_REPOSITORY:$GITHUB_REF_NAME" + printf '%s' "$GHCR_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 "Kubernetes Data Helper $GITHUB_REF_NAME" \ + --notes 'Pure numeric one-shot data-volume helper release with isolated runtime smoke test, vulnerability scan, SBOM, package inventory, checksums, and provenance.' \ + evidence/runtime-security.json evidence/runtime.cdx.json \ + evidence/runtime-ubuntu-packages.tsv 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 + docker volume rm -f "pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >/dev/null 2>&1 + rm -rf -- evidence "$RUNNER_TEMP/trivy-cache" diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml new file mode 100644 index 0000000..d0380ff --- /dev/null +++ b/.github/workflows/security-release-gate.yml @@ -0,0 +1,110 @@ +name: Build, smoke test, and security evidence + +on: + pull_request: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: kubernetes-data-helper-security-${{ github.ref }} + cancel-in-progress: false + +jobs: + build-test-sbom: + runs-on: ubuntu-24.04 + timeout-minutes: 45 + env: + CANDIDATE_IMAGE: local/pasturestack/kubernetes-data-helper-image:${{ github.sha }} + TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969 + steps: + - name: Check out candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - name: Validate source contract + shell: bash + run: | + set -euo pipefail + test -z "$(git status --porcelain)" + bash scripts/validate + git diff --check + + - name: Build and smoke-test the exact image + shell: bash + run: | + set -euo pipefail + docker build --pull \ + --build-arg IMAGE_VERSION=v0.1.2 \ + --build-arg SOURCE_REVISION="$GITHUB_SHA" \ + --tag "$CANDIDATE_IMAGE" . + test "$(docker image inspect --format '{{.Config.User}}' "$CANDIDATE_IMAGE")" = 65532:65532 + test "$(docker image inspect --format '{{json .Config.Entrypoint}}' "$CANDIDATE_IMAGE")" = '["/bin/true"]' + test "$(docker image inspect --format '{{index .Config.Labels "org.opencontainers.image.version"}}' "$CANDIDATE_IMAGE")" = v0.1.2 + test "$(docker image inspect --format '{{index .Config.Labels "org.opencontainers.image.revision"}}' "$CANDIDATE_IMAGE")" = "$GITHUB_SHA" + test "$(docker image inspect --format '{{json .Config.ExposedPorts}}' "$CANDIDATE_IMAGE")" = null + volume="pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" + docker volume create "$volume" >/dev/null + trap 'docker volume rm -f "$volume" >/dev/null 2>&1 || true' EXIT + docker run --rm --network none --read-only --cap-drop ALL \ + --security-opt no-new-privileges:true --volume "$volume:/data" \ + "$CANDIDATE_IMAGE" + docker volume inspect "$volume" >/dev/null + docker run --rm --entrypoint /bin/sh "$CANDIDATE_IMAGE" \ + -c 'test ! -e /usr/bin/pebble && test -r /usr/share/licenses/pasturestack-kubernetes-data-helper-image/LICENSE' + docker volume rm "$volume" >/dev/null + trap - EXIT + + - name: Scan runtime and generate release evidence + shell: bash + run: | + set -euo pipefail + mkdir -p evidence "$RUNNER_TEMP/trivy-cache" + docker pull "$TRIVY_IMAGE" >/dev/null + docker run --rm \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume "$PWD/evidence:/evidence" \ + --volume "$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 "$CANDIDATE_IMAGE" + docker run --rm \ + --volume /var/run/docker.sock:/var/run/docker.sock \ + --volume "$PWD/evidence:/evidence" \ + --volume "$RUNNER_TEMP/trivy-cache:/root/.cache/trivy" \ + "$TRIVY_IMAGE" image --format cyclonedx \ + --output /evidence/runtime.cdx.json "$CANDIDATE_IMAGE" + docker run --rm --entrypoint /usr/bin/dpkg-query "$CANDIDATE_IMAGE" \ + -W '-f=${binary:Package}\t${Version}\n' \ + | LC_ALL=C sort > evidence/runtime-ubuntu-packages.tsv + jq -e '.bomFormat == "CycloneDX" and (.components | length > 0)' \ + evidence/runtime.cdx.json >/dev/null + jq -e '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" or .Severity == "HIGH")] | length == 0' \ + evidence/runtime-security.json >/dev/null + jq -e '[.Results[]?.Secrets[]?] | length == 0' evidence/runtime-security.json >/dev/null + sha256sum evidence/runtime-security.json evidence/runtime.cdx.json \ + evidence/runtime-ubuntu-packages.tsv | LC_ALL=C sort -k2 > evidence/SHA256SUMS + + - name: Upload reviewed evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: kubernetes-data-helper-security-${{ github.sha }} + path: evidence/ + if-no-files-found: error + retention-days: 30 + + - name: Clean run-owned resources + if: always() + shell: bash + run: | + set +e + docker ps -aq --filter "ancestor=$CANDIDATE_IMAGE" | xargs -r docker rm -f + docker image rm -f "$CANDIDATE_IMAGE" "$TRIVY_IMAGE" >/dev/null 2>&1 + docker volume rm -f "pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >/dev/null 2>&1 + rm -rf -- evidence "$RUNNER_TEMP/trivy-cache" diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index a22948e..9722ddd 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -23,7 +23,7 @@ Run this test on a Docker-capable validation host from a clean copy of the repos ```sh set -eu -image='ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.1-pasturestack.1' +image='local/pasturestack/kubernetes-data-helper-image:v0.1.2' volume='pasturestack-kubernetes-data-helper-poc' cleanup() { diff --git a/Dockerfile b/Dockerfile index f283f43..e372d32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ARG UBUNTU_IMAGE=ubuntu:26.04@sha256:2260313b31c8c011cd2eebe728008efac1b3982be73eb71348ea2648d2c0e09b FROM ${UBUNTU_IMAGE} -ARG IMAGE_VERSION=v0.1.1-pasturestack.1 +ARG IMAGE_VERSION=v0.1.2 ARG SOURCE_REVISION=unknown LABEL org.opencontainers.image.title="PastureStack/kubernetes-data-helper-image" \ diff --git a/ORIGIN.md b/ORIGIN.md index e68af8d..d4f5884 100644 --- a/ORIGIN.md +++ b/ORIGIN.md @@ -4,10 +4,13 @@ This repository preserves its complete pre-migration Git history. Commit `03bb31 The maintenance image was created to replace the historical `busybox` data helper used by a legacy Kubernetes catalog while preserving its one-shot, successful-exit behavior. It was not published by the platform vendor, SUSE, or the Kubernetes project. -The PastureStack repository and image name are: +The PastureStack repository is: - Repository: `PastureStack/kubernetes-data-helper-image` -- Release image: `ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.1-pasturestack.1` + +The maintained image uses the pure numeric release `v0.1.2`. Earlier +non-numeric compatibility releases remain immutable historical evidence; their +qualifiers are intentionally not reused or advertised as current coordinates. Historical names and image references may appear in this file, compatibility documentation, preserved Git history, and source attribution. They identify origins or compatibility targets and do not imply sponsorship or endorsement. diff --git a/README.md b/README.md index 50e0478..8227ce1 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,15 @@ PastureStack is an independent community effort to preserve, audit, and moderniz ## Release image -The reviewed `linux/amd64` release used by the catalog is: +The maintained release uses a pure numeric semantic version: ```text -ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.1-pasturestack.1 +ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.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. +Release evidence records the immutable digest separately so a long digest never +appears in the user interface. Earlier non-numeric releases remain immutable +historical evidence and must not be copied into new release names. ## Build @@ -24,9 +26,9 @@ Build the reviewed source tree: ```sh docker build --pull \ - --build-arg IMAGE_VERSION=v0.1.1-pasturestack.1 \ + --build-arg IMAGE_VERSION=v0.1.2 \ --build-arg SOURCE_REVISION="$(git rev-parse HEAD)" \ - --tag ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.1-pasturestack.1 \ + --tag local/pasturestack/kubernetes-data-helper-image:v0.1.2 \ . ``` @@ -40,7 +42,7 @@ Run the image with the constraints supplied by the historical catalog and verify docker run --rm \ --network none \ --volume pasturestack-kubernetes-data-helper-poc:/data \ - ghcr.io/pasturestack/kubernetes-data-helper-image:v0.1.1-pasturestack.1 + local/pasturestack/kubernetes-data-helper-image:v0.1.2 test "$?" -eq 0 ``` diff --git a/scripts/validate b/scripts/validate new file mode 100644 index 0000000..6f05dcb --- /dev/null +++ b/scripts/validate @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(cd "$(dirname "$0")/.." && pwd) +cd "$repo_root" + +test -f Dockerfile +test -f LICENSE +test -f COMPATIBILITY.md +test -f ORIGIN.md +test "$(sed -n 's/^ARG IMAGE_VERSION=//p' Dockerfile)" = v0.1.2 +grep -Fq 'USER 65532:65532' Dockerfile +grep -Fq 'ENTRYPOINT ["/bin/true"]' Dockerfile +grep -Fq 'rm -f /usr/bin/pebble' Dockerfile +grep -Fq 'org.opencontainers.image.source="https://github.com/PastureStack/kubernetes-data-helper-image"' Dockerfile + +if grep -E -n 'v[0-9]+\.[0-9]+\.[0-9]+-[A-Za-z]' Dockerfile README.md COMPATIBILITY.md; then + echo 'Current product coordinates must use pure numeric semantic versions' >&2 + exit 1 +fi + +while IFS= read -r action_line; do + if ! printf '%s\n' "$action_line" | grep -Eq '@[0-9a-f]{40}([[:space:]]+#.*)?[[:space:]]*$'; then + printf 'GitHub Action is not pinned to a full commit SHA: %s\n' "$action_line" >&2 + exit 1 + fi +done < <(grep -R -h -E '^[[:space:]]+uses:' .github/workflows) + +if grep -R -E -l --exclude-dir=.git -- 'BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY' .; then + echo 'Rejected private key material in committed source' >&2 + exit 1 +fi + +echo 'KUBERNETES_DATA_HELPER_VALIDATE_OK version=v0.1.2' From 3d8b3b9e53f9cf62fce671187af97817f95d363d Mon Sep 17 00:00:00 2001 From: chen21019 Date: Wed, 9 Sep 2026 18:56:23 +0800 Subject: [PATCH 2/2] Clean root-owned scan cache --- .github/workflows/release.yml | 3 ++- .github/workflows/security-release-gate.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d110e30..e967048 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -132,4 +132,5 @@ jobs: docker logout ghcr.io >/dev/null 2>&1 docker image rm -f "$TARGET_REPOSITORY:$GITHUB_REF_NAME" "$TRIVY_IMAGE" >/dev/null 2>&1 docker volume rm -f "pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >/dev/null 2>&1 - rm -rf -- evidence "$RUNNER_TEMP/trivy-cache" + rm -rf -- evidence + sudo rm -rf -- "$RUNNER_TEMP/trivy-cache" diff --git a/.github/workflows/security-release-gate.yml b/.github/workflows/security-release-gate.yml index d0380ff..4b2f692 100644 --- a/.github/workflows/security-release-gate.yml +++ b/.github/workflows/security-release-gate.yml @@ -107,4 +107,5 @@ jobs: docker ps -aq --filter "ancestor=$CANDIDATE_IMAGE" | xargs -r docker rm -f docker image rm -f "$CANDIDATE_IMAGE" "$TRIVY_IMAGE" >/dev/null 2>&1 docker volume rm -f "pasturestack-kdh-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >/dev/null 2>&1 - rm -rf -- evidence "$RUNNER_TEMP/trivy-cache" + rm -rf -- evidence + sudo rm -rf -- "$RUNNER_TEMP/trivy-cache"