Secure releases & self-update¶
rtb-update will only replace the
running binary with an artefact it can verify. That guarantee depends
on how you build and publish releases.
Build artefacts the updater trusts¶
For every release archive, publish a minisign detached signature
alongside it, named <archive>.minisig.
The signature must be minisign's prehashed ED variant —
ed25519(BLAKE2b-512(archive)). The legacy pure-Ed variant is
rejected. That is deliberate: cargo-binstall requires the prehashed
variant, so accepting only ED means the signature rtb-update trusts
is byte-for-byte the one cargo-binstall trusts. Verification is
delegated to [minisign-verify], the same crate cargo-binstall uses,
rather than reimplemented — two implementations of one check is how the
two consumers would silently drift apart.
Prehashing is also what allows an HSM- or KMS-held key to sign an artefact of any size: the signer only ever sees a 64-byte digest.
A SHA-256 checksums file is optional. Set
ToolMetadata::update_checksums_asset to the asset's name and the
updater will additionally verify the archive against it, in
sha256sum format. Leave it unset — as rtb itself does — and the
signature is the sole authority. It already covers the archive bytes,
so a checksums file adds transport-error detection, not security.
Before swapping, the updater self-tests the staged binary: it runs it and checks it reports the expected version. Verification failure, a checksum mismatch, or a failed self-test all abort the swap and leave the running binary untouched — the binary on disk is always either the old version or the fully-verified new one, never a partial write.
Signing keys are pinned in ToolMetadata::update_public_keys, which is
any-one-verifies: a rotation ships as {old, new} and spans the
change with no dual-signing window.
How rtb's own releases are built¶
Tagging rtb-cli-bin-v* runs the dist stage in
.gitlab-ci.yml:
it builds the binary per target, signs each archive with
sigillum against an AWS
KMS-held Ed25519 key, uploads both to the generic package registry, and
attaches them to the GitLab release as asset links. No private key
material exists outside KMS; CI authenticates by OIDC and is permitted
only to sign.
Versions and tags are produced by release-plz, not cargo-release.
Supported targets¶
rtb currently ships x86_64-unknown-linux-gnu and
x86_64-unknown-linux-musl. macOS and Windows targets are not yet
built — the eventual target set is darwin-{aarch64,x86_64},
linux-{aarch64,x86_64,musl}, windows-{aarch64,x86_64}.
Verify a download yourself¶
Every rtb archive is signed under this key, which is also pinned in
the binary and published at
keys.phpboyscout.uk:
Download an archive and its .minisig from the
releases page,
then:
$ minisign -Vm rtb-<version>-<target>.tar.gz \
-P 'RWSkmyHppi7WSWp8Mii3RJByLf0eLckGPVajCrpX+zRD/3WWhXCibJ7y'
Signature and comment signature verified
minisign verifies the trusted comment as well as the archive, so the
comment cannot be altered without detection. Compare the key above
against the published copy before trusting a first download — pinning
it in the binary only protects upgrades, not the initial install.
Install with cargo binstall¶
rtb-cli-bin carries [package.metadata.binstall], so
cargo-binstall fetches
the signed release binary and verifies the signature before
installing — the same .minisig, checked against the same key:
$ cargo binstall rtb-cli-bin
INFO Verified signature for package 'rtb-cli-bin': … key:A49B21E9A62ED649
If verification fails, cargo-binstall refuses the binary and falls back
to building from source. That fallback is deliberate and load-bearing:
prebuilt binaries currently exist only for the two Linux targets above,
so every other platform reaches rtb through a source build. Disabling
the fallback would turn "slower install" into "no install".
cargo-binstall and rtb-update verify the same file with the same
crate ([minisign-verify]). Neither has its own opinion about what a
valid signature is, which is what keeps the two from drifting apart.
Wire the updater¶
use rtb_update::{CheckOutcome, RunOptions, Updater};
# async fn check(app: &rtb_app::app::App, provider: std::sync::Arc<dyn rtb_forge::ReleaseProvider>) -> Result<(), rtb_update::UpdateError> {
let updater = Updater::builder().app(app).provider(provider).build();
match updater.check().await? {
CheckOutcome::Newer { latest, .. } => {
println!("updating to {latest}");
updater.run(RunOptions::default()).await?;
}
CheckOutcome::UpToDate { .. } => println!("already current"),
CheckOutcome::Older { .. } => println!("local build ahead of upstream; not downgrading"),
}
# Ok(())
# }
The ReleaseProvider is selected by ToolMetadata::release_source
and wrapped in Arc<dyn ReleaseProvider> — downstream tools never
import octocrab or gitlab directly. See
rtb-forge (formerly rtb-vcs).
Airgapped updates¶
Updater::run_from_file verifies and swaps from a locally-staged
archive, for environments with no network path to the release host.
The same signature verification and self-test apply — staging a file
by hand does not bypass them.
Dry run¶
Set RunOptions { dry_run: true, .. } to verify and stage the new
binary into the cache dir without swapping — useful for validating a
release pipeline end to end.