feat: add suspicious traffic detection dashboard widget with sensitive port monitoring and security posture indicator

Add dashboard_suspicious_traffic to detect external connections to sensitive ports (SSH/RDP/SMB/VNC/PostgreSQL/MySQL/Redis) from outside IPAM subnets with severity classification, extend Dashboard type with security_posture/suspicious_traffic/last_syncs fields, implement BarList component for traffic visualization with percentage bars and byte formatting, add security posture card
This commit is contained in:
2026-07-09 15:50:42 +02:00
parent 32906bca1e
commit 571d1513e7
4 changed files with 182 additions and 17 deletions
+45 -1
View File
@@ -334,6 +334,47 @@ def dashboard_top_talkers(db: Session) -> list[dict[str, int | str]]:
]
def dashboard_suspicious_traffic(db: Session) -> list[dict[str, int | str]]:
sensitive_ports = {
22: "SSH exposed from outside IPAM",
3389: "RDP exposed from outside IPAM",
445: "SMB exposed from outside IPAM",
5900: "VNC exposed from outside IPAM",
5432: "PostgreSQL exposed from outside IPAM",
3306: "MySQL exposed from outside IPAM",
6379: "Redis exposed from outside IPAM",
}
subnets = db.scalars(select(Subnet).order_by(Subnet.cidr)).all()
workload_ips = {
address.address
for address in db.scalars(select(IpAddress).where(IpAddress.workload_id.is_not(None))).all()
}
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:
continue
key = (flow.source_ip, flow.destination_ip, port)
event = events.setdefault(
key,
{
"source": flow.source_ip,
"destination": flow.destination_ip,
"protocol": flow.protocol,
"port": port,
"bytes": 0,
"reason": sensitive_ports[port],
"severity": "high" if port in {22, 3389, 445} else "medium",
},
)
event["bytes"] = int(event["bytes"]) + int(flow.bytes or 0)
return sorted(events.values(), key=lambda item: int(item["bytes"]), reverse=True)[:5]
def proxmox_action(action: str) -> str:
return {"allow": "ACCEPT", "deny": "DROP", "reject": "REJECT"}.get(action, "ACCEPT")
@@ -455,12 +496,15 @@ def complete_setup(payload: SetupCompleteRequest, db: Session = Depends(get_db))
def dashboard(_: CurrentUser, db: Session = Depends(get_db)) -> dict:
last_syncs = db.scalars(select(Cluster).order_by(Cluster.updated_at.desc()).limit(5)).all()
faulty_nodes = db.scalars(select(Node).where(Node.status != "online")).all()
suspicious = dashboard_suspicious_traffic(db)
return {
"clusters": db.scalar(select(func.count()).select_from(Cluster)),
"nodes": db.scalar(select(func.count()).select_from(Node)),
"workloads": db.scalar(select(func.count()).select_from(Workload)),
"networks": db.scalar(select(func.count()).select_from(Network)),
"open_policy_violations": 1,
"open_policy_violations": len(suspicious),
"security_posture": "attention" if suspicious or faulty_nodes else "stable",
"suspicious_traffic": suspicious,
"last_syncs": [
{
"id": cluster.id,