ci: verify the executor builds reproducibly from source
SECURITY.md asserted pipeline inputs were deterministic but that no digest comparison across rebuilds ran. verify-reproducibility builds the executor from source twice and compares both from-source builds and the already-pushed image, failing the pipeline if any of the three digests disagree.
For each of the standard and FIPS flavours, the job builds the Dockerfile.standard builder stage from source twice, --no-cache both times so a shared layer cache cannot pass for free, extracts /out/executor from each, pulls the image container-build-standard(-fips) already pushed for that flavour, and extracts /kaniko/executor from it. It fails unless all three SHA256 digests agree. scripts/verify-reproducibility.sh is what the job calls; README.md's Verification section points readers at the same script to check any published release from a workstation.
Proof, run locally on a workstation before writing the job: two independent --no-cache builds of the standard flavour produced identical digests, 3ee56c266e657134a003aa9d9df126fe337d1ffcf793db436de47899d655d30f both times. Same for FIPS, d8a4bd8ab84a12759eb2d202b25f3f14d21477528b320d51cf0a3eecc95eb9e8 both times. No Dockerfile flag change was needed; GOFLAGS=-trimpath and CGO_ENABLED=0, already in place, are sufficient on their own.
Comparing those against the published v1.25.19 and v1.25.19-fips tags mismatches locally (773effe0db2be00e1353aa2c4a887f1cc68f38c5a42cd30cdc4560b045d6574e and 06b0d98af8bf839011000321f71bdbeea7d34abe950f24541a667e1792ad5cc2), expected for two reasons that do not apply in CI: that tag predates go-overrides.txt, and those proofs ran as native arm64 builds while the published images are amd64. In the pipeline all three binaries, the two from-source builds and the pushed image, come from the same amd64 SaaS runner, so neither confound survives there.
One finding from the proofs shaped the script. Two runs an hour apart on the same workstation hashed differently, 3ee56c... and then 83cebb..., with no command-line difference. --no-cache defeats the layer cache but does not re-pull the FROM image, and the UBI9 base tag is multi-arch, so each build silently used whichever architecture of that tag sat in local storage: the first run an arm64 copy, the second an amd64 copy pulled days earlier. Rebuilding against each cached copy reproduced each digest exactly, and file confirmed one ARM aarch64 and one x86-64 binary. The script now passes --platform linux/amd64 on both builds, matching what ships, so a workstation run on any architecture reproduces the published bytes, emulated and slower on arm64.
Cost: a single --no-cache build of the builder stage measured about 4.5 minutes on a workstation (native arm64, not emulated). Each job in the matrix runs two of those plus one image pull, so roughly 10 minutes per job; the two-way matrix (standard, fips) runs in parallel like the existing container-build-* jobs, so this adds about 10 minutes to the verify stage's wall clock and about 20 runner-minutes total.
README.md documents the scripts/verify-reproducibility.sh recipe in its Verification section, SECURITY.md describes the check, and CHANGELOG.md records it under Unreleased.