diff --git a/S%C3%A9curit%C3%A9.md b/S%C3%A9curit%C3%A9.md new file mode 100644 index 0000000..a64a7dc --- /dev/null +++ b/S%C3%A9curit%C3%A9.md @@ -0,0 +1,109 @@ +# Sécurité + +## Principes + +Le projet applique trois règles absolues pour protéger les secrets : + +- **Aucun secret en clair** : ni dans le code source, ni dans les logs, ni dans les messages d'erreur, ni dans les fixtures de test. +- **Masquage systématique** : utiliser `redact_url()`, `redact_secrets()` et `redact_exception()` depuis `utils/redaction.py`. +- **Chaînage d'exceptions** : ne jamais conserver une exception externe brute comme `__cause__` ou `__context__`. Journaliser la version expurgée puis utiliser `raise ... from None`, ou chaîner une cause elle-même expurgée. + +## Masquage des données sensibles + +### Masquage des URLs + +La fonction `redact_url()` masque les paramètres sensibles dans les URLs. Les clés sensibles (insensibles à la casse) sont : `icalsecurise`, `token`, `key`, `password`, `secret`. Elle utilise `urlsplit`/`urlunsplit`/`parse_qsl` pour reconstruire l'URL. En cas d'erreur de parsing, elle retourne `REDACTED_URL`. + +### Masquage des secrets dans le texte + +La fonction `redact_secrets()` masque les URLs et les tokens isolés dans un texte. Elle traite les motifs comme : `icalsecurise=XXX`, `token=XXX`, `password=XXX`, `secret=XXX`. Les secrets supplémentaires (`extra_secrets`) sont triés par longueur décroissante pour éviter les masquages partiels. + +### Masquage des exceptions + +La fonction `redact_exception(exc, extra_secrets=())` applique `redact_secrets` au message de l'exception et aux secrets configurés. La méthode `Settings.redaction_secrets()` retourne un tuple de tous les secrets configurés (mots de passe, clés API) à transmettre. + +## Configuration des logs + +Le *formatter* `RedactingFormatter` dans `utils/logging.py` masque les secrets dans tous les enregistrements de log : + +- Masquage de `record.msg` +- Masquage de `record.args` (gère les tuples et les dictionnaires) +- `setup_logging()` configure un *handler* `stdout` avec ce *formatter* +- Les loggers tiers (`urllib3`, `slixmpp`) sont positionnés sur `WARNING` + +## Types sécurisés + +Les mots de passe et clés API utilisent `pydantic.SecretStr` : + +- Ils ne sont jamais sérialisés en clair. +- Ils sont masqués dans les messages d'erreur Pydantic (`hide_input_in_errors` pour les paramètres XMPP). + +## Vérification avant déploiement + +### Règles de détection + +Le script `scripts/check_secrets.py` analyse le dépôt pour détecter les secrets littéraux avec trois motifs regex : + +- **Affectation littérale** : `key = "value"` ou `key: "value"` (valeur de 3+ caractères entre guillemets) +- **Affectation non quotée** : pour les fichiers de configuration (`.conf`, `.ini`, `.toml`, `.yaml`, `.yml`), `key = value` (valeur de 3+ caractères non quotée) +- **Paramètre d'URL** : `?token=XXX` ou `&api_key=XXX` (valeur de 3+ caractères) + +### Exclusions + +Le script exclut : + +- Les répertoires `.git`, `.venv`, `.worktrees`, `__pycache__` +- Les fichiers `.env` (jamais versionnés) +- Le fichier `GUIDE_DEV_PYTHON.md` (exemples intentionnels) +- Le répertoire `tests/` +- Les lignes marquées avec `secret-check: allow` (liste blanche locale pour les fixtures/exemples) + +### Utilisation + +```bash +# Vérifier tout le dépôt +python scripts/check_secrets.py + +# Vérifier uniquement les fichiers indexés (avant un commit) +python scripts/check_secrets.py --staged +``` + +Les codes de sortie sont : + +- `0` : aucun secret détecté +- `1` : secrets trouvés +- `2` : erreur lors de l'exécution + +Les résultats n'exposent **jamais** la valeur détectée, seulement le chemin, le numéro de ligne et le nom de la règle. + +## Tests de non-fuite + +La suite de tests inclut des **tests de non-fuite obligatoires** : + +- Injecter des valeurs sentinelles distinctes dans l'URL, les identifiants, le mot de passe et la clé API +- Provoquer une erreur externe +- Vérifier l'absence dans : + - Le message d'erreur + - Les logs + - `__cause__` et `__context__` + - Le *traceback* complet + +Chaque secret utilise une sentinelle distincte pour identifier précisément toute fuite éventuelle. + +## Fichier `.env` + +Le fichier `.env` n'est **jamais** commité (couvert par `.gitignore`). Utiliser `.env.example` comme modèle. En production, le fichier doit être local et lisible uniquement par le compte de service : + +```bash +sudo install -d -m 0700 -o -g +sudo install -m 0600 -o -g .env +``` + +## Bonnes pratiques + +- Ne jamais placer de secrets dans les unités systemd, les commandes shell ou les journaux système. +- Éviter de partager les exports de logs sans revue : les données sensibles de l'environnement ou des outils tiers ne doivent pas être supposées sûres par défaut. +- Après modification des identifiants, relancer `check_secrets.py` et un `--dry-run` avant de redémarrer le service. +- En cas d'échec, ne pas redémarrer automatiquement après modification des identifiants : corriger la configuration, relancer les vérifications, puis consulter le journal expurgé. + +Voir aussi : [Configuration](Configuration) pour les variables d'environnement, [Déploiement](Déploiement) pour la configuration en production. \ No newline at end of file