Report attacker IPs observed by APIARY to public threat-intel blocklists via their APIs.
Status: built.
reporter/(Go, not the Python layout sketched below -- that part of this plan is superseded) is a real service inarcane/home/honeypot-utilities/compose.yml. Phase 1 (#68) and Phase 2 (#69) are both closed. Phase 3-4 (reputation validation, operator observability) is #153, closed and implemented:reporter/greynoise.goimplements the Phase 3 GreyNoise validation, andreporter/metrics.goimplements Phase 4's observability counters (attempted/suppressed/dryRun/sent/failed) as a JSON snapshot — a deliberate deviation from the Prometheus sketch originally proposed below.Reporting is outbound and irreversible. An abuse report cannot be unsent, and it names a third party's address from this stack. The reporter ships dry-run by default (
REPORTER_LIVE/ABUSEIPDB_API_KEYboth unset) and stays that way until live reporting is separately approved — a standing constraint on this feature, not a Phase 1 convenience.This is defensive reporting (get an attacker's IP blocked elsewhere) -- deliberately kept separate from offensive/research community threat-intel sharing (contributing this stack's observations to a shared corpus other researchers draw from), which was evaluated and declined; see
community-threat-intel-sharing.md.
- Report every IP that touches a honeypot sensor to one or more blocklist feeds
- Deduplicate: never report the same IP twice within a cooldown window
- Respect rate limits of each upstream API
- Run as a lightweight sidecar container (
reporter), tailing sensor logs from the volumes the stack already writes - After keys are set in
.envand dry-run output has been reviewed, no per-report human step
| Service | API | Free tier | Best for |
|---|---|---|---|
| AbuseIPDB | POST /api/v2/reports |
1 000 reports/day | General abuse, SSH, HTTP scans |
| Blocklist.de | POST https://api.blocklist.de/api |
Unlimited | Traditional fail2ban-style reporting |
| GreyNoise RIOT | Read-only free tier | — | Cross-check before reporting (avoid FP) |
Primary target: AbuseIPDB — widely used, has a public confidence score, and feeds back into many firewall blocklists automatically.
flowchart TD
Sensors["Cowrie / Dionaea / Conpot / HTTP-honeypot / DNP3"]
Reporter["reporter (Python)<br/>new Docker Compose service"]
AbuseIPDB["AbuseIPDB"]
Blocklist["Blocklist.de"]
Sensors -->|"JSON event logs on the shared Docker volumes, tailed —<br/>not Redis pub-sub; see 'Resolved design decisions'"| Reporter
Reporter -->|"POST /api/v2/reports"| AbuseIPDB
AbuseIPDB -->|optional| Blocklist
The reporter container:
- Watches the same log/event volume already mounted by the
analysisandml-workercontainers - Maintains a local SQLite DB (
/data/reported.db) to deduplicate IPs per service per 24 h - Exposes a
/metricsendpoint (Prometheus) so Grafana can graph reports-per-hour
| Sensor | Log path (inside container) | Event format |
|---|---|---|
| cowrie | /cowrie/var/log/cowrie/cowrie.json |
JSON lines |
| dionaea | /opt/dionaea/var/log/dionaea/ |
JSON / SQLite |
| conpot | /var/log/conpot/ |
JSON lines |
| http-honeypot | stdout JSON | JSON lines |
| dnp3-honeypot | stdout JSON | JSON lines |
| snare/tanner | tanner REST API | JSON |
All sensors already write structured JSON. The reporter will tail these files (or consume Redis events if the stack is extended to pub-sub).
Map honeypot events to AbuseIPDB category codes:
| Honeypot event | Category codes |
|---|---|
| SSH brute-force (cowrie) | 18 (Brute-Force), 22 (SSH) |
| Telnet login attempt (cowrie) | 18, 23 (IoT Targeted) |
| HTTP scan / exploit attempt | 21 (Web App Attack) |
| Port scan (multipot/conpot) | 14 (Port Scan) |
| Malware download (dionaea) | 20 (Malware) |
| ICS/SCADA probe (conpot/dnp3) | 14, 15 (Hacking) |
| DNS abuse | 11 (DNS Compromise) |
- Create
reporter/directory with:reporter.py— main event loopDockerfile— slim Python 3.12 imagerequirements.txt—requests,watchdog,prometheus-client
- Logic:
# Pseudocode for event in tail_logs(SENSOR_PATHS): ip = event["src_ip"] if not recently_reported(ip, service="abuseipdb", window_hours=24): categories = map_to_categories(event["type"]) abuseipdb_report(ip, categories, comment=build_comment(event)) mark_reported(ip, "abuseipdb")
- SQLite schema:
CREATE TABLE reports ( ip TEXT NOT NULL, service TEXT NOT NULL, reported_at INTEGER NOT NULL, -- Unix timestamp event_type TEXT, PRIMARY KEY (ip, service, reported_at / 86400) -- 1 report per IP per day );
- Add secondary reporter for Blocklist.de using the same dedup logic
- Blocklist.de uses a simple HTTP POST with
server,ip,logsfields - Useful for SSH/FTP/SIP — feeds European ISP blocklists
Before reporting, cross-check against:
- GreyNoise RIOT (
GET /v3/riot/{ip}) — skip known benign scanners (Shodan, Censys, security researchers) - Local whitelist file
reporter/whitelist.txt— your own IPs, Tor exits you want to skip - RFC 1918 / loopback / link-local guard (never report private IPs)
- Prometheus metrics from
reporter:honeypot_reports_total{service, category}counterhoneypot_report_errors_total{service, error}counterhoneypot_dedup_skips_totalcounter
- Add Grafana panel to existing dashboard: "Reports sent this week" bar chart
- Alert webhook (existing
HONEYPOT_ALERT_WEBHOOK_URL) if AbuseIPDB rate limit is approaching
Add to docker-compose.yml:
reporter:
build: ./reporter
restart: unless-stopped
environment:
ABUSEIPDB_API_KEY: ${ABUSEIPDB_API_KEY}
BLOCKLIST_DE_EMAIL: ${BLOCKLIST_DE_EMAIL}
BLOCKLIST_DE_PASSWORD: ${BLOCKLIST_DE_PASSWORD}
GREYNOISE_API_KEY: ${GREYNOISE_API_KEY:-} # optional
REPORTER_COOLDOWN_HOURS: ${REPORTER_COOLDOWN_HOURS:-24}
REPORTER_WHITELIST: /config/whitelist.txt
volumes:
- cowrie_logs:/logs/cowrie:ro
- dionaea_logs:/logs/dionaea:ro
- conpot_logs:/logs/conpot:ro
- reporter_data:/data
- ./reporter/whitelist.txt:/config/whitelist.txt:ro
ports:
- "127.0.0.1:9101:9101" # Prometheus metrics
networks:
- honeypot_internalAdd to .env.example:
# IP Blocklist Reporting
ABUSEIPDB_API_KEY=
BLOCKLIST_DE_EMAIL=
BLOCKLIST_DE_PASSWORD=
GREYNOISE_API_KEY=
REPORTER_COOLDOWN_HOURS=24| Service | Limit | Mitigation |
|---|---|---|
| AbuseIPDB free | 1 000 reports/day | Dedup per 24 h; batch high-volume periods |
| AbuseIPDB paid | 10 000/day | Upgrade if volume exceeds free tier |
| Blocklist.de | No hard limit; fair use | 1 report per IP per 24 h |
| GreyNoise free | 1 000 lookups/day | Cache RIOT lookups for 6 h in SQLite |
The reporter will track a daily counter and pause with exponential back-off on 429 responses.
- GreyNoise RIOT check: if
riot=true→ skip (known benign scanner) - Minimum event threshold: only report after ≥ 3 events from same IP within 10 min (configurable)
- Whitelist:
reporter/whitelist.txt— CIDR and single IP entries, one per line - ASN filter: optionally skip reports for IPs belonging to major cloud providers unless event severity is high
flowchart TD
ReporterDir["reporter/"] --> Dockerfile["Dockerfile"]
ReporterDir --> Requirements["requirements.txt"]
ReporterDir --> ReporterPy["reporter.py<br/>main loop"]
ReporterDir --> SourcesPy["sources.py<br/>per-sensor log parsers"]
ReporterDir --> ApisPy["apis.py<br/>AbuseIPDB + Blocklist.de clients"]
ReporterDir --> DedupPy["dedup.py<br/>SQLite-backed deduplication"]
ReporterDir --> WhitelistTxt["whitelist.txt<br/>safe IPs/CIDRs to never report"]
ReporterDir --> MetricsPy["metrics.py<br/>Prometheus exporter"]
DocsDir["docs/"] --> PlanMd["ip-reporting-plan.md<br/>this file"]
These were open when the plan was written. They are settled; the implementation work they govern is tracked in #68 and #69.
- Ingest via file tailing, not a message bus. A Redis pub-sub channel has
lower latency but adds a service to Compose for a reporter whose cooldown
windows are measured in hours. Tail the logs; revisit only if
ml-workerbrings pubsub in for its own reasons. - Suricata IDS alerts are in scope, alongside honeypot hits, from Phase 2 (#69).
- No auto-banning. The reporter stays stateless and reporting-only. Firewall enforcement is a different trust boundary and a different failure mode: a false positive that costs an abuse report is recoverable, one that installs an nftables rule is not.