Documentation updates and added cargo deny and dependabot (#24) #48
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/**' | |
| push: | |
| branches: | |
| - main | |
| release: | |
| types: | |
| - published | |
| # Defaults for jobs that do not declare their own permissions block. | |
| # Jobs with special needs (docker, attest, sign, upload) override at job level. | |
| permissions: | |
| contents: read | |
| id-token: write | |
| security-events: write | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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 | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.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@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.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@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.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@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6.19.2 | |
| 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@398d4b0eeef1380460a10c8013a76f728fb906ac # v3.9.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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| 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@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Attest ${{ matrix.variant.name }} image provenance | |
| uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 | |
| with: | |
| subject-name: ghcr.io/${{ matrix.variant.image-repository }} | |
| subject-digest: ${{ matrix.variant.digest }} | |
| push-to-registry: true | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a # v3.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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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' | |
| - name: Upload Trivy IaC SARIF to Code Scanning | |
| uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a # v3.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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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 — all events. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| trivy: | |
| name: 🔭 Container Security Scan - ${{ matrix.variant.name }} | |
| runs-on: ubuntu-latest | |
| needs: [docker, extract-version] | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Log in to GHCR | |
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Run Trivy scan on ${{ matrix.variant.name }} image | |
| uses: firestoned/github-actions/security/trivy-scan@53b483254bc648903c364ee3c73a546d0936a91e # v1.3.6 | |
| with: | |
| image-ref: ghcr.io/${{ matrix.variant.image-repository }}:${{ matrix.variant.image-tag }} | |
| sarif-category: trivy-${{ github.event_name }}-${{ matrix.variant.name }} | |
| upload-artifact-name: ${{ github.event_name == 'release' && format('trivy-release-{0}', matrix.variant.name) || format('trivy-main-{0}-{1}', matrix.variant.name, github.run_number) }} | |
| output-filename: trivy-${{ github.event_name }}-${{ matrix.variant.name }}.json | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Download binary artifact | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| 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@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0 | |
| with: | |
| subject-path: artifacts/${{ matrix.platform.artifact_name }}/${{ matrix.platform.artifact_name }}.tar.gz | |
| - name: Upload signed artifacts | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| 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@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| 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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - 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@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| 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] | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| path: ./artifacts | |
| pattern: "{5spot-*,docker-sbom-*,*.intoto.jsonl,deploy-manifests}" | |
| - 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 | |
| # 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@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2 | |
| with: | |
| files: | | |
| artifacts/release/*.tar.gz | |
| artifacts/sboms/*.json | |
| artifacts/signatures/*.bundle | |
| artifacts/provenance/*.intoto.jsonl | |
| artifacts/release/checksums.sha256 |