Merge pull request #124 from BlessedRebuS/feat/improve-logging

Feat/improved logging
This commit is contained in:
Patrick Di Fazio
2026-03-09 18:12:15 +01:00
committed by GitHub
7 changed files with 54 additions and 12 deletions

View File

@@ -2,8 +2,8 @@ apiVersion: v2
name: krawl-chart name: krawl-chart
description: A Helm chart for Krawl honeypot server description: A Helm chart for Krawl honeypot server
type: application type: application
version: 1.1.6 version: 1.1.7
appVersion: 1.1.6 appVersion: 1.1.7
keywords: keywords:
- honeypot - honeypot
- security - security

View File

@@ -16,7 +16,7 @@ from config import get_config
from tracker import AccessTracker, set_tracker from tracker import AccessTracker, set_tracker
from database import initialize_database from database import initialize_database
from tasks_master import get_tasksmaster from tasks_master import get_tasksmaster
from logger import initialize_logging, get_app_logger from logger import initialize_logging, get_app_logger, get_access_logger
from generators import random_server_header from generators import random_server_header
@@ -121,11 +121,43 @@ def create_app() -> FastAPI:
application.add_middleware(DeceptionMiddleware) application.add_middleware(DeceptionMiddleware)
# Banned IP check middleware (outermost — runs first on request) # Banned IP check middleware
from middleware.ban_check import BanCheckMiddleware from middleware.ban_check import BanCheckMiddleware
application.add_middleware(BanCheckMiddleware) application.add_middleware(BanCheckMiddleware)
# Access log middleware (outermost — logs every request with real client IP)
@application.middleware("http")
async def access_log_middleware(request: Request, call_next):
from dependencies import get_client_ip
try:
response: Response = await call_next(request)
except ConnectionResetError:
client_ip = get_client_ip(request)
path = request.url.path
method = request.method
get_access_logger().info(f"[BANNED] [{method}] {client_ip} - {path}")
raise
client_ip = get_client_ip(request)
path = request.url.path
method = request.method
status = response.status_code
access_logger = get_access_logger()
user_agent = request.headers.get("User-Agent", "")
tracker = request.app.state.tracker
suspicious = tracker.is_suspicious_user_agent(user_agent)
if suspicious:
access_logger.warning(
f"[SUSPICIOUS] [{method}] {client_ip} - {path} - {status} - {user_agent[:50]}"
)
else:
access_logger.info(f"[{method}] {client_ip} - {path} - {status}")
return response
# Mount static files for the dashboard # Mount static files for the dashboard
config = get_config() config = get_config()
secret = config.dashboard_secret_path.lstrip("/") secret = config.dashboard_secret_path.lstrip("/")

View File

@@ -258,6 +258,9 @@ def override_config_from_env(config: Config = None):
try: try:
field_type = config.__dataclass_fields__[field].type field_type = config.__dataclass_fields__[field].type
env_value = os.environ[env_var] env_value = os.environ[env_var]
# If password is overridden, it's no longer auto-generated
if field == "dashboard_password":
config.dashboard_password_generated = False
if field_type == int: if field_type == int:
setattr(config, field, int(env_value)) setattr(config, field, int(env_value))
elif field_type == float: elif field_type == float:

View File

@@ -112,6 +112,10 @@ class LoggerManager:
credential_file_handler.setFormatter(credential_format) credential_file_handler.setFormatter(credential_format)
self._credential_logger.addHandler(credential_file_handler) self._credential_logger.addHandler(credential_file_handler)
# Disable uvicorn's default access log to avoid duplicate entries
# with the wrong (proxy) IP. Our custom access logger handles this.
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
self._initialized = True self._initialized = True
@property @property

View File

@@ -2,6 +2,7 @@
""" """
Middleware for checking if client IP is banned. Middleware for checking if client IP is banned.
Resets the connection for banned IPs instead of sending a response.
""" """
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
@@ -11,6 +12,13 @@ from starlette.responses import Response
from dependencies import get_client_ip from dependencies import get_client_ip
class ConnectionResetResponse(Response):
"""Response that abruptly closes the connection without sending data."""
async def __call__(self, scope, receive, send):
raise ConnectionResetError()
class BanCheckMiddleware(BaseHTTPMiddleware): class BanCheckMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next): async def dispatch(self, request: Request, call_next):
# Skip ban check for dashboard routes # Skip ban check for dashboard routes
@@ -23,7 +31,7 @@ class BanCheckMiddleware(BaseHTTPMiddleware):
tracker = request.app.state.tracker tracker = request.app.state.tracker
if tracker.is_banned_ip(client_ip): if tracker.is_banned_ip(client_ip):
return Response(status_code=500) return ConnectionResetResponse()
response = await call_next(request) response = await call_next(request)
return response return response

View File

@@ -73,6 +73,7 @@ async def authenticate(request: Request, body: AuthRequest):
if hmac.compare_digest(body.fingerprint, expected): if hmac.compare_digest(body.fingerprint, expected):
# Success — clear failed attempts # Success — clear failed attempts
_auth_attempts.pop(ip, None) _auth_attempts.pop(ip, None)
get_app_logger().info(f"[AUTH] Successful login from {ip}")
token = secrets.token_hex(32) token = secrets.token_hex(32)
_auth_tokens.add(token) _auth_tokens.add(token)
response = JSONResponse(content={"authenticated": True}) response = JSONResponse(content={"authenticated": True})
@@ -85,6 +86,7 @@ async def authenticate(request: Request, body: AuthRequest):
return response return response
# Failed attempt — track and possibly lock out # Failed attempt — track and possibly lock out
get_app_logger().warning(f"[AUTH] Failed login attempt from {ip}")
if not record: if not record:
record = {"attempts": 0, "locked_until": 0, "lockouts": 0} record = {"attempts": 0, "locked_until": 0, "lockouts": 0}
_auth_attempts[ip] = record _auth_attempts[ip] = record

View File

@@ -394,13 +394,6 @@ async def trap_page(request: Request, path: str):
is_suspicious = tracker.is_suspicious_user_agent(user_agent) is_suspicious = tracker.is_suspicious_user_agent(user_agent)
if is_suspicious:
access_logger.warning(
f"[SUSPICIOUS] {client_ip} - {user_agent[:50]} - {full_path}"
)
else:
access_logger.info(f"[REQUEST] {client_ip} - {full_path}")
# Record access unless the router dependency already handled it # Record access unless the router dependency already handled it
# (attack pattern or honeypot path → already recorded by _track_honeypot_request) # (attack pattern or honeypot path → already recorded by _track_honeypot_request)
if not tracker.detect_attack_type(full_path) and not tracker.is_honeypot_path( if not tracker.detect_attack_type(full_path) and not tracker.is_honeypot_path(