filterframe: a DDoS mitigation control plane that only ever adds BGP objects #2
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
| 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 |