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
|
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
|
||||||
|
|||||||
36
src/app.py
36
src/app.py
@@ -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("/")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user