Files
formation-manager-itinova/server/fail2banDb.ts
Manus 1c15df377d Checkpoint: Correction des erreurs Fail2Ban et SQL DATE_FORMAT :
**1. Correction erreur Fail2Ban (fail2banDb.ts) :**
- Ajout de vérifications pour détecter si Fail2Ban est installé (commande `which fail2ban-client`)
- Retour de valeurs par défaut (0, tableaux vides) au lieu de throw d'erreur dans l'environnement sandbox
- Corrections appliquées aux 3 fonctions : getFail2BanStatus(), getBanHistory(), countRecentBans()
- La page /admin/fail2ban s'affiche maintenant correctement sans erreur dans la sandbox

**2. Correction erreur SQL DATE_FORMAT (analyticsDb.ts) :**
- Ajout d'un filtre `IS NOT NULL` sur la colonne dateInscription dans getInscriptionsByMonth()
- Exclusion des inscriptions avec dateInscription NULL qui causaient des erreurs SQL
- Le graphique "Évolution des inscriptions par mois" affiche maintenant "Aucune donnée disponible" au lieu d'une erreur

**Tests réussis :**
-  Page Fail2Ban : affiche correctement les statistiques à 0 et les messages appropriés
-  Tableau de bord analytique : tous les graphiques fonctionnent sans erreur SQL
-  Les autres pages (rapport-public-cible, suivi questionnaires) continuent de fonctionner correctement

**Résultat :**
 Toutes les erreurs signalées sont corrigées
 L'application fonctionne correctement dans l'environnement sandbox
 Sur le VPS de production, Fail2Ban affichera les vraies valeurs
2026-01-20 11:52:47 -05:00

173 lines
5.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 {
// Vérifier si Fail2Ban est disponible
try {
await execAsync('which fail2ban-client');
} catch (e) {
// Fail2Ban n'est pas installé (environnement sandbox), retourner des valeurs par défaut
console.log('[Fail2Ban] Service non disponible (environnement sandbox)');
return {
currentlyBanned: 0,
totalBanned: 0,
currentlyFailed: 0,
totalFailed: 0,
bannedIPs: [],
};
}
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 {
// Vérifier si Fail2Ban est disponible
try {
await execAsync('which fail2ban-client');
} catch (e) {
// Fail2Ban n'est pas installé (environnement sandbox)
console.log('[Fail2Ban] Service non disponible (environnement sandbox)');
return [];
}
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 {
// Vérifier si Fail2Ban est disponible
try {
await execAsync('which fail2ban-client');
} catch (e) {
// Fail2Ban n'est pas installé (environnement sandbox)
console.log('[Fail2Ban] Service non disponible (environnement sandbox)');
return 0;
}
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;
}
}