Skip to content

filterframe: a DDoS mitigation control plane that only ever adds BGP objects #2

filterframe: a DDoS mitigation control plane that only ever adds BGP objects

filterframe: a DDoS mitigation control plane that only ever adds BGP objects #2

Workflow file for this run

name: CI
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
env:
# Pinned to match rust-toolchain.toml. Both move together, in one PR.
RUST_STABLE: "1.97.1"
jobs:
check:
name: fmt, clippy, test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# Installed with rustup directly rather than via a third-party action, so
# the toolchain version is visible in this file and not in someone else's.
- name: Install Rust ${{ env.RUST_STABLE }}
run: |
rustup toolchain install "$RUST_STABLE" --profile minimal --component rustfmt,clippy
rustup default "$RUST_STABLE"
- uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
# A proto edited by hand, or a partial re-vendor, would otherwise ship an
# encoder that silently disagrees with the daemon on the other end. A
# field number that moved is not a compile error — it is a runtime
# disagreement — so this is the only thing that catches it early.
- name: Verify the vendored GoBGP protos
run: |
./ci/refresh-proto-manifest.sh
git diff --exit-code crates/bgp/proto/SOURCE.json \
|| { echo "::error::vendored protos do not match SOURCE.json"; exit 1; }
- run: cargo fmt --all --check
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo test --workspace
cross-build:
name: cross-build ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-gnu
- x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.RUST_STABLE }}
run: |
rustup toolchain install "$RUST_STABLE" --profile minimal --component clippy
rustup default "$RUST_STABLE"
rustup target add "${{ matrix.target }}"
- uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cross-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- run: cargo install cross --locked --version 0.2.5
# Per-triple clippy over --all-targets BEFORE the build. `cross build`
# covers lib and bins only, and the host clippy run above sees exactly one
# triple — so without this, a test that fails to compile on aarch64, or a
# cfg-gated path only reachable on musl, ships green.
#
# Run through `cross`, not bare cargo. `reqwest`'s rustls feature pulls
# aws-lc-sys, which needs a C compiler for the *target*, and the runner
# has no aarch64 or musl cross-toolchain. Linting in a different
# environment from the one you build in is how a lint goes green while
# the build goes red — which is exactly what happened the first time this
# ran.
- run: cross clippy --workspace --all-targets --all-features --target "${{ matrix.target }}" -- -D warnings
- run: cross build --workspace --release --target "${{ matrix.target }}"
package:
name: package ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
arch: amd64
- target: aarch64-unknown-linux-gnu
arch: arm64
steps:
- uses: actions/checkout@v7
- name: Install Rust ${{ env.RUST_STABLE }}
run: |
rustup toolchain install "$RUST_STABLE" --profile minimal
rustup default "$RUST_STABLE"
rustup target add "${{ matrix.target }}"
- uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-pkg-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
- run: cargo install cross --locked --version 0.2.5
- run: cargo install cargo-deb --locked --version 2.7.0
# Reproducible: the timestamp comes from the commit, not from the clock.
- name: Build release
run: |
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct HEAD) \
cross build --workspace --release --target "${{ matrix.target }}"
# Every pull request produces an installable artifact an operator can drop
# on a staging node. The version carries the short SHA so a test package is
# never mistaken for a release, and sorts below the release it precedes.
- name: Build .deb
id: deb
run: |
VERSION="$(cat VERSION)~git$(git rev-parse --short HEAD)"
SOURCE_DATE_EPOCH=$(git log -1 --pretty=%ct HEAD) \
cargo deb -p filterframe-cli \
--target "${{ matrix.target }}" \
--no-build --no-strip \
--deb-version "$VERSION"
echo "path=$(ls target/${{ matrix.target }}/debian/*.deb)" >> "$GITHUB_OUTPUT"
# The point of building packages on every PR rather than only at release:
# packaging breakage is caught by the change that caused it. Installing in
# a clean container is what makes this a test and not just an artifact.
- name: Verify the package installs
if: matrix.arch == 'amd64'
run: |
docker run --rm -v "$PWD:/w" -w /w debian:trixie-slim bash -euxo pipefail -c '
apt-get update -qq
apt-get install -y -qq --no-install-recommends systemd >/dev/null
dpkg -i "${{ steps.deb.outputs.path }}"
# The unit must be valid to systemd itself, not merely present.
# `systemd-analyze verify` exits non-zero on a malformed unit and on
# an ExecStart that does not exist.
systemd-analyze verify /lib/systemd/system/filterframe.service
# Installed disabled and stopped, deliberately.
! systemctl is-enabled filterframe 2>/dev/null
test -f /etc/filterframe/example.conf
test -x /usr/bin/filterframe
# Argument parsing works with no configuration present.
filterframe --version
filterframe version
filterframe --help >/dev/null
dpkg -r filterframe
'
- uses: actions/upload-artifact@v7
with:
name: filterframe-${{ matrix.arch }}-deb
path: target/${{ matrix.target }}/debian/*.deb
retention-days: 14
if-no-files-found: error