Skip to content

chore: CI/CD supply-chain hardening (#57) #152

chore: CI/CD supply-chain hardening (#57)

chore: CI/CD supply-chain hardening (#57) #152

Workflow file for this run

# CI — runs on every push to main and on every pull request targeting main.
#
# Responsibilities:
# - Validate code quality (fmt, clippy, tests, audit)
# - Build release binaries to catch compile errors early
# - Build Docker images to verify Dockerfiles are healthy
#
# What this workflow does NOT do:
# - Push Docker images to any registry ← happens only in release.yml
# - Publish binaries or create releases ← happens only in release.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
# Least-privilege default token scope. CI only reads the repo; no job here
# writes contents, packages, or issues. Jobs may widen this locally if needed.
permissions:
contents: read
jobs:
# ── Format ────────────────────────────────────────────────────────────────────
# Ensures all Rust code is formatted with `cargo fmt`.
# Fails fast so contributors get feedback before heavier jobs run.
fmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
with:
components: rustfmt
- run: cargo fmt --all --check
# ── Lint ──────────────────────────────────────────────────────────────────────
# Runs clippy with -D warnings so any lint becomes a hard failure.
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
with:
components: clippy
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- run: cargo clippy --workspace -- -D warnings
# ── Test ──────────────────────────────────────────────────────────────────────
# Runs the full test suite including ferrox-cp integration tests which require
# a live Postgres instance. sqlx::test spins up isolated temp databases per test.
test:
name: Test
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: testpass
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
env:
DATABASE_URL: postgres://postgres:testpass@localhost:5432/postgres
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- run: cargo test --workspace -- --test-threads=1
# ── Build ─────────────────────────────────────────────────────────────────────
# Release build for the whole workspace. Catches compile errors that only
# surface with optimisations or release-profile codegen (e.g. dead-code elim).
build:
name: Build (release)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- name: Install protobuf compiler
run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
- run: cargo build --release --workspace
# ── Security audit ────────────────────────────────────────────────────────────
# Checks all dependencies against the RustSec advisory database.
audit:
name: Security audit
runs-on: ubuntu-latest
# rustsec/audit-check publishes advisories as a Check run, which needs
# `checks: write` on top of the workflow's read-only default.
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: rustsec/audit-check@e9159ac5f7d7d873ce074ca134da2f445c0a4a61 # v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
# RUSTSEC-2023-0071: timing side-channel in `rsa` crate (Marvin Attack).
# Pulled in transitively via sqlx → sqlx-mysql → rsa. We do not use MySQL
# or any RSA operations through sqlx-mysql; no patch is available upstream.
ignore: RUSTSEC-2023-0071
# ── Admin UI ──────────────────────────────────────────────────────────────────
# Type-checks and builds the React + TypeScript SPA that is embedded into the
# ferrox-cp binary at compile time via include_dir!.
ui:
name: UI build & type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
cache: npm
cache-dependency-path: ferrox-cp/ui/package-lock.json
- run: cd ferrox-cp/ui && npm ci
- run: cd ferrox-cp/ui && npm run build
# ── Config schema validation ──────────────────────────────────────────────────
# Validates all committed YAML configs against the JSON Schema to catch
# configuration mistakes before they reach a running instance.
validate-config:
name: Validate config schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.12"
- run: pip install check-jsonschema
- run: check-jsonschema --schemafile ferrox/config.schema.json ferrox/config/*.yaml
# ── Docker build — ferrox (gateway) ──────────────────────────────────────────
# Builds the ferrox gateway Docker image to verify the Dockerfile is healthy.
# Image is NOT pushed here; pushing only happens in release.yml on a
# published GitHub release.
docker:
name: Docker build (ferrox)
runs-on: ubuntu-latest
needs: [fmt, clippy, test, build]
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build image (verify only — no push)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: Dockerfile
push: false
tags: ghcr.io/${{ github.repository_owner }}/ferrox:ci
cache-from: type=gha
cache-to: type=gha,mode=max
# ── Docker build — ferrox-cp (control plane) ─────────────────────────────────
# Builds the ferrox-cp Docker image (including the Node.js UI build stage) to
# verify the multi-stage Dockerfile is healthy.
# Image is NOT pushed here; pushing only happens in release.yml on a
# published GitHub release.
docker-cp:
name: Docker build (ferrox-cp)
runs-on: ubuntu-latest
needs: [fmt, clippy, test, build, ui]
steps:
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Build image (verify only — no push)
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
with:
context: .
file: ferrox-cp/Dockerfile
push: false
tags: ghcr.io/${{ github.repository_owner }}/ferrox-cp:ci
cache-from: type=gha
cache-to: type=gha,mode=max