feat(ucp): NexusPBX v2.5.0 Portail Utilisateur isolé (Refs #4)
This commit is contained in:
1 parent
7922e338e3
commit
72e43f1a9a
18 files changed
+3499
-62
No files matched your search
+294
-26
@@ -13,6 +13,9 @@ class FirewallManager
|
||||
public const JAIL_LOCAL_PATH = '/etc/fail2ban/jail.local';
|
||||
public const NGINX_CONF_PATH = '/etc/nginx/sites-available/nexus-pbx';
|
||||
public const WEB_FILTER_PATH = '/etc/fail2ban/filter.d/nexus-web.conf';
|
||||
public const UCP_FILTER_PATH = '/etc/fail2ban/filter.d/nexus-ucp.conf';
|
||||
public const UCP_SECURITY_LOG = '/var/log/nexus_ucp_security.log';
|
||||
public const UCP_ROOT_DIR = '/var/www/nexus-pbx/ucp';
|
||||
|
||||
/**
|
||||
* Récupère la configuration actuelle du pare-feu et de Fail2ban depuis SQLite.
|
||||
@@ -23,7 +26,9 @@ class FirewallManager
|
||||
$stmt = $db->query("SELECT
|
||||
firewall_enabled, admin_https_port, certbot_strict_port80,
|
||||
fail2ban_sip_maxretry, fail2ban_sip_findtime, fail2ban_sip_bantime,
|
||||
fail2ban_web_maxretry, fail2ban_web_bantime, fail2ban_whitelist_ips
|
||||
fail2ban_web_maxretry, fail2ban_web_bantime, fail2ban_whitelist_ips,
|
||||
ucp_enabled, ucp_https_port, ucp_http_port,
|
||||
fail2ban_ucp_maxretry, fail2ban_ucp_bantime
|
||||
FROM settings WHERE id = 1");
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
@@ -38,6 +43,11 @@ class FirewallManager
|
||||
'fail2ban_web_maxretry' => 3,
|
||||
'fail2ban_web_bantime' => 3600,
|
||||
'fail2ban_whitelist_ips' => '127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 82.122.84.60',
|
||||
'ucp_enabled' => 1,
|
||||
'ucp_https_port' => 8444,
|
||||
'ucp_http_port' => 8084,
|
||||
'fail2ban_ucp_maxretry' => 3,
|
||||
'fail2ban_ucp_bantime' => 3600,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -51,6 +61,11 @@ class FirewallManager
|
||||
'fail2ban_web_maxretry' => (int)($row['fail2ban_web_maxretry'] ?? 3),
|
||||
'fail2ban_web_bantime' => (int)($row['fail2ban_web_bantime'] ?? 3600),
|
||||
'fail2ban_whitelist_ips' => (string)($row['fail2ban_whitelist_ips'] ?? '127.0.0.1/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 82.122.84.60'),
|
||||
'ucp_enabled' => (int)($row['ucp_enabled'] ?? 1),
|
||||
'ucp_https_port' => (int)($row['ucp_https_port'] ?? 8444),
|
||||
'ucp_http_port' => (int)($row['ucp_http_port'] ?? 8084),
|
||||
'fail2ban_ucp_maxretry' => (int)($row['fail2ban_ucp_maxretry'] ?? 3),
|
||||
'fail2ban_ucp_bantime' => (int)($row['fail2ban_ucp_bantime'] ?? 3600),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -111,11 +126,8 @@ class FirewallManager
|
||||
}
|
||||
}
|
||||
|
||||
// IP actuelle du client
|
||||
$currentIp = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
|
||||
if (str_contains($currentIp, ',')) {
|
||||
$currentIp = trim(explode(',', $currentIp)[0]);
|
||||
}
|
||||
// IP actuelle du client (avec résolution reverse proxy si proxy privé/local)
|
||||
$currentIp = self::resolveClientIp();
|
||||
|
||||
$status = [
|
||||
'is_running' => $isRunning,
|
||||
@@ -151,7 +163,7 @@ class FirewallManager
|
||||
}
|
||||
} else {
|
||||
// Débannir de toutes les prisons connues
|
||||
foreach (['asterisk', 'nexus-web', 'sshd'] as $j) {
|
||||
foreach (['asterisk', 'nexus-web', 'nexus-ucp', 'sshd'] as $j) {
|
||||
$cmd = "timeout 2 sudo -n /usr/bin/fail2ban-client set " . escapeshellarg($j) . " unbanip " . escapeshellarg($ip) . " 2>&1";
|
||||
exec($cmd, $out, $code);
|
||||
if ($code === 0) {
|
||||
@@ -200,6 +212,7 @@ class FirewallManager
|
||||
{
|
||||
$db = Database::getConnection();
|
||||
|
||||
$oldSettings = self::getSettings();
|
||||
$strictPort80 = !empty($data['certbot_strict_port80']) ? 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)));
|
||||
@@ -209,6 +222,26 @@ class FirewallManager
|
||||
$webMaxRetry = max(1, min(20, (int)($data['fail2ban_web_maxretry'] ?? 3)));
|
||||
$webBanTime = max(60, min(31536000, (int)($data['fail2ban_web_bantime'] ?? 3600)));
|
||||
|
||||
$ucpEnabled = isset($data['ucp_enabled']) ? (!empty($data['ucp_enabled']) ? 1 : 0) : (int)$oldSettings['ucp_enabled'];
|
||||
$ucpHttpsPort = isset($data['ucp_https_port']) ? max(0, min(65535, (int)$data['ucp_https_port'])) : (int)$oldSettings['ucp_https_port'];
|
||||
$ucpHttpPort = isset($data['ucp_http_port']) ? max(0, min(65535, (int)$data['ucp_http_port'])) : (int)$oldSettings['ucp_http_port'];
|
||||
$ucpMaxRetry = isset($data['fail2ban_ucp_maxretry']) ? max(1, min(20, (int)$data['fail2ban_ucp_maxretry'])) : (int)$oldSettings['fail2ban_ucp_maxretry'];
|
||||
$ucpBanTime = isset($data['fail2ban_ucp_bantime']) ? max(60, min(31536000, (int)$data['fail2ban_ucp_bantime'])) : (int)$oldSettings['fail2ban_ucp_bantime'];
|
||||
|
||||
if ($ucpEnabled === 1 && $ucpHttpsPort === 0 && $ucpHttpPort === 0) {
|
||||
throw new InvalidArgumentException("Lorsque l'UCP est activé, au moins un port (HTTPS ou HTTP) doit être supérieur à 0.");
|
||||
}
|
||||
$reservedPorts = [80, 443, 8080, $adminPort, 5060, 5061];
|
||||
if ($ucpHttpsPort > 0 && in_array($ucpHttpsPort, $reservedPorts, true)) {
|
||||
throw new InvalidArgumentException("Le port HTTPS UCP ({$ucpHttpsPort}) entre en conflit avec un port réservé (Admin/SIP/ACME).");
|
||||
}
|
||||
if ($ucpHttpPort > 0 && in_array($ucpHttpPort, $reservedPorts, true)) {
|
||||
throw new InvalidArgumentException("Le port HTTP UCP ({$ucpHttpPort}) entre en conflit avec un port réservé (Admin/SIP/ACME).");
|
||||
}
|
||||
if ($ucpHttpsPort > 0 && $ucpHttpPort > 0 && $ucpHttpsPort === $ucpHttpPort) {
|
||||
throw new InvalidArgumentException("Les ports HTTPS et HTTP de l'UCP doivent être différents.");
|
||||
}
|
||||
|
||||
$whitelist = trim((string)($data['fail2ban_whitelist_ips'] ?? ''));
|
||||
$cleanTokens = array_values(array_filter(preg_split('/\s+/', $whitelist)));
|
||||
|
||||
@@ -233,22 +266,33 @@ class FirewallManager
|
||||
fail2ban_web_maxretry = :wmr,
|
||||
fail2ban_web_bantime = :wbt,
|
||||
fail2ban_whitelist_ips = :wl,
|
||||
ucp_enabled = :ue,
|
||||
ucp_https_port = :uhs,
|
||||
ucp_http_port = :uhp,
|
||||
fail2ban_ucp_maxretry = :umr,
|
||||
fail2ban_ucp_bantime = :ubt,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = 1");
|
||||
|
||||
$stmt->execute([
|
||||
':fe' => $firewallEnabled,
|
||||
':ap' => $adminPort,
|
||||
':fe' => $firewallEnabled,
|
||||
':ap' => $adminPort,
|
||||
':sp80' => $strictPort80,
|
||||
':smr' => $sipMaxRetry,
|
||||
':sft' => $sipFindTime,
|
||||
':sbt' => $sipBanTime,
|
||||
':wmr' => $webMaxRetry,
|
||||
':wbt' => $webBanTime,
|
||||
':wl' => $finalWhitelist,
|
||||
':smr' => $sipMaxRetry,
|
||||
':sft' => $sipFindTime,
|
||||
':sbt' => $sipBanTime,
|
||||
':wmr' => $webMaxRetry,
|
||||
':wbt' => $webBanTime,
|
||||
':wl' => $finalWhitelist,
|
||||
':ue' => $ucpEnabled,
|
||||
':uhs' => $ucpHttpsPort,
|
||||
':uhp' => $ucpHttpPort,
|
||||
':umr' => $ucpMaxRetry,
|
||||
':ubt' => $ucpBanTime,
|
||||
]);
|
||||
|
||||
// 2. Génération et application du fichier /etc/fail2ban/jail.local
|
||||
// 2. Garantir la présence du filtre & journal Fail2ban UCP puis générer /etc/fail2ban/jail.local
|
||||
self::ensureUcpFail2banArtifacts();
|
||||
self::generateJailLocal(
|
||||
$finalWhitelist,
|
||||
$sipMaxRetry,
|
||||
@@ -256,7 +300,12 @@ class FirewallManager
|
||||
$sipBanTime,
|
||||
$webMaxRetry,
|
||||
$webBanTime,
|
||||
$adminPort
|
||||
$adminPort,
|
||||
$ucpEnabled,
|
||||
$ucpHttpsPort,
|
||||
$ucpHttpPort,
|
||||
$ucpMaxRetry,
|
||||
$ucpBanTime
|
||||
);
|
||||
|
||||
// 3. Rechargement / Redémarrage propre de Fail2ban
|
||||
@@ -265,14 +314,19 @@ class FirewallManager
|
||||
exec('timeout 5 sudo -n /usr/bin/systemctl restart fail2ban 2>&1');
|
||||
}
|
||||
|
||||
// 4. Mise à jour de la configuration Nginx
|
||||
self::applyNginxConfig($adminPort, $strictPort80);
|
||||
// 4. Mise à jour de la configuration Nginx & ouverture/fermeture dynamique des ports UCP
|
||||
self::applyNginxConfig($adminPort, $strictPort80, $ucpEnabled, $ucpHttpsPort, $ucpHttpPort);
|
||||
self::syncUcpFirewallPorts($oldSettings, [
|
||||
'ucp_enabled' => $ucpEnabled,
|
||||
'ucp_https_port' => $ucpHttpsPort,
|
||||
'ucp_http_port' => $ucpHttpPort,
|
||||
]);
|
||||
|
||||
@unlink('/tmp/nexus_fw_status_cache.json');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => 'Configuration Pare-feu & Fail2ban appliquée avec succès ! Nginx et Fail2ban rechargés.',
|
||||
'message' => 'Configuration Pare-feu, UCP & Fail2ban appliquée avec succès ! Nginx et Fail2ban rechargés.',
|
||||
'settings' => self::getSettings(),
|
||||
];
|
||||
}
|
||||
@@ -287,9 +341,30 @@ class FirewallManager
|
||||
int $sipBanTime,
|
||||
int $webMaxRetry,
|
||||
int $webBanTime,
|
||||
int $adminPort
|
||||
int $adminPort,
|
||||
?int $ucpEnabled = null,
|
||||
?int $ucpHttpsPort = null,
|
||||
?int $ucpHttpPort = null,
|
||||
?int $ucpMaxRetry = null,
|
||||
?int $ucpBanTime = null
|
||||
): void {
|
||||
if ($ucpEnabled === null || $ucpHttpsPort === null || $ucpHttpPort === null || $ucpMaxRetry === null || $ucpBanTime === null) {
|
||||
$st = self::getSettings();
|
||||
$ucpEnabled = $ucpEnabled ?? $st['ucp_enabled'];
|
||||
$ucpHttpsPort = $ucpHttpsPort ?? $st['ucp_https_port'];
|
||||
$ucpHttpPort = $ucpHttpPort ?? $st['ucp_http_port'];
|
||||
$ucpMaxRetry = $ucpMaxRetry ?? $st['fail2ban_ucp_maxretry'];
|
||||
$ucpBanTime = $ucpBanTime ?? $st['fail2ban_ucp_bantime'];
|
||||
}
|
||||
|
||||
self::ensureUcpFail2banArtifacts();
|
||||
|
||||
$portsWeb = "http,https,{$adminPort}";
|
||||
$ucpPortsList = [];
|
||||
if ($ucpHttpsPort > 0) $ucpPortsList[] = (string)$ucpHttpsPort;
|
||||
if ($ucpHttpPort > 0) $ucpPortsList[] = (string)$ucpHttpPort;
|
||||
$ucpJailEnabled = ($ucpEnabled === 1 && !empty($ucpPortsList)) ? 'true' : 'false';
|
||||
$portsUcp = !empty($ucpPortsList) ? implode(',', $ucpPortsList) : '8444';
|
||||
|
||||
$conf = <<<EOF
|
||||
# Fichier généré automatiquement par NexusPBX
|
||||
@@ -331,6 +406,16 @@ bantime = {$webBanTime}
|
||||
port = {$portsWeb}
|
||||
protocol = tcp
|
||||
|
||||
[nexus-ucp]
|
||||
enabled = {$ucpJailEnabled}
|
||||
filter = nexus-ucp
|
||||
logpath = /var/log/nexus_ucp_security.log
|
||||
maxretry = {$ucpMaxRetry}
|
||||
findtime = 600
|
||||
bantime = {$ucpBanTime}
|
||||
port = {$portsUcp}
|
||||
protocol = tcp
|
||||
|
||||
EOF;
|
||||
|
||||
file_put_contents(self::JAIL_LOCAL_PATH, $conf);
|
||||
@@ -339,10 +424,22 @@ EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour Nginx pour séparer le port 80 (Certbot/ACME uniquement si strict) et écouter sur le port d'admin.
|
||||
* Met à jour Nginx pour séparer le port 80 (Certbot/ACME), le port d'admin et les VHosts isolés de l'UCP.
|
||||
*/
|
||||
public static function applyNginxConfig(int $adminHttpsPort, int $strictPort80): void
|
||||
{
|
||||
public static function applyNginxConfig(
|
||||
int $adminHttpsPort,
|
||||
int $strictPort80,
|
||||
?int $ucpEnabled = null,
|
||||
?int $ucpHttpsPort = null,
|
||||
?int $ucpHttpPort = null
|
||||
): void {
|
||||
if ($ucpEnabled === null || $ucpHttpsPort === null || $ucpHttpPort === null) {
|
||||
$st = self::getSettings();
|
||||
$ucpEnabled = $ucpEnabled ?? $st['ucp_enabled'];
|
||||
$ucpHttpsPort = $ucpHttpsPort ?? $st['ucp_https_port'];
|
||||
$ucpHttpPort = $ucpHttpPort ?? $st['ucp_http_port'];
|
||||
}
|
||||
|
||||
$phpSock = '/run/php/php8.4-fpm.sock';
|
||||
if (!file_exists($phpSock)) {
|
||||
$found = glob('/run/php/php*-fpm.sock');
|
||||
@@ -485,7 +582,19 @@ server {
|
||||
}
|
||||
EOF;
|
||||
|
||||
$fullNginxConfig = $port80Block . "\n\n" . $phonebookBlock . "\n\n" . $httpsBlock . "\n";
|
||||
$blocks = [$port80Block, $phonebookBlock, $httpsBlock];
|
||||
|
||||
// VirtualHosts Nginx totalement isolés pour l'UCP (/var/www/nexus-pbx/ucp)
|
||||
if ($ucpEnabled === 1 && is_dir(self::UCP_ROOT_DIR)) {
|
||||
if ($ucpHttpsPort > 0) {
|
||||
$blocks[] = self::buildUcpServerBlock($ucpHttpsPort, true, $phpSock);
|
||||
}
|
||||
if ($ucpHttpPort > 0) {
|
||||
$blocks[] = self::buildUcpServerBlock($ucpHttpPort, false, $phpSock);
|
||||
}
|
||||
}
|
||||
|
||||
$fullNginxConfig = implode("\n\n", $blocks) . "\n";
|
||||
file_put_contents(self::NGINX_CONF_PATH, $fullNginxConfig);
|
||||
@chgrp(self::NGINX_CONF_PATH, 'asterisk');
|
||||
@chmod(self::NGINX_CONF_PATH, 0664);
|
||||
@@ -500,6 +609,159 @@ EOF;
|
||||
exec('sudo -n /usr/bin/systemctl reload nginx 2>&1');
|
||||
}
|
||||
|
||||
private static function buildUcpServerBlock(int $port, bool $ssl, string $phpSock): string
|
||||
{
|
||||
$ucpRoot = self::UCP_ROOT_DIR;
|
||||
$listenDirectives = $ssl
|
||||
? "listen {$port} ssl;\n listen [::]:{$port} ssl;"
|
||||
: "listen {$port};\n listen [::]:{$port};";
|
||||
|
||||
$sslDirectives = $ssl ? <<<SSL
|
||||
|
||||
ssl_certificate /etc/asterisk/keys/active_cert.crt;
|
||||
ssl_certificate_key /etc/asterisk/keys/active_key.key;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||
ssl_prefer_server_ciphers on;
|
||||
SSL : '';
|
||||
|
||||
$logPrefix = $ssl ? 'nexus-ucp.ssl' : 'nexus-ucp';
|
||||
|
||||
return <<<EOF
|
||||
server {
|
||||
{$listenDirectives}
|
||||
server_name _;{$sslDirectives}
|
||||
|
||||
client_max_body_size 16M;
|
||||
|
||||
root {$ucpRoot};
|
||||
index index.php;
|
||||
|
||||
access_log /var/log/nginx/{$logPrefix}.access.log;
|
||||
error_log /var/log/nginx/{$logPrefix}.error.log;
|
||||
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
location / {
|
||||
try_files \$uri \$uri/ /index.php?\$query_string;
|
||||
}
|
||||
|
||||
location ~* \.(?:css|js)$ {
|
||||
expires -1;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
|
||||
try_files \$uri =404;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_pass unix:{$phpSock};
|
||||
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
EOF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée le filtre et le fichier de log Fail2ban dédiés au portail UCP.
|
||||
*/
|
||||
public static function ensureUcpFail2banArtifacts(): void
|
||||
{
|
||||
$filterContent = "[Definition]\nfailregex = ^.*\\[NEXUS-UCP-AUTH-FAIL\\]:\\s*<HOST>.*$\nignoreregex =\n";
|
||||
if (!file_exists(self::UCP_FILTER_PATH) || file_get_contents(self::UCP_FILTER_PATH) !== $filterContent) {
|
||||
@file_put_contents(self::UCP_FILTER_PATH, $filterContent);
|
||||
@chmod(self::UCP_FILTER_PATH, 0644);
|
||||
}
|
||||
if (!file_exists(self::UCP_SECURITY_LOG)) {
|
||||
@touch(self::UCP_SECURITY_LOG);
|
||||
}
|
||||
@chown(self::UCP_SECURITY_LOG, 'www-data');
|
||||
@chgrp(self::UCP_SECURITY_LOG, 'asterisk');
|
||||
@chmod(self::UCP_SECURITY_LOG, 0664);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ouvre/ferme les ports UCP configurés dans UFW / iptables uniquement si un filtrage actif existe.
|
||||
*/
|
||||
private static function syncUcpFirewallPorts(array $oldSettings, array $newSettings): void
|
||||
{
|
||||
$oldPorts = [];
|
||||
if (!empty($oldSettings['ucp_enabled'])) {
|
||||
if ((int)($oldSettings['ucp_https_port'] ?? 0) > 0) $oldPorts[] = (int)$oldSettings['ucp_https_port'];
|
||||
if ((int)($oldSettings['ucp_http_port'] ?? 0) > 0) $oldPorts[] = (int)$oldSettings['ucp_http_port'];
|
||||
}
|
||||
$newPorts = [];
|
||||
if (!empty($newSettings['ucp_enabled'])) {
|
||||
if ((int)($newSettings['ucp_https_port'] ?? 0) > 0) $newPorts[] = (int)$newSettings['ucp_https_port'];
|
||||
if ((int)($newSettings['ucp_http_port'] ?? 0) > 0) $newPorts[] = (int)$newSettings['ucp_http_port'];
|
||||
}
|
||||
|
||||
$toRemove = array_diff(array_unique($oldPorts), array_unique($newPorts));
|
||||
$toAdd = array_diff(array_unique($newPorts), array_unique($oldPorts));
|
||||
|
||||
// UFW si installé et actif
|
||||
$ufwStatus = trim((string)@shell_exec('sudo -n /usr/sbin/ufw status 2>/dev/null'));
|
||||
if ($ufwStatus !== '' && stripos($ufwStatus, 'Status: active') !== false) {
|
||||
foreach ($toRemove as $p) {
|
||||
@shell_exec('sudo -n /usr/sbin/ufw delete allow ' . (int)$p . '/tcp >/dev/null 2>&1');
|
||||
}
|
||||
foreach ($newPorts as $p) {
|
||||
@shell_exec('sudo -n /usr/sbin/ufw allow ' . (int)$p . '/tcp >/dev/null 2>&1');
|
||||
}
|
||||
}
|
||||
|
||||
// iptables si la politique INPUT par défaut bloque le trafic (DROP/REJECT)
|
||||
$iptPolicy = trim((string)@shell_exec("sudo -n /usr/sbin/iptables -S INPUT 2>/dev/null | head -n 1"));
|
||||
if (str_contains($iptPolicy, 'DROP') || str_contains($iptPolicy, 'REJECT')) {
|
||||
foreach ($toRemove as $p) {
|
||||
@shell_exec("sudo -n /usr/sbin/iptables -D INPUT -p tcp --dport " . (int)$p . " -j ACCEPT >/dev/null 2>&1");
|
||||
}
|
||||
foreach ($toAdd as $p) {
|
||||
@shell_exec("sudo -n /usr/sbin/iptables -C INPUT -p tcp --dport " . (int)$p . " -j ACCEPT >/dev/null 2>&1 || sudo -n /usr/sbin/iptables -I INPUT -p tcp --dport " . (int)$p . " -j ACCEPT >/dev/null 2>&1");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout l'IP réelle du client en tenant compte d'un reverse proxy local / RFC1918 de confiance.
|
||||
*/
|
||||
public static function resolveClientIp(): string
|
||||
{
|
||||
$remote = trim((string)($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1'));
|
||||
if ($remote === '' || !filter_var($remote, FILTER_VALIDATE_IP)) {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
|
||||
// Ne faire confiance aux en-têtes proxy que si la connexion TCP provient d'un réseau privé ou loopback
|
||||
$isTrustedProxy = (
|
||||
filter_var($remote, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false
|
||||
);
|
||||
|
||||
if ($isTrustedProxy) {
|
||||
$candidates = [];
|
||||
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
|
||||
$candidates[] = trim((string)$_SERVER['HTTP_X_REAL_IP']);
|
||||
}
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
foreach (explode(',', (string)$_SERVER['HTTP_X_FORWARDED_FOR']) as $part) {
|
||||
$candidates[] = trim($part);
|
||||
}
|
||||
}
|
||||
foreach ($candidates as $cand) {
|
||||
if ($cand !== '' && filter_var($cand, FILTER_VALIDATE_IP)) {
|
||||
return $cand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $remote;
|
||||
}
|
||||
|
||||
private static function getJailDetails(string $jail): array
|
||||
{
|
||||
exec('timeout 2 sudo -n /usr/bin/fail2ban-client status ' . escapeshellarg($jail) . ' 2>&1', $out);
|
||||
@@ -526,7 +788,12 @@ EOF;
|
||||
$totalFailed = max($totalFailed, $logIncidents);
|
||||
} elseif ($jail === 'nexus-web') {
|
||||
$logIncidents = is_readable('/var/log/nexus_security.log')
|
||||
? (int)shell_exec("grep -c 'FailedLogin' /var/log/nexus_security.log 2>/dev/null")
|
||||
? (int)shell_exec("grep -c -E 'NEXUS-WEB-AUTH-FAIL|FailedLogin' /var/log/nexus_security.log 2>/dev/null")
|
||||
: 0;
|
||||
$totalFailed = max($totalFailed, $logIncidents);
|
||||
} elseif ($jail === 'nexus-ucp') {
|
||||
$logIncidents = is_readable(self::UCP_SECURITY_LOG)
|
||||
? (int)shell_exec("grep -c 'NEXUS-UCP-AUTH-FAIL' " . escapeshellarg(self::UCP_SECURITY_LOG) . " 2>/dev/null")
|
||||
: 0;
|
||||
$totalFailed = max($totalFailed, $logIncidents);
|
||||
}
|
||||
@@ -558,6 +825,7 @@ EOF;
|
||||
return match ($jail) {
|
||||
'asterisk' => 'Téléphonie SIP (PJSIP)',
|
||||
'nexus-web' => 'Interface Web Admin',
|
||||
'nexus-ucp' => 'Portail Utilisateur (UCP)',
|
||||
'sshd' => 'Accès Console SSH',
|
||||
default => ucfirst($jail),
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user