Vulnerability fixes (#34) #70
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copyright (c) 2025 Erick Bourgeois, firestoned | |
| # SPDX-License-Identifier: Apache-2.0 | |
| name: Build | |
| on: | |
| pull_request: | |
| branches: | |
| - main | |
| paths: | |
| - 'src/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'Dockerfile*' | |
| - '.github/workflows/build.yaml' | |
| - '.github/actions/**' | |
| - '.vex/**' | |
| - 'tools/**' | |
| push: | |
| branches: | |
| - main | |
| release: | |
| types: | |
| - published | |
| # Top-level GITHUB_TOKEN is read-only by default (OpenSSF Scorecard | |
| # Token-Permissions rule). Jobs that need elevated scopes — id-token:write | |
| # for keyless Cosign / Sigstore, security-events:write for SARIF upload to | |
| # Code Scanning, packages:write for GHCR push, attestations:write for | |
| # actions/attest-build-provenance, contents:write for release-asset upload | |
| # — declare them explicitly at job level. See the docker / attest / | |
| # build-vex / semgrep-sast / iac-scan / grype / sign-artifacts / | |
| # slsa-provenance / upload-release-assets jobs below. | |
| permissions: | |
| contents: read | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| # Override the cross-compilation linkers set in .cargo/config.toml. | |
| # config.toml specifies the Homebrew cross-compilers needed when a macOS | |
| # developer runs `cargo build --target x86_64-unknown-linux-gnu` locally. | |
| # On Linux CI runners the native target IS x86_64/aarch64-unknown-linux-gnu, | |
| # so cargo would try to invoke those cross-compilers — which are not | |
| # installed. Setting these vars to `cc` restores the default system linker. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER: cc | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: cc | |
| jobs: | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # License headers — always runs (PRs are pre-filtered by path at the trigger). | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| license-check: | |
| name: ⚖️ Verify SPDX License Headers | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Check license headers | |
| uses: firestoned/github-actions/security/license-check@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| copyright-holder: "Erick Bourgeois, firestoned" | |
| license-id: "Apache-2.0" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Commit signatures — verify-mode differs by event type. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| verify-commits: | |
| name: 🔐 Verify Signed Commits | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| - name: Verify commits (PR) | |
| if: github.event_name == 'pull_request' | |
| uses: firestoned/github-actions/security/verify-signed-commits@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| base-ref: origin/${{ github.base_ref }} | |
| verify-mode: pr | |
| - name: Verify commits (push to main) | |
| if: github.event_name == 'push' | |
| uses: firestoned/github-actions/security/verify-signed-commits@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| verify-mode: push | |
| - name: Verify commits (release) | |
| if: github.event_name == 'release' | |
| uses: firestoned/github-actions/security/verify-signed-commits@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| verify-mode: release | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # PR only: formatting + clippy. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| format: | |
| name: 🎨 Check Formatting | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [license-check, verify-commits] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| with: | |
| components: rustfmt | |
| - name: Check formatting | |
| run: cargo fmt -- --check | |
| clippy: | |
| name: 📎 Clippy | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [format] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| with: | |
| components: clippy | |
| - name: Cache cargo dependencies | |
| uses: firestoned/github-actions/rust/cache-cargo@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| - name: Run clippy | |
| run: cargo clippy --all-targets --all-features -- -D warnings | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # VEX schema gate — PR only. | |
| # Enforces schema, enum, and CVE-uniqueness on every file under .vex/ so that | |
| # malformed VEX statements can never merge to main. The validator itself has | |
| # its own fixture-based unit tests which also run here. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| validate-vex: | |
| name: 🧾 Validate VEX Statements | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [verify-commits] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: '3.12' | |
| - name: Run validator unit tests | |
| run: ./tools/tests/validate-vex-tests.sh | |
| - name: Validate .vex/ directory | |
| run: ./tools/validate-vex.sh | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Extract version — all events; downstream build jobs depend on this. | |
| # format/clippy are independent PR quality checks and do not gate the build. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| extract-version: | |
| name: 🏷️ Extract Version Information | |
| runs-on: ubuntu-latest | |
| needs: [license-check, verify-commits] | |
| outputs: | |
| version: ${{ steps.version-chainguard.outputs.version }} | |
| tag_name: ${{ steps.version-chainguard.outputs.tag-name }} | |
| image-tag-chainguard: ${{ steps.version-chainguard.outputs.image-tag }} | |
| image-tag-distroless: ${{ steps.version-distroless.outputs.image-tag }} | |
| image-repository-chainguard: ${{ steps.version-chainguard.outputs.image-repository }} | |
| image-repository-distroless: ${{ steps.version-distroless.outputs.image-repository }} | |
| short-sha: ${{ steps.version-chainguard.outputs.short-sha }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Extract version for Chainguard | |
| id: version-chainguard | |
| uses: ./.github/actions/extract-version | |
| with: | |
| repository: ${{ github.repository }} | |
| workflow-type: ${{ github.event_name == 'pull_request' && 'pr' || github.event_name == 'push' && 'main' || 'release' }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| release-tag: ${{ github.event.release.tag_name }} | |
| image-tag-suffix: "" | |
| - name: Extract version for Distroless | |
| id: version-distroless | |
| uses: ./.github/actions/extract-version | |
| with: | |
| repository: ${{ github.repository }} | |
| workflow-type: ${{ github.event_name == 'pull_request' && 'pr' || github.event_name == 'push' && 'main' || 'release' }} | |
| pr-number: ${{ github.event.pull_request.number }} | |
| release-tag: ${{ github.event.release.tag_name }} | |
| image-tag-suffix: "-distroless" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Build binary — all events. | |
| # On release the Cargo.toml version is bumped to match the release tag. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| build: | |
| name: 🦀 Build - ${{ matrix.platform.name }} | |
| runs-on: ${{ matrix.platform.runner }} | |
| needs: [extract-version] | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - name: Linux x86_64 | |
| runner: ubuntu-24.04 | |
| artifact_name: 5spot-linux-amd64 | |
| binary_name: 5spot | |
| - name: Linux ARM64 | |
| runner: ubuntu-24.04-arm | |
| artifact_name: 5spot-linux-arm64 | |
| binary_name: 5spot | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Update Cargo.toml version | |
| if: github.event_name == 'release' | |
| run: | | |
| perl -i -pe 's/^version = ".*"/version = "${{ needs.extract-version.outputs.version }}"/' Cargo.toml | |
| echo "Updated Cargo.toml to version ${{ needs.extract-version.outputs.version }}" | |
| grep '^version = ' Cargo.toml | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| - name: Cache cargo dependencies | |
| uses: firestoned/github-actions/rust/cache-cargo@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| - name: Build binary | |
| run: cargo build --release | |
| - name: Install cargo-cyclonedx | |
| run: cargo install cargo-cyclonedx --locked --quiet | |
| - name: Generate SBOM | |
| run: | | |
| cargo cyclonedx --format json | |
| find . -maxdepth 1 -name "*.cdx.json" -exec mv {} target/release/ \; | |
| # PR and push artifacts expire after 1 day — they are not release assets. | |
| - name: Upload artifacts (PR / push) | |
| if: github.event_name != 'release' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ matrix.platform.artifact_name }} | |
| path: | | |
| target/release/${{ matrix.platform.binary_name }} | |
| target/release/*.cdx.json | |
| retention-days: 1 | |
| # Release artifacts keep the default retention so re-runs remain possible. | |
| - name: Upload artifacts (release) | |
| if: github.event_name == 'release' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ matrix.platform.artifact_name }} | |
| path: | | |
| target/release/${{ matrix.platform.binary_name }} | |
| target/release/*.cdx.json | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Tests — all events; running unconditionally lets docker depend on it simply. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| test: | |
| name: 🧪 Test | |
| runs-on: ubuntu-latest | |
| needs: [build] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| - name: Download x86_64 build artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: 5spot-linux-amd64 | |
| path: target/release/ | |
| - name: Cache cargo dependencies | |
| uses: firestoned/github-actions/rust/cache-cargo@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| - name: Run tests | |
| run: cargo test --all --verbose | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Docker build and push — all events. | |
| # | |
| # Metadata tags vary by event (three separate metadata steps, only one runs | |
| # per invocation; empty outputs from skipped steps are filtered out by | |
| # docker/build-push-action). On release, Cosign signing and SBOM generation | |
| # steps are also enabled. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| docker: | |
| name: 🐳 Build and Push Docker Image - ${{ matrix.variant.name }} | |
| runs-on: ubuntu-latest | |
| needs: [build, extract-version, test] | |
| permissions: | |
| contents: read | |
| packages: write | |
| id-token: write | |
| outputs: | |
| digest-chainguard: ${{ steps.export-digest.outputs.digest-chainguard }} | |
| digest-distroless: ${{ steps.export-digest.outputs.digest-distroless }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| variant: | |
| - name: Chainguard | |
| dockerfile: Dockerfile.chainguard | |
| suffix: "" | |
| description: "5-Spot Chainguard Zero-CVE (glibc, FIPS-ready)" | |
| image-tag: ${{ needs.extract-version.outputs.image-tag-chainguard }} | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-chainguard }} | |
| - name: Distroless | |
| dockerfile: Dockerfile | |
| suffix: "-distroless" | |
| description: "5-Spot Google Distroless (glibc)" | |
| image-tag: ${{ needs.extract-version.outputs.image-tag-distroless }} | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-distroless }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Prepare Docker binaries | |
| uses: ./.github/actions/prepare-docker-binaries | |
| - name: Setup Docker environment | |
| uses: firestoned/github-actions/docker/setup-docker@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| # Only one of the three metadata steps below runs per job instance. | |
| # docker/build-push-action concatenates all tags/labels outputs and | |
| # silently drops the empty lines produced by the two skipped steps. | |
| - name: Extract metadata (PR) | |
| id: meta-pr | |
| if: github.event_name == 'pull_request' | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ matrix.variant.image-repository }} | |
| tags: | | |
| type=raw,value=${{ matrix.variant.image-tag }} | |
| type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }} | |
| flavor: | | |
| latest=false | |
| - name: Extract metadata (push to main) | |
| id: meta-main | |
| if: github.event_name == 'push' | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ matrix.variant.image-repository }} | |
| tags: | | |
| type=raw,value=${{ matrix.variant.image-tag }} | |
| type=raw,value=main-{{date 'YYYY-MM-DD'}} | |
| type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }} | |
| type=raw,value=latest | |
| flavor: | | |
| latest=false | |
| - name: Extract metadata (release) | |
| id: meta-release | |
| if: github.event_name == 'release' | |
| uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 | |
| with: | |
| images: ghcr.io/${{ matrix.variant.image-repository }} | |
| tags: | | |
| type=semver,pattern={{version}} | |
| type=semver,pattern={{major}}.{{minor}} | |
| type=semver,pattern={{major}} | |
| type=raw,value=sha-${{ needs.extract-version.outputs.short-sha }}${{ matrix.variant.suffix }} | |
| flavor: | | |
| latest=false | |
| prefix=v | |
| suffix=${{ matrix.variant.suffix }} | |
| - name: Build and push Docker image (${{ matrix.variant.name }}) | |
| id: docker_build | |
| uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 | |
| with: | |
| context: . | |
| file: ${{ matrix.variant.dockerfile }} | |
| platforms: linux/amd64,linux/arm64 | |
| push: true | |
| tags: | | |
| ${{ steps.meta-pr.outputs.tags }} | |
| ${{ steps.meta-main.outputs.tags }} | |
| ${{ steps.meta-release.outputs.tags }} | |
| labels: | | |
| ${{ steps.meta-pr.outputs.labels }} | |
| ${{ steps.meta-main.outputs.labels }} | |
| ${{ steps.meta-release.outputs.labels }} | |
| org.opencontainers.image.description=${{ matrix.variant.description }} | |
| build-args: | | |
| ${{ github.event_name == 'release' && format('VERSION={0}', needs.extract-version.outputs.version) || '' }} | |
| cache-from: type=gha,scope=${{ matrix.variant.name }} | |
| cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }} | |
| sbom: true | |
| provenance: true | |
| # Push to main and release: sign the image with Cosign (keyless, Sigstore). | |
| # PR images are ephemeral and not deployed, so signing them is skipped. | |
| # Sign by digest (not tag) to guarantee we sign exactly what was pushed. | |
| - name: Install Cosign | |
| if: github.event_name != 'pull_request' | |
| uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 | |
| - name: Sign container image with Cosign | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| cosign sign --yes \ | |
| ghcr.io/${{ matrix.variant.image-repository }}@${{ steps.docker_build.outputs.digest }} | |
| # Release only: generate a CycloneDX SBOM for the published image. | |
| - name: Generate Docker SBOM for ${{ matrix.variant.name }} | |
| if: github.event_name == 'release' | |
| uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 | |
| with: | |
| image: ghcr.io/${{ matrix.variant.image-repository }}:${{ matrix.variant.image-tag }} | |
| format: cyclonedx-json | |
| output-file: docker-sbom-${{ matrix.variant.name }}.json | |
| upload-release-assets: false | |
| - name: Upload Docker SBOM as artifact | |
| if: github.event_name == 'release' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: docker-sbom-${{ matrix.variant.name }} | |
| path: docker-sbom-${{ matrix.variant.name }}.json | |
| - name: Export image digest | |
| id: export-digest | |
| run: | | |
| case "${{ matrix.variant.name }}" in | |
| Chainguard) echo "digest-chainguard=${{ steps.docker_build.outputs.digest }}" >> "$GITHUB_OUTPUT" ;; | |
| Distroless) echo "digest-distroless=${{ steps.docker_build.outputs.digest }}" >> "$GITHUB_OUTPUT" ;; | |
| esac | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # GitHub Artifact Attestation — all events. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| attest: | |
| name: 🔏 Attest Docker Image - ${{ matrix.variant.name }} | |
| runs-on: ubuntu-latest | |
| needs: [docker, extract-version] | |
| permissions: | |
| id-token: write | |
| attestations: write | |
| packages: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| variant: | |
| - name: Chainguard | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-chainguard }} | |
| digest: ${{ needs.docker.outputs.digest-chainguard }} | |
| - name: Distroless | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-distroless }} | |
| digest: ${{ needs.docker.outputs.digest-distroless }} | |
| steps: | |
| - name: Log in to GHCR | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Attest ${{ matrix.variant.name }} image provenance | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-name: ghcr.io/${{ matrix.variant.image-repository }} | |
| subject-digest: ${{ matrix.variant.digest }} | |
| push-to-registry: true | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # VEX document — push to main + release. | |
| # | |
| # Reads hand-authored per-CVE triage from .vex/*.toml, assembles a single | |
| # OpenVEX document, and (on push/release only) Cosign-attests it to both | |
| # image digests. The `openvex` artifact is always uploaded so the `grype` | |
| # container scan job can consume it via `--vex` and suppress already- | |
| # triaged findings from GitHub Code Scanning. On release, the GitHub | |
| # build-provenance bundle is also emitted and attached to the release. | |
| # | |
| # The validator runs first so malformed VEX can never leak through, even | |
| # if a bad merge slipped past the PR gate. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| build-vex: | |
| name: 🧾 Assemble OpenVEX | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [docker, extract-version] | |
| permissions: | |
| contents: read | |
| id-token: write | |
| attestations: write | |
| packages: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Set up Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 | |
| with: | |
| python-version: '3.12' | |
| - name: Validate .vex/ (defensive re-run before assembly) | |
| run: ./tools/validate-vex.sh | |
| # Canonical @id varies by event so downstream consumers can link back | |
| # to the exact commit or release tag that produced the document. | |
| - name: Assemble OpenVEX document | |
| id: assemble | |
| run: | | |
| set -euo pipefail | |
| case "${{ github.event_name }}" in | |
| release) | |
| vex_id="https://github.com/${{ github.repository }}/releases/tag/${{ needs.extract-version.outputs.tag_name }}/vex" | |
| ;; | |
| push) | |
| vex_id="https://github.com/${{ github.repository }}/commit/${{ github.sha }}/vex" | |
| ;; | |
| *) | |
| vex_id="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/vex" | |
| ;; | |
| esac | |
| python3 tools/assemble_openvex.py \ | |
| --vex-dir .vex \ | |
| --id "$vex_id" \ | |
| --author "${{ github.event.release.author.login || github.actor }}" \ | |
| --output vex.openvex.json | |
| echo "── vex.openvex.json ─────────────────────────────────────────" | |
| cat vex.openvex.json | |
| # Always upload the assembled document so the `grype` scan job can | |
| # download it via actions/download-artifact@v4 regardless of event. | |
| - name: Upload OpenVEX document artifact | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: openvex | |
| path: vex.openvex.json | |
| if-no-files-found: error | |
| - name: Install Cosign | |
| uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1 | |
| - name: Log in to GHCR (for cosign attest) | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Cosign-attest the OpenVEX document to each image digest (keyless OIDC, | |
| # writes to the Sigstore transparency log + registry). Runs on both | |
| # main push and release so staging images have the same VEX posture | |
| # as release images. | |
| - name: Cosign attest OpenVEX to Chainguard image | |
| run: | | |
| cosign attest --yes \ | |
| --predicate vex.openvex.json \ | |
| --type openvex \ | |
| ghcr.io/${{ needs.extract-version.outputs.image-repository-chainguard }}@${{ needs.docker.outputs.digest-chainguard }} | |
| - name: Cosign attest OpenVEX to Distroless image | |
| run: | | |
| cosign attest --yes \ | |
| --predicate vex.openvex.json \ | |
| --type openvex \ | |
| ghcr.io/${{ needs.extract-version.outputs.image-repository-distroless }}@${{ needs.docker.outputs.digest-distroless }} | |
| # GitHub-native attestation parity with other release artifacts. | |
| # Release-only because the bundle is attached as a release asset and | |
| # the attestation links to the release tag. | |
| - name: Attest OpenVEX build provenance | |
| if: github.event_name == 'release' | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| id: attest-vex | |
| with: | |
| subject-path: vex.openvex.json | |
| # The attest-build-provenance action writes the sigstore bundle to a | |
| # path exposed via its `bundle-path` output. Copy it next to the VEX | |
| # document so the release-assets job can pick it up. | |
| - name: Stage VEX bundle beside document | |
| if: github.event_name == 'release' | |
| run: | | |
| set -euo pipefail | |
| if [ -n "${{ steps.attest-vex.outputs.bundle-path }}" ]; then | |
| cp "${{ steps.attest-vex.outputs.bundle-path }}" vex.openvex.json.bundle | |
| fi | |
| ls -lh vex.openvex.json.bundle | |
| - name: Upload OpenVEX bundle artifact (release) | |
| if: github.event_name == 'release' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: openvex-bundle | |
| path: vex.openvex.json.bundle | |
| if-no-files-found: error | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Security scan — all events. | |
| # Gitleaks secret scan only runs on PRs; release uploads the audit report. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| security: | |
| name: 🛡️ Security Vulnerability Scan | |
| runs-on: ubuntu-latest | |
| needs: [verify-commits] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| - name: Run security scan (PR / push) | |
| if: github.event_name != 'release' | |
| uses: firestoned/github-actions/rust/security-scan@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| cargo-audit-version: "0.22.0" | |
| - name: Run security scan (release) | |
| if: github.event_name == 'release' | |
| uses: firestoned/github-actions/rust/security-scan@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| cargo-audit-version: "0.22.0" | |
| upload-artifact-name: cargo-audit-report-release | |
| - name: Run gitleaks (secret scanning) | |
| if: github.event_name == 'pull_request' | |
| run: make gitleaks | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # SAST (Semgrep OSS) — PR + push to main. | |
| # Runs the community Rust / security-audit / secrets / OWASP Top Ten rulesets | |
| # and uploads SARIF to the Code Scanning dashboard. No token required — this | |
| # is the free OSS Community Edition; findings appear under the `semgrep` tool | |
| # category in the Security tab. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| semgrep-sast: | |
| name: 🔎 SAST (Semgrep) | |
| if: github.event_name != 'release' | |
| runs-on: ubuntu-latest | |
| needs: [verify-commits] | |
| permissions: | |
| contents: read | |
| security-events: write | |
| container: | |
| image: returntocorp/semgrep | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run Semgrep | |
| run: | | |
| semgrep scan \ | |
| --config=p/rust \ | |
| --config=p/security-audit \ | |
| --config=p/secrets \ | |
| --config=p/owasp-top-ten \ | |
| --sarif --output=semgrep.sarif \ | |
| --metrics=off \ | |
| --error || true | |
| - name: Upload Semgrep SARIF to Code Scanning | |
| uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 | |
| with: | |
| sarif_file: semgrep.sarif | |
| category: semgrep | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # IaC security scan (Trivy config) — PR + push to main. | |
| # Scans Kubernetes manifests under deploy/ and the Dockerfiles for | |
| # misconfigurations. Uploads SARIF to the Code Scanning dashboard. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| iac-scan: | |
| name: 🏗️ IaC Security Scan (Trivy config) | |
| if: github.event_name != 'release' | |
| runs-on: ubuntu-latest | |
| needs: [verify-commits] | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run Trivy config scan | |
| uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0 | |
| with: | |
| scan-type: config | |
| scan-ref: . | |
| format: sarif | |
| output: trivy-iac.sarif | |
| severity: CRITICAL,HIGH,MEDIUM | |
| exit-code: '0' | |
| # Justified suppressions live in .trivyignore at the repo root; | |
| # each entry is commented with an architectural rationale. | |
| trivyignores: ./.trivyignore | |
| - name: Upload Trivy IaC SARIF to Code Scanning | |
| uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 | |
| with: | |
| sarif_file: trivy-iac.sarif | |
| category: trivy-iac | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # cargo-deny — PR + push to main. | |
| # Enforces the license allow-list, RustSec advisories (same DB as | |
| # cargo-audit), and forbids dependencies from unknown registries or git | |
| # URLs. Configured via deny.toml at repo root. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| cargo-deny: | |
| name: 🧪 cargo-deny (License + Advisory + Sources) | |
| if: github.event_name != 'release' | |
| runs-on: ubuntu-latest | |
| needs: [verify-commits] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Run cargo-deny | |
| uses: EmbarkStudios/cargo-deny-action@91bf2b620e09e18d6eb78b92e7861937469acedb # v2.0.17 | |
| with: | |
| log-level: warn | |
| command: check | |
| arguments: --all-features | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Container security scan (Grype) — all events. | |
| # | |
| # Grype is the canonical Anchore/Chainguard scanner and natively consumes | |
| # OpenVEX via --vex to suppress already-triaged CVEs (Trivy's VEX support | |
| # is less mature and requires a different input shape). On push + release | |
| # the build-vex job assembles a VEX document; on PR there is no VEX yet, | |
| # so the scan runs without suppression. SARIF is uploaded to GitHub Code | |
| # Scanning under a category segmented by event + variant so historical | |
| # findings remain separable across branches. | |
| # | |
| # Ordering note: build-vex is listed in `needs` so the VEX artifact is | |
| # ready before grype runs. On PR events build-vex is skipped, so the `if` | |
| # guard lets grype proceed regardless of that skipped status. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| grype: | |
| name: 🔭 Container Security Scan - ${{ matrix.variant.name }} | |
| runs-on: ubuntu-latest | |
| needs: [docker, extract-version, build-vex] | |
| if: | | |
| always() && | |
| needs.docker.result == 'success' && | |
| needs.extract-version.result == 'success' && | |
| (needs.build-vex.result == 'success' || needs.build-vex.result == 'skipped') | |
| permissions: | |
| contents: read | |
| packages: read | |
| id-token: write | |
| security-events: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| variant: | |
| - name: Chainguard | |
| image-tag: ${{ needs.extract-version.outputs.image-tag-chainguard }} | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-chainguard }} | |
| - name: Distroless | |
| image-tag: ${{ needs.extract-version.outputs.image-tag-distroless }} | |
| image-repository: ${{ needs.extract-version.outputs.image-repository-distroless }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # Download the VEX document assembled by build-vex. Only present on | |
| # push + release events — PR events have no document to consume. | |
| - name: Download OpenVEX document | |
| if: github.event_name != 'pull_request' | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: openvex | |
| path: ./vex | |
| # Pinned-version install from the Anchore release tarball. Bump via | |
| # GRYPE_VERSION and record in | |
| # ~/dev/roadmaps/5spot-vex-generation-and-signing.md § Dependencies. | |
| - name: Install Grype | |
| env: | |
| GRYPE_VERSION: '0.87.0' | |
| run: | | |
| set -euo pipefail | |
| url="https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_amd64.tar.gz" | |
| curl -fsSLo grype.tgz "$url" | |
| tar -xzf grype.tgz grype | |
| sudo install -m 0755 grype /usr/local/bin/grype | |
| grype version | |
| # Build --vex only when we actually have a document. Grype ignores | |
| # statements whose product purl does not match the scanned image, so | |
| # passing an over-inclusive VEX is safe. A missing VEX simply means | |
| # no suppressions apply (PR events). | |
| - name: Run Grype scan on ${{ matrix.variant.name }} image | |
| env: | |
| IMAGE_REF: ghcr.io/${{ matrix.variant.image-repository }}:${{ matrix.variant.image-tag }} | |
| SARIF_FILE: grype-${{ github.event_name }}-${{ matrix.variant.name }}.sarif | |
| run: | | |
| set -euo pipefail | |
| vex_args=() | |
| if [ -f vex/vex.openvex.json ]; then | |
| echo "Using VEX document: vex/vex.openvex.json" | |
| vex_args+=(--vex vex/vex.openvex.json) | |
| else | |
| echo "No VEX document available — scanning without suppressions." | |
| fi | |
| grype "$IMAGE_REF" \ | |
| "${vex_args[@]}" \ | |
| --output sarif \ | |
| --file "$SARIF_FILE" | |
| - name: Upload Grype SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2 | |
| with: | |
| sarif_file: grype-${{ github.event_name }}-${{ matrix.variant.name }}.sarif | |
| category: grype-${{ github.event_name }}-${{ matrix.variant.name }} | |
| - name: Upload Grype SARIF artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ github.event_name == 'release' && format('grype-release-{0}', matrix.variant.name) || format('grype-{0}-{1}-{2}', github.event_name, matrix.variant.name, github.run_number) }} | |
| path: grype-${{ github.event_name }}-${{ matrix.variant.name }}.sarif | |
| if-no-files-found: error | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Push-to-main and release: sign binaries with Cosign and emit GitHub | |
| # artifact attestations. Skipped on PRs. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| sign-artifacts: | |
| name: ✍️ Sign Binary Artifacts | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [build, extract-version] | |
| permissions: | |
| id-token: write | |
| attestations: write | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - artifact_name: 5spot-linux-amd64 | |
| binary_name: 5spot | |
| - artifact_name: 5spot-linux-arm64 | |
| binary_name: 5spot | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Download binary artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: ${{ matrix.platform.artifact_name }} | |
| path: ./artifacts/${{ matrix.platform.artifact_name }} | |
| - name: Create tarball for signing | |
| run: | | |
| cd artifacts/${{ matrix.platform.artifact_name }} | |
| chmod +x ${{ matrix.platform.binary_name }} | |
| tar czf ${{ matrix.platform.artifact_name }}.tar.gz ${{ matrix.platform.binary_name }} | |
| ls -lh ${{ matrix.platform.artifact_name }}.tar.gz | |
| - name: Sign binary tarball with Cosign | |
| uses: firestoned/github-actions/security/cosign-sign@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| artifact-path: artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz | |
| - name: Attest binary tarball provenance | |
| uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 | |
| with: | |
| subject-path: artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz | |
| - name: Upload signed artifacts | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: ${{ matrix.platform.artifact_name }}-signed | |
| path: | | |
| artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz | |
| artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz.bundle | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Push-to-main and release: compute SHA-256 hashes of signed tarballs for SLSA. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| generate-provenance-subjects: | |
| name: 🔬 Generate SLSA Provenance Subjects | |
| if: github.event_name != 'pull_request' | |
| runs-on: ubuntu-latest | |
| needs: [sign-artifacts, extract-version] | |
| outputs: | |
| hashes: ${{ steps.hash.outputs.hashes }} | |
| steps: | |
| - name: Download signed artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: 5spot-*-signed | |
| path: ./artifacts | |
| merge-multiple: true | |
| - name: Generate hashes for SLSA provenance | |
| id: hash | |
| run: | | |
| cd artifacts | |
| sha256sum *.tar.gz > checksums.txt | |
| cat checksums.txt | |
| echo "hashes=$(cat checksums.txt | base64 -w0)" >> "$GITHUB_OUTPUT" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Push-to-main and release: SLSA Level 3 provenance via the official generator. | |
| # `upload-assets` only applies on release (there is no GitHub Release object | |
| # on a push-to-main to attach assets to); on push the provenance lands as a | |
| # workflow artifact, verifiable with slsa-verifier or `gh attestation verify`. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| slsa-provenance: | |
| name: 🏛️ Generate SLSA Provenance | |
| if: github.event_name != 'pull_request' | |
| needs: [generate-provenance-subjects, extract-version] | |
| permissions: | |
| actions: read | |
| id-token: write | |
| contents: write | |
| # MUST remain on a semver tag, not a SHA: SLSA Level 3 verification | |
| # requires the generator ref to be one of the approved released versions. | |
| # SHA-pinning this line would break slsa-verifier attestation validation. | |
| # See https://github.com/slsa-framework/slsa-github-generator#verification-of-provenance | |
| uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 | |
| with: | |
| base64-subjects: "${{ needs.generate-provenance-subjects.outputs.hashes }}" | |
| upload-assets: ${{ github.event_name == 'release' }} | |
| provenance-name: "${{ needs.extract-version.outputs.version }}.intoto.jsonl" | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Release only: regenerate and package the CRD deploy manifests. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| package-deploy-manifests: | |
| name: 📦 Package Deployment Manifests | |
| if: github.event_name == 'release' | |
| runs-on: ubuntu-latest | |
| needs: [extract-version] | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable (moving ref — re-pin quarterly) | |
| - name: Cache cargo dependencies | |
| uses: firestoned/github-actions/rust/cache-cargo@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| - name: Regenerate CRDs | |
| run: cargo run --quiet --bin crdgen > deploy/crds/scheduledmachine.yaml | |
| - name: Package deploy manifests | |
| run: tar czf deploy-manifests.tar.gz deploy/ | |
| - name: Upload deploy manifests | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: deploy-manifests | |
| path: deploy-manifests.tar.gz | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # Release only: collate all artifacts and upload to the GitHub Release. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| upload-release-assets: | |
| name: 🚀 Upload Release Assets | |
| if: github.event_name == 'release' | |
| runs-on: ubuntu-latest | |
| needs: [build, docker, attest, sign-artifacts, slsa-provenance, package-deploy-manifests, build-vex] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| path: ./artifacts | |
| pattern: "{5spot-*,docker-sbom-*,*.intoto.jsonl,deploy-manifests,openvex,openvex-bundle}" | |
| - name: 🧹 Organize release artifacts | |
| run: | | |
| cd artifacts | |
| mkdir -p release sboms signatures provenance | |
| # Copy signed tarballs and signature bundles | |
| for dir in 5spot-*-signed; do | |
| if [ -d "$dir" ]; then | |
| platform="${dir%-signed}" | |
| if compgen -G "$dir/*.tar.gz" > /dev/null; then | |
| cp "$dir"/*.tar.gz release/ | |
| fi | |
| if compgen -G "$dir/*.tar.gz.bundle" > /dev/null; then | |
| cp "$dir"/*.tar.gz.bundle signatures/ | |
| fi | |
| fi | |
| done | |
| # Collect binary SBOMs (Linux builds only) | |
| for dir in 5spot-linux-amd64 5spot-linux-arm64; do | |
| if [ -d "$dir" ] && compgen -G "$dir/*.cdx.json" > /dev/null; then | |
| cp "$dir"/*.cdx.json "sboms/${dir}-sbom.json" | |
| fi | |
| done | |
| # Copy Docker SBOMs | |
| for variant in Chainguard Distroless; do | |
| if [ -d "docker-sbom-${variant}" ] && [ -f "docker-sbom-${variant}/docker-sbom-${variant}.json" ]; then | |
| cp "docker-sbom-${variant}/docker-sbom-${variant}.json" "sboms/docker-sbom-${variant}.json" | |
| fi | |
| done | |
| # Copy SLSA provenance | |
| find . -name "*.intoto.jsonl" -type f -exec cp {} provenance/ \; | |
| # Copy deploy manifests | |
| if [ -f "deploy-manifests/deploy-manifests.tar.gz" ]; then | |
| cp deploy-manifests/deploy-manifests.tar.gz release/ | |
| fi | |
| # Copy OpenVEX document (release/) and its attestation bundle | |
| # (signatures/). The release/ location keeps the document browseable | |
| # next to the binaries; the bundle sits with the other Sigstore | |
| # bundles so consumers can find them in one place. The document | |
| # ships via the always-uploaded `openvex` artifact; the signed | |
| # bundle ships via the release-only `openvex-bundle` artifact. | |
| if [ -f "openvex/vex.openvex.json" ]; then | |
| cp openvex/vex.openvex.json release/ | |
| fi | |
| if [ -f "openvex-bundle/vex.openvex.json.bundle" ]; then | |
| cp openvex-bundle/vex.openvex.json.bundle signatures/ | |
| fi | |
| # Generate SHA256 checksums | |
| cd release && sha256sum * > checksums.sha256 | |
| cd ../sboms && sha256sum *.json >> ../release/checksums.sha256 | |
| cd ../signatures && sha256sum *.bundle >> ../release/checksums.sha256 | |
| cd ../provenance && compgen -G "*.intoto.jsonl" > /dev/null && sha256sum *.intoto.jsonl >> ../release/checksums.sha256 || true | |
| echo "Release artifacts:" | |
| ls -lh ../release/ | |
| - name: Upload release assets | |
| uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 | |
| with: | |
| files: | | |
| artifacts/release/*.tar.gz | |
| artifacts/release/vex.openvex.json | |
| artifacts/sboms/*.json | |
| artifacts/signatures/*.bundle | |
| artifacts/provenance/*.intoto.jsonl | |
| artifacts/release/checksums.sha256 |