145 lines
4.6 KiB
PHP
145 lines
4.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Modals;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Models\Server;
|
|
use App\Services\Fail2banService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Tune the fail2ban [DEFAULT] policy (Sperrdauer / Max. Fehlversuche / Zeitfenster)
|
|
* via a small German form — no shell commands are ever shown. mount() loads the
|
|
* current values; save() clamps + validates the integers, writes jail.local, AUDITs,
|
|
* notifies and closes.
|
|
*/
|
|
class Fail2banConfig extends ModalComponent
|
|
{
|
|
public int $serverId;
|
|
|
|
public string $serverName = '';
|
|
|
|
/** fail2ban duration grammar (e.g. 600, 10m, 1h 30m, -1 = dauerhaft) — kept verbatim. */
|
|
public string $bantime = '10m';
|
|
|
|
public int $maxretry = 5;
|
|
|
|
public string $findtime = '10m';
|
|
|
|
public ?string $error = null;
|
|
|
|
/** True only once the current policy was read — guards save() from overwriting with unseen defaults. */
|
|
public bool $loaded = false;
|
|
|
|
public function mount(int $serverId, Fail2banService $fail2ban): void
|
|
{
|
|
abort_unless(auth()->user()?->can('manage-network'), 403);
|
|
|
|
$this->serverId = $serverId;
|
|
|
|
$server = Server::find($serverId);
|
|
if (! $server) {
|
|
$this->error = __('common.server_not_found');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->serverName = $server->name;
|
|
|
|
try {
|
|
$current = $fail2ban->readConfig($server);
|
|
$this->bantime = $current['bantime'];
|
|
$this->maxretry = $current['maxretry'];
|
|
$this->findtime = $current['findtime'];
|
|
$this->loaded = true;
|
|
} catch (Throwable $e) {
|
|
$this->error = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
public static function modalMaxWidth(): string
|
|
{
|
|
return 'lg';
|
|
}
|
|
|
|
public function save(Fail2banService $fail2ban): void
|
|
{
|
|
abort_unless(auth()->user()?->can('manage-network'), 403);
|
|
|
|
// Never overwrite the remote policy with defaults the operator never saw —
|
|
// saving is only allowed once the current values were read successfully.
|
|
if (! $this->loaded) {
|
|
$this->error = __('modals.fail2ban_config.error_read_locked');
|
|
|
|
return;
|
|
}
|
|
|
|
$this->error = null;
|
|
|
|
// fail2ban duration grammar: a number (seconds) optionally with a unit
|
|
// (s/m/h/d/w/mo/y), space-separated composites, or -1 for a permanent ban.
|
|
$duration = ['required', 'string', 'max:40', 'regex:/^(?:-1|\d+(?:\.\d+)?\s*(?:s|m|h|d|w|mo|y)?(?:\s+\d+(?:\.\d+)?\s*(?:s|m|h|d|w|mo|y)?)*)$/i'];
|
|
|
|
$validated = $this->validate([
|
|
'bantime' => $duration,
|
|
'maxretry' => ['required', 'integer', 'min:1', 'max:100'],
|
|
'findtime' => $duration,
|
|
], [
|
|
'bantime.regex' => __('modals.fail2ban_config.error_bantime_invalid'),
|
|
'findtime.regex' => __('modals.fail2ban_config.error_findtime_invalid'),
|
|
], [
|
|
'bantime' => __('modals.fail2ban_config.attr_bantime'),
|
|
'maxretry' => __('modals.fail2ban_config.attr_maxretry'),
|
|
'findtime' => __('modals.fail2ban_config.attr_findtime'),
|
|
]);
|
|
|
|
$server = Server::find($this->serverId);
|
|
if (! $server) {
|
|
$this->error = __('common.server_not_found');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Tuning lives in its OWN drop-in (separate from the whitelist), so this can
|
|
// never touch ignoreip. Values were validated/clamped above.
|
|
$result = $fail2ban->writeTuning(
|
|
$server,
|
|
(string) $validated['bantime'],
|
|
(int) $validated['maxretry'],
|
|
(string) $validated['findtime'],
|
|
);
|
|
} catch (Throwable $e) {
|
|
$this->error = $e->getMessage();
|
|
|
|
return;
|
|
}
|
|
|
|
if (! $result['ok']) {
|
|
$this->error = __('modals.fail2ban_config.error_save_failed', ['output' => $result['output']]);
|
|
|
|
return;
|
|
}
|
|
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'server_id' => $server->id,
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => 'fail2ban.configure',
|
|
'target' => $server->name.' · bantime '.$validated['bantime'].', maxretry '.$validated['maxretry'],
|
|
'ip' => request()->ip(),
|
|
]);
|
|
|
|
$this->dispatch('hardeningApplied');
|
|
$this->dispatch('notify', message: __('modals.fail2ban_config.notify_saved'));
|
|
$this->closeModal();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.modals.fail2ban-config');
|
|
}
|
|
}
|