Files
intrusion-detection/scan.py
T
zwitschi 0e2c69579a Remove MikroTik block list script and enhance scan.py for firewall synchronization
- Deleted the existing MikroTik block list script (mikrotik-block.rsc) which contained whitelisted and blocked IP addresses.
- Updated scan.py to include options for synchronizing the MikroTik blocked list after scanning.
- Added functionality to preview firewall changes without applying them.
- Integrated the MikrotikFirewall class to manage the synchronization of blocked IPs with the MikroTik device.
2026-08-04 18:38:08 +02:00

147 lines
5.9 KiB
Python

#!/usr/bin/env python3
"""Consolidated intrusion detection log scanner.
Runs both copyparty and Nginx Proxy Manager (NPM) log analyzers,
and outputs a single consolidated MikroTik RouterOS blocklist script.
"""
from __future__ import annotations
import argparse
import os
from datetime import datetime
import utils
from firewall import MikrotikFirewall
from copyparty import CopypartyAnalyzer, DEFAULT_LOG_PATH as CP_DEFAULT_LOG, DEFAULT_REPORT_DIR as CP_DEFAULT_REPORT
from npm import NpmAnalyzer, DEFAULT_LOG_PATH as NPM_DEFAULT_LOG, DEFAULT_REPORT_DIR as NPM_DEFAULT_REPORT
DEFAULT_CONSOLIDATED_REPORT_DIR = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "reports")
def main() -> None:
parser = argparse.ArgumentParser(
description="Consolidated IDS Scanner: run copyparty + NPM analyzers and generate unified blocklist."
)
# copyparty arguments
parser.add_argument("--copyparty-log-path", default=CP_DEFAULT_LOG,
help=f"copyparty log directory (default: {CP_DEFAULT_LOG})")
parser.add_argument("--copyparty-report-dir", default=CP_DEFAULT_REPORT,
help=f"copyparty report directory (default: {CP_DEFAULT_REPORT})")
parser.add_argument("--min-denied", type=int, default=1,
help="minimum copyparty denied hits before flagging (default: 1)")
# NPM arguments
parser.add_argument("--npm-log-path", default=NPM_DEFAULT_LOG,
help=f"NPM log directory (default: {NPM_DEFAULT_LOG})")
parser.add_argument("--npm-report-dir", default=NPM_DEFAULT_REPORT,
help=f"NPM report directory (default: {NPM_DEFAULT_REPORT})")
parser.add_argument("--min-errors", type=int, default=5,
help="minimum NPM error responses to flag (default: 5)")
parser.add_argument("--error-ratio", type=float, default=0.5,
help="minimum NPM error ratio to flag (default: 0.5)")
# Shared arguments
parser.add_argument("--report-dir", default=DEFAULT_CONSOLIDATED_REPORT_DIR,
help=f"consolidated reports directory (default: {DEFAULT_CONSOLIDATED_REPORT_DIR})")
parser.add_argument("--top", type=int, default=30,
help="rows in the top-IP tables (default: 30)")
parser.add_argument("--quiet", action="store_true",
help="write files, suppress console reports")
parser.add_argument("--update-firewall", action="store_true",
help="synchronize the MikroTik Blocked list after scanning")
parser.add_argument("--dry-run", action="store_true",
help="preview firewall changes without applying them")
args = parser.parse_args()
# 1. Initialize Analyzers
# We turn off inner write_rsc of individual analyzers to avoid duplicate/redundant rsc writes,
# or let them write their individual ones if they want to. Writing individual scripts is harmless.
# We'll match the individual tools' default write behavior, but generate the consolidated one.
cp_analyzer = CopypartyAnalyzer(
log_path=args.copyparty_log_path,
report_dir=args.copyparty_report_dir,
top=args.top,
quiet=args.quiet,
write_rsc=False,
min_denied=args.min_denied,
)
npm_analyzer = NpmAnalyzer(
log_path=args.npm_log_path,
report_dir=args.npm_report_dir,
top=args.top,
quiet=args.quiet,
write_rsc=False,
min_errors=args.min_errors,
error_ratio=args.error_ratio,
)
# 2. Run copyparty analysis
print("=== Running copyparty scan ===")
cp_exists = True
try:
cp_analyzer.run()
except Exception as e:
print(f"Error running copyparty scan: {e}")
cp_exists = False
# 3. Run NPM analysis
print("\n=== Running NPM scan ===")
npm_exists = True
try:
npm_analyzer.run()
except Exception as e:
print(f"Error running NPM scan: {e}")
npm_exists = False
if not cp_exists and not npm_exists:
print("\nBoth scans failed. Unable to generate consolidated blocklist.")
return
# 4. Consolidate block candidates
consolidated_candidates: set[str] = set()
if cp_exists:
consolidated_candidates.update(cp_analyzer.block_candidates())
if npm_exists:
consolidated_candidates.update(npm_analyzer.block_candidates())
# Exclude whitelisted IPs
final_blocked_ips = {
ip for ip in consolidated_candidates if not utils.is_whitelisted(ip)}
# Group into subnets via WHOIS if multiple malicious IPs in same subnet (threshold = 3)
print("\nGrouping blocked IPs into subnets using WHOIS...")
final_blocked_targets = utils.consolidate_by_subnet(
final_blocked_ips, threshold=3)
blocked_ipv4, blocked_ipv6 = utils.split_versions(final_blocked_targets)
print("\n=== Consolidated Summary ===")
print(
f"copyparty block candidates: {len(cp_analyzer.block_candidates()) if cp_exists else 0}")
print(
f"NPM block candidates: {len(npm_analyzer.block_candidates()) if npm_exists else 0}")
print(f"Total unified blocked IPs: {len(final_blocked_ips)}")
print(f"Total target blocks/subnets: {len(final_blocked_targets)}")
if args.update_firewall:
firewall = MikrotikFirewall()
firewall.connect()
try:
changes = firewall.sync_blocklist(
blocked_ipv4, blocked_ipv6, dry_run=args.dry_run,
report_dir=args.report_dir)
finally:
firewall.disconnect()
action = "Would" if args.dry_run else ""
print(f"{action} add {len(changes['added_ipv4'])} IPv4 and "
f"{len(changes['added_ipv6'])} IPv6 addresses; "
f"remove {len(changes['removed_ipv4'])} IPv4 and "
f"{len(changes['removed_ipv6'])} IPv6 addresses.")
if __name__ == "__main__":
main()