Compare commits
3 Commits
a858958481
...
40f8f05e87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
40f8f05e87 | ||
|
|
3faddb3f5f | ||
|
|
20bdf72f81 |
@ -2,7 +2,7 @@
|
|||||||
-- PostgreSQL database dump
|
-- PostgreSQL database dump
|
||||||
--
|
--
|
||||||
|
|
||||||
\restrict em7gYgifRwixbLZU8Owl6B7Kqa069IOhR6jKrqfBAnZ0Vp3qeadEaTcm0QvNCf8
|
\restrict aCgEgb8lET7e1jInisWq8PxX1FjLAEzvVLqvthf8YBIYG3k2DGoUIepNBg4w74z
|
||||||
|
|
||||||
-- Dumped from database version 16.11 (df20cf9)
|
-- Dumped from database version 16.11 (df20cf9)
|
||||||
-- Dumped by pg_dump version 16.10
|
-- Dumped by pg_dump version 16.10
|
||||||
@ -387,5 +387,5 @@ ALTER TABLE ONLY public.public_blacklist_ips
|
|||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
\unrestrict em7gYgifRwixbLZU8Owl6B7Kqa069IOhR6jKrqfBAnZ0Vp3qeadEaTcm0QvNCf8
|
\unrestrict aCgEgb8lET7e1jInisWq8PxX1FjLAEzvVLqvthf8YBIYG3k2DGoUIepNBg4w74z
|
||||||
|
|
||||||
|
|||||||
317
server/mikrotik.ts
Normal file
317
server/mikrotik.ts
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
interface RouterConfig {
|
||||||
|
id: string;
|
||||||
|
ipAddress: string;
|
||||||
|
apiPort: number;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlockResult {
|
||||||
|
routerIp: string;
|
||||||
|
routerName?: string;
|
||||||
|
success: boolean;
|
||||||
|
alreadyExists?: boolean;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mikrotikRequest(
|
||||||
|
router: RouterConfig,
|
||||||
|
method: string,
|
||||||
|
path: string,
|
||||||
|
body?: any,
|
||||||
|
timeoutMs: number = 10000
|
||||||
|
): Promise<{ status: number; data: any }> {
|
||||||
|
const useHttps = router.apiPort === 443;
|
||||||
|
const protocol = useHttps ? "https" : "http";
|
||||||
|
const url = `${protocol}://${router.ipAddress}:${router.apiPort}${path}`;
|
||||||
|
const auth = Buffer.from(`${router.username}:${router.password}`).toString("base64");
|
||||||
|
|
||||||
|
const origTlsReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||||
|
if (useHttps) {
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fetchOptions: RequestInit = {
|
||||||
|
method,
|
||||||
|
headers: {
|
||||||
|
"Authorization": `Basic ${auth}`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
fetchOptions.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(url, fetchOptions);
|
||||||
|
clearTimeout(timeout);
|
||||||
|
|
||||||
|
let data: any;
|
||||||
|
const text = await response.text();
|
||||||
|
try {
|
||||||
|
data = JSON.parse(text);
|
||||||
|
} catch {
|
||||||
|
data = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { status: response.status, data };
|
||||||
|
} catch (error: any) {
|
||||||
|
clearTimeout(timeout);
|
||||||
|
if (useHttps && origTlsReject !== undefined) {
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
|
||||||
|
} else if (useHttps) {
|
||||||
|
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
if (useHttps) {
|
||||||
|
if (origTlsReject !== undefined) {
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = origTlsReject;
|
||||||
|
} else {
|
||||||
|
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function testRouterConnection(router: RouterConfig): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const { status } = await mikrotikRequest(router, "GET", "/rest/system/identity");
|
||||||
|
return status === 200;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getExistingBlockedIps(
|
||||||
|
router: RouterConfig,
|
||||||
|
listName: string = "ddos_blocked"
|
||||||
|
): Promise<Set<string>> {
|
||||||
|
try {
|
||||||
|
const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list", undefined, 20000);
|
||||||
|
if (status === 200 && Array.isArray(data)) {
|
||||||
|
const ips = new Set<string>();
|
||||||
|
for (const entry of data) {
|
||||||
|
if (entry.list === listName) {
|
||||||
|
ips.add(entry.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
return new Set();
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(`[MIKROTIK] Failed to get address-list from ${router.ipAddress}: ${e.message}`);
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addToAddressList(
|
||||||
|
router: RouterConfig,
|
||||||
|
ipAddress: string,
|
||||||
|
listName: string = "ddos_blocked",
|
||||||
|
comment: string = "",
|
||||||
|
timeoutDuration: string = "1h"
|
||||||
|
): Promise<BlockResult> {
|
||||||
|
try {
|
||||||
|
const { status, data } = await mikrotikRequest(router, "POST", "/rest/ip/firewall/address-list/add", {
|
||||||
|
list: listName,
|
||||||
|
address: ipAddress,
|
||||||
|
comment: comment || `IDS block ${new Date().toISOString()}`,
|
||||||
|
timeout: timeoutDuration,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 200 || status === 201) {
|
||||||
|
return { routerIp: router.ipAddress, success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === 400 || status === 409) {
|
||||||
|
const text = typeof data === "string" ? data.toLowerCase() : JSON.stringify(data).toLowerCase();
|
||||||
|
if (text.includes("already") || text.includes("exists") || text.includes("duplicate") || text.includes("failure: already")) {
|
||||||
|
return { routerIp: router.ipAddress, success: true, alreadyExists: true };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const verifyResult = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
|
||||||
|
if (verifyResult.status === 200 && Array.isArray(verifyResult.data)) {
|
||||||
|
for (const entry of verifyResult.data) {
|
||||||
|
if (entry.address === ipAddress && entry.list === listName) {
|
||||||
|
return { routerIp: router.ipAddress, success: true, alreadyExists: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
return {
|
||||||
|
routerIp: router.ipAddress,
|
||||||
|
success: false,
|
||||||
|
error: `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
routerIp: router.ipAddress,
|
||||||
|
success: false,
|
||||||
|
error: `HTTP ${status}: ${typeof data === "string" ? data : JSON.stringify(data)}`,
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
routerIp: router.ipAddress,
|
||||||
|
success: false,
|
||||||
|
error: error.message || "Connection failed",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeFromAddressList(
|
||||||
|
router: RouterConfig,
|
||||||
|
ipAddress: string,
|
||||||
|
listName: string = "ddos_blocked"
|
||||||
|
): Promise<BlockResult> {
|
||||||
|
try {
|
||||||
|
const { status, data } = await mikrotikRequest(router, "GET", "/rest/ip/firewall/address-list");
|
||||||
|
if (status !== 200 || !Array.isArray(data)) {
|
||||||
|
return { routerIp: router.ipAddress, success: false, error: "Failed to read address list" };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of data) {
|
||||||
|
if (entry.address === ipAddress && entry.list === listName) {
|
||||||
|
const entryId = entry[".id"];
|
||||||
|
const delResult = await mikrotikRequest(router, "DELETE", `/rest/ip/firewall/address-list/${entryId}`);
|
||||||
|
if (delResult.status === 200 || delResult.status === 204) {
|
||||||
|
return { routerIp: router.ipAddress, success: true };
|
||||||
|
}
|
||||||
|
return { routerIp: router.ipAddress, success: false, error: `Delete failed: ${delResult.status}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { routerIp: router.ipAddress, success: true };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { routerIp: router.ipAddress, success: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function blockIpOnAllRouters(
|
||||||
|
routers: RouterConfig[],
|
||||||
|
ipAddress: string,
|
||||||
|
listName: string = "ddos_blocked",
|
||||||
|
comment: string = "",
|
||||||
|
timeoutDuration: string = "1h"
|
||||||
|
): Promise<BlockResult[]> {
|
||||||
|
const enabled = routers.filter((r) => r.enabled);
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
enabled.map((r) => addToAddressList(r, ipAddress, listName, comment, timeoutDuration))
|
||||||
|
);
|
||||||
|
|
||||||
|
return results.map((r, i) =>
|
||||||
|
r.status === "fulfilled" ? r.value : { routerIp: enabled[i].ipAddress, success: false, error: String(r.reason) }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unblockIpOnAllRouters(
|
||||||
|
routers: RouterConfig[],
|
||||||
|
ipAddress: string,
|
||||||
|
listName: string = "ddos_blocked"
|
||||||
|
): Promise<BlockResult[]> {
|
||||||
|
const enabled = routers.filter((r) => r.enabled);
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
enabled.map((r) => removeFromAddressList(r, ipAddress, listName))
|
||||||
|
);
|
||||||
|
|
||||||
|
return results.map((r, i) =>
|
||||||
|
r.status === "fulfilled" ? r.value : { routerIp: enabled[i].ipAddress, success: false, error: String(r.reason) }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function bulkBlockIps(
|
||||||
|
routers: RouterConfig[],
|
||||||
|
ipList: string[],
|
||||||
|
listName: string = "ddos_blocked",
|
||||||
|
commentPrefix: string = "IDS bulk-block",
|
||||||
|
timeoutDuration: string = "1h",
|
||||||
|
concurrency: number = 10
|
||||||
|
): Promise<{ blocked: number; failed: number; skipped: number; details: Array<{ ip: string; status: string }> }> {
|
||||||
|
const enabled = routers.filter((r) => r.enabled);
|
||||||
|
if (enabled.length === 0) {
|
||||||
|
return { blocked: 0, failed: 0, skipped: 0, details: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[BULK-BLOCK] Starting: ${ipList.length} IPs on ${enabled.length} routers`);
|
||||||
|
|
||||||
|
const existingCache = new Map<string, Set<string>>();
|
||||||
|
await Promise.allSettled(
|
||||||
|
enabled.map(async (router) => {
|
||||||
|
const existing = await getExistingBlockedIps(router, listName);
|
||||||
|
existingCache.set(router.ipAddress, existing);
|
||||||
|
console.log(`[BULK-BLOCK] Router ${router.ipAddress}: ${existing.size} IPs already in list`);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const newIps: string[] = [];
|
||||||
|
const skippedIps: string[] = [];
|
||||||
|
|
||||||
|
for (const ip of ipList) {
|
||||||
|
let alreadyOnAll = true;
|
||||||
|
for (const router of enabled) {
|
||||||
|
const existing = existingCache.get(router.ipAddress) || new Set();
|
||||||
|
if (!existing.has(ip)) {
|
||||||
|
alreadyOnAll = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (alreadyOnAll) {
|
||||||
|
skippedIps.push(ip);
|
||||||
|
} else {
|
||||||
|
newIps.push(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[BULK-BLOCK] ${skippedIps.length} already blocked, ${newIps.length} new to block`);
|
||||||
|
|
||||||
|
let blocked = 0;
|
||||||
|
let failed = 0;
|
||||||
|
const details: Array<{ ip: string; status: string }> = [];
|
||||||
|
|
||||||
|
async function processIp(ip: string) {
|
||||||
|
const routerResults = await Promise.allSettled(
|
||||||
|
enabled.map(async (router) => {
|
||||||
|
const existing = existingCache.get(router.ipAddress) || new Set();
|
||||||
|
if (existing.has(ip)) return true;
|
||||||
|
const result = await addToAddressList(router, ip, listName, `${commentPrefix} ${ip}`, timeoutDuration);
|
||||||
|
return result.success;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const anySuccess = routerResults.some(
|
||||||
|
(r) => r.status === "fulfilled" && r.value === true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (anySuccess) {
|
||||||
|
blocked++;
|
||||||
|
details.push({ ip, status: "blocked" });
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
details.push({ ip, status: "failed" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < newIps.length; i += concurrency) {
|
||||||
|
const batch = newIps.slice(i, i + concurrency);
|
||||||
|
await Promise.allSettled(batch.map((ip) => processIp(ip)));
|
||||||
|
|
||||||
|
if ((i + concurrency) % 50 === 0 || i + concurrency >= newIps.length) {
|
||||||
|
console.log(`[BULK-BLOCK] Progress: ${Math.min(i + concurrency, newIps.length)}/${newIps.length}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const ip of skippedIps) {
|
||||||
|
details.push({ ip, status: "already_blocked" });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[BULK-BLOCK] Done: ${blocked} blocked, ${failed} failed, ${skippedIps.length} skipped`);
|
||||||
|
|
||||||
|
return { blocked, failed, skipped: skippedIps.length, details };
|
||||||
|
}
|
||||||
151
server/routes.ts
151
server/routes.ts
@ -4,6 +4,7 @@ import { storage } from "./storage";
|
|||||||
import { insertRouterSchema, insertDetectionSchema, insertWhitelistSchema, insertPublicListSchema, networkAnalytics, routers, detections, networkLogs } from "@shared/schema";
|
import { insertRouterSchema, insertDetectionSchema, insertWhitelistSchema, insertPublicListSchema, networkAnalytics, routers, detections, networkLogs } from "@shared/schema";
|
||||||
import { db } from "./db";
|
import { db } from "./db";
|
||||||
import { desc, eq, gte, sql } from "drizzle-orm";
|
import { desc, eq, gte, sql } from "drizzle-orm";
|
||||||
|
import { blockIpOnAllRouters, unblockIpOnAllRouters, bulkBlockIps, testRouterConnection } from "./mikrotik";
|
||||||
|
|
||||||
export async function registerRoutes(app: Express): Promise<Server> {
|
export async function registerRoutes(app: Express): Promise<Server> {
|
||||||
// Routers
|
// Routers
|
||||||
@ -139,28 +140,16 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
const validatedData = insertWhitelistSchema.parse(req.body);
|
const validatedData = insertWhitelistSchema.parse(req.body);
|
||||||
const item = await storage.createWhitelist(validatedData);
|
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 {
|
try {
|
||||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
const allRouters = await storage.getAllRouters();
|
||||||
if (mlApiKey) {
|
const enabledRouters = allRouters.filter(r => r.enabled);
|
||||||
headers['X-API-Key'] = mlApiKey;
|
if (enabledRouters.length > 0) {
|
||||||
}
|
const results = await unblockIpOnAllRouters(enabledRouters as any, validatedData.ipAddress);
|
||||||
const unblockResponse = await fetch(`${mlBackendUrl}/unblock-ip`, {
|
const unblocked = results.filter(r => r.success).length;
|
||||||
method: 'POST',
|
console.log(`[WHITELIST] Auto-unblocked ${validatedData.ipAddress} from ${unblocked}/${enabledRouters.length} routers`);
|
||||||
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) {
|
} catch (unblockError) {
|
||||||
// Don't fail if ML backend is unavailable
|
console.warn(`[WHITELIST] Auto-unblock failed for ${validatedData.ipAddress}:`, unblockError);
|
||||||
console.warn(`[WHITELIST] ML backend unavailable for auto-unblock: ${unblockError}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(item);
|
res.json(item);
|
||||||
@ -169,7 +158,6 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Unblock IP from all routers (proxy to ML backend)
|
|
||||||
app.post("/api/unblock-ip", async (req, res) => {
|
app.post("/api/unblock-ip", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { ipAddress, listName = "ddos_blocked" } = req.body;
|
const { ipAddress, listName = "ddos_blocked" } = req.body;
|
||||||
@ -178,31 +166,31 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
return res.status(400).json({ error: "IP address is required" });
|
return res.status(400).json({ error: "IP address is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const mlBackendUrl = process.env.ML_BACKEND_URL || 'http://localhost:8000';
|
const allRouters = await storage.getAllRouters();
|
||||||
const mlApiKey = process.env.IDS_API_KEY;
|
const enabledRouters = allRouters.filter(r => r.enabled);
|
||||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' };
|
|
||||||
if (mlApiKey) {
|
if (enabledRouters.length === 0) {
|
||||||
headers['X-API-Key'] = mlApiKey;
|
return res.status(400).json({ error: "Nessun router abilitato" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${mlBackendUrl}/unblock-ip`, {
|
const results = await unblockIpOnAllRouters(enabledRouters as any, ipAddress, listName);
|
||||||
method: 'POST',
|
const successCount = results.filter(r => r.success).length;
|
||||||
headers,
|
|
||||||
body: JSON.stringify({ ip_address: ipAddress, list_name: listName })
|
await db.update(detections)
|
||||||
|
.set({ blocked: false })
|
||||||
|
.where(eq(detections.sourceIp, ipAddress));
|
||||||
|
|
||||||
|
console.log(`[UNBLOCK] ${ipAddress} rimosso da ${successCount}/${enabledRouters.length} router`);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: `IP ${ipAddress} sbloccato da ${successCount} router`,
|
||||||
|
unblocked_from: successCount,
|
||||||
|
total_routers: enabledRouters.length,
|
||||||
|
results: results.map(r => ({ router: r.routerIp, success: r.success, error: r.error }))
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
} catch (error: any) {
|
||||||
console.error('[UNBLOCK] Error:', error);
|
console.error('[UNBLOCK] Error:', error);
|
||||||
res.status(500).json({ error: error.message || "Failed to unblock IP from routers" });
|
res.status(500).json({ error: error.message || "Errore sblocco IP" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -639,36 +627,75 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
|
|
||||||
app.post("/api/ml/block-all-critical", async (req, res) => {
|
app.post("/api/ml/block-all-critical", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { min_score = 80 } = req.body;
|
const { min_score = 80, list_name = "ddos_blocked" } = req.body;
|
||||||
|
|
||||||
const controller = new AbortController();
|
const allRouters = await storage.getAllRouters();
|
||||||
const timeout = setTimeout(() => controller.abort(), 300000); // 5 min timeout
|
const enabledRouters = allRouters.filter(r => r.enabled);
|
||||||
|
|
||||||
const response = await fetch(`${ML_BACKEND_URL}/block-all-critical`, {
|
if (enabledRouters.length === 0) {
|
||||||
method: "POST",
|
return res.status(400).json({ error: "Nessun router abilitato" });
|
||||||
headers: getMLBackendHeaders(),
|
}
|
||||||
body: JSON.stringify({ min_score }),
|
|
||||||
signal: controller.signal,
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(timeout);
|
const unblockedDetections = await db.execute(
|
||||||
|
sql`SELECT DISTINCT source_ip, MAX(CAST(risk_score AS FLOAT)) as max_score, MAX(anomaly_type) as anomaly_type
|
||||||
|
FROM detections
|
||||||
|
WHERE CAST(risk_score AS FLOAT) >= ${min_score}
|
||||||
|
AND blocked = false
|
||||||
|
AND source_ip NOT IN (SELECT ip_address FROM whitelist WHERE active = true)
|
||||||
|
GROUP BY source_ip
|
||||||
|
ORDER BY max_score DESC`
|
||||||
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
const rows = (unblockedDetections as any).rows || unblockedDetections;
|
||||||
const errorData = await response.json().catch(() => ({}));
|
|
||||||
return res.status(response.status).json({
|
if (!rows || rows.length === 0) {
|
||||||
error: errorData.detail || "Block all critical failed",
|
return res.json({
|
||||||
|
message: "Nessun IP critico da bloccare",
|
||||||
|
blocked: 0,
|
||||||
|
failed: 0,
|
||||||
|
total_critical: 0,
|
||||||
|
skipped: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const ipList = rows.map((r: any) => r.source_ip);
|
||||||
res.json(data);
|
console.log(`[BLOCK-ALL] Avvio blocco massivo: ${ipList.length} IP con score >= ${min_score} su ${enabledRouters.length} router`);
|
||||||
|
|
||||||
|
const result = await bulkBlockIps(
|
||||||
|
enabledRouters as any,
|
||||||
|
ipList,
|
||||||
|
list_name,
|
||||||
|
`IDS bulk-block (score>=${min_score})`,
|
||||||
|
"1h",
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.blocked > 0) {
|
||||||
|
const blockedIps = result.details
|
||||||
|
.filter(d => d.status === "blocked")
|
||||||
|
.map(d => d.ip);
|
||||||
|
|
||||||
|
const batchSize = 200;
|
||||||
|
for (let i = 0; i < blockedIps.length; i += batchSize) {
|
||||||
|
const batch = blockedIps.slice(i, i + batchSize);
|
||||||
|
const ipValues = batch.map(ip => `'${ip.replace(/'/g, "''")}'`).join(',');
|
||||||
|
await db.execute(
|
||||||
|
sql`UPDATE detections SET blocked = true, blocked_at = NOW() WHERE source_ip IN (${sql.raw(ipValues)}) AND blocked = false`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
console.log(`[BLOCK-ALL] Database aggiornato: ${blockedIps.length} IP marcati come bloccati`);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
message: `Blocco massivo completato: ${result.blocked} IP bloccati, ${result.failed} falliti, ${result.skipped} già bloccati`,
|
||||||
|
blocked: result.blocked,
|
||||||
|
failed: result.failed,
|
||||||
|
skipped: result.skipped,
|
||||||
|
total_critical: ipList.length,
|
||||||
|
details: result.details.slice(0, 100)
|
||||||
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (error.name === 'AbortError') {
|
console.error('[BLOCK-ALL] Error:', error);
|
||||||
return res.status(504).json({ error: "Timeout - operazione troppo lunga" });
|
|
||||||
}
|
|
||||||
if (error.code === 'ECONNREFUSED') {
|
|
||||||
return res.status(503).json({ error: "ML backend non disponibile" });
|
|
||||||
}
|
|
||||||
res.status(500).json({ error: error.message || "Errore blocco massivo" });
|
res.status(500).json({ error: error.message || "Errore blocco massivo" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
16
version.json
16
version.json
@ -1,7 +1,13 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.111",
|
"version": "1.0.112",
|
||||||
"lastUpdate": "2026-02-16T10:51:45.634Z",
|
"lastUpdate": "2026-02-16T11:06:31.030Z",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
|
{
|
||||||
|
"version": "1.0.112",
|
||||||
|
"date": "2026-02-16",
|
||||||
|
"type": "patch",
|
||||||
|
"description": "Deployment automatico v1.0.112"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "1.0.111",
|
"version": "1.0.111",
|
||||||
"date": "2026-02-16",
|
"date": "2026-02-16",
|
||||||
@ -295,12 +301,6 @@
|
|||||||
"date": "2025-11-24",
|
"date": "2025-11-24",
|
||||||
"type": "patch",
|
"type": "patch",
|
||||||
"description": "Deployment automatico v1.0.63"
|
"description": "Deployment automatico v1.0.63"
|
||||||
},
|
|
||||||
{
|
|
||||||
"version": "1.0.62",
|
|
||||||
"date": "2025-11-24",
|
|
||||||
"type": "patch",
|
|
||||||
"description": "Deployment automatico v1.0.62"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user