From e856a2d378aecfd4f69ceca4d0141fe5c1edd063 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 12 Aug 2026 17:11:39 -0300 Subject: [PATCH] fix(build): repo gate signature must strictly match the CLI key; document gate in BUILD.md Per review of the gate design: - Signature: fail (SIGKEY) if --gpg-key-name cannot be resolved to a fingerprint, so the gate always confirms the repo was signed by EXACTLY the CLI key -- never a soft pass. (EL already dies loudly on a duplicate rpm version via rpm_version; Ubuntu now matches.) - Document the gate + its intentional idiosyncrasies (what 'the repo' is, duplicate=hard error, signature identity) in BUILD.md. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- BUILD.md | 29 +++++++++++++++++++++++++++++ mockbuild-all.pl | 17 ++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/BUILD.md b/BUILD.md index c3ea15b..40ee7e8 100644 --- a/BUILD.md +++ b/BUILD.md @@ -320,6 +320,35 @@ prove t/ # or: perl t/mockbuild-all.t The RPM-identity / `cross_copy_genesis` cases build tiny fixture rpms and are skipped automatically if `rpmbuild` is unavailable. +# Repository verification gate + +After each per-target repo is built + signed, `mockbuild-all.pl` runs a **manifest-driven gate** that +fails the build if the published repo is incomplete or mis-signed. It uses `packages-manifest.conf` as +the single source of truth and is layered so the decision logic is pure and unit-tested +(`MockBuildUtils::verify_repo_packages` / `verify_repo_signature`), separate from the disk/gpg I/O. + +- **Runs automatically** at the end of `deploy_target` (per `rh/` cell). Suppress with + `--no-verify-repo`. Verify an already-built repo out of band with `--verify-repo=` + (target derived from the `rh/` path, or pass `--target`; manifest from `/ + packages-manifest.conf`; key/home from `--gpg-key-name`/`--gpg-home`). +- **Completeness:** every package the target's manifest section requires (after `required_pkgs` + skip-filtering) must be present with a version satisfying its pin. +- **Signature:** the repo's `repodata/repomd.xml.asc` must be a *good* signature whose **primary-key + fingerprint equals the fingerprint of `--gpg-key-name`** — i.e. the repo was signed by exactly the + CLI key. Expired/revoked keys and expired signatures are rejected (not just `VALIDSIG`). If the CLI + key cannot be resolved to a fingerprint (not in the keyring) the gate fails (`SIGKEY`), never passes. + +**Semantic idiosyncrasies (intentional, and mirrored in the Ubuntu `sbuild-all.pl` gate):** + +- **What "the repo" is:** the EL gate reads the **binary rpm files** in the per-target dir (via + `rpm_version`); the Ubuntu gate reads the **published `binary-/Packages` index**. Both check + the artifact that ships; they differ only in the RHEL-vs-Debian notion of "the repository". +- **Duplicate = hard error:** if a required package appears with **two distinct versions** (a stale + artifact not cleaned before the build), the gate **dies loudly** rather than silently picking one — + identical on both EL (`rpm_version`) and Ubuntu (`parse_packages_index`). +- **Version pins** are the manifest's *upstream* version; the Debian gate strips the epoch/revision + (`deb_upstream_version`) before comparing, the EL gate compares `%{version}` directly. + # References - [mock project repository](https://github.com/rpm-software-management/mock) diff --git a/mockbuild-all.pl b/mockbuild-all.pl index 675460c..76979a9 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -1168,11 +1168,18 @@ sub verify_target_repo { # Skipped with a printed note only when no gpg key/home is configured (nothing to check against). if ($gpg_sign || $gpg_home ne '') { require_command('gpg'); - my $repomd = "$dir/repodata/repomd.xml"; - my $asc = "$repomd.asc"; - my %exp_sig = ('repomd' => gpg_key_fingerprint($gpg_key_name, $gpg_home)); - my %obs_sig = ('repomd' => repomd_observed_signer($asc, $repomd, $gpg_home)); - push @problems, verify_repo_signature(\%exp_sig, \%obs_sig); + my $repomd = "$dir/repodata/repomd.xml"; + my $asc = "$repomd.asc"; + my $exp_fpr = gpg_key_fingerprint($gpg_key_name, $gpg_home); + # STRICT: the CLI key MUST resolve to a fingerprint so we can confirm it signed the repo. If it + # does not (not in the keyring), we cannot verify -> hard fail, never a presence-only pass. + if ($exp_fpr !~ /^[0-9A-Fa-f]{16,}$/) { + push @problems, "SIGKEY: cannot resolve --gpg-key-name '$gpg_key_name' to a fingerprint (in the $gpg_home keyring?)"; + } else { + my %exp_sig = ('repomd' => $exp_fpr); + my %obs_sig = ('repomd' => repomd_observed_signer($asc, $repomd, $gpg_home)); + push @problems, verify_repo_signature(\%exp_sig, \%obs_sig); + } } else { print "[verify-repo] $tgt: no gpg key/home configured -- skipping repomd signature check\n"; }