add IMPORTANT_CONTAINERS

This commit is contained in:
David Duran Rosich committed 2026-10-24 21:59:12 +02:00
1 parent 21b35a4890
commit 18a1d0e863
7 files changed
+588 -40

No files matched your search

+112
View File
@@ -298,6 +298,7 @@ class SettingsTests(unittest.TestCase):
"IGNORE_UNFIXED": "true",
"MAX_FINDINGS_IN_BODY": "3",
"TRIVY_VEX": "/vex/app.json repo",
"IMPORTANT_CONTAINERS": "traefik, web-frontend\nworker",
}
with mock.patch.dict(os.environ, env, clear=False):
settings = scan.Settings.from_env([])
@@ -306,6 +307,9 @@ class SettingsTests(unittest.TestCase):
self.assertTrue(settings.ignore_unfixed)
self.assertEqual(settings.max_findings, 3)
self.assertEqual(settings.trivy_vex, ["/vex/app.json", "repo"])
self.assertEqual(
settings.important_containers, ["traefik", "web-frontend", "worker"]
)
def test_settings_rejects_unknown_severity(self) -> None:
with mock.patch.dict(os.environ, {"NOTIFY_MIN_SEVERITY": "SEVERE"}, clear=False):
@@ -452,6 +456,68 @@ class ReportTests(unittest.TestCase):
self.assertIn("cache", body)
self.assertIn("redis:7", body)
def test_cve_entries_split_fixable_and_unfixable(self) -> None:
entries = scan.cve_entries(scan.summarise(REPORT), scan.severity_rank("HIGH"))
self.assertEqual(scan.split_counts(entries), (3, 2, 1))
self.assertEqual([entry.cve for entry in entries], ["CVE-1", "CVE-2", "CVE-5"])
self.assertEqual(scan.severity_counts(entries), {
"CRITICAL": 1,
"HIGH": 2,
"MEDIUM": 0,
"LOW": 0,
})
def test_build_important_body_lists_every_cve(self) -> None:
result = scan.summarise(REPORT)
target = scan.ScanTarget(image="nginx:1.27", containers=["web"])
body = scan.build_important_body(
"web", target, result, "/reports/x.json", "HIGH"
)
self.assertIn("Image: nginx:1.27", body)
self.assertIn("Container: web", body)
self.assertIn("Total: 3 (fixable: 2, unfixable: 1)", body)
self.assertIn("CVE-1", body)
self.assertIn("CVE-2", body)
self.assertIn("CVE-5", body)
self.assertIn("no fix available", body)
self.assertIn("/reports/x.json", body)
def test_build_important_body_respects_notification_threshold(self) -> None:
result = scan.summarise(REPORT)
target = scan.ScanTarget(image="nginx:1.27", containers=["web"])
body = scan.build_important_body(
"web", target, result, "/reports/x.json", "CRITICAL"
)
self.assertIn("CVE-1", body)
self.assertNotIn("CVE-2", body)
self.assertNotIn("CVE-5", body)
self.assertIn("Total: 1 (fixable: 1, unfixable: 0)", body)
def test_build_other_containers_body_has_counts_per_container(self) -> None:
body = scan.build_other_containers_body(
[
("nginx:1.27", "web-worker", 3, 2, 1),
("redis:7", "cache", 4, 0, 4),
]
)
self.assertIn("* nginx:1.27 [web-worker]: 3 total (2 fixable, 1 unfixable)", body)
self.assertIn("* redis:7 [cache]: 4 total (0 fixable, 4 unfixable)", body)
self.assertIn("Total: 7 CVE(s) in 2 container(s)", body)
def test_build_missing_body_separates_missing_and_unscanned(self) -> None:
body = scan.build_missing_body(["gone"], ["web"])
self.assertIn("not running", body)
self.assertIn("* gone", body)
self.assertIn("image was not scanned", body)
self.assertIn("* web", body)
def test_split_body_keeps_every_line(self) -> None:
body = "\n".join(f"CVE-{index}" for index in range(10))
parts = scan._split_body(body, 20)
self.assertGreater(len(parts), 1)
self.assertTrue(all(len(part) <= 20 for part in parts))
self.assertEqual("\n".join(parts), body)
class TrivyCommandTests(unittest.TestCase):
def test_run_trivy_passes_vex_sources_in_order(self) -> None:
@@ -584,6 +650,52 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG"
self.assertIn("CVE-2026-0001", notification)
self.assertIn("testphp", notification)
def test_important_container_gets_full_list_and_others_get_summary(self) -> None:
containers = [
container("a" * 64, "testphp-php", name="web"),
container("b" * 64, "redis:7", name="cache"),
]
with FakeDocker(containers) as docker:
code = self.run_main(
[], docker.socket_path, IMPORTANT_CONTAINERS="web"
)
self.assertEqual(code, 1)
notification = Path(self.apprise_log).read_text()
self.assertIn("Vulnerability scan: web (testphp-php)", notification)
self.assertIn("Container: web", notification)
self.assertIn("CVE-2026-0001", notification)
self.assertIn("Vulnerability scan: other containers", notification)
self.assertIn("redis:7 [cache]", notification)
self.assertIn("(1 fixable, 0 unfixable)", notification)
# The important container must not also appear in the summary.
self.assertNotIn("testphp-php [web]", notification)
def test_missing_important_container_is_reported(self) -> None:
containers = [container("a" * 64, "testphp-php", name="web")]
with FakeDocker(containers) as docker:
code = self.run_main(
[], docker.socket_path, IMPORTANT_CONTAINERS="web stale"
)
self.assertEqual(code, 1)
notification = Path(self.apprise_log).read_text()
self.assertIn("Vulnerability scan: important containers", notification)
self.assertIn("Important containers are not running:", notification)
self.assertIn("* stale", notification)
def test_important_container_with_unscanned_image_is_reported(self) -> None:
containers = [container("a" * 64, "other:1", name="web")]
with FakeDocker(containers) as docker:
code = self.run_main(
["nginx:1.27"], docker.socket_path, IMPORTANT_CONTAINERS="web"
)
self.assertEqual(code, 1)
notification = Path(self.apprise_log).read_text()
self.assertIn("image was not scanned", notification)
self.assertIn("* web", notification)
def test_scan_failure_notification_includes_container(self) -> None:
containers = [container("a" * 64, "testphp-php", name="testphp")]
with FakeDocker(containers) as docker: