veza/veza-stream-server/AUDIT_EXHAUSTIF_STREAM_SERVER.md
2025-12-12 21:34:34 -05:00

1264 lines
39 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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=<JWT>&user_id=<UUID>`
- **Streaming** : URLs signées avec `expires` et `sig` (HMAC-SHA256)
### Auth (JWT/SSO)
**JWT Validation** :
- Header : `Authorization: Bearer <token>`
- Query param : `?token=<JWT>` (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<T, AppError>` 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<AppState>,
Json(payload): Json<serde_json::Value>,
) -> Result<Json<serde_json::Value>, (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<tokio::sync::RwLock<HashMap<String, u64>>>, // 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<PathBuf> {
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>`, `&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<HashMap>`
- `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**