veza/veza-stream-server/AUDIT_EXHAUSTIF_STREAM_SERVER.md

1265 lines
39 KiB
Markdown
Raw Normal View History

2025-12-13 02:34:34 +00:00
# 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**