diff --git a/install.sh b/install.sh index 33abea6..8df4922 100755 --- a/install.sh +++ b/install.sh @@ -57,7 +57,8 @@ apt-get install -y --no-install-recommends \ openssl \ ca-certificates \ iptables \ - iptables-persistent + iptables-persistent \ + acl # Vérification et installation d'Asterisk echo -e "${C_GREEN} -> Installation du moteur Asterisk PBX...${C_RESET}" @@ -156,8 +157,13 @@ mkdir -p /var/spool/asterisk/voicemail/default mkdir -p /var/log/asterisk mkdir -p /var/log/nexus-pbx -touch /var/log/asterisk/messages -chown asterisk:asterisk /var/log/asterisk/messages 2>/dev/null || true +touch /var/log/asterisk/messages /var/log/asterisk/security.log +chown asterisk:asterisk /var/log/asterisk/messages /var/log/asterisk/security.log 2>/dev/null || true +chmod 0664 /var/log/asterisk/messages /var/log/asterisk/security.log 2>/dev/null || true + +touch /var/log/nexus_security.log +chown www-data:asterisk /var/log/nexus_security.log 2>/dev/null || true +chmod 0664 /var/log/nexus_security.log 2>/dev/null || true # Droits applicatifs stricts chown -R www-data:asterisk "$TARGET_DIR" @@ -168,8 +174,11 @@ chmod 2775 "$TARGET_DIR/storage" \ "$TARGET_DIR/public/avatars" chown -R asterisk:asterisk /etc/asterisk /var/lib/asterisk /var/spool/asterisk /var/log/asterisk +chmod -R 775 /etc/asterisk chmod 2775 /etc/asterisk chmod 2750 /etc/asterisk/keys +setfacl -R -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk 2>/dev/null || true +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 @@ -194,6 +203,8 @@ timestampevents = yes secret = AsteriskAmi2026! deny = 0.0.0.0/0.0.0.0 permit = 127.0.0.1/255.255.255.0 +permit = 127.0.0.1/32 +permit = ::1/128 read = all write = all EOF @@ -201,18 +212,17 @@ chown asterisk:asterisk /etc/asterisk/manager.conf chmod 0640 /etc/asterisk/manager.conf # Fichier de logs pour Fail2ban -if [ ! -f /etc/asterisk/logger.conf ] || ! grep -q 'security' /etc/asterisk/logger.conf 2>/dev/null; then - cat << 'EOF' > /etc/asterisk/logger.conf +cat << 'EOF' > /etc/asterisk/logger.conf [general] dateformat=%F %T [logfiles] console => notice,warning,error messages => notice,warning,error,security +security.log => security EOF - chown asterisk:asterisk /etc/asterisk/logger.conf 2>/dev/null || true - chmod 0644 /etc/asterisk/logger.conf 2>/dev/null || true -fi +chown asterisk:asterisk /etc/asterisk/logger.conf 2>/dev/null || true +chmod 0664 /etc/asterisk/logger.conf 2>/dev/null || true # Certificat TLS auto-signé initial pour PJSIP TLS (port 5061) et Nginx HTTPS (port 8443) if [ ! -f /etc/asterisk/keys/asterisk.crt ] || [ ! -f /etc/asterisk/keys/asterisk.key ]; then @@ -230,18 +240,32 @@ fi chown asterisk:asterisk /etc/asterisk/keys/* 2>/dev/null || true chmod 0640 /etc/asterisk/keys/* 2>/dev/null || true -# Inclusions custom dans Asterisk -touch /etc/asterisk/pjsip.conf /etc/asterisk/extensions.conf -touch /etc/asterisk/pjsip.custom.conf /etc/asterisk/extensions.custom.conf +# Inclusions custom dans Asterisk & initialisation des fichiers cibles +touch /etc/asterisk/pjsip.conf \ + /etc/asterisk/pjsip.custom.conf \ + /etc/asterisk/extensions.conf \ + /etc/asterisk/extensions.custom.conf \ + /etc/asterisk/musiconhold.custom.conf \ + /etc/asterisk/voicemail.custom.conf \ + /etc/asterisk/rtp.conf \ + /etc/asterisk/features.conf + if ! grep -q 'pjsip.custom.conf' /etc/asterisk/pjsip.conf 2>/dev/null; then printf '\n#tryinclude "pjsip.custom.conf"\n' >> /etc/asterisk/pjsip.conf fi if ! grep -q 'extensions.custom.conf' /etc/asterisk/extensions.conf 2>/dev/null; then printf '\n#tryinclude "extensions.custom.conf"\n' >> /etc/asterisk/extensions.conf fi -chown asterisk:asterisk /etc/asterisk/pjsip* /etc/asterisk/extensions* 2>/dev/null || true -chmod 0664 /etc/asterisk/pjsip.conf /etc/asterisk/extensions.conf 2>/dev/null || true -chmod 0660 /etc/asterisk/*.custom.conf 2>/dev/null || true +chown -R asterisk:asterisk /etc/asterisk +chmod 0664 /etc/asterisk/*.conf /etc/asterisk/*.custom.conf 2>/dev/null || true +chmod 2750 /etc/asterisk/keys +chmod 2775 /etc/asterisk +setfacl -R -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk 2>/dev/null || true +setfacl -R -d -m u:www-data:rwx,g:asterisk:rwx /etc/asterisk 2>/dev/null || true + +# Rechargement Asterisk immédiat pour appliquer AMI et Logger +asterisk -rx "core reload" 2>/dev/null || true +asterisk -rx "manager reload" 2>/dev/null || true # 6. Initialisation de la Base de données SQLite & Configuration initiale echo -e "${C_GREEN}[5/8] Initialisation de la base SQLite et configuration NexusPBX...${C_RESET}" @@ -318,13 +342,21 @@ $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 +# Filtre Fail2ban Asterisk & Nexus-Web cat << 'EOF' > /etc/fail2ban/filter.d/asterisk.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 + +cat << 'EOF' > /etc/fail2ban/filter.d/nexus-web.conf +[Definition] +failregex = ^.*\[NEXUS-WEB-AUTH-FAIL\]:\s*.*$ +ignoreregex = +EOF +chmod 0644 /etc/fail2ban/filter.d/nexus-web.conf # Jails Fail2ban cat << 'EOF' > /etc/fail2ban/jail.local @@ -350,8 +382,25 @@ 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 +chown root:asterisk /etc/fail2ban/jail.local +chmod 0664 /etc/fail2ban/jail.local +setfacl -m u:www-data:rw /etc/fail2ban/jail.local 2>/dev/null || true + +chown root:asterisk /etc/nginx/sites-available/nexus-pbx +chmod 0664 /etc/nginx/sites-available/nexus-pbx +setfacl -m u:www-data:rw /etc/nginx/sites-available/nexus-pbx 2>/dev/null || true + # 8. Crontab & Bannière d'accueil Console (Style FreePBX) echo -e "${C_GREEN}[7/8] Mise en place des tâches programmées et de la bannière console...${C_RESET}" @@ -381,14 +430,16 @@ chmod +x /etc/profile.d/nexus-motd.sh echo -e "${C_GREEN}[8/8] Démarrage et activation des services système...${C_RESET}" systemctl daemon-reload -# Redémarrage PHP-FPM +# Redémarrage PHP-FPM (rechargement de l'appartenance au groupe asterisk) +systemctl restart php*-fpm 2>/dev/null || true PHP_SVC=$(systemctl list-unit-files --type=service 2>/dev/null | grep -E "php.*-fpm" | awk '{print $1}' | head -n 1 || echo "php8.4-fpm") -systemctl restart "$PHP_SVC" && systemctl enable "$PHP_SVC" +systemctl restart "$PHP_SVC" 2>/dev/null || true +systemctl enable "$PHP_SVC" 2>/dev/null || true # Redémarrage Asterisk, Nginx, Fail2ban, Cron systemctl restart asterisk && systemctl enable asterisk -nginx -t && systemctl restart nginx && systemctl enable nginx systemctl restart fail2ban && systemctl enable fail2ban +nginx -t && systemctl restart nginx && systemctl enable nginx systemctl restart cron && systemctl enable cron # Exécution immédiate de la bannière console diff --git a/src/ConfigGenerator.php b/src/ConfigGenerator.php index 8c2e653..647b3fc 100644 --- a/src/ConfigGenerator.php +++ b/src/ConfigGenerator.php @@ -1729,16 +1729,33 @@ class ConfigGenerator $tmpPath = $targetPath . '.tmp.' . getmypid(); if (@file_put_contents($tmpPath, $content, LOCK_EX) === false) { + // Repli en écriture directe si le fichier existe déjà et est accessible + if (file_exists($targetPath) && is_writable($targetPath)) { + if (@file_put_contents($targetPath, $content, LOCK_EX) !== false) { + @chgrp($targetPath, 'asterisk'); + @chmod($targetPath, 0664); + return; + } + } throw new RuntimeException("Impossible d'écrire dans {$targetPath}. Vérifiez que www-data fait partie du groupe asterisk et que /etc/asterisk est accessible en écriture de groupe (chmod 2775)."); } @chgrp($tmpPath, 'asterisk'); - @chmod($tmpPath, 0660); + @chmod($tmpPath, 0664); if (!@rename($tmpPath, $targetPath)) { @unlink($tmpPath); + // Repli en écriture directe si le renommage échoue + if (@file_put_contents($targetPath, $content, LOCK_EX) !== false) { + @chgrp($targetPath, 'asterisk'); + @chmod($targetPath, 0664); + return; + } throw new RuntimeException("Échec du remplacement atomique de {$targetPath}."); } + + @chgrp($targetPath, 'asterisk'); + @chmod($targetPath, 0664); } public static function normalizeInboundRoutes(string $json): array diff --git a/src/FirewallManager.php b/src/FirewallManager.php index d0035a2..6c41094 100644 --- a/src/FirewallManager.php +++ b/src/FirewallManager.php @@ -291,8 +291,6 @@ ignoreip = {$whitelist} findtime = {$sipFindTime} bantime = {$sipBanTime} maxretry = {$sipMaxRetry} -banaction = nftables-multiport -banaction_allports = nftables-allports [sshd] enabled = true @@ -306,7 +304,7 @@ bantime = 3600 [asterisk] enabled = true filter = asterisk -logpath = /var/log/asterisk/security.log +logpath = /var/log/asterisk/messages maxretry = {$sipMaxRetry} findtime = {$sipFindTime} bantime = {$sipBanTime} @@ -326,6 +324,8 @@ protocol = tcp EOF; file_put_contents(self::JAIL_LOCAL_PATH, $conf); + @chgrp(self::JAIL_LOCAL_PATH, 'asterisk'); + @chmod(self::JAIL_LOCAL_PATH, 0664); } /** @@ -477,6 +477,8 @@ EOF; $fullNginxConfig = $port80Block . "\n\n" . $phonebookBlock . "\n\n" . $httpsBlock . "\n"; file_put_contents(self::NGINX_CONF_PATH, $fullNginxConfig); + @chgrp(self::NGINX_CONF_PATH, 'asterisk'); + @chmod(self::NGINX_CONF_PATH, 0664); // Test de syntaxe exec('sudo /usr/sbin/nginx -t 2>&1', $tOut, $tCode); diff --git a/src/SystemChecker.php b/src/SystemChecker.php index 2835e3f..dd58293 100644 --- a/src/SystemChecker.php +++ b/src/SystemChecker.php @@ -6,6 +6,9 @@ namespace App; use PDO; use Throwable; +require_once __DIR__ . '/ConfigGenerator.php'; +require_once __DIR__ . '/AmiClient.php'; + class SystemChecker { public static function runDiagnostics(PDO $db): array