#!/usr/bin/env python3 """Focused refusal and preservation checks for the ISO FIPS derivative.""" import importlib.util import io import hashlib from pathlib import Path import sys import tarfile import tempfile import unittest SOURCE = Path(__file__).with_name("build-iso-fips-package.py") spec = importlib.util.spec_from_file_location("iso_fips_package", SOURCE) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) def tar(members): buffer = io.BytesIO() with tarfile.open(fileobj=buffer, mode="w:xz") as archive: for name, content in members: entry = tarfile.TarInfo("./" + name) entry.mode = 0o644 entry.uid = entry.gid = 0 entry.size = len(content) archive.addfile(entry, io.BytesIO(content)) return buffer.getvalue() def fixture(*, version="0.4.1-1", nft=True, declared=True, md5sums=False): controls = [ ("control", f"Package: fips\nVersion: {version}\nArchitecture: amd64\n".encode()), ("conffiles", b"/etc/fips/hosts\n" + (b"/etc/fips/fips.nft\n" if declared else b"")), ("postinst", b"#!/bin/sh\nexit 0\n"), ] if md5sums: controls.append(("md5sums", b"deadbeef etc/fips/fips.nft\n")) data = [("etc/fips/hosts", b"hosts\n")] if nft: data.append(("etc/fips/fips.nft", b"old nft\n")) return module.write_ar([ ("debian-binary", b"2.0\n"), ("control.tar.xz", tar(controls)), ("data.tar.xz", tar(data)), ]) class DerivativeTest(unittest.TestCase): def test_only_declared_changes(self): result, receipt = module.derive(fixture()) members = module.read_ar(result) controls = dict((module.normalized(item.name), content) for item, content in module.tar_items(members[1][1])) data = dict((module.normalized(item.name), content) for item, content in module.tar_items(members[2][1])) self.assertIn(b"Version: 0.4.1-1+archy.iso1", controls["control"]) self.assertEqual(controls["conffiles"], b"/etc/fips/hosts\n") self.assertEqual(controls["postinst"], b"#!/bin/sh\nexit 0\n") self.assertEqual(data, {"etc/fips/hosts": b"hosts\n"}) self.assertEqual(receipt["unchanged_data_members"], 1) def test_missing_file_refused(self): with self.assertRaisesRegex(ValueError, "default fips.nft"): module.derive(fixture(nft=False)) def test_missing_conffile_refused(self): with self.assertRaisesRegex(ValueError, "conffile declaration"): module.derive(fixture(declared=False)) def test_unreviewed_version_refused(self): with self.assertRaisesRegex(ValueError, "package version"): module.derive(fixture(version="0.5.3")) def test_unreviewed_md5sums_refused(self): with self.assertRaisesRegex(ValueError, "md5sums"): module.derive(fixture(md5sums=True)) def test_reviewed_handoff_version(self): _, receipt = module.derive( fixture(version="0.5.3+archy.handoff1"), "0.5.3+archy.handoff1", "0.5.3+archy.handoff1.iso1") self.assertEqual(receipt["new_version"], "0.5.3+archy.handoff1.iso1") def test_dangling_output_symlink_refused(self): package = fixture() digest = hashlib.sha256(package).hexdigest() with tempfile.TemporaryDirectory() as directory: root = Path(directory) source, output, receipt = root / "source.deb", root / "output.deb", root / "receipt.json" source.write_bytes(package) output.symlink_to(root / "missing") old_argv = sys.argv module.REVIEWED_INPUTS[digest] = ("0.4.1-1", "0.4.1-1+archy.iso1") sys.argv = [str(SOURCE), "--input", str(source), "--expected-input-sha256", digest, "--output", str(output), "--receipt", str(receipt)] try: with self.assertRaises(FileExistsError): module.main() finally: sys.argv = old_argv del module.REVIEWED_INPUTS[digest] self.assertFalse((root / "missing").exists()) self.assertFalse(receipt.exists()) if __name__ == "__main__": unittest.main()