starting full refactor with FastAPI routes + HTMX and AlpineJS on client side
This commit is contained in:
149
src/app.py
Normal file
149
src/app.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
FastAPI application factory for the Krawl honeypot.
|
||||
Replaces the old http.server-based server.py.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
from config import get_config
|
||||
from tracker import AccessTracker
|
||||
from database import initialize_database
|
||||
from tasks_master import get_tasksmaster
|
||||
from logger import initialize_logging, get_app_logger
|
||||
from generators import random_server_header
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""Application startup and shutdown lifecycle."""
|
||||
config = get_config()
|
||||
|
||||
# Initialize logging
|
||||
initialize_logging()
|
||||
app_logger = get_app_logger()
|
||||
|
||||
# Initialize database
|
||||
try:
|
||||
initialize_database(config.database_path)
|
||||
app_logger.info(f"Database initialized at: {config.database_path}")
|
||||
except Exception as e:
|
||||
app_logger.warning(
|
||||
f"Database initialization failed: {e}. Continuing with in-memory only."
|
||||
)
|
||||
|
||||
# Initialize tracker
|
||||
tracker = AccessTracker(config.max_pages_limit, config.ban_duration_seconds)
|
||||
|
||||
# Store in app.state for dependency injection
|
||||
app.state.config = config
|
||||
app.state.tracker = tracker
|
||||
|
||||
# Load webpages file if provided via env var
|
||||
webpages = None
|
||||
webpages_file = os.environ.get("KRAWL_WEBPAGES_FILE")
|
||||
if webpages_file:
|
||||
try:
|
||||
with open(webpages_file, "r") as f:
|
||||
webpages = f.readlines()
|
||||
if not webpages:
|
||||
app_logger.warning(
|
||||
"The webpages file was empty. Using randomly generated links."
|
||||
)
|
||||
webpages = None
|
||||
except IOError:
|
||||
app_logger.warning("Can't read webpages file. Using randomly generated links.")
|
||||
app.state.webpages = webpages
|
||||
|
||||
# Initialize canary counter
|
||||
app.state.counter = config.canary_token_tries
|
||||
|
||||
# Start scheduled tasks
|
||||
tasks_master = get_tasksmaster()
|
||||
tasks_master.run_scheduled_tasks()
|
||||
|
||||
banner = f"""
|
||||
|
||||
============================================================
|
||||
DASHBOARD AVAILABLE AT
|
||||
{config.dashboard_secret_path}
|
||||
============================================================
|
||||
"""
|
||||
app_logger.info(banner)
|
||||
app_logger.info(f"Starting deception server on port {config.port}...")
|
||||
if config.canary_token_url:
|
||||
app_logger.info(
|
||||
f"Canary token will appear after {config.canary_token_tries} tries"
|
||||
)
|
||||
else:
|
||||
app_logger.info(
|
||||
"No canary token configured (set CANARY_TOKEN_URL to enable)"
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
# Shutdown
|
||||
app_logger.info("Server shutting down...")
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""Create and configure the FastAPI application."""
|
||||
application = FastAPI(
|
||||
docs_url=None,
|
||||
redoc_url=None,
|
||||
openapi_url=None,
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Random server header middleware (innermost — runs last on request, first on response)
|
||||
@application.middleware("http")
|
||||
async def server_header_middleware(request: Request, call_next):
|
||||
response: Response = await call_next(request)
|
||||
response.headers["Server"] = random_server_header()
|
||||
return response
|
||||
|
||||
# Deception detection middleware (path traversal, XXE, command injection)
|
||||
from middleware.deception import DeceptionMiddleware
|
||||
|
||||
application.add_middleware(DeceptionMiddleware)
|
||||
|
||||
# Banned IP check middleware (outermost — runs first on request)
|
||||
from middleware.ban_check import BanCheckMiddleware
|
||||
|
||||
application.add_middleware(BanCheckMiddleware)
|
||||
|
||||
# Mount static files for the dashboard
|
||||
config = get_config()
|
||||
secret = config.dashboard_secret_path.lstrip("/")
|
||||
static_dir = os.path.join(os.path.dirname(__file__), "templates", "static")
|
||||
application.mount(
|
||||
f"/{secret}/static",
|
||||
StaticFiles(directory=static_dir),
|
||||
name="dashboard-static",
|
||||
)
|
||||
|
||||
# Import and include routers
|
||||
from routes.honeypot import router as honeypot_router
|
||||
from routes.api import router as api_router
|
||||
from routes.dashboard import router as dashboard_router
|
||||
from routes.htmx import router as htmx_router
|
||||
|
||||
# Dashboard/API/HTMX routes (prefixed with secret path, before honeypot catch-all)
|
||||
dashboard_prefix = f"/{secret}"
|
||||
application.include_router(dashboard_router, prefix=dashboard_prefix)
|
||||
application.include_router(api_router, prefix=dashboard_prefix)
|
||||
application.include_router(htmx_router, prefix=dashboard_prefix)
|
||||
|
||||
# Honeypot routes (catch-all must be last)
|
||||
application.include_router(honeypot_router)
|
||||
|
||||
return application
|
||||
|
||||
|
||||
app = create_app()
|
||||
Reference in New Issue
Block a user