Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
182d98de0d | ||
|
|
a1be759431 | ||
|
|
f404952e0e | ||
|
|
0a269a9032 | ||
|
|
1133ca356f | ||
|
|
aa74340706 |
@ -0,0 +1,51 @@
|
|||||||
|
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.
|
||||||
@ -5,40 +5,74 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Slider } from "@/components/ui/slider";
|
import { Slider } from "@/components/ui/slider";
|
||||||
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus, ShieldCheck, Unlock } from "lucide-react";
|
import { AlertTriangle, Search, Shield, Globe, MapPin, Building2, ShieldPlus, ShieldCheck, Unlock, ChevronLeft, ChevronRight } from "lucide-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { useState } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import type { Detection, Whitelist } from "@shared/schema";
|
import type { Detection, Whitelist } from "@shared/schema";
|
||||||
import { getFlag } from "@/lib/country-flags";
|
import { getFlag } from "@/lib/country-flags";
|
||||||
import { apiRequest, queryClient } from "@/lib/queryClient";
|
import { apiRequest, queryClient } from "@/lib/queryClient";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
|
||||||
|
const ITEMS_PER_PAGE = 50;
|
||||||
|
|
||||||
|
interface DetectionsResponse {
|
||||||
|
detections: Detection[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Detections() {
|
export default function Detections() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchInput, setSearchInput] = useState("");
|
||||||
|
const [debouncedSearch, setDebouncedSearch] = useState("");
|
||||||
const [anomalyTypeFilter, setAnomalyTypeFilter] = useState<string>("all");
|
const [anomalyTypeFilter, setAnomalyTypeFilter] = useState<string>("all");
|
||||||
const [minScore, setMinScore] = useState(0);
|
const [minScore, setMinScore] = useState(0);
|
||||||
const [maxScore, setMaxScore] = useState(100);
|
const [maxScore, setMaxScore] = useState(100);
|
||||||
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// Build query params
|
// Debounce search input
|
||||||
const queryParams = new URLSearchParams();
|
useEffect(() => {
|
||||||
queryParams.set("limit", "5000");
|
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());
|
||||||
if (anomalyTypeFilter !== "all") {
|
if (anomalyTypeFilter !== "all") {
|
||||||
queryParams.set("anomalyType", anomalyTypeFilter);
|
params.set("anomalyType", anomalyTypeFilter);
|
||||||
}
|
}
|
||||||
if (minScore > 0) {
|
if (minScore > 0) {
|
||||||
queryParams.set("minScore", minScore.toString());
|
params.set("minScore", minScore.toString());
|
||||||
}
|
}
|
||||||
if (maxScore < 100) {
|
if (maxScore < 100) {
|
||||||
queryParams.set("maxScore", maxScore.toString());
|
params.set("maxScore", maxScore.toString());
|
||||||
}
|
}
|
||||||
|
if (debouncedSearch.trim()) {
|
||||||
|
params.set("search", debouncedSearch.trim());
|
||||||
|
}
|
||||||
|
return params.toString();
|
||||||
|
}, [currentPage, anomalyTypeFilter, minScore, maxScore, debouncedSearch]);
|
||||||
|
|
||||||
const { data: detections, isLoading } = useQuery<Detection[]>({
|
const { data, isLoading } = useQuery<DetectionsResponse>({
|
||||||
queryKey: ["/api/detections", anomalyTypeFilter, minScore, maxScore],
|
queryKey: ["/api/detections", currentPage, anomalyTypeFilter, minScore, maxScore, debouncedSearch],
|
||||||
queryFn: () => fetch(`/api/detections?${queryParams.toString()}`).then(r => r.json()),
|
queryFn: () => fetch(`/api/detections?${queryParams}`).then(r => r.json()),
|
||||||
refetchInterval: 5000,
|
refetchInterval: 10000,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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
|
// Fetch whitelist to check if IP is already whitelisted
|
||||||
const { data: whitelistData } = useQuery<Whitelist[]>({
|
const { data: whitelistData } = useQuery<Whitelist[]>({
|
||||||
queryKey: ["/api/whitelist"],
|
queryKey: ["/api/whitelist"],
|
||||||
@ -47,11 +81,6 @@ export default function Detections() {
|
|||||||
// Create a Set of whitelisted IPs for fast lookup
|
// Create a Set of whitelisted IPs for fast lookup
|
||||||
const whitelistedIps = new Set(whitelistData?.map(w => w.ipAddress) || []);
|
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
|
// Mutation per aggiungere a whitelist
|
||||||
const addToWhitelistMutation = useMutation({
|
const addToWhitelistMutation = useMutation({
|
||||||
mutationFn: async (detection: Detection) => {
|
mutationFn: async (detection: Detection) => {
|
||||||
@ -137,9 +166,9 @@ export default function Detections() {
|
|||||||
<div className="relative flex-1 min-w-[200px]">
|
<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" />
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="Cerca per IP o tipo anomalia..."
|
placeholder="Cerca per IP, paese, organizzazione..."
|
||||||
value={searchQuery}
|
value={searchInput}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchInput(e.target.value)}
|
||||||
className="pl-9"
|
className="pl-9"
|
||||||
data-testid="input-search"
|
data-testid="input-search"
|
||||||
/>
|
/>
|
||||||
@ -191,9 +220,36 @@ export default function Detections() {
|
|||||||
{/* Detections List */}
|
{/* Detections List */}
|
||||||
<Card data-testid="card-detections-list">
|
<Card data-testid="card-detections-list">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center justify-between gap-2 flex-wrap">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<AlertTriangle className="h-5 w-5" />
|
<AlertTriangle className="h-5 w-5" />
|
||||||
Rilevamenti ({filteredDetections?.length || 0})
|
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>
|
||||||
|
)}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@ -201,9 +257,9 @@ export default function Detections() {
|
|||||||
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
|
<div className="text-center py-8 text-muted-foreground" data-testid="text-loading">
|
||||||
Caricamento...
|
Caricamento...
|
||||||
</div>
|
</div>
|
||||||
) : filteredDetections && filteredDetections.length > 0 ? (
|
) : detections.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
{filteredDetections.map((detection) => (
|
{detections.map((detection) => (
|
||||||
<div
|
<div
|
||||||
key={detection.id}
|
key={detection.id}
|
||||||
className="p-4 rounded-lg border hover-elevate"
|
className="p-4 rounded-lg border hover-elevate"
|
||||||
@ -356,11 +412,40 @@ export default function Detections() {
|
|||||||
<div className="text-center py-12 text-muted-foreground" data-testid="text-no-results">
|
<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" />
|
<AlertTriangle className="h-12 w-12 mx-auto mb-2 opacity-50" />
|
||||||
<p>Nessun rilevamento trovato</p>
|
<p>Nessun rilevamento trovato</p>
|
||||||
{searchQuery && (
|
{debouncedSearch && (
|
||||||
<p className="text-sm">Prova con un altro termine di ricerca</p>
|
<p className="text-sm">Prova con un altro termine di ricerca</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
-- PostgreSQL database dump
|
-- PostgreSQL database dump
|
||||||
--
|
--
|
||||||
|
|
||||||
\restrict 0WksqiXSxEbKkimrOffHTFz303y80NXUjCIEjcTgyodl4SsmTQnolXeqWX5mUy4
|
\restrict Jq3ohS02Qcz3l9bNbeQprTZolEFbFh84eEwk4en2HkAqc2Xojxrd4AFqHJvBETG
|
||||||
|
|
||||||
-- Dumped from database version 16.11 (74c6bb6)
|
-- Dumped from database version 16.11 (74c6bb6)
|
||||||
-- 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 0WksqiXSxEbKkimrOffHTFz303y80NXUjCIEjcTgyodl4SsmTQnolXeqWX5mUy4
|
\unrestrict Jq3ohS02Qcz3l9bNbeQprTZolEFbFh84eEwk4en2HkAqc2Xojxrd4AFqHJvBETG
|
||||||
|
|
||||||
|
|||||||
33
deployment/migrations/009_add_microsoft_meta_lists.sql
Normal file
33
deployment/migrations/009_add_microsoft_meta_lists.sql
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
-- 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,6 +176,70 @@ class GCPParser(ListParser):
|
|||||||
return ips
|
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):
|
class CloudflareParser(ListParser):
|
||||||
"""Parser for Cloudflare IP list"""
|
"""Parser for Cloudflare IP list"""
|
||||||
|
|
||||||
@ -263,6 +327,11 @@ PARSERS: Dict[str, type[ListParser]] = {
|
|||||||
'talos': TalosParser,
|
'talos': TalosParser,
|
||||||
'aws': AWSParser,
|
'aws': AWSParser,
|
||||||
'gcp': GCPParser,
|
'gcp': GCPParser,
|
||||||
|
'google': GCPParser,
|
||||||
|
'azure': AzureParser,
|
||||||
|
'microsoft': AzureParser,
|
||||||
|
'meta': MetaParser,
|
||||||
|
'facebook': MetaParser,
|
||||||
'cloudflare': CloudflareParser,
|
'cloudflare': CloudflareParser,
|
||||||
'iana': IANAParser,
|
'iana': IANAParser,
|
||||||
'ntp': NTPPoolParser,
|
'ntp': NTPPoolParser,
|
||||||
|
|||||||
@ -77,18 +77,22 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
// Detections
|
// Detections
|
||||||
app.get("/api/detections", async (req, res) => {
|
app.get("/api/detections", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const limit = req.query.limit ? parseInt(req.query.limit as string) : 500;
|
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 anomalyType = req.query.anomalyType as string | undefined;
|
const anomalyType = req.query.anomalyType as string | undefined;
|
||||||
const minScore = req.query.minScore ? parseFloat(req.query.minScore 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 maxScore = req.query.maxScore ? parseFloat(req.query.maxScore as string) : undefined;
|
||||||
|
const search = req.query.search as string | undefined;
|
||||||
|
|
||||||
const detections = await storage.getAllDetections({
|
const result = await storage.getAllDetections({
|
||||||
limit,
|
limit,
|
||||||
|
offset,
|
||||||
anomalyType,
|
anomalyType,
|
||||||
minScore,
|
minScore,
|
||||||
maxScore
|
maxScore,
|
||||||
|
search
|
||||||
});
|
});
|
||||||
res.json(detections);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[DB ERROR] Failed to fetch detections:', error);
|
console.error('[DB ERROR] Failed to fetch detections:', error);
|
||||||
res.status(500).json({ error: "Failed to fetch detections" });
|
res.status(500).json({ error: "Failed to fetch detections" });
|
||||||
@ -474,14 +478,15 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
app.get("/api/stats", async (req, res) => {
|
app.get("/api/stats", async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const routers = await storage.getAllRouters();
|
const routers = await storage.getAllRouters();
|
||||||
const detections = await storage.getAllDetections({ limit: 1000 });
|
const detectionsResult = await storage.getAllDetections({ limit: 1000 });
|
||||||
const recentLogs = await storage.getRecentLogs(1000);
|
const recentLogs = await storage.getRecentLogs(1000);
|
||||||
const whitelist = await storage.getAllWhitelist();
|
const whitelist = await storage.getAllWhitelist();
|
||||||
const latestTraining = await storage.getLatestTraining();
|
const latestTraining = await storage.getLatestTraining();
|
||||||
|
|
||||||
const blockedCount = detections.filter(d => d.blocked).length;
|
const detectionsList = detectionsResult.detections;
|
||||||
const criticalCount = detections.filter(d => parseFloat(d.riskScore) >= 85).length;
|
const blockedCount = detectionsList.filter(d => d.blocked).length;
|
||||||
const highCount = detections.filter(d => parseFloat(d.riskScore) >= 70 && parseFloat(d.riskScore) < 85).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;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
routers: {
|
routers: {
|
||||||
@ -489,7 +494,7 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
|||||||
enabled: routers.filter(r => r.enabled).length
|
enabled: routers.filter(r => r.enabled).length
|
||||||
},
|
},
|
||||||
detections: {
|
detections: {
|
||||||
total: detections.length,
|
total: detectionsResult.total,
|
||||||
blocked: blockedCount,
|
blocked: blockedCount,
|
||||||
critical: criticalCount,
|
critical: criticalCount,
|
||||||
high: highCount
|
high: highCount
|
||||||
|
|||||||
@ -43,10 +43,12 @@ export interface IStorage {
|
|||||||
// Detections
|
// Detections
|
||||||
getAllDetections(options: {
|
getAllDetections(options: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
anomalyType?: string;
|
anomalyType?: string;
|
||||||
minScore?: number;
|
minScore?: number;
|
||||||
maxScore?: number;
|
maxScore?: number;
|
||||||
}): Promise<Detection[]>;
|
search?: string;
|
||||||
|
}): Promise<{ detections: Detection[]; total: number }>;
|
||||||
getDetectionByIp(sourceIp: string): Promise<Detection | undefined>;
|
getDetectionByIp(sourceIp: string): Promise<Detection | undefined>;
|
||||||
createDetection(detection: InsertDetection): Promise<Detection>;
|
createDetection(detection: InsertDetection): Promise<Detection>;
|
||||||
updateDetection(id: string, detection: Partial<InsertDetection>): Promise<Detection | undefined>;
|
updateDetection(id: string, detection: Partial<InsertDetection>): Promise<Detection | undefined>;
|
||||||
@ -174,11 +176,13 @@ export class DatabaseStorage implements IStorage {
|
|||||||
// Detections
|
// Detections
|
||||||
async getAllDetections(options: {
|
async getAllDetections(options: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
anomalyType?: string;
|
anomalyType?: string;
|
||||||
minScore?: number;
|
minScore?: number;
|
||||||
maxScore?: number;
|
maxScore?: number;
|
||||||
}): Promise<Detection[]> {
|
search?: string;
|
||||||
const { limit = 5000, anomalyType, minScore, maxScore } = options;
|
}): Promise<{ detections: Detection[]; total: number }> {
|
||||||
|
const { limit = 50, offset = 0, anomalyType, minScore, maxScore, search } = options;
|
||||||
|
|
||||||
// Build WHERE conditions
|
// Build WHERE conditions
|
||||||
const conditions = [];
|
const conditions = [];
|
||||||
@ -196,17 +200,36 @@ export class DatabaseStorage implements IStorage {
|
|||||||
conditions.push(sql`${detections.riskScore}::numeric <= ${maxScore}`);
|
conditions.push(sql`${detections.riskScore}::numeric <= ${maxScore}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const query = db
|
// Search by IP or anomaly type (case-insensitive)
|
||||||
.select()
|
if (search && search.trim()) {
|
||||||
.from(detections)
|
const searchLower = search.trim().toLowerCase();
|
||||||
.orderBy(desc(detections.detectedAt))
|
conditions.push(sql`(
|
||||||
.limit(limit);
|
LOWER(${detections.sourceIp}) LIKE ${'%' + searchLower + '%'} OR
|
||||||
|
LOWER(${detections.anomalyType}) LIKE ${'%' + searchLower + '%'} OR
|
||||||
if (conditions.length > 0) {
|
LOWER(COALESCE(${detections.country}, '')) LIKE ${'%' + searchLower + '%'} OR
|
||||||
return await query.where(and(...conditions));
|
LOWER(COALESCE(${detections.organization}, '')) LIKE ${'%' + searchLower + '%'}
|
||||||
|
)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await query;
|
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
|
||||||
|
.select()
|
||||||
|
.from(detections)
|
||||||
|
.where(whereClause)
|
||||||
|
.orderBy(desc(detections.detectedAt))
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset);
|
||||||
|
|
||||||
|
return { detections: results, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDetectionByIp(sourceIp: string): Promise<Detection | undefined> {
|
async getDetectionByIp(sourceIp: string): Promise<Detection | undefined> {
|
||||||
|
|||||||
40
version.json
40
version.json
@ -1,7 +1,25 @@
|
|||||||
{
|
{
|
||||||
"version": "1.0.100",
|
"version": "1.0.103",
|
||||||
"lastUpdate": "2026-01-02T15:51:11.271Z",
|
"lastUpdate": "2026-01-02T16:33:13.545Z",
|
||||||
"changelog": [
|
"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",
|
"version": "1.0.100",
|
||||||
"date": "2026-01-02",
|
"date": "2026-01-02",
|
||||||
@ -283,24 +301,6 @@
|
|||||||
"date": "2025-11-24",
|
"date": "2025-11-24",
|
||||||
"type": "patch",
|
"type": "patch",
|
||||||
"description": "Deployment automatico v1.0.54"
|
"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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user