add IMPORTANT_CONTAINERS
This commit is contained in:
1 parent
21b35a4890
commit
18a1d0e863
7 files changed
+588
-40
No files matched your search
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user