diff --git a/build-apt-repo.sh b/build-apt-repo.sh index 9707b0b..ee93f04 100755 --- a/build-apt-repo.sh +++ b/build-apt-repo.sh @@ -13,6 +13,15 @@ GPG_KEY_ID="xcat@megware.com" SKIP_SIGN=0 DRY_RUN=0 GENESIS_RELEASE="" +GENESIS_CHECKSUMS="" +GENESIS_VERIFIER="" + +cleanup() { + if [[ -n "$GENESIS_CHECKSUMS" ]]; then + rm -f -- "$GENESIS_CHECKSUMS" + fi +} +trap cleanup EXIT declare -A CODENAME_MAP=( [ubuntu22.04]=jammy @@ -86,10 +95,8 @@ APT_DIR="${APT_DIR:-$REPO_ROOT/repos/apt}" if [[ -n "$GENESIS_RELEASE" ]]; then [[ -d "$GENESIS_RELEASE" ]] || die "Genesis release directory not found: $GENESIS_RELEASE" GENESIS_RELEASE="$(cd "$GENESIS_RELEASE" && pwd)" - verifier="$SCRIPT_DIR/genesis-openembedded/verify-release" - [[ -x "$verifier" ]] || die "Genesis release verifier not found: $verifier" - "$verifier" --complete --format deb "$GENESIS_RELEASE" - echo "Genesis release: $GENESIS_RELEASE" + GENESIS_VERIFIER="$SCRIPT_DIR/genesis-openembedded/verify-release" + [[ -x "$GENESIS_VERIFIER" ]] || die "Genesis release verifier not found: $GENESIS_VERIFIER" fi # Default to all known versions when no DIST arg was given; otherwise validate @@ -110,6 +117,21 @@ command -v apt-ftparchive >/dev/null 2>&1 \ || die "apt-ftparchive not found. Install: sudo apt-get install apt-utils" command -v gpg >/dev/null 2>&1 \ || die "gpg not found. Install: sudo apt-get install gnupg" +command -v cmp >/dev/null 2>&1 \ + || die "cmp not found. Install: sudo apt-get install diffutils" +command -v dpkg-deb >/dev/null 2>&1 \ + || die "dpkg-deb not found. Install: sudo apt-get install dpkg" +command -v sha256sum >/dev/null 2>&1 \ + || die "sha256sum not found. Install: sudo apt-get install coreutils" + +if [[ -n "$GENESIS_RELEASE" ]]; then + GENESIS_CHECKSUMS=$(mktemp "${TMPDIR:-/tmp}/xcat-genesis-checksums.XXXXXX") + cp -- "$GENESIS_RELEASE/SHA256SUMS" "$GENESIS_CHECKSUMS" + "$GENESIS_VERIFIER" --complete --format deb "$GENESIS_RELEASE" + cmp -s "$GENESIS_CHECKSUMS" "$GENESIS_RELEASE/SHA256SUMS" \ + || die "Genesis release changed during verification" + echo "Genesis release: $GENESIS_RELEASE" +fi if [[ $SKIP_SIGN -eq 0 ]]; then if ! gpg --list-secret-keys "$GPG_KEY_ID" >/dev/null 2>&1; then @@ -168,6 +190,27 @@ copy_deb() { ln "$source" "$destination" 2>/dev/null || cp "$source" "$destination" } +copy_genesis_deb() { + local source="$1" + local directory="$2" + local name destination relative expected actual + name=$(basename "$source") + destination="$directory/$name" + relative="deb/$name" + if [[ -e "$destination" ]]; then + cmp -s "$source" "$destination" \ + || die "Package collision with different content: $destination" + else + cp -- "$source" "$destination" + fi + expected=$(awk -v path="$relative" '$2 == path { print $1 }' "$GENESIS_CHECKSUMS") + [[ -n "$expected" ]] || die "Missing verified checksum for $relative" + actual=$(sha256sum "$destination") + actual=${actual%% *} + [[ "$actual" = "$expected" ]] \ + || die "Collected Genesis package checksum mismatch: $destination" +} + for ver in "${SELECTED_VERS[@]}"; do codename="${CODENAME_MAP[$ver]}" src="$APT_DIR/$ver" @@ -179,7 +222,7 @@ for ver in "${SELECTED_VERS[@]}"; do done if [[ -n "$GENESIS_RELEASE" ]]; then for deb in "$GENESIS_RELEASE"/deb/*.deb; do - copy_deb "$deb" "$dst" + copy_genesis_deb "$deb" "$dst" done fi fi diff --git a/genesis-openembedded/lib/XCAT/GenesisRelease.pm b/genesis-openembedded/lib/XCAT/GenesisRelease.pm index 892f948..6c87015 100644 --- a/genesis-openembedded/lib/XCAT/GenesisRelease.pm +++ b/genesis-openembedded/lib/XCAT/GenesisRelease.pm @@ -13,10 +13,12 @@ our @EXPORT_OK = qw( deb_package_name read_release_manifest rpm_package_name + validated_release_checksums validate_architecture validate_complete_release validate_export validate_release + verify_release_file ); my @ARCHITECTURES = qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64); @@ -126,7 +128,7 @@ sub _verify_checksums { for my $name (keys %{$checksums}) { die "Checksum names a missing file: $name\n" unless $files{$name}; } - return 1; + return $checksums; } sub validate_export { @@ -256,4 +258,23 @@ sub validate_complete_release { return $manifest; } +sub validated_release_checksums { + my ($directory) = @_; + validate_release($directory); + return _read_checksums($directory); +} + +sub verify_release_file { + my ($checksums, $relative, $path) = @_; + die "Missing verified checksum for $relative\n" + unless ref($checksums) eq 'HASH' && exists($checksums->{$relative}); + die "Invalid collected release file: $path\n" unless -f $path && !-l $path; + open(my $fh, '<:raw', $path) or die "Cannot read $path: $!\n"; + my $digest = Digest::SHA->new(256)->addfile($fh)->hexdigest; + close($fh) or die "Cannot close $path: $!\n"; + die "Collected release file checksum mismatch: $path\n" + unless $digest eq $checksums->{$relative}; + return 1; +} + 1; diff --git a/mockbuild-all.pl b/mockbuild-all.pl index 867c8c1..dc723be 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -9,9 +9,15 @@ use File::Copy qw(copy); use File::Find qw(find); use File::Glob qw(bsd_glob); use File::Path qw(make_path); +use FindBin; use Getopt::Long qw(GetOptions); use Parallel::ForkManager; use POSIX qw(strftime); +use lib "$FindBin::Bin/genesis-openembedded/lib"; +use XCAT::GenesisRelease qw( + validated_release_checksums + verify_release_file +); my $script_dir = abs_path(dirname(__FILE__)); my $repo_root = abs_path($script_dir); @@ -38,6 +44,7 @@ my $skip_genesis = 0; my $skip_createrepo = 0; my $skip_tarball = 0; my $genesis_release = ''; +my $genesis_release_checksums; my $scrub_all_chroots = 0; my $dry_run = 0; my @extra_collect_dirs; @@ -139,14 +146,18 @@ require_command('rpmsign') if $gpg_sign; require_command('gpg') if $gpg_sign; if ($genesis_release ne '') { - require_command('cmp'); $genesis_release = abs_path($genesis_release) or die "Cannot resolve --genesis-release directory\n"; die "Genesis release directory not found: $genesis_release\n" unless -d $genesis_release; my $verifier = "$repo_root/genesis-openembedded/verify-release"; die "Genesis release verifier not found: $verifier\n" unless -x $verifier; + my $checksums_before = validated_release_checksums($genesis_release); run_argv($^X, $verifier, '--complete', '--format', 'rpm', $genesis_release); + my $checksums_after = validated_release_checksums($genesis_release); + die "Genesis release changed during verification\n" + unless checksum_sets_match($checksums_before, $checksums_after); + $genesis_release_checksums = $checksums_before; $skip_genesis = 1; } @@ -933,23 +944,27 @@ sub remove_genesis_packages { sub assert_genesis_release_copied { my ($binary_directory, $source_directory) = @_; for my $entry ( - [ "$genesis_release/rpm", $binary_directory ], - [ "$genesis_release/srpm", $source_directory ], + [ 'rpm', $binary_directory ], + [ 'srpm', $source_directory ], ) { - my ($source_root, $destination_root) = @{$entry}; - for my $source (bsd_glob("$source_root/*.rpm")) { - my $destination = "$destination_root/" . basename($source); - die "Genesis package was not collected: $destination\n" unless -f $destination; - die "Collected Genesis package differs from release: $destination\n" - unless files_match($source, $destination); + my ($prefix, $destination_root) = @{$entry}; + for my $relative ( + sort grep { /^\Q$prefix\E\/[^\/]+\.rpm\z/ } keys %{$genesis_release_checksums} + ) + { + my $destination = "$destination_root/" . basename($relative); + verify_release_file($genesis_release_checksums, $relative, $destination); } } } -sub files_match { +sub checksum_sets_match { my ($left, $right) = @_; - return 0 unless -f $left && -f $right; - return system('cmp', '-s', $left, $right) == 0; + return 0 unless keys(%{$left}) == keys(%{$right}); + for my $name (keys %{$left}) { + return 0 unless exists($right->{$name}) && $left->{$name} eq $right->{$name}; + } + return 1; } sub collect_rpms {