feat: add need_reevaluation column to IpStats and update related logic

This commit is contained in:
Lorenzo Venerandi
2026-02-22 16:03:08 +01:00
parent 2f82d3a3bd
commit db848e7ecb
4 changed files with 50 additions and 7 deletions

View File

@@ -261,7 +261,7 @@ class DatabaseManager:
session.add(detection) session.add(detection)
# Update IP stats # Update IP stats
self._update_ip_stats(session, ip) self._update_ip_stats(session, ip, is_suspicious)
session.commit() session.commit()
return access_log.id return access_log.id
@@ -313,13 +313,16 @@ class DatabaseManager:
finally: finally:
self.close_session() self.close_session()
def _update_ip_stats(self, session: Session, ip: str) -> None: def _update_ip_stats(
self, session: Session, ip: str, is_suspicious: bool = False
) -> None:
""" """
Update IP statistics (upsert pattern). Update IP statistics (upsert pattern).
Args: Args:
session: Active database session session: Active database session
ip: IP address to update ip: IP address to update
is_suspicious: Whether the request was flagged as suspicious
""" """
sanitized_ip = sanitize_ip(ip) sanitized_ip = sanitize_ip(ip)
now = datetime.now() now = datetime.now()
@@ -329,9 +332,15 @@ class DatabaseManager:
if ip_stats: if ip_stats:
ip_stats.total_requests += 1 ip_stats.total_requests += 1
ip_stats.last_seen = now ip_stats.last_seen = now
if is_suspicious:
ip_stats.need_reevaluation = True
else: else:
ip_stats = IpStats( ip_stats = IpStats(
ip=sanitized_ip, total_requests=1, first_seen=now, last_seen=now ip=sanitized_ip,
total_requests=1,
first_seen=now,
last_seen=now,
need_reevaluation=is_suspicious,
) )
session.add(ip_stats) session.add(ip_stats)
@@ -385,6 +394,7 @@ class DatabaseManager:
ip_stats.category = category ip_stats.category = category
ip_stats.category_scores = category_scores ip_stats.category_scores = category_scores
ip_stats.last_analysis = last_analysis ip_stats.last_analysis = last_analysis
ip_stats.need_reevaluation = False
try: try:
session.commit() session.commit()
@@ -637,6 +647,24 @@ class DatabaseManager:
finally: finally:
self.close_session() self.close_session()
def get_ips_needing_reevaluation(self) -> List[str]:
"""
Get all IP addresses that have been flagged for reevaluation.
Returns:
List of IP addresses where need_reevaluation is True
"""
session = self.session
try:
ips = (
session.query(IpStats.ip)
.filter(IpStats.need_reevaluation == True)
.all()
)
return [ip[0] for ip in ips]
finally:
self.close_session()
def get_access_logs( def get_access_logs(
self, self,
limit: int = 100, limit: int = 100,

View File

@@ -38,6 +38,16 @@ def _migrate_raw_request_column(cursor) -> bool:
return True return True
def _migrate_need_reevaluation_column(cursor) -> bool:
"""Add need_reevaluation column to ip_stats if missing."""
if _column_exists(cursor, "ip_stats", "need_reevaluation"):
return False
cursor.execute(
"ALTER TABLE ip_stats ADD COLUMN need_reevaluation BOOLEAN DEFAULT 0"
)
return True
def _migrate_performance_indexes(cursor) -> List[str]: def _migrate_performance_indexes(cursor) -> List[str]:
"""Add performance indexes to attack_detections if missing.""" """Add performance indexes to attack_detections if missing."""
added = [] added = []
@@ -77,6 +87,9 @@ def run_migrations(database_path: str) -> None:
if _migrate_raw_request_column(cursor): if _migrate_raw_request_column(cursor):
applied.append("add raw_request column to access_logs") applied.append("add raw_request column to access_logs")
if _migrate_need_reevaluation_column(cursor):
applied.append("add need_reevaluation column to ip_stats")
idx_added = _migrate_performance_indexes(cursor) idx_added = _migrate_performance_indexes(cursor)
for idx in idx_added: for idx in idx_added:
applied.append(f"add index {idx}") applied.append(f"add index {idx}")

View File

@@ -200,6 +200,9 @@ class IpStats(Base):
category_scores: Mapped[Dict[str, int]] = mapped_column(JSON, nullable=True) category_scores: Mapped[Dict[str, int]] = mapped_column(JSON, nullable=True)
manual_category: Mapped[bool] = mapped_column(Boolean, default=False, nullable=True) manual_category: Mapped[bool] = mapped_column(Boolean, default=False, nullable=True)
last_analysis: Mapped[datetime] = mapped_column(DateTime, nullable=True) last_analysis: Mapped[datetime] = mapped_column(DateTime, nullable=True)
need_reevaluation: Mapped[bool] = mapped_column(
Boolean, default=False, nullable=True
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<IpStats(ip='{self.ip}', total_requests={self.total_requests})>" return f"<IpStats(ip='{self.ip}', total_requests={self.total_requests})>"

View File

@@ -94,12 +94,11 @@ def main():
"attack_url": 0, "attack_url": 0,
}, },
} }
# Get IPs with recent activity (last minute to match cron schedule) # Get IPs flagged for reevaluation (set when a suspicious request arrives)
recent_accesses = db_manager.get_access_logs(limit=999999999, since_minutes=1) ips_to_analyze = set(db_manager.get_ips_needing_reevaluation())
ips_to_analyze = {item["ip"] for item in recent_accesses}
if not ips_to_analyze: if not ips_to_analyze:
app_logger.debug("[Background Task] analyze-ips: No recent activity, skipping") app_logger.debug("[Background Task] analyze-ips: No IPs need reevaluation, skipping")
return return
for ip in ips_to_analyze: for ip in ips_to_analyze: