mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-14 06:37:25 +00:00
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>
125 lines
4.1 KiB
Python
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()
|