import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export interface BannedIP { ip: string; banTime: string; failCount: number; } export interface Fail2BanStats { currentlyBanned: number; totalBanned: number; currentlyFailed: number; totalFailed: number; bannedIPs: BannedIP[]; } /** * Récupère le statut de Fail2Ban pour SSH */ export async function getFail2BanStatus(): Promise { try { const { stdout } = await execAsync('sudo fail2ban-client status sshd'); const stats: Fail2BanStats = { currentlyBanned: 0, totalBanned: 0, currentlyFailed: 0, totalFailed: 0, bannedIPs: [], }; // Parser le statut const currentlyBannedMatch = stdout.match(/Currently banned:\s+(\d+)/); const totalBannedMatch = stdout.match(/Total banned:\s+(\d+)/); const currentlyFailedMatch = stdout.match(/Currently failed:\s+(\d+)/); const totalFailedMatch = stdout.match(/Total failed:\s+(\d+)/); const bannedIPsMatch = stdout.match(/Banned IP list:\s+(.+)/); if (currentlyBannedMatch) stats.currentlyBanned = parseInt(currentlyBannedMatch[1]); if (totalBannedMatch) stats.totalBanned = parseInt(totalBannedMatch[1]); if (currentlyFailedMatch) stats.currentlyFailed = parseInt(currentlyFailedMatch[1]); if (totalFailedMatch) stats.totalFailed = parseInt(totalFailedMatch[1]); // Récupérer les IPs bannies avec détails if (bannedIPsMatch && bannedIPsMatch[1].trim()) { const ips = bannedIPsMatch[1].trim().split(/\s+/); for (const ip of ips) { try { // Récupérer les détails de chaque IP bannie const { stdout: ipInfo } = await execAsync(`sudo fail2ban-client get sshd actionban | grep -A 10 "${ip}" || echo ""`); stats.bannedIPs.push({ ip, banTime: new Date().toISOString(), // Approximation, Fail2Ban ne stocke pas la date exacte facilement failCount: 3, // Valeur par défaut basée sur maxretry }); } catch (err) { // Si on ne peut pas récupérer les détails, on ajoute quand même l'IP stats.bannedIPs.push({ ip, banTime: new Date().toISOString(), failCount: 3, }); } } } return stats; } catch (error) { console.error('[Fail2Ban] Erreur lors de la récupération du statut:', error); throw new Error('Impossible de récupérer le statut Fail2Ban'); } } /** * Débannit une IP spécifique */ export async function unbanIP(ip: string): Promise { try { await execAsync(`sudo fail2ban-client set sshd unbanip ${ip}`); console.log(`[Fail2Ban] IP ${ip} débannie avec succès`); return true; } catch (error) { console.error(`[Fail2Ban] Erreur lors du débannissement de ${ip}:`, error); return false; } } /** * Récupère l'historique des bannissements depuis les logs */ export async function getBanHistory(limit: number = 50): Promise> { try { const { stdout } = await execAsync(`sudo grep "Ban" /var/log/fail2ban.log | tail -${limit}`); const history: Array<{ ip: string; date: string; action: string }> = []; const lines = stdout.trim().split('\n'); for (const line of lines) { // Format: 2026-01-18 19:00:00,123 fail2ban.actions [12345]: NOTICE [sshd] Ban 1.2.3.4 const match = line.match(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}).*\[(sshd)\] (Ban|Unban) ([\d.]+)/); if (match) { history.push({ date: match[1], action: match[3], ip: match[4], }); } } return history.reverse(); // Plus récent en premier } catch (error) { console.error('[Fail2Ban] Erreur lors de la récupération de l\'historique:', error); return []; } } /** * Compte le nombre d'IPs bannies dans la dernière heure */ export async function countRecentBans(hours: number = 1): Promise { try { const since = new Date(); since.setHours(since.getHours() - hours); const sinceStr = since.toISOString().slice(0, 19).replace('T', ' '); const { stdout } = await execAsync(`sudo grep "Ban" /var/log/fail2ban.log | grep -v "Unban" | awk '{print $1, $2}' | awk -v since="${sinceStr}" '$0 >= since' | wc -l`); return parseInt(stdout.trim()) || 0; } catch (error) { console.error('[Fail2Ban] Erreur lors du comptage des bannissements récents:', error); return 0; } }