188 lines
6.0 KiB
Python
188 lines
6.0 KiB
Python
|
|
"""Tests for titan-iac supply-chain compliance report generation."""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
from ci.scripts import supply_chain_report
|
||
|
|
|
||
|
|
|
||
|
|
def _write_json(path: Path, payload: dict):
|
||
|
|
"""Write compact JSON fixtures for report tests."""
|
||
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_report_separates_waived_and_open_misconfigurations(tmp_path: Path):
|
||
|
|
"""Existing waivers suppress only exact target/id pairs."""
|
||
|
|
waivers = tmp_path / "waivers.json"
|
||
|
|
_write_json(
|
||
|
|
waivers,
|
||
|
|
{
|
||
|
|
"default_expires_at": "2026-05-22",
|
||
|
|
"misconfigurations": [
|
||
|
|
{
|
||
|
|
"id": "KSV-0014",
|
||
|
|
"targets": ["services/example/deployment.yaml"],
|
||
|
|
}
|
||
|
|
],
|
||
|
|
},
|
||
|
|
)
|
||
|
|
trivy_payload = {
|
||
|
|
"Results": [
|
||
|
|
{
|
||
|
|
"Target": "services/example/deployment.yaml",
|
||
|
|
"Misconfigurations": [
|
||
|
|
{
|
||
|
|
"ID": "KSV-0014",
|
||
|
|
"Status": "FAIL",
|
||
|
|
"Severity": "HIGH",
|
||
|
|
"Title": "Root file system is not read-only",
|
||
|
|
},
|
||
|
|
{
|
||
|
|
"ID": "KSV-0118",
|
||
|
|
"Status": "FAIL",
|
||
|
|
"Severity": "HIGH",
|
||
|
|
"Title": "Default security context configured",
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
report = supply_chain_report.build_report(trivy_payload, waivers, today_override="2026-04-22")
|
||
|
|
|
||
|
|
assert report["status"] == "failed"
|
||
|
|
assert report["waived_misconfigurations"] == 1
|
||
|
|
assert report["high_or_critical_misconfigurations"] == 1
|
||
|
|
assert report["open_misconfiguration_examples"] == [
|
||
|
|
{
|
||
|
|
"id": "KSV-0118",
|
||
|
|
"target": "services/example/deployment.yaml",
|
||
|
|
"severity": "HIGH",
|
||
|
|
"title": "Default security context configured",
|
||
|
|
}
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_report_fails_expired_waivers_and_secrets(tmp_path: Path):
|
||
|
|
"""Expired waivers intentionally stop hiding old baseline debt."""
|
||
|
|
waivers = tmp_path / "waivers.json"
|
||
|
|
_write_json(
|
||
|
|
waivers,
|
||
|
|
{
|
||
|
|
"default_expires_at": "2026-04-01",
|
||
|
|
"misconfigurations": [
|
||
|
|
{
|
||
|
|
"id": "KSV-0014",
|
||
|
|
"targets": ["services/example/deployment.yaml"],
|
||
|
|
}
|
||
|
|
],
|
||
|
|
},
|
||
|
|
)
|
||
|
|
trivy_payload = {
|
||
|
|
"Results": [
|
||
|
|
{
|
||
|
|
"Target": "services/example/deployment.yaml",
|
||
|
|
"Secrets": [{"RuleID": "secret"}],
|
||
|
|
"Misconfigurations": [
|
||
|
|
{
|
||
|
|
"ID": "KSV-0014",
|
||
|
|
"Status": "FAIL",
|
||
|
|
"Severity": "CRITICAL",
|
||
|
|
"Title": "Root file system is not read-only",
|
||
|
|
}
|
||
|
|
],
|
||
|
|
}
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
report = supply_chain_report.build_report(trivy_payload, waivers, today_override="2026-04-22")
|
||
|
|
|
||
|
|
assert report["status"] == "failed"
|
||
|
|
assert report["compliant"] is False
|
||
|
|
assert report["secrets"] == 1
|
||
|
|
assert report["expired_waivers"] == 1
|
||
|
|
assert report["waived_misconfigurations"] == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_build_report_handles_missing_and_malformed_waiver_entries(tmp_path: Path):
|
||
|
|
"""Malformed waiver rows are ignored instead of hiding real findings."""
|
||
|
|
missing_waivers = tmp_path / "missing.json"
|
||
|
|
empty_report = supply_chain_report.build_report({"Results": []}, missing_waivers, today_override="2026-04-22")
|
||
|
|
assert empty_report["status"] == "ok"
|
||
|
|
|
||
|
|
waivers = tmp_path / "waivers.json"
|
||
|
|
_write_json(
|
||
|
|
waivers,
|
||
|
|
{
|
||
|
|
"misconfigurations": [
|
||
|
|
"bad row",
|
||
|
|
{"id": "", "targets": ["services/example/deployment.yaml"]},
|
||
|
|
{"id": "KSV-0014", "targets": "not a list"},
|
||
|
|
{"id": "KSV-0118", "expires_at": "2026-05-22", "targets": [""]},
|
||
|
|
]
|
||
|
|
},
|
||
|
|
)
|
||
|
|
trivy_payload = {
|
||
|
|
"Results": [
|
||
|
|
"bad result",
|
||
|
|
{
|
||
|
|
"Target": "services/example/deployment.yaml",
|
||
|
|
"Vulnerabilities": [
|
||
|
|
{"Severity": "HIGH"},
|
||
|
|
{"Severity": "CRITICAL"},
|
||
|
|
{"Severity": "LOW"},
|
||
|
|
],
|
||
|
|
"Misconfigurations": [
|
||
|
|
"bad misconfiguration",
|
||
|
|
{"ID": "KSV-0001", "Status": "PASS", "Severity": "CRITICAL"},
|
||
|
|
{"ID": "KSV-0002", "Status": "FAIL", "Severity": "LOW"},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
]
|
||
|
|
}
|
||
|
|
|
||
|
|
report = supply_chain_report.build_report(trivy_payload, waivers, today_override="2026-04-22")
|
||
|
|
|
||
|
|
assert report["status"] == "failed"
|
||
|
|
assert report["critical_vulnerabilities"] == 1
|
||
|
|
assert report["high_vulnerabilities"] == 1
|
||
|
|
assert report["high_or_critical_misconfigurations"] == 0
|
||
|
|
|
||
|
|
|
||
|
|
def test_read_json_rejects_non_object_payload(tmp_path: Path):
|
||
|
|
"""Pipeline evidence files must be JSON objects, not arrays or scalars."""
|
||
|
|
path = tmp_path / "array.json"
|
||
|
|
path.write_text("[]", encoding="utf-8")
|
||
|
|
|
||
|
|
try:
|
||
|
|
supply_chain_report._read_json(path)
|
||
|
|
except ValueError as exc:
|
||
|
|
assert "must contain a JSON object" in str(exc)
|
||
|
|
else: # pragma: no cover - keeps the assertion message readable on failure
|
||
|
|
raise AssertionError("expected ValueError")
|
||
|
|
|
||
|
|
|
||
|
|
def test_main_writes_compliance_report(tmp_path: Path):
|
||
|
|
"""The Jenkins CLI path writes the exact report artifact it publishes."""
|
||
|
|
trivy_json = tmp_path / "trivy.json"
|
||
|
|
output = tmp_path / "ironbank-compliance.json"
|
||
|
|
_write_json(trivy_json, {"Results": []})
|
||
|
|
|
||
|
|
rc = supply_chain_report.main(
|
||
|
|
[
|
||
|
|
"--trivy-json",
|
||
|
|
str(trivy_json),
|
||
|
|
"--output",
|
||
|
|
str(output),
|
||
|
|
"--today",
|
||
|
|
"2026-04-22",
|
||
|
|
]
|
||
|
|
)
|
||
|
|
|
||
|
|
assert rc == 0
|
||
|
|
payload = json.loads(output.read_text(encoding="utf-8"))
|
||
|
|
assert payload["status"] == "ok"
|
||
|
|
assert payload["compliant"] is True
|