La protection des donnĂ©es personnelles est un point crucial de tout systĂšme d’information. Il l’est devenu d’autant plus depuis la mise en place du RGPD il y a quelques annĂ©es. Ce rĂšglement a contraint les professionnels Ă  mettre en place des solutions pour assurer la confidentialitĂ© de ces donnĂ©es et ainsi, respecter la vie privĂ©e.

Parmi les points critiques d’un systĂšme d’information, on trouve, entre autre, les bases de donnĂ©es qui peuvent contenir une quantitĂ© incroyable de donnĂ©es sensibles (noms, coordonnĂ©es, informations bancaires, 
). Mais ces bases de production (et leur contenu) sont souvent utilisĂ©es par les dĂ©veloppeurs eux-mĂȘmes, des prestataires externes, 
 pour faire des tests de leurs applications ou logiciels, et tous ces acteurs peuvent donc avoir accĂšs Ă  toutes ces donnĂ©es sensibles. Une des solutions possibles est d’anonymiser ces bases pour assurer la confidentialitĂ© des donnĂ©es personnelles, et ainsi Ă©viter d’identifier des personnes en particulier.

Dans cet article, je vais vous prĂ©senter un outil permettant d’anonymiser une base de donnĂ©es PostgreSQL pour pouvoir l’utiliser dans un environnement de dĂ©veloppement (ou de recette) en prĂ©servant la confidentialitĂ© des donnĂ©es : PostgreSQL Anonymizer.

RGPD, données personnelles et anonymisation

Le RGPD (RĂšglement GĂ©nĂ©ral sur la Protection des DonnĂ©es) rĂšglemente le traitement des donnĂ©es personnelles dans l’Union EuropĂ©enne et est entrĂ© en vigueur le 25 mai 2018. Il oblige les entitĂ©s collectant des donnĂ©es personnelles Ă  se mettre dans une dĂ©marche et une rĂ©flexion pour assurer la protection de ces donnĂ©es :

  • ne collecter que les donnĂ©es nĂ©cessaires
  • ĂȘtre transparent sur les donnĂ©es collectĂ©es
  • permettre Ă  la personne concernĂ©e d’accĂ©der, de modifier ou supprimer ces donnĂ©es
  • ne conserver ces donnĂ©es que le temps nĂ©cessaire
  • sĂ©curiser ces donnĂ©es

Mais qu’est-ce qu’une donnĂ©e personnelle ? C’est une information permettant d’identifier une personne physique :

  • directement : par son nom ou son prĂ©nom
  • indirectement : par son numĂ©ro de tĂ©lĂ©phone, son adresse, un numĂ©ro d’identification, 
 Avec ces donnĂ©es, il peut ĂȘtre plus ou moins facile de retrouver une personne et de recueillir Ă©normĂ©ment d’informations la concernant en recoupant toutes ces donnĂ©es.

Pour assurer la sĂ©curisation des donnĂ©es et prĂ©server leur confidentialitĂ©, la CNIL propose, entre autres, comme solution, l’anonymisation de ces donnĂ©es, empĂȘchant ainsi l’identification d’une personne physique de maniĂšre irrĂ©versible. L’anonymisation consiste notamment Ă  supprimer les donnĂ©es d’identification directe (nom, prĂ©nom, 
), les valeurs rares (les personnes centenaires ou les personnes vivant dans des endroits peu peuplĂ©s seront facilement identifiables), 
 tout en conservant un jeu de donnĂ©es cohĂ©rent et utilisable. L’anonymisation peut se faire Ă  l’aide de diffĂ©rentes techniques :

  • la randomisation : remplace les donnĂ©es par des valeurs alĂ©atoires pour Ă©viter de dĂ©duire de nouvelles informations sur la personne (ex : nom alĂ©atoire, adresse alĂ©atoire, 
)
  • la gĂ©nĂ©ralisation : modifie l’échelle des valeurs pour Ă©viter d’individualiser une personne (ex : ramener la date de naissance au 1er janvier, ramener la ville Ă  la prĂ©fecture du dĂ©partement, 
)

Attention, il faut diffĂ©rencier l’anonymisation et la pseudonymisation : la pseudonymisation remplace une donnĂ©e directement identifiante (nom, prĂ©nom, 
) par une donnĂ©e indirectement identifiante (pseudo, numĂ©ro, 
) ce qui permet tout de mĂȘme de retrouver d’autres infos personnelles. Contrairement Ă  l’anonymisation qui est un processus irrĂ©versible, la pseudonymisation est un processus rĂ©verssible. Cependant, la pseudonymisation reste une mesure recommandĂ©e, Ă  dĂ©faut d’anonymisation complĂšte. Le but est de compliquer la rĂ©cupĂ©ration des donnĂ©es personnelles.

Le RGPD se base sur 3 critĂšres pour garantir l’anonymat des donnĂ©es :

  • l’individualisation : il ne doit pas ĂȘtre possible d’isoler un individu dans le jeu de donnĂ©es
  • la corrĂ©lation : il ne doit pas ĂȘtre possible de relier entre eux des ensembles de donnĂ©es distincts concernant un mĂȘme individu l’infĂ©rence : il ne doit pas ĂȘtre possible de dĂ©duire, de façon quasi certaine, de nouvelles informations sur un individu

(source CNIL)

Présentation de PostgreSQL Anonymizer

PostgreSQL Anonymizer est une extension de PostgreSQL permettant d’anonymiser une base de donnĂ©es. Elle est, Ă  l’heure oĂč j’écris cet article, en version beta mais est trĂšs performante dans la version actuelle.

PostgreSQL Anonymizer permet d’anonymiser une base en dĂ©clarant des rĂšgles de “masquage” sur les colonnes “sensibles”.

PostgreSQL Anonymizer propose 3 façons d’anonymiser une base de donnĂ©es :

  • la gĂ©nĂ©ration d’un dump anonyme à partir d’une base de donnĂ©es source. Cette solution prĂ©serve le contenu de la base de donnĂ©e source. C’est cette solution que je vais mettre en oeuvre dans la suite de l’article.
  • le masquage statique qui anonymise directement la base de donnĂ©es source et altĂšre donc dĂ©finitivement les donnĂ©es.
  • le masquage dynamique qui anonymise “à la volĂ©e” les donnĂ©es masquĂ©es pour un rĂŽle d’utilisateur donnĂ©.

PostgreSQL Anonymizer contient tout un ensemble de fonctions de masquage basiques :

  • fonctions de destruction qui permettent de remplacer la valeur originale par une constante
  • fonctions alĂ©atoires qui permettent de gĂ©nĂ©rer alĂ©atoirement une valeur : texte, date, nombre, 

  • fonctions fake qui permettent de gĂ©nĂ©rer alĂ©atoirement des valeurs issues d’un rĂ©fĂ©rentiel de valeurs possibles : nom, prĂ©nom, ville, pays, lorem ipsum, 
 Ces rĂ©fĂ©rentiels sont des tables initialisĂ©es lors de l’initialisation de l’extension.
  • fonctions de pseudonymisation qui permettent de gĂ©nĂ©rer des valeurs Ă  partir de la valeur originale et d’un rĂ©fĂ©rentiel de valeurs possibles : nom, prĂ©nom, ville, pays, 
 Attention, ces fonctions sont dĂ©terministes : pour la mĂȘme valeur de dĂ©part, ce sera toujours la mĂȘme valeur qui sera gĂ©nĂ©rĂ©e !
  • fonctions de brouillage partiel qui permettent de masquer partiellement la valeur originale avec un caractĂšre spĂ©cifique : numĂ©ro de tĂ©lĂ©phone (06********), mail (te***@gm***.com)

PostgreSQL Anonymizer permet aussi d’utiliser ses propres fonctions de masquage personnalisĂ©es pour des besoins spĂ©cifiques ou un peu plus complexes.

PostgreSQL Anonymizer propose une image docker qui contient PostgreSQL accompagnĂ© de l’extension PostgreSQL Anonymizer. Cette image docker Ă©vite d’installer l’extension PostgreSQL Anonymizer directement sur la base de production (non intrusif), et permet ainsi de cloisonner le processus d’anonymisation de cette base (ex : sur un autre serveur) sans interfĂ©rer avec la base originale.

Mise en oeuvre

Dans cet exemple, nous allons gĂ©nĂ©rer avec PostgreSQL Anonymizer, un dump anonymisĂ© Ă  partir d’un dump d’une base non-anonymisĂ©e (ex : une base de prod). Pour cela, nous allons utiliser l’image docker de l’extension.

Pré-requis :

  • Etre sous Ubuntu.
  • docker doit ĂȘtre installĂ© sur le poste. Voir la page de la documentation officielle ou cette page.
  • Au moment oĂč cet article est Ă©crit, l’exemple suivant se base sur la version 0.8 de PostgreSQL Anonymizer et la version 13.1 de PostgreSQL.

PrĂ©sentation de l’exemple

Nous allons prendre un exemple simpliste d’une base de donnĂ©es bancaires contenant une table personne avec leurs nom, prĂ©nom, date de naissance, coordonnĂ©es et commentaire et une table compte_bancaire d’une personne contenant la date d’ouverture, l’IBAN et le montant qu’il y a sur le compte.

db_compte.png

Voici le contenu des tables :

table_personne.png

table_compte_bancaire.png

Installation de PostgresSQL Anonymizer

RĂ©cupĂ©rer la derniĂšre version de l’image docker de PostgreSQL Anonymizer :

docker pull registry.gitlab.com/dalibo/postgresql_anonymizer

Lancer un conteneur docker de PostgreSQL Anonymizer nommé test_pganonymizer_container en spĂ©cifiant les identifiants Postgres correspondants Ă  la base que vous souhaitez anonymiser (ici test_pga). Cela va lancer Postgres en ajoutant l’extension Ă  PostgreSQL Anonymizer :

docker run -d --name test_pganonymizer_container -p 6543:5432 -e POSTGRES_USER=test_pga -e POSTGRES_PASSWORD=test_pga -e POSTGRES_DB=test_pga registry.gitlab.com/dalibo/postgresql_anonymizer

RĂ©cupĂ©rer le script d’initialisation de l’extension PostgreSQL Anonymizer :

wget https://scub-france.github.io/anonymisation-base-de-donnees-avec-postgresql-anonymizer/init_pg_anonymizer.sql
SELECT pg_catalog.set_config('search_path', 'public', false);
CREATE EXTENSION IF NOT EXISTS anon CASCADE;
SELECT anon.init();

Initialiser l’extension PostgreSQL Anonymizer sur la base de donnĂ©es test_pga avec le script prĂ©cĂ©dent :

cat init_pg_anonymizer.sql | docker exec -i test_pganonymizer_container psql -U test_pga

Si l’installation s’est bien passĂ©e, vous devriez voir dans les logs l’anonymisation d’une adresse mail :

     partial_email     
-----------------------
 te******@te******.net
(1 row)

A partir de lĂ , vous avez un conteneur prĂȘt pour gĂ©nĂ©rer un dump anonymisĂ© de la base de donnĂ©es que vous souhaitez. Pour l’exemple, ce sera notre base de donnĂ©es bancaires.

Télécharger le dump source de la base de données bancaire dump_source.sql :

wget https://scub-france.github.io/anonymisation-base-de-donnees-avec-postgresql-anonymizer/dump_source.sql

Restaurer la base de donnĂ©es bancaires dans le conteneur d’anonymisation :

cat dump_source.sql | docker exec -i test_pganonymizer_container psql -U test_pga

RĂšgles / Masques basiques

L’ajout d’une rĂšgle de masquage sur une colonne consiste en une requĂȘte SQL. Cet ajout se fait en dĂ©clarant la fonction Ă  appliquer sur la colonne par l’intermĂ©diaire du SECURITY LABEL et en passant les paramĂštres souhaitĂ©s (nom de colonne, variable, constante, 
) :

SECURITY LABEL FOR anon ON COLUMN nom_colonne IS 'MASKED WITH FUNCTION nom_fonction (nom_colonne_ou_params)';

ou pour l’ajout d’une constante :

SECURITY LABEL FOR anon ON COLUMN nom_colonne IS 'MASKED WITH VALUE ' 'La constante''';

Voici la présentation de quelques fonctions de masquages que nous allons appliquer sur notre exemple :

  • anon.fake_last_name() sur la colonne personne.nom : retourne alĂ©atoirement un nom parmi les noms proposĂ©s par l’extension.
SECURITY LABEL FOR anon ON COLUMN public.personne.nom IS 'MASKED WITH FUNCTION anon.fake_last_name()';
  • anon.fake_first_name() sur la colonne personne.prenom : retourne alĂ©atoirement un prĂ©nom parmi les prĂ©noms proposĂ©s par l’extension.
SECURITY LABEL FOR anon ON COLUMN public.personne.prenom IS 'MASKED WITH FUNCTION anon.fake_first_name()';
  • anon.fake_city() sur la colonne personne.ville_naissance : retourne alĂ©atoirement une ville parmi les villes proposĂ©es par l’extension.
SECURITY LABEL FOR anon ON COLUMN public.personne.ville_naissance IS 'MASKED WITH FUNCTION anon.fake_city()';
  • anon.random_phone() sur la colonne personne.telephone : retourne alĂ©atoirement un numĂ©ro de tĂ©lĂ©phone Ă  10 chiffres.
SECURITY LABEL FOR anon ON COLUMN public.personne.telephone IS 'MASKED WITH FUNCTION anon.random_phone()';
  • anon.partial_email(email) sur la colonne personne.mail : retourne le mail passĂ© en paramĂštre partiellement masquĂ©
SECURITY LABEL FOR anon ON COLUMN public.personne.mail IS 'MASKED WITH FUNCTION anon.partial_email(mail)';
  • anon.lorem_ipsum() sur la colonne personne.commentaire : retourne un extrait de Lorem Ipsum. DiffĂ©rents paramĂštres permettent de dĂ©finir la longueur souhaitĂ©e (paragraphes, mots, caractĂšres, 
).
SECURITY LABEL FOR anon
ON COLUMN public.personne.commentaire IS 'MASKED WITH FUNCTION anon.lorem_ipsum(words := 5)';
  • constante “PRENOM” sur la colonne personne.prenom_formate : retourne la constante demandĂ©e.
SECURITY LABEL FOR anon ON COLUMN public.personne.prenom_formate IS 'MASKED WITH VALUE' 'PRENOM''';
  • anon.random_date() sur la colonne compte_bancaire.date_ouverture : retourne alĂ©atoirement une date.
SECURITY LABEL FOR anon
ON COLUMN public.compte_bancaire.date_ouverture IS 'MASKED WITH FUNCTION anon.random_date()';
  • anon.fake_iban() sur la colonne compte_bancaire.iban : retourne un IBAN.
SECURITY LABEL FOR anon ON COLUMN public.compte_bancaire.iban IS 'MASKED WITH FUNCTION anon.fake_iban()';
  • anon.random_int_between(v1, v2) sur la colonne compte_bancaire.montant : retourne alĂ©atoirement un entier appartenant Ă  la plage demandĂ©e.
SECURITY LABEL FOR anon
ON COLUMN public.compte_bancaire.montant IS 'MASKED WITH FUNCTION anon.random_int_between(5000, 200000)';

Pour un aperçu plus exhaustif des rÚgles de masquage, consulter cette page.

RÚgles / Masques personnalisés

Pour dĂ©finir une rĂšgle de masquage personnalisĂ©e pour un comportement spĂ©cifique, il suffit d’écrire une fonction SQL qui implĂ©mente la rĂšgle et dĂ©clarer cette fonction comme rĂšgle sur la colonne souhaitĂ©e.

  • Pour la date de naissance d’une personne, nous aimerions que cette date soit ramenĂ©e au premier juin de l’annĂ©e de naissance. Pour cela, il faut Ă©crire la fonction anon.date_naissance_tronquee(date_initiale) correspondante prenant en paramĂštre la date initiale et la dĂ©clarer sur la colonne souhaitĂ©e :
CREATE OR REPLACE FUNCTION anon.date_naissance_tronquee(date_initiale TIMESTAMP)
RETURNS TEXT
LANGUAGE plpgsal
AS
$$
BEGIN
RETURN date_trunc('year', initial_value) + interval '5 month';
END;
$$;

SECURITY LABEL FOR anon
ON COLUMN public.personne.date_naissance IS
'MASKED WITH FUNCTION anon.date_naissance_tronquee(date_naissance)';
SECURITY LABEL FOR anon
ON COLUMN public.personne.date_naissance IS
'MASKED WITH FUNCTION anon.date_naissance_tronquee(date_naissance) ';
  • Pour le nom formatĂ© d’une personne, nous aimerions que cette valeur soit la valeur en minuscule du nom de la personne. Or, la valeur gĂ©nĂ©rĂ©e pour le nom de la personne est alĂ©atoire (rĂšgle fake_last_name()). Il y a donc peu de chance que l’on gĂ©nĂšre Ă  nouveau le mĂȘme nom pour la colonne nom_formate. Pour contourner ce problĂšme, nous allons utiliser la fonction dĂ©terministe anon.pseudo_last_name(‘seed’,’salt’) qui permet de retourner le mĂȘme nom avec les mĂȘmes paramĂštres. Cette fonction sera utilisĂ©e comme rĂšgle sur la colonne personne.nom et dans une nouvelle fonction personnalisĂ©e utilisĂ©e comme rĂšgle de la colonne personne.nom_formate. Le seed sera la concatĂ©nation de l’identifiant et du nom de la personne pour qu’une autre personne ayant le mĂȘme nom ait un nom “anonymisĂ©â€ diffĂ©rent. Le salt sera une chaine alĂ©atoire gĂ©nĂ©rĂ©e Ă  l’initialisation des rĂšgles pour qu’une personne ait un nom “anonymisĂ©â€ diffĂ©rent Ă  chaque processus d’anonymisation.
CREATE OR REPLACE FUNCTION anon.nom_minuscule(seed TEXT, salt TEXT)
RETURNS TEXT
LANGUAGE plpgsal
AS
$$
BEGIN
RETURN lower(anon.pseudo_last_name(seed, salt));
END;
$$;
SECURITY LABEL FOR anon ON COLUMN public.personne.nom IS
'MASKED WITH FUNCTION anon.pseudo_last_name(nom || id, ''1234'')';

Toutes les rÚgles déclarées précédemment sont disponibles dans le fichier regles.sql :

wget https://scub-france.github.io/anonymisation-base-de-donnees-avec-postgresql-anonymizer/regles.sql

DĂ©clarer l’ensemble des rĂšgles de masquage dans le conteneur d’anonymisation :

cat regles.sql | docker exec -i test_pganonymizer_container psql -U test_pga

Génération du dump anonyme

Pour gĂ©nĂ©rer le dump anonyme de la base source, il faut utiliser la commande pg_dump_anon qui s’utilise comme la commande pg_dump :

docker exec -i test_pganonymizer_container pg_dump_anon -h localhost -U test_pga > dump_anonyme.sql

Une fois le dump anonyme restaurĂ© sur une autre base, voici le rĂ©sultat de l’anonymisation :

table_personne_anonyme.png

table_compte_bancaire_anonyme.png

Une fois l’anonymisation terminĂ©, vous pouvez faire un peu de nettoyage (arrĂȘt et suppression du conteneur) :

docker container stop test_pganonymizer_container
docker container rm test_pganonymizer_container

Avantages / Inconvénients

Avantages

  • Nombreuses fonctions d’anonymisation fournies pour les types de donnĂ©es les plus classiques.
  • PossibilitĂ© d’ajouter ses propres rĂšgles.
  • Processus d’anonymisation non intrusif en utilisation “docker”.
  • Fonctionne sur plusieurs schĂ©mas.
  • DĂ©veloppeur rĂ©actif en cas de bug.

Inconvénients

  • SpĂ©cifique Postgres.
  • Encore en version bĂ©ta : nombreux bugs encore prĂ©sents (avec possibilitĂ© de les contourner).
  • Si l’on ne veut pas toucher Ă  la base de production, il faut passer par une base intermĂ©diaire temporaire pour effectuer le processus d’anonymisation, ce qui oblige Ă  restaurer la base Ă  anonymiser dans cette base temporaire.
  • Le temps de traitement peut ĂȘtre trĂšs long si la base Ă  anonymiser est volumineuse. Notamment lorsque l’on utilise la gĂ©nĂ©ration alĂ©atoire pour les noms, prĂ©noms, villes, 
 : une requĂȘte select est lancĂ©e dans une table Ă  chaque ligne et chaque colonne ayant ce type de rĂšgle. Une astuce est proposĂ©e plus bas pour accĂ©lĂ©rer le traitement.
  • Les valeurs de sĂ©quences prĂ©sentes dans la base source ne sont pas reprises dans le dump anonymisĂ© gĂ©nĂ©rĂ©. Ce bug est en cours de rĂ©solution.

ProblĂšmes gĂ©nĂ©riques Ă  l’anonymisation

  • DifficultĂ© Ă  garder la cohĂ©rence des donnĂ©es : anonymiser certaines donnĂ©es peut potentiellement entraĂźner des erreurs techniques ou fonctionnelles dans l’application :
    • Date de naissance incohĂ©rente avec un numĂ©ro de sĂ©curitĂ© sociale
    • NumĂ©ro de client anonymisĂ© inexistant chez le fournisseur d’un web-service
  • Pertinence de l’anonymisation d’une base de production : pour la recette ou le dĂ©veloppement, ne faut-il pas plutĂŽt se contenter d’une base de donnĂ©es avec quelques donnĂ©es fictives ?
  • Faire la distinction entre anonymisation et pseudonymisation. Il faut toujours se poser la question : “Est-il encore possible d’identifier la personne Ă  partir des donnĂ©es que je suis en train d’anonymiser ?”

Astuces

Comme vu prĂ©cĂ©demment, le temps de traitement peut ĂȘtre long sur les bases volumineuses Ă  cause des requĂȘtes faites sur les tables de rĂ©fĂ©rences pour gĂ©nĂ©rer des valeurs alĂ©atoires (noms, villes, 
). Pour Ă©viter ces nombreux accĂšs en base et avoir un traitement beaucoup plus rapide, vous pouvez remplacer les appels aux fonctions fake
() par des fonctions personnalisĂ©es s’appuyant sur des tableaux de valeurs. Voici un exemple d’une fonction de gĂ©nĂ©ration alĂ©atoire de villes s’appuyant sur un tableau :

CREATE OR REPLACE FUNCTION anon.ville_personnalisee()
RETURNS TEXT
LANGUAGE plpgsal
AS
$$
BEGIN
RETURN anon.random_in(ARRAY['Paris', 'Londres', 'Washington', 'Tokyo']);
END;
$$;

Conclusion

PostgreSQL Anonymizer, avec ces nombreuses fonctions de masquage et de remplacement, est un outil trĂšs efficace pour gĂ©nĂ©rer des dumps anonymisĂ©s de votre base Postgres, et ainsi, se conformer au RGPD. La principale difficultĂ© va ĂȘtre dans le choix des diffĂ©rentes rĂšgles d’anonymisation afin d’assurer la confidentialitĂ© totale des donnĂ©es (ou du moins, de s’en approcher fortement). Cette rĂ©flexion sur la confidentialitĂ© des donnĂ©es devrait se faire dĂšs la conception de la base (quand c’est possible).

Liens :