clusev/app/Livewire/Modals/HardeningAction.php

133 lines
4.0 KiB
PHP

<?php
namespace App\Livewire\Modals;
use App\Models\AuditEvent;
use App\Models\Server;
use App\Services\FirewallService;
use App\Services\HardeningService;
use Illuminate\Support\Facades\Auth;
use LivewireUI\Modal\ModalComponent;
use Throwable;
/**
* Preview + confirm modal for a single server-hardening action (R5).
*
* mount() loads a human title + the EXACT shell commands (from the service's
* preview) so the operator sees what will run BEFORE applying. `apply()` runs
* the action over SSH, AUDITs (`harden.<key>`), notifies, asks the page to
* reload the snapshot, and — on success — closes.
*
* Action keys: ssh_root, ssh_password, fail2ban, unattended, firewall.
*/
class HardeningAction extends ModalComponent
{
public int $serverId;
public string $action;
/** Optional TCP port (reserved for future allow/deny firewall actions). */
public ?int $port = null;
public string $heading = '';
public string $description = '';
public string $preview = '';
/** Result of the apply run, set after the operator confirms. */
public bool $done = false;
public bool $ok = false;
public string $output = '';
/** Set when the action key is unknown / server vanished — blocks apply. */
public ?string $error = null;
public function mount(int $serverId, string $action, ?int $port = null): void
{
$this->serverId = $serverId;
$this->action = $action;
$this->port = $port;
$server = Server::find($serverId);
if (! $server) {
$this->error = 'Server nicht gefunden.';
return;
}
try {
if ($action === 'firewall') {
$this->heading = 'Firewall (UFW) aktivieren';
$this->description = 'Öffnet zuerst den SSH-Port und 80/443, dann wird UFW aktiviert — die laufende SSH-Sitzung bleibt erreichbar.';
$this->preview = app(FirewallService::class)->enablePreview($server);
} else {
$hardening = app(HardeningService::class);
$this->heading = $hardening->title($action);
$this->description = $hardening->description($action);
$this->preview = $hardening->preview($server, $action);
}
} catch (Throwable $e) {
$this->error = $e->getMessage();
}
}
public static function modalMaxWidth(): string
{
return 'lg';
}
public function apply(): void
{
if ($this->error !== null || $this->done) {
return;
}
$server = Server::find($this->serverId);
if (! $server) {
$this->error = 'Server nicht gefunden.';
return;
}
try {
$result = $this->action === 'firewall'
? app(FirewallService::class)->enable($server)
: app(HardeningService::class)->apply($server, $this->action);
} catch (Throwable $e) {
$this->done = true;
$this->ok = false;
$this->output = $e->getMessage();
return;
}
$this->done = true;
$this->ok = $result['ok'];
$this->output = $result['output'] !== '' ? $result['output'] : ($result['ok'] ? 'Erfolgreich angewendet.' : 'Fehlgeschlagen.');
AuditEvent::create([
'user_id' => Auth::id(),
'server_id' => $server->id,
'actor' => Auth::user()?->name ?? 'system',
'action' => 'harden.'.$this->action,
'target' => $server->name.' · '.$this->heading.($this->ok ? '' : ' (fehlgeschlagen)'),
'ip' => request()->ip(),
]);
if ($this->ok) {
$this->dispatch('hardeningApplied');
$this->dispatch('notify', message: $this->heading.' angewendet.');
} else {
$this->dispatch('notify', message: $this->heading.' fehlgeschlagen.');
}
}
public function render()
{
return view('livewire.modals.hardening-action');
}
}