Merge pull request #124 from BlessedRebuS/feat/improve-logging
Feat/improved logging
This commit is contained in:
@@ -2,8 +2,8 @@ apiVersion: v2
|
||||
name: krawl-chart
|
||||
description: A Helm chart for Krawl honeypot server
|
||||
type: application
|
||||
version: 1.1.6
|
||||
appVersion: 1.1.6
|
||||
version: 1.1.7
|
||||
appVersion: 1.1.7
|
||||
keywords:
|
||||
- honeypot
|
||||
- security
|
||||
|
||||
36
src/app.py
36
src/app.py
@@ -16,7 +16,7 @@ from config import get_config
|
||||
from tracker import AccessTracker, set_tracker
|
||||
from database import initialize_database
|
||||
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
|
||||
|
||||
|
||||
@@ -121,11 +121,43 @@ def create_app() -> FastAPI:
|
||||
|
||||
application.add_middleware(DeceptionMiddleware)
|
||||
|
||||
# Banned IP check middleware (outermost — runs first on request)
|
||||
# Banned IP check middleware
|
||||
from middleware.ban_check import 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
|
||||
config = get_config()
|
||||
secret = config.dashboard_secret_path.lstrip("/")
|
||||
|
||||
@@ -258,6 +258,9 @@ def override_config_from_env(config: Config = None):
|
||||
try:
|
||||
field_type = config.__dataclass_fields__[field].type
|
||||
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:
|
||||
setattr(config, field, int(env_value))
|
||||
elif field_type == float:
|
||||
|
||||
@@ -112,6 +112,10 @@ class LoggerManager:
|
||||
credential_file_handler.setFormatter(credential_format)
|
||||
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
|
||||
|
||||
@property
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
"""
|
||||
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
|
||||
@@ -11,6 +12,13 @@ from starlette.responses import Response
|
||||
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):
|
||||
async def dispatch(self, request: Request, call_next):
|
||||
# Skip ban check for dashboard routes
|
||||
@@ -23,7 +31,7 @@ class BanCheckMiddleware(BaseHTTPMiddleware):
|
||||
tracker = request.app.state.tracker
|
||||
|
||||
if tracker.is_banned_ip(client_ip):
|
||||
return Response(status_code=500)
|
||||
return ConnectionResetResponse()
|
||||
|
||||
response = await call_next(request)
|
||||
return response
|
||||
|
||||
@@ -73,6 +73,7 @@ async def authenticate(request: Request, body: AuthRequest):
|
||||
if hmac.compare_digest(body.fingerprint, expected):
|
||||
# Success — clear failed attempts
|
||||
_auth_attempts.pop(ip, None)
|
||||
get_app_logger().info(f"[AUTH] Successful login from {ip}")
|
||||
token = secrets.token_hex(32)
|
||||
_auth_tokens.add(token)
|
||||
response = JSONResponse(content={"authenticated": True})
|
||||
@@ -85,6 +86,7 @@ async def authenticate(request: Request, body: AuthRequest):
|
||||
return response
|
||||
|
||||
# Failed attempt — track and possibly lock out
|
||||
get_app_logger().warning(f"[AUTH] Failed login attempt from {ip}")
|
||||
if not record:
|
||||
record = {"attempts": 0, "locked_until": 0, "lockouts": 0}
|
||||
_auth_attempts[ip] = record
|
||||
|
||||
@@ -394,13 +394,6 @@ async def trap_page(request: Request, path: str):
|
||||
|
||||
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
|
||||
# (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(
|
||||
|
||||
Reference in New Issue
Block a user