""" InnotexBoard - Interface d'administration Debian Backend FastAPI """ from fastapi import FastAPI, Request, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.trustedhost import TrustedHostMiddleware from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded import uvicorn import logging import os from pathlib import Path from app.core.config import settings from app.api.routes import api_router from app.api.websocket import router as ws_router # Configuration du répertoire de logs log_dir = Path(os.getenv('LOG_DIR', '/var/log/innotexboard')) if not log_dir.exists(): try: log_dir.mkdir(parents=True, exist_ok=True) except PermissionError: # Fallback sur un répertoire local si pas de permissions log_dir = Path('./logs') log_dir.mkdir(parents=True, exist_ok=True) log_file = log_dir / 'security.log' # Configuration du logging de sécurité logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler(str(log_file)), logging.StreamHandler() ] ) logger = logging.getLogger("innotexboard.security") # Réduire le bruit des logs watchfiles en développement logging.getLogger("watchfiles.main").setLevel(logging.WARNING) logging.getLogger("watchfiles").setLevel(logging.WARNING) # Rate limiting pour prévenir les attaques brute force et DDoS limiter = Limiter(key_func=get_remote_address, default_limits=["200/minute"]) # Initialiser l'application FastAPI app = FastAPI( title=settings.API_TITLE, description=settings.API_DESCRIPTION, version=settings.API_VERSION, docs_url="/docs" if settings.DEBUG else None, # Désactiver docs en production redoc_url="/redoc" if settings.DEBUG else None, openapi_url="/openapi.json" if settings.DEBUG else None, ) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # Middleware de sécurité CORS - Configuration stricte app.add_middleware( CORSMiddleware, allow_origins=settings.ALLOWED_ORIGINS, # Liste blanche uniquement allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH"], # Méthodes explicites allow_headers=["Authorization", "Content-Type", "X-Requested-With"], expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"], max_age=3600, # Cache preflight 1h ) # Middleware pour les hôtes de confiance app.add_middleware( TrustedHostMiddleware, allowed_hosts=settings.ALLOWED_HOSTS, ) @app.middleware("http") async def security_headers_middleware(request: Request, call_next): """Ajoute les headers de sécurité HTTP à toutes les réponses""" response = await call_next(request) # Protection contre le clickjacking response.headers["X-Frame-Options"] = "DENY" # Force HTTPS et HSTS (HTTP Strict Transport Security) response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" # Prévention du MIME-sniffing response.headers["X-Content-Type-Options"] = "nosniff" # Protection XSS intégrée au navigateur response.headers["X-XSS-Protection"] = "1; mode=block" # Content Security Policy - Politique stricte response.headers["Content-Security-Policy"] = ( "default-src 'self'; " "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " "style-src 'self' 'unsafe-inline'; " "img-src 'self' data: https:; " "font-src 'self' data:; " "connect-src 'self'; " "frame-ancestors 'none'; " "base-uri 'self'; " "form-action 'self';" ) # Contrôle des permissions response.headers["Permissions-Policy"] = ( "geolocation=(), microphone=(), camera=(), payment=()" ) # Désactiver la divulgation de la version du serveur response.headers["Server"] = "InnotexBoard" return response @app.middleware("http") async def log_security_events(request: Request, call_next): """Log les événements de sécurité suspects""" client_ip = get_remote_address(request) # Logger les tentatives d'accès aux endpoints sensibles if "/api/v1/auth" in request.url.path: logger.info(f"Auth attempt from {client_ip} to {request.url.path}") response = await call_next(request) # Logger les erreurs d'authentification if response.status_code == 401: logger.warning(f"Failed authentication from {client_ip} to {request.url.path}") # Logger les tentatives d'accès non autorisé if response.status_code == 403: logger.warning(f"Forbidden access attempt from {client_ip} to {request.url.path}") return response # Inclure les routes API app.include_router(api_router, prefix="/api/v1") app.include_router(ws_router, prefix="/api/v1") @app.get("/") @limiter.limit("10/minute") async def root(request: Request): """Endpoint racine""" return { "message": "Bienvenue sur InnotexBoard", "version": settings.API_VERSION, "docs": "/docs" if settings.DEBUG else "Documentation disabled in production", "openapi": "/openapi.json" if settings.DEBUG else "OpenAPI disabled in production" } @app.get("/health") async def health_check(): """Vérification de la santé de l'application""" return { "status": "healthy", "service": "InnotexBoard API" } if __name__ == "__main__": # Configuration pour le développement uvicorn.run( "main:app", host="0.0.0.0", port=8000, reload=True, log_level="info", )