diff --git a/client/src/pages/Detections.tsx b/client/src/pages/Detections.tsx
index a6ab5a4..2c1efae 100644
--- a/client/src/pages/Detections.tsx
+++ b/client/src/pages/Detections.tsx
@@ -5,7 +5,7 @@ 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 } from "lucide-react";
+import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus, ShieldCheck, Unlock } from "lucide-react";
import { format } from "date-fns";
import { useState } from "react";
import type { Detection, Whitelist } from "@shared/schema";
@@ -63,7 +63,7 @@ export default function Detections() {
onSuccess: (_, detection) => {
toast({
title: "IP aggiunto alla whitelist",
- description: `${detection.sourceIp} è stato aggiunto alla whitelist con successo.`,
+ description: `${detection.sourceIp} è stato aggiunto alla whitelist e sbloccato dai router.`,
});
queryClient.invalidateQueries({ queryKey: ["/api/whitelist"] });
queryClient.invalidateQueries({ queryKey: ["/api/detections"] });
@@ -77,6 +77,29 @@ 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 CRITICO;
@@ -310,6 +333,20 @@ export default function Detections() {
Whitelist
)}
+
+ {detection.blocked && (
+
+ )}
diff --git a/replit.md b/replit.md
index f9c1fa7..ee9b2b8 100644
--- a/replit.md
+++ b/replit.md
@@ -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.
+- **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.
- **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.
diff --git a/server/routes.ts b/server/routes.ts
index 3c8ce6e..a41ec82 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -133,19 +133,26 @@ export async function registerRoutes(app: Express): Promise {
// 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 = { 'Content-Type': 'application/json' };
+ if (mlApiKey) {
+ headers['X-API-Key'] = mlApiKey;
+ }
const unblockResponse = await fetch(`${mlBackendUrl}/unblock-ip`, {
method: 'POST',
- headers: { 'Content-Type': 'application/json' },
+ 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.log(`[WHITELIST] ML backend unavailable for auto-unblock: ${unblockError}`);
+ console.warn(`[WHITELIST] ML backend unavailable for auto-unblock: ${unblockError}`);
}
res.json(item);
@@ -164,18 +171,26 @@ export async function registerRoutes(app: Express): Promise {
}
const mlBackendUrl = process.env.ML_BACKEND_URL || 'http://localhost:8000';
+ const mlApiKey = process.env.IDS_API_KEY;
+ const headers: Record = { 'Content-Type': 'application/json' };
+ if (mlApiKey) {
+ headers['X-API-Key'] = mlApiKey;
+ }
+
const response = await fetch(`${mlBackendUrl}/unblock-ip`, {
method: 'POST',
- headers: { 'Content-Type': 'application/json' },
+ headers,
body: JSON.stringify({ ip_address: ipAddress, list_name: listName })
});
if (!response.ok) {
- const error = await response.text();
- return res.status(response.status).json({ error: error || "Failed to unblock IP" });
+ 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);