feat: add automatic cluster sync and IPAM discovery with configurable intervals, firewall log file parsing, and enhanced flow prioritization
Add RuntimeSettingsRead/RuntimeSettingsUpdate schemas with auto_node_sync_enabled/auto_node_sync_interval_minutes/auto_ipam_sync_enabled/auto_ipam_sync_interval_minutes/last_node_auto_sync_at/last_ipam_auto_sync_at fields, implement firewall_log_lines_from_files to parse /var/log/pve-firewall.log with max_lines limit and error collection, extend collect_firewall_log
This commit is contained in:
@@ -18,7 +18,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
VERSION = "0.2.1"
|
||||
VERSION = "0.2.2"
|
||||
VM_INTERFACE_RE = re.compile(r"(?:tap|fwbr|fwln|fwpr)(\d+)")
|
||||
VM_INTERFACE_DETAIL_RE = re.compile(r"(?:tap|fwbr|fwln|fwpr)(\d+)i(\d+)")
|
||||
LOG_FIELD_RE = re.compile(r"\b([A-Z]+)=([^\s]+)")
|
||||
@@ -362,18 +362,39 @@ def parse_firewall_log_line(line: str) -> dict[str, Any] | None:
|
||||
}
|
||||
|
||||
|
||||
def firewall_log_lines_from_files(max_lines: int = 5000) -> tuple[list[str], list[dict[str, str]]]:
|
||||
lines: list[str] = []
|
||||
errors: list[dict[str, str]] = []
|
||||
for path in ("/var/log/pve-firewall.log", "/var/log/pve-firewall.log.1"):
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as handle:
|
||||
file_lines = handle.readlines()[-max_lines:]
|
||||
lines.extend(line.rstrip("\n") for line in file_lines)
|
||||
except OSError as exc:
|
||||
errors.append({"path": path, "error": str(exc)})
|
||||
return lines[-max_lines:], errors
|
||||
|
||||
|
||||
def collect_firewall_log_flows(since_minutes: int = 5, limit: int = 500) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
||||
diagnostics = {"collector": "journalctl-kernel", "since_minutes": since_minutes, "errors": []}
|
||||
diagnostics = {"collector": "journalctl-kernel+pve-firewall-log", "since_minutes": since_minutes, "errors": []}
|
||||
code, output = run_command(
|
||||
["journalctl", "-k", "--since", f"-{max(since_minutes, 1)} min", "--no-pager", "-o", "cat"],
|
||||
timeout=10,
|
||||
)
|
||||
if code != 0 or not output:
|
||||
log_lines: list[str] = []
|
||||
if code == 0 and output:
|
||||
log_lines.extend(output.splitlines())
|
||||
else:
|
||||
diagnostics["errors"].append(output or "journalctl returned no firewall log output")
|
||||
file_lines, file_errors = firewall_log_lines_from_files()
|
||||
log_lines.extend(file_lines)
|
||||
diagnostics["file_errors"] = file_errors
|
||||
diagnostics["lines_scanned"] = len(log_lines)
|
||||
if not log_lines:
|
||||
return [], diagnostics
|
||||
|
||||
flows: dict[tuple[object, ...], dict[str, Any]] = {}
|
||||
for line in output.splitlines():
|
||||
for line in log_lines:
|
||||
if "SRC=" not in line or "DST=" not in line:
|
||||
continue
|
||||
flow = parse_firewall_log_line(line)
|
||||
@@ -418,7 +439,16 @@ def merge_flow_sources(*sources: list[dict[str, Any]], limit: int = 500) -> list
|
||||
current["bytes"] = int(current.get("bytes") or 0) + int(flow.get("bytes") or 0)
|
||||
continue
|
||||
flows[key] = dict(flow)
|
||||
return sorted(flows.values(), key=lambda item: int(item.get("bytes") or 0), reverse=True)[:limit]
|
||||
return sorted(
|
||||
flows.values(),
|
||||
key=lambda item: (
|
||||
1 if str(item.get("decision") or item.get("state") or "").lower() in {"blocked", "drop", "dropped", "reject", "rejected", "deny", "denied"} else 0,
|
||||
1 if item.get("collector") == "firewall-log" else 0,
|
||||
int(item.get("bytes") or 0),
|
||||
int(item.get("packets") or 0),
|
||||
),
|
||||
reverse=True,
|
||||
)[:limit]
|
||||
|
||||
|
||||
def collect_conntrack() -> dict[str, Any]:
|
||||
@@ -470,7 +500,7 @@ def collect_firewall() -> dict[str, Any]:
|
||||
def collect_payload(config: dict[str, Any]) -> dict[str, Any]:
|
||||
uptime = read_text("/proc/uptime")
|
||||
interfaces = collect_interfaces()
|
||||
flow_limit = int(config.get("flow_limit", 500))
|
||||
flow_limit = int(config.get("flow_limit", 2000))
|
||||
packet_flows: list[dict[str, Any]] = []
|
||||
packet_diagnostics: dict[str, Any] | None = None
|
||||
firewall_log_flows: list[dict[str, Any]] = []
|
||||
|
||||
Reference in New Issue
Block a user