feat: add kernel firewall log parsing to agent with blocked traffic detection and dashboard suspicious traffic enhancement
Add parse_firewall_log_line to extract SRC/DST/PROTO/SPT/DPT/LEN from kernel log lines with drop/reject/accept decision classification, implement collect_firewall_log_flows to parse journalctl -k output from last 5 minutes with flow aggregation by 5-tuple+decision, add merge_flow_sources to combine packet flows and firewall log flows with deduplication, extend agent config with
This commit is contained in:
@@ -526,6 +526,16 @@ def flow_policy_decision(active_matches: list[dict[str, object]], policy_matches
|
||||
return "observed"
|
||||
|
||||
|
||||
def raw_flow_decision(flow: TrafficFlow) -> str | None:
|
||||
raw = flow.raw if isinstance(flow.raw, dict) else {}
|
||||
decision = str(raw.get("decision") or flow.state or "").lower()
|
||||
if decision in {"blocked", "drop", "dropped", "reject", "rejected", "deny", "denied"}:
|
||||
return "blocked"
|
||||
if decision in {"allowed", "accept", "accepted", "allow"}:
|
||||
return "allowed"
|
||||
return None
|
||||
|
||||
|
||||
def policy_read_payload(policy: Policy, deployment_status: dict[str, object] | None = None) -> dict[str, object]:
|
||||
return {
|
||||
"id": policy.id,
|
||||
@@ -739,11 +749,14 @@ def dashboard_suspicious_traffic(db: Session) -> list[dict[str, int | str]]:
|
||||
events: dict[tuple[str, str, int], dict[str, int | str]] = {}
|
||||
for flow in db.scalars(select(TrafficFlow).order_by(TrafficFlow.updated_at.desc()).limit(500)).all():
|
||||
port = flow.destination_port or 0
|
||||
if port not in sensitive_ports:
|
||||
continue
|
||||
source_internal = bool(subnet_label_for_ip(subnets, flow.source_ip))
|
||||
destination_internal = bool(subnet_label_for_ip(subnets, flow.destination_ip)) or flow.destination_ip in workload_ips
|
||||
if source_internal or not destination_internal:
|
||||
if not destination_internal:
|
||||
continue
|
||||
decision = raw_flow_decision(flow)
|
||||
if source_internal and decision != "blocked":
|
||||
continue
|
||||
if port not in sensitive_ports and decision != "blocked":
|
||||
continue
|
||||
key = (flow.source_ip, flow.destination_ip, port)
|
||||
event = events.setdefault(
|
||||
@@ -754,8 +767,9 @@ def dashboard_suspicious_traffic(db: Session) -> list[dict[str, int | str]]:
|
||||
"protocol": flow.protocol,
|
||||
"port": port,
|
||||
"bytes": 0,
|
||||
"reason": sensitive_ports[port],
|
||||
"severity": "high" if port in {22, 3389, 445} else "medium",
|
||||
"reason": "Blocked by firewall" if decision == "blocked" else sensitive_ports[port],
|
||||
"severity": "high" if port in {22, 3389, 445} or decision == "blocked" else "medium",
|
||||
"decision": decision or "observed",
|
||||
},
|
||||
)
|
||||
event["bytes"] = int(event["bytes"]) + int(flow.bytes or 0)
|
||||
@@ -1227,6 +1241,8 @@ cat > "$CONFIG_DIR/config.json" <<'JSON'
|
||||
"flow_limit": 500,
|
||||
"packet_flow_collector": true,
|
||||
"packet_flow_window_seconds": 10,
|
||||
"firewall_log_collector": true,
|
||||
"firewall_log_window_minutes": 5,
|
||||
"verify_tls": true
|
||||
}}
|
||||
JSON
|
||||
@@ -1359,7 +1375,7 @@ def agent_heartbeat(payload: AgentHeartbeat, authorization: str | None = Header(
|
||||
destination_port=flow_int(raw_flow.get("destination_port"), 0) or None,
|
||||
bytes=flow_int(raw_flow.get("bytes")),
|
||||
packets=flow_int(raw_flow.get("packets")),
|
||||
state=str(raw_flow.get("state") or "") or None,
|
||||
state=str(raw_flow.get("decision") or raw_flow.get("state") or "") or None,
|
||||
observed_at=observed_at.replace(tzinfo=None) if observed_at.tzinfo else observed_at,
|
||||
raw=raw_flow,
|
||||
)
|
||||
@@ -1416,6 +1432,8 @@ async def workload_insights(workload_id: str, _: CurrentUser, db: Session = Depe
|
||||
for policy in policies
|
||||
if policy_matches_flow(db, policy, flow, ip_owners, workload_ips_by_id, workload.cluster_id)
|
||||
]
|
||||
raw_decision = raw_flow_decision(flow)
|
||||
raw_payload = flow.raw if isinstance(flow.raw, dict) else {}
|
||||
traffic.append(
|
||||
{
|
||||
"source": flow_endpoint_label(source_owner, known_subnets, flow.source_ip),
|
||||
@@ -1430,7 +1448,8 @@ async def workload_insights(workload_id: str, _: CurrentUser, db: Session = Depe
|
||||
"bytes": flow.bytes,
|
||||
"packets": flow.packets,
|
||||
"state": flow.state,
|
||||
"decision": flow_policy_decision(matching_firewall_rules, matching_policies),
|
||||
"decision": raw_decision or flow_policy_decision(matching_firewall_rules, matching_policies),
|
||||
"collector": raw_payload.get("collector"),
|
||||
"matching_firewall_rules": matching_firewall_rules,
|
||||
"matching_audit_policies": [
|
||||
policy for policy in matching_policies if str(policy.get("enforcement_mode")) == "audit"
|
||||
|
||||
Reference in New Issue
Block a user