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.
Voici le contenu des tables :
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 :
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 :