1264 lines
39 KiB
Markdown
1264 lines
39 KiB
Markdown
# 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**
|
||
|