diff --git a/.github/workflows/verify-published-image.yml b/.github/workflows/verify-published-image.yml new file mode 100644 index 0000000..77c0721 --- /dev/null +++ b/.github/workflows/verify-published-image.yml @@ -0,0 +1,72 @@ +name: Verify published trex-core image + +on: + workflow_dispatch: + inputs: + version: + description: "Strict release tag (for example v0.2.0)" + required: true + type: string + commit: + description: "Exact 40-character lowercase source commit" + required: true + type: string + +permissions: + contents: read + packages: read + +jobs: + verify: + runs-on: ubuntu-24.04 + steps: + - name: Validate immutable selectors and image contract + env: + REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ACTOR: ${{ github.actor }} + VERSION: ${{ inputs.version }} + COMMIT: ${{ inputs.commit }} + IMAGE: ghcr.io/fourblocks2020/trex-core + run: | + set -euo pipefail + [[ "$VERSION" =~ ^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] + [[ "$COMMIT" =~ ^[0-9a-f]{40}$ ]] + short_sha="${COMMIT:0:12}" + + printf '%s' "$REGISTRY_TOKEN" | docker login ghcr.io --username "$ACTOR" --password-stdin >/dev/null + version_raw="$(docker buildx imagetools inspect --raw "$IMAGE:$VERSION")" + source_raw="$(docker buildx imagetools inspect --raw "$IMAGE:$short_sha")" + version_digest="sha256:$(printf '%s' "$version_raw" | sha256sum | cut -d' ' -f1)" + source_digest="sha256:$(printf '%s' "$source_raw" | sha256sum | cut -d' ' -f1)" + test "$version_digest" = "$source_digest" + + docker pull "$IMAGE@$version_digest" >/dev/null + test "$(docker image inspect "$IMAGE@$version_digest" --format '{{.Architecture}}')" = amd64 + test "$(docker image inspect "$IMAGE@$version_digest" --format '{{.Config.User}}')" = 10001:10001 + test "$(docker image inspect "$IMAGE@$version_digest" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$COMMIT" + test "$(docker image inspect "$IMAGE@$version_digest" --format '{{index .Config.Labels "org.opencontainers.image.source"}}')" = https://github.com/fourblocks2020/trex-core + test "$(docker image inspect "$IMAGE@$version_digest" --format '{{json .Config.Entrypoint}}')" = '["trex-core-daemon"]' + test "$(docker run --rm --entrypoint python "$IMAGE@$version_digest" -c 'from trex_core.adapters.persistence.migrate import migration_files; p=migration_files(); print(f"count={len(p)} last={p[-1].name}")')" = 'count=22 last=022_persist_order_timeout_anchor.sql' + + if docker buildx imagetools inspect "$IMAGE:latest" >/dev/null 2>&1; then + echo "mutable latest selector exists" >&2 + exit 1 + fi + + echo "digest=$version_digest" + echo "version_selector=$IMAGE:$VERSION" + echo "source_selector=$IMAGE:$short_sha" + echo "immutable=$IMAGE@$version_digest" + { + echo "## Verified published trex-core image" + echo + echo "- digest: \`$version_digest\`" + echo "- version selector: \`$IMAGE:$VERSION\`" + echo "- source selector: \`$IMAGE:$short_sha\`" + echo "- source commit: \`$COMMIT\`" + echo "- architecture: \`linux/amd64\`" + echo "- runtime user: \`10001:10001\`" + echo "- entrypoint: \`trex-core-daemon\`" + echo "- migration inventory: \`22\`, last \`022_persist_order_timeout_anchor.sql\`" + echo "- latest: absent" + } >> "$GITHUB_STEP_SUMMARY"