protection de l'application contre les attaques numériques
This commit is contained in:
127
backend/main.py
127
backend/main.py
@@ -3,49 +3,158 @@ InnotexBoard - Interface d'administration Debian
|
||||
Backend FastAPI
|
||||
"""
|
||||
|
||||
from fastapi import 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,
|
||||
)
|
||||
|
||||
# Middleware de sécurité CORS
|
||||
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,
|
||||
allow_origins=settings.ALLOWED_ORIGINS, # Liste blanche uniquement
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
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=["localhost", "127.0.0.1"],
|
||||
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("/")
|
||||
async def root():
|
||||
@limiter.limit("10/minute")
|
||||
async def root(request: Request):
|
||||
"""Endpoint racine"""
|
||||
return {
|
||||
"message": "Bienvenue sur InnotexBoard",
|
||||
"version": settings.API_VERSION,
|
||||
"docs": "/docs",
|
||||
"openapi": "/openapi.json"
|
||||
"docs": "/docs" if settings.DEBUG else "Documentation disabled in production",
|
||||
"openapi": "/openapi.json" if settings.DEBUG else "OpenAPI disabled in production"
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user