Ctrl K
ComunidadGuías…

scripts/test_m2image_sbom.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
import importlib.util
import json
import os
import tempfile
import unittest
from pathlib import Path
from unittest import mock

MODULE_PATH = Path(__file__).with_name("m2image_sbom.py")
spec = importlib.util.spec_from_file_location("m2image_sbom", MODULE_PATH)
assert spec and spec.loader
sbom = importlib.util.module_from_spec(spec)
spec.loader.exec_module(sbom)


class M2ImageSbomTests(unittest.TestCase):
    def test_parse_alpine_installed_database(self):
        packages = sbom.parse_alpine(
            "P:busybox\n"
            "V:1.37.0-r18\n"
            "A:x86_64\n"
            "L:GPL-2.0-only\n"
            "o:busybox\n"
            "\n"
            "P:linux-virt\n"
            "V:6.15.4-r0\n"
            "A:x86_64\n"
            "L:GPL-2.0-only\n"
            "o:linux-lts\n"
        )
        self.assertEqual([p["name"] for p in packages], ["busybox", "linux-virt"])
        self.assertEqual(packages[1]["version"], "6.15.4-r0")
        self.assertEqual(packages[1]["source"], "linux-lts")

    def test_parse_dpkg_ignores_non_installed_entries(self):
        packages = sbom.parse_dpkg(
            "Package: linux-image-6.17.0-10-generic\n"
            "Status: install ok installed\n"
            "Architecture: amd64\n"
            "Version: 6.17.0-10.10\n"
            "Source: linux-signed (6.17.0-10.10)\n"
            "\n"
            "Package: removed-package\n"
            "Status: deinstall ok config-files\n"
            "Architecture: amd64\n"
            "Version: 1.0\n"
        )
        self.assertEqual(len(packages), 1)
        self.assertEqual(packages[0]["name"], "linux-image-6.17.0-10-generic")
        self.assertEqual(packages[0]["source"], "linux-signed")

    def test_parse_rpm_tsv_retains_license_and_source_rpm(self):
        packages = sbom.parse_rpm_tsv(
            "kernel-core\t0:5.14.0-570.26.1.el9_6\tx86_64\tGPLv2\tkernel-5.14.0-570.26.1.el9_6.src.rpm\n"
        )
        self.assertEqual(packages[0]["license"], "GPLv2")
        self.assertTrue(packages[0]["source"].endswith(".src.rpm"))

    def test_rpm_gpg_pubkey_is_explicit_non_source_metadata(self):
        packages = sbom.parse_rpm_tsv(
            "gpg-pubkey\t0:350d275d-627e00a1\t(none)\tpubkey\t(none)\n"
        )
        self.assertEqual(packages[0]["source"], "")
        self.assertEqual(packages[0]["source_disposition"], "rpm-key-metadata")
        document = sbom.make_spdx(
            distribution="rocky",
            image_alias="rocky-9.8",
            image_version="9.8",
            architecture="x86_64",
            packages=packages,
        )
        self.assertIn("source-disposition=rpm-key-metadata", document["packages"][1]["comment"])
        self.assertNotIn("source-package=", document["packages"][1]["comment"])

    def test_rpm_non_key_package_without_source_rpm_is_rejected(self):
        with self.assertRaisesRegex(ValueError, "has no SOURCERPM"):
            sbom.parse_rpm_tsv("local-rpm\t0:3.0-1\tx86_64\tMIT\t(none)\n")

    def test_rpm_tsv_rejects_wrong_field_count(self):
        with self.assertRaisesRegex(ValueError, "expected 5 fields"):
            sbom.parse_rpm_tsv("kernel-core\t1.2\tx86_64\n")

    def test_spdx_is_sorted_and_records_actual_kernel_version(self):
        packages = [
            {"name": "zlib", "version": "1.3", "arch": "amd64", "license": "", "source": "zlib"},
            {
                "name": "linux-image-6.17.0-10-generic",
                "version": "6.17.0-10.10",
                "arch": "amd64",
                "license": "",
                "source": "linux-signed",
            },
        ]
        with mock.patch.dict(os.environ, {"SOURCE_DATE_EPOCH": "0"}):
            document = sbom.make_spdx(
                distribution="ubuntu",
                image_alias="ubuntu-26.04",
                image_version="26.04",
                architecture="x86_64",
                packages=packages,
            )
        self.assertEqual(document["creationInfo"]["created"], "1970-01-01T00:00:00Z")
        self.assertEqual([p["name"] for p in document["packages"][1:]], ["linux-image-6.17.0-10-generic", "zlib"])
        self.assertIn("linux-image-6.17.0-10-generic@6.17.0-10.10", document["annotations"][0]["comment"])
        self.assertEqual(document["relationships"][0]["relationshipType"], "DESCRIBES")
        self.assertTrue(all(r["relationshipType"] == "CONTAINS" for r in document["relationships"][1:]))

    def test_spdx_namespace_is_content_stable(self):
        packages_a = [
            {"name": "busybox", "version": "1.37.0-r18", "arch": "aarch64", "license": "GPL-2.0-only", "source": "busybox"},
            {"name": "linux-virt", "version": "6.15.4-r0", "arch": "aarch64", "license": "GPL-2.0-only", "source": "linux-lts"},
        ]
        packages_b = list(reversed(packages_a))
        with mock.patch.dict(os.environ, {"SOURCE_DATE_EPOCH": "123"}):
            a = sbom.make_spdx(
                distribution="alpine",
                image_alias="alpine-3.24.1",
                image_version="3.24.1",
                architecture="aarch64",
                packages=packages_a,
            )
            b = sbom.make_spdx(
                distribution="alpine",
                image_alias="alpine-3.24.1",
                image_version="3.24.1",
                architecture="aarch64",
                packages=packages_b,
            )
        self.assertEqual(a["documentNamespace"], b["documentNamespace"])
        self.assertEqual(json.dumps(a, sort_keys=True), json.dumps(b, sort_keys=True))
        purls = [p["externalRefs"][0]["referenceLocator"] for p in a["packages"][1:]]
        self.assertTrue(all(p.startswith("pkg:apk/alpine/") for p in purls))

    def test_duplicate_package_records_are_rejected(self):
        pkg = {"name": "busybox", "version": "1", "arch": "x86_64", "license": "", "source": ""}
        with self.assertRaisesRegex(ValueError, "duplicate"):
            sbom.make_spdx(
                distribution="alpine",
                image_alias="alpine",
                image_version="1",
                architecture="x86_64",
                packages=[pkg, dict(pkg)],
            )

    def test_cli_writes_spdx_json(self):
        with tempfile.TemporaryDirectory() as tmp:
            db = Path(tmp) / "installed"
            out = Path(tmp) / "sbom.spdx.json"
            db.write_text("P:busybox\nV:1.37.0-r18\nA:x86_64\nL:GPL-2.0-only\no:busybox\n", encoding="utf-8")
            with mock.patch.dict(os.environ, {"SOURCE_DATE_EPOCH": "0"}):
                rc = sbom.main(
                    [
                        "--format", "alpine",
                        "--distribution", "alpine",
                        "--image-alias", "alpine-3.24.1",
                        "--image-version", "3.24.1",
                        "--architecture", "x86_64",
                        "--package-db", str(db),
                        "--output", str(out),
                    ]
                )
            self.assertEqual(rc, 0)
            parsed = json.loads(out.read_text(encoding="utf-8"))
            self.assertEqual(parsed["spdxVersion"], "SPDX-2.3")
            self.assertEqual(parsed["packages"][1]["name"], "busybox")

    def test_source_identity_falls_back_to_binary_name_for_apk_and_dpkg(self):
        alpine = sbom.parse_alpine("P:local-apk\nV:1-r0\nA:x86_64\nL:MIT\n")
        dpkg = sbom.parse_dpkg(
            "Package: same-source-deb\n"
            "Status: install ok installed\n"
            "Architecture: amd64\n"
            "Version: 2.0-1\n"
        )
        self.assertEqual(alpine[0]["source"], "local-apk")
        self.assertEqual(dpkg[0]["source"], "same-source-deb")

    def test_parsed_source_identity_or_disposition_is_never_implicit(self):
        packages = []
        packages += sbom.parse_alpine("P:a\nV:1\n")
        packages += sbom.parse_dpkg(
            "Package: d\nStatus: install ok installed\nVersion: 1\nArchitecture: all\n"
        )
        packages += sbom.parse_rpm_tsv("gpg-pubkey\t0:1-1\t(none)\tpubkey\t(none)\n")
        self.assertTrue(
            all(pkg.get("source") or pkg.get("source_disposition") for pkg in packages)
        )

    def test_source_provenance_preserves_alpine_commit_and_dpkg_source_version(self):
        alpine = sbom.parse_alpine(
            "P:busybox\nV:1.37.0-r31\nA:x86_64\nL:GPL-2.0-only\n"
            "o:busybox\nc:0123456789abcdef0123456789abcdef01234567\n"
        )[0]
        deb = sbom.parse_dpkg(
            "Package: linux-image-virtual\nStatus: install ok installed\n"
            "Architecture: amd64\nVersion: 7.0.0-30.30\n"
            "Source: linux-meta (7.0.0.30.30)\n"
        )[0]
        self.assertEqual(alpine["source_version"], "1.37.0-r31")
        self.assertEqual(alpine["source_commit"], "0123456789abcdef0123456789abcdef01234567")
        self.assertEqual(deb["source"], "linux-meta")
        self.assertEqual(deb["source_version"], "7.0.0.30.30")

    def test_spdx_emits_source_version_and_commit_evidence(self):
        packages = sbom.parse_alpine(
            "P:busybox\nV:1.37.0-r31\nA:x86_64\nL:GPL-2.0-only\n"
            "o:busybox\nc:0123456789abcdef0123456789abcdef01234567\n"
        )
        document = sbom.make_spdx(
            distribution="alpine",
            image_alias="alpine-3.24.1",
            image_version="3.24.1",
            architecture="x86_64",
            packages=packages,
        )
        comment = document["packages"][1]["comment"]
        self.assertIn("source-version=1.37.0-r31", comment)
        self.assertIn("source-commit=0123456789abcdef0123456789abcdef01234567", comment)


if __name__ == "__main__":
    unittest.main()