965 lines
33 KiB
PHP
965 lines
33 KiB
PHP
<?php
|
||
|
||
|
||
namespace App\Livewire\Ui\Security;
|
||
|
||
use Illuminate\Support\Facades\Log;
|
||
use Livewire\Attributes\On;
|
||
use Livewire\Component;
|
||
|
||
class Fail2BanCard extends Component
|
||
{
|
||
public bool $available = true;
|
||
public bool $permDenied = false;
|
||
public bool $error = false;
|
||
public int $activeBans = 0;
|
||
public array $jails = [];
|
||
|
||
public function mount(): void
|
||
{
|
||
$this->load();
|
||
}
|
||
|
||
public function render()
|
||
{
|
||
return view('livewire.ui.security.fail2-ban-card');
|
||
}
|
||
|
||
#[On('f2b:refresh-banlist')]
|
||
public function refresh(): void
|
||
{
|
||
$this->load(true);
|
||
}
|
||
|
||
public function openDetails(string $jail): void
|
||
{
|
||
$this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]);
|
||
}
|
||
|
||
/* ---------------- intern ---------------- */
|
||
|
||
protected function load(bool $force = false): void
|
||
{
|
||
$this->available = $this->permDenied = $this->error = false;
|
||
$this->activeBans = 0;
|
||
$this->jails = [];
|
||
|
||
$bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
if ($bin === '') {
|
||
$this->available = false;
|
||
return;
|
||
}
|
||
$this->available = true;
|
||
|
||
[, $ping] = $this->f2b('ping');
|
||
if ($this->looksDenied($ping)) {
|
||
$this->permDenied = true;
|
||
return;
|
||
}
|
||
|
||
[, $status] = $this->f2b('status');
|
||
if ($this->looksDenied($status)) {
|
||
$this->permDenied = true;
|
||
return;
|
||
}
|
||
if (!preg_match('/Jail list:\s*(.+)$/mi', $status, $mm)) {
|
||
$this->error = true;
|
||
Log::warning('Fail2BanCard: unexpected status output', ['status' => $status]);
|
||
return;
|
||
}
|
||
|
||
$jails = array_filter(array_map('trim', preg_split('/\s*,\s*/', $mm[1] ?? '')));
|
||
$sum = 0;
|
||
$rows = [];
|
||
|
||
foreach ($jails as $j) {
|
||
$jEsc = escapeshellarg($j);
|
||
[, $s] = $this->f2b("status {$jEsc}");
|
||
if ($this->looksDenied($s)) {
|
||
$this->permDenied = true;
|
||
return;
|
||
}
|
||
|
||
$banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
$bantime = $this->getBantime($j);
|
||
|
||
$rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime, 'ips' => []];
|
||
$sum += $banned;
|
||
}
|
||
|
||
$this->activeBans = $sum;
|
||
$this->jails = $rows;
|
||
}
|
||
|
||
private function f2b(string $args): array
|
||
{
|
||
$sudo = $this->bin('sudo');
|
||
$f2b = $this->bin('fail2ban-client');
|
||
$cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||
$out = (string)@shell_exec($cmd);
|
||
|
||
$ok = stripos($out, 'Status') !== false
|
||
|| stripos($out, 'Jail list') !== false
|
||
|| stripos($out, 'pong') !== false;
|
||
|
||
return [$ok, $out];
|
||
}
|
||
|
||
private function getBantime(string $jail): int
|
||
{
|
||
[, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||
if ($this->looksDenied($out)) {
|
||
$this->permDenied = true;
|
||
return 600;
|
||
}
|
||
if (preg_match('/-?\d+/', trim($out), $m)) return (int)$m[0];
|
||
return 600;
|
||
}
|
||
|
||
private function looksDenied(string $out): bool
|
||
{
|
||
return (bool)preg_match('/(permission denied|not allowed to execute|a password is required)/i', $out);
|
||
}
|
||
|
||
private function firstMatch(string $pattern, string $haystack): ?string
|
||
{
|
||
return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
}
|
||
|
||
private function bin(string $name): string
|
||
{
|
||
$p = trim((string)@shell_exec("command -v " . escapeshellarg($name) . " 2>/dev/null"));
|
||
return $p !== '' ? $p : $name;
|
||
}
|
||
}
|
||
|
||
//namespace App\Livewire\Ui\Security;
|
||
//
|
||
//use Illuminate\Support\Facades\Log;
|
||
//use Livewire\Attributes\On;
|
||
//use Livewire\Component;
|
||
//
|
||
//class Fail2BanCard extends Component
|
||
//{
|
||
// public bool $available = true; // fail2ban-client vorhanden?
|
||
// public bool $permDenied = false; // sudo / Socket-Rechte fehlen?
|
||
// public bool $error = false; // anderer Fehler (Output unerwartet)
|
||
// public int $activeBans = 0;
|
||
// public array $jails = []; // [['name','banned','bantime','ips'=>[...]]]
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.security.fail2-ban-card');
|
||
// }
|
||
//
|
||
// #[On('f2b:refresh-banlist')]
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// public function openDetails(string $jail): void
|
||
// {
|
||
// // wire-elements/modal (v2): Event-Namen + Component + Params
|
||
// $this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]);
|
||
// }
|
||
//
|
||
// /* ------------------- intern ------------------- */
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// $this->available = true;
|
||
// $this->permDenied = false;
|
||
// $this->error = false;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
//
|
||
// // existiert fail2ban-client?
|
||
// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
// if ($bin === '') {
|
||
// $this->available = false;
|
||
// return;
|
||
// }
|
||
//
|
||
// // Rechte / Erreichbarkeit
|
||
// [, $ping] = $this->f2b('ping');
|
||
// if ($this->looksDenied($ping)) {
|
||
// $this->permDenied = true;
|
||
// return;
|
||
// }
|
||
//
|
||
// // Jails lesen
|
||
// [, $status] = $this->f2b('status');
|
||
// if ($this->looksDenied($status)) {
|
||
// $this->permDenied = true;
|
||
// return;
|
||
// }
|
||
// if (!preg_match('/Jail list:\s*(.+)$/mi', $status, $mm)) {
|
||
// // etwas stimmt nicht – loggen und „error“ zeigen
|
||
// $this->error = true;
|
||
// Log::warning('Fail2BanCard: unexpected status output', ['status' => $status]);
|
||
// return;
|
||
// }
|
||
//
|
||
// $jails = array_filter(array_map('trim', preg_split('/\s*,\s*/', $mm[1] ?? '')));
|
||
// $sum = 0;
|
||
// $rows = [];
|
||
//
|
||
// foreach ($jails as $j) {
|
||
// $jEsc = escapeshellarg($j);
|
||
// [, $s] = $this->f2b("status {$jEsc}");
|
||
// if ($this->looksDenied($s)) {
|
||
// $this->permDenied = true;
|
||
// return;
|
||
// }
|
||
//
|
||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
// $bantime = $this->getBantime($j);
|
||
// $ipLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||
// $ips = $ipLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipLine)))) : [];
|
||
//
|
||
// $rows[] = [
|
||
// 'name' => $j,
|
||
// 'banned' => $banned,
|
||
// 'bantime' => $bantime,
|
||
// // wir zeigen IPs NICHT mehr in der Card; Details sind im Modal
|
||
// 'ips' => [],
|
||
// ];
|
||
// $sum += $banned;
|
||
// }
|
||
//
|
||
// $this->activeBans = $sum;
|
||
// $this->jails = $rows;
|
||
// }
|
||
//
|
||
// private function f2b(string $args): array
|
||
// {
|
||
// $sudo = '/usr/bin/sudo';
|
||
// $f2b = '/usr/bin/fail2ban-client';
|
||
// $cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||
// $out = (string)@shell_exec($cmd);
|
||
//
|
||
// $ok = stripos($out, 'Status') !== false
|
||
// || stripos($out, 'Jail list') !== false
|
||
// || stripos($out, 'pong') !== false;
|
||
//
|
||
// return [$ok, $out];
|
||
// }
|
||
//
|
||
// private function getBantime(string $jail): int
|
||
// {
|
||
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||
// if ($this->looksDenied($out)) {
|
||
// $this->permDenied = true;
|
||
// return 600;
|
||
// }
|
||
// $val = trim($out);
|
||
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
||
// return 600;
|
||
// }
|
||
//
|
||
// private function looksDenied(string $out): bool
|
||
// {
|
||
// return preg_match('/(permission denied|not allowed to execute|a password is required)/i', $out) === 1;
|
||
// }
|
||
//
|
||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||
// {
|
||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
// }
|
||
//}
|
||
|
||
//namespace App\Livewire\Ui\Security;
|
||
//
|
||
//use Livewire\Attributes\On;
|
||
//use Livewire\Component;
|
||
//
|
||
//class Fail2BanCard extends Component
|
||
//{
|
||
// public bool $available = true;
|
||
// public bool $permDenied = false;
|
||
// public int $activeBans = 0;
|
||
// public array $jails = [];
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.security.fail2-ban-card');
|
||
// }
|
||
//
|
||
// #[On('f2b:refresh-banlist')]
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// public function openDetails(string $jail): void
|
||
// {
|
||
// // KORREKTER DISPATCH für wire-elements/modal
|
||
// $this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]);
|
||
// }
|
||
//
|
||
// /* ------------------- intern ------------------- */
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
// if ($bin === '') {
|
||
// $this->available = false;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // Rechte prüfen
|
||
// [$ok, $raw] = $this->f2b('ping');
|
||
// if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||
// $this->available = true;
|
||
// $this->permDenied = true;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // Jail-Liste
|
||
// [, $status] = $this->f2b('status');
|
||
// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
|
||
// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
|
||
//
|
||
// $rows = [];
|
||
// $sum = 0;
|
||
//
|
||
// foreach ($jails as $j) {
|
||
// $jEsc = escapeshellarg($j);
|
||
// [, $s] = $this->f2b("status {$jEsc}");
|
||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
// $bantime = $this->getBantime($j);
|
||
// $ipListLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||
// $ips = $ipListLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipListLine)))) : [];
|
||
//
|
||
// // Details inkl. Restzeit je IP
|
||
// $ipDetails = $this->buildIpDetails($j, $ips, $bantime);
|
||
//
|
||
// $rows[] = [
|
||
// 'name' => $j,
|
||
// 'banned' => $banned,
|
||
// 'bantime' => $bantime, // Sek. (-1 = permanent)
|
||
// 'ips' => $ipDetails, // [['ip'=>..., 'remaining'=>..., 'until'=>...], ...]
|
||
// ];
|
||
// $sum += $banned;
|
||
// }
|
||
//
|
||
// $this->available = true;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = $sum;
|
||
// $this->jails = $rows;
|
||
// }
|
||
//
|
||
// private function f2b(string $args): array
|
||
// {
|
||
// $sudo = '/usr/bin/sudo';
|
||
// $f2b = '/usr/bin/fail2ban-client';
|
||
// $cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||
// $out = (string)@shell_exec($cmd);
|
||
//
|
||
// $ok = stripos($out, 'Status') !== false
|
||
// || stripos($out, 'Jail list') !== false
|
||
// || stripos($out, 'pong') !== false;
|
||
//
|
||
// return [$ok, $out];
|
||
// }
|
||
//
|
||
// /** konfig. Bantime des Jails in Sekunden (-1 = permanent) */
|
||
// private function getBantime(string $jail): int
|
||
// {
|
||
// [, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
||
// $val = trim($out);
|
||
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
||
// return 600; // konservativer Fallback
|
||
// }
|
||
//
|
||
// /** Letzten Ban-Zeitpunkt (Unix-Timestamp) aus /var/log/fail2ban.log ermitteln. */
|
||
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
||
// {
|
||
// $file = '/var/log/fail2ban.log';
|
||
// if (!is_readable($file)) return null;
|
||
//
|
||
// // nur das Ende der Datei lesen (Performance, auch bei Rotation groß genug wählen)
|
||
// $tailBytes = 400000; // 400 KB
|
||
// $size = @filesize($file) ?: 0;
|
||
// $seek = max(0, $size - $tailBytes);
|
||
//
|
||
// $fh = @fopen($file, 'rb');
|
||
// if (!$fh) return null;
|
||
// if ($seek > 0) fseek($fh, $seek);
|
||
// $data = stream_get_contents($fh) ?: '';
|
||
// fclose($fh);
|
||
//
|
||
// // Beispielzeile:
|
||
// // 2025-10-30 22:34:20,797 fail2ban.actions [...] NOTICE [sshd] Ban 193.46.255.244
|
||
// $j = preg_quote($jail, '/');
|
||
// $p = preg_quote($ip, '/');
|
||
// $pattern = '/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}),\d+.*\['.$j.'\]\s+Ban\s+'.$p.'\s*$/m';
|
||
//
|
||
// if (preg_match_all($pattern, $data, $m) && !empty($m[1])) {
|
||
// $date = end($m[1]); // YYYY-MM-DD
|
||
// $time = end($m[2]); // HH:MM:SS
|
||
// $dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time", new \DateTimeZone(date_default_timezone_get()));
|
||
// return $dt ? $dt->getTimestamp() : null;
|
||
// }
|
||
// return null;
|
||
// }
|
||
//
|
||
// /** Baut Details inkl. Restzeit (Sekunden; -1 = permanent). */
|
||
// private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||
// {
|
||
// $now = time();
|
||
// $out = [];
|
||
//
|
||
// foreach ($ips as $ip) {
|
||
// $banAt = $this->lastBanTimestamp($jail, $ip);
|
||
// $remaining = null;
|
||
// $until = null;
|
||
//
|
||
// if ($bantime === -1) {
|
||
// $remaining = -1; // permanent
|
||
// } elseif ($banAt !== null) {
|
||
// $remaining = max(0, $bantime - ($now - $banAt));
|
||
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||
// }
|
||
//
|
||
// $out[] = [
|
||
// 'ip' => $ip,
|
||
// 'remaining' => $remaining, // -1 = permanent, null = Ban-Zeitpunkt nicht gefunden, >=0 = Sekunden
|
||
// 'until' => $until, // Unix-Timestamp oder null
|
||
// ];
|
||
// }
|
||
// return $out;
|
||
// }
|
||
//
|
||
//
|
||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||
// {
|
||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
// }
|
||
//}
|
||
|
||
|
||
//namespace App\Livewire\Ui\Security;
|
||
//
|
||
//use Livewire\Component;
|
||
//
|
||
//class Fail2BanCard extends Component
|
||
//{
|
||
// public bool $available = true; // fail2ban-client vorhanden?
|
||
// public bool $permDenied = false; // Socket/Root-Rechte fehlen?
|
||
// public int $activeBans = 0; // Summe gebannter IPs
|
||
// /** @var array<int,array{name:string,banned:int,bantime:int}> */
|
||
// public array $jails = [];
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.security.fail2-ban-card');
|
||
// }
|
||
//
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// // Optional: öffnet später dein Detail-Modal/Tab
|
||
// public function openDetails(string $jail): void
|
||
// {
|
||
// $this->dispatch('openModal', 'ui.security.modal.fail2-ban-jail-modal', ['jail' => $jail]);
|
||
// }
|
||
// /* ---------------- intern ---------------- */
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
// if ($bin === '') {
|
||
// $this->available = false;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // Rechtecheck
|
||
// [$ok, $raw] = $this->f2b('ping');
|
||
// if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||
// $this->available = true;
|
||
// $this->permDenied = true;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // Jails laden
|
||
// [, $status] = $this->f2b('status');
|
||
// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
|
||
// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
|
||
//
|
||
// $rows = [];
|
||
// $sum = 0;
|
||
//
|
||
// foreach ($jails as $j) {
|
||
// [, $s] = $this->f2b('status ' . escapeshellarg($j));
|
||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
// $bantime = $this->getBantime($j); // Sek.; -1 = permanent
|
||
// $rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime];
|
||
// $sum += $banned;
|
||
// }
|
||
//
|
||
// $this->available = true;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = $sum;
|
||
// $this->jails = $rows;
|
||
// }
|
||
//
|
||
// /** sudo + fail2ban-client ausführen; [ok, output] */
|
||
// private function f2b(string $args): array
|
||
// {
|
||
// $sudo = '/usr/bin/sudo';
|
||
// $f2b = '/usr/bin/fail2ban-client';
|
||
// $out = (string)@shell_exec("timeout 2 $sudo -n $f2b $args 2>&1");
|
||
// $ok = stripos($out, 'Status') !== false
|
||
// || stripos($out, 'Jail list') !== false
|
||
// || stripos($out, 'pong') !== false;
|
||
// return [$ok, $out];
|
||
// }
|
||
//
|
||
// private function getBantime(string $jail): int
|
||
// {
|
||
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||
// $val = trim($out);
|
||
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
||
// return 600; // defensiver Default
|
||
// }
|
||
//
|
||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||
// {
|
||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
// }
|
||
//}
|
||
|
||
|
||
//namespace App\Livewire\Ui\Security;
|
||
//
|
||
//use Livewire\Component;
|
||
//
|
||
//class Fail2BanCard extends Component
|
||
//{
|
||
// public bool $available = true; // fail2ban-client vorhanden?
|
||
// public bool $permDenied = false; // Socket/Root-Rechte fehlen?
|
||
// public int $activeBans = 0; // Summe gebannter IPs über alle Jails
|
||
// public array $jails = []; // [['name','banned','bantime','ips'=>[['ip','remaining','until'],...]],...]
|
||
// public array $topIps = []; // [['ip'=>'x.x.x.x','count'=>N], ...]
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.security.fail2-ban-card');
|
||
// }
|
||
//
|
||
// /** Button „Neu prüfen“ */
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// /* --------------------- intern --------------------- */
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// // existiert fail2ban-client?
|
||
// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
// if ($bin === '') {
|
||
// $this->available = false;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// $this->topIps = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // ping → prüft zugleich Rechte (bei Permission-Fehler kommt Klartext)
|
||
// [$ok, $raw] = $this->f2b('ping'); // ok == "pong" erkannt
|
||
// if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||
// $this->available = true;
|
||
// $this->permDenied = true;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// $this->topIps = $this->collectTopIps();
|
||
// return;
|
||
// }
|
||
//
|
||
// // Jails auflisten
|
||
// [, $status] = $this->f2b('status');
|
||
// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
|
||
// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
|
||
//
|
||
// $total = 0;
|
||
// $rows = [];
|
||
//
|
||
// foreach ($jails as $j) {
|
||
// $bantimeSecs = $this->getBantime($j); // Sek., -1 = permanent
|
||
//
|
||
// [, $s] = $this->f2b('status ' . escapeshellarg($j));
|
||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
||
//
|
||
// // Restzeiten je IP bestimmen (aus /var/log/fail2ban.log)
|
||
// $ipDetails = [];
|
||
// foreach (array_slice($ips, 0, 50) as $ip) {
|
||
// $banAt = $this->lastBanTimestamp($j, $ip); // Unix-Timestamp oder null
|
||
// $remaining = null;
|
||
// $until = null;
|
||
//
|
||
// if ($banAt !== null) {
|
||
// if ((int)$bantimeSecs === -1) {
|
||
// $remaining = -1; // permanent
|
||
// } else {
|
||
// $remaining = max(0, $bantimeSecs - (time() - $banAt));
|
||
// $until = $remaining > 0 ? ($banAt + $bantimeSecs) : null;
|
||
// }
|
||
// }
|
||
//
|
||
// $ipDetails[] = [
|
||
// 'ip' => $ip,
|
||
// 'remaining' => $remaining, // -1 = permanent, 0 = abgelaufen, >0 Sek.
|
||
// 'until' => $until, // Unix-Timestamp oder null
|
||
// ];
|
||
// }
|
||
//
|
||
// $rows[] = [
|
||
// 'name' => $j,
|
||
// 'banned' => $banned,
|
||
// 'ips' => $ipDetails,
|
||
// 'bantime' => (int)$bantimeSecs,
|
||
// ];
|
||
// $total += $banned;
|
||
// }
|
||
//
|
||
// $this->available = true;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = $total;
|
||
// $this->jails = $rows;
|
||
// $this->topIps = $this->collectTopIps();
|
||
// }
|
||
//
|
||
// /** führt fail2ban-client via sudo aus; gibt [ok, output] zurück */
|
||
// private function f2b(string $args): array
|
||
// {
|
||
// $sudo = '/usr/bin/sudo';
|
||
// $f2b = '/usr/bin/fail2ban-client';
|
||
// $cmd = "timeout 2 $sudo -n $f2b $args 2>&1";
|
||
// $out = (string)@shell_exec($cmd);
|
||
//
|
||
// $ok = stripos($out, 'Status') !== false
|
||
// || stripos($out, 'Jail list') !== false
|
||
// || stripos($out, 'pong') !== false;
|
||
//
|
||
// return [$ok, $out];
|
||
// }
|
||
//
|
||
// private function getBantime(string $jail): int
|
||
// {
|
||
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||
// $val = trim($out);
|
||
// if (preg_match('/-?\d+/', $val, $m)) {
|
||
// return (int)$m[0];
|
||
// }
|
||
// return 600; // defensiver Default
|
||
// }
|
||
//
|
||
// /** letzte Ban-Zeile aus /var/log/fail2ban.log → Unix-Timestamp */
|
||
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
||
// {
|
||
// $cmd = "grep -F \"[{$jail}] Ban {$ip}\" /var/log/fail2ban.log 2>/dev/null | tail -n 1";
|
||
// $line = trim((string)@shell_exec($cmd));
|
||
// if ($line === '') return null;
|
||
//
|
||
// // "YYYY-MM-DD HH:MM:SS,mmm ..."
|
||
// if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||
// $ts = strtotime($m[1] . ' ' . $m[2]);
|
||
// return $ts ?: null;
|
||
// }
|
||
// return null;
|
||
// }
|
||
//
|
||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||
// {
|
||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
// }
|
||
//
|
||
// /** Top-IPs grob zählen (aus der aktuellen Jail-Liste; Fallback: Log) */
|
||
// private function collectTopIps(): array
|
||
// {
|
||
// $map = [];
|
||
// foreach ($this->jails as $jail) {
|
||
// foreach ($jail['ips'] as $row) {
|
||
// $ip = $row['ip'] ?? null;
|
||
// if (!$ip) continue;
|
||
// $map[$ip] = ($map[$ip] ?? 0) + 1;
|
||
// }
|
||
// }
|
||
//
|
||
// if (!empty($map)) {
|
||
// arsort($map);
|
||
// $out = [];
|
||
// foreach (array_slice($map, 0, 5, true) as $ip => $count) {
|
||
// $out[] = ['ip' => $ip, 'count' => $count];
|
||
// }
|
||
// return $out;
|
||
// }
|
||
//
|
||
// // Fallback: aus fail2ban.log
|
||
// $cmd = 'grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/fail2ban.log 2>/dev/null'
|
||
// . ' | sort | uniq -c | sort -nr | head -5';
|
||
// $log = (string)@shell_exec($cmd);
|
||
// $rows = [];
|
||
// if ($log !== '') {
|
||
// foreach (preg_split('/\R+/', trim($log)) as $l) {
|
||
// if (preg_match('/^\s*(\d+)\s+(\d+\.\d+\.\d+\.\d+)/', $l, $m)) {
|
||
// $rows[] = ['ip' => $m[2], 'count' => (int)$m[1]];
|
||
// }
|
||
// }
|
||
// }
|
||
// return $rows;
|
||
// }
|
||
//}
|
||
|
||
//
|
||
//namespace App\Livewire\Ui\Security;
|
||
//
|
||
//use Livewire\Component;
|
||
//
|
||
//class Fail2BanCard extends Component
|
||
//{
|
||
// public bool $available = true; // fail2ban-client vorhanden?
|
||
// public bool $permDenied = false; // Socket/Root-Rechte fehlen?
|
||
// public int $activeBans = 0; // Summe gebannter IPs über alle Jails
|
||
// public array $jails = []; // [['name'=>'sshd','banned'=>2,'ips'=>['1.2.3.4',...]], ...]
|
||
// public array $topIps = []; // [['ip'=>'x.x.x.x','count'=>N], ...]
|
||
//
|
||
// public function mount(): void
|
||
// {
|
||
// $this->load();
|
||
// }
|
||
//
|
||
// public function render()
|
||
// {
|
||
// return view('livewire.ui.security.fail2-ban-card');
|
||
// }
|
||
//
|
||
// // Wird vom Button "Neu prüfen" genutzt
|
||
// public function refresh(): void
|
||
// {
|
||
// $this->load(true);
|
||
// }
|
||
//
|
||
// /* --------------------- intern --------------------- */
|
||
//
|
||
// protected function load(bool $force = false): void
|
||
// {
|
||
// // existiert fail2ban-client?
|
||
// $bin = trim((string) @shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
|
||
// if ($bin === '') {
|
||
// $this->available = false;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// $this->topIps = [];
|
||
// return;
|
||
// }
|
||
//
|
||
// // ping → prüft zugleich Rechte (bei Permission-Fehler kommt Klartext)
|
||
// [$ok, $raw] = $this->f2b('ping'); // ok == "pong" erkannt
|
||
// if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||
// $this->available = true;
|
||
// $this->permDenied = true;
|
||
// $this->activeBans = 0;
|
||
// $this->jails = [];
|
||
// $this->topIps = $this->collectTopIps();
|
||
// return;
|
||
// }
|
||
//
|
||
// // Jails auflisten
|
||
// [, $status] = $this->f2b('status');
|
||
// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
|
||
// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
|
||
//
|
||
// $total = 0; $rows = [];
|
||
//// ... in load() NACH dem Einlesen der Jail-Liste:
|
||
// $rows = [];
|
||
// foreach ($jails as $j) {
|
||
// $bantimeSecs = $this->getBantime($j); // konfigurierter Wert (Sekunden, -1 = permanent)
|
||
//
|
||
// [, $s] = $this->f2b('status '.escapeshellarg($j));
|
||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
||
//
|
||
// // Restzeiten je IP bestimmen (aus /var/log/fail2ban.log)
|
||
// $ipDetails = [];
|
||
// foreach (array_slice($ips, 0, 50) as $ip) {
|
||
// $banAt = $this->lastBanTimestamp($j, $ip); // Unix-Timestamp oder null
|
||
// $remaining = null;
|
||
// $until = null;
|
||
//
|
||
// if ($banAt !== null) {
|
||
// if ((int)$bantimeSecs === -1) {
|
||
// $remaining = -1; // permanent
|
||
// } else {
|
||
// $remaining = max(0, $bantimeSecs - (time() - $banAt));
|
||
// $until = $remaining > 0 ? ($banAt + $bantimeSecs) : null;
|
||
// }
|
||
// }
|
||
//
|
||
// $ipDetails[] = [
|
||
// 'ip' => $ip,
|
||
// 'remaining' => $remaining, // -1 = permanent, 0 = abgelaufen, >0 Sek.
|
||
// 'until' => $until, // Unix-Timestamp oder null
|
||
// ];
|
||
// }
|
||
//
|
||
// $rows[] = [
|
||
// 'name' => $j,
|
||
// 'banned' => $banned,
|
||
// 'ips' => $ipDetails, // jetzt mit Details
|
||
// 'bantime' => (int)$bantimeSecs,
|
||
// ];
|
||
// $total += $banned;
|
||
// }
|
||
//
|
||
// // foreach ($jails as $j) {
|
||
//// [, $s] = $this->f2b('status '.escapeshellarg($j));
|
||
//// $banned = (int) ($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||
//// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||
//// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
||
//// $rows[] = ['name'=>$j,'banned'=>$banned,'ips'=>array_slice($ips, 0, 8)];
|
||
//// $total += $banned;
|
||
//// }
|
||
//
|
||
//
|
||
//
|
||
// $this->available = true;
|
||
// $this->permDenied = false;
|
||
// $this->activeBans = $total;
|
||
// $this->jails = $rows;
|
||
// $this->topIps = $this->collectTopIps();
|
||
// }
|
||
//
|
||
// /** führt fail2ban-client via sudo aus; gibt [ok, output] zurück */
|
||
// private function f2b(string $args): array
|
||
// {
|
||
// $sudo = '/usr/bin/sudo';
|
||
// $f2b = '/usr/bin/fail2ban-client';
|
||
// $cmd = "timeout 2 $sudo -n $f2b $args 2>&1";
|
||
// $out = (string) @shell_exec($cmd);
|
||
//
|
||
// $ok = stripos($out, 'Status') !== false
|
||
// || stripos($out, 'Jail list') !== false
|
||
// || stripos($out, 'pong') !== false;
|
||
//
|
||
// return [$ok, $out];
|
||
// }
|
||
//
|
||
// private function getBantime(string $jail): int
|
||
// {
|
||
// [, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
||
// // fail2ban liefert Seconds als Zahl (oder mit Newline)
|
||
// $val = trim($out);
|
||
// // Fallback: manche Versionen geben nur Zahl ohne Kontext zurück,
|
||
// // sonst aus jail.local ermitteln wäre overkill -> einfache Zahl extrahieren:
|
||
// if (preg_match('/-?\d+/', $val, $m)) {
|
||
// return (int)$m[0];
|
||
// }
|
||
// // wenn nicht ermittelbar: 600 Sekunden als conservative default
|
||
// return 600;
|
||
// }
|
||
//
|
||
// /** Sucht die letzte "Ban <IP>"-Zeile für Jail in /var/log/fail2ban.log und gibt Unix-Timestamp zurück. */
|
||
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
||
// {
|
||
// // Beispiel-Logzeilen:
|
||
// // 2025-10-29 18:07:11,436 fail2ban.actions [12345]: NOTICE [sshd] Ban 1.2.3.4
|
||
// // Wir holen die letzte passende Zeile (tail mit grep), dann parsen Datum.
|
||
// $pattern = escapeshellarg(sprintf('\\[%s\\] Ban %s', $jail, $ip));
|
||
// $cmd = "grep -F \"[{$jail}] Ban {$ip}\" /var/log/fail2ban.log 2>/dev/null | tail -n 1";
|
||
// $line = (string)@shell_exec($cmd);
|
||
// $line = trim($line);
|
||
// if ($line === '') {
|
||
// return null;
|
||
// }
|
||
// // Datumsformat am Anfang: "YYYY-MM-DD HH:MM:SS,mmm"
|
||
// if (preg_match('/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||
// $ts = strtotime($m[1].' '.$m[2]);
|
||
// return $ts ?: null;
|
||
// }
|
||
// return null;
|
||
// }
|
||
//
|
||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||
// {
|
||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||
// }
|
||
//
|
||
// /** Zählt die häufigsten IPs aus den letzten Fail2Ban-Logs (ban/unban Events) */
|
||
// private function collectTopIps(): array
|
||
// {
|
||
// // 1. Versuch: IPs direkt aus den Jails
|
||
// $rows = [];
|
||
// foreach ($this->jails as $jail) {
|
||
// foreach ($jail['ips'] as $ip) {
|
||
// $rows[$ip] = ($rows[$ip] ?? 0) + 1;
|
||
// }
|
||
// }
|
||
//
|
||
// if (!empty($rows)) {
|
||
// arsort($rows);
|
||
// return collect($rows)
|
||
// ->map(fn($count, $ip) => ['ip' => $ip, 'count' => $count])
|
||
// ->values()
|
||
// ->take(5)
|
||
// ->toArray();
|
||
// }
|
||
//
|
||
// // 2. Fallback: Falls keine Jails/IPs → Logdatei
|
||
// $cmd = 'grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" /var/log/fail2ban.log 2>/dev/null'
|
||
// . ' | sort | uniq -c | sort -nr | head -5';
|
||
// $log = (string) @shell_exec($cmd);
|
||
//
|
||
// $rows = [];
|
||
// if ($log !== '') {
|
||
// foreach (preg_split('/\R+/', trim($log)) as $l) {
|
||
// if (preg_match('/^\s*(\d+)\s+(\d+\.\d+\.\d+\.\d+)/', $l, $m)) {
|
||
// $rows[] = ['ip'=>$m[2],'count'=>(int)$m[1]];
|
||
// }
|
||
// }
|
||
// }
|
||
// return $rows;
|
||
// }
|
||
//}
|