From a4baedffd958b83da700431b53f91eb63c858803 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Sun, 28 Dec 2025 13:52:46 -0600 Subject: [PATCH] updated dashboard to pull data from db. This closes issue #10 --- src/database.py | 196 +++++++++++++++++++++++++++- src/models.py | 4 +- src/templates/dashboard_template.py | 2 +- src/tracker.py | 35 +++-- tests/test_credentials.sh | 4 +- 5 files changed, 218 insertions(+), 23 deletions(-) diff --git a/src/database.py b/src/database.py index 58a4505..e0de320 100644 --- a/src/database.py +++ b/src/database.py @@ -10,7 +10,7 @@ import stat from datetime import datetime from typing import Optional, List, Dict, Any -from sqlalchemy import create_engine +from sqlalchemy import create_engine, func, distinct, case from sqlalchemy.orm import sessionmaker, scoped_session, Session from models import Base, AccessLog, CredentialAttempt, AttackDetection, IpStats @@ -346,6 +346,200 @@ class DatabaseManager: finally: self.close_session() + def get_dashboard_counts(self) -> Dict[str, int]: + """ + Get aggregate statistics for the dashboard. + + Returns: + Dictionary with total_accesses, unique_ips, unique_paths, + suspicious_accesses, honeypot_triggered, honeypot_ips + """ + session = self.session + try: + # Get main aggregate counts in one query + result = session.query( + func.count(AccessLog.id).label('total_accesses'), + func.count(distinct(AccessLog.ip)).label('unique_ips'), + func.count(distinct(AccessLog.path)).label('unique_paths'), + func.sum(case((AccessLog.is_suspicious == True, 1), else_=0)).label('suspicious_accesses'), + func.sum(case((AccessLog.is_honeypot_trigger == True, 1), else_=0)).label('honeypot_triggered') + ).first() + + # Get unique IPs that triggered honeypots + honeypot_ips = session.query( + func.count(distinct(AccessLog.ip)) + ).filter(AccessLog.is_honeypot_trigger == True).scalar() or 0 + + return { + 'total_accesses': result.total_accesses or 0, + 'unique_ips': result.unique_ips or 0, + 'unique_paths': result.unique_paths or 0, + 'suspicious_accesses': int(result.suspicious_accesses or 0), + 'honeypot_triggered': int(result.honeypot_triggered or 0), + 'honeypot_ips': honeypot_ips + } + finally: + self.close_session() + + def get_top_ips(self, limit: int = 10) -> List[tuple]: + """ + Get top IP addresses by access count. + + Args: + limit: Maximum number of results + + Returns: + List of (ip, count) tuples ordered by count descending + """ + session = self.session + try: + results = session.query( + AccessLog.ip, + func.count(AccessLog.id).label('count') + ).group_by(AccessLog.ip).order_by( + func.count(AccessLog.id).desc() + ).limit(limit).all() + + return [(row.ip, row.count) for row in results] + finally: + self.close_session() + + def get_top_paths(self, limit: int = 10) -> List[tuple]: + """ + Get top paths by access count. + + Args: + limit: Maximum number of results + + Returns: + List of (path, count) tuples ordered by count descending + """ + session = self.session + try: + results = session.query( + AccessLog.path, + func.count(AccessLog.id).label('count') + ).group_by(AccessLog.path).order_by( + func.count(AccessLog.id).desc() + ).limit(limit).all() + + return [(row.path, row.count) for row in results] + finally: + self.close_session() + + def get_top_user_agents(self, limit: int = 10) -> List[tuple]: + """ + Get top user agents by access count. + + Args: + limit: Maximum number of results + + Returns: + List of (user_agent, count) tuples ordered by count descending + """ + session = self.session + try: + results = session.query( + AccessLog.user_agent, + func.count(AccessLog.id).label('count') + ).filter( + AccessLog.user_agent.isnot(None), + AccessLog.user_agent != '' + ).group_by(AccessLog.user_agent).order_by( + func.count(AccessLog.id).desc() + ).limit(limit).all() + + return [(row.user_agent, row.count) for row in results] + finally: + self.close_session() + + def get_recent_suspicious(self, limit: int = 20) -> List[Dict[str, Any]]: + """ + Get recent suspicious access attempts. + + Args: + limit: Maximum number of results + + Returns: + List of access log dictionaries with is_suspicious=True + """ + session = self.session + try: + logs = session.query(AccessLog).filter( + AccessLog.is_suspicious == True + ).order_by(AccessLog.timestamp.desc()).limit(limit).all() + + return [ + { + 'ip': log.ip, + 'path': log.path, + 'user_agent': log.user_agent, + 'timestamp': log.timestamp.isoformat() + } + for log in logs + ] + finally: + self.close_session() + + def get_honeypot_triggered_ips(self) -> List[tuple]: + """ + Get IPs that triggered honeypot paths with the paths they accessed. + + Returns: + List of (ip, [paths]) tuples + """ + session = self.session + try: + # Get all honeypot triggers grouped by IP + results = session.query( + AccessLog.ip, + AccessLog.path + ).filter( + AccessLog.is_honeypot_trigger == True + ).all() + + # Group paths by IP + ip_paths: Dict[str, List[str]] = {} + for row in results: + if row.ip not in ip_paths: + ip_paths[row.ip] = [] + if row.path not in ip_paths[row.ip]: + ip_paths[row.ip].append(row.path) + + return [(ip, paths) for ip, paths in ip_paths.items()] + finally: + self.close_session() + + def get_recent_attacks(self, limit: int = 20) -> List[Dict[str, Any]]: + """ + Get recent access logs that have attack detections. + + Args: + limit: Maximum number of results + + Returns: + List of access log dicts with attack_types included + """ + session = self.session + try: + # Get access logs that have attack detections + logs = session.query(AccessLog).join( + AttackDetection + ).order_by(AccessLog.timestamp.desc()).limit(limit).all() + + return [ + { + 'ip': log.ip, + 'path': log.path, + 'user_agent': log.user_agent, + 'timestamp': log.timestamp.isoformat(), + 'attack_types': [d.attack_type for d in log.attack_detections] + } + for log in logs + ] + finally: + self.close_session() + # Module-level singleton instance _db_manager = DatabaseManager() diff --git a/src/models.py b/src/models.py index f6e7d30..40dae0b 100644 --- a/src/models.py +++ b/src/models.py @@ -53,9 +53,11 @@ class AccessLog(Base): cascade="all, delete-orphan" ) - # Composite index for common queries + # Indexes for common queries __table_args__ = ( Index('ix_access_logs_ip_timestamp', 'ip', 'timestamp'), + Index('ix_access_logs_is_suspicious', 'is_suspicious'), + Index('ix_access_logs_is_honeypot_trigger', 'is_honeypot_trigger'), ) def __repr__(self) -> str: diff --git a/src/templates/dashboard_template.py b/src/templates/dashboard_template.py index 2323843..455833d 100644 --- a/src/templates/dashboard_template.py +++ b/src/templates/dashboard_template.py @@ -190,7 +190,7 @@ def generate_dashboard(stats: dict) -> str:
-

🍯 Honeypot Triggers

+

🍯 Honeypot Triggers by IP

diff --git a/src/tracker.py b/src/tracker.py index 2d3d34a..4c89c0b 100644 --- a/src/tracker.py +++ b/src/tracker.py @@ -276,21 +276,20 @@ class AccessTracker: return [(ip, paths) for ip, paths in self.honeypot_triggered.items()] def get_stats(self) -> Dict: - """Get statistics summary""" - suspicious_count = sum(1 for log in self.access_log if log.get('suspicious', False)) - honeypot_count = sum(1 for log in self.access_log if log.get('honeypot_triggered', False)) - return { - 'total_accesses': len(self.access_log), - 'unique_ips': len(self.ip_counts), - 'unique_paths': len(self.path_counts), - 'suspicious_accesses': suspicious_count, - 'honeypot_triggered': honeypot_count, - 'honeypot_ips': len(self.honeypot_triggered), - 'top_ips': self.get_top_ips(10), - 'top_paths': self.get_top_paths(10), - 'top_user_agents': self.get_top_user_agents(10), - 'recent_suspicious': self.get_suspicious_accesses(20), - 'honeypot_triggered_ips': self.get_honeypot_triggered_ips(), - 'attack_types': self.get_attack_type_accesses(20), - 'credential_attempts': self.credential_attempts[-50:] # Last 50 attempts - } + """Get statistics summary from database.""" + if not self.db: + raise RuntimeError("Database not available for dashboard stats") + + # Get aggregate counts from database + stats = self.db.get_dashboard_counts() + + # Add detailed lists from database + stats['top_ips'] = self.db.get_top_ips(10) + stats['top_paths'] = self.db.get_top_paths(10) + stats['top_user_agents'] = self.db.get_top_user_agents(10) + stats['recent_suspicious'] = self.db.get_recent_suspicious(20) + stats['honeypot_triggered_ips'] = self.db.get_honeypot_triggered_ips() + stats['attack_types'] = self.db.get_recent_attacks(20) + stats['credential_attempts'] = self.db.get_credential_attempts(limit=50) + + return stats diff --git a/tests/test_credentials.sh b/tests/test_credentials.sh index 6379b92..68ee2c0 100755 --- a/tests/test_credentials.sh +++ b/tests/test_credentials.sh @@ -134,9 +134,9 @@ echo -e "${GREEN}✓ All credential tests completed!${NC}" echo -e "${BLUE}========================================${NC}\n" echo -e "${YELLOW}Check the results:${NC}" -echo -e " 1. View the log file: ${GREEN}cat src/logs/credentials.log${NC}" +echo -e " 1. View the log file: ${GREEN}tail -20 logs/credentials.log${NC}" echo -e " 2. View the dashboard: ${GREEN}${BASE_URL}/dashboard${NC}" -echo -e " 3. Check recent logs: ${GREEN}tail -20 src/logs/krawl.log${NC}\n" +echo -e " 3. Check recent logs: ${GREEN}tail -20 logs/access.log ${NC}\n" # Display last 10 credential entries if log file exists if [ -f "src/logs/credentials.log" ]; then