# Masquage statique

> Transcription française complète du chapitre « Masquage statique » du manuel DEV42, révision 26.09, pages PDF 489 à 504.

---

Index LLMS : [llms.txt](/fr/llms.txt)

---

<!-- Source: https://public.dalibo.com/exports/formation/manuels/formations/dev42/dev42.handout.html, révision 26.09. -->

# Masquage statique {#dev42-ch12}

- Le plus simple
- Destructif

Le masquage statique est la méthode la plus simple pour cacher des données personnelles. L’idée est simplement de détruire les données originales et de les remplacer par des données artificielles.

## L’histoire {#dev42-ch12-1}

- 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 ? {#dev42-ch12-2}

<img src="/images/dev42/html/ch12-afe1aa0b6279.png" height="600" alt="Illustration du manuel DEV42" />

## Objectifs {#dev42-ch12-3}

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 » {#dev42-ch12-4}

``` sql
\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 {#dev42-ch12-4-1}

Insertion de quelques personnes :

``` sql
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')
;
```

``` sql
SELECT * FROM customer;
```

``` default
 id  | firstname | lastname  |    phone     |   birth    | postcode
-----+-----------+-----------+--------------+------------+----------
 107 | Sarah     | Conor     | 060-911-0911 | 1965-10-10 | 90016
 258 | Luke      | Skywalker |              | 1951-09-25 | 90120
 341 | Don       | Draper    | 347-515-3423 | 1926-06-01 | 04520
(3 lignes)
```

## Table « payout » {#dev42-ch12-5}

Les ventes sont suivies dans cette simple table :

``` sql
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 {#dev42-ch12-5-1}

Quelques commandes :

``` sql
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')
;
```

## Déclarer et appliquer les règles de masquage {#dev42-ch12-6}

Pour le masquage statique, deux étapes :

1.  Déclaration des règles de masquage (`SECURITY LABEL`);
2.  Application des règles de masquage.

Pour le masquage statique, la déclaration des règles de masquage est à faire avant leur application. Cette déclaration passe par la création de `SECURITY LABEL`.

Le masquage statique est exécuté par les fonctions `anon.anonymize_table()` et `anon.anonymize_column()`

## Déclarer les règles de masquage {#dev42-ch12-7}

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

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

Dans cet exemple, Paul veut masquer le nom de famille et le numéro de téléphone de ses clients.

Pour cela, il utilise les fonctions `dummy_last_name()` et `partial()`.

## Appliquer les règles de manière permanente {#dev42-ch12-8}

Application des règles de masquage :

``` sql
SELECT anon.anonymize_table('customer');
```

Cette fonction ne fait qu’appliquer la règle et doit renvoyer *True*. Ensuite :

``` sql
SELECT id, firstname, lastname, phone
FROM customer;
```

``` default
 id  | firstname | lastname |    phone
-----+-----------+----------+--------------
 107 | Sarah     | Okuneva  | 06X-XXX-XX11
 258 | Luke      | Okuneva  |
 341 | Don       | Boyle    | 34X-XXX-XX23
```

> Cette technique est nommée « masquage statique » car la donnée réelle a été détruite de manière définitive. L’anonymisation dynamique et les exports seront vus plus loin.

## Gestion des SECURITY LABEL {#dev42-ch12-9}

- Obtenir la liste des labels :

``` sql
SELECT pg_class.relname AS table, pg_seclabel.label
FROM pg_seclabel INNER JOIN pg_class ON pg_seclabel.objoid = pg_class.oid
WHERE provider = 'anon';
```

- Suppression d’un label :

``` sql
SECURITY LABEL FOR anon ON COLUMN <table>.<colonne> IS NULL;
```

Les règles de masquage passent systématiquement par des `SECURITY LABEL` définis pour le schéma `anon`.

En cas d’erreur lors de la déclaration d’une fonction de masquage, il n’y a pas d’autre solution que de supprimer et de recréer le label.

## Exercices {#dev42-ch12-10}

### E101 - Masquer les prénoms des clients {#dev42-ch12-10-1}

- Déclarer une nouvelle règle de masquage et relancer l’anonymisation statique.

### E102 - Masquer les 3 derniers chiffres du code postal {#dev42-ch12-10-2}

Paul réalise que le code postal est un bon indice sur l’endroit où vivent ses clients. Cependant, il voudrait pouvoir faire des statistiques par département.

- Créer une règle de masquage pour remplacer les 3 derniers chiffres du code postal par ‘x’.

### E103 - Compter le nombre de clients dans chaque département. {#dev42-ch12-10-3}

- Agréger les clients selon le code postal anonymisé.

### E104 - Ne garder que l’année dans les dates de naissance {#dev42-ch12-10-4}

Paul veut des statistiques selon l’âge. Mais il veut aussi masquer les vraies dates de naissance de ses clients.

- Remplacer toutes les dates de naissance par le 1er janvier, en conservant l’année réelle. \\
- Utiliser la fonction [date_trunc](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TABLE).

### E105 - Identifier un client particulier {#dev42-ch12-10-5}

Même si un client est correctement anonymisé, il est possible d’isoler un individu grâce à des données d’autres tables. Par exemple, il est possible d’identifier le meilleur client de Paul avec une telle requête :

``` sql
WITH best_client AS (
    SELECT SUM(amount), fk_customer_id
    FROM payout
    GROUP BY fk_customer_id
    ORDER BY 1 DESC
    LIMIT 1
)
SELECT c.*
FROM customer c
JOIN best_client b ON (c.id = b.fk_customer_id) ;
```

``` default
 id  | firstname | lastname |    phone     |   birth    | postcode
-----+-----------+----------+--------------+------------+----------
 341 | Don       | Boyle    | 34X-XXX-XX23 | 1926-06-01 | 04520
```

Ce processus est appelé « singularisation » (*[singling out](https://www.pnas.org/content/117/15/8344)*) d’une personne.

Il faut donc aller plus loin dans l’anonymisation, en supprimant le lien entre une personne et sa société. Dans la table des commandes `order`, ce lien est matérialisé par une clé étrangère sur le champ `fk_company_id`. Mais nous ne pouvons supprimer des valeurs de cette colonne ou y insérer de faux identifiants, car cela briserait la contrainte de clé étrangère.

Comment séparer les clients de leurs paiements tout en respectant l’intégrité des données ?

- Trouver une fonction qui mélange les valeurs de `fk_company_id` dans la table `payout`.\\
- Consulter la section *[shuffling](https://postgresql-anonymizer.readthedocs.io/en/stable/static_masking/#shuffling)* de la [documentation](https://postgresql-anonymizer.readthedocs.io/en/stable/).

## Solutions {#dev42-ch12-11}

### S101 {#dev42-ch12-11-1}

- Déclarer une nouvelle règle de masquage et relancer l’anonymisation statique.

``` sql
SECURITY LABEL FOR anon ON COLUMN customer.firstname
IS 'MASKED WITH FUNCTION anon.dummy_first_name()' ;

SELECT anon.anonymize_table('customer') ;
```

La table anonymisée devient :

``` sql
SELECT id, firstname, lastname
FROM customer ;
```

``` default
 id  | firstname | lastname
-----+-----------+----------
 107 | Hans       | Barton
 258 | Jacqueline | Dare
 341 | Sibyl      | Runte
```

### S102 {#dev42-ch12-11-2}

- Créer une règle de masquage pour remplacer les 3 derniers chiffres du code postal par ‘x’.

``` sql
SECURITY LABEL FOR anon ON COLUMN customer.postcode
IS 'MASKED WITH FUNCTION anon.partial(postcode,2,$$xxx$$,0)' ;

SELECT anon.anonymize_table('customer') ;
```

Le code postal anonymisé devient :

``` sql
SELECT id, firstname, lastname, postcode
FROM customer ;
```

``` default
 id  | firstname | lastname | postcode
-----+-----------+----------+----------
 107 | Curt      | O'Hara   | 90xxx
 258 | Agusta    | Towne    | 90xxx
 341 | Sid       | Hane     | 04xxx
```

Noter que les noms ont encore changé après application de `anon.anonymize_table()`.

### S103 {#dev42-ch12-11-3}

- Agréger les clients selon le code postal anonymisé.

``` sql
SELECT postcode, COUNT(id)
FROM customer
GROUP BY postcode;
```

``` default
 postcode | count
----------+-------
 90xxx    |     2
 04xxx    |     1
```

### S104 {#dev42-ch12-11-4}

- Remplacer toutes les dates de naissance par le 1er janvier, en conservant l’année réelle. \\
- Utiliser la fonction [date_trunc](https://www.postgresql.org/docs/current/functions-datetime.html#FUNCTIONS-DATETIME-TABLE).

``` sql
SECURITY LABEL FOR anon ON FUNCTION pg_catalog.date_trunc(text,interval) IS 'TRUSTED';

SECURITY LABEL FOR anon ON COLUMN customer.birth IS
$$ MASKED WITH FUNCTION pg_catalog.date_trunc('year',birth) $$;

SELECT anon.anonymize_table('customer');
```

Les dates de naissance anonymisées deviennent :

``` sql
SELECT id, firstname, lastname, birth
FROM customer ;
```

``` default
 id  | firstname | lastname |   birth
-----+-----------+----------+------------
 107 | Pinkie    | Sporer   | 1965-01-01
 258 | Zebulon   | Gerlach  | 1951-01-01
 341 | Erna      | Emmerich | 1926-01-01
```

### S105 {#dev42-ch12-11-5}

- Trouver une fonction qui mélange les valeurs de `fk_company_id` dans la table `payout`.\\
- Consulter la section shuffling de la documentation.

Pour mélanger les valeurs de `fk_customer_id` :

``` sql
SELECT anon.shuffle_column('payout','fk_customer_id','id') ;
```

Si l’on essaie à nouveau d’identifier le meilleur client :

``` sql
WITH best_client AS (
    SELECT SUM(amount), fk_customer_id
    FROM payout
    GROUP BY fk_customer_id
    ORDER BY 1 DESC
    LIMIT 1
)
SELECT c.*
FROM customer c
JOIN best_client b ON (c.id = b.fk_customer_id) ;
```

``` default
 id  | firstname | lastname | phone |   birth    | postcode
-----+-----------+----------+-------+------------+----------
 258 | Zebulon   | Gerlach  |       | 1951-01-01 | 90xxx
```

> Noter que le lien entre un client (`customer`) et ses paiements (`payout`) est à présent complètement faux !

Par exemple, si un client A a deux paiements, l’un se retrouvera associé à un client B, et l’autre à un client C. En d’autres termes, cette méthode de mélange respectera la contrainte d’intégrité technique, mais brisera l’intégrité des données. Pour certaines utilisations, ce peut être problématique.

Ici, Pierre ne pourra pas produire de rapport avec les données mélangées.
## Navigation

Pour revenir au chapitre précédent, voir [le chapitre précédent](/fr/dev42/11-prerequis/#dev42-ch11).
Pour poursuivre la lecture, voir [le chapitre suivant](/fr/dev42/13-masquage-dynamique/#dev42-ch13).
