diff --git a/, b/, deleted file mode 100644 index e69de29..0000000 diff --git a/0 b/0 deleted file mode 100644 index e69de29..0000000 diff --git a/Setting::get('woltguard.services') b/Setting::get('woltguard.services') deleted file mode 100644 index 457148f..0000000 --- a/Setting::get('woltguard.services') +++ /dev/null @@ -1,9 +0,0 @@ -= App\Models\Setting {#6409 - id: 13, - group: "woltguard", - key: "services", - value: "{"ts":1761504019,"rows":[{"name":"postfix","ok":true},{"name":"dovecot","ok":true},{"name":"rspamd","ok":true},{"name":"clamav","ok":true},{"name":"db","ok":true},{"name":"redis","ok":true},{"name":"php-fpm","ok":true},{"name":"nginx","ok":true},{"name":"mw-queue","ok":true},{"name":"mw-schedule","ok":true},{"name":"mw-ws","ok":true},{"name":"fail2ban","ok":true},{"name":"journal","ok":true}]}", - created_at: "2025-10-26 19:40:19", - updated_at: "2025-10-26 19:40:19", - } - diff --git a/[hint], b/[hint], deleted file mode 100644 index e69de29..0000000 diff --git a/[label], b/[label], deleted file mode 100644 index e69de29..0000000 diff --git a/app/Livewire/Ui/Security/Fail2banSettings.php b/app/Livewire/Ui/Security/Fail2banSettings.php new file mode 100644 index 0000000..16a36dd --- /dev/null +++ b/app/Livewire/Ui/Security/Fail2banSettings.php @@ -0,0 +1,124 @@ +whitelist = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray(); + $this->blacklist = Fail2banIpList::where('type', 'blacklist')->pluck('ip')->toArray(); + } + + public function mount(): void + { + // Setting holen oder mit Defaults anlegen + $this->settings = Fail2banSetting::first() ?? Fail2banSetting::create([ + 'bantime' => 3600, 'max_bantime' => 43200, 'bantime_increment' => true, + 'bantime_factor' => 1.5, 'max_retry' => 3, 'findtime' => 600, + 'cidr_v4' => 32, 'cidr_v6' => 128, 'external_mode' => false, + ]); + + // Properties füllen (KEINE Mixed-Objekte in Inputs binden) + $this->fill([ + 'bantime' => (int)$this->settings->bantime, + 'max_bantime' => (int)$this->settings->max_bantime, + 'bantime_increment' => (bool)$this->settings->bantime_increment, + 'bantime_factor' => (float)$this->settings->bantime_factor, + 'max_retry' => (int)$this->settings->max_retry, + 'findtime' => (int)$this->settings->findtime, + 'cidr_v4' => (int)$this->settings->cidr_v4, + 'cidr_v6' => (int)$this->settings->cidr_v6, + 'external_mode' => (bool)$this->settings->external_mode, + ]); + + $this->whitelist = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); + $this->blacklist = Fail2banIpList::where('type','blacklist')->pluck('ip')->toArray(); + } + + public function save(): void + { + $this->validate([ + 'bantime' => 'required|integer|min:60', + 'max_bantime' => 'required|integer|min:60', + 'bantime_factor' => 'required|numeric|min:1', + 'max_retry' => 'required|integer|min:1', + 'findtime' => 'required|integer|min:60', + 'cidr_v4' => 'required|integer|min:8|max:32', + 'cidr_v6' => 'required|integer|min:8|max:128', + ]); + + $this->settings->update([ + 'bantime' => $this->bantime, + 'max_bantime' => $this->max_bantime, + 'bantime_increment' => $this->bantime_increment, + 'bantime_factor' => $this->bantime_factor, + 'max_retry' => $this->max_retry, + 'findtime' => $this->findtime, + 'cidr_v4' => $this->cidr_v4, + 'cidr_v6' => $this->cidr_v6, + 'external_mode' => $this->external_mode, + ]); + + $this->writeDefaultsConfig(); + $this->writeWhitelistConfig(); + + @shell_exec('sudo fail2ban-client reload'); + $this->dispatch('notify', message: 'Gespeichert & Fail2Ban neu geladen.'); + } + + protected function writeDefaultsConfig(): void + { + $s = $this->settings; + $content = <<bantime} +findtime = {$s->findtime} +maxretry = {$s->max_retry} +bantime.increment = {$this->boolToStr($s->bantime_increment)} +bantime.factor = {$s->bantime_factor} +bantime.maxtime = {$s->max_bantime} +CONF; + file_put_contents('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content); + } + + protected function writeWhitelistConfig(): void + { + $ips = Fail2banIpList::where('type','whitelist')->pluck('ip')->toArray(); + $ignore = implode(' ', array_unique(array_filter($ips))); + $content = "[DEFAULT]\nignoreip = {$ignore}\n"; + file_put_contents('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content); + } + + private function boolToStr(bool $v): string + { + return $v ? 'true' : 'false'; + } + + public function render() + { + return view('livewire.ui.security.fail2ban-settings'); + } +} diff --git a/app/Livewire/Ui/Security/Modal/Fail2banIpModal.php b/app/Livewire/Ui/Security/Modal/Fail2banIpModal.php new file mode 100644 index 0000000..3fcc01f --- /dev/null +++ b/app/Livewire/Ui/Security/Modal/Fail2banIpModal.php @@ -0,0 +1,160 @@ +type = $type; + $this->mode = $mode; + $this->ip = $ip ?? ''; + $this->prefill = $ip; + } + + public function render() + { + return view('livewire.ui.security.modal.fail2ban-ip-modal'); + } + + /* ---------------- actions ---------------- */ + + public function save(): void + { + $this->assertAddMode(); + $ip = trim($this->ip); + + if (!$this->isValidIpOrCidr($ip)) { + throw ValidationException::withMessages(['ip' => 'Ungültige IP oder CIDR.']); + } + + // DB schreiben + Fail2banIpList::firstOrCreate(['ip' => $ip, 'type' => $this->type]); + + if ($this->type === 'whitelist') { + $this->writeWhitelistConfig(); + $this->reloadFail2ban(); + } else { + // Blacklist = sofort bannen im dedizierten Jail + $this->banIp($ip); + } + + $this->dispatch('f2b:refresh'); + $this->dispatch('notify', message: ucfirst($this->type).' aktualisiert.'); + $this->closeModal(); + $this->dispatch('f2b:refresh'); // falls du eine Liste neu laden willst + } + + public function remove(): void + { + $this->assertRemoveMode(); + $ip = trim($this->prefill ?? $this->ip); + + if ($ip === '') return; + + Fail2banIpList::where('type', $this->type)->where('ip', $ip)->delete(); + + if ($this->type === 'whitelist') { + $this->writeWhitelistConfig(); + $this->reloadFail2ban(); + } else { + // aus Blacklist-Jail entbannen, falls noch aktiv + $this->unbanIp($ip); + } + + $this->dispatch('f2b:refresh'); + $this->dispatch('notify', message: ucfirst($this->type).' Eintrag entfernt.'); + $this->closeModal(); + $this->dispatch('f2b:refresh'); + } + + /* ---------------- helper ---------------- */ + + private function assertAddMode(): void + { + if ($this->mode !== 'add') throw new \LogicException('Wrong mode'); + } + + private function assertRemoveMode(): void + { + if ($this->mode !== 'remove') throw new \LogicException('Wrong mode'); + } + + private function isValidIpOrCidr(string $s): bool + { + // IP + if (filter_var($s, FILTER_VALIDATE_IP)) return true; + + // CIDR + if (strpos($s, '/') !== false) { + [$ip, $mask] = explode('/', $s, 2); + if (!filter_var($ip, FILTER_VALIDATE_IP)) return false; + if (strpos($ip, ':') !== false) { + // IPv6 + return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 128; + } + // IPv4 + return ctype_digit($mask) && (int)$mask >= 8 && (int)$mask <= 32; + } + return false; + } + + private function writeWhitelistConfig(): void + { + $ips = Fail2banIpList::where('type', 'whitelist')->pluck('ip')->toArray(); + $ignore = implode(' ', array_unique(array_filter($ips))); + $content = "[DEFAULT]\nignoreip = {$ignore}\n"; + + $file = '/etc/fail2ban/jail.d/mailwolt-whitelist.local'; + $tmp = $file.'.tmp'; + @file_put_contents($tmp, $content, LOCK_EX); + @chmod($tmp, 0644); + @rename($tmp, $file); + } + + private function reloadFail2ban(): void + { + @shell_exec('sudo fail2ban-client reload 2>&1'); + } + + private function banIp(string $ip): void + { + $ipEsc = escapeshellarg($ip); + @shell_exec("sudo fail2ban-client set mailwolt-blacklist banip {$ipEsc} 2>&1"); + // optional: in DB zusätzlich behalten, damit UI konsistent ist (bereits oben getan) + } + + private function unbanIp(string $ip): void + { + $ipEsc = escapeshellarg($ip); + @shell_exec("sudo fail2ban-client set mailwolt-blacklist unbanip {$ipEsc} 2>&1"); + } +} diff --git a/app/Models/Fail2banIpList.php b/app/Models/Fail2banIpList.php new file mode 100644 index 0000000..9393730 --- /dev/null +++ b/app/Models/Fail2banIpList.php @@ -0,0 +1,60 @@ + 'string', + 'type' => 'string', + ]; + + const TYPE_WHITELIST = 'whitelist'; + const TYPE_BLACKLIST = 'blacklist'; + + /** + * Scopes + */ + public function scopeWhitelist($query) + { + return $query->where('type', self::TYPE_WHITELIST); + } + + public function scopeBlacklist($query) + { + return $query->where('type', self::TYPE_BLACKLIST); + } + + /** + * Validiert grob die IP. + */ + public function isValidIp(): bool + { + return filter_var($this->ip, FILTER_VALIDATE_IP) !== false; + } + + /** + * Gibt Liste aller Whitelist-IPs als Array zurück. + */ + public static function whitelistArray(): array + { + return static::where('type', self::TYPE_WHITELIST)->pluck('ip')->all(); + } + + /** + * Gibt Liste aller Blacklist-IPs als Array zurück. + */ + public static function blacklistArray(): array + { + return static::where('type', self::TYPE_BLACKLIST)->pluck('ip')->all(); + } +} diff --git a/app/Models/Fail2banSetting.php b/app/Models/Fail2banSetting.php new file mode 100644 index 0000000..106e13a --- /dev/null +++ b/app/Models/Fail2banSetting.php @@ -0,0 +1,51 @@ + 'integer', + 'max_bantime' => 'integer', + 'bantime_increment' => 'boolean', + 'bantime_factor' => 'float', + 'max_retry' => 'integer', + 'findtime' => 'integer', + 'cidr_v4' => 'integer', + 'cidr_v6' => 'integer', + 'external_mode' => 'boolean', + ]; + +// /** +// * Gibt die erste Konfiguration oder Default-Werte zurück. +// */ +// public static function current(): self +// { +// return static::first() ?? new static([ +// 'bantime' => 3600, +// 'max_bantime' => 43200, +// 'bantime_increment' => true, +// 'bantime_factor' => 1.5, +// 'max_retry' => 5, +// 'findtime' => 600, +// 'cidr_v4' => 32, +// 'cidr_v6' => 128, +// 'external_mode' => false, +// ]); +// } + + /** + * Konvertiert bool zu "true"/"false" (für Config-Dateien). + */ + public function boolToString(bool $val): string + { + return $val ? 'true' : 'false'; + } +} diff --git a/badgeClass b/badgeClass deleted file mode 100644 index e69de29..0000000 diff --git a/badgeIcon b/badgeIcon deleted file mode 100644 index e69de29..0000000 diff --git a/badgeText b/badgeText deleted file mode 100644 index e69de29..0000000 diff --git a/database/migrations/2025_10_31_150406_create_fail2ban_settings_table.php b/database/migrations/2025_10_31_150406_create_fail2ban_settings_table.php new file mode 100644 index 0000000..ac7cee6 --- /dev/null +++ b/database/migrations/2025_10_31_150406_create_fail2ban_settings_table.php @@ -0,0 +1,36 @@ +id(); + $table->integer('bantime')->default(3600); + $table->integer('max_bantime')->default(43200); + $table->boolean('bantime_increment')->default(true); + $table->float('bantime_factor')->default(1.5); + $table->integer('max_retry')->default(3); + $table->integer('findtime')->default(600); + $table->integer('cidr_v4')->default(32); + $table->integer('cidr_v6')->default(128); + $table->boolean('external_mode')->default(false); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fail2ban_settings'); + } +}; diff --git a/database/migrations/2025_10_31_150451_create_fail2ban_ip_lists_table.php b/database/migrations/2025_10_31_150451_create_fail2ban_ip_lists_table.php new file mode 100644 index 0000000..54e85dc --- /dev/null +++ b/database/migrations/2025_10_31_150451_create_fail2ban_ip_lists_table.php @@ -0,0 +1,29 @@ +id(); + $table->string('ip'); + $table->enum('type', ['whitelist', 'blacklist']); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('fail2ban_ip_lists'); + } +}; diff --git a/database/seeders/Fail2banSeeder.php b/database/seeders/Fail2banSeeder.php new file mode 100644 index 0000000..0d40a31 --- /dev/null +++ b/database/seeders/Fail2banSeeder.php @@ -0,0 +1,109 @@ +command->info('⚙️ Fail2ban Defaults werden initialisiert …'); + + // ----------------------------------------------------------- + // 1) Standardwerte für Fail2ban Settings + // ----------------------------------------------------------- + $settings = Fail2banSetting::firstOrCreate([], [ + 'bantime' => 3600, // 1h + 'max_bantime' => 43200, // 12h + 'bantime_increment' => true, + 'bantime_factor' => 1.5, + 'max_retry' => 5, + 'findtime' => 600, // 10m + 'cidr_v4' => 32, + 'cidr_v6' => 128, + 'external_mode' => false, + ]); + + // ----------------------------------------------------------- + // 2) Standard-IPs für Whitelist + // ----------------------------------------------------------- + $defaultWhitelist = [ + '127.0.0.1/8', + '::1', + ]; + + foreach ($defaultWhitelist as $ip) { + Fail2banIpList::firstOrCreate([ + 'ip' => $ip, + 'type' => Fail2banIpList::TYPE_WHITELIST, + ]); + } + + // ----------------------------------------------------------- + // 3) Fail2ban Config-Dateien erzeugen + // ----------------------------------------------------------- + $this->writeDefaultsConfig($settings); + $this->writeWhitelistConfig(); + + // ----------------------------------------------------------- + // 4) Fail2ban reload (optional, falls Dienst läuft) + // ----------------------------------------------------------- + $out = shell_exec('sudo -n fail2ban-client reload 2>&1') ?? ''; + if (stripos($out, 'OK') === false && stripos($out, 'Reloaded') === false) { + Log::warning('Fail2ban reload output', ['out' => $out]); + $this->command->warn('⚠️ Fail2ban reload möglicherweise nicht erfolgreich.'); + } else { + $this->command->info('✅ Fail2ban reload erfolgreich.'); + } + } + + // ----------------------------------------------------------- + // interne Hilfsfunktionen + // ----------------------------------------------------------- + + private function writeDefaultsConfig(Fail2banSetting $s): void + { + $content = <<bantime} +findtime = {$s->findtime} +maxretry = {$s->max_retry} +bantime.increment = {$this->boolToString($s->bantime_increment)} +bantime.factor = {$s->bantime_factor} +bantime.maxtime = {$s->max_bantime} +CONF; + + $this->atomicWrite('/etc/fail2ban/jail.d/00-mailwolt-defaults.local', $content); + } + + private function writeWhitelistConfig(): void + { + $ips = Fail2banIpList::where('type', Fail2banIpList::TYPE_WHITELIST) + ->pluck('ip') + ->toArray(); + + $ignore = implode(' ', array_unique(array_filter($ips))); + $content = "[DEFAULT]\nignoreip = {$ignore}\n"; + + $this->atomicWrite('/etc/fail2ban/jail.d/mailwolt-whitelist.local', $content); + } + + private function atomicWrite(string $path, string $content): void + { + $tmp = $path . '.tmp'; + file_put_contents($tmp, $content); + rename($tmp, $path); + @chown($path, 'root'); + @chgrp($path, 'root'); + @chmod($path, 0644); + } + + private function boolToString(bool $v): string + { + return $v ? 'true' : 'false'; + } +} diff --git a/guardOk b/guardOk deleted file mode 100644 index e69de29..0000000 diff --git a/okCount b/okCount deleted file mode 100644 index e69de29..0000000 diff --git a/resources/views/livewire/ui/security/fail2ban-settings.blade.php b/resources/views/livewire/ui/security/fail2ban-settings.blade.php new file mode 100644 index 0000000..0c74f66 --- /dev/null +++ b/resources/views/livewire/ui/security/fail2ban-settings.blade.php @@ -0,0 +1,130 @@ +
+ {{-- LEFT 2/3 --}} +
+
+
+
+ + Fail2Ban Konfiguration +
+ +
+ +
+
+ + +

Standard-Sperrzeit.

+
+ +
+ + +

Obergrenze bei dynamischer Erhöhung.

+
+ +
+ + +

Zeitraum für Wiederholungen.

+
+ +
+ + +

Fehlversuche bis Bann.

+
+ +
+ +
+ +
+ + +

Multiplikator (z. B. 1.5).

+
+
+
+ +
+
+ + Hinweise +
+ +
    +
  • bantime.increment = true bedeutet, dass sich die Sperrzeit bei wiederholten + Angriffen erhöht (z. B. 1 h → 1.5 h → 2.25 h …). +
  • +
  • Die SQLite-Datenbank befindet sich unter /var/lib/fail2ban/fail2ban.sqlite3.
  • +
  • Alle Änderungen hier werden nach Klick auf „Speichern & Reload“ sofort aktiv.
  • +
+
+
+ + {{-- RIGHT 1/3 --}} +
+
+
+ + Whitelist +
+ + @forelse($whitelist as $ip) +
+ {{ $ip }} + +
+ @empty +
Keine Einträge.
+ @endforelse + + +
+ +
+
+ + Blacklist +
+ + @forelse($blacklist as $ip) +
+ {{ $ip }} + +
+ @empty +
Keine Einträge.
+ @endforelse + + +
+
+
diff --git a/resources/views/livewire/ui/security/modal/fail2ban-ip-modal.blade.php b/resources/views/livewire/ui/security/modal/fail2ban-ip-modal.blade.php new file mode 100644 index 0000000..87b5615 --- /dev/null +++ b/resources/views/livewire/ui/security/modal/fail2ban-ip-modal.blade.php @@ -0,0 +1,55 @@ +
+ {{-- Header --}} +
+
+ + + {{ strtoupper($type) }} – {{ $mode === 'add' ? 'hinzufügen' : 'entfernen' }} + +
+ + +
+ + {{-- Body --}} +
+ @if($mode === 'add') +
+ + + @error('ip')

{{ $message }}

@enderror +
+ + + @if($type === 'blacklist') +

+ Wird sofort im Jail mailwolt-blacklist gebannt (bantime = permanent). +

+ @endif + @else +
+
IP: {{ $prefill ?? $ip }}
+
Wird aus der {{ $type }} entfernt + @if($type === 'blacklist') und im Blacklist-Jail entbannt @endif. +
+
+ + @endif +
+
diff --git a/resources/views/ui/security/fail2ban.blade.php b/resources/views/ui/security/fail2ban.blade.php index b3d9bbc..260c6d9 100644 --- a/resources/views/ui/security/fail2ban.blade.php +++ b/resources/views/ui/security/fail2ban.blade.php @@ -1 +1,10 @@ - + +@endsection diff --git a/totalCount b/totalCount deleted file mode 100644 index e69de29..0000000