1

PostgreSQL Anonymizer

twitterlinkedinmail

Dans un contexte où la sécurité est un enjeu majeur, l’anonymisation des données est un moyen supplémentaire d’assurer la protection de celles-ci.
Développée par Dalibo, PostgreSQL anonymizer est une extension qui permet de masquer ou de remplacer les données sensibles d’une base de données postgres.

L’extension a une approche déclarative de l’anonymisation : on peut déclarer les règles de masquage directement dans la définition des tables en utilisant le langage de création DDL habituel.
Cet article a pour but de faire une démonstration de l’installation et le l’utilisation de cette extension avec des cas d’exemples créés pour l’occasion.

L’installation de l’extension sur ubuntu étant un peu particulière, pour ceux souhaitant l’installer sur d’autres systèmes, je vous mets ici le lien vers une documentation permettant de le faire sans encombres.

Etape 1 : Installer un Postgresql 13 :

Pour commencer, on installe une instance PostgreSQL 13 sur notre machine ubuntu

root@sarah:~# sudo apt-get -y install postgresql-13
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
libfreetype6 postgresql-client-10 postgresql-client-12
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
postgresql-plpython3-13
The following packages will be upgraded:
postgresql-13 postgresql-plpython3-13
2 upgraded, 0 newly installed, 0 to remove and 23 not upgraded.
Need to get 15.0 MB of archives.
After this operation, 24.6 kB of additional disk space will be used.
Get:1 http://apt.postgresql.org/pub/repos/apt bionic-pgdg/main amd64 postgresql-                                                                                                                                                             plpython3-13 amd64 13.7-1.pgdg18.04+1 [105 kB]
Get:2 http://apt.postgresql.org/pub/repos/apt bionic-pgdg/main amd64 postgresql-                                                                                                                                                             13 amd64 13.7-1.pgdg18.04+1 [14.9 MB]
Fetched 15.0 MB in 1s (17.2 MB/s)
Preconfiguring packages ...
(Reading database ... 38668 files and directories currently installed.)
Preparing to unpack .../postgresql-plpython3-13_13.7-1.pgdg18.04+1_amd64.deb ...
Unpacking postgresql-plpython3-13 (13.7-1.pgdg18.04+1) over (13.6-1.pgdg18.04+1)                                                                                                                                                              ...
Preparing to unpack .../postgresql-13_13.7-1.pgdg18.04+1_amd64.deb ...
Unpacking postgresql-13 (13.7-1.pgdg18.04+1) over (13.6-1.pgdg18.04+1) ...
Setting up postgresql-13 (13.7-1.pgdg18.04+1) ...
Setting up postgresql-plpython3-13 (13.7-1.pgdg18.04+1) ...
Processing triggers for postgresql-common (238.pgdg18.04+1) ...
Building PostgreSQL dictionaries from installed myspell/hunspell packages...
Removing obsolete dictionary files:

Une fois l’instance installée, on doit télécharger les outils de développement de postgreSQL afin d’être en mesure de compiler.

Etape 2 : Télécharger les outils de dev de postgresql

root@sarah:~# sudo apt-get install postgresql-server-dev-13
Reading package lists... Done
Building dependency tree
Reading state information... Done
...
Setting up postgresql-server-dev-13 (13.7-1.pgdg18.04+1) ...
Processing triggers for man-db (2.8.3-2ubuntu0.1) ...
Processing triggers for mime-support (3.60ubuntu1) ...
Processing triggers for ureadahead (0.100.0-21) ...
Processing triggers for install-info (6.5.0.dfsg.1-2) ...
Processing triggers for libc-bin (2.27-3ubuntu1.5) ...
Processing triggers for systemd (237-3ubuntu10.53) 

Ces outils seront essentiels pour installer l’extension et pouvoir la faire fonctionner correctement. Il n’existe pas à ce jour de version toute prête pour Ubuntu.

Etape 3 : Télécharger les sources depuis le repo git

root@sarah:~# git clone https://gitlab.com/dalibo/postgresql_anonymizer/-/tree/master
Cloning into 'master'...
fatal: unable to update url base from redirection:
asked for: https://gitlab.com/dalibo/postgresql_anonymizer/-/tree/master/info/refs?service=git-upload-pack
redirect: https://gitlab.com/dalibo/postgresql_anonymizer/-/tree/master
root@sarah:~# git clone https://gitlab.com/dalibo/postgresql_anonymizer.git
Cloning into 'postgresql_anonymizer'...
remote: Enumerating objects: 5145, done.
remote: Counting objects: 100% (487/487), done.
remote: Compressing objects: 100% (271/271), done.
remote: Total 5145 (delta 327), reused 277 (delta 216), pack-reused 4658
Receiving objects: 100% (5145/5145), 25.71 MiB | 19.23 MiB/s, done.
Resolving deltas: 100% (3304/3304), done.

Etape 4 : Compiler les sources

Une fois nos ressources téléchargées, il faut les compiler pour être en mesure de les utiliser.

root@sarah:~/postgresql_anonymizer# make extension PG_CONFIG=/usr/lib/postgresql/13/bin/pg_config
mkdir -p anon
cp anon.sql anon/anon--1.1.0.sql
cp data/*.csv anon/
cp python/populate.py anon/
root@sarah:~/postgresql_anonymizer# sudo make install PG_CONFIG=/usr/lib/postgresql/13/bin/pg_config
cp anon.sql anon/anon--1.1.0.sql
cp data/*.csv anon/
cp python/populate.py anon/
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -Wno-unused-variable -I. -I./ -I/usr/include/postgresql/13/server -I/usr/include/postgresql/internal  -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2   -c -o anon.o anon.c
gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3 -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -g -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fno-omit-frame-pointer -fPIC -Wno-unused-variable anon.o -L/usr/lib/x86_64-linux-gnu -Wl,-Bsymbolic-functions -Wl,-z,relro -Wl,-z,now -L/usr/lib/llvm-6.0/lib  -Wl,--as-needed  -shared -o anon.so
/usr/bin/clang-6.0 -Wno-ignored-attributes -fno-strict-aliasing -fwrapv -Wno-unused-command-line-argument -O2  -I. -I./ -I/usr/include/postgresql/13/server -I/usr/include/postgresql/internal  -Wdate-time -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -I/usr/include/libxml2  -flto=thin -emit-llvm -c -o anon.bc anon.c
/bin/mkdir -p '/usr/share/postgresql/13/extension'
/bin/mkdir -p '/usr/share/postgresql/13/extension/anon'
/bin/mkdir -p '/usr/lib/postgresql/13/lib'
install -d /usr/lib/postgresql/13/bin
install -m 0755 bin/pg_dump_anon.sh /usr/lib/postgresql/13/bin
/usr/bin/install -c -m 644 .//anon.control '/usr/share/postgresql/13/extension/'
/usr/bin/install -c -m 644 .//anon/*  '/usr/share/postgresql/13/extension/anon/'
/usr/bin/install -c -m 755  anon.so '/usr/lib/postgresql/13/lib/'
/bin/mkdir -p '/usr/lib/postgresql/13/lib/bitcode/anon'
/bin/mkdir -p '/usr/lib/postgresql/13/lib/bitcode'/anon/
/usr/bin/install -c -m 644 anon.bc '/usr/lib/postgresql/13/lib/bitcode'/anon/./
cd '/usr/lib/postgresql/13/lib/bitcode' && /usr/lib/llvm-6.0/bin/llvm-lto -thinlto -thinlto-action=thinlink -o anon.index.bc anon/anon.bc

A présent l’extension devrait être utilisable si tout s’est bien passé. Il ne nous reste plus qu’à mettre en place notre environnement de test.

Etape 5 : On se connecte, on crée une database client, avec une table client et on insère notre premier client dedans

postgres@sarah:~$ psql
psql (13.7 (Ubuntu 13.7-1.pgdg18.04+1))
Type "help" for help.
postgres=# create database client;
CREATE DATABASE
postgres=# \connect client
You are now connected to database "client" as user "postgres".
client=# create table pizza_client (id integer, nom varchar(30), prenom varchar(30), numero varchar(20));
CREATE TABLE
client=# insert into pizza_client values (1, 'CONNOR', 'Sarah', '0600000000');
INSERT 0 1
client=# select * from pizza_client;
id | nom | prenom | numero
----+--------+--------+------------
1 | CONNOR | Sarah | 0600000000
(1 row)

Etape 6 : Créer et charger l’extension dans notre base de données

client=# ALTER DATABASE client SET session_preload_libraries = 'anon';
ALTER DATABASE
client=# CREATE EXTENSION anon CASCADE;
CREATE EXTENSION
client=# SELECT anon.init();
NOTICE:  The anon extension is already initialized.
init
------
t
(1 row)

Voici notre extension installée et prête à fonctionner.
Il y a trois grandes fonctionnalités à connaitre dans PostgreSQL anonymizer : le masquage par destruction de données, le masquage dynamique et le dump sécurisé.
Les trois fonctionnent avec les mêmes fonctions d’anonymisation, que vous pouvez retrouver listées ici.

Exemple 1 : Masquage par destruction de données

Le premier exemple que nous allons aborder est le masquage par destruction de données. C’est un masquage définitif et pour tout les utilisateurs. Il peut servir notamment quand vous faites des recopies de données sensibles de prod vers la préprod par exemple, afin d’anonymiser les données que vous recopiez.
Pour se faire, il faut déclarer pour chaque champ de la table qu’on souhaite anonymiser une règle que l’extension suivra pour modifier les données contenues dans les colonnes.
Si nous reprenons notre exemple avec notre table pizza_client. On souhaiterait masquer les informations sensibles de nos données : les noms et prénoms de nos clients, et leurs numéros de téléphone.
Pour chacune des colonnes qu’on veut anonymiser on choisit une fonction correspondant à la modification qu’on veut appliquer.

client=# SECURITY LABEL FOR anon ON COLUMN pizza_client.nom IS  'MASKED WITH FUNCTION anon.fake_last_name()';
SECURITY LABEL
client=# SECURITY LABEL FOR anon ON COLUMN pizza_client.prenom IS 'MASKED WITH FUNCTION anon.fake_first_name()';
SECURITY LABEL
client=# SECURITY LABEL FOR anon ON COLUMN pizza_client.numero IS 'MASKED WITH FUNCTION anon.random_phone()';
SECURITY LABEL

Et une fois qu’on a définit toutes les règles de masquage que l’on désire, on a plus qu’à utiliser la fonction anon.anonymize_database() pour les appliquer.

client=# SELECT anon.anonymize_database();
anonymize_database
--------------------
t
(1 row)
client=# select * from pizza_client;
id | nom | prenom | numero
----+----------+--------+------------
1 | Prosacco | Karl | 0466543869
(1 row)

On peut constater que les données d’origine ont été modifiées au passage de la fonction. La modification est définitive, et on peut parfaitement envisager d’utiliser ces données modifiées comme jeu de test anonymisé.

Exemple 2 : Le masquage dynamique

Le principe du masquage dynamique est de définir un ou plusieurs utilisateurs qui n’auraient pas le droit de voir les données comme elles sont dans la base de données. Cette méthode ne modifie rien, c’est simplement du masquage appliqué pour un profil en particulier.
Recréons notre exemple précédent pour tester ce principe de masquage dynamique

On commence par créer notre table :

client=# drop table pizza_client;
DROP TABLE
client=# create table pizza_client (id integer, nom varchar(30), prenom varchar(30), numero varchar(20));
CREATE TABLE
client=# insert into pizza_client values (1, 'CONNOR', 'Sarah', '0600000000');
INSERT 0 1

On applique ensuite la fonction permettant de démarrer le masquage dynamique :

client=# SELECT anon.start_dynamic_masking();
start_dynamic_masking
-----------------------
t
(1 row)

Ensuite, on créé un nouveau rôle, le rôle pour qui les données seront masquées et on lui applique le label masqué :

client=# CREATE ROLE capdata LOGIN;
CREATE ROLE
client=# SECURITY LABEL FOR anon ON ROLE capdata IS 'MASKED';
SECURITY LABEL

Et on applique ensuite les label de masquage sur les données qu’on autorise pas l’utilisateur à consulter :

client=# SECURITY LABEL FOR anon ON COLUMN pizza_client.nom IS  'MASKED WITH FUNCTION anon.fake_last_name()';
SECURITY LABEL
client=# SECURITY LABEL FOR anon ON COLUMN pizza_client.numero IS 'MASKED WITH FUNCTION anon.partial(numero,2,$$******$$,2)';
SECURITY LABEL

A présent il ne nous reste plus qu’à nous connecter avec l’utilisateur créé pour le test, et à faire un select sur notre table pour voir si la fonction de masquage dynamique a fonctionné.

client=# \c - capdata
You are now connected to database "client" as user "capdata".
client=> select * from pizza_client
client-> ;
id | nom | prenom |   numero
----+-----+--------+------------
1 | Rau | Sarah  | 06******00
(1 row)

Exemple 3 : Le Dump anonymisé

Cette option est la dernière proposée par l’extension.
Elle fonctionne exactement pareil que le pg_dump, mais elle créé des dumps anonymisés selon les règles définies dans les différentes tables comme vu précédemment.
Cependant, cette partie de l’extension est très critiquable. Elle fonctionne correctement mais possède plusieurs inconvénients :
– On ne peut exporter qu’en format plain, les autres formats ne fonctionnent pas.
– Si des ordres ddl ou dml sont passés pendant que le dump est en cours, alors le fichier de sauvegarde sera inconsistant.

Pour faire fonctionner le dump anonymisé, il suffit d’utiliser le script pg_dump_anon.sh. Il s’utilise de la même façon que le pg_dump, possédant les mêmes options :

pg_dump_anon.sh --help

Usage: pg_dump_anon.sh [OPTION]... [DBNAME]

General options:
-f, --file=FILENAME output file
--help display this message

Options controlling the output content:
-n, --schema=PATTERN dump the specified schema(s) only
-N, --exclude-schema=PATTERN do NOT dump the specified schema(s)
-t, --table=PATTERN dump the specified table(s) only
-T, --exclude-table=PATTERN do NOT dump the specified table(s)
--exclude-table-data=PATTERN do NOT dump data for the specified table(s)

Connection options:
-d, --dbname=DBNAME database to dump
-h, --host=HOSTNAME database server host or socket directory
-p, --port=PORT database server port number
-U, --username=NAME connect as specified database user
-w, --no-password never prompt for password
-W, --password force password prompt (should happen automatically)

If no database name is supplied, then the PGDATABASE environment
variable value is used.

Conclusion :

Dans le trousseau de sécurité de PostgreSQL, l’anonymisation des données est un must-have pour les utilisateurs manipulant des données sensibles dans des systèmes protégés.

PostgreSQL Anonymizer est simple d’utilisation et pratique. Il n’entraine pas de grosses chutes de performances et il est utilisable de différentes manières comme vu dans l’article.

A ma connaissance il n’existe pas d’autres extension sur le marché capable de réaliser le même travail que PostgreSQL anonymizer.

Continuez votre lecture sur le blog :

twitterlinkedinmail

Sarah FAVEERE

Un commentaire

  1. Hello,
    très bon article !
    Il existe aussi “pgcrypto” qui fait de l’anonymisation unidirectionnel (crypte la donnée selon un algo) ou bidirectionnel (crypte la donnée selon un algo avec une clé de cryptage et decrypte en utilisant cette même clé).
    Mais il possède moins de fonctions et procédures attachées pour gérer de façon fine chaque champs. De plus il ne permet pas cette gestion dynamique en passant par un rôle.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.