Files
computer-playbook/tests/unit/python/utils/security/test_database.py
T
kevinveenbirkenbachandClaude Opus 5.5 043e001669 feat(ci): rotate the vulnerability engine per row and hand its databases over
Each CI deploy row now draws one vulnerability engine (trivy, grype or osv)
as a fifth rotation axis, shown in the job name next to distro and
filesystem (🔬 axis, 🔺 trivy, 🍇 grype, 🐙 osv). The deploy-time scan of
sys-ctl-hlth-runtime runs that engine behind Syft and the Grype runtime pass.

- A vulnerability-db job keeps one Actions cache per database and day and
  downloads only the missing ones. Every deploy job restores Grype plus its
  drawn engine, fails on a cache miss and never downloads a database itself.
- Local stacks share one host directory, INFINITO_VULNERABILITY_DB_HOST_PATH,
  mounted read-only at INFINITO_VULNERABILITY_DB_DIR in compose.yml and in
  every swarm node. A local deploy without a pinned engine draws one and
  refreshes only the databases it reads, at most once a day, under a lock.
- A pinned engine whose database is missing stops the deploy before it
  starts.

Part of #1324

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 02:44:58 +01:00

125 lines
4.1 KiB
Python

from __future__ import annotations
import os
import tempfile
import time
from pathlib import Path
from types import SimpleNamespace
from unittest import TestCase, main, mock
from utils.security import database
from utils.security.pipeline import pipeline
P = pipeline()
def _touch(path: Path, age: float) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("db", encoding="utf-8")
stamp = time.time() - age
os.utime(path, (stamp, stamp))
class _Root(TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.root = Path(self.tmp.name)
def tearDown(self):
self.tmp.cleanup()
class TestOsvUrl(TestCase):
def test_an_ecosystem_with_a_space_is_quoted(self):
self.assertEqual(
database.osv_url("Red Hat"),
"https://osv-vulnerabilities.storage.googleapis.com/Red%20Hat/all.zip",
)
class TestFetchDatabases(_Root):
def _fetch(self, engines, returncode):
downloads = []
calls = []
def runner(argv):
calls.append(list(argv))
return SimpleNamespace(
returncode=returncode, stdout="", stderr="no network"
)
def fetch(url, destination):
downloads.append(url)
destination.write_bytes(b"zip")
errors = database.fetch_databases(P, self.root, engines, runner, fetch)
return errors, calls, downloads
def test_osv_lands_in_the_layout_the_scanner_reads(self):
errors, _calls, downloads = self._fetch(["osv"], 0)
self.assertEqual(errors, [])
self.assertTrue(
(self.root / "osv" / "osv-scalibr" / "Ubuntu" / "all.zip").is_file()
)
self.assertEqual(len(downloads), len(database.osv_zips(P, self.root)))
def test_a_failed_download_is_reported(self):
errors, calls, _downloads = self._fetch(["grype"], 1)
self.assertTrue(errors[0].startswith("grype database: exited 1"))
self.assertIn(["db", "update"], [call[-2:] for call in calls])
def test_a_download_that_leaves_no_database_is_reported(self):
errors, _calls, _downloads = self._fetch(["trivy"], 0)
self.assertTrue(any("provided database" in error for error in errors))
class TestStale(_Root):
def test_a_missing_database_is_stale(self):
self.assertTrue(database.stale(P, self.root, "grype", time.time()))
def test_a_database_of_today_is_fresh(self):
_touch(self.root / "grype" / "6" / "vulnerability.db", 3600)
self.assertFalse(database.stale(P, self.root, "grype", time.time()))
def test_a_database_older_than_a_day_is_stale(self):
_touch(
self.root / "grype" / "6" / "vulnerability.db",
database.MAX_AGE_SECONDS + 60,
)
self.assertTrue(database.stale(P, self.root, "grype", time.time()))
def test_osv_is_stale_while_one_ecosystem_is_missing(self):
zips = database.osv_zips(P, self.root)
for path in zips[1:]:
_touch(path, 60)
self.assertTrue(database.stale(P, self.root, "osv", time.time()))
class TestRefresh(_Root):
def test_only_stale_engines_are_downloaded(self):
_touch(self.root / "grype" / "6" / "vulnerability.db", 60)
with (
mock.patch.object(database, "LOCK_FILE", self.root / "lock"),
mock.patch.object(database, "fetch_databases", return_value=[]) as fetch,
):
database.refresh(
self.root, ["trivy", "grype"], lambda argv: None, lambda u, d: None
)
self.assertEqual(fetch.call_args.args[2], ["trivy"])
def test_fresh_databases_download_nothing(self):
_touch(self.root / "grype" / "6" / "vulnerability.db", 60)
with (
mock.patch.object(database, "LOCK_FILE", self.root / "lock"),
mock.patch.object(database, "fetch_databases") as fetch,
):
errors = database.refresh(
self.root, ["grype"], lambda argv: None, lambda u, d: None
)
self.assertEqual(errors, [])
fetch.assert_not_called()
if __name__ == "__main__":
main()