Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ jobs:
if: steps.platform-check.outputs.skip != 'true' && matrix.platform == 'mac'
uses: dtolnay/rust-toolchain@stable

- name: Install cargo-auditable (Mac)
if: steps.platform-check.outputs.skip != 'true' && matrix.platform == 'mac'
run: cargo install cargo-auditable@0.7.5 --locked

- name: Build binaries (Mac)
if: steps.platform-check.outputs.skip != 'true' && matrix.platform == 'mac'
env:
Expand All @@ -157,7 +161,7 @@ jobs:
if [[ -n "$FEATURES" ]]; then
FEATURE_FLAG="--features $FEATURES"
fi
cargo build --profile ${{ matrix.profile }} $FEATURE_FLAG \
cargo auditable build --profile ${{ matrix.profile }} $FEATURE_FLAG \
--bin rbuilder \
--bin rbuilder-operator \
--bin tbv-bidding-service \
Expand Down
43 changes: 32 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,26 @@ else
BUILD_ENV =
endif

# The cargo-auditable embedded data is sorted and timestamp-free
.PHONY: install-cargo-auditable
install-cargo-auditable:
@command -v cargo-auditable >/dev/null 2>&1 || cargo install cargo-auditable@0.7.5 --locked
Comment on lines +74 to +77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command -v guard defeats the version pin that the PR is built around.

If any cargo-auditable is already on PATH β€” an older 0.6.x on a dev machine, a preinstalled one on a CI runner, whatever a future base image ships β€” the install is skipped and the build silently uses that version instead of 0.7.5. So the claim that "cargo-auditable is pinned to 0.7.5 in all build paths so every artifact embeds identically formatted data" doesn't actually hold for the make path.

Gate on the version rather than on presence:

Suggested change
# The cargo-auditable embedded data is sorted and timestamp-free
.PHONY: install-cargo-auditable
install-cargo-auditable:
@command -v cargo-auditable >/dev/null 2>&1 || cargo install cargo-auditable@0.7.5 --locked
CARGO_AUDITABLE_VERSION := 0.7.5
.PHONY: install-cargo-auditable
install-cargo-auditable:
@cargo install --list | grep -q '^cargo-auditable v$(CARGO_AUDITABLE_VERSION)' \
|| cargo install cargo-auditable@$(CARGO_AUDITABLE_VERSION) --locked

(The dangling # The cargo-auditable embedded data is sorted and timestamp-free comment sits above the install target where it reads as a claim about the installer; it belongs next to the build target or in the reproducibility section.)

Same pattern applies to install-cargo-audit below. I realize install-cargo-deb already uses command -v, so this is pre-existing repo style β€” but the pin matters more here, since the whole point is byte-identical embedded metadata across build paths.


.PHONY: build
build: ## Build (release version)
$(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --profile $(BUILD_PROFILE) --workspace
build: install-cargo-auditable ## Build (release version)
$(BUILD_ENV) cargo auditable build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --profile $(BUILD_PROFILE) --workspace

.PHONY: build-bid-scraper
build-bid-scraper: ## Build the bid-scraper binary (release version)
$(BUILD_ENV) cargo build --features "$(FEATURES)" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin bid-scraper --profile $(BUILD_PROFILE)
build-bid-scraper: install-cargo-auditable ## Build the bid-scraper binary (release version)
$(BUILD_ENV) cargo auditable build --features "$(FEATURES)" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin bid-scraper --profile $(BUILD_PROFILE)

.PHONY: build-rbuilder-operator
build-rbuilder-operator: ## Build the rbuilder-operator binary (release version)
$(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-operator --profile $(BUILD_PROFILE)
build-rbuilder-operator: install-cargo-auditable ## Build the rbuilder-operator binary (release version)
$(BUILD_ENV) cargo auditable build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-operator --profile $(BUILD_PROFILE)

.PHONY: build-rbuilder-rebalancer
build-rbuilder-rebalancer: ## Build the rbuilder-rebalancer binary (release version)
$(BUILD_ENV) cargo build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-rebalancer --profile $(BUILD_PROFILE)
build-rbuilder-rebalancer: install-cargo-auditable ## Build the rbuilder-rebalancer binary (release version)
$(BUILD_ENV) cargo auditable build --features "$(FEATURES) jemalloc-unprefixed" --locked $(if $(BUILD_TARGET),--target $(BUILD_TARGET)) --bin rbuilder-rebalancer --profile $(BUILD_PROFILE)

.PHONY: build-dev
build-dev: ## Build (debug version)
Expand All @@ -106,9 +111,10 @@ docker-image-test-relay: ## Build a test relay Docker image
##@ Debian Packages

# Define binary paths for smart dependencies
BID_SCRAPER_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/bid-scraper
RBUILDER_OPERATOR_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/rbuilder-operator
RBUILDER_REBALANCER_BIN := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)/rbuilder-rebalancer
BUILD_OUTPUT_DIR := target/$(if $(BUILD_TARGET),$(BUILD_TARGET)/)$(BUILD_PROFILE)
BID_SCRAPER_BIN := $(BUILD_OUTPUT_DIR)/bid-scraper
RBUILDER_OPERATOR_BIN := $(BUILD_OUTPUT_DIR)/rbuilder-operator
RBUILDER_REBALANCER_BIN := $(BUILD_OUTPUT_DIR)/rbuilder-rebalancer

.PHONY: install-cargo-deb
install-cargo-deb:
Expand Down Expand Up @@ -150,6 +156,21 @@ build-deb: build-deb-bid-scraper build-deb-rbuilder-operator build-deb-rbuilder-

##@ Dev

.PHONY: install-cargo-audit
install-cargo-audit:
@command -v cargo-audit >/dev/null 2>&1 || cargo install cargo-audit@0.22.2 --locked

.PHONY: audit-bin
audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build")
@fail=0; \
for bin in $(BUILD_OUTPUT_DIR)/*; do \
if [ -f "$$bin" ] && [ -x "$$bin" ]; then \
echo "==> $$bin"; \
cargo audit bin "$$bin" || fail=1; \
fi; \
done; \
exit $$fail
Comment on lines +163 to +172

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

audit-bin passes vacuously when there is nothing to scan.

The target doesn't depend on build, and if $(BUILD_OUTPUT_DIR) doesn't exist (or is empty) the glob stays unexpanded as the literal string target/.../*, [ -f ... ] is false, the loop body never runs, fail stays 0 and the target exits 0. A fresh checkout β€” or someone running make audit-bin before make build, or with a different BUILD_PROFILE/BUILD_TARGET than the one that was built β€” gets a green "audit passed" having scanned zero binaries. That's the worst failure mode for a security-scanning target.

Also worth handling: cargo audit bin on a binary with no .dep-v0 section falls back to guessing versions from panic strings, so stale non-auditable artifacts left in target/ produce low-confidence results silently mixed in with the real ones.

Suggest counting what was scanned and failing if it's zero:

Suggested change
.PHONY: audit-bin
audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build")
@fail=0; \
for bin in $(BUILD_OUTPUT_DIR)/*; do \
if [ -f "$$bin" ] && [ -x "$$bin" ]; then \
echo "==> $$bin"; \
cargo audit bin "$$bin" || fail=1; \
fi; \
done; \
exit $$fail
.PHONY: audit-bin
audit-bin: install-cargo-audit ## Scan built binaries for vulnerable dependencies (uses the audit data embedded by "make build")
@fail=0; count=0; \
for bin in $(BUILD_OUTPUT_DIR)/*; do \
if [ -f "$$bin" ] && [ -x "$$bin" ]; then \
echo "==> $$bin"; \
count=$$((count + 1)); \
cargo audit bin "$$bin" || fail=1; \
fi; \
done; \
if [ "$$count" -eq 0 ]; then \
echo "error: no binaries found in $(BUILD_OUTPUT_DIR); run 'make build' first" >&2; \
exit 1; \
fi; \
exit $$fail

Comment on lines +165 to +172

.PHONY: lint
lint: ## Run the linters
cargo fmt -- --check
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,19 @@ $ sha256sum target/release/rbuilder
d92ac33b94e16ed4a035b9dd52108fe78bd9bb160a91fced8e439f59b84c3207 target/release/rbuilder
```

### Auditable builds

Release builds (`make build`, the Docker images, and the released binaries) go through
[`cargo auditable`](https://github.com/rust-secure-code/cargo-auditable), which embeds the full
dependency list into each binary. To scan the built binaries against the RustSec advisory database:

```bash
make audit-bin
```

The embedded data can also be picked up by scanners such as `trivy`, `grype`, `syft`. Meaning that container
images built from these binaries can be scanned for Rust dependencies as well.
Comment on lines +178 to +189

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section says release builds go through cargo auditable, but the "Reproducible builds" section immediately above (lines 157–176) tells users to run plain cargo build --release and shows a specific expected sha256sum. Those two now describe divergent binaries: following the reproducible-builds instructions produces an artifact without the .dep-v0 section, and one whose hash won't match anything make build produces. Worth reconciling the two sections so it's clear which command corresponds to the released artifact.

Nit: "such as trivy, grype, syft. Meaning that container images…" β€” sentence fragment; syft, meaning that container images…`.

Comment on lines +188 to +189

---

## Release Stability and Development Process
Expand Down
5 changes: 3 additions & 2 deletions docker/Dockerfile.rbuilder
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ RUN set -eux; \
rm -rf /tmp/sccache.tar.gz /tmp/sccache-v0.8.2-${ARCH_TAG}

RUN cargo install cargo-chef --version ^0.1
RUN cargo install cargo-auditable@0.7.5 --locked


ENV CARGO_HOME=/usr/local/cargo
Expand Down Expand Up @@ -72,14 +73,14 @@ ARG FEATURES
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo build --release --features="$FEATURES" --package=${RBUILDER_BIN}
cargo auditable build --release --features="$FEATURES" --package=${RBUILDER_BIN}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth verifying once that the .dep-v0 section actually survives this build path. cargo-auditable injects via RUSTC_WORKSPACE_WRAPPER, and this stage inherits RUSTC_WRAPPER=sccache from base (line 43), so cargo ends up chaining cargo-auditable β†’ sccache β†’ rustc. That chaining is supposed to work, but combined with the cargo chef cook layer (line 66) building deps without the wrapper and the persistent sccache mount, a cache hit silently returning a non-instrumented artifact would produce a released image with no audit data and no error.

A cargo audit bin /app/target/release/${RBUILDER_BIN} (or a readelf -S | grep dep-v0) right after the build would turn that into a hard failure instead of a silent one.


FROM builder AS test-relay
ARG FEATURES
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=$SCCACHE_DIR,sharing=locked \
cargo build --release --features="$FEATURES" --package=test-relay
cargo auditable build --release --features="$FEATURES" --package=test-relay


# Runtime container for test-relay
Expand Down
1 change: 1 addition & 0 deletions docker/Dockerfile.reproducible
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ RUN apt-get -o Acquire::Check-Valid-Until=false update && \
libclang-dev \
protobuf-compiler \
cmake
RUN cargo install cargo-auditable@0.7.5 --locked

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the image that backs the reproducible Linux release artifacts, and the PR changes what it emits without re-verifying determinism. Two things to confirm before merging:

  1. cargo-auditable links an extra generated object into the binary. It's documented as sorted/deterministic, but this repo goes to unusual lengths for bit-identical output (-C metadata='', --build-id=none, --remap-path-prefix, SOURCE_DATE_EPOCH) β€” the object file is produced in a temp dir, so it's worth an actual two-run hash comparison rather than taking the docs' word for it.
  2. Note that everything above this line is pinned to a Debian snapshot repo specifically so the toolchain doesn't drift. cargo install ... --locked from crates.io is now the one un-snapshotted network fetch in the reproducible builder. It's pinned by version+lockfile so it should resolve identically, but it does mean the reproducible image's inputs are no longer sourced from a single snapshot.

Also a minor cost: this compiles cargo-auditable and its dependency tree from source on every cold build of this image, with no registry cache mount.

WORKDIR /build
COPY . .
RUN SOURCE_DATE=1730000000 make build && make build-deb
Expand Down
Loading