PeerCortex/audit/daily_audit.py
Rene Fichtmueller 2766872aad snapshot: preserve production-only changes (Izzy PDF export, adoption tracker, audit scripts)
Working tree was 3+ months ahead of git in uncommitted local edits, sitting
on a detached HEAD at d3611a8 (2026-04-09), never reconciled with main.
Committing as-is on its own branch, without touching main or the detached
HEAD, to stop these files existing only on this one server:

- public/index.html + server.js: PDF export add-on gated to client Izzy,
  full Adoption Tracker overlay (ASPA + IPv6 tabs) -- neither exists
  anywhere in git history on any branch until now
- audit/: daily/rotating audit scripts + email report sender, previously
  untracked
- scripts/: tunnel-cleanup.sh, refresh-peeringdb.sh, previously untracked
- deploy-from-scp.sh, webhook-subs.json, hijack-alerts.json,
  aspa-adoption-history.json: previously untracked runtime/deploy state
- public/public/: duplicate mirror of the HTML variants found alongside
  the real public/ dir, preserved as found

No reconciliation with main attempted here -- that needs a deliberate pass,
not a side effect of a backup commit.
2026-07-16 06:42:03 +00:00

177 lines
7.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""
PeerCortex Final Quality Audit v4
Tests 103 ASNs against v0.6.7 (running, stable)
Uses cached responses where available, skips ASNs that don't respond in 15s
Cross-validates: prefix counts, RPKI math, IX dedup, facilities, RIR, country
"""
import json, urllib.request, time, sys
from datetime import datetime, timezone
def fetch(url, timeout=15):
try:
req = urllib.request.Request(url, headers={"User-Agent": "PeerCortex-Audit/4.0"})
with urllib.request.urlopen(req, timeout=timeout) as r:
return json.loads(r.read())
except Exception as e:
return None
ASNS = [
1, 174, 714, 1299, 2497, 2516, 2906, 3257, 3303, 3320, 3356,
4134, 4766, 5089, 5483, 5511, 6057, 6128, 6147, 6453, 6695,
6762, 6830, 6939, 7018, 7473, 7545, 7922, 8075, 8151, 8220,
8422, 8708, 9121, 9304, 9318, 9498, 10429, 10474, 10796, 12479,
12714, 12956, 13237, 13335, 14080, 15133, 15169, 15600, 15704,
16276, 16509, 17676, 18403, 19281, 20115, 20562, 20932, 20940,
21928, 22773, 22822, 23106, 24115, 24429, 24940, 24961, 25091,
29049, 32934, 33891, 34465, 34549, 35369, 37100, 38001, 39122,
41327, 42, 42739, 43350, 43996, 44574, 45899, 47692, 48159,
50629, 51088, 54113, 56630, 57695, 58057, 59947, 60068, 60501,
62240, 64271, 132335, 136907, 137409, 139070, 197540, 199121,
]
results = {"ok": 0, "warn": 0, "crit": 0, "skip": 0, "total": len(ASNS)}
all_issues = []
responded = []
print(f"PeerCortex Final Audit v4 — {datetime.now(timezone.utc).isoformat()}")
print(f"Target: http://localhost:3101 | ASNs: {len(ASNS)}")
print("=" * 75)
for i, asn in enumerate(ASNS):
sys.stdout.write(f"\r [{i+1:3d}/{len(ASNS)}] AS{asn}... ")
sys.stdout.flush()
d = fetch(f"http://localhost:3101/api/lookup?asn={asn}", timeout=15)
if not d or "network" not in d or d.get("error"):
results["skip"] += 1
continue
responded.append(asn)
n = d.get("network", {})
p = d.get("prefixes", {})
r = d.get("rpki", {})
ix = d.get("ix_presence", {})
fac = d.get("facilities", {})
meta= d.get("meta", {})
reg = d.get("registration", {})
issues = []
# ── Math checks ─────────────────────────────────────────
if p.get("ipv4", 0) + p.get("ipv6", 0) != p.get("total", -1):
issues.append(("CRITICAL", "prefix_math",
f"v4({p.get('ipv4')})+v6({p.get('ipv6')})={p.get('ipv4',0)+p.get('ipv6',0)} ≠ total({p.get('total')})"))
rpki_sum = r.get("valid",0) + r.get("invalid",0) + r.get("not_found",0)
if rpki_sum != r.get("checked", 0):
issues.append(("CRITICAL", "rpki_sum",
f"{rpki_sum} ≠ checked({r.get('checked')})"))
if r.get("checked", 0) > 0:
exp = round(r.get("valid",0) / r.get("checked") * 100)
if abs(exp - r.get("coverage_percent", 0)) > 1:
issues.append(("CRITICAL", "rpki_pct",
f"expected {exp}% got {r.get('coverage_percent')}%"))
conns = ix.get("connections", [])
dedup = len(set(c.get("ix_id") for c in conns if c.get("ix_id")))
if dedup != ix.get("unique_ixps", 0):
issues.append(("CRITICAL", "ix_dedup",
f"dedup={dedup} ≠ unique_ixps={ix.get('unique_ixps')}"))
if len(conns) != ix.get("total_connections", 0):
issues.append(("WARNING", "ix_total",
f"len={len(conns)} ≠ total_connections={ix.get('total_connections')}"))
if len(fac.get("list",[])) != fac.get("total", 0):
issues.append(("WARNING", "fac_total",
f"len={len(fac.get('list',[]))} ≠ total={fac.get('total')}"))
# ── Mandatory fields ─────────────────────────────────────
if not n.get("name") or n.get("name") == "Unknown":
issues.append(("CRITICAL", "name_empty", f"name={n.get('name')!r}"))
if not n.get("asn"):
issues.append(("CRITICAL", "asn_empty", "ASN field missing"))
if not n.get("rir") and not reg.get("rir"):
issues.append(("WARNING", "rir_empty", f"rir empty, country={n.get('country')!r}"))
if not n.get("country"):
issues.append(("WARNING", "country_empty", "country field empty"))
if meta.get("version") != "0.6.7":
issues.append(("WARNING", "version", f"got {meta.get('version')}"))
# ── Categorize ───────────────────────────────────────────
crits = [x for x in issues if x[0] == "CRITICAL"]
warns = [x for x in issues if x[0] == "WARNING"]
if crits:
results["crit"] += 1
elif warns:
results["warn"] += 1
else:
results["ok"] += 1
if issues:
all_issues.append((asn, n.get("name","?"), issues,
p.get("total"), r.get("coverage_percent"),
ix.get("total_connections"), fac.get("total"),
n.get("rir"), n.get("country")))
print(f"\r Done. {len(responded)} responded, {results['skip']} skipped. ")
print()
print("=" * 75)
print(f"FINAL AUDIT SUMMARY — {len(responded)} ASNs responded ({results['skip']} timed out)")
print("=" * 75)
print(f" ✓ PERFECT (zero issues): {results['ok']:3d}")
print(f" ⚠ WARNING only: {results['warn']:3d}")
print(f" ✗ CRITICAL: {results['crit']:3d}")
print(f" ⏭ Skipped (no response): {results['skip']:3d}")
if results["crit"] == 0:
print("\n ✅ NO CRITICAL DATA ERRORS IN ANY RESPONDED ASN")
# Per-category counts
warn_counts = {}
crit_counts = {}
for _, _, issues, *_ in all_issues:
for sev, field, _ in issues:
if sev == "CRITICAL":
crit_counts[field] = crit_counts.get(field, 0) + 1
else:
warn_counts[field] = warn_counts.get(field, 0) + 1
if crit_counts:
print("\n CRITICAL by field:")
for f, c in sorted(crit_counts.items(), key=lambda x: -x[1]):
print(f" {f:35s}: {c} ASNs")
if warn_counts:
print("\n WARNING by field:")
for f, c in sorted(warn_counts.items(), key=lambda x: -x[1]):
print(f" {f:35s}: {c} ASNs")
# Detail for any issues
if all_issues:
print("\n DETAILS:")
for asn, name, issues, pfx, rpki_pct, ix_cnt, fac_cnt, rir, cc in all_issues:
for sev, field, msg in issues:
print(f" [{sev[:4]}] AS{asn:>7} | {field:30s} | {msg[:60]}")
print()
print(" SAMPLE DATA (responded ASNs):")
print(f" {'ASN':>8} {'Name':24s} {'RIR':>6} {'CC':>3} {'Pfx':>5} {'RPKI%':>6} {'IX':>4} {'Fac':>4}")
print(f" {'-'*70}")
for asn in responded[:25]:
d = fetch(f"http://localhost:3101/api/lookup?asn={asn}", timeout=5)
if not d: continue
n=d.get("network",{}); p=d.get("prefixes",{}); r=d.get("rpki",{}); ix=d.get("ix_presence",{}); fac=d.get("facilities",{})
print(f" AS{asn:>6} {n.get('name','?')[:22]:24s} {n.get('rir','?'):>6} {n.get('country','?'):>3} {p.get('total',0):>5} {r.get('coverage_percent',0):>5}% {ix.get('total_connections',0):>4} {fac.get('total',0):>4}")
print()
print(f"Audit complete: {datetime.now(timezone.utc).isoformat()}")