Files
mass-scan2/app/main.py
2025-10-17 13:54:04 -05:00

109 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""
port_checker.py
- expects `expected.json` in same dir (see format below)
- writes nmap XML to a temp file, parses, compares, prints a report
"""
import os
import json
import subprocess
import tempfile
from datetime import datetime
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Dict, List, Set
from utils.scanner import nmap_scanner
from utils.models import HostResult
from reporting_jinja import write_html_report_jinja
EXPECTED_FILE = Path() / "data" / "expected.json"
HTML_REPORT_FILE = Path() / "data" / "report.html"
def load_expected(path: Path) -> Dict[str, Dict[str, Set[int]]]:
with path.open() as fh:
arr = json.load(fh)
out = {}
for entry in arr:
ip = entry["ip"]
out[ip] = {
"expected_tcp": set(entry.get("expected_tcp", [])),
"expected_udp": set(entry.get("expected_udp", [])),
}
return out
# def write_targets(expected: Dict[str, Dict[str, Set[int]]], path: Path) -> None:
path.write_text("\n".join(sorted(expected.keys())) + "\n")
#
def results_to_open_sets(
results: List[HostResult],
count_as_open: Set[str] = frozenset({"open", "open|filtered"})) -> Dict[str, Dict[str, Set[int]]]:
"""
Convert HostResult list to:
{ ip: {"tcp": {open ports}, "udp": {open ports}} }
Only include ports whose state is in `count_as_open`.
"""
out: Dict[str, Dict[str, Set[int]]] = {}
for hr in results:
tcp = set()
udp = set()
for p in hr.ports:
if p.state.lower() in count_as_open:
(tcp if p.protocol == "tcp" else udp).add(p.port)
out[hr.address] = {"tcp": tcp, "udp": udp}
return out
# Build the "reports" dict (what the HTML renderer expects)
def build_reports(
expected: Dict[str, Dict[str, Set[int]]],
discovered: Dict[str, Dict[str, Set[int]]],
) -> Dict[str, Dict[str, List[int]]]:
"""
Create the per-IP delta structure:
{
ip: {
"unexpected_tcp": [...],
"missing_tcp": [...],
"unexpected_udp": [...],
"missing_udp": [...]
}
}
"""
reports: Dict[str, Dict[str, List[int]]] = {}
all_ips = set(expected.keys()) | set(discovered.keys())
for ip in sorted(all_ips):
exp_tcp = expected.get(ip, {}).get("expected_tcp", set())
exp_udp = expected.get(ip, {}).get("expected_udp", set())
disc_tcp = discovered.get(ip, {}).get("tcp", set())
disc_udp = discovered.get(ip, {}).get("udp", set())
reports[ip] = {
"unexpected_tcp": sorted(disc_tcp - exp_tcp),
"missing_tcp": sorted(exp_tcp - disc_tcp),
"unexpected_udp": sorted(disc_udp - exp_udp),
"missing_udp": sorted(exp_udp - disc_udp),
}
return reports
def main():
# repo = ScanConfigRepository()
if not EXPECTED_FILE.exists():
print("Expected File not found")
return
expected = load_expected(EXPECTED_FILE)
targets = sorted(expected.keys())
scanner = nmap_scanner(targets)
scan_results = scanner.scan_targets()
discovered_sets = results_to_open_sets(scan_results, count_as_open={"open", "open|filtered"})
reports = build_reports(expected, discovered_sets)
write_html_report_jinja(reports=reports,host_results=scan_results,out_path=HTML_REPORT_FILE,title="Compliance Report",only_issues=True)
scanner.cleanup()
if __name__ == "__main__":
main()