feat: add timestamp tracking to flows with first_seen_at/last_seen_at/observed_at fields across all collectors

Add FIREWALL_LOG_TS_RE regex to parse timestamps from firewall log lines, implement firewall_log_seen_at to extract and convert log timestamps to UTC ISO format, add first_seen_at/last_seen_at/observed_at fields to flows in collect_packet_flows (AF_PACKET collector) with timestamp updates on flow aggregation, add timestamp fields to collect_flows (conntrack collector) and parse_firewall_log_line (
This commit is contained in:
2026-07-10 14:44:42 +02:00
parent 757fecc686
commit 2509c4fa28
4 changed files with 66 additions and 4 deletions
+15 -2
View File
@@ -656,6 +656,20 @@ def traffic_flow_key(node_id: str, raw_flow: dict[str, object]) -> tuple[object,
)
def flow_observed_at(raw_flow: dict[str, object], payload_collected_at: datetime | None) -> datetime:
for key in ("last_seen_at", "observed_at", "first_seen_at"):
value = raw_flow.get(key)
if not value:
continue
try:
parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
except ValueError:
continue
return parsed.replace(tzinfo=None) if parsed.tzinfo else parsed
fallback = payload_collected_at or datetime.utcnow()
return fallback.replace(tzinfo=None) if fallback.tzinfo else fallback
def compact_agent_payload(payload: AgentHeartbeat) -> dict:
value = payload.model_dump(mode="json")
flows = value.get("flows")
@@ -1581,8 +1595,7 @@ def agent_heartbeat(payload: AgentHeartbeat, authorization: str | None = Header(
destination_ip = str(raw_flow.get("destination_ip") or "")
if not source_ip or not destination_ip:
continue
observed_at = payload.collected_at or datetime.utcnow()
observed_at = observed_at.replace(tzinfo=None) if observed_at.tzinfo else observed_at
observed_at = flow_observed_at(raw_flow, payload.collected_at)
key = traffic_flow_key(node.id, raw_flow)
existing = existing_flows.get(key)
if existing: