# Fix Sécurité JWT — Rapport complet **Date**: 2025-01-27 **Faille corrigée**: JWT_SECRET avec valeur par défaut hardcodée **Sévérité**: 🔴 CRITIQUE **Statut**: ✅ CORRIGÉ --- ## 1. Fichiers impactés ### Fichiers modifiés - ✅ **`internal/config/config.go`** (lignes 115-122) - **Avant**: `jwtSecret := getEnv("JWT_SECRET", "your-super-secret-jwt-key")` - **Après**: `jwtSecret := getEnvRequired("JWT_SECRET")` - **Avant**: `DatabaseURL: getEnv("DATABASE_URL", "postgresql://veza:password@localhost:5432/veza_db")` - **Après**: `DatabaseURL: getEnvRequired("DATABASE_URL")` - ✅ **`internal/config/config_test.go`** (nouveaux tests ajoutés) - Ajout de `TestNewConfig_RequiresJWTSecret()` (ligne 287) - Ajout de `TestNewConfig_RequiresDatabaseURL()` (ligne 310) - ✅ **`cmd/migrate_tool/main.go`** (lignes 16-20) - **Avant**: `Password: getEnv("DB_PASSWORD", "veza")` - **Après**: `Password: getEnvRequired("DB_PASSWORD")` - Ajout de la fonction `getEnvRequired()` dans ce fichier - ✅ **`.env.example`** (nouveau fichier créé) - Documentation complète des variables d'environnement - JWT_SECRET et DATABASE_URL marqués comme REQUIS ### Fichiers analysés (non modifiés) - `internal/config/config.go` - Fonction `Load()` utilise déjà `getEnvRequired()` ✅ - `internal/services/jwt_service.go` - Gère correctement l'absence de secret ✅ - `internal/config/secrets.go` - Liste des secrets correctement définie ✅ --- ## 2. Autres secrets avec défaut dangereux trouvés | Variable | Fichier | Action | Statut | |----------|---------|--------|--------| | **JWT_SECRET** | `internal/config/config.go:116` | Remplacé par `getEnvRequired()` | ✅ CORRIGÉ | | **DATABASE_URL** | `internal/config/config.go:122` | Remplacé par `getEnvRequired()` (contient password) | ✅ CORRIGÉ | | **DB_PASSWORD** | `cmd/migrate_tool/main.go:20` | Remplacé par `getEnvRequired()` | ✅ CORRIGÉ | | DB_PASSWORD (test) | `internal/database/pool_test.go:23,86` | Acceptable (fichier de test uniquement) | ✅ OK | ### Variables avec défaut acceptable (gardées) | Variable | Fichier | Justification | |----------|---------|---------------| | **PORT** | `config.go:113` | Valeur par défaut "8080" acceptable pour dev local | | **LOG_LEVEL** | `config.go:110` | Valeur par défaut "INFO" acceptable | | **REDIS_URL** | `config.go:121` | URL locale par défaut acceptable pour dev | | **CORS_ORIGINS** | `config.go:101` | Défaut "*" acceptable pour dev local | | **CHAT_JWT_SECRET** | `config.go:120` | Fallback vers JWT_SECRET (maintenant requis) ✅ | --- ## 3. Code du fix ### 3.1 Fonction `getEnvRequired()` (déjà existante) ```422:429:veza-backend-api/internal/config/config.go // getEnvRequired récupère une variable d'environnement requise (panique si absente) func getEnvRequired(key string) string { value := os.Getenv(key) if value == "" { panic(fmt.Sprintf("Required environment variable %s is not set", key)) } return value } ``` ### 3.2 Modification dans `NewConfig()` **AVANT** (ligne 116): ```go jwtSecret := getEnv("JWT_SECRET", "your-super-secret-jwt-key") ``` **APRÈS** (ligne 115-116): ```go // SECURITY: JWT_SECRET est REQUIS - pas de valeur par défaut pour éviter les failles de sécurité jwtSecret := getEnvRequired("JWT_SECRET") ``` **AVANT** (ligne 122): ```go DatabaseURL: getEnv("DATABASE_URL", "postgresql://veza:password@localhost:5432/veza_db"), ``` **APRÈS** (ligne 122-123): ```go // SECURITY: DATABASE_URL est REQUIS - contient des credentials sensibles DatabaseURL: getEnvRequired("DATABASE_URL"), ``` ### 3.3 Correction dans `cmd/migrate_tool/main.go` **AVANT**: ```go Password: getEnv("DB_PASSWORD", "veza"), ``` **APRÈS**: ```go // SECURITY: DB_PASSWORD is required - no default value to prevent security issues dbPassword := getEnvRequired("DB_PASSWORD") // ... Password: dbPassword, ``` Avec ajout de la fonction `getEnvRequired()` dans ce fichier. --- ## 4. Tests ajoutés ### 4.1 Test pour JWT_SECRET manquant ```287:308:veza-backend-api/internal/config/config_test.go // TestNewConfig_RequiresJWTSecret vérifie que NewConfig() refuse de démarrer sans JWT_SECRET // Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut hardcodée func TestNewConfig_RequiresJWTSecret(t *testing.T) { // Sauvegarder les valeurs originales originalJWTSecret := os.Getenv("JWT_SECRET") originalDatabaseURL := os.Getenv("DATABASE_URL") // Nettoyer après le test defer func() { if originalJWTSecret != "" { os.Setenv("JWT_SECRET", originalJWTSecret) } else { os.Unsetenv("JWT_SECRET") } if originalDatabaseURL != "" { os.Setenv("DATABASE_URL", originalDatabaseURL) } else { os.Unsetenv("DATABASE_URL") } }() // Supprimer JWT_SECRET - devrait causer un panic os.Unsetenv("JWT_SECRET") // Définir DATABASE_URL pour éviter un panic sur cette variable (on teste seulement JWT_SECRET) os.Setenv("DATABASE_URL", "postgresql://test:test@localhost:5432/test_db") // Devrait paniquer car JWT_SECRET est requis assert.Panics(t, func() { _, _ = NewConfig() }, "NewConfig should panic when JWT_SECRET is missing") } ``` ### 4.2 Test pour DATABASE_URL manquant ```310:337:veza-backend-api/internal/config/config_test.go // TestNewConfig_RequiresDatabaseURL vérifie que NewConfig() refuse de démarrer sans DATABASE_URL // Ce test valide la correction de sécurité qui empêche l'utilisation d'une valeur par défaut avec credentials func TestNewConfig_RequiresDatabaseURL(t *testing.T) { // Sauvegarder les valeurs originales originalJWTSecret := os.Getenv("JWT_SECRET") originalDatabaseURL := os.Getenv("DATABASE_URL") // Nettoyer après le test defer func() { if originalJWTSecret != "" { os.Setenv("JWT_SECRET", originalJWTSecret) } else { os.Unsetenv("JWT_SECRET") } if originalDatabaseURL != "" { os.Setenv("DATABASE_URL", originalDatabaseURL) } else { os.Unsetenv("DATABASE_URL") } }() // Définir JWT_SECRET (minimum 32 caractères pour passer la validation) os.Setenv("JWT_SECRET", "test-jwt-secret-key-minimum-32-characters-long") // Supprimer DATABASE_URL - devrait causer un panic os.Unsetenv("DATABASE_URL") // Devrait paniquer car DATABASE_URL est requis assert.Panics(t, func() { _, _ = NewConfig() }, "NewConfig should panic when DATABASE_URL is missing") } ``` ### 4.3 Résultat des tests ```bash $ go test ./internal/config -run TestNewConfig_RequiresJWTSecret -v === RUN TestNewConfig_RequiresJWTSecret --- PASS: TestNewConfig_RequiresJWTSecret (0.00s) PASS ok veza-backend-api/internal/config 0.015s ``` ✅ **Tests passent avec succès** --- ## 5. Documentation mise à jour ### 5.1 Fichier `.env.example` créé Nouveau fichier créé : `veza-backend-api/.env.example` **Contenu clé**: - Section "VARIABLES REQUISES" avec JWT_SECRET et DATABASE_URL - Instructions claires pour générer JWT_SECRET - Toutes les variables optionnelles documentées avec leurs valeurs par défaut - Commentaires explicatifs pour chaque variable **Extrait**: ```bash # ============================================ # VARIABLES REQUISES (DOIVENT ÊTRE DÉFINIES) # ============================================ # JWT_SECRET - REQUIS - Secret pour signer et valider les tokens JWT # DOIT être défini - minimum 32 caractères pour la sécurité # Générer avec: openssl rand -base64 32 JWT_SECRET= # DATABASE_URL - REQUIS - URL de connexion à la base de données PostgreSQL # Format: postgresql://user:password@host:port/database?sslmode=disable # DOIT être défini - contient des credentials sensibles DATABASE_URL= ``` ### 5.2 Documentation existante - ✅ `internal/config/docs.go` - JWT_SECRET déjà marqué comme `Required: true` - ✅ `internal/config/docs_test.go` - Tests vérifient que JWT_SECRET est requis - ⚠️ README principal - Ne mentionne pas les variables d'environnement (non critique) --- ## 6. Audit secrets supplémentaires ### 6.1 Recherche exhaustive effectuée **Commandes exécutées**: ```bash grep -r "JWT_SECRET" veza-backend-api/ grep -r "jwt.*secret\|secret.*jwt" veza-backend-api/ -i grep -r "getEnv.*secret\|getEnv.*JWT" veza-backend-api/ -i grep -r "your-super-secret" veza-backend-api/ -i grep -r "password\|secret\|api_key" veza-backend-api/internal/config/ -i ``` ### 6.2 Résultats de l'audit #### ✅ Secrets correctement gérés | Secret | Fichier | Statut | |--------|---------|--------| | JWT_SECRET | `internal/config/config.go` | ✅ Corrigé (getEnvRequired) | | DATABASE_URL | `internal/config/config.go` | ✅ Corrigé (getEnvRequired) | | DB_PASSWORD | `cmd/migrate_tool/main.go` | ✅ Corrigé (getEnvRequired) | | JWT_SECRET | `internal/config/Load()` | ✅ Déjà requis (getEnvRequired) | | DB_PASSWORD | `internal/config/Load()` | ✅ Déjà requis (getEnvRequired) | #### ✅ Secrets dans les tests (acceptables) | Secret | Fichier | Statut | |--------|---------|--------| | DB_PASSWORD | `internal/database/pool_test.go` | ✅ OK (fichier de test uniquement) | | JWT_SECRET | `internal/config/testutils.go` | ✅ OK (utilitaire de test) | #### ✅ Secrets correctement masqués dans les logs - `internal/config/secrets.go` - Fonction `MaskSecret()` implémentée - `internal/config/config.go:549` - JWT_SECRET masqué dans les logs - `internal/config/config.go:550` - DATABASE_URL masqué dans les logs ### 6.3 Aucun secret hardcodé trouvé ✅ **Aucune autre valeur par défaut dangereuse trouvée dans le code de production** --- ## 7. Commandes pour appliquer ### 7.1 Vérification des modifications ```bash cd veza-backend-api # Vérifier que le code compile go build ./internal/config/... # Exécuter les tests go test ./internal/config -run TestNewConfig_Requires -v # Vérifier tous les tests de config go test ./internal/config/... -v ``` ### 7.2 Application en production **⚠️ IMPORTANT**: Cette correction est **BREAKING** pour les environnements qui n'ont pas défini JWT_SECRET. **Étapes de déploiement**: 1. **Avant le déploiement**: ```bash # Vérifier que JWT_SECRET est défini dans tous les environnements echo $JWT_SECRET # Ne doit pas être vide echo $DATABASE_URL # Ne doit pas être vide ``` 2. **Déployer le code**: ```bash git add internal/config/config.go internal/config/config_test.go .env.example cmd/migrate_tool/main.go git commit -m "security: Remove hardcoded JWT_SECRET default value - Replace getEnv() with getEnvRequired() for JWT_SECRET in NewConfig() - Replace getEnv() with getEnvRequired() for DATABASE_URL (contains credentials) - Add tests to verify panic when required variables are missing - Create .env.example with clear documentation of required variables - Fix DB_PASSWORD default in migrate_tool BREAKING CHANGE: JWT_SECRET and DATABASE_URL are now required. Application will panic at startup if these variables are not set." git push ``` 3. **Vérifier le démarrage**: ```bash # L'application doit démarrer normalement si les variables sont définies # L'application doit PANIC si JWT_SECRET ou DATABASE_URL sont absents ``` ### 7.3 Migration des environnements existants **Pour les environnements qui utilisent encore la valeur par défaut**: 1. Générer un nouveau JWT_SECRET: ```bash openssl rand -base64 32 ``` 2. Définir la variable d'environnement: ```bash export JWT_SECRET="" # Ou dans .env: echo "JWT_SECRET=" >> .env ``` 3. Redémarrer l'application --- ## 8. Impact et compatibilité ### 8.1 Rétrocompatibilité ✅ **Rétrocompatible** pour les environnements déjà configurés correctement : - Si `JWT_SECRET` est défini → Aucun changement de comportement - Si `DATABASE_URL` est défini → Aucun changement de comportement ❌ **Breaking change** pour les environnements non configurés : - Si `JWT_SECRET` n'est pas défini → Application panic au démarrage - Si `DATABASE_URL` n'est pas défini → Application panic au démarrage ### 8.2 Message d'erreur En cas de variable manquante, l'application affichera : ``` panic: Required environment variable JWT_SECRET is not set ``` ou ``` panic: Required environment variable DATABASE_URL is not set ``` **Avantage**: Message clair et explicite, pas de crash silencieux. --- ## 9. Validation finale ### ✅ Checklist de sécurité - [x] JWT_SECRET n'a plus de valeur par défaut hardcodée - [x] DATABASE_URL n'a plus de valeur par défaut avec credentials - [x] DB_PASSWORD dans migrate_tool corrigé - [x] Tests ajoutés pour vérifier le comportement - [x] Documentation créée (.env.example) - [x] Aucun autre secret avec défaut dangereux trouvé - [x] Code compile sans erreur - [x] Tests passent ### ✅ Tests de validation ```bash # Test 1: Vérifier que NewConfig() panic sans JWT_SECRET $ go test ./internal/config -run TestNewConfig_RequiresJWTSecret -v PASS # Test 2: Vérifier que NewConfig() panic sans DATABASE_URL $ go test ./internal/config -run TestNewConfig_RequiresDatabaseURL -v PASS # Test 3: Compilation $ go build ./internal/config/... OK ``` --- ## 10. Conclusion ✅ **Faille de sécurité corrigée avec succès** - **3 fichiers modifiés** pour corriger les valeurs par défaut dangereuses - **2 nouveaux tests** ajoutés pour valider le comportement - **1 fichier de documentation** créé (.env.example) - **Aucun secret hardcodé** restant dans le code de production **L'application refuse maintenant de démarrer si JWT_SECRET ou DATABASE_URL ne sont pas définis**, empêchant ainsi l'utilisation accidentelle de valeurs par défaut non sécurisées. --- **Rapport généré le**: 2025-01-27 **Validé par**: Tests automatisés ✅