130 lines
4.3 KiB
Python
130 lines
4.3 KiB
Python
"""
|
|
Endpoint de recherche globale rapide — utilisé par la barre Cmd+K
|
|
"""
|
|
from fastapi import APIRouter, Query
|
|
from ..database import db
|
|
|
|
router = APIRouter(prefix="/api/search", tags=["search"])
|
|
|
|
IP_RE = r"^(\d{1,3}\.){0,3}\d{1,3}$"
|
|
|
|
|
|
@router.get("/quick")
|
|
async def quick_search(q: str = Query(..., min_length=1, max_length=100)):
|
|
"""
|
|
Recherche unifiée sur IPs, JA4, ASN, hosts.
|
|
Retourne jusqu'à 5 résultats par catégorie.
|
|
"""
|
|
q = q.strip()
|
|
pattern = f"%{q}%"
|
|
results = []
|
|
|
|
# ── IPs ──────────────────────────────────────────────────────────────────
|
|
ip_rows = db.query(
|
|
"""
|
|
SELECT
|
|
replaceRegexpAll(toString(src_ip), '^::ffff:', '') AS clean_ip,
|
|
count() AS hits,
|
|
max(detected_at) AS last_seen,
|
|
any(threat_level) AS threat_level
|
|
FROM ml_detected_anomalies
|
|
WHERE ilike(toString(src_ip), %(p)s)
|
|
AND detected_at >= now() - INTERVAL 24 HOUR
|
|
GROUP BY clean_ip
|
|
ORDER BY hits DESC
|
|
LIMIT 5
|
|
""",
|
|
{"p": pattern},
|
|
)
|
|
for r in ip_rows.result_rows:
|
|
ip = str(r[0])
|
|
results.append({
|
|
"type": "ip",
|
|
"value": ip,
|
|
"label": ip,
|
|
"meta": f"{r[1]} détections · {r[3]}",
|
|
"url": f"/detections/ip/{ip}",
|
|
"investigation_url": f"/investigation/{ip}",
|
|
})
|
|
|
|
# ── JA4 fingerprints ─────────────────────────────────────────────────────
|
|
ja4_rows = db.query(
|
|
"""
|
|
SELECT
|
|
ja4,
|
|
count() AS hits,
|
|
uniq(src_ip) AS unique_ips
|
|
FROM ml_detected_anomalies
|
|
WHERE ilike(ja4, %(p)s)
|
|
AND ja4 != ''
|
|
AND detected_at >= now() - INTERVAL 24 HOUR
|
|
GROUP BY ja4
|
|
ORDER BY hits DESC
|
|
LIMIT 5
|
|
""",
|
|
{"p": pattern},
|
|
)
|
|
for r in ja4_rows.result_rows:
|
|
results.append({
|
|
"type": "ja4",
|
|
"value": str(r[0]),
|
|
"label": str(r[0]),
|
|
"meta": f"{r[1]} détections · {r[2]} IPs",
|
|
"url": f"/investigation/ja4/{r[0]}",
|
|
})
|
|
|
|
# ── Hosts ─────────────────────────────────────────────────────────────────
|
|
host_rows = db.query(
|
|
"""
|
|
SELECT
|
|
host,
|
|
count() AS hits,
|
|
uniq(src_ip) AS unique_ips
|
|
FROM ml_detected_anomalies
|
|
WHERE ilike(host, %(p)s)
|
|
AND host != ''
|
|
AND detected_at >= now() - INTERVAL 24 HOUR
|
|
GROUP BY host
|
|
ORDER BY hits DESC
|
|
LIMIT 5
|
|
""",
|
|
{"p": pattern},
|
|
)
|
|
for r in host_rows.result_rows:
|
|
results.append({
|
|
"type": "host",
|
|
"value": str(r[0]),
|
|
"label": str(r[0]),
|
|
"meta": f"{r[1]} hits · {r[2]} IPs",
|
|
"url": f"/detections?search={r[0]}",
|
|
})
|
|
|
|
# ── ASN ───────────────────────────────────────────────────────────────────
|
|
asn_rows = db.query(
|
|
"""
|
|
SELECT
|
|
asn_org,
|
|
asn_number,
|
|
count() AS hits,
|
|
uniq(src_ip) AS unique_ips
|
|
FROM ml_detected_anomalies
|
|
WHERE (ilike(asn_org, %(p)s) OR ilike(asn_number, %(p)s))
|
|
AND asn_org != '' AND asn_number != ''
|
|
AND detected_at >= now() - INTERVAL 24 HOUR
|
|
GROUP BY asn_org, asn_number
|
|
ORDER BY hits DESC
|
|
LIMIT 5
|
|
""",
|
|
{"p": pattern},
|
|
)
|
|
for r in asn_rows.result_rows:
|
|
results.append({
|
|
"type": "asn",
|
|
"value": str(r[1]),
|
|
"label": f"AS{r[1]} — {r[0]}",
|
|
"meta": f"{r[2]} hits · {r[3]} IPs",
|
|
"url": f"/detections?asn={r[1]}",
|
|
})
|
|
|
|
return {"query": q, "results": results}
|