Compare commits
No commits in common. "main" and "v1.0.97" have entirely different histories.
@ -1,51 +0,0 @@
|
||||
journalctl -u ids-list-fetcher -n 50 --no-pager
|
||||
Jan 02 17:10:02 ids.alfacom.it ids-list-fetcher[2139]: ============================================================
|
||||
Jan 02 17:10:02 ids.alfacom.it ids-list-fetcher[2139]: ============================================================
|
||||
Jan 02 17:10:02 ids.alfacom.it ids-list-fetcher[2139]: RUNNING MERGE LOGIC
|
||||
Jan 02 17:10:02 ids.alfacom.it ids-list-fetcher[2139]: ============================================================
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: INFO:merge_logic:Bulk sync complete: {'created': 0, 'cleaned': 0, 'skipped_whitelisted': 0}
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: Merge Logic Stats:
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: Created detections: 0
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: Cleaned invalid detections: 0
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: Skipped (whitelisted): 0
|
||||
Jan 02 17:10:12 ids.alfacom.it ids-list-fetcher[2139]: ============================================================
|
||||
Jan 02 17:10:12 ids.alfacom.it systemd[1]: ids-list-fetcher.service: Deactivated successfully.
|
||||
Jan 02 17:10:12 ids.alfacom.it systemd[1]: Finished IDS Public Lists Fetcher Service.
|
||||
Jan 02 17:12:35 ids.alfacom.it systemd[1]: Starting IDS Public Lists Fetcher Service...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [2026-01-02 17:12:35] PUBLIC LISTS SYNC
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: Found 4 enabled lists
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Downloading Spamhaus from https://www.spamhaus.org/drop/drop_v4.json...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Downloading AWS from https://ip-ranges.amazonaws.com/ip-ranges.json...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Downloading Google Cloud from https://www.gstatic.com/ipranges/cloud.json...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Downloading Google globali from https://www.gstatic.com/ipranges/goog.json...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Parsing AWS...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Found 9548 IPs, syncing to database...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] ✓ AWS: +0 -0 ~9548
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Parsing Google globali...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] ✗ Google globali: No valid IPs found in list
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Parsing Google Cloud...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] ✗ Google Cloud: No valid IPs found in list
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Parsing Spamhaus...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] Found 1468 IPs, syncing to database...
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: [17:12:35] ✓ Spamhaus: +0 -0 ~1468
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: SYNC SUMMARY
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: Success: 2/4
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: Errors: 2/4
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: Total IPs Added: 0
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: Total IPs Removed: 0
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: RUNNING MERGE LOGIC
|
||||
Jan 02 17:12:35 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: INFO:merge_logic:Bulk sync complete: {'created': 0, 'cleaned': 0, 'skipped_whitelisted': 0}
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: Merge Logic Stats:
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: Created detections: 0
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: Cleaned invalid detections: 0
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: Skipped (whitelisted): 0
|
||||
Jan 02 17:12:45 ids.alfacom.it ids-list-fetcher[2279]: ============================================================
|
||||
Jan 02 17:12:45 ids.alfacom.it systemd[1]: ids-list-fetcher.service: Deactivated successfully.
|
||||
Jan 02 17:12:45 ids.alfacom.it systemd[1]: Finished IDS Public Lists Fetcher Service.
|
||||
@ -1,4 +0,0 @@
|
||||
curl -X POST http://localhost:8000/detect \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"max_records": 5000, "hours_back": 1, "risk_threshold": 80, "auto_block": true}'
|
||||
{"detections":[{"source_ip":"108.139.210.107","risk_score":98.55466848373413,"confidence_level":"high","action_recommendation":"auto_block","anomaly_type":"ddos","reason":"High connection rate: 403.7 conn/s","log_count":1211,"total_packets":1211,"total_bytes":2101702,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":95.0},{"source_ip":"216.58.209.54","risk_score":95.52801848493884,"confidence_level":"high","action_recommendation":"auto_block","anomaly_type":"brute_force","reason":"High connection rate: 184.7 conn/s","log_count":554,"total_packets":554,"total_bytes":782397,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":95.0},{"source_ip":"95.127.69.202","risk_score":93.58280514393482,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"brute_force","reason":"High connection rate: 93.7 conn/s","log_count":281,"total_packets":281,"total_bytes":369875,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0},{"source_ip":"95.127.72.207","risk_score":92.50694363471318,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"brute_force","reason":"High connection rate: 76.3 conn/s","log_count":229,"total_packets":229,"total_bytes":293439,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0},{"source_ip":"95.110.183.67","risk_score":86.42278405656512,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"brute_force","reason":"High connection rate: 153.0 conn/s","log_count":459,"total_packets":459,"total_bytes":20822,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0},{"source_ip":"54.75.71.86","risk_score":83.42037059381207,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"brute_force","reason":"High connection rate: 58.0 conn/s","log_count":174,"total_packets":174,"total_bytes":25857,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0},{"source_ip":"79.10.127.217","risk_score":82.32814469102843,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"brute_force","reason":"High connection rate: 70.0 conn/s","log_count":210,"total_packets":210,"total_bytes":18963,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0},{"source_ip":"142.251.140.100","risk_score":76.61422108557721,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"botnet","reason":"Anomalous pattern detected (botnet)","log_count":16,"total_packets":16,"total_bytes":20056,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:53","confidence":75.0},{"source_ip":"142.250.181.161","risk_score":76.3802033958719,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"botnet","reason":"Anomalous pattern detected (botnet)","log_count":15,"total_packets":15,"total_bytes":5214,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:51","confidence":75.0},{"source_ip":"142.250.180.131","risk_score":72.7723405111559,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"suspicious","reason":"Anomalous pattern detected (suspicious)","log_count":8,"total_packets":8,"total_bytes":5320,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:53","confidence":75.0},{"source_ip":"157.240.231.60","risk_score":72.26853648050493,"confidence_level":"medium","action_recommendation":"manual_review","anomaly_type":"botnet","reason":"Anomalous pattern detected (botnet)","log_count":16,"total_packets":16,"total_bytes":4624,"first_seen":"2026-01-02T16:41:51","last_seen":"2026-01-02T16:41:54","confidence":75.0}],"total":11,"blocked":0,"message":"Trovate 11 anomalie"}[root@ids python_ml]#
|
||||
@ -1,51 +0,0 @@
|
||||
journalctl -u ids-list-fetcher -n 50 --no-pager
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: Merge Logic Stats:
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: Created detections: 0
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: Cleaned invalid detections: 0
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: Skipped (whitelisted): 0
|
||||
Jan 02 16:11:31 ids.alfacom.it ids-list-fetcher[10401]: ============================================================
|
||||
Jan 02 16:11:31 ids.alfacom.it systemd[1]: ids-list-fetcher.service: Deactivated successfully.
|
||||
Jan 02 16:11:31 ids.alfacom.it systemd[1]: Finished IDS Public Lists Fetcher Service.
|
||||
Jan 02 16:15:04 ids.alfacom.it systemd[1]: Starting IDS Public Lists Fetcher Service...
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [2026-01-02 16:15:04] PUBLIC LISTS SYNC
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: Found 2 enabled lists
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] Downloading Spamhaus from https://www.spamhaus.org/drop/drop_v4.json...
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] Downloading AWS from https://ip-ranges.amazonaws.com/ip-ranges.json...
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] Parsing Spamhaus...
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] Found 1468 IPs, syncing to database...
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] ✓ Spamhaus: +0 -0 ~1468
|
||||
Jan 02 16:15:04 ids.alfacom.it ids-list-fetcher[10801]: [16:15:04] Parsing AWS...
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: [16:15:05] Found 9548 IPs, syncing to database...
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: [16:15:05] ✓ AWS: +9548 -0 ~0
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: SYNC SUMMARY
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Success: 2/2
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Errors: 0/2
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Total IPs Added: 9548
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Total IPs Removed: 0
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: RUNNING MERGE LOGIC
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ERROR:merge_logic:Failed to sync detections: column "risk_score" is of type numeric but expression is of type text
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: LINE 13: '75',
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ^
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: HINT: You will need to rewrite or cast the expression.
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Traceback (most recent call last):
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: File "/opt/ids/python_ml/merge_logic.py", line 264, in sync_public_blacklist_detections
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: cur.execute("""
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: psycopg2.errors.DatatypeMismatch: column "risk_score" is of type numeric but expression is of type text
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: LINE 13: '75',
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ^
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: HINT: You will need to rewrite or cast the expression.
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Merge Logic Stats:
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Created detections: 0
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Cleaned invalid detections: 0
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: Skipped (whitelisted): 0
|
||||
Jan 02 16:15:05 ids.alfacom.it ids-list-fetcher[10801]: ============================================================
|
||||
Jan 02 16:15:05 ids.alfacom.it systemd[1]: ids-list-fetcher.service: Deactivated successfully.
|
||||
Jan 02 16:15:05 ids.alfacom.it systemd[1]: Finished IDS Public Lists Fetcher Service.
|
||||
@ -5,81 +5,44 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus, ShieldCheck, Unlock, ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
import type { Detection, Whitelist } from "@shared/schema";
|
||||
import { useState } from "react";
|
||||
import type { Detection } from "@shared/schema";
|
||||
import { getFlag } from "@/lib/country-flags";
|
||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
|
||||
const ITEMS_PER_PAGE = 50;
|
||||
|
||||
interface DetectionsResponse {
|
||||
detections: Detection[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export default function Detections() {
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [anomalyTypeFilter, setAnomalyTypeFilter] = useState<string>("all");
|
||||
const [minScore, setMinScore] = useState(0);
|
||||
const [maxScore, setMaxScore] = useState(100);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const { toast } = useToast();
|
||||
|
||||
// Debounce search input
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedSearch(searchInput);
|
||||
setCurrentPage(1); // Reset to first page on search
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchInput]);
|
||||
|
||||
// Reset page on filter change
|
||||
useEffect(() => {
|
||||
setCurrentPage(1);
|
||||
}, [anomalyTypeFilter, minScore, maxScore]);
|
||||
|
||||
// Build query params with pagination and search
|
||||
const queryParams = useMemo(() => {
|
||||
const params = new URLSearchParams();
|
||||
params.set("limit", ITEMS_PER_PAGE.toString());
|
||||
params.set("offset", ((currentPage - 1) * ITEMS_PER_PAGE).toString());
|
||||
// Build query params
|
||||
const queryParams = new URLSearchParams();
|
||||
queryParams.set("limit", "5000");
|
||||
if (anomalyTypeFilter !== "all") {
|
||||
params.set("anomalyType", anomalyTypeFilter);
|
||||
queryParams.set("anomalyType", anomalyTypeFilter);
|
||||
}
|
||||
if (minScore > 0) {
|
||||
params.set("minScore", minScore.toString());
|
||||
queryParams.set("minScore", minScore.toString());
|
||||
}
|
||||
if (maxScore < 100) {
|
||||
params.set("maxScore", maxScore.toString());
|
||||
queryParams.set("maxScore", maxScore.toString());
|
||||
}
|
||||
if (debouncedSearch.trim()) {
|
||||
params.set("search", debouncedSearch.trim());
|
||||
}
|
||||
return params.toString();
|
||||
}, [currentPage, anomalyTypeFilter, minScore, maxScore, debouncedSearch]);
|
||||
|
||||
const { data, isLoading } = useQuery<DetectionsResponse>({
|
||||
queryKey: ["/api/detections", currentPage, anomalyTypeFilter, minScore, maxScore, debouncedSearch],
|
||||
queryFn: () => fetch(`/api/detections?${queryParams}`).then(r => r.json()),
|
||||
refetchInterval: 10000,
|
||||
const { data: detections, isLoading } = useQuery<Detection[]>({
|
||||
queryKey: ["/api/detections", anomalyTypeFilter, minScore, maxScore],
|
||||
queryFn: () => fetch(`/api/detections?${queryParams.toString()}`).then(r => r.json()),
|
||||
refetchInterval: 5000,
|
||||
});
|
||||
|
||||
const detections = data?.detections || [];
|
||||
const totalCount = data?.total || 0;
|
||||
const totalPages = Math.ceil(totalCount / ITEMS_PER_PAGE);
|
||||
|
||||
// Fetch whitelist to check if IP is already whitelisted
|
||||
const { data: whitelistData } = useQuery<Whitelist[]>({
|
||||
queryKey: ["/api/whitelist"],
|
||||
});
|
||||
|
||||
// Create a Set of whitelisted IPs for fast lookup
|
||||
const whitelistedIps = new Set(whitelistData?.map(w => w.ipAddress) || []);
|
||||
const filteredDetections = detections?.filter((d) =>
|
||||
d.sourceIp.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
d.anomalyType.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
// Mutation per aggiungere a whitelist
|
||||
const addToWhitelistMutation = useMutation({
|
||||
@ -92,7 +55,7 @@ export default function Detections() {
|
||||
onSuccess: (_, detection) => {
|
||||
toast({
|
||||
title: "IP aggiunto alla whitelist",
|
||||
description: `${detection.sourceIp} è stato aggiunto alla whitelist e sbloccato dai router.`,
|
||||
description: `${detection.sourceIp} è stato aggiunto alla whitelist con successo.`,
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/whitelist"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/detections"] });
|
||||
@ -106,29 +69,6 @@ export default function Detections() {
|
||||
}
|
||||
});
|
||||
|
||||
// Mutation per sbloccare IP dai router
|
||||
const unblockMutation = useMutation({
|
||||
mutationFn: async (detection: Detection) => {
|
||||
return await apiRequest("POST", "/api/unblock-ip", {
|
||||
ipAddress: detection.sourceIp
|
||||
});
|
||||
},
|
||||
onSuccess: (data: any, detection) => {
|
||||
toast({
|
||||
title: "IP sbloccato",
|
||||
description: `${detection.sourceIp} è stato rimosso dalla blocklist di ${data.unblocked_from || 0} router.`,
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: ["/api/detections"] });
|
||||
},
|
||||
onError: (error: any, detection) => {
|
||||
toast({
|
||||
title: "Errore sblocco",
|
||||
description: error.message || `Impossibile sbloccare ${detection.sourceIp} dai router.`,
|
||||
variant: "destructive",
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const getRiskBadge = (riskScore: string) => {
|
||||
const score = parseFloat(riskScore);
|
||||
if (score >= 85) return <Badge variant="destructive">CRITICO</Badge>;
|
||||
@ -166,9 +106,9 @@ export default function Detections() {
|
||||
<div className="relative flex-1 min-w-[200px]">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Cerca per IP, paese, organizzazione..."
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
placeholder="Cerca per IP o tipo anomalia..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
data-testid="input-search"
|
||||
/>
|
||||
@ -220,36 +160,9 @@ export default function Detections() {
|
||||
{/* Detections List */}
|
||||
<Card data-testid="card-detections-list">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center justify-between gap-2 flex-wrap">
|
||||
<div className="flex items-center gap-2">
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<AlertTriangle className="h-5 w-5" />
|
||||
Rilevamenti ({totalCount})
|
||||
</div>
|
||||
{totalPages > 1 && (
|
||||
<div className="flex items-center gap-2 text-sm font-normal">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
data-testid="button-prev-page"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
<span data-testid="text-pagination">
|
||||
Pagina {currentPage} di {totalPages}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
data-testid="button-next-page"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
Rilevamenti ({filteredDetections?.length || 0})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -257,9 +170,9 @@ export default function Detections() {
|
||||
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
|
||||
Caricamento...
|
||||
</div>
|
||||
) : detections.length > 0 ? (
|
||||
) : filteredDetections && filteredDetections.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{detections.map((detection) => (
|
||||
{filteredDetections.map((detection) => (
|
||||
<div
|
||||
key={detection.id}
|
||||
className="p-4 rounded-lg border hover-elevate"
|
||||
@ -365,18 +278,6 @@ export default function Detections() {
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{whitelistedIps.has(detection.sourceIp) ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
disabled
|
||||
className="w-full bg-green-500/10 border-green-500 text-green-600 dark:text-green-400"
|
||||
data-testid={`button-whitelist-${detection.id}`}
|
||||
>
|
||||
<ShieldCheck className="h-3 w-3 mr-1" />
|
||||
In Whitelist
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
@ -388,21 +289,6 @@ export default function Detections() {
|
||||
<ShieldPlus className="h-3 w-3 mr-1" />
|
||||
Whitelist
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{detection.blocked && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => unblockMutation.mutate(detection)}
|
||||
disabled={unblockMutation.isPending}
|
||||
className="w-full"
|
||||
data-testid={`button-unblock-${detection.id}`}
|
||||
>
|
||||
<Unlock className="h-3 w-3 mr-1" />
|
||||
Sblocca Router
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -412,40 +298,11 @@ export default function Detections() {
|
||||
<div className="text-center py-12 text-muted-foreground" data-testid="text-no-results">
|
||||
<AlertTriangle className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||||
<p>Nessun rilevamento trovato</p>
|
||||
{debouncedSearch && (
|
||||
{searchQuery && (
|
||||
<p className="text-sm">Prova con un altro termine di ricerca</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Bottom pagination */}
|
||||
{totalPages > 1 && detections.length > 0 && (
|
||||
<div className="flex items-center justify-center gap-4 mt-6 pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(p => Math.max(1, p - 1))}
|
||||
disabled={currentPage === 1}
|
||||
data-testid="button-prev-page-bottom"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 mr-1" />
|
||||
Precedente
|
||||
</Button>
|
||||
<span className="text-sm text-muted-foreground" data-testid="text-pagination-bottom">
|
||||
Pagina {currentPage} di {totalPages} ({totalCount} totali)
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(p => Math.min(totalPages, p + 1))}
|
||||
disabled={currentPage === totalPages}
|
||||
data-testid="button-next-page-bottom"
|
||||
>
|
||||
Successiva
|
||||
<ChevronRight className="h-4 w-4 ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@ import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { queryClient, apiRequest } from "@/lib/queryClient";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Shield, Plus, Trash2, CheckCircle2, XCircle, Search } from "lucide-react";
|
||||
import { Shield, Plus, Trash2, CheckCircle2, XCircle } from "lucide-react";
|
||||
import { format } from "date-fns";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
@ -44,7 +44,6 @@ const whitelistFormSchema = insertWhitelistSchema.extend({
|
||||
export default function WhitelistPage() {
|
||||
const { toast } = useToast();
|
||||
const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
|
||||
const form = useForm<z.infer<typeof whitelistFormSchema>>({
|
||||
resolver: zodResolver(whitelistFormSchema),
|
||||
@ -60,13 +59,6 @@ export default function WhitelistPage() {
|
||||
queryKey: ["/api/whitelist"],
|
||||
});
|
||||
|
||||
// Filter whitelist based on search query
|
||||
const filteredWhitelist = whitelist?.filter((item) =>
|
||||
item.ipAddress.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.reason?.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
item.comment?.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const addMutation = useMutation({
|
||||
mutationFn: async (data: z.infer<typeof whitelistFormSchema>) => {
|
||||
return await apiRequest("POST", "/api/whitelist", data);
|
||||
@ -197,27 +189,11 @@ export default function WhitelistPage() {
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
{/* Search Bar */}
|
||||
<Card data-testid="card-search">
|
||||
<CardContent className="pt-6">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Cerca per IP, motivo o note..."
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="pl-9"
|
||||
data-testid="input-search-whitelist"
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card data-testid="card-whitelist">
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Shield className="h-5 w-5" />
|
||||
IP Protetti ({filteredWhitelist?.length || 0}{searchQuery && whitelist ? ` di ${whitelist.length}` : ''})
|
||||
IP Protetti ({whitelist?.length || 0})
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
@ -225,9 +201,9 @@ export default function WhitelistPage() {
|
||||
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
|
||||
Caricamento...
|
||||
</div>
|
||||
) : filteredWhitelist && filteredWhitelist.length > 0 ? (
|
||||
) : whitelist && whitelist.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{filteredWhitelist.map((item) => (
|
||||
{whitelist.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
className="p-4 rounded-lg border hover-elevate"
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
-- PostgreSQL database dump
|
||||
--
|
||||
|
||||
\restrict Jq3ohS02Qcz3l9bNbeQprTZolEFbFh84eEwk4en2HkAqc2Xojxrd4AFqHJvBETG
|
||||
\restrict bpBxNz70Ka0m0tyhrhacCuMK1bx7vbLdZerc7LHt1LG4ZFdy4h6aJ0zgCj4XJhK
|
||||
|
||||
-- Dumped from database version 16.11 (74c6bb6)
|
||||
-- Dumped by pg_dump version 16.10
|
||||
@ -387,5 +387,5 @@ ALTER TABLE ONLY public.public_blacklist_ips
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
\unrestrict Jq3ohS02Qcz3l9bNbeQprTZolEFbFh84eEwk4en2HkAqc2Xojxrd4AFqHJvBETG
|
||||
\unrestrict bpBxNz70Ka0m0tyhrhacCuMK1bx7vbLdZerc7LHt1LG4ZFdy4h6aJ0zgCj4XJhK
|
||||
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
-- Migration 009: Add Microsoft Azure and Meta/Facebook public lists
|
||||
-- Date: 2026-01-02
|
||||
|
||||
-- Microsoft Azure IP ranges (whitelist - cloud provider)
|
||||
INSERT INTO public_lists (name, url, type, format, enabled, description, fetch_interval)
|
||||
VALUES (
|
||||
'Microsoft Azure',
|
||||
'https://raw.githubusercontent.com/femueller/cloud-ip-ranges/master/microsoft-azure-ip-ranges.json',
|
||||
'whitelist',
|
||||
'json',
|
||||
true,
|
||||
'Microsoft Azure cloud IP ranges - auto-updated from Azure Service Tags',
|
||||
3600
|
||||
) ON CONFLICT (name) DO UPDATE SET
|
||||
url = EXCLUDED.url,
|
||||
description = EXCLUDED.description;
|
||||
|
||||
-- Meta/Facebook IP ranges (whitelist - major service provider)
|
||||
INSERT INTO public_lists (name, url, type, format, enabled, description, fetch_interval)
|
||||
VALUES (
|
||||
'Meta (Facebook)',
|
||||
'https://raw.githubusercontent.com/parseword/util-misc/master/block-facebook/facebook-ip-ranges.txt',
|
||||
'whitelist',
|
||||
'plain',
|
||||
true,
|
||||
'Meta/Facebook IP ranges (includes Instagram, WhatsApp, Oculus) from BGP AS32934/AS54115/AS63293',
|
||||
3600
|
||||
) ON CONFLICT (name) DO UPDATE SET
|
||||
url = EXCLUDED.url,
|
||||
description = EXCLUDED.description;
|
||||
|
||||
-- Verify insertion
|
||||
SELECT id, name, type, enabled, url FROM public_lists WHERE name IN ('Microsoft Azure', 'Meta (Facebook)');
|
||||
@ -176,70 +176,6 @@ class GCPParser(ListParser):
|
||||
return ips
|
||||
|
||||
|
||||
class AzureParser(ListParser):
|
||||
"""Parser for Microsoft Azure IP ranges JSON (Service Tags format)"""
|
||||
|
||||
@staticmethod
|
||||
def parse(content: str) -> Set[tuple[str, Optional[str]]]:
|
||||
"""
|
||||
Parse Azure Service Tags JSON format:
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"name": "ActionGroup",
|
||||
"properties": {
|
||||
"addressPrefixes": ["1.2.3.0/24", "5.6.7.0/24"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"""
|
||||
ips = set()
|
||||
|
||||
try:
|
||||
data = json.loads(content)
|
||||
|
||||
for value in data.get('values', []):
|
||||
properties = value.get('properties', {})
|
||||
prefixes = properties.get('addressPrefixes', [])
|
||||
|
||||
for prefix in prefixes:
|
||||
if prefix and ListParser.validate_ip(prefix):
|
||||
ips.add(ListParser.normalize_cidr(prefix))
|
||||
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
|
||||
return ips
|
||||
|
||||
|
||||
class MetaParser(ListParser):
|
||||
"""Parser for Meta/Facebook IP ranges (plain CIDR list from BGP)"""
|
||||
|
||||
@staticmethod
|
||||
def parse(content: str) -> Set[tuple[str, Optional[str]]]:
|
||||
"""
|
||||
Parse Meta format (plain CIDR list):
|
||||
31.13.24.0/21
|
||||
31.13.64.0/18
|
||||
157.240.0.0/17
|
||||
"""
|
||||
ips = set()
|
||||
lines = content.strip().split('\n')
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
|
||||
# Skip empty lines and comments
|
||||
if not line or line.startswith('#') or line.startswith('//'):
|
||||
continue
|
||||
|
||||
if ListParser.validate_ip(line):
|
||||
ips.add(ListParser.normalize_cidr(line))
|
||||
|
||||
return ips
|
||||
|
||||
|
||||
class CloudflareParser(ListParser):
|
||||
"""Parser for Cloudflare IP list"""
|
||||
|
||||
@ -327,11 +263,6 @@ PARSERS: Dict[str, type[ListParser]] = {
|
||||
'talos': TalosParser,
|
||||
'aws': AWSParser,
|
||||
'gcp': GCPParser,
|
||||
'google': GCPParser,
|
||||
'azure': AzureParser,
|
||||
'microsoft': AzureParser,
|
||||
'meta': MetaParser,
|
||||
'facebook': MetaParser,
|
||||
'cloudflare': CloudflareParser,
|
||||
'iana': IANAParser,
|
||||
'ntp': NTPPoolParser,
|
||||
|
||||
@ -169,27 +169,17 @@ class MergeLogic:
|
||||
INSERT INTO detections (
|
||||
source_ip,
|
||||
risk_score,
|
||||
confidence,
|
||||
anomaly_type,
|
||||
reason,
|
||||
log_count,
|
||||
first_seen,
|
||||
last_seen,
|
||||
detection_source,
|
||||
blacklist_id,
|
||||
detected_at,
|
||||
blocked
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
RETURNING id
|
||||
""", (
|
||||
ip_address,
|
||||
risk_score, # numeric, not string
|
||||
100.0, # confidence
|
||||
str(risk_score),
|
||||
'public_blacklist',
|
||||
'IP in public blacklist',
|
||||
1, # log_count
|
||||
datetime.utcnow(), # first_seen
|
||||
datetime.utcnow(), # last_seen
|
||||
'public_blacklist',
|
||||
blacklist_id,
|
||||
datetime.utcnow(),
|
||||
@ -223,7 +213,6 @@ class MergeLogic:
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
# Delete detections for IPs in whitelist ranges (CIDR-aware)
|
||||
# Cast both sides to inet explicitly for type safety
|
||||
cur.execute("""
|
||||
DELETE FROM detections d
|
||||
WHERE d.detection_source = 'public_blacklist'
|
||||
@ -232,8 +221,8 @@ class MergeLogic:
|
||||
WHERE wl.active = true
|
||||
AND wl.ip_inet IS NOT NULL
|
||||
AND (
|
||||
d.source_ip::inet = wl.ip_inet::inet
|
||||
OR d.source_ip::inet <<= wl.ip_inet::inet
|
||||
d.source_ip::inet = wl.ip_inet
|
||||
OR d.source_ip::inet <<= wl.ip_inet
|
||||
)
|
||||
)
|
||||
""")
|
||||
@ -276,12 +265,7 @@ class MergeLogic:
|
||||
INSERT INTO detections (
|
||||
source_ip,
|
||||
risk_score,
|
||||
confidence,
|
||||
anomaly_type,
|
||||
reason,
|
||||
log_count,
|
||||
first_seen,
|
||||
last_seen,
|
||||
detection_source,
|
||||
blacklist_id,
|
||||
detected_at,
|
||||
@ -289,13 +273,8 @@ class MergeLogic:
|
||||
)
|
||||
SELECT DISTINCT
|
||||
bl.ip_address,
|
||||
75::numeric,
|
||||
100::numeric,
|
||||
'75',
|
||||
'public_blacklist',
|
||||
'IP in public blacklist',
|
||||
1,
|
||||
NOW(),
|
||||
NOW(),
|
||||
'public_blacklist',
|
||||
bl.id,
|
||||
NOW(),
|
||||
@ -304,15 +283,14 @@ class MergeLogic:
|
||||
WHERE bl.is_active = true
|
||||
AND bl.ip_inet IS NOT NULL
|
||||
-- Priority 1: Exclude if in manual whitelist (highest priority)
|
||||
-- Cast to inet explicitly for type safety
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM whitelist wl
|
||||
WHERE wl.active = true
|
||||
AND wl.source = 'manual'
|
||||
AND wl.ip_inet IS NOT NULL
|
||||
AND (
|
||||
bl.ip_inet::inet = wl.ip_inet::inet
|
||||
OR bl.ip_inet::inet <<= wl.ip_inet::inet
|
||||
bl.ip_inet = wl.ip_inet
|
||||
OR bl.ip_inet <<= wl.ip_inet
|
||||
)
|
||||
)
|
||||
-- Priority 2: Exclude if in public whitelist
|
||||
@ -322,8 +300,8 @@ class MergeLogic:
|
||||
AND wl.source != 'manual'
|
||||
AND wl.ip_inet IS NOT NULL
|
||||
AND (
|
||||
bl.ip_inet::inet = wl.ip_inet::inet
|
||||
OR bl.ip_inet::inet <<= wl.ip_inet::inet
|
||||
bl.ip_inet = wl.ip_inet
|
||||
OR bl.ip_inet <<= wl.ip_inet
|
||||
)
|
||||
)
|
||||
-- Avoid duplicate detections
|
||||
|
||||
@ -25,7 +25,7 @@ The IDS employs a React-based frontend for real-time monitoring, detection visua
|
||||
**Key Architectural Decisions & Features:**
|
||||
- **Log Collection & Processing**: MikroTik syslog data (UDP:514) is parsed by `syslog_parser.py` and stored in PostgreSQL with a 3-day retention policy. The parser includes auto-reconnect and error recovery mechanisms.
|
||||
- **Machine Learning**: An Isolation Forest model (sklearn.IsolectionForest) trained on 25 network log features performs real-time anomaly detection, assigning a risk score (0-100 across five risk levels). A hybrid ML detector (Isolation Forest + Ensemble Classifier with weighted voting) reduces false positives. The system supports weekly automatic retraining of models.
|
||||
- **Automated Blocking**: Critical IPs (score >= 80) are automatically blocked in parallel across configured MikroTik routers via their REST API. **Auto-unblock on whitelist**: When an IP is added to the whitelist, it is automatically removed from all router blocklists. Manual unblock button available in Detections page.
|
||||
- **Automated Blocking**: Critical IPs (score >= 80) are automatically blocked in parallel across configured MikroTik routers via their REST API.
|
||||
- **Public Lists Integration (v2.0.0 - CIDR Complete)**: Automatic fetcher syncs blacklist/whitelist feeds every 10 minutes (Spamhaus, Talos, AWS, GCP, Cloudflare, IANA, NTP Pool). **Full CIDR support** using PostgreSQL INET/CIDR types with `<<=` containment operators for network range matching. Priority-based merge logic: Manual whitelist > Public whitelist > Blacklist (CIDR-aware). Detections created for blacklisted IPs/ranges (excluding whitelisted ranges). CRUD API + UI for list management. See `deployment/docs/PUBLIC_LISTS_V2_CIDR.md` for implementation details.
|
||||
- **Automatic Cleanup**: An hourly systemd timer (`cleanup_detections.py`) removes old detections (48h) and auto-unblocks IPs (2h).
|
||||
- **Service Monitoring & Management**: A dashboard provides real-time status (ML Backend, Database, Syslog Parser). API endpoints, secured with API key authentication and Systemd integration, allow for service management (start/stop/restart) of Python services.
|
||||
|
||||
@ -77,22 +77,18 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
// Detections
|
||||
app.get("/api/detections", async (req, res) => {
|
||||
try {
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 50;
|
||||
const offset = req.query.offset ? parseInt(req.query.offset as string) : 0;
|
||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 500;
|
||||
const anomalyType = req.query.anomalyType as string | undefined;
|
||||
const minScore = req.query.minScore ? parseFloat(req.query.minScore as string) : undefined;
|
||||
const maxScore = req.query.maxScore ? parseFloat(req.query.maxScore as string) : undefined;
|
||||
const search = req.query.search as string | undefined;
|
||||
|
||||
const result = await storage.getAllDetections({
|
||||
const detections = await storage.getAllDetections({
|
||||
limit,
|
||||
offset,
|
||||
anomalyType,
|
||||
minScore,
|
||||
maxScore,
|
||||
search
|
||||
maxScore
|
||||
});
|
||||
res.json(result);
|
||||
res.json(detections);
|
||||
} catch (error) {
|
||||
console.error('[DB ERROR] Failed to fetch detections:', error);
|
||||
res.status(500).json({ error: "Failed to fetch detections" });
|
||||
@ -134,74 +130,12 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
try {
|
||||
const validatedData = insertWhitelistSchema.parse(req.body);
|
||||
const item = await storage.createWhitelist(validatedData);
|
||||
|
||||
// Auto-unblock from routers when adding to whitelist
|
||||
const mlBackendUrl = process.env.ML_BACKEND_URL || 'http://localhost:8000';
|
||||
const mlApiKey = process.env.IDS_API_KEY;
|
||||
try {
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (mlApiKey) {
|
||||
headers['X-API-Key'] = mlApiKey;
|
||||
}
|
||||
const unblockResponse = await fetch(`${mlBackendUrl}/unblock-ip`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ ip_address: validatedData.ipAddress })
|
||||
});
|
||||
if (unblockResponse.ok) {
|
||||
const result = await unblockResponse.json();
|
||||
console.log(`[WHITELIST] Auto-unblocked ${validatedData.ipAddress} from ${result.unblocked_from} routers`);
|
||||
} else {
|
||||
console.warn(`[WHITELIST] Failed to auto-unblock ${validatedData.ipAddress}: ${unblockResponse.status}`);
|
||||
}
|
||||
} catch (unblockError) {
|
||||
// Don't fail if ML backend is unavailable
|
||||
console.warn(`[WHITELIST] ML backend unavailable for auto-unblock: ${unblockError}`);
|
||||
}
|
||||
|
||||
res.json(item);
|
||||
} catch (error) {
|
||||
res.status(400).json({ error: "Invalid whitelist data" });
|
||||
}
|
||||
});
|
||||
|
||||
// Unblock IP from all routers (proxy to ML backend)
|
||||
app.post("/api/unblock-ip", async (req, res) => {
|
||||
try {
|
||||
const { ipAddress, listName = "ddos_blocked" } = req.body;
|
||||
|
||||
if (!ipAddress) {
|
||||
return res.status(400).json({ error: "IP address is required" });
|
||||
}
|
||||
|
||||
const mlBackendUrl = process.env.ML_BACKEND_URL || 'http://localhost:8000';
|
||||
const mlApiKey = process.env.IDS_API_KEY;
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
||||
if (mlApiKey) {
|
||||
headers['X-API-Key'] = mlApiKey;
|
||||
}
|
||||
|
||||
const response = await fetch(`${mlBackendUrl}/unblock-ip`, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ ip_address: ipAddress, list_name: listName })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`[UNBLOCK] ML backend error for ${ipAddress}: ${response.status} - ${errorText}`);
|
||||
return res.status(response.status).json({ error: errorText || "Failed to unblock IP" });
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log(`[UNBLOCK] Successfully unblocked ${ipAddress} from ${result.unblocked_from || 0} routers`);
|
||||
res.json(result);
|
||||
} catch (error: any) {
|
||||
console.error('[UNBLOCK] Error:', error);
|
||||
res.status(500).json({ error: error.message || "Failed to unblock IP from routers" });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete("/api/whitelist/:id", async (req, res) => {
|
||||
try {
|
||||
const success = await storage.deleteWhitelist(req.params.id);
|
||||
@ -478,15 +412,14 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
app.get("/api/stats", async (req, res) => {
|
||||
try {
|
||||
const routers = await storage.getAllRouters();
|
||||
const detectionsResult = await storage.getAllDetections({ limit: 1000 });
|
||||
const detections = await storage.getAllDetections({ limit: 1000 });
|
||||
const recentLogs = await storage.getRecentLogs(1000);
|
||||
const whitelist = await storage.getAllWhitelist();
|
||||
const latestTraining = await storage.getLatestTraining();
|
||||
|
||||
const detectionsList = detectionsResult.detections;
|
||||
const blockedCount = detectionsList.filter(d => d.blocked).length;
|
||||
const criticalCount = detectionsList.filter(d => parseFloat(d.riskScore) >= 85).length;
|
||||
const highCount = detectionsList.filter(d => parseFloat(d.riskScore) >= 70 && parseFloat(d.riskScore) < 85).length;
|
||||
const blockedCount = detections.filter(d => d.blocked).length;
|
||||
const criticalCount = detections.filter(d => parseFloat(d.riskScore) >= 85).length;
|
||||
const highCount = detections.filter(d => parseFloat(d.riskScore) >= 70 && parseFloat(d.riskScore) < 85).length;
|
||||
|
||||
res.json({
|
||||
routers: {
|
||||
@ -494,7 +427,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
enabled: routers.filter(r => r.enabled).length
|
||||
},
|
||||
detections: {
|
||||
total: detectionsResult.total,
|
||||
total: detections.length,
|
||||
blocked: blockedCount,
|
||||
critical: criticalCount,
|
||||
high: highCount
|
||||
|
||||
@ -43,12 +43,10 @@ export interface IStorage {
|
||||
// Detections
|
||||
getAllDetections(options: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
anomalyType?: string;
|
||||
minScore?: number;
|
||||
maxScore?: number;
|
||||
search?: string;
|
||||
}): Promise<{ detections: Detection[]; total: number }>;
|
||||
}): Promise<Detection[]>;
|
||||
getDetectionByIp(sourceIp: string): Promise<Detection | undefined>;
|
||||
createDetection(detection: InsertDetection): Promise<Detection>;
|
||||
updateDetection(id: string, detection: Partial<InsertDetection>): Promise<Detection | undefined>;
|
||||
@ -176,13 +174,11 @@ export class DatabaseStorage implements IStorage {
|
||||
// Detections
|
||||
async getAllDetections(options: {
|
||||
limit?: number;
|
||||
offset?: number;
|
||||
anomalyType?: string;
|
||||
minScore?: number;
|
||||
maxScore?: number;
|
||||
search?: string;
|
||||
}): Promise<{ detections: Detection[]; total: number }> {
|
||||
const { limit = 50, offset = 0, anomalyType, minScore, maxScore, search } = options;
|
||||
}): Promise<Detection[]> {
|
||||
const { limit = 5000, anomalyType, minScore, maxScore } = options;
|
||||
|
||||
// Build WHERE conditions
|
||||
const conditions = [];
|
||||
@ -200,36 +196,17 @@ export class DatabaseStorage implements IStorage {
|
||||
conditions.push(sql`${detections.riskScore}::numeric <= ${maxScore}`);
|
||||
}
|
||||
|
||||
// Search by IP or anomaly type (case-insensitive)
|
||||
if (search && search.trim()) {
|
||||
const searchLower = search.trim().toLowerCase();
|
||||
conditions.push(sql`(
|
||||
LOWER(${detections.sourceIp}) LIKE ${'%' + searchLower + '%'} OR
|
||||
LOWER(${detections.anomalyType}) LIKE ${'%' + searchLower + '%'} OR
|
||||
LOWER(COALESCE(${detections.country}, '')) LIKE ${'%' + searchLower + '%'} OR
|
||||
LOWER(COALESCE(${detections.organization}, '')) LIKE ${'%' + searchLower + '%'}
|
||||
)`);
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? and(...conditions) : undefined;
|
||||
|
||||
// Get total count for pagination
|
||||
const countResult = await db
|
||||
.select({ count: sql<number>`count(*)::int` })
|
||||
.from(detections)
|
||||
.where(whereClause);
|
||||
const total = countResult[0]?.count || 0;
|
||||
|
||||
// Get paginated results
|
||||
const results = await db
|
||||
const query = db
|
||||
.select()
|
||||
.from(detections)
|
||||
.where(whereClause)
|
||||
.orderBy(desc(detections.detectedAt))
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
.limit(limit);
|
||||
|
||||
return { detections: results, total };
|
||||
if (conditions.length > 0) {
|
||||
return await query.where(and(...conditions));
|
||||
}
|
||||
|
||||
return await query;
|
||||
}
|
||||
|
||||
async getDetectionByIp(sourceIp: string): Promise<Detection | undefined> {
|
||||
|
||||
76
version.json
76
version.json
@ -1,43 +1,7 @@
|
||||
{
|
||||
"version": "1.0.103",
|
||||
"lastUpdate": "2026-01-02T16:33:13.545Z",
|
||||
"version": "1.0.97",
|
||||
"lastUpdate": "2026-01-02T14:50:15.450Z",
|
||||
"changelog": [
|
||||
{
|
||||
"version": "1.0.103",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.103"
|
||||
},
|
||||
{
|
||||
"version": "1.0.102",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.102"
|
||||
},
|
||||
{
|
||||
"version": "1.0.101",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.101"
|
||||
},
|
||||
{
|
||||
"version": "1.0.100",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.100"
|
||||
},
|
||||
{
|
||||
"version": "1.0.99",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.99"
|
||||
},
|
||||
{
|
||||
"version": "1.0.98",
|
||||
"date": "2026-01-02",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.98"
|
||||
},
|
||||
{
|
||||
"version": "1.0.97",
|
||||
"date": "2026-01-02",
|
||||
@ -301,6 +265,42 @@
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.54"
|
||||
},
|
||||
{
|
||||
"version": "1.0.53",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.53"
|
||||
},
|
||||
{
|
||||
"version": "1.0.52",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.52"
|
||||
},
|
||||
{
|
||||
"version": "1.0.51",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.51"
|
||||
},
|
||||
{
|
||||
"version": "1.0.50",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.50"
|
||||
},
|
||||
{
|
||||
"version": "1.0.49",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.49"
|
||||
},
|
||||
{
|
||||
"version": "1.0.48",
|
||||
"date": "2025-11-24",
|
||||
"type": "patch",
|
||||
"description": "Deployment automatico v1.0.48"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user