feat: add firewall logs functionality to Proxmox Firewall component, enabling users to view recent firewall activity and enhance monitoring capabilities
This commit is contained in:
@@ -19,6 +19,8 @@ class Firewall extends Component
|
||||
public bool $showRuleForm = false;
|
||||
public array $networkAdapters = [];
|
||||
public array $adapterFirewallStates = [];
|
||||
public array $firewallLogs = [];
|
||||
public int $logLimit = 100;
|
||||
|
||||
// Rule creation/editing
|
||||
public ?int $editingRulePos = null;
|
||||
@@ -52,6 +54,7 @@ class Firewall extends Component
|
||||
$this->loadAliases();
|
||||
$this->loadIpsets();
|
||||
$this->loadNetworkAdapters();
|
||||
$this->loadFirewallLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -192,6 +195,28 @@ class Firewall extends Component
|
||||
public function startCreateRule(): void
|
||||
{
|
||||
$this->resetRuleForm();
|
||||
|
||||
// Set default destination to first IP address
|
||||
$allIps = collect();
|
||||
if ($this->server->primaryIpv4) {
|
||||
$allIps->push($this->server->primaryIpv4->ip);
|
||||
}
|
||||
if ($this->server->primaryIpv6) {
|
||||
$allIps->push($this->server->primaryIpv6->ip);
|
||||
}
|
||||
foreach ($this->server->ipAddresses as $ipAddress) {
|
||||
$allIps->push($ipAddress->ip);
|
||||
}
|
||||
$uniqueIps = $allIps->unique();
|
||||
if ($uniqueIps->isNotEmpty()) {
|
||||
$this->ruleDest = $uniqueIps->first();
|
||||
}
|
||||
|
||||
// Set default interface to first network adapter
|
||||
if (!empty($this->networkAdapters)) {
|
||||
$this->ruleIface = $this->networkAdapters[0]['name'];
|
||||
}
|
||||
|
||||
$this->editingRulePos = null;
|
||||
$this->showRuleForm = true;
|
||||
}
|
||||
@@ -248,6 +273,7 @@ class Firewall extends Component
|
||||
if (!empty(trim($this->ruleSource))) {
|
||||
$payload['source'] = trim($this->ruleSource);
|
||||
}
|
||||
// Destination is required, always include it
|
||||
if (!empty(trim($this->ruleDest))) {
|
||||
$payload['dest'] = trim($this->ruleDest);
|
||||
}
|
||||
@@ -266,6 +292,7 @@ class Firewall extends Component
|
||||
if (!empty(trim($this->ruleMacro))) {
|
||||
$payload['macro'] = trim($this->ruleMacro);
|
||||
}
|
||||
// Interface is required, always include it
|
||||
if (!empty(trim($this->ruleIface))) {
|
||||
$payload['iface'] = trim($this->ruleIface);
|
||||
}
|
||||
@@ -687,6 +714,104 @@ class Firewall extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function loadFirewallLogs(): void
|
||||
{
|
||||
try {
|
||||
$response = $this->firewallRequest(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/firewall/log',
|
||||
'get',
|
||||
['limit' => $this->logLimit]
|
||||
);
|
||||
$rawLogs = $response->json()['data'] ?? [];
|
||||
|
||||
$this->firewallLogs = [];
|
||||
foreach ($rawLogs as $logEntry) {
|
||||
$logText = $logEntry['n'] ?? $logEntry['t'] ?? '';
|
||||
if (empty($logText)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Parse the log entry to extract simplified information
|
||||
$parsed = $this->parseFirewallLog($logText);
|
||||
if ($parsed) {
|
||||
$this->firewallLogs[] = $parsed;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->firewallLogs = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseFirewallLog(string $logText): ?array
|
||||
{
|
||||
// Example log: 202500009 2 tap202500009i0-IN 01/Dec/2025:09:00:39 -1000 REJECT: IN=fwbr202500009i0 OUT=fwbr202500009i0 PHYSIN=fwln202500009i0 PHYSOUT=tap202500009i0 MAC=8e:ef:c1:86:d4:20:58:9c:fc:10:ff:d3:08:00 SRC=24.210.66.222 DST=45.154.99.21 LEN=52 TOS=0x00 PREC=0x00 TTL=115 ID=58510 DF PROTO=TCP SPT=50692 DPT=22 SEQ=2584199599 ACK=0 WINDOW=65535 SYN
|
||||
|
||||
// Extract SRC, DST, TTL, PROTO, SPT, DPT, ACK
|
||||
$result = [
|
||||
'source' => null,
|
||||
'destination' => null,
|
||||
'ttl' => null,
|
||||
'proto' => null,
|
||||
'spt' => null,
|
||||
'dpt' => null,
|
||||
'ack' => null,
|
||||
'action' => null,
|
||||
'timestamp' => null,
|
||||
];
|
||||
|
||||
// Extract action (REJECT, ACCEPT, DROP) - usually before the colon
|
||||
if (preg_match('/(REJECT|ACCEPT|DROP):/i', $logText, $matches)) {
|
||||
$result['action'] = strtoupper($matches[1]);
|
||||
}
|
||||
|
||||
// Extract timestamp - look for date pattern like "01/Dec/2025:09:00:39"
|
||||
if (preg_match('/(\d{2}\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2})/', $logText, $matches)) {
|
||||
$result['timestamp'] = $matches[1];
|
||||
}
|
||||
|
||||
// Extract SRC
|
||||
if (preg_match('/SRC=([^\s]+)/', $logText, $matches)) {
|
||||
$result['source'] = $matches[1];
|
||||
}
|
||||
|
||||
// Extract DST
|
||||
if (preg_match('/DST=([^\s]+)/', $logText, $matches)) {
|
||||
$result['destination'] = $matches[1];
|
||||
}
|
||||
|
||||
// Extract TTL
|
||||
if (preg_match('/TTL=(\d+)/', $logText, $matches)) {
|
||||
$result['ttl'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
// Extract PROTO
|
||||
if (preg_match('/PROTO=(\w+)/', $logText, $matches)) {
|
||||
$result['proto'] = strtoupper($matches[1]);
|
||||
}
|
||||
|
||||
// Extract SPT (Source Port)
|
||||
if (preg_match('/SPT=(\d+)/', $logText, $matches)) {
|
||||
$result['spt'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
// Extract DPT (Destination Port)
|
||||
if (preg_match('/DPT=(\d+)/', $logText, $matches)) {
|
||||
$result['dpt'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
// Extract ACK
|
||||
if (preg_match('/ACK=(\d+)/', $logText, $matches)) {
|
||||
$result['ack'] = (int) $matches[1];
|
||||
}
|
||||
|
||||
// Only return if we have at least some data
|
||||
if ($result['source'] || $result['destination'] || $result['proto']) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('proxmox::firewall')->layoutData([
|
||||
|
||||
Reference in New Issue
Block a user