diff --git a/extensions/Servers/Proxmox/Livewire/Firewall.php b/extensions/Servers/Proxmox/Livewire/Firewall.php index 04341b872..bb7cc370c 100644 --- a/extensions/Servers/Proxmox/Livewire/Firewall.php +++ b/extensions/Servers/Proxmox/Livewire/Firewall.php @@ -9,7 +9,7 @@ use Illuminate\Support\Facades\Log; use Paymenter\Extensions\Servers\Proxmox\Models\Server; use Paymenter\Extensions\Servers\Proxmox\Proxmox; -class FirewallNew extends Component +class Firewall extends Component { // Server & Core State public bool $firewallEnabled = false; @@ -1089,12 +1089,6 @@ class FirewallNew extends Component $this->showIPSetMemberForm = false; } - protected function resetIPSetForm(): void - { - $this->ipsetName = ''; - $this->ipsetComment = ''; - } - // ==================== FIREWALL LOGS ==================== public function loadFirewallLogs(): void diff --git a/extensions/Servers/Proxmox/Livewire/FirewallNew.php b/extensions/Servers/Proxmox/Livewire/FirewallNew.php deleted file mode 100644 index 1987f76bb..000000000 --- a/extensions/Servers/Proxmox/Livewire/FirewallNew.php +++ /dev/null @@ -1,1279 +0,0 @@ - 'None', - 'Amanda' => 'Amanda Backup', - 'Auth' => 'Auth (identd)', - 'BGP' => 'Border Gateway Protocol', - 'BitTorrent' => 'BitTorrent traffic', - 'BitTorrent32' => 'BitTorrent traffic (ports 32768:65535)', - 'Ceph' => 'Ceph Storage Cluster', - 'CVSup' => 'CVSup', - 'DHCPfwd' => 'Forwarded DHCP traffic', - 'DHCPv6fwd' => 'Forwarded DHCPv6 traffic', - 'DNS' => 'Domain Name System', - 'Distcc' => 'Distcc', - 'FTP' => 'File Transfer Protocol', - 'Git' => 'Git', - 'HKP' => 'OpenPGP HTTP Keyserver', - 'HTTP' => 'Hypertext Transfer Protocol', - 'HTTPS' => 'Hypertext Transfer Protocol (SSL)', - 'ICPV2' => 'Internet Cache Protocol V2', - 'IMAP' => 'Internet Message Access Protocol', - 'IMAPS' => 'Internet Message Access Protocol (SSL)', - 'IPsec' => 'IPsec', - 'IPsecah' => 'IPsec AH', - 'IPsecnat' => 'IPsec NAT-T', - 'IRC' => 'Internet Relay Chat', - 'Jetdirect' => 'HP Jetdirect', - 'L2TP' => 'Layer 2 Tunneling Protocol', - 'LDAP' => 'Lightweight Directory Access Protocol', - 'LDAPS' => 'Lightweight Directory Access Protocol (SSL)', - 'MSNP' => 'Microsoft Notification Protocol', - 'MSSQL' => 'Microsoft SQL Server', - 'Mail' => 'Mail traffic (SMTP, SMTPS, Submission)', - 'NFS' => 'Network File System', - 'NNTP' => 'NNTP', - 'NNTPS' => 'NNTPS', - 'NTP' => 'Network Time Protocol', - 'OSPF' => 'OSPF', - 'OpenVPN' => 'OpenVPN', - 'PCA' => 'Symantec PCAnywhere', - 'PMG' => 'Proxmox Mail Gateway', - 'POP3' => 'POP3', - 'POP3S' => 'POP3S', - 'PPtP' => 'Point-to-Point Tunneling Protocol', - 'Ping' => 'ICMP echo request', - 'PostgreSQL' => 'PostgreSQL', - 'Printer' => 'Line Printer Daemon', - 'RDP' => 'Microsoft Remote Desktop', - 'RIP' => 'Routing Information Protocol', - 'RNDC' => 'BIND remote management', - 'Razor' => 'Razor', - 'Rsyncd' => 'Rsync daemon', - 'SANE' => 'SANE network scanner', - 'SMB' => 'Microsoft SMB', - 'SMTP' => 'Simple Mail Transfer Protocol', - 'SMTPS' => 'Simple Mail Transfer Protocol (SSL)', - 'SNMP' => 'Simple Network Management Protocol', - 'SPAMD' => 'SpamAssassin daemon traffic', - 'SSH' => 'Secure Shell', - 'SVN' => 'Apache Subversion', - 'SixXS' => 'SixXS IPv6 Deployment', - 'SpiceProxy' => 'Proxmox VE SPICE Display Proxy', - 'Squid' => 'Squid web proxy', - 'Submission' => 'Mail message submission', - 'Syslog' => 'Syslog protocol', - 'TFTP' => 'Trivial File Transfer Protocol', - 'Telnet' => 'Telnet', - 'Telnets' => 'Telnet (SSL)', - 'Time' => 'RFC 868 Time', - 'Trcrt' => 'Traceroute', - 'VNC' => 'VNC traffic', - 'VNCL' => 'VNC Latin1', - 'Web' => 'WWW traffic (HTTP and HTTPS)', - 'Webcache' => 'Web Cache/Proxy', - 'Webmin' => 'Webmin', - 'Whois' => 'Whois' - ]; - - // IP Aliases - public array $aliases = []; - public bool $showAliasForm = false; - public ?string $editingAliasName = null; - public string $aliasName = ''; - public string $aliasCidr = ''; - public string $aliasComment = ''; - public bool $showDeleteAliasModal = false; - public ?string $deletingAliasName = null; - - // IPSets - public array $ipsets = []; - public bool $showIPSetForm = false; - public ?string $editingIPSetName = null; - public string $ipsetName = ''; - public string $ipsetComment = ''; - public bool $showDeleteIPSetModal = false; - public ?string $deletingIPSetName = null; - - // IPSet Members - public array $ipsetMembers = []; - public ?string $selectedIPSetName = null; - public bool $showIPSetMemberForm = false; - public string $memberCidr = ''; - public string $memberComment = ''; - public bool $memberNomatch = false; - public bool $showDeleteIPSetMemberModal = false; - public ?string $deletingMemberCidr = null; - - // Firewall Logs - public array $firewallLogs = []; - public int $logLimit = 1000; - public string $logError = ''; - - // Available IPs for rules - public array $availableIps = []; - - public function mount(Server $server): void - { - parent::mount($server); - $this->server->loadMissing('ipAddresses'); - - $this->loadAvailableIps(); - $this->loadNetworkAdapters(); - $this->loadFirewallOptions(); - $this->loadFirewallRules(); - $this->loadAliases(); - $this->loadIPSets(); - $this->loadFirewallLogs(); - } - - // ==================== AVAILABLE IPS ==================== - - protected function loadAvailableIps(): void - { - $allIps = collect(); - - if ($this->server->primaryIpv4) { - $allIps->push([ - 'ip' => $this->server->primaryIpv4->ip, - 'type' => 'IPv4', - 'label' => 'Primary IPv4' - ]); - } - - if ($this->server->primaryIpv6) { - $allIps->push([ - 'ip' => $this->server->primaryIpv6->ip, - 'type' => 'IPv6', - 'label' => 'Primary IPv6' - ]); - } - - foreach ($this->server->ipAddresses as $ipAddress) { - $allIps->push([ - 'ip' => $ipAddress->ip, - 'type' => strtoupper($ipAddress->type), - 'label' => 'Additional' - ]); - } - - $this->availableIps = $allIps->unique('ip')->values()->toArray(); - } - - // ==================== FIREWALL API REQUEST ==================== - - protected function firewallRequest(string $url, string $method = 'get', array $data = []): \Illuminate\Http\Client\Response - { - $baseUrl = 'https://pve.virtnet.bond'; - $user = 'root@pam'; - $tokenId = 'firewall'; - $secret = '0a439967-0a44-4b94-8906-c36b3a40cf68'; - - $fullUrl = rtrim($baseUrl, '/') . '/api2/json' . $url; - $authHeader = 'PVEAPIToken=' . $user . '!' . $tokenId . '=' . $secret; - - $acceptHeader = str_contains($url, '/firewall/log') - ? 'application/json, text/plain, */*' - : 'application/json'; - - $request = Http::withHeaders([ - 'Authorization' => $authHeader, - 'Accept' => $acceptHeader, - ])->withoutVerifying(); - - $response = match($method) { - 'post' => $request->post($fullUrl, $data), - 'put' => $request->put($fullUrl, $data), - 'delete' => $request->delete($fullUrl), - default => $request->get($fullUrl, $data), - }; - - return $response; - } - - // ==================== NETWORK ADAPTERS ==================== - - protected function loadNetworkAdapters(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config' - ); - - if ($response->successful()) { - $config = $response->json()['data'] ?? []; - $adapters = []; - - foreach ($config as $key => $value) { - if (preg_match('/^net(\d+)$/', $key, $matches)) { - $parts = explode(',', $value); - $model = explode('=', $parts[0])[0] ?? 'unknown'; - $mac = null; - - foreach ($parts as $part) { - if (str_contains($part, '=')) { - [$k, $v] = explode('=', $part, 2); - if (trim($k) === 'firewall') { - $this->adapterFirewallStates['net' . $matches[1]] = (int)$v === 1; - } elseif (trim($k) === 'macaddr') { - $mac = $v; - } - } - } - - $adapters[] = [ - 'name' => 'net' . $matches[1], - 'model' => $model, - 'mac' => $mac, - 'enabled' => $this->adapterFirewallStates['net' . $matches[1]] ?? false - ]; - } - } - - $this->networkAdapters = $adapters; - } - } catch (\Exception $e) { - Log::error('Failed to load network adapters: ' . $e->getMessage()); - } - } - - public function toggleAdapterFirewall(string $adapter): void - { - $this->loading = true; - - try { - $currentState = $this->adapterFirewallStates[$adapter] ?? false; - $newState = !$currentState; - - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', - 'put', - [$adapter . '.firewall' => $newState ? 1 : 0] - ); - - if ($response->successful()) { - $this->adapterFirewallStates[$adapter] = $newState; - $this->notify('Adapter firewall ' . ($newState ? 'enabled' : 'disabled'), 'success'); - } else { - $this->error = 'Failed to toggle adapter firewall'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to toggle adapter firewall: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - // ==================== FIREWALL OPTIONS ==================== - - protected function loadFirewallOptions(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options' - ); - - if ($response->successful()) { - $options = $response->json()['data'] ?? []; - $this->firewallEnabled = ($options['enable'] ?? 0) == 1; - $this->policyIn = strtoupper($options['policy_in'] ?? 'DROP'); - $this->policyOut = strtoupper($options['policy_out'] ?? 'ACCEPT'); - $this->ipFilter = ($options['ipfilter'] ?? 1) == 1; - $this->macFilter = ($options['macfilter'] ?? 1) == 1; - } - } catch (\Exception $e) { - Log::error('Failed to load firewall options: ' . $e->getMessage()); - } - } - - public function toggleFirewall(): void - { - $this->loading = true; - - try { - $newState = !$this->firewallEnabled; - - // Always force DHCP and NDP (router advertisements) to 0 for security - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options', - 'put', - [ - 'enable' => $newState ? 1 : 0, - 'dhcp' => 0, // Always disabled - 'ndp' => 0, // Always disabled (prevents router advertisements) - ] - ); - - if ($response->successful()) { - $this->firewallEnabled = $newState; - $this->notify('Firewall ' . ($newState ? 'enabled' : 'disabled'), 'success'); - } else { - $this->error = 'Failed to toggle firewall'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to toggle firewall: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function updatePolicies(): void - { - $this->loading = true; - - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/options', - 'put', - [ - 'policy_in' => $this->policyIn, - 'policy_out' => $this->policyOut, - 'ipfilter' => $this->ipFilter ? 1 : 0, - 'macfilter' => $this->macFilter ? 1 : 0, - 'dhcp' => 0, // Always disabled - 'ndp' => 0, // Always disabled - ] - ); - - if ($response->successful()) { - $this->notify('Firewall policies updated successfully', 'success'); - $this->loadFirewallOptions(); - } else { - $this->error = 'Failed to update firewall policies'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to update policies: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function openPolicyInfoModal(): void - { - $this->showPolicyInfoModal = true; - } - - public function closePolicyInfoModal(): void - { - $this->showPolicyInfoModal = false; - } - - // ==================== RULES ==================== - - protected function loadFirewallRules(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules' - ); - - if ($response->successful()) { - $this->rules = $response->json()['data'] ?? []; - } - } catch (\Exception $e) { - Log::error('Failed to load firewall rules: ' . $e->getMessage()); - } - } - - public function startCreateRule(): void - { - $this->resetRuleForm(); - - // Set default destination to first IP - if (!empty($this->availableIps)) { - $this->ruleDest = $this->availableIps[0]['ip']; - } - - // Set default interface to first adapter - if (!empty($this->networkAdapters)) { - $this->ruleIface = $this->networkAdapters[0]['name']; - } - - $this->editingRuleIndex = null; - $this->showRuleForm = true; - } - - public function startEditRule(int $index): void - { - if (!isset($this->rules[$index])) { - $this->error = 'Rule not found'; - $this->showErrorModal = true; - return; - } - - $rule = $this->rules[$index]; - $this->editingRuleIndex = $index; - $this->showRuleForm = true; - - $this->ruleAction = $rule['action'] ?? 'ACCEPT'; - $this->ruleType = $rule['type'] ?? 'in'; - $this->ruleSource = $rule['source'] ?? ''; - $this->ruleDest = $rule['dest'] ?? ''; - $this->ruleProto = $rule['proto'] ?? ''; - $this->ruleDport = $rule['dport'] ?? ''; - $this->ruleSport = $rule['sport'] ?? ''; - $this->ruleComment = $rule['comment'] ?? ''; - $this->ruleEnable = !isset($rule['enable']) || $rule['enable'] != 0; - $this->ruleMacro = $rule['macro'] ?? ''; - $this->ruleIface = $rule['iface'] ?? ''; - $this->ruleLog = $rule['log'] ?? 'nolog'; - } - - public function saveRule(): void - { - $this->loading = true; - - try { - $payload = [ - 'action' => $this->ruleAction, - 'type' => $this->ruleType, - 'enable' => $this->ruleEnable ? 1 : 0, - ]; - - if (!empty(trim($this->ruleMacro))) { - $payload['macro'] = trim($this->ruleMacro); - } - - if (!empty(trim($this->ruleIface))) { - $payload['iface'] = trim($this->ruleIface); - } - - if (!empty(trim($this->ruleSource))) { - $payload['source'] = trim($this->ruleSource); - } - - if (!empty(trim($this->ruleDest))) { - $payload['dest'] = trim($this->ruleDest); - } - - if (!empty(trim($this->ruleProto))) { - $payload['proto'] = trim($this->ruleProto); - } - - if (!empty(trim($this->ruleDport))) { - $payload['dport'] = trim($this->ruleDport); - } - - if (!empty(trim($this->ruleSport))) { - $payload['sport'] = trim($this->ruleSport); - } - - if (!empty(trim($this->ruleComment))) { - $payload['comment'] = trim($this->ruleComment); - } - - if (!empty(trim($this->ruleLog)) && $this->ruleLog !== 'nolog') { - $payload['log'] = $this->ruleLog; - } - - if ($this->editingRuleIndex !== null && isset($this->rules[$this->editingRuleIndex])) { - $pos = $this->rules[$this->editingRuleIndex]['pos']; - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, - 'put', - $payload - ); - } else { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules', - 'post', - $payload - ); - } - - if ($response->successful()) { - $this->notify('Rule ' . ($this->editingRuleIndex !== null ? 'updated' : 'created') . ' successfully', 'success'); - $this->resetRuleForm(); - $this->editingRuleIndex = null; - $this->showRuleForm = false; - $this->loadFirewallRules(); - } else { - $message = $response->json()['errors'][0]['message'] ?? 'Unknown error'; - $this->error = 'Failed to save rule: ' . $message; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to save rule: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function toggleRule(int $index): void - { - try { - if (!isset($this->rules[$index])) { - return; - } - - $rule = $this->rules[$index]; - $pos = $rule['pos']; - $currentEnable = isset($rule['enable']) ? (int)$rule['enable'] : 1; - $newEnable = $currentEnable == 0 ? 1 : 0; - - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, - 'put', - ['enable' => $newEnable] - ); - - if ($response->successful()) { - $this->notify('Rule ' . ($newEnable == 1 ? 'enabled' : 'disabled'), 'success'); - $this->loadFirewallRules(); - } else { - $this->error = 'Failed to toggle rule'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to toggle rule: ' . $e->getMessage(); - $this->showErrorModal = true; - } - } - - public function confirmDeleteRule(int $index): void - { - if (!isset($this->rules[$index])) { - return; - } - - $this->deletingRuleIndex = $index; - $this->showDeleteRuleModal = true; - } - - public function deleteRule(): void - { - if ($this->deletingRuleIndex === null || !isset($this->rules[$this->deletingRuleIndex])) { - return; - } - - $rule = $this->rules[$this->deletingRuleIndex]; - $pos = $rule['pos']; - $this->loading = true; - $this->showDeleteRuleModal = false; - - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $pos, - 'delete' - ); - - if ($response->successful()) { - $this->notify('Rule deleted successfully', 'success'); - $this->loadFirewallRules(); - } else { - $this->error = 'Failed to delete rule'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to delete rule: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - $this->deletingRuleIndex = null; - } - } - - public function cancelDeleteRule(): void - { - $this->deletingRuleIndex = null; - $this->showDeleteRuleModal = false; - } - - public function reorderRules(int $fromPos, int $toPos): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/rules/' . $fromPos, - 'put', - ['moveto' => $toPos] - ); - - if ($response->successful()) { - $this->notify('Rules reordered successfully', 'success'); - $this->loadFirewallRules(); - } else { - $this->error = 'Failed to reorder rules'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to reorder rules: ' . $e->getMessage(); - $this->showErrorModal = true; - } - } - - protected function resetRuleForm(): void - { - $this->ruleAction = 'ACCEPT'; - $this->ruleType = 'in'; - $this->ruleSource = ''; - $this->ruleDest = ''; - $this->ruleProto = ''; - $this->ruleDport = ''; - $this->ruleSport = ''; - $this->ruleComment = ''; - $this->ruleEnable = true; - $this->ruleMacro = ''; - $this->ruleIface = ''; - $this->ruleLog = 'nolog'; - } - - public function cancelRuleForm(): void - { - $this->resetRuleForm(); - $this->editingRuleIndex = null; - $this->showRuleForm = false; - } - - // ==================== ALIASES ==================== - - protected function loadAliases(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases' - ); - - if ($response->successful()) { - $this->aliases = $response->json()['data'] ?? []; - } - } catch (\Exception $e) { - Log::error('Failed to load aliases: ' . $e->getMessage()); - } - } - - public function startCreateAlias(): void - { - $this->resetAliasForm(); - $this->editingAliasName = null; - $this->showAliasForm = true; - } - - public function startEditAlias(string $name): void - { - $alias = collect($this->aliases)->firstWhere('name', $name); - - if ($alias) { - $this->editingAliasName = $name; - $this->aliasName = $alias['name']; - $this->aliasCidr = $alias['cidr']; - $this->aliasComment = $alias['comment'] ?? ''; - $this->showAliasForm = true; - } - } - - public function saveAlias(): void - { - $this->validate([ - 'aliasName' => 'required|string|max:64', - 'aliasCidr' => 'required|string', - ]); - - $this->loading = true; - - try { - $payload = [ - 'name' => trim($this->aliasName), - 'cidr' => trim($this->aliasCidr), - ]; - - if (!empty(trim($this->aliasComment))) { - $payload['comment'] = trim($this->aliasComment); - } - - if ($this->editingAliasName) { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases/' . $this->editingAliasName, - 'put', - $payload - ); - } else { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases', - 'post', - $payload - ); - } - - if ($response->successful()) { - $this->notify('Alias ' . ($this->editingAliasName ? 'updated' : 'created') . ' successfully', 'success'); - $this->resetAliasForm(); - $this->editingAliasName = null; - $this->showAliasForm = false; - $this->loadAliases(); - } else { - $message = $response->json()['errors'][0]['message'] ?? 'Unknown error'; - $this->error = 'Failed to save alias: ' . $message; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to save alias: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function confirmDeleteAlias(string $name): void - { - $this->deletingAliasName = $name; - $this->showDeleteAliasModal = true; - } - - public function deleteAlias(): void - { - if (!$this->deletingAliasName) { - return; - } - - $this->loading = true; - $this->showDeleteAliasModal = false; - - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/aliases/' . $this->deletingAliasName, - 'delete' - ); - - if ($response->successful()) { - $this->notify('Alias deleted successfully', 'success'); - $this->loadAliases(); - } else { - $this->error = 'Failed to delete alias'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to delete alias: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - $this->deletingAliasName = null; - } - } - - public function cancelDeleteAlias(): void - { - $this->deletingAliasName = null; - $this->showDeleteAliasModal = false; - } - - protected function resetAliasForm(): void - { - $this->aliasName = ''; - $this->aliasCidr = ''; - $this->aliasComment = ''; - } - - public function cancelAliasForm(): void - { - $this->resetAliasForm(); - $this->editingAliasName = null; - $this->showAliasForm = false; - } - - // ==================== IPSETS ==================== - - protected function loadIPSets(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset' - ); - - if ($response->successful()) { - $this->ipsets = $response->json()['data'] ?? []; - } - } catch (\Exception $e) { - Log::error('Failed to load IPSets: ' . $e->getMessage()); - } - } - - public function startCreateIPSet(): void - { - $this->resetIPSetForm(); - $this->editingIPSetName = null; - $this->showIPSetForm = true; - } - - public function saveIPSet(): void - { - $this->validate([ - 'ipsetName' => 'required|string|max:64', - ]); - - $this->loading = true; - - try { - $payload = [ - 'name' => trim($this->ipsetName), - ]; - - if (!empty(trim($this->ipsetComment))) { - $payload['comment'] = trim($this->ipsetComment); - } - - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset', - 'post', - $payload - ); - - if ($response->successful()) { - $this->notify('IPSet created successfully', 'success'); - $this->resetIPSetForm(); - $this->showIPSetForm = false; - $this->loadIPSets(); - } else { - $message = $response->json()['errors'][0]['message'] ?? 'Unknown error'; - $this->error = 'Failed to create IPSet: ' . $message; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to create IPSet: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function confirmDeleteIPSet(string $name): void - { - $this->deletingIPSetName = $name; - $this->showDeleteIPSetModal = true; - } - - public function deleteIPSet(): void - { - if (!$this->deletingIPSetName) { - return; - } - - $this->loading = true; - $this->showDeleteIPSetModal = false; - - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->deletingIPSetName, - 'delete' - ); - - if ($response->successful()) { - $this->notify('IPSet deleted successfully', 'success'); - $this->loadIPSets(); - - // Clear selected IPSet if it was deleted - if ($this->selectedIPSetName === $this->deletingIPSetName) { - $this->selectedIPSetName = null; - $this->ipsetMembers = []; - } - } else { - $this->error = 'Failed to delete IPSet'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to delete IPSet: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - $this->deletingIPSetName = null; - } - } - - public function cancelDeleteIPSet(): void - { - $this->deletingIPSetName = null; - $this->showDeleteIPSetModal = false; - } - - protected function resetIPSetForm(): void - { - $this->ipsetName = ''; - $this->ipsetComment = ''; - } - - public function cancelIPSetForm(): void - { - $this->resetIPSetForm(); - $this->editingIPSetName = null; - $this->showIPSetForm = false; - } - - // ==================== IPSET MEMBERS ==================== - - public function selectIPSet(string $name): void - { - $this->selectedIPSetName = $name; - $this->loadIPSetMembers($name); - } - - protected function loadIPSetMembers(string $name): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $name - ); - - if ($response->successful()) { - $this->ipsetMembers = $response->json()['data'] ?? []; - } - } catch (\Exception $e) { - Log::error('Failed to load IPSet members: ' . $e->getMessage()); - } - } - - public function startAddIPSetMember(): void - { - $this->memberCidr = ''; - $this->memberComment = ''; - $this->memberNomatch = false; - $this->showIPSetMemberForm = true; - } - - public function saveIPSetMember(): void - { - if (!$this->selectedIPSetName) { - return; - } - - $this->validate([ - 'memberCidr' => 'required|string', - ]); - - $this->loading = true; - - try { - $payload = [ - 'cidr' => trim($this->memberCidr), - ]; - - if (!empty(trim($this->memberComment))) { - $payload['comment'] = trim($this->memberComment); - } - - if ($this->memberNomatch) { - $payload['nomatch'] = 1; - } - - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName, - 'post', - $payload - ); - - if ($response->successful()) { - $this->notify('IP/CIDR added to IPSet successfully', 'success'); - $this->memberCidr = ''; - $this->memberComment = ''; - $this->memberNomatch = false; - $this->showIPSetMemberForm = false; - $this->loadIPSetMembers($this->selectedIPSetName); - } else { - $message = $response->json()['errors'][0]['message'] ?? 'Unknown error'; - $this->error = 'Failed to add member: ' . $message; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to add member: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - } - } - - public function confirmDeleteIPSetMember(string $cidr): void - { - $this->deletingMemberCidr = $cidr; - $this->showDeleteIPSetMemberModal = true; - } - - public function deleteIPSetMember(): void - { - if (!$this->selectedIPSetName || !$this->deletingMemberCidr) { - return; - } - - $this->loading = true; - $this->showDeleteIPSetMemberModal = false; - - try { - // URL encode the CIDR for the DELETE request - $encodedCidr = urlencode($this->deletingMemberCidr); - - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/ipset/' . $this->selectedIPSetName . '/' . $encodedCidr, - 'delete' - ); - - if ($response->successful()) { - $this->notify('IP/CIDR removed from IPSet successfully', 'success'); - $this->loadIPSetMembers($this->selectedIPSetName); - } else { - $this->error = 'Failed to remove member'; - $this->showErrorModal = true; - } - } catch (\Exception $e) { - $this->error = 'Failed to remove member: ' . $e->getMessage(); - $this->showErrorModal = true; - } finally { - $this->loading = false; - $this->deletingMemberCidr = null; - } - } - - public function cancelDeleteIPSetMember(): void - { - $this->deletingMemberCidr = null; - $this->showDeleteIPSetMemberModal = false; - } - - public function cancelIPSetMemberForm(): void - { - $this->memberCidr = ''; - $this->memberComment = ''; - $this->memberNomatch = false; - $this->showIPSetMemberForm = false; - } - - protected function resetIPSetForm(): void - { - $this->ipsetName = ''; - $this->ipsetComment = ''; - } - - // ==================== FIREWALL LOGS ==================== - - public function loadFirewallLogs(): void - { - try { - $response = $this->firewallRequest( - '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log', - 'get', - ['limit' => $this->logLimit] - ); - - if ($response->successful()) { - $rawLogs = $response->json()['data'] ?? []; - $this->firewallLogs = $this->parseFirewallLogs($rawLogs); - $this->logError = ''; - } else { - $this->logError = 'Failed to load firewall logs'; - } - } catch (\Exception $e) { - $this->logError = 'Failed to load logs: ' . $e->getMessage(); - Log::error('Firewall logs error: ' . $e->getMessage()); - } - } - - protected function parseFirewallLogs(array $rawLogs): array - { - $parsed = []; - - foreach ($rawLogs as $logEntry) { - if (empty($logEntry) || !is_string($logEntry)) { - continue; - } - - $log = $this->parseLogLine($logEntry); - if ($log) { - $parsed[] = $log; - } - } - - return $parsed; - } - - protected function parseLogLine(string $line): ?array - { - // Parse Proxmox firewall log format - // Format: "vmid level chain timestamp action: KEY=VALUE ..." - - if (!preg_match('/^(\d+)\s+(\d+)\s+(\S+)\s+([^\s]+\s+[^\s]+)\s+(\w+):\s*(.*)$/', $line, $matches)) { - return null; - } - - $fields = [ - 'raw' => $line, - 'vmid' => $matches[1], - 'level' => (int)$matches[2], - 'chain' => $matches[3], - 'timestamp' => $matches[4], - 'action' => $matches[5], - ]; - - // Parse key-value pairs - $kvString = $matches[6]; - if (preg_match_all('/(\w+)=([^\s]+)/', $kvString, $kvMatches, PREG_SET_ORDER)) { - foreach ($kvMatches as $match) { - $key = strtolower($match[1]); - $value = $match[2]; - $fields[$key] = $value; - } - } - - // Map fields to standardized names - return [ - 'raw' => $line, - 'timestamp' => $fields['timestamp'] ?? '-', - 'timestamp_iso' => $this->convertTimestamp($fields['timestamp'] ?? ''), - 'action' => $fields['action'] ?? '-', - 'level' => $fields['level'] ?? 0, - 'level_label' => $this->getLevelLabel($fields['level'] ?? 0), - 'level_badge_class' => $this->getLevelBadgeClass($fields['action'] ?? '-'), - 'chain' => $fields['chain'] ?? '-', - 'interface' => $fields['in'] ?? '-', - 'source' => $fields['src'] ?? '-', - 'destination' => $fields['dst'] ?? '-', - 'source_port' => $fields['spt'] ?? '-', - 'destination_port' => $fields['dpt'] ?? '-', - 'protocol' => $fields['proto'] ?? '-', - 'mac' => $fields['mac'] ?? '-', - ]; - } - - protected function convertTimestamp(string $timestamp): string - { - try { - $dt = Carbon::createFromFormat('d/M/Y:H:i:s O', $timestamp); - return $dt->toIso8601String(); - } catch (\Exception $e) { - return $timestamp; - } - } - - protected function getLevelLabel(int $level): string - { - return match($level) { - 0 => 'Emergency', - 1 => 'Alert', - 2 => 'Critical', - 3 => 'Error', - 4 => 'Warning', - 5 => 'Notice', - 6 => 'Info', - 7 => 'Debug', - default => 'Unknown', - }; - } - - protected function getLevelBadgeClass(string $action): string - { - return match(strtoupper($action)) { - 'ACCEPT' => 'bg-success/20 text-success', - 'DROP' => 'bg-error/20 text-error', - 'REJECT' => 'bg-warning/20 text-warning', - default => 'bg-base/20 text-base/60', - }; - } - - // ==================== IP LOCATION ==================== - - public function fetchIPLocation(string $ip): array - { - try { - if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) { - return [ - 'status' => 'fail', - 'message' => 'Invalid or private IP address' - ]; - } - - $response = Http::timeout(5)->get("http://ip-api.com/json/{$ip}"); - - if ($response->successful()) { - $data = $response->json(); - - if (isset($data['status']) && $data['status'] === 'success') { - return $data; - } - - return [ - 'status' => 'fail', - 'message' => $data['message'] ?? 'Unable to fetch location data' - ]; - } - - return [ - 'status' => 'fail', - 'message' => 'Failed to fetch location data (HTTP ' . $response->status() . ')' - ]; - } catch (\Exception $e) { - return [ - 'status' => 'fail', - 'message' => 'Error: ' . $e->getMessage() - ]; - } - } - - // ==================== HELPER METHODS ==================== - - public function checkIsPublicIP(string $ip): bool - { - if (empty($ip) || $ip === '—' || $ip === '-') { - return false; - } - - // Use PHP's filter_var with appropriate flags - return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false; - } - - public function render() - { - return view('proxmox::firewall-new'); - } -} -