140 lines
4.5 KiB
TypeScript
140 lines
4.5 KiB
TypeScript
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<Fail2BanStats> {
|
|
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<boolean> {
|
|
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<Array<{ ip: string; date: string; action: string }>> {
|
|
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<number> {
|
|
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;
|
|
}
|
|
}
|