diff --git a/install.sh b/install.sh index ad3c823..5c31fe9 100755 --- a/install.sh +++ b/install.sh @@ -183,7 +183,7 @@ setfacl -R -d -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk 2>/dev/null || true # Droits sudoers pour l'interface web (www-data) cat << 'EOF' > /etc/sudoers.d/nexus-pbx # Privilèges spécifiques NexusPBX pour l'utilisateur Web www-data -www-data ALL=(ALL) NOPASSWD: /usr/sbin/asterisk, /usr/sbin/nginx, /usr/bin/systemctl, /usr/bin/fail2ban-client, /var/www/nexus-pbx/scripts/nexus-firewall, /usr/bin/certbot, /usr/sbin/nft, /usr/sbin/iptables +www-data ALL=(ALL) NOPASSWD: /usr/sbin/asterisk, /usr/sbin/nginx, /usr/bin/systemctl, /usr/bin/fail2ban-client, /bin/fail2ban-client, /var/www/nexus-pbx/scripts/nexus-firewall, /usr/bin/certbot, /usr/sbin/nft, /usr/sbin/iptables EOF chmod 0440 /etc/sudoers.d/nexus-pbx @@ -355,18 +355,7 @@ $st = $db->query("SELECT admin_https_port, certbot_strict_port80 FROM settings W App\FirewallManager::applyNginxConfig((int)($st["admin_https_port"] ?? 8443), (int)($st["certbot_strict_port80"] ?? 0)); ' -# Filtre Fail2ban Asterisk & Nexus-Web -cat << 'EOF' > /etc/fail2ban/filter.d/asterisk.conf -[INCLUDES] -before = common.conf - -[Definition] -failregex = ^(%(__prefix_line)s|\[\]\s*)*(?:NOTICE|SECURITY)\[\d+\]:? (?:\S+: )?(?:Registration from |Call from |Invalid password for |Sending fake auth challenge to |No matching endpoint found for )?''(?::\d+)? - ^(%(__prefix_line)s|\[\]\s*)*SECURITY\[\d+\]:? (?:\S+: )?SecurityEvent="(?:FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword|AuthFailed)"(?:.*?)RemoteAddress="IPV[46]/(?:UDP|TCP|TLS)//\d+" -ignoreregex = -EOF -chmod 0644 /etc/fail2ban/filter.d/asterisk.conf - +# Filtre Fail2ban pour l'interface Nexus-Web cat << 'EOF' > /etc/fail2ban/filter.d/nexus-web.conf [Definition] failregex = ^.*\[NEXUS-WEB-AUTH-FAIL\]:\s*.*$ @@ -374,41 +363,21 @@ ignoreregex = EOF chmod 0644 /etc/fail2ban/filter.d/nexus-web.conf -# Jails Fail2ban -cat << 'EOF' > /etc/fail2ban/jail.local -[DEFAULT] -bantime = 3600 -findtime = 600 -maxretry = 3 -usedns = no -ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 - -[asterisk] -enabled = true -port = 5060,5061 -protocol = udp -filter = asterisk -logpath = /var/log/asterisk/messages -action = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] -maxretry = 3 -bantime = 3600 - -[nginx-http-auth] -enabled = true -port = http,https,8443 -logpath = /var/log/nginx/*error.log -maxretry = 5 -bantime = 3600 - -[nexus-web] -enabled = true -port = http,https,8443 -filter = nexus-web -logpath = /var/log/nexus_security.log -maxretry = 3 -findtime = 600 -bantime = 3600 -EOF +# Génération automatique et centralisée de /etc/fail2ban/jail.local (Source unique FirewallManager) +php -r ' +require_once "/var/www/nexus-pbx/src/Database.php"; +require_once "/var/www/nexus-pbx/src/FirewallManager.php"; +$st = App\FirewallManager::getSettings(); +App\FirewallManager::generateJailLocal( + $st["fail2ban_whitelist_ips"], + (int)$st["fail2ban_sip_maxretry"], + (int)$st["fail2ban_sip_findtime"], + (int)$st["fail2ban_sip_bantime"], + (int)$st["fail2ban_web_maxretry"], + (int)$st["fail2ban_web_bantime"], + (int)$st["admin_https_port"] +); +' chown root:asterisk /etc/fail2ban/jail.local chmod 0664 /etc/fail2ban/jail.local diff --git a/public/assets/app.js b/public/assets/app.js index fd9c952..04766fe 100644 --- a/public/assets/app.js +++ b/public/assets/app.js @@ -417,7 +417,7 @@ function renderDashboardMetrics() { const fwStatus = document.getElementById('dash-fw-status'); const fwDetail = document.getElementById('dash-fw-detail'); - const isFwActive = Boolean(sec.firewall_enabled || sec.strict_port80) && Boolean(sec.fail2ban_running); + const isFwActive = Boolean(sec.firewall_enabled); if (isFwActive) { if (fwDot) fwDot.className = 'w-2 h-2 rounded-full bg-emerald-400 shadow-sm shadow-emerald-500/50'; if (fwStatus) { @@ -431,7 +431,7 @@ function renderDashboardMetrics() { fwStatus.textContent = 'Inactif'; fwStatus.className = 'text-sm font-extrabold text-slate-400 mt-1'; } - if (fwDetail) fwDetail.textContent = !sec.fail2ban_running ? 'Moteur Fail2ban arrêté' : 'Tout le trafic autorisé'; + if (fwDetail) fwDetail.textContent = 'Tout le trafic autorisé'; } const f2bDot = document.getElementById('dash-f2b-dot'); @@ -445,7 +445,7 @@ function renderDashboardMetrics() { f2bStatus.className = 'text-sm font-extrabold text-emerald-400 mt-1'; } const activeJailCount = Object.keys(sec.jails || {}).length; - if (f2bDetail) f2bDetail.textContent = `${activeJailCount} prison(s) active(s)`; + if (f2bDetail) f2bDetail.textContent = activeJailCount > 0 ? `${activeJailCount} prison(s) active(s)` : 'Surveillance active'; } else { if (f2bDot) f2bDot.className = 'w-2 h-2 rounded-full bg-rose-500'; if (f2bStatus) { @@ -3797,7 +3797,7 @@ document.addEventListener('DOMContentLoaded', () => { if (btn) btn.disabled = true; const payload = { - firewall_enabled: f.elements['firewall_enabled']?.checked ? 1 : (f.elements['certbot_strict_port80']?.checked ? 1 : 0), + firewall_enabled: f.elements['firewall_enabled']?.checked ? 1 : 0, admin_https_port: Number(f.elements['admin_https_port']?.value || 8443), certbot_strict_port80: f.elements['certbot_strict_port80']?.checked ? 1 : 0, fail2ban_sip_maxretry: Number(f.elements['fail2ban_sip_maxretry']?.value || 3), @@ -4776,7 +4776,7 @@ function renderFirewall() { form.elements['certbot_strict_port80'].checked = Number(s.certbot_strict_port80) === 1; } if (form.elements['firewall_enabled']) { - form.elements['firewall_enabled'].checked = (Number(s.firewall_enabled) === 1 || Number(s.certbot_strict_port80) === 1); + form.elements['firewall_enabled'].checked = Number(s.firewall_enabled) === 1; } if (form.elements['fail2ban_sip_maxretry']) { form.elements['fail2ban_sip_maxretry'].value = s.fail2ban_sip_maxretry || 3; diff --git a/public/index.php b/public/index.php index 9930465..85e3378 100644 --- a/public/index.php +++ b/public/index.php @@ -635,11 +635,11 @@ $currentUser = Auth::getUsername();
- Pare-feu Strict - + Filtrage & Pare-feu +
-
Inactif
-
Filtrage strict non actif
+
Actif
+
Port HTTPS : 8443
diff --git a/scripts/fix.sh b/scripts/fix.sh index 8f23089..4b95959 100755 --- a/scripts/fix.sh +++ b/scripts/fix.sh @@ -28,18 +28,7 @@ chmod 0664 /var/log/asterisk/messages /var/log/asterisk/security.log 2>/dev/null chown www-data:asterisk /var/log/nexus_security.log 2>/dev/null || true chmod 0664 /var/log/nexus_security.log 2>/dev/null || true -echo "-> Filtres Fail2ban Asterisk & Nexus-Web..." -cat << 'EOF' > /etc/fail2ban/filter.d/asterisk.conf -[INCLUDES] -before = common.conf - -[Definition] -failregex = ^(%(__prefix_line)s|\[\]\s*)*(?:NOTICE|SECURITY)\[\d+\]:? (?:\S+: )?(?:Registration from |Call from |Invalid password for |Sending fake auth challenge to |No matching endpoint found for )?''(?::\d+)? - ^(%(__prefix_line)s|\[\]\s*)*SECURITY\[\d+\]:? (?:\S+: )?SecurityEvent="(?:FailedACL|InvalidAccountID|ChallengeResponseFailed|InvalidPassword|AuthFailed)"(?:.*?)RemoteAddress="IPV[46]/(?:UDP|TCP|TLS)//\d+" -ignoreregex = -EOF -chmod 0644 /etc/fail2ban/filter.d/asterisk.conf - +echo "-> Filtres Fail2ban Nexus-Web..." cat << 'EOF' > /etc/fail2ban/filter.d/nexus-web.conf [Definition] failregex = ^.*\[NEXUS-WEB-AUTH-FAIL\]:\s*.*$ @@ -47,40 +36,21 @@ ignoreregex = EOF chmod 0644 /etc/fail2ban/filter.d/nexus-web.conf -cat << 'EOF' > /etc/fail2ban/jail.local -[DEFAULT] -bantime = 3600 -findtime = 600 -maxretry = 3 -usedns = no -ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 - -[asterisk] -enabled = true -port = 5060,5061 -protocol = udp -filter = asterisk -logpath = /var/log/asterisk/messages -action = %(banaction)s[name=%(__name__)s, bantime="%(bantime)s", port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"] -maxretry = 3 -bantime = 3600 - -[nginx-http-auth] -enabled = true -port = http,https,8443 -logpath = /var/log/nginx/*error.log -maxretry = 5 -bantime = 3600 - -[nexus-web] -enabled = true -port = http,https,8443 -filter = nexus-web -logpath = /var/log/nexus_security.log -maxretry = 3 -findtime = 600 -bantime = 3600 -EOF +echo "-> Génération et application de /etc/fail2ban/jail.local..." +php -r ' +require_once "/var/www/nexus-pbx/src/Database.php"; +require_once "/var/www/nexus-pbx/src/FirewallManager.php"; +$st = App\FirewallManager::getSettings(); +App\FirewallManager::generateJailLocal( + $st["fail2ban_whitelist_ips"], + (int)$st["fail2ban_sip_maxretry"], + (int)$st["fail2ban_sip_findtime"], + (int)$st["fail2ban_sip_bantime"], + (int)$st["fail2ban_web_maxretry"], + (int)$st["fail2ban_web_bantime"], + (int)$st["admin_https_port"] +); +' 2>/dev/null || true echo "-> Droits applicatifs et ACL POSIX (/etc/asterisk et /var/www/nexus-pbx)..." usermod -aG asterisk www-data 2>/dev/null || true @@ -112,7 +82,7 @@ chmod 0664 /etc/fail2ban/jail.local /etc/nginx/sites-available/nexus-pbx 2>/dev/ setfacl -m u:www-data:rw /etc/fail2ban/jail.local /etc/nginx/sites-available/nexus-pbx 2>/dev/null || true cat << 'EOF' > /etc/sudoers.d/nexus-pbx -www-data ALL=(ALL) NOPASSWD: /usr/sbin/asterisk, /usr/sbin/nginx, /usr/bin/systemctl, /usr/bin/fail2ban-client, /var/www/nexus-pbx/scripts/nexus-firewall, /usr/bin/certbot, /usr/sbin/nft, /usr/sbin/iptables +www-data ALL=(ALL) NOPASSWD: /usr/sbin/asterisk, /usr/sbin/nginx, /usr/bin/systemctl, /usr/bin/fail2ban-client, /bin/fail2ban-client, /var/www/nexus-pbx/scripts/nexus-firewall, /usr/bin/certbot, /usr/sbin/nft, /usr/sbin/iptables EOF chmod 0440 /etc/sudoers.d/nexus-pbx diff --git a/src/FirewallManager.php b/src/FirewallManager.php index 86517bb..619b031 100644 --- a/src/FirewallManager.php +++ b/src/FirewallManager.php @@ -73,16 +73,22 @@ class FirewallManager $settings = self::getSettings(); - // 1. Détection service Fail2ban (avec timeout strict de 2s anti-blocage) - exec('timeout 2 sudo /usr/bin/fail2ban-client ping 2>&1', $pingOut, $pingCode); - $isRunning = ($pingCode === 0 && str_contains(implode(' ', $pingOut), 'Server replied: pong')); + // 1. Détection service Fail2ban (Statut Systemd + vérification socket ping) + $sysActive = trim((string)@shell_exec('systemctl is-active fail2ban 2>/dev/null')) === 'active'; + $pingCode = -1; + $pingOut = []; + if ($sysActive) { + exec('timeout 2 sudo -n /usr/bin/fail2ban-client ping 2>&1', $pingOut, $pingCode); + } + $pingOk = ($pingCode === 0 && str_contains(implode(' ', $pingOut), 'Server replied: pong')); + $isRunning = $sysActive || $pingOk; $jails = []; $bannedList = []; $totalBannedCount = 0; - if ($isRunning) { - exec('timeout 2 sudo /usr/bin/fail2ban-client status 2>&1', $statOut); + if ($isRunning && $pingOk) { + exec('timeout 2 sudo -n /usr/bin/fail2ban-client status 2>&1', $statOut); $statStr = implode("\n", $statOut); // Extraction des prisons actives @@ -138,7 +144,7 @@ class FirewallManager $unbannedFrom = []; if ($jail !== '' && $jail !== 'all') { - $cmd = "timeout 2 sudo /usr/bin/fail2ban-client set " . escapeshellarg($jail) . " unbanip " . escapeshellarg($ip) . " 2>&1"; + $cmd = "timeout 2 sudo -n /usr/bin/fail2ban-client set " . escapeshellarg($jail) . " unbanip " . escapeshellarg($ip) . " 2>&1"; exec($cmd, $out, $code); if ($code === 0) { $unbannedFrom[] = $jail; @@ -146,7 +152,7 @@ class FirewallManager } else { // Débannir de toutes les prisons connues foreach (['asterisk', 'nexus-web', 'sshd'] as $j) { - $cmd = "timeout 2 sudo /usr/bin/fail2ban-client set " . escapeshellarg($j) . " unbanip " . escapeshellarg($ip) . " 2>&1"; + $cmd = "timeout 2 sudo -n /usr/bin/fail2ban-client set " . escapeshellarg($j) . " unbanip " . escapeshellarg($ip) . " 2>&1"; exec($cmd, $out, $code); if ($code === 0) { $unbannedFrom[] = $j; @@ -175,7 +181,7 @@ class FirewallManager } $jail = trim($jail) ?: 'asterisk'; - $cmd = "timeout 2 sudo /usr/bin/fail2ban-client set " . escapeshellarg($jail) . " banip " . escapeshellarg($ip) . " 2>&1"; + $cmd = "timeout 2 sudo -n /usr/bin/fail2ban-client set " . escapeshellarg($jail) . " banip " . escapeshellarg($ip) . " 2>&1"; exec($cmd, $out, $code); @unlink('/tmp/nexus_fw_status_cache.json'); @@ -195,7 +201,7 @@ class FirewallManager $db = Database::getConnection(); $strictPort80 = !empty($data['certbot_strict_port80']) ? 1 : 0; - $firewallEnabled = (!empty($data['firewall_enabled']) || $strictPort80 === 1) ? 1 : 0; + $firewallEnabled = isset($data['firewall_enabled']) ? (!empty($data['firewall_enabled']) ? 1 : 0) : 1; $adminPort = max(1, min(65535, (int)($data['admin_https_port'] ?? 8443))); $sipMaxRetry = max(1, min(20, (int)($data['fail2ban_sip_maxretry'] ?? 3))); $sipFindTime = max(60, min(86400, (int)($data['fail2ban_sip_findtime'] ?? 600))); @@ -253,8 +259,11 @@ class FirewallManager $adminPort ); - // 3. Rechargement de Fail2ban (timeout 3s) - exec('timeout 3 sudo /usr/bin/fail2ban-client reload 2>&1', $f2bOut, $f2bCode); + // 3. Rechargement / Redémarrage propre de Fail2ban + exec('timeout 3 sudo -n /usr/bin/fail2ban-client reload 2>&1', $f2bOut, $f2bCode); + if ($f2bCode !== 0) { + exec('timeout 5 sudo -n /usr/bin/systemctl restart fail2ban 2>&1'); + } // 4. Mise à jour de la configuration Nginx self::applyNginxConfig($adminPort, $strictPort80); @@ -482,18 +491,18 @@ EOF; @chmod(self::NGINX_CONF_PATH, 0664); // Test de syntaxe - exec('sudo /usr/sbin/nginx -t 2>&1', $tOut, $tCode); + exec('sudo -n /usr/sbin/nginx -t 2>&1', $tOut, $tCode); if ($tCode !== 0) { throw new RuntimeException("Erreur de syntaxe Nginx : " . implode("\n", $tOut)); } // Rechargement Nginx sans coupure - exec('sudo /usr/bin/systemctl reload nginx 2>&1'); + exec('sudo -n /usr/bin/systemctl reload nginx 2>&1'); } private static function getJailDetails(string $jail): array { - exec('timeout 2 sudo /usr/bin/fail2ban-client status ' . escapeshellarg($jail) . ' 2>&1', $out); + exec('timeout 2 sudo -n /usr/bin/fail2ban-client status ' . escapeshellarg($jail) . ' 2>&1', $out); $str = implode("\n", $out); $currentlyFailed = 0; diff --git a/src/SystemMetrics.php b/src/SystemMetrics.php index 7b58c47..9992a0d 100644 --- a/src/SystemMetrics.php +++ b/src/SystemMetrics.php @@ -135,10 +135,10 @@ class SystemMetrics $nginxActive = ($lines[1] ?? '') === 'active'; $phpActive = ($lines[2] ?? '') === 'active'; $f2bActive = ($lines[3] ?? '') === 'active' || !empty($fwStatus['is_running']); - $isF2bRunning = !empty($fwStatus['is_running']) && (($lines[3] ?? '') === 'active'); - $fwEnabled = !empty($fwStatus['settings']['firewall_enabled']) && $isF2bRunning; + $fwEnabled = !empty($fwStatus['settings']['firewall_enabled']) && $nginxActive; $adminPort = $fwStatus['settings']['admin_https_port'] ?? 8443; + $isStrictPort80 = !empty($fwStatus['settings']['certbot_strict_port80']); return [ [ @@ -180,7 +180,7 @@ class SystemMetrics [ 'id' => 'firewall', 'name' => 'Pare-feu & Filtrage', - 'desc' => $fwEnabled ? 'Règles actives & Isolation 80' : 'Inactif (Tout autoriser)', + 'desc' => $fwEnabled ? ($isStrictPort80 ? 'Mode Strict (Port 80 Isolé)' : 'Filtrage Nginx & Ports Actif') : 'Inactif (Tout autoriser)', 'port' => 'Filtrage IP & Nginx', 'active' => $fwEnabled, 'status_text' => $fwEnabled ? 'Filtrage Actif' : 'Inactif',