Files
home-network-k3s-impl/conformance/tests/l1-rendering/vuln-scanner-acceptances/check.py
T
jakman85andClaude Opus 5.5 50ec2297ee feat(vuln): scanner service and fixture-driven L1 tests
Stdlib-only scanner per docs/17: Kubernetes API inventory, Trivy runner,
persisted state, schema-validated report.json, strict acceptances parser,
server-rendered page, aggregate metrics, internal API, and a scan loop
that stays live while scanning.

L1 tests under conformance/tests/l1-rendering/vuln-scanner-* run the
actual modules against recorded fixtures with a fake Trivy runner.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-09 09:41:23 -06:00

112 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""R-VULN-08: acceptance parsing, expiry and fail-safe behaviour."""
import sys
from datetime import date, datetime, timedelta, timezone
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parents[3] / "platform/vuln-scanner/src"))
import acceptances # noqa: E402
import report # noqa: E402
FAILED = []
TODAY = date(2026, 11, 9)
NOW = datetime(2026, 11, 9, 12, 0, tzinfo=timezone.utc)
A1 = "sha256:" + "a1" * 32
def check(name, condition):
print(("ok " if condition else "FAIL ") + name)
if not condition:
FAILED.append(name)
def parse(text):
return acceptances.parse_text(text, TODAY)
entries, errors = parse("""# header comment
- id: CVE-2026-1
image: docker.io/library/nginx # exact match
reason: not reachable - feature disabled
expires: 2027-02-01
- id: CVE-2026-2
reason: "platform wide"
expires: 2026-12-01
""")
check("valid flat entries parse with no errors", len(entries) == 2 and errors == [])
check("inline comment is stripped", entries[0]["image"] == "docker.io/library/nginx")
check("omitted image means platform-wide", entries[1]["image"] is None)
check("empty list form is accepted", parse("# none\n[]\n") == ([], []))
check("empty file is accepted", parse("") == ([], []))
for name, text in (
("missing reason", "- id: CVE-1\n expires: 2026-12-01\n"),
("missing expires", "- id: CVE-1\n reason: x\n"),
("expires beyond 180 days", "- id: CVE-1\n reason: x\n expires: 2027-06-01\n"),
("unknown key", "- id: CVE-1\n reason: x\n expires: 2026-12-01\n scope: all\n"),
("not a date", "- id: CVE-1\n reason: x\n expires: soon\n"),
("impossible date", "- id: CVE-1\n reason: x\n expires: 2026-13-45\n"),
("duplicate key", "- id: CVE-1\n reason: x\n reason: y\n expires: 2026-12-01\n"),
("flow value", "- id: CVE-1\n reason: [x]\n expires: 2026-12-01\n"),
):
found, problems = parse(text)
check("rejected: " + name, found == [] and len(problems) == 1)
found, problems = parse("- id: CVE-1\n reason: x\n expires: 2026-12-01\n- id: bad entry\n expires: 2026-12-01\n")
check("one bad entry is ignored, the valid one kept", len(found) == 1 and len(problems) == 1)
found, problems = parse("- id: CVE-1\n reason: x\n")
check("bad indentation invalidates the whole file", found == [] and problems)
found, problems = parse("garbage\n- id: CVE-1\n reason: x\n expires: 2026-12-01\n")
check("a stray line invalidates the whole file", found == [] and problems)
check("missing file is an error, not an empty allowlist", acceptances.load("/nonexistent/acceptances.yaml")[1])
entry = {"id": "CVE-1", "image": None, "reason": "r", "expires": date(2026, 11, 9)}
check("acceptance holds on its expiry day", acceptances.match([entry], "CVE-1", "any/image", TODAY) is entry)
check("acceptance gone the day after", acceptances.match([entry], "CVE-1", "any/image", TODAY + timedelta(days=1)) is None)
scoped = {"id": "CVE-1", "image": "docker.io/library/nginx", "reason": "r", "expires": date(2026, 12, 1)}
check("image match is exact", acceptances.match([scoped], "CVE-1", "docker.io/library/nginx-extra", TODAY) is None)
check("image match accepts the exact image", acceptances.match([scoped], "CVE-1", "docker.io/library/nginx", TODAY) is scoped)
def state_with(findings):
target = {"id": "docker.io/library/nginx@" + A1, "type": "image", "image": "docker.io/library/nginx", "digest": A1,
"ref": "", "reason": None, "workloads": []}
return {
"inventory": [target], "database": {"updatedAt": "2026-11-09T06:00:00Z", "version": "2"},
"run": {"startedAt": "2026-11-09T09:00:00Z", "finishedAt": "2026-11-09T09:30:00Z", "status": "complete"},
"remediation": [], "inventoryError": None, "loadError": None,
"scans": {A1: {"firstDiscoveredAt": "2026-11-01T00:00:00Z", "lastInventoriedAt": "2026-11-09T09:00:00Z",
"lastAttemptAt": "2026-11-09T09:00:00Z", "lastSuccessAt": "2026-11-09T09:00:00Z", "outcome": "ok",
"reason": None, "failedSince": None, "findings": findings,
"firstSeen": {f["id"]: "2026-11-01T00:00:00Z" for f in findings}}},
}
FINDING = {"id": "CVE-1", "severity": "CRITICAL", "cvss": 9.0, "package": "p", "installedVersion": "1", "fixedVersion": "2",
"title": "t", "url": ""}
ST = state_with([FINDING])
def status(when, accepted, errs=()):
built = report.build(ST, accepted, list(errs), when)
return built, built["findings"][0]
entry = {"id": "CVE-1", "image": None, "reason": "not reachable", "expires": date(2026, 11, 9)}
built, finding = status(NOW, [entry])
check("unexpired acceptance marks the finding accepted", finding["status"] == "accepted"
and finding["acceptance"] == {"reason": "not reachable", "expires": "2026-11-09"})
check("accepted report is schema-valid", report.validate(built) == [])
_, finding = status(NOW + timedelta(days=1), [entry])
check("expired acceptance reverts to the finding's own status", finding["status"] == "fix-available" and "acceptance" not in finding)
_, finding = status(NOW, [])
check("mutation: no acceptance leaves it open/fix-available", finding["status"] == "fix-available")
built, finding = status(NOW, [], ["bad entry"])
check("an invalid entry degrades the run", built["run"]["status"] == "degraded")
check("an invalid entry never hides the finding", finding["status"] == "fix-available")
print("FAILED: " + str(len(FAILED)) if FAILED else "all checks passed")
sys.exit(1 if FAILED else 0)