Toutes les notes

Sécurité · Django · 8 min

Concevoir un journal d'audit utile sans exposer les données sensibles

Designing useful audit logs without exposing sensitive data

Un journal d'audit doit reconstruire une action. Il ne devrait pas devenir une deuxième base de données contenant les mêmes renseignements qu'il cherche à protéger.

Le bon objectif n'est pas « tout enregistrer ». C'est « conserver assez de contexte pour rendre les actions vérifiables, avec le moins de données sensibles possible ».

Commencer par les questions auxquelles le journal doit répondre

Dans une application métier, l'équipe administrative veut généralement savoir qui a consulté un dossier, qui l'a créé, quel champ a été modifié, quand une exportation a été produite ou pourquoi un accès a changé. Ces questions définissent le modèle d'audit. Elles ne nécessitent pas automatiquement une copie de chaque ancienne et nouvelle valeur.

Le modèle que j'utilise sépare donc l'événement de son contenu métier: type d'action, auteur, dossier concerné, nom du champ, chemin applicatif et horodatage. Les métadonnées sont limitées à des valeurs contrôlées par le code, par exemple le résultat d'une tentative de connexion ou le nombre de résultats retournés.

Architecture séparant application, base métier, audit, export et sauvegarde
Le journal d'audit est une responsabilité distincte. Il observe les opérations sans devenir la source de vérité des dossiers.

Tracer le changement, pas nécessairement la valeur

Pour une modification de dossier, l'application prend un instantané avant et après la validation du formulaire. Elle compare les deux états et crée une entrée par champ modifié. L'entrée indique donc que statut_couple ou telephone a changé, mais les colonnes old_value et new_value restent vides pour ce flux.

for field_name, _old, _new in diff_snapshots(before, after):
    log_event(
        request,
        EventType.UPDATE,
        client=client,
        field_name=field_name,
    )

Ce compromis conserve l'imputabilité: auteur, moment, objet et champ touché. Il réduit en même temps le risque qu'une sauvegarde de journaux, un écran administratif ou une erreur de diagnostic révèle une ancienne adresse, un numéro de téléphone ou une information clinique.

Les recherches sont un piège discret

Une boîte de recherche accepte souvent un nom, un numéro de téléphone ou un identifiant. Enregistrer la requête brute dans les métadonnées recréerait exactement les données que l'on essaie de limiter. L'application journalise plutôt la longueur de la requête et le nombre de résultats. On peut alors constater qu'une recherche a eu lieu et évaluer son comportement sans conserver ce qui a été saisi.

La même règle s'applique aux erreurs. Un message utile décrit le contexte technique et un identifiant de corrélation; il évite d'imprimer tout le formulaire ou l'objet sérialisé.

Prévoir l'anonymisation dès le modèle

L'anonymisation d'un dossier serait incomplète si ses anciennes valeurs restaient accessibles dans le journal. Le flux de rétention retire donc le lien vers le client, l'identifiant public, l'identifiant d'objet, les anciennes et nouvelles valeurs ainsi que les métadonnées associées. Une entrée d'anonymisation distincte permet encore de prouver que l'opération a eu lieu.

Cette décision doit être prise tôt. Plus les journaux contiennent de structures libres, plus il devient difficile de garantir qu'aucune donnée personnelle ne survit dans un coin inattendu.

Restreindre la lecture est aussi important que l'écriture

Les intervenants peuvent travailler sur les dossiers, mais la vue globale du journal reste administrative. Le panneau d'administration Django n'est pas exposé quand l'application tourne en production. Les événements sont indexés par type et date, client et date, puis auteur et date afin que les investigations courantes restent rapides sans donner accès à davantage d'information.

Tester les absences, pas seulement les présences

Les tests les plus importants affirment parfois qu'une donnée n'existe pas. Ici, un test vérifie qu'une recherche ne stocke pas la requête brute. Un autre confirme qu'une modification ne conserve pas les valeurs du champ. Un troisième anonymise un dossier et vérifie que les anciennes entrées ont été expurgées.

Ces assertions protègent une propriété de sécurité qui serait invisible dans un test fonctionnel classique. L'écran peut continuer de fonctionner même si un futur changement recommence accidentellement à stocker trop d'information.

La règle pratique

  • Définir les questions d'audit avant les colonnes.
  • Utiliser des types d'événements et métadonnées contrôlés.
  • Éviter les charges utiles, formulaires et requêtes brutes.
  • Séparer les permissions de modification et de consultation du journal.
  • Concevoir la rétention et l'anonymisation en même temps que le modèle.
  • Ajouter des tests qui prouvent que les valeurs sensibles ne sont pas conservées.

Un journal d'audit est réussi quand il rend une action compréhensible sans élargir inutilement la surface de données à protéger.