diff --git a/docker-security/test_security.py b/docker-security/test_security.py index 1128166..c4529b1 100644 --- a/docker-security/test_security.py +++ b/docker-security/test_security.py @@ -5,6 +5,9 @@ from pathlib import Path import subprocess import tempfile import unittest +import time +import datetime as dt +from io import BytesIO from unittest.mock import patch HERE = Path(__file__).parent @@ -31,6 +34,31 @@ def report(severity='CRITICAL', fixed='', cve='CVE-2025-12345'): class SecurityTests(unittest.TestCase): + def test_kev_403_uses_official_mirror_and_records_freshness(self): + now = time.time() + doc = {'count': 1, 'catalogVersion': 'fixture', + 'dateReleased': dt.datetime.fromtimestamp(now, dt.timezone.utc).isoformat(), + 'vulnerabilities': [{'cveID': 'CVE-2026-12345'}]} + with tempfile.TemporaryDirectory() as td: + with patch.object(scan, 'BASE', Path(td)), patch.object(scan.urllib.request, 'urlopen', + side_effect=[RuntimeError('403'), BytesIO(json.dumps(doc).encode())]) as fetch: + ids, fresh, error = scan.kev_catalog() + self.assertEqual(ids, {'CVE-2026-12345'}) + self.assertTrue(fresh) + self.assertIsNone(error) + self.assertEqual(fetch.call_args_list[1].args[0].full_url, scan.KEV_URLS[1]) + + def test_kev_stale_mirror_and_both_sources_failed_remain_unknown(self): + doc = {'count': 1, 'dateReleased': '2020-01-01T00:00:00Z', + 'vulnerabilities': [{'cveID': 'CVE-2026-12345'}]} + with tempfile.TemporaryDirectory() as td: + with patch.object(scan, 'BASE', Path(td)), patch.object(scan.urllib.request, 'urlopen', + side_effect=[RuntimeError('403'), BytesIO(json.dumps(doc).encode())]): + ids, fresh, error = scan.kev_catalog() + self.assertEqual(ids, set()) + self.assertFalse(fresh) + self.assertIsNotNone(error) + def test_installer_syntax_and_embedded_sources(self): text = (HERE / 'docker-security.sh').read_text() p = subprocess.run(['bash', '-n'], input=text, text=True, capture_output=True) @@ -148,12 +176,14 @@ class SecurityTests(unittest.TestCase): patch.object(scan, 'docker_images', return_value={IMAGE: META}), patch.object(scan, 'run', side_effect=cli): self.assertEqual(scan.main(), 0) self.assertEqual(scan.main(), 0) - self.assertEqual(len(log.read_text().splitlines()), 1) + self.assertEqual(len([x for x in log.read_text().splitlines() if json.loads(x)['event'] != 'scan_status']), 1) with patch.object(scan, 'run', side_effect=RuntimeError('failure')): self.assertEqual(scan.main(), 1) self.assertEqual(scan.main(), 1) records = [json.loads(line) for line in log.read_text().splitlines()] - self.assertEqual([x['event'] for x in records], ['finding_new', 'scan_problem']) + self.assertEqual([x['event'] for x in records if x['event'] != 'scan_status'], ['finding_new', 'scan_problem']) + self.assertEqual(records[-1]['event'], 'scan_status') + self.assertEqual(records[-1]['complete'], 'no') inventory = json.loads((p / 'inventory.json').read_text()) self.assertFalse(inventory['complete']) self.assertEqual(len(inventory['findings']), 1)