Files
honeydany/app/dashboard/blocklist_export.go
T

328 lines
11 KiB
Go

package dashboard
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
)
// BlocklistExporter handles exporting threat intelligence for external use
type BlocklistExporter struct {
analyzer *ThreatAnalyzer
}
// NewBlocklistExporter creates a new blocklist exporter
func NewBlocklistExporter(analyzer *ThreatAnalyzer) *BlocklistExporter {
return &BlocklistExporter{
analyzer: analyzer,
}
}
// RegisterExportRoutes registers blocklist export endpoints
func (be *BlocklistExporter) RegisterExportRoutes(mux *http.ServeMux, sm *SecurityManager) {
// Public blocklist endpoints (no auth required for consumption)
mux.HandleFunc("/api/export/blocklist/txt", be.handleTxtBlocklist)
mux.HandleFunc("/api/export/blocklist/json", be.handleJSONBlocklist)
mux.HandleFunc("/api/export/blocklist/csv", be.handleCSVBlocklist)
mux.HandleFunc("/api/export/blocklist/suricata", be.handleSuricataBlocklist)
mux.HandleFunc("/api/export/blocklist/pf", be.handlePFBlocklist)
mux.HandleFunc("/api/export/blocklist/iptables", be.handleIPTablesBlocklist)
// Statistics endpoint
mux.HandleFunc("/api/export/stats", be.handleStats)
}
// BlocklistEntry represents an entry in the blocklist
type BlocklistEntry struct {
IP string `json:"ip"`
ThreatScore int `json:"threat_score"`
FirstSeen time.Time `json:"first_seen"`
LastSeen time.Time `json:"last_seen"`
TotalEvents int `json:"total_events"`
Services []string `json:"services"`
EventTypes []string `json:"event_types"`
IsBlocked bool `json:"is_blocked"`
Confidence string `json:"confidence"` // high, medium, low
}
// getBlocklistEntries retrieves blocklist entries with filtering
func (be *BlocklistExporter) getBlocklistEntries(minScore int, maxAge time.Duration, includeUnblocked bool) ([]BlocklistEntry, error) {
// Get IP reports with filters
filters := map[string]interface{}{
"min_threat_score": minScore,
}
if !includeUnblocked {
filters["is_blocked"] = true
}
reports, err := be.analyzer.GetIPReports(filters)
if err != nil {
return nil, err
}
var entries []BlocklistEntry
cutoff := time.Now().Add(-maxAge)
for _, report := range reports {
// Skip old entries if maxAge is specified
if maxAge > 0 && report.LastSeen.Before(cutoff) {
continue
}
// Get threat events for this IP
events, _ := be.analyzer.GetThreatEventsByIP(report.IP)
var services []string
var eventTypes []string
eventTypeMap := make(map[string]bool)
services = report.Services
for _, event := range events {
if !eventTypeMap[event.EventType] {
eventTypes = append(eventTypes, event.EventType)
eventTypeMap[event.EventType] = true
}
}
// Determine confidence level
confidence := "low"
if report.ThreatScore >= 80 {
confidence = "high"
} else if report.ThreatScore >= 50 {
confidence = "medium"
}
entries = append(entries, BlocklistEntry{
IP: report.IP,
ThreatScore: report.ThreatScore,
FirstSeen: report.FirstSeen,
LastSeen: report.LastSeen,
TotalEvents: len(events),
Services: services,
EventTypes: eventTypes,
IsBlocked: report.IsBlocked,
Confidence: confidence,
})
}
return entries, nil
}
// handleTxtBlocklist exports blocklist in plain text format (one IP per line)
func (be *BlocklistExporter) handleTxtBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 50)
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour) // 30 days default
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.txt")
// Add header comment
fmt.Fprintf(w, "# Honeypot Threat Intelligence Blocklist\n")
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
fmt.Fprintf(w, "# Min Score: %d\n", minScore)
fmt.Fprintf(w, "# Total IPs: %d\n", len(entries))
fmt.Fprintf(w, "#\n")
for _, entry := range entries {
fmt.Fprintf(w, "%s\n", entry.IP)
}
}
// handleJSONBlocklist exports blocklist in JSON format
func (be *BlocklistExporter) handleJSONBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 50)
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour)
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
response := map[string]interface{}{
"generated_at": time.Now().UTC(),
"min_score": minScore,
"total_ips": len(entries),
"entries": entries,
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.json")
json.NewEncoder(w).Encode(response)
}
// handleCSVBlocklist exports blocklist in CSV format
func (be *BlocklistExporter) handleCSVBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 50)
maxAge := be.getDurationParam(r, "max_age", 30*24*time.Hour)
includeUnblocked := be.getBoolParam(r, "include_unblocked", false)
entries, err := be.getBlocklistEntries(minScore, maxAge, includeUnblocked)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_blocklist.csv")
// CSV header
fmt.Fprintf(w, "ip,threat_score,first_seen,last_seen,total_events,services,event_types,confidence\n")
for _, entry := range entries {
fmt.Fprintf(w, "%s,%d,%s,%s,%d,\"%s\",\"%s\",%s\n",
entry.IP,
entry.ThreatScore,
entry.FirstSeen.Format(time.RFC3339),
entry.LastSeen.Format(time.RFC3339),
entry.TotalEvents,
strings.Join(entry.Services, ";"),
strings.Join(entry.EventTypes, ";"),
entry.Confidence,
)
}
}
// handleSuricataBlocklist exports blocklist in Suricata format
func (be *BlocklistExporter) handleSuricataBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 70) // Higher threshold for Suricata
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour) // 7 days for active blocking
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_suricata.rules")
// Generate Suricata rules
fmt.Fprintf(w, "# Honeypot Threat Intelligence - Suricata Rules\n")
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
fmt.Fprintf(w, "#\n")
for i, entry := range entries {
fmt.Fprintf(w, "drop ip %s any -> any any (msg:\"Honeypot Threat - %s (Score: %d)\"; sid:%d; rev:1;)\n",
entry.IP, strings.Join(entry.EventTypes, ","), entry.ThreatScore, 1000000+i)
}
}
// handlePFBlocklist exports blocklist in PF (BSD firewall) format
func (be *BlocklistExporter) handlePFBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 60)
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour)
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_pf_blocklist.conf")
fmt.Fprintf(w, "# Honeypot Threat Intelligence - PF Blocklist\n")
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
fmt.Fprintf(w, "# Usage: pfctl -t honeypot_threats -T add -f honeypot_pf_blocklist.conf\n")
fmt.Fprintf(w, "#\n")
for _, entry := range entries {
fmt.Fprintf(w, "%s\n", entry.IP)
}
}
// handleIPTablesBlocklist exports blocklist in iptables format
func (be *BlocklistExporter) handleIPTablesBlocklist(w http.ResponseWriter, r *http.Request) {
minScore := be.getIntParam(r, "min_score", 60)
maxAge := be.getDurationParam(r, "max_age", 7*24*time.Hour)
entries, err := be.getBlocklistEntries(minScore, maxAge, false)
if err != nil {
http.Error(w, "Failed to get blocklist", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/plain")
w.Header().Set("Content-Disposition", "attachment; filename=honeypot_iptables.sh")
fmt.Fprintf(w, "#!/bin/bash\n")
fmt.Fprintf(w, "# Honeypot Threat Intelligence - iptables Rules\n")
fmt.Fprintf(w, "# Generated: %s\n", time.Now().UTC().Format(time.RFC3339))
fmt.Fprintf(w, "#\n\n")
for _, entry := range entries {
fmt.Fprintf(w, "iptables -A INPUT -s %s -j DROP # Score: %d, Events: %d\n",
entry.IP, entry.ThreatScore, entry.TotalEvents)
}
}
// handleStats provides statistics about the threat intelligence
func (be *BlocklistExporter) handleStats(w http.ResponseWriter, r *http.Request) {
// Get various statistics
allEntries, _ := be.getBlocklistEntries(0, 0, true)
blockedEntries, _ := be.getBlocklistEntries(0, 0, false)
highThreatEntries, _ := be.getBlocklistEntries(80, 0, true)
recentEntries, _ := be.getBlocklistEntries(0, 24*time.Hour, true)
stats := map[string]interface{}{
"generated_at": time.Now().UTC(),
"total_ips": len(allEntries),
"blocked_ips": len(blockedEntries),
"high_threat_ips": len(highThreatEntries),
"recent_ips_24h": len(recentEntries),
"export_formats": []string{"txt", "json", "csv", "suricata", "pf", "iptables"},
"api_endpoints": map[string]string{
"txt": "/api/export/blocklist/txt",
"json": "/api/export/blocklist/json",
"csv": "/api/export/blocklist/csv",
"suricata": "/api/export/blocklist/suricata",
"pf": "/api/export/blocklist/pf",
"iptables": "/api/export/blocklist/iptables",
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(stats)
}
// Helper functions for parameter parsing
func (be *BlocklistExporter) getIntParam(r *http.Request, param string, defaultValue int) int {
if val := r.URL.Query().Get(param); val != "" {
if parsed, err := strconv.Atoi(val); err == nil {
return parsed
}
}
return defaultValue
}
func (be *BlocklistExporter) getDurationParam(r *http.Request, param string, defaultValue time.Duration) time.Duration {
if val := r.URL.Query().Get(param); val != "" {
if parsed, err := time.ParseDuration(val); err == nil {
return parsed
}
}
return defaultValue
}
func (be *BlocklistExporter) getBoolParam(r *http.Request, param string, defaultValue bool) bool {
if val := r.URL.Query().Get(param); val != "" {
return val == "true" || val == "1"
}
return defaultValue
}