fix(wp-a): dashboard fail-closed sans mot de passe par défaut

Le dashboard refuse de démarrer si DASHBOARD_PASSWORD absent ET auth non
explicitement désactivée (DASHBOARD_AUTH_DISABLED). Supprime le mot de passe
par défaut hardcodé exploitable.

- web_dashboard/app.py : _require_dashboard_password() fail-closed (lève en prod
  sans secret ; mode dev/test = DASHBOARD_AUTH_DISABLED=true)
- tests/unit/conftest.py : DASHBOARD_AUTH_DISABLED=true par défaut pour les tests
- tests/unit/test_dashboard_failclosed_wpa.py : 5 tests (fail-closed, anti-régression défaut)
- tests/unit/test_dashboard_auth_p0a.py : fixture _restore_module restaure un état neutre sûr

48 tests dashboard verts (WP-A + non-régression auth/routes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-06-08 15:27:06 +02:00
parent 0e215da842
commit 549ea0631b
4 changed files with 66 additions and 12 deletions

View File

@@ -70,19 +70,25 @@ _DASHBOARD_AUTH_DISABLED = os.getenv("DASHBOARD_AUTH_DISABLED", "").lower() in (
"1", "true", "yes",
)
# Si pas de password défini en env ET auth pas explicitement désactivée →
# on utilise un mot de passe par défaut "safe" (long, random-ish) ET on log
# un WARNING très visible au démarrage pour forcer Dom à le configurer
# avant un déploiement prod. On ne veut surtout pas générer un mot de passe
# aléatoire à chaque boot (même problème que l'API token auto-généré).
if not _DASHBOARD_PASSWORD and not _DASHBOARD_AUTH_DISABLED:
_DASHBOARD_PASSWORD = "changeme-dashboard-RpaVision2026!"
api_logger.warning(
"[SÉCURITÉ] DASHBOARD_PASSWORD non défini en env — utilisation d'un "
"mot de passe par défaut temporaire. DÉFINIR DASHBOARD_PASSWORD "
"AVANT TOUT DÉPLOIEMENT (identifiant : DASHBOARD_USER)."
# WP-A (fail-closed sécurité) : en l'absence de DASHBOARD_PASSWORD et hors mode dev/test
# explicite (DASHBOARD_AUTH_DISABLED), on REFUSE de démarrer plutôt que de tomber sur un
# mot de passe par défaut connu (exploitable). Dev/test : DASHBOARD_AUTH_DISABLED=true ou
# un vrai DASHBOARD_PASSWORD.
def _require_dashboard_password(password: str, auth_disabled: bool) -> str:
"""Résout le mot de passe dashboard en fail-closed. Lève si ni secret ni dev explicite."""
if password:
return password
if auth_disabled:
return "" # auth explicitement désactivée (dev/test) — password non utilisé
raise RuntimeError(
"[SÉCURITÉ] DASHBOARD_PASSWORD non défini et auth non désactivée : le dashboard "
"refuse de démarrer (fail-closed). Définir DASHBOARD_PASSWORD (prod) ou "
"DASHBOARD_AUTH_DISABLED=true (dev/test)."
)
_DASHBOARD_PASSWORD = _require_dashboard_password(_DASHBOARD_PASSWORD, _DASHBOARD_AUTH_DISABLED)
# Paths publics (pas d'auth, pour healthchecks externes)
_PUBLIC_DASHBOARD_PATHS = {
"/health",