mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
149 lines
7.0 KiB
Python
149 lines
7.0 KiB
Python
# Copyright 2026 514 LLC d/b/a OpenGlow
|
|
# Written by Scott Wiederhold
|
|
# https://community.openglow.org
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
import copy
|
|
import json
|
|
import unittest
|
|
|
|
import helpers
|
|
from forgetest import artifact, campaign, catalog
|
|
from forgetest import manifest as manifest_mod
|
|
from test_campaign import rec_campaign, rec_result
|
|
|
|
|
|
class ArtifactTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.man = helpers.make_manifest()
|
|
self.core = helpers.make_test("image.health", [("linux-fslc", "**")], always=True)
|
|
self.ui = helpers.make_test("forgectrl.panel", [("forgectrl", "src/ui.c")])
|
|
self.cool = helpers.make_test("cooling.flow", [("forgectrl", "src/cool.c")])
|
|
self.reg = helpers.registry(self.core, self.ui, self.cool)
|
|
self.tests = list(self.reg.values())
|
|
self.chash = catalog.catalog_hash(self.reg)
|
|
# campaign 1 on the base image: everything passes
|
|
self.recs = [rec_campaign("c1", self.man, self.chash, "2026-08-20T10:00:00Z")]
|
|
for i, t in enumerate(self.tests):
|
|
self.recs.append(rec_result("c1", t, self.man, "PASS", "2026-08-20T10:0%d:00Z" % (i + 1)))
|
|
# image 2: cool.c changed; campaign 2 reruns core + cool
|
|
self.man2 = helpers.with_file(self.man, "forgectrl", "src/cool.c", "cool v2")
|
|
self.recs.append(rec_campaign("c2", self.man2, self.chash, "2026-08-21T10:00:00Z"))
|
|
self.recs.append(rec_result("c2", self.core, self.man2, "PASS", "2026-08-21T10:01:00Z"))
|
|
self.recs.append(rec_result("c2", self.cool, self.man2, "PASS", "2026-08-21T10:02:00Z"))
|
|
|
|
def build(self, man=None):
|
|
man = man or self.man2
|
|
st = campaign.compute(self.recs, self.tests, man, self.chash)
|
|
return artifact.build(st, self.tests, man, self.recs, self.chash), st
|
|
|
|
def release_manifest(self, man=None):
|
|
"""The release build's manifest: same identity, no forgetest component."""
|
|
man = man or self.man2
|
|
data = copy.deepcopy(man.data)
|
|
data["components"].pop("forgetest", None)
|
|
data["image"] = {"name": "forgefirm-image", "version": "v0.1.0"}
|
|
return manifest_mod.Manifest(data)
|
|
|
|
def test_build_and_verify(self):
|
|
art, st = self.build()
|
|
self.assertTrue(art["authorized"])
|
|
by = {t["id"]: t for t in art["tests"]}
|
|
self.assertTrue(by["forgectrl.panel"]["inherited"])
|
|
self.assertEqual(by["forgectrl.panel"]["record"]["campaign"], "c1")
|
|
self.assertFalse(by["image.health"]["inherited"])
|
|
text = artifact.to_json(art)
|
|
art2 = json.loads(text)
|
|
ok, rows, problems = artifact.verify(art2, self.release_manifest(), self.tests, self.chash,
|
|
expect_machine="glowforge")
|
|
self.assertEqual(problems, [])
|
|
self.assertTrue(ok)
|
|
md = artifact.to_markdown(art)
|
|
self.assertIn("Release authorized: YES", md)
|
|
self.assertIn("forgectrl.panel", md)
|
|
|
|
def test_tamper_detected(self):
|
|
art, _ = self.build()
|
|
art2 = json.loads(artifact.to_json(art))
|
|
art2["tests"][1]["record"]["result"] = "PASS"
|
|
art2["counts"]["required"] = 0
|
|
art2["authorized"] = True
|
|
art2["tests"][2]["record"]["ts"] = "2026-08-21T10:02:01Z"
|
|
ok, rows, problems = artifact.verify(art2, self.release_manifest(), self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertIn("self-hash", problems[0])
|
|
|
|
def test_release_differs_in_covered_file(self):
|
|
art, _ = self.build()
|
|
rel = self.release_manifest(helpers.with_file(self.man2, "forgectrl", "src/ui.c", "ui v3"))
|
|
ok, rows, problems = artifact.verify(art, rel, self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("forgectrl.panel" in p and "fingerprint" in p for p in problems), problems)
|
|
# an uncovered change is fine
|
|
rel2 = self.release_manifest(helpers.with_file(self.man2, "forgectrl", "README.md", "docs"))
|
|
ok, rows, problems = artifact.verify(art, rel2, self.tests, self.chash)
|
|
self.assertTrue(ok, problems)
|
|
|
|
def test_release_platform_differs(self):
|
|
art, _ = self.build()
|
|
rel = self.release_manifest(helpers.with_platform(self.man2, dtb={"glowforge.dtb": "0" * 64}))
|
|
ok, rows, problems = artifact.verify(art, rel, self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertEqual(len([r for r in rows if not r["ok"]]), 3)
|
|
|
|
def test_unauthorized_artifact(self):
|
|
self.recs.append(rec_result("c2", self.ui, self.man2, "FAIL", "2026-08-21T10:03:00Z"))
|
|
art, st = self.build()
|
|
self.assertFalse(art["authorized"])
|
|
ok, rows, problems = artifact.verify(art, self.release_manifest(), self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("does not claim authorization" in p for p in problems))
|
|
|
|
def test_core_inherited_refused(self):
|
|
art, _ = self.build()
|
|
# forge an artifact whose core record is marked inherited (self-hash recomputed)
|
|
body = json.loads(artifact.to_json(art))
|
|
body.pop("sha256")
|
|
for t in body["tests"]:
|
|
if t["id"] == "image.health":
|
|
t["inherited"] = True
|
|
body["sha256"] = manifest_mod.sha256_text(manifest_mod.canonical(body))
|
|
ok, rows, problems = artifact.verify(body, self.release_manifest(), self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("always-required" in p for p in problems), problems)
|
|
|
|
def test_invalidate_epoch(self):
|
|
art, _ = self.build()
|
|
body = json.loads(artifact.to_json(art))
|
|
body.pop("sha256")
|
|
body["invalidate"] = {"t": "invalidate", "ts": "2026-08-20T12:00:00Z", "reason": "tube"}
|
|
body["sha256"] = manifest_mod.sha256_text(manifest_mod.canonical(body))
|
|
ok, rows, problems = artifact.verify(body, self.release_manifest(), self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("predates the invalidate" in p for p in problems), problems)
|
|
|
|
def test_catalog_changed(self):
|
|
art, _ = self.build()
|
|
reg = helpers.registry(self.core, self.ui, self.cool, helpers.make_test("new.one", []))
|
|
tests = list(reg.values())
|
|
ok, rows, problems = artifact.verify(art, self.release_manifest(), tests, catalog.catalog_hash(reg))
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("catalog changed" in p for p in problems))
|
|
self.assertTrue(any("new.one" in p for p in problems))
|
|
|
|
def test_implementation_changed(self):
|
|
art, _ = self.build()
|
|
body = json.loads(artifact.to_json(art))
|
|
body.pop("sha256")
|
|
for t in body["tests"]:
|
|
if t["id"] == "cooling.flow":
|
|
t["source_sha"] = "0" * 64
|
|
body["sha256"] = manifest_mod.sha256_text(manifest_mod.canonical(body))
|
|
ok, rows, problems = artifact.verify(body, self.release_manifest(), self.tests, self.chash)
|
|
self.assertFalse(ok)
|
|
self.assertTrue(any("implementation changed" in p for p in problems), problems)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|