fix(audit-1.6,1.7): remove hardcoded test secrets, block bypass flags in prod
- 1.6: Replace hardcoded JWT secrets in chat server tests with runtime-generated values (env TEST_JWT_SECRET or uuid-based fallback) - 1.7: Add validateNoBypassFlagsInProduction() in config; fail startup if BYPASS_CONTENT_CREATOR_ROLE or CSRF_DISABLED is set in production Refs: AUDIT_TECHNIQUE_INTEGRAL_2026_02_15.md items 1.6, 1.7
This commit is contained in:
parent
a6a9be9ada
commit
2e04d45a14
5 changed files with 108 additions and 4 deletions
|
|
@ -653,8 +653,8 @@ veza/
|
|||
| 1.3 | ~~Ajouter auth sur les endpoints HLS~~ | **M** | `veza-stream-server/src/routes/api.rs` | **✅ Fait** |
|
||||
| 1.4 | ~~Remplacer `panic()` par erreur gracieuse si Redis down~~ | **S** | `veza-backend-api/internal/api/routes_core.go:242` | **✅ Fait** |
|
||||
| 1.5 | ~~Corriger les `.unwrap()` critiques en Rust (paths de production)~~ | **M** | `veza-stream-server/src/`, `veza-chat-server/src/` | **✅ Fait** |
|
||||
| 1.6 | Supprimer les secrets test hardcodés | **S** | `veza-chat-server/src/config.rs:216`, `jwt_manager.rs:575` | P1 |
|
||||
| 1.7 | Ajouter protection contre bypass flags en production | **S** | `veza-backend-api/internal/middleware/auth.go`, `csrf.go` | P1 |
|
||||
| 1.6 | ~~Supprimer les secrets test hardcodés~~ | **S** | `veza-chat-server/src/config.rs:216`, `jwt_manager.rs:575` | **✅ Fait** |
|
||||
| 1.7 | ~~Ajouter protection contre bypass flags en production~~ | **S** | `veza-backend-api/internal/middleware/auth.go`, `csrf.go` | **✅ Fait** |
|
||||
| 1.8 | Implémenter OAuth user lookup | **M** | `veza-backend-api/internal/database/database.go:559` | P1 |
|
||||
| 1.9 | Ajouter `cargo audit` au CI | **S** | `.github/workflows/chat-ci.yml`, `stream-ci.yml` | P2 |
|
||||
|
||||
|
|
|
|||
|
|
@ -1393,6 +1393,31 @@ func (c *Config) Validate() error {
|
|||
return fmt.Errorf("RATE_LIMIT_WINDOW validation failed: %w", err)
|
||||
}
|
||||
|
||||
// Audit 1.7: Fail startup if bypass flags are set in production
|
||||
if err := validateNoBypassFlagsInProduction(c.Env); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateNoBypassFlagsInProduction vérifie qu'aucun flag de bypass n'est activé en production (audit 1.7)
|
||||
func validateNoBypassFlagsInProduction(env string) error {
|
||||
envNorm := strings.ToLower(strings.TrimSpace(env))
|
||||
if envNorm != "production" && envNorm != "prod" {
|
||||
return nil // Pas en production, pas de vérification
|
||||
}
|
||||
var violations []string
|
||||
if os.Getenv("BYPASS_CONTENT_CREATOR_ROLE") == "true" {
|
||||
violations = append(violations, "BYPASS_CONTENT_CREATOR_ROLE=true")
|
||||
}
|
||||
if os.Getenv("CSRF_DISABLED") == "true" {
|
||||
violations = append(violations, "CSRF_DISABLED=true")
|
||||
}
|
||||
if len(violations) > 0 {
|
||||
return fmt.Errorf("security: bypass flags are not allowed in production: %s. Remove these environment variables before deploying",
|
||||
strings.Join(violations, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -291,3 +292,78 @@ func TestConfig_Validate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestValidateNoBypassFlagsInProduction vérifie que les bypass flags bloquent le démarrage en production (audit 1.7)
|
||||
func TestValidateNoBypassFlagsInProduction(t *testing.T) {
|
||||
validConfig := &Config{
|
||||
Env: "development",
|
||||
AppPort: 8080,
|
||||
JWTSecret: strings.Repeat("a", 32),
|
||||
DatabaseURL: "postgresql://user:pass@localhost:5432/db",
|
||||
RedisURL: "redis://localhost:6379",
|
||||
RateLimitLimit: 100,
|
||||
RateLimitWindow: 60,
|
||||
}
|
||||
|
||||
t.Run("production with BYPASS_CONTENT_CREATOR_ROLE fails", func(t *testing.T) {
|
||||
orig := os.Getenv("BYPASS_CONTENT_CREATOR_ROLE")
|
||||
defer func() {
|
||||
if orig != "" {
|
||||
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", orig)
|
||||
} else {
|
||||
os.Unsetenv("BYPASS_CONTENT_CREATOR_ROLE")
|
||||
}
|
||||
}()
|
||||
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", "true")
|
||||
|
||||
cfg := *validConfig
|
||||
cfg.Env = "production"
|
||||
err := cfg.Validate()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "BYPASS_CONTENT_CREATOR_ROLE")
|
||||
assert.Contains(t, err.Error(), "bypass flags")
|
||||
})
|
||||
|
||||
t.Run("production with CSRF_DISABLED fails", func(t *testing.T) {
|
||||
orig := os.Getenv("CSRF_DISABLED")
|
||||
defer func() {
|
||||
if orig != "" {
|
||||
os.Setenv("CSRF_DISABLED", orig)
|
||||
} else {
|
||||
os.Unsetenv("CSRF_DISABLED")
|
||||
}
|
||||
}()
|
||||
os.Setenv("CSRF_DISABLED", "true")
|
||||
|
||||
cfg := *validConfig
|
||||
cfg.Env = "production"
|
||||
err := cfg.Validate()
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "CSRF_DISABLED")
|
||||
assert.Contains(t, err.Error(), "bypass flags")
|
||||
})
|
||||
|
||||
t.Run("development with bypass flags succeeds", func(t *testing.T) {
|
||||
origBypass := os.Getenv("BYPASS_CONTENT_CREATOR_ROLE")
|
||||
origCsrf := os.Getenv("CSRF_DISABLED")
|
||||
defer func() {
|
||||
if origBypass != "" {
|
||||
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", origBypass)
|
||||
} else {
|
||||
os.Unsetenv("BYPASS_CONTENT_CREATOR_ROLE")
|
||||
}
|
||||
if origCsrf != "" {
|
||||
os.Setenv("CSRF_DISABLED", origCsrf)
|
||||
} else {
|
||||
os.Unsetenv("CSRF_DISABLED")
|
||||
}
|
||||
}()
|
||||
os.Setenv("BYPASS_CONTENT_CREATOR_ROLE", "true")
|
||||
os.Setenv("CSRF_DISABLED", "true")
|
||||
|
||||
cfg := *validConfig
|
||||
cfg.Env = "development"
|
||||
err := cfg.Validate()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -213,7 +213,8 @@ impl Default for SecurityConfig {
|
|||
|
||||
#[cfg(test)]
|
||||
Self {
|
||||
jwt_secret: "test_jwt_secret_minimum_32_characters_long".to_string(),
|
||||
jwt_secret: env::var("TEST_JWT_SECRET")
|
||||
.unwrap_or_else(|_| format!("test_{}_{}", uuid::Uuid::new_v4(), "x".repeat(20))),
|
||||
jwt_access_duration: Duration::from_secs(900), // 15 min
|
||||
jwt_refresh_duration: Duration::from_secs(86400 * 30), // 30 days
|
||||
jwt_algorithm: "HS256".to_string(),
|
||||
|
|
|
|||
|
|
@ -571,8 +571,10 @@ mod tests {
|
|||
use std::time::Duration;
|
||||
|
||||
fn create_test_config() -> SecurityConfig {
|
||||
let jwt_secret = std::env::var("TEST_JWT_SECRET")
|
||||
.unwrap_or_else(|_| format!("test_{}_{}", Uuid::new_v4(), "x".repeat(20)));
|
||||
SecurityConfig {
|
||||
jwt_secret: "test_secret_key_32_chars_minimum_required".to_string(),
|
||||
jwt_secret,
|
||||
jwt_access_duration: Duration::from_secs(3600), // 1 heure
|
||||
jwt_refresh_duration: Duration::from_secs(86400), // 24 heures
|
||||
jwt_algorithm: "HS256".to_string(),
|
||||
|
|
|
|||
Loading…
Reference in a new issue