From 0158726999d18cf10bd4f8883e1b4d9aa21cc749 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Fri, 10 Mar 2023 18:26:39 -0500 Subject: [PATCH] Add script to check newly added packages against buildinfo files buildinfo files contain package checksums in a machine-readable format, so script checking newly added packages against those. This will be added to CI for securedrop-apt-test and securedrop-apt-prod. The main iffy part of this is how it compares against "origin/main", but I think for PRs it'll mostly do the right thing. We only check new packages because old ones don't have buildinfo published. Maybe once we no longer have any legacy cases left, we just check everything in the repository. Likely there are more checks that could be added, but this is a start. Refs . --- scripts/check-buildinfo | 89 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100755 scripts/check-buildinfo diff --git a/scripts/check-buildinfo b/scripts/check-buildinfo new file mode 100755 index 00000000..2d54e15e --- /dev/null +++ b/scripts/check-buildinfo @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Check newly added packages against their buildinfo files + +Example: + ./check-buildinfo package.deb package.buildinfo + +""" +import argparse +import hashlib +import subprocess +import sys +from pathlib import Path +from typing import List + +from debian.deb822 import BuildInfo + + +def lookup_buildinfos(buildinfos: Path) -> dict: + """Extract checksums out of every buildinfo file we can find""" + data = {} + for path in buildinfos.glob("**/*.buildinfo"): + info = BuildInfo(path.read_text()) + for details in info['Checksums-Sha256']: + if details['name'].endswith('.deb'): + data[details['name']] = details['sha256'] + return data + + +def check_package(package: Path, buildinfos: dict) -> bool: + """Verify the package's checksum matches buildinfo""" + try: + expected = buildinfos[str(package.name)] + except IndexError: + print(f"ERROR: Unable to find buildinfo containing {package.name}") + return False + actual = hashlib.sha256(package.read_bytes()).hexdigest() + if actual == expected: + print(f"OK: got expected checksum {actual} for {package.name}") + return True + else: + print(f"ERROR: package is {actual}, buildinfo has {expected} for {package.name}") + return False + + +def added_files(against="origin/main") -> List[Path]: + """Get list of added files compared to main""" + added = [] + output = subprocess.check_output([ + "git", "log", + # Only list added files + "--diff-filter=A", + # Set our terminal width to be huge so it doesn't truncate + "--stat=999999", + # Output nothing else + "--pretty=", + f"{against}..HEAD" + ], text=True) + for line in output.splitlines(): + if "|" not in line: + continue + path = Path(line.split("|", 1)[0].strip()) + if path.exists(): + # Wasn't deleted in an intermediate commit + added.append(path) + added.sort(key=lambda x: x.name) + return added + + +def main(): + parser = argparse.ArgumentParser( + description="Check packages against their buildinfo files" + ) + parser.add_argument("buildinfos", type=Path, help="Folder with buildinfo files") + args = parser.parse_args() + buildinfos = lookup_buildinfos(args.buildinfos) + status = 0 + added = added_files() + if not added: + print("No new packages detected.") + sys.exit(0) + for package in added: + if not check_package(package, buildinfos): + status = 1 + sys.exit(status) + + +if __name__ == '__main__': + main()