Masquage de données & postgresql_anonymizer

Module Y5

Dalibo SCOP

24.12

18 décembre 2024

Sur ce document

Formation Module Y5
Titre Masquage de données & postgresql_anonymizer
Révision 24.12
PDF https://dali.bo/y5_pdf
EPUB https://dali.bo/y5_epub
HTML https://dali.bo/y5_html
Slides https://dali.bo/y5_slides

Licence Creative Commons CC-BY-NC-SA

Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :

  • Paternité
  • Pas d’utilisation commerciale
  • Partage des conditions initiales à l’identique

Marques déposées

PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.

Versions de PostgreSQL couvertes

Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 13 à 17.

Masquage de données & postgresql_anonymizer

postgresql_anonymizer

Cas d’usage

  • Paul : le propriétaire
  • Pierre : data scientist
  • Jack : employé chargé des fournisseurs

Objectifs

Nous allons découvrir :

  • comment écrire des règles de masquage
  • la différence entre masquage dynamique et masquage statique
  • comment implémenter un masquage avancé

PostgreSQL Anonymizer

Principe

Principe :

  • Extension
  • Déclaratif (DDL)

Masquages

Principe :

  • statique
  • dynamique
  • sauvegardes anonymisées
  • « généralisation »

Pré-requis

Cet exemple nécessite :

  • une instance PostgreSQL ;
  • l’extension PostgreSQL Anonymizer (anon)
    • installée, initialisée par un super-utilisateur
  • une base boutique
    • dont le propriétaire est paul, super-utilisateur
  • les rôles pierre et jack
    • avec droits de connexion à boutique

Base d’exemple

pierre, paul, jack et la base boutique :

CREATE ROLE paul LOGIN SUPERUSER;
CREATE ROLE pierre LOGIN;
CREATE ROLE jack LOGIN;

-- Define a password for each user with:
-- \password paul  or  ALTER ROLE paul PASSWORD 'change-me';

CREATE DATABASE boutique OWNER paul;
ALTER DATABASE boutique
  SET session_preload_libraries = 'anon';

Masquage statique avec postgresql_anonymizer

  • Le plus simple
  • Destructif

L’histoire

  • Au fil des années, Paul a accumulé des données sur ses clients et leurs achats dans une base de données très simple.

  • Il a récemment installé un nouveau logiciel de ventes, et l’ancienne base est obsolète.

  • Avant de l’archiver, il voudrait en supprimer toutes les données personnelles.

Comment ça marche

Objectifs

Nous allons voir :

  • comment écrire des règles de masquage simples
  • les intérêts et limitations du masquage statique
  • le concept de « singularisation » d’une personne (singling out)

Table « customer »

\c boutique paul
DROP TABLE IF EXISTS customer CASCADE;
DROP TABLE IF EXISTS payout CASCADE;

CREATE TABLE customer (
    id SERIAL PRIMARY KEY,
    firstname TEXT,
    lastname TEXT,
    phone TEXT,
    birth DATE,
    postcode TEXT
);

Quelques clients

Insertion de quelques personnes :

INSERT INTO customer
VALUES
(107,'Sarah','Conor','060-911-0911', '1965-10-10', '90016'),
(258,'Luke', 'Skywalker', NULL, '1951-09-25', '90120'),
(341,'Don', 'Draper','347-515-3423', '1926-06-01', '04520')
;

Table « payout »

Les ventes sont suivies dans cette simple table :

CREATE TABLE payout (
    id SERIAL PRIMARY KEY,
    fk_customer_id INT REFERENCES customer(id),
    order_date DATE,
    payment_date DATE,
    amount INT
);

Quelques données

Quelques commandes :

INSERT INTO payout
VALUES
(1,107,'2021-10-01','2021-10-01', '7'),
(2,258,'2021-10-02','2021-10-03', '20'),
(3,341,'2021-10-02','2021-10-02', '543'),
(4,258,'2021-10-05','2021-10-05', '12'),
(5,258,'2021-10-06','2021-10-06', '92')
;

Activer l’extension

CREATE EXTENSION IF NOT EXISTS anon CASCADE ;
SELECT anon.init() ;
SELECT setseed(0) ;

Déclarer les règles de masquage

SECURITY LABEL FOR anon ON COLUMN customer.lastname
IS 'MASKED WITH FUNCTION anon.fake_last_name()';

SECURITY LABEL FOR anon ON COLUMN customer.phone
IS 'MASKED WITH FUNCTION anon.partial(phone,2,$$X-XXX-XX$$,2)';

Appliquer les règles de manière permanente

SELECT anon.anonymize_table('customer');

Exercices

E101 - Masquer les prénoms des clients

E102 - Masquer les 3 derniers chiffres du code postal

E103 - Compter le nombre de clients dans chaque département.

E104 - Ne garder que l’année dans les dates de naissance

E105 - Identifier un client particulier

Solutions

Masquage dynamique avec postgresql_anonymizer

Principe du masquage dynamique

  • Masquer les données personnelles à certains utilisateurs
    • mais pas tous

L’histoire

Paul a 2 employés :

  • Jack s’occupe du nouveau logiciel de ventes.
    • il a besoin d’accéder aux vraies données
    • pour le RGPD c’est un « processeur de données »
  • Pierre est un analyste qui exécute des requêtes statistiques
    • il ne doit pas avoir accès aux données personnelles

Comment ça marche

Objectifs de la section

Nous allons voir :

  • comment écrire des règles de masquage simple
  • les avantages et limitations du masquage dynamique
  • le concept de « recoupement » d’une personne (linkability)

Table « company »

DROP TABLE IF EXISTS supplier CASCADE;
DROP TABLE IF EXISTS company CASCADE;

CREATE TABLE company (
    id SERIAL PRIMARY KEY,
    name TEXT,
    vat_id TEXT UNIQUE
);

Quelques données

INSERT INTO company
VALUES
(952,'Shadrach', 'FR62684255667'),
(194,E'Johnny\'s Shoe Store','CHE670945644'),
(346,'Capitol Records','GB663829617823')
;

Table « supplier »

CREATE TABLE supplier (
    id SERIAL PRIMARY KEY,
    fk_company_id INT REFERENCES company(id),
    contact TEXT,
    phone TEXT,
    job_title TEXT
) ;

Quelques données

INSERT INTO supplier
VALUES
(299,194,'Johnny Ryall','597-500-569','CEO'),
(157,346,'George Clinton', '131-002-530','Sales manager')
;

Activer l’extension

CREATE EXTENSION IF NOT EXISTS anon CASCADE ;
SELECT anon.init() ;
SELECT setseed(0) ;

Activer le masquage dynamique

SELECT anon.start_dynamic_masking();

Rôle masqué

SECURITY LABEL FOR anon ON ROLE pierre IS 'MASKED' ;
GRANT ALL ON SCHEMA public TO jack ;
GRANT ALL ON ALL TABLES IN SCHEMA public TO jack ;
GRANT SELECT ON supplier TO pierre ;

Masquer le nom des fournisseurs

En tant que Paul, une règle de masquage se définit ainsi :

\c boutique paul
SECURITY LABEL FOR anon ON COLUMN supplier.contact
IS 'MASKED WITH VALUE $$CONFIDENTIAL$$';

Exercices

E201 - Deviner qui est le PDG de « Johnny’s Shoe Store »

E202 - Anonymiser les sociétés

E203 - Pseudonymiser le nom des sociétés

Solutions

Sauvegardes anonymes avec postgresql_anonymizer

L’histoire

  • Paul a un site web qui dispose d’une section commentaires où les utilisateurs peuvent partager leurs points de vue.

  • Paul a engagé un prestataire pour développer le nouveau design de son site web.

  • Le prestataire lui demande un export de la base de données.

  • Paul veut « nettoyer » le dump et y retirer toute information personnelle qui pourrait figurer dans la section commentaire.

Comment ça marche ?

Objectifs

  • Extraire les données anonymisées de la base de données
  • Écrire une fonction de masquage personnalisée pour gérer une colonne de type JSON

Table « website_comment »

\c boutique paul
DROP TABLE IF EXISTS website_comment CASCADE ;

CREATE TABLE website_comment (
  id SERIAL PRIMARY KEY,
  message jsonb
) ;

Quelques données

curl -kLs https://dali.bo/website_comment -o /tmp/website_comment.tsv
head /tmp/website_comment.tsv
    1   {"meta": {"name": "Lee Perry", "ip_addr": "40.87.29.113"}, "content": "Hello Nasty!"}
    2   {"meta": {"name": "", "email": "biz@bizmarkie.com"}, "content": "Great Shop"}
    3   {"meta": {"name": "Jimmy"}, "content": "Hi! This is me, Jimmy James "}

Activer l’extension

\c boutique paul

CREATE EXTENSION IF NOT EXISTS anon CASCADE;

SELECT anon.init();

SELECT setseed(0);

Masquer une colonne de type JSON

Généralement, les données non structurées sont difficiles à masquer…

SELECT message - ARRAY['content']
FROM website_comment
WHERE id=1 ;

Fonctions de masquage personnalisées

\c boutique paul

CREATE SCHEMA IF NOT EXISTS my_masks;

SECURITY LABEL FOR anon ON SCHEMA my_masks IS 'TRUSTED';
CREATE OR REPLACE FUNCTION my_masks.remove_content(j jsonb)
RETURNS jsonb
AS $func$
  SELECT j - ARRAY['content']
$func$
LANGUAGE sql ;
  • Super-utilisateurs seulement !

Utilisation de la fonction de masquage personnalisée

SECURITY LABEL FOR anon ON COLUMN website_comment.message
IS 'MASKED WITH FUNCTION my_masks.remove_content(message)';

Sauvegarde anonymisée

  • Export
pg_dump_anon.sh -U paul -d boutique --table=website_comment > /tmp/dump.sql
  • Limitations : risque d’inconsistence, format plain

Exercices

E301 - Exporter les données anonymisées dans une nouvelle base de données

E302 - Pseudonymiser les métadonnées du commentaire

Solutions

Généralisation avec postgresql_anonymizer

Principe

  • Flouter les données
  • Par ex : 25 juillet 1989 années 1980

L’histoire

  • Paul a embauché des dizaines de salariés au fil du temps.

  • Il conserve une trace sur la couleur de leurs cheveux, leurs tailles, et leurs conditions médicales.

  • Paul souhaite extraire des statistiques depuis ces détails.

  • Il fournit des vues généralisées à Pierre.

Comment ça marche ?

Objectifs

Nous allons voir :

  • la différence entre le masquage et la généralisation
  • le concept de « k-anonymat »

Table « employee »

DROP TABLE IF EXISTS employee CASCADE;
CREATE TABLE employee (
  id INT PRIMARY KEY,
  full_name  TEXT,
  first_day DATE, last_day DATE,
  height INT,
  hair TEXT, eyes TEXT, size TEXT,
  asthma BOOLEAN,
  CHECK(hair = ANY(ARRAY['bald','blond','dark','red'])),
  CHECK(eyes = ANY(ARRAY['blue','green','brown'])) ,
  CHECK(size = ANY(ARRAY['S','M','L','XL','XXL']))
);
  • Légal ?

Quelques données

curl -Ls https://dali.bo/employee -o /tmp/employee.tsv
\c boutique paul
\COPY employee FROM '/tmp/employee.tsv'

Suppression de données

Pierre peut trouver un lien entre asthme et yeux verts :

\c boutique paul
DROP MATERIALIZED VIEW IF EXISTS v_asthma_eyes ;

CREATE MATERIALIZED VIEW v_asthma_eyes AS
SELECT eyes, asthma
FROM employee ;

Calculer le k-anonymat

  • Les colonnes asthma et eyes sont considérés comme des identifiants indirects.
\c boutique paul
SECURITY LABEL FOR anon ON COLUMN v_asthma_eyes.eyes
IS 'INDIRECT IDENTIFIER';

SECURITY LABEL FOR anon ON COLUMN v_asthma_eyes.asthma
IS 'INDIRECT IDENTIFIER';
SELECT anon.k_anonymity('v_asthma_eyes');

Fonctions d’intervalle et de généralisation

\c boutique paul
DROP MATERIALIZED VIEW IF EXISTS v_staff_per_month ;

CREATE MATERIALIZED VIEW v_staff_per_month AS
SELECT
    anon.generalize_daterange(first_day,'month') AS first_day,
    anon.generalize_daterange(last_day, 'month') AS last_day
FROM employee ;

GRANT SELECT ON v_staff_per_month TO pierre ;

Déclarer les identifiants indirects

Calculer le facteur de k-anonymat de cette vue :

\c boutique paul
SECURITY LABEL FOR anon ON COLUMN v_staff_per_month.first_day
IS 'INDIRECT IDENTIFIER';
SECURITY LABEL FOR anon ON COLUMN v_staff_per_month.last_day
IS 'INDIRECT IDENTIFIER';
SELECT anon.k_anonymity('v_staff_per_month');

Exercices

E401 - Simplifier la vue v_staff_per_month pour en réduire la granularité.

E402 - Progression du personnel au fil des années

E403 - Atteindre le facteur 2-anonymity sur la vue v_staff_per_year

Solutions

Conclusion sur postgresql_anonymizer

Beaucoup de stratégies de masquage

Beaucoup de fonctions de masquage

  • Destruction partielle ou totale
  • Ajout de bruit
  • Randomisation
  • Falsification et falsification avancée
  • Pseudonymisation
  • Hachage générique
  • Masquage personnalisé

RTFM -> Fonctions de masquage

Avantages

  • Règles de masquage en SQL
  • Règles de masquage stockées dans le schéma de la base
  • Pas besoin d’un ETL
  • Fonctionne avec toutes les versions actuelles de PostgreSQL
  • Multiples stratégies, multiples fonctions.

Inconvénients

  • Ne fonctionne pas avec d’autres systèmes de gestion de bases de données (comme le nom l’indique)
  • Peu de retour d’expérience sur de gros volumes (> 10 To)

Pour aller plus loin

D’autres projets qui pourraient vous plaire :

  • pg_sample : Extraire un petit jeu de données d’une base de données volumineuse
  • PostgreSQL Faker : Une extension de falsification avancée basée sur la bibliothèque python Faker.

Contribuez !

C’est un projet libre !

https://labs.dalibo.com/postgresql_anonymizer

Merci de vos retours sur la manière dont vous l’utilisez, comment il répond ou non à vos attentes, etc.

Questions

N’hésitez pas, c’est le moment !