feat(blocklist): make SQLite ledger authoritative for the cumulative set (B001)
- Add repo.record_blocklist(): grow-only, never auto-removes (mirrors router cumulative model; preserves entries that leave the NPM log window). - scan.py uses it on the scan path and derives the report's complete set from the ledger (_db_complete_blocklist), falling back to git history only if the DB is unavailable.
This commit is contained in:
1 parent
4a30f6b582
commit
ba48054293
2 files changed
+77
-7
No files matched your search
@@ -322,6 +322,32 @@ def unblock_entry(conn: sqlite3.Connection, cidr: str, *,
|
||||
return removed
|
||||
|
||||
|
||||
def record_blocklist(conn: sqlite3.Connection, cidrs: Sequence[str], *,
|
||||
added_by: str | None = None, reasons=None) -> dict[str, Any]:
|
||||
"""Grow-only record: add any ``cidrs`` not already active to the ledger.
|
||||
|
||||
Unlike :func:`sync_blocklist`, this **never removes** entries. It mirrors
|
||||
the router's cumulative model: once an IP/subnet has ever been blocked it
|
||||
stays active even after it leaves the scan window (e.g. NPM ~30-day log
|
||||
retention), exactly like the old git-history backfill. Returns the added
|
||||
delta and the resulting active count.
|
||||
"""
|
||||
cidr_set = set(cidrs)
|
||||
before = {r["cidr"] for r in active_blocklist(conn)}
|
||||
to_add = sorted(cidr_set - before)
|
||||
now = utcnow()
|
||||
with transaction(conn):
|
||||
for cidr in to_add:
|
||||
_block_entry_inner(conn, cidr, added_by=added_by, last_seen=now,
|
||||
reasons=reasons)
|
||||
# Refresh last_seen on entries that are still desired so the ledger
|
||||
# reflects recency for existing rows too.
|
||||
conn.executemany(
|
||||
"UPDATE blocklist_entries SET last_seen=? WHERE cidr=? AND status='active'",
|
||||
[(now, cidr) for cidr in cidr_set.intersection(before)])
|
||||
return {"added": to_add, "active": len(before) + len(to_add)}
|
||||
|
||||
|
||||
def sync_blocklist(conn: sqlite3.Connection, desired_cidrs: Sequence[str], *,
|
||||
added_by: str | None = None) -> dict[str, Any]:
|
||||
"""Reconcile active blocklist to ``desired_cidrs``.
|
||||
@@ -465,6 +491,20 @@ def save_firewall_snapshot(conn: sqlite3.Connection, *,
|
||||
return snapshot_id
|
||||
|
||||
|
||||
def recent_firewall_snapshots(conn: sqlite3.Connection,
|
||||
limit: int = 10) -> list[dict[str, Any]]:
|
||||
"""Most recent firewall snapshots with entry counts (F001)."""
|
||||
rows = conn.execute(
|
||||
"SELECT fs.id, fs.captured_at, fs.kind, fs.trigger, "
|
||||
"(SELECT COUNT(*) FROM firewall_address_list_entries e "
|
||||
" WHERE e.snapshot_id = fs.id) AS addr_entries, "
|
||||
"(SELECT COUNT(*) FROM firewall_rule_entries r "
|
||||
" WHERE r.snapshot_id = fs.id) AS rule_entries "
|
||||
"FROM firewall_snapshots fs ORDER BY fs.id DESC LIMIT ?",
|
||||
(limit,)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WHOIS cache
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user