# AUDIT TECHNIQUE EXHAUSTIF - VEZA STREAM SERVER ## Module: veza-stream-server (Rust) **Date**: 2025-01-27 **Version du module**: 0.2.0 **Auditeur**: AI Technical Auditor **Méthodologie**: Audit ultra-exhaustif, priorisé, actionnable --- # PHASE A — CARTOGRAPHIE DU MODULE ## 1. But du module **Rôle exact dans Veza** : Serveur de streaming audio haute performance avec : - Streaming HTTP Range Requests (partial content) - WebSocket pour streaming temps réel synchronisé multi-client - Transcodage audio (FFmpeg pipeline) - Génération HLS (HTTP Live Streaming) - Synchronisation multi-client (<10ms de précision) - Cache audio et compression adaptative - Analytics et monitoring **Position dans l'architecture Veza** : - **Backend Go** (veza-backend-api) : API REST, gestion utilisateurs, métadonnées - **Chat Server Rust** : WebSocket chat - **Stream Server Rust** (ce module) : Streaming audio, transcodage, HLS - **Frontend React** : Interface utilisateur ## 2. Entrées / Sorties ### APIs exposées #### HTTP REST (Port 3002/8082) - `GET /` : Health check basique - `GET /health` : Health check détaillé - `GET /healthz` : Liveness probe (K8s) - `GET /readyz` : Readiness probe (K8s) - `GET /metrics` : Métriques Prometheus - `GET /stream/:filename?expires=&sig=` : Streaming audio avec signature HMAC - `POST /internal/jobs/transcode` : Transcodage interne (non authentifié ⚠️) - `POST /v1/stream/transcode` : Transcodage HLS - `GET /v1/stream/job/:id` : Statut job transcodage - `GET /api/streams/jobs/:id/status` : Statut détaillé job - `GET /v1/stream/hls/:job_id/index.m3u8` : Manifest HLS master - `GET /v1/stream/hls/:job_id/:segment` : Segment HLS (.ts) - `GET /hls/:track_id/master.m3u8` : Master playlist HLS - `GET /hls/:track_id/:quality/playlist.m3u8` : Playlist qualité spécifique - `GET /hls/:track_id/:quality/:segment` : Segment HLS #### WebSocket (Port 3002/8082) - `GET /ws?token=&user_id=` : Connexion WebSocket pour streaming temps réel - Commandes : Play, Pause, Stop, Seek, Subscribe, GetStatus, Ping - Événements : PlaybackStarted, PlaybackPaused, PlaybackFinished, Error #### gRPC (si activé) - Service `StreamService` (proto/stream/stream.proto) - Service `AuthService` (proto/common/auth.proto) ### Formats de données **JSON** : - Requêtes/Réponses REST : JSON standard - WebSocket messages : JSON avec `type` discriminator - Health checks : JSON structuré **Protobuf** : - gRPC services : Messages définis dans `proto/` **Binaire** : - Audio streams : MP3, WAV, FLAC, AAC, Opus - HLS segments : .ts (MPEG-TS) - HLS playlists : .m3u8 (text) ## 3. Dépendances internes ### Modules Veza partagés - `veza-common` : Types communs, utilitaires partagés - Path : `../veza-common` (workspace local) ### Types communs attendus - UUID pour IDs (utilisateurs, tracks, streams) - Formats de dates/timestamps (chrono) - Structures d'erreurs standardisées ## 4. Dépendances externes ### Base de données - **PostgreSQL** (via sqlx 0.7) - Tables : `stream_jobs`, `stream_segments` - Références : `tracks` (table externe, backend Go) - Pool : 10 max, 1 min, timeout 30s ### Cache - **Redis** (optionnel, via redis 0.25) - URL : `REDIS_URL` (env) - Pool : configurable ### Message Queue - **RabbitMQ** (via lapin 2.3) - URL : `RABBITMQ_URL` (env, défaut: `amqp://guest:guest@localhost:5672/`) - Mode dégradé : Le serveur démarre même si RabbitMQ est down ### Stockage fichiers - **Filesystem local** : `AUDIO_DIR` (défaut: `./audio`) - **S3/MinIO** : ❌ Absent (stockage local uniquement) ### Outils externes - **FFmpeg** : Transcodage audio (optionnel, chemin via `FFMPEG_PATH`) - Formats : MP3, AAC, FLAC, Opus, HLS ### Services externes - **Backend API** : `BACKEND_URL` (défaut: `http://backend-api:8080`) - Utilisé pour : Validation auth, métadonnées tracks ## 5. Exécution ### Commandes de build/run ```bash # Build cargo build --release # Run (dev) cargo run # Run (production) ./target/release/stream_server # Tests cargo test # Clippy (lint) cargo clippy -- -D warnings # Benchmarks cargo bench ``` ### Configuration (variables d'environnement) **Obligatoires** : - `SECRET_KEY` : Clé HMAC (min 32 chars) pour signatures URLs - `DATABASE_URL` : PostgreSQL connection string - `JWT_SECRET` : Secret JWT (min 32 chars) **Optionnelles** (avec defaults) : - `STREAM_PORT` / `PORT` : Port serveur (défaut: 3002) - `AUDIO_DIR` : Dossier fichiers audio (défaut: `./audio`) - `ALLOWED_ORIGINS` : CORS origins (défaut: `*` ⚠️) - `MAX_FILE_SIZE` : Taille max fichier (défaut: 100MB) - `MAX_RANGE_SIZE` : Taille max range request (défaut: 10MB) - `RABBITMQ_URL` : URL RabbitMQ (défaut: `amqp://guest:guest@localhost:5672/`) - `REDIS_URL` : URL Redis (optionnel) - `RUST_LOG` : Niveau logging (défaut: `stream_server=info`) **Fichiers de config** : - `.env` : Variables d'environnement (optionnel, chargé via dotenv) - `env.example` : Template de configuration ### Docker ```bash # Build docker build -t veza-stream-server . # Run (compose) docker-compose up # Run (production compose) docker-compose -f docker-compose.production.yml up ``` **Dockerfile** : - Multi-stage build (builder + runtime) - Base : `rust:alpine` → `alpine:latest` - User non-root : `app` (UID 1001) - Healthcheck : `wget http://localhost:8082/health` - Port exposé : 8082 ### Kubernetes **Manifests** : `k8s/production/` - `stream-server-deployment.yaml` : Deployment - `configmap.yaml` : Configuration - `secrets.yaml` : Secrets (DB, JWT, TLS) ## 6. Points d'intégration avec autres services ### Contrats d'API attendus #### Backend Go (veza-backend-api) - **Auth** : Validation JWT via `JWT_SECRET` partagé - Audience : `veza-services` - Issuer : `veza-platform` - **Métadonnées** : Table `tracks` (UUID, metadata) - Référence FK : `stream_jobs.track_id → tracks.id` - **Format** : UUID v4 pour tous les IDs #### Frontend React - **CORS** : `ALLOWED_ORIGINS` (⚠️ `*` par défaut en dev) - **WebSocket** : `ws://host:port/ws?token=&user_id=` - **Streaming** : URLs signées avec `expires` et `sig` (HMAC-SHA256) ### Auth (JWT/SSO) **JWT Validation** : - Header : `Authorization: Bearer ` - Query param : `?token=` (WebSocket) - Validation : `iss=veza-platform`, `aud=veza-services`, `alg=HS256` - Expiration : Configurable (défaut: 3600s) - Révocation : Session-based (HashMap en mémoire ⚠️) **Permissions** : - `StreamAudio` : Accès streaming basique - `StreamHighQuality` : Streaming haute qualité - `StreamUnlimited` : Streaming illimité - `UploadAudio` : Upload fichiers - `SystemAdmin` : Admin système **Rôles** : - `Admin`, `Moderator`, `User`, `Premium`, `Artist`, `Guest` ### Headers HTTP **CORS** : - `Access-Control-Allow-Origin` : `ALLOWED_ORIGINS` (⚠️ `*` par défaut) - `Access-Control-Allow-Methods` : GET, POST, PUT, DELETE, OPTIONS - `Access-Control-Allow-Headers` : Content-Type, Authorization, Accept **Sécurité** : - `X-Content-Type-Options: nosniff` - `X-Frame-Options: DENY` - `X-XSS-Protection: 1; mode=block` - `Content-Security-Policy: default-src 'none'; media-src 'self'` - `Referrer-Policy: strict-origin-when-cross-origin` ### Schéma DB / UUID / Conventions **UUID** : - Format : UUID v4 (via `uuid` crate) - IDs utilisateurs : `String` (UUID en string) - IDs tracks : `UUID` (PostgreSQL) - IDs streams : `UUID` (PostgreSQL) **Tables PostgreSQL** : - `stream_jobs` : Jobs transcodage (FK → `tracks.id`) - `stream_segments` : Segments HLS (FK → `tracks.id`) - `tracks` : ❌ **Table externe** (backend Go, doit exister) **Conventions** : - Timestamps : `TIMESTAMPTZ` (PostgreSQL) - Durées : `FLOAT` (secondes) ou `INTEGER` (millisecondes) - Status : `TEXT` avec CHECK constraints --- # PHASE B — SANTÉ TECHNIQUE ## Build Status ### Compilation **État** : ✅ Compile avec warnings **Commandes de validation** : ```bash cargo check # ✅ Passe (avec warnings) cargo build --release # ✅ Passe ``` **Warnings détectés** : - 174 occurrences de `unwrap()` / `expect()` dans 39 fichiers - Dépendances obsolètes (axum 0.7 vs 0.8, sqlx 0.7 vs 0.8, etc.) - Clippy warnings non traités (109 warnings selon audit précédent) ### Erreurs de types / Linters **Clippy** : ```bash cargo clippy -- -D warnings ``` - ⚠️ **109 warnings** (non bloquants mais qualité dégradée) - Patterns détectés : `unwrap()`, `expect()`, allocations inutiles, clones **Rustfmt** : ```bash cargo fmt --check ``` - ✅ Formatage cohérent (sauf fichiers non formatés) ### Tests **Couverture** : - Tests unitaires : ~25% (estimé) - Tests d'intégration : ❌ Absents - Tests E2E : ❌ Absents **Fichiers de tests** : - `src/lib.rs` : 1 test basique (structure) - `src/config/mod.rs` : 3 tests (config, dotenv, default) - `src/error.rs` : 6 tests (conversions erreurs) - `src/structured_logging.rs` : 5 tests (logging) - `src/utils/mod.rs` : 3 tests (validation, signature) - `src/middleware/security.rs` : 3 tests (patterns dangereux) - `src/database/pool.rs` : 2 tests (ignorés, nécessitent DB) **Tests manquants** : - ❌ Tests WebSocket (connexion, messages, déconnexion) - ❌ Tests streaming HTTP Range - ❌ Tests transcodage FFmpeg - ❌ Tests HLS generation - ❌ Tests synchronisation multi-client - ❌ Tests auth JWT - ❌ Tests rate limiting - ❌ Tests cache - ❌ Tests analytics ### Gestion des erreurs **Patterns utilisés** : - ✅ `Result` pour erreurs applicatives - ✅ `thiserror` pour définitions d'erreurs - ✅ `anyhow` pour erreurs contextuelles - ⚠️ **174 `unwrap()` / `expect()`** : Risque de panics en production **Fichiers avec le plus d'`unwrap()`** : - `src/core/sync.rs` : 16 occurrences - `src/analytics/mod.rs` : 11 occurrences - `src/auth/mod.rs` : 12 occurrences - `src/middleware/logging.rs` : 6 occurrences - `src/routes/api.rs` : 3 occurrences **Erreurs HTTP** : - ✅ Mapping `AppError` → `StatusCode` (via `IntoResponse`) - ✅ Messages d'erreur structurés (JSON) - ⚠️ Certaines erreurs retournent 500 au lieu de codes appropriés ### Cohérence des conventions **Naming** : - ✅ Snake_case pour fonctions/variables - ✅ PascalCase pour types - ✅ Modules organisés par domaine **Structure dossiers** : ``` src/ ├── analytics/ # Analytics engine ├── audio/ # Audio processing ├── auth/ # Authentication ├── cache/ # File cache ├── codecs/ # Audio codecs ├── config/ # Configuration ├── core/ # Core streaming logic ├── database/ # DB pool ├── middleware/ # HTTP middlewares ├── monitoring/ # Metrics, health ├── routes/ # HTTP routes ├── streaming/ # Streaming protocols ├── transcoding/ # FFmpeg transcoding └── utils/ # Utilities ``` **Séparation couches** : - ✅ Routes → Handlers → Services → DB - ✅ Middlewares séparés (auth, rate limit, security, logging) - ⚠️ Certains handlers font trop de logique métier --- # PHASE C — SÉCURITÉ ## Top 10 Risques Sécurité ### 1. **MOD-P0-001** : CORS permissif par défaut (`*`) **Impact** : Attaques CSRF, accès non autorisé depuis n'importe quelle origine **Preuve** : ```rust:340:344:src/config/mod.rs allowed_origins: env::var("ALLOWED_ORIGINS") .unwrap_or_else(|_| "*".to_string()) .split(',') .map(|s| s.trim().to_string()) .collect(), ``` ```rust:38:41:src/routes/api.rs let cors = if allowed_origins_str.trim() == "*" { // Mode développement: autoriser toutes les origines CorsLayer::new() .allow_origin(Any) ``` **Cause racine** : Configuration par défaut trop permissive pour faciliter le développement **Fix minimal** : ```rust // Dans src/config/mod.rs allowed_origins: env::var("ALLOWED_ORIGINS") .unwrap_or_else(|_| { if cfg!(debug_assertions) { "http://localhost:5176,http://localhost:3000".to_string() } else { panic!("ALLOWED_ORIGINS must be set in production") } }) ``` **Plan de validation** : 1. Test : `curl -H "Origin: https://evil.com" http://localhost:8082/health` 2. Vérifier : Header `Access-Control-Allow-Origin` ne doit pas être `*` en prod 3. Test : Connexion WebSocket depuis origine non autorisée doit échouer **Effet de bord** : Devs doivent configurer `ALLOWED_ORIGINS` en local **Effort** : S (1h) --- ### 2. **MOD-P0-002** : Endpoint transcodage interne non authentifié **Impact** : N'importe qui peut déclencher des jobs de transcodage (DoS, consommation CPU) **Preuve** : ```rust:118:118:src/routes/api.rs .route("/internal/jobs/transcode", post(internal_transcode_handler)) ``` ```rust:146:184:src/routes/api.rs async fn internal_transcode_handler( State(state): State, Json(payload): Json, ) -> Result, (StatusCode, String)> { // Aucune vérification d'authentification ``` **Cause racine** : Endpoint prévu pour usage interne mais exposé publiquement **Fix minimal** : ```rust // Ajouter middleware auth ou vérification IP .route( "/internal/jobs/transcode", post(internal_transcode_handler) .layer(axum::middleware::from_fn_with_state( state.clone(), auth_middleware, )) ) ``` **Plan de validation** : 1. Test : `curl -X POST http://localhost:8082/internal/jobs/transcode -d '{}'` 2. Vérifier : 401 Unauthorized sans token 3. Test : Avec token valide, doit fonctionner **Effet de bord** : Services internes doivent s'authentifier **Effort** : S (1h) --- ### 3. **MOD-P0-003** : WebSocket sans authentification obligatoire **Impact** : Connexions WebSocket anonymes possibles, accès streaming non contrôlé **Preuve** : ```rust:802:808:src/streaming/websocket.rs // Si un token est fourni, on le valide (pour l'instant, on accepte la connexion) // En production, on validerait le token avec AuthManager let user_id = params.user_id.or_else(|| { // Si un token est fourni, on pourrait extraire user_id du token // Pour l'instant, on utilise le user_id fourni dans les params None }); ``` **Cause racine** : Authentification WebSocket non implémentée (TODO) **Fix minimal** : ```rust // Dans websocket_handler let token = params.token.ok_or_else(|| { tracing::warn!("WebSocket connection without token rejected"); return StatusCode::UNAUTHORIZED; })?; let validation_result = auth_manager.validate_token(&token).await; if !validation_result.valid { return StatusCode::UNAUTHORIZED.into_response(); } ``` **Plan de validation** : 1. Test : `wscat -c "ws://localhost:8082/ws"` (sans token) 2. Vérifier : Connexion rejetée 3. Test : Avec token valide, connexion acceptée **Effet de bord** : Clients doivent fournir JWT pour WebSocket **Effort** : M (4h) --- ### 4. **MOD-P0-004** : Secrets en clair dans k8s/secrets.yaml **Impact** : Secrets commités dans Git (même si base64, facilement décodables) **Preuve** : ```yaml:8:9:k8s/production/secrets.yaml data: url: cG9zdGdyZXNxbDovL3ZlemE6c2VjdXJlX3Bhc3NAcG9zdGdyZXM6NTQzMi92ZXphX3Byb2Q= ``` **Cause racine** : Secrets de test commités par erreur **Fix minimal** : 1. Supprimer `k8s/production/secrets.yaml` du repo 2. Ajouter à `.gitignore` : `**/secrets.yaml` 3. Documenter : Utiliser Sealed Secrets ou Vault **Plan de validation** : 1. Vérifier : `git log --all --full-history -- k8s/production/secrets.yaml` 2. Vérifier : Fichier absent du repo 3. Test : Secrets créés via `kubectl create secret` **Effet de bord** : Devs doivent créer secrets manuellement **Effort** : S (30min) --- ### 5. **MOD-P1-005** : Rate limiting non implémenté (stub) **Impact** : Pas de protection contre DoS, requêtes illimitées **Preuve** : ```rust:57:73:src/middleware/rate_limit.rs async fn check_rate_limit(state: &AppState, client_ip: &str) -> bool { // Implémentation basique du rate limiting // Dans une vraie application, on utiliserait un store externe comme Redis let max_requests_per_minute = state.config.security.rate_limit_requests_per_minute; let _now = Instant::now(); // Pour cette implémentation basique, on permet toutes les requêtes // En production, il faudrait implémenter un vrai système de rate limiting tracing::debug!( client_ip = %client_ip, limit = max_requests_per_minute, "Vérification du rate limit" ); true // ⚠️ TOUJOURS TRUE } ``` **Cause racine** : Implémentation placeholder non complétée **Fix minimal** : ```rust // Utiliser governor ou implémenter avec Redis use governor::{Quota, RateLimiter}; let limiter = RateLimiter::keyed(Quota::per_minute(nonzero!(max_requests_per_minute))); limiter.check_key(&client_ip).is_ok() ``` **Plan de validation** : 1. Test : 1000 requêtes rapides depuis même IP 2. Vérifier : Après 60 req/min, 429 Too Many Requests 3. Test : Différentes IPs, limites indépendantes **Effet de bord** : Redis requis si implémentation distribuée **Effort** : M (4h) --- ### 6. **MOD-P1-006** : Révocation JWT en mémoire (non persistant) **Impact** : Tokens révoqués revalidés après redémarrage serveur **Preuve** : ```rust:124:124:src/auth/mod.rs revoked_tokens: Arc>>, // session_id -> revocation_time ``` **Cause racine** : Stockage en mémoire pour simplicité **Fix minimal** : - Option 1 : Redis pour révocation (TTL = expiration token) - Option 2 : DB table `revoked_tokens` (session_id, revoked_at, expires_at) **Plan de validation** : 1. Test : Révoquer token, vérifier rejeté 2. Test : Redémarrer serveur, token toujours rejeté (si Redis/DB) 3. Test : Nettoyage automatique tokens expirés **Effet de bord** : Redis/DB requis pour révocation persistante **Effort** : M (4h) --- ### 7. **MOD-P1-007** : Validation signature HMAC avec timing attack possible **Impact** : Attaque par timing pour deviner signature (faible mais présent) **Preuve** : ```rust:194:195:src/utils/mod.rs use subtle::ConstantTimeEq; expected_sig.as_bytes().ct_eq(sig.as_bytes()).into() ``` ✅ **DÉJÀ CORRIGÉ** : Utilise `subtle::ConstantTimeEq` (comparaison temps constant) **Note** : Pas de problème, mais vérifier que tous les usages utilisent `ct_eq` --- ### 8. **MOD-P1-008** : Path traversal possible si validation bypass **Impact** : Accès fichiers hors `AUDIO_DIR` (si validation contournée) **Preuve** : ```rust:51:67:src/utils/mod.rs pub fn build_safe_path(config: &Config, filename: &str) -> Result { let audio_dir = Path::new(&config.audio_dir); let file_path = audio_dir.join(filename); // Utiliser un check basique au lieu de canonicalize qui nécessite que le fichier existe // Ceci est nécessaire pour les tests où audio_dir peut ne pas exister physiquement // Dans un environnement de prod, le dossier existe. // Pour la sécurité, on vérifie juste qu'il n'y a pas de composants ".." après normalisation // Note: std::fs::canonicalize requires file to exist. // We can try to simplify path manually or just rely on validate_filename having rejected ".." // Pour une sécurité maximale tout en permettant les tests: let normalized_path = file_path; // validate_filename a déjà filtré ".." Ok(normalized_path) } ``` **Cause racine** : `canonicalize()` nécessite fichier existant, donc contourné **Fix minimal** : ```rust // Vérifier que le chemin résolu est bien dans audio_dir let canonical_audio_dir = std::fs::canonicalize(&config.audio_dir)?; let canonical_file_path = std::fs::canonicalize(&file_path)?; if !canonical_file_path.starts_with(&canonical_audio_dir) { return Err(AppError::Forbidden); } ``` **Plan de validation** : 1. Test : `GET /stream/../../../etc/passwd?expires=...&sig=...` 2. Vérifier : 403 Forbidden ou 400 Bad Request 3. Test : Fichier valide dans `AUDIO_DIR`, doit fonctionner **Effet de bord** : `AUDIO_DIR` doit exister (pas de problème en prod) **Effort** : S (2h) --- ### 9. **MOD-P2-009** : Headers sécurité manquants (HSTS, CSP strict) **Impact** : Vulnérabilités XSS, clickjacking (mitigé mais pas optimal) **Preuve** : ```rust:178:183:src/middleware/security.rs // HSTS (si HTTPS) // Note: À activer uniquement en HTTPS // headers.insert( // HeaderName::from_static("strict-transport-security"), // HeaderValue::from_static("max-age=31536000; includeSubDomains") // ); ``` **Cause racine** : HSTS commenté (nécessite HTTPS) **Fix minimal** : ```rust if config.tls_enabled() { headers.insert( HeaderName::from_static("strict-transport-security"), HeaderValue::from_static("max-age=31536000; includeSubDomains") ); } ``` **Plan de validation** : 1. Test : Requête HTTPS, vérifier header `Strict-Transport-Security` 2. Test : Requête HTTP, header absent **Effet de bord** : Nécessite TLS configuré **Effort** : S (1h) --- ### 10. **MOD-P2-010** : Injection SQL possible si requêtes non paramétrées **Impact** : SQL injection si requêtes construites dynamiquement (non vérifié partout) **Preuve** : ```rust:589:596:src/analytics/mod.rs // Utiliser des requêtes SQL simples sans macros pour éviter les erreurs de driver let total_sessions = sqlx::query( "SELECT COUNT(*) as count FROM play_sessions WHERE started_at BETWEEN ? AND ?", ) .bind(start_ts) .bind(end_ts) ``` ✅ **DÉJÀ PROTÉGÉ** : Utilise `sqlx::query()` avec `.bind()` (requêtes paramétrées) **Note** : Vérifier tous les usages de `sqlx::query!` et `sqlx::query_scalar!` utilisent des paramètres --- ## Autres Risques Sécurité (P2/P3) ### MOD-P2-011 : Secrets dans logs (potentiel) **Impact** : Tokens JWT, secrets exposés dans logs **Preuve** : Vérifier tous les `tracing::*!` ne loggent pas de secrets **Fix** : Audit des logs, masquer tokens/secrets **Effort** : M (4h) --- ### MOD-P2-012 : Pas de validation taille upload **Impact** : DoS via upload fichiers énormes **Preuve** : Endpoint upload non trouvé (peut-être absent) **Fix** : Limiter taille body requests **Effort** : S (2h) --- ### MOD-P3-013 : Dépendances obsolètes (vulnérabilités) **Impact** : Vulnérabilités connues dans dépendances **Preuve** : - `axum 0.7` (latest: 0.8) - `sqlx 0.7` (latest: 0.8) - `jsonwebtoken 9.2` (latest: 10.2) **Fix** : `cargo audit` + mise à jour dépendances **Effort** : M (4h) --- # PHASE D — ROBUSTESSE & OBSERVABILITÉ ## Logs structurés **État** : ✅ Implémenté avec `tracing` **Format** : - Dev : Pretty (lisible) - Prod : JSON (structuré) **Champs contextuels** : - `stream_id`, `user_id`, `track_id`, `client_ip`, `event`, etc. **Rotation** : - ✅ Configuré (via `tracing-appender`) - Rotation : Daily (>100MB) ou Hourly (≤100MB) **Niveaux** : - `RUST_LOG` : `stream_server=info` (défaut) - Filtrage par module possible **Gaps** : - ⚠️ Pas de corrélation `request_id` / `trace_id` automatique - ⚠️ Logs fichiers optionnels (pas de config par défaut) --- ## Métriques **État** : ✅ Prometheus intégré **Endpoints** : - `GET /metrics` : Métriques Prometheus **Métriques disponibles** : - Via `metrics` crate (0.22) - Exporter : `metrics-exporter-prometheus` (0.13) **Gaps** : - ⚠️ Métriques custom non documentées - ⚠️ Pas de dashboard Grafana fourni (template existe mais non déployé) --- ## Healthchecks **État** : ✅ Implémenté **Endpoints** : - `GET /healthz` : Liveness (basique) - `GET /readyz` : Readiness (détaillé) - `GET /health` : Health détaillé **Vérifications** : - ✅ Database : Ping PostgreSQL - ✅ Audio directory : Existence - ✅ RabbitMQ : Connexion (mode dégradé si down) - ✅ Services : Statut global **Gaps** : - ⚠️ Pas de vérification Redis (si activé) - ⚠️ Pas de vérification FFmpeg disponible --- ## Timeouts / Retries / Circuit Breakers **Timeouts** : - ✅ HTTP : 30s (via `TimeoutLayer`) - ✅ DB : 30s (connection timeout) - ⚠️ WebSocket : Pas de timeout explicite - ⚠️ FFmpeg : Pas de timeout (peut bloquer indéfiniment) **Retries** : - ✅ RabbitMQ : 3 retries avec backoff (2s) - ⚠️ DB : Pas de retry automatique - ⚠️ HTTP client (reqwest) : Pas de retry configuré **Circuit Breakers** : - ❌ Absent --- ## Gestion de charge **Pool DB** : - ✅ Configuré : 10 max, 1 min - ✅ Timeouts : 30s acquire, 600s idle, 3600s max lifetime **WebSocket** : - ⚠️ Pas de limite de connexions - ⚠️ Pas de backpressure **Streaming** : - ✅ `MAX_RANGE_SIZE` : 10MB - ✅ `MAX_FILE_SIZE` : 100MB - ⚠️ Pas de limite de streams simultanés **FFmpeg** : - ✅ `max_concurrent_jobs` : 4 (configurable) - ⚠️ Pas de queue limit (peut consommer mémoire) --- ## Migrations / Compatibilité **Migrations** : - ✅ SQLx migrations : `migrations/` - ✅ Auto-migration : `DB_MIGRATE_ON_START=true` (défaut) **Tables** : - `stream_jobs` : ✅ Migration 001 - `stream_segments` : ✅ Migration 002 - ⚠️ **FK vers `tracks`** : Table externe (backend Go), doit exister **Compatibilité** : - ⚠️ Pas de versioning de schéma - ⚠️ Pas de rollback migrations --- # PHASE E — PERFORMANCE & SCALABILITÉ ## Hotspots évidents ### 1. Allocations inutiles **Preuve** : - Clones de `AppState` (Arc mais clones fréquents) - String allocations dans hot paths **Fix** : Utiliser `Cow`, `&str` où possible **Effort** : M (4h) --- ### 2. Copies de buffers audio **Preuve** : Streaming peut copier buffers inutilement **Fix** : Utiliser `Bytes` (zero-copy) pour streaming **Effort** : M (6h) --- ### 3. JSON sérialisation dans hot paths **Preuve** : WebSocket messages sérialisés à chaque envoi **Fix** : Cache sérialisation ou utiliser binaire (MessagePack) **Effort** : L (8h) --- ### 4. Locks contention (DashMap/RwLock) **Preuve** : - `WebSocketManager.connections` : `RwLock` - `SyncEngine` : Multiples `DashMap` / `RwLock` **Fix** : Sharding, lock-free structures où possible **Effort** : L (10h) --- ### 5. FFmpeg blocking dans async **Preuve** : FFmpeg exécuté dans threads bloquants (correct) mais monitoring peut bloquer **Fix** : Vérifier tous les appels FFmpeg sont dans `spawn_blocking` **Effort** : M (4h) --- ## Streaming optimisations **HTTP Range** : - ✅ Implémenté avec `tokio::fs::File::seek` - ✅ Streaming avec `ReaderStream` (pas de chargement complet en mémoire) **HLS** : - ⚠️ Génération playlists synchrones (peut bloquer) - ⚠️ Segments non pré-générés (génération à la volée) **WebSocket** : - ✅ Broadcast channel (1000 capacity) - ⚠️ Pas de compression WebSocket (per-message deflate) --- ## Rust-specific **Async runtime** : - ✅ Tokio 1.35 (à jour) - ⚠️ Pas de tuning runtime (threads, etc.) **Blocking in async** : - ⚠️ Vérifier tous les I/O bloquants sont dans `spawn_blocking` **Memory** : - ⚠️ Pas de profiling mémoire - ⚠️ Pas de limites mémoire explicites --- # PHASE F — LISTE EXHAUSTIVE DES PROBLÈMES ## P0 — Bloquants (Sécurité / Crash / Build) | ID | Titre | Impact | Preuve | Cause | Fix | Validation | Effet de bord | Effort | |----|-------|--------|--------|-------|-----|------------|---------------|--------| | **MOD-P0-001** | CORS permissif `*` par défaut | CSRF, accès non autorisé | `src/config/mod.rs:340`, `src/routes/api.rs:38` | Default trop permissif | Require `ALLOWED_ORIGINS` en prod | Test CORS depuis origine non autorisée | Devs doivent configurer | S (1h) | | **MOD-P0-002** | Endpoint `/internal/jobs/transcode` non authentifié | DoS, jobs transcodage illimités | `src/routes/api.rs:118,146` | Endpoint interne exposé | Ajouter middleware auth | Test POST sans token → 401 | Services internes doivent s'auth | S (1h) | | **MOD-P0-003** | WebSocket sans auth obligatoire | Streaming anonyme possible | `src/streaming/websocket.rs:802` | Auth non implémentée | Valider JWT avant connexion | Test WS sans token → rejet | Clients doivent fournir JWT | M (4h) | | **MOD-P0-004** | Secrets en clair dans Git | Secrets compromis | `k8s/production/secrets.yaml:8` | Secrets commités | Supprimer du repo, utiliser Sealed Secrets | Vérifier fichier absent | Devs créent secrets manuellement | S (30min) | | **MOD-P0-005** | Table `tracks` externe non vérifiée | FK violations, crashes | `migrations/001_create_stream_jobs.sql:14` | Dépendance externe non validée | Vérifier existence table au démarrage | Test FK violation → erreur claire | Backend Go doit créer table | S (2h) | ## P1 — Critiques (Bugs fréquents / Dette bloquante) | ID | Titre | Impact | Preuve | Cause | Fix | Validation | Effet de bord | Effort | |----|-------|--------|--------|-------|-----|------------|---------------|--------| | **MOD-P1-006** | Rate limiting stub (toujours `true`) | Pas de protection DoS | `src/middleware/rate_limit.rs:72` | Implémentation placeholder | Implémenter avec `governor` ou Redis | Test 1000 req → 429 après limite | Redis requis si distribué | M (4h) | | **MOD-P1-007** | Révocation JWT en mémoire | Tokens révoqués revalidés après restart | `src/auth/mod.rs:124` | Stockage non persistant | Redis ou DB pour révocation | Test révocation → restart → toujours rejeté | Redis/DB requis | M (4h) | | **MOD-P1-008** | Path traversal si validation bypass | Accès fichiers hors `AUDIO_DIR` | `src/utils/mod.rs:51` | `canonicalize()` contourné | Vérifier chemin résolu dans `AUDIO_DIR` | Test `../../../etc/passwd` → 403 | `AUDIO_DIR` doit exister | S (2h) | | **MOD-P1-009** | 174 `unwrap()` / `expect()` | Panics en production | `grep -r "unwrap\|expect" src/` (174 matches) | Gestion erreurs incomplète | Remplacer par `?` ou `match` | Test erreurs → pas de panic | Code plus verbeux | L (20h) | | **MOD-P1-010** | FFmpeg sans timeout | Jobs peuvent bloquer indéfiniment | `src/transcoding/engine.rs` (à vérifier) | Pas de timeout configuré | Ajouter timeout FFmpeg (5min) | Test job bloqué → timeout | Jobs longs peuvent échouer | S (2h) | | **MOD-P1-011** | WebSocket pas de limite connexions | DoS mémoire | `src/streaming/websocket.rs:231` | Pas de limite | Limiter connexions (1000 max) | Test 2000 connexions → rejet | Clients peuvent être rejetés | S (2h) | | **MOD-P1-012** | HLS segments générés à la volée | Latence première requête | `src/routes/api.rs:356` | Pas de pré-génération | Pré-générer segments lors transcodage | Test première requête → rapide | Stockage requis | M (6h) | | **MOD-P1-013** | Pas de tests intégration | Bugs non détectés | Aucun test dans `tests/` | Tests manquants | Ajouter tests intégration | `cargo test --test integration` | Maintenance tests | L (16h) | ## P2 — Majeurs (Qualité / Maintenabilité) | ID | Titre | Impact | Preuve | Cause | Fix | Validation | Effet de bord | Effort | |----|-------|--------|--------|-------|-----|------------|---------------|--------| | **MOD-P2-014** | 109 warnings Clippy | Qualité code dégradée | `cargo clippy` (109 warnings) | Warnings non traités | Corriger warnings | `cargo clippy` → 0 warnings | Code plus strict | M (8h) | | **MOD-P2-015** | Dépendances obsolètes | Vulnérabilités, perf | `Cargo.toml` (axum 0.7, sqlx 0.7) | Pas de mise à jour | `cargo update` + tests | `cargo audit` → 0 vulns | Tests de régression | M (4h) | | **MOD-P2-016** | Pas de `request_id` / `trace_id` | Debugging difficile | Logs sans corrélation | Pas implémenté | Middleware `request_id` | Test logs → même `request_id` | Overhead minimal | M (4h) | | **MOD-P2-017** | HSTS manquant (si HTTPS) | Man-in-the-middle | `src/middleware/security.rs:178` | Commenté | Activer si TLS | Test HTTPS → header HSTS | Nécessite TLS | S (1h) | | **MOD-P2-018** | Pas de circuit breaker | Cascading failures | Absent | Pas implémenté | Ajouter circuit breaker (DB, Redis) | Test DB down → circuit ouvert | Retries échouent plus tôt | M (6h) | | **MOD-P2-019** | Pas de retry DB | Erreurs transitoires non récupérées | `src/database/pool.rs` | Pas de retry | Ajouter retry avec backoff | Test DB temporairement down → retry | Latence augmentée | S (2h) | | **MOD-P2-020** | Pas de monitoring Redis | État Redis inconnu | Health check ne vérifie pas Redis | Non implémenté | Ajouter check Redis dans health | Test Redis down → health degraded | Redis requis | S (1h) | | **MOD-P2-021** | Pas de compression WebSocket | Bande passante gaspillée | `src/streaming/websocket.rs` | Pas activé | Activer per-message deflate | Test taille messages → réduite | CPU légèrement augmenté | S (2h) | | **MOD-P2-022** | Pas de pré-génération HLS | Latence première requête | `src/routes/api.rs:356` | Génération à la volée | Pré-générer lors transcodage | Test première requête → rapide | Stockage requis | M (6h) | | **MOD-P2-023** | Pas de cache Redis pour métadonnées | Requêtes DB répétées | `src/cache/audio_cache.rs` | Cache fichiers uniquement | Cache DB queries | Test requêtes répétées → cache hit | Redis requis | M (4h) | | **MOD-P2-024** | Pas de backpressure WebSocket | Mémoire illimitée | `src/streaming/websocket.rs:248` | Pas de limite | Limiter buffer messages | Test messages rapides → backpressure | Messages peuvent être perdus | M (4h) | | **MOD-P2-025** | Pas de profiling mémoire | Fuites mémoire non détectées | Absent | Pas d'outil | Ajouter `dhat` ou `valgrind` | Test fuites → détectées | Overhead profiling | S (2h) | ## P3 — Mineurs (Cosmétique / Refactors) | ID | Titre | Impact | Preuve | Cause | Fix | Validation | Effet de bord | Effort | |----|-------|--------|--------|-------|-----|------------|---------------|--------| | **MOD-P3-026** | Documentation cargo doc incomplète | DX dégradée | `cargo doc` (docstrings manquantes) | Pas de doc | Ajouter docstrings | `cargo doc --open` → docs complètes | Maintenance docs | M (8h) | | **MOD-P3-027** | Tests unitaires manquants | Couverture faible (~25%) | `cargo test` (peu de tests) | Tests non écrits | Ajouter tests unitaires | `cargo test --coverage` → >80% | Maintenance tests | L (20h) | | **MOD-P3-028** | Pas de benchmarks | Performance non mesurée | `cargo bench` (peu de benches) | Benchmarks manquants | Ajouter benchmarks | `cargo bench` → métriques | Maintenance benches | M (6h) | | **MOD-P3-029** | Code dupliqué (validation patterns) | Maintenance difficile | Plusieurs fichiers | Pas de factorisation | Extraire helpers | Test factorisation → moins de code | Refactor | M (4h) | | **MOD-P3-030** | Pas de migration rollback | Rollback impossible | `migrations/` (pas de down) | Pas implémenté | Ajouter migrations down | Test rollback → succès | Maintenance migrations | M (4h) | --- # PHASE G — PLAN D'EXÉCUTION ## Checklist P0 (Ordre strict) 1. **MOD-P0-004** : Supprimer secrets du repo (30min) - Supprimer `k8s/production/secrets.yaml` - Ajouter à `.gitignore` - Documenter Sealed Secrets 2. **MOD-P0-001** : Fix CORS permissif (1h) - Require `ALLOWED_ORIGINS` en prod - Test CORS 3. **MOD-P0-002** : Auth endpoint transcodage (1h) - Ajouter middleware auth - Test 401 sans token 4. **MOD-P0-005** : Vérifier table `tracks` (2h) - Check existence au démarrage - Erreur claire si absente 5. **MOD-P0-003** : Auth WebSocket (4h) - Valider JWT avant connexion - Test rejet sans token **Total P0** : ~8h (1 jour) --- ## Checklist P1 (Par lots cohérents) ### Lot 1 : Rate limiting + Révocation (8h) 6. **MOD-P1-006** : Implémenter rate limiting (4h) 7. **MOD-P1-007** : Révocation JWT persistante (4h) ### Lot 2 : Sécurité paths + FFmpeg (4h) 8. **MOD-P1-008** : Fix path traversal (2h) 9. **MOD-P1-010** : Timeout FFmpeg (2h) ### Lot 3 : WebSocket + HLS (10h) 10. **MOD-P1-011** : Limite connexions WS (2h) 11. **MOD-P1-012** : Pré-génération HLS (6h) 12. **MOD-P1-013** : Tests intégration (2h, début) ### Lot 4 : Gestion erreurs (20h) 13. **MOD-P1-009** : Remplacer `unwrap()` (20h, progressif) **Total P1** : ~42h (1 semaine) --- ## Quick Wins (≤ 1h chacun) - **MOD-P2-017** : HSTS si HTTPS (1h) - **MOD-P2-020** : Monitoring Redis (1h) - **MOD-P2-021** : Compression WebSocket (2h) - **MOD-P2-019** : Retry DB (2h) **Total Quick Wins** : ~6h --- ## Tests à ajouter en priorité 1. **Tests WebSocket** (4h) - Connexion, messages, déconnexion - Auth, rate limiting 2. **Tests streaming HTTP** (4h) - Range requests, signatures, path traversal 3. **Tests transcodage** (6h) - FFmpeg pipeline, HLS generation, timeouts 4. **Tests intégration** (8h) - End-to-end streaming, WebSocket sync **Total Tests** : ~22h --- ## PR Plan ### PR 1 : Sécurité critique (P0) **Titre** : `fix(security): Remove secrets from repo, fix CORS, add auth to internal endpoints` **Changements** : - MOD-P0-004 : Secrets - MOD-P0-001 : CORS - MOD-P0-002 : Auth transcodage **Tests** : Tests sécurité ajoutés --- ### PR 2 : WebSocket auth **Titre** : `feat(websocket): Add JWT authentication to WebSocket connections` **Changements** : - MOD-P0-003 : Auth WebSocket **Tests** : Tests WebSocket auth --- ### PR 3 : Rate limiting + Révocation **Titre** : `feat(security): Implement rate limiting and persistent JWT revocation` **Changements** : - MOD-P1-006 : Rate limiting - MOD-P1-007 : Révocation **Tests** : Tests rate limiting --- ### PR 4 : Path traversal + FFmpeg timeout **Titre** : `fix(security): Fix path traversal validation and add FFmpeg timeout` **Changements** : - MOD-P1-008 : Path traversal - MOD-P1-010 : FFmpeg timeout **Tests** : Tests sécurité --- ### PR 5 : WebSocket improvements **Titre** : `feat(websocket): Add connection limits and compression` **Changements** : - MOD-P1-011 : Limite connexions - MOD-P2-021 : Compression **Tests** : Tests WebSocket --- ### PR 6 : HLS pré-génération **Titre** : `feat(hls): Pre-generate HLS segments during transcoding` **Changements** : - MOD-P1-012 : Pré-génération HLS **Tests** : Tests HLS --- ### PR 7 : Tests intégration **Titre** : `test: Add integration tests for streaming and WebSocket` **Changements** : - MOD-P1-013 : Tests intégration --- ### PR 8 : Code quality **Titre** : `chore: Fix Clippy warnings and update dependencies` **Changements** : - MOD-P2-014 : Clippy - MOD-P2-015 : Dépendances --- ### PR 9 : Observabilité **Titre** : `feat(observability): Add request_id, Redis monitoring, circuit breakers` **Changements** : - MOD-P2-016 : Request ID - MOD-P2-020 : Redis monitoring - MOD-P2-018 : Circuit breakers --- ### PR 10 : Gestion erreurs (progressif) **Titre** : `refactor: Replace unwrap() with proper error handling` **Changements** : - MOD-P1-009 : Remplacer `unwrap()` (par lots) **Tests** : Tests erreurs --- ## Estimation Totale | Priorité | Problèmes | Effort | Délai | |----------|-----------|--------|-------| | **P0** | 5 | 8h | 1 jour | | **P1** | 8 | 42h | 1 semaine | | **P2** | 12 | 40h | 1 semaine | | **P3** | 5 | 42h | 1 semaine | | **Tests** | - | 22h | 3 jours | | **TOTAL** | **30** | **154h** | **~4 semaines** | --- # CONCLUSION Le module **veza-stream-server** est **fonctionnel** mais présente des **gaps critiques de sécurité et de robustesse** qui doivent être corrigés avant production. **Points forts** : - Architecture solide (Axum, Tokio, SQLx) - Logging structuré (tracing) - Métriques Prometheus - Health checks **Points faibles** : - Sécurité : CORS permissif, endpoints non authentifiés - Robustesse : Rate limiting stub, révocation JWT non persistante - Tests : Couverture faible (~25%) - Performance : Pas de benchmarks, optimisations manquantes **Recommandation** : **Ne pas déployer en production** avant correction des **P0** et **P1 critiques** (sécurité). **Prochaines étapes** : 1. Corriger P0 (1 jour) 2. Corriger P1 critiques (1 semaine) 3. Ajouter tests (3 jours) 4. Code review sécurité 5. Déploiement staging 6. Tests charge 7. Déploiement production --- **Fin du rapport d'audit**