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
136 changes: 136 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
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
sudo rm -rf -- "$RUNNER_TEMP/trivy-cache"
111 changes: 111 additions & 0 deletions .github/workflows/security-release-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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
sudo rm -rf -- "$RUNNER_TEMP/trivy-cache"
2 changes: 1 addition & 1 deletion COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -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" \
Expand Down
7 changes: 5 additions & 2 deletions ORIGIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,25 @@ 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

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 \
.
```

Expand All @@ -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
```

Expand Down
34 changes: 34 additions & 0 deletions scripts/validate
Original file line number Diff line number Diff line change
@@ -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'