L’extension doit passer par shared_preload_libraries (et non session_preload_libraries) car les workers de réplication logique ne chargent pas ces derniers : sans ce réglage, les règles de masquage ne s’appliqueraient pas aux données entrantes.
Côté réplica : activer le masquage
-- connexion à l'instance réplica
\c"port=5433 dbname=boutique_replica user=paul"CREATEEXTENSIONIFNOTEXISTSanon;SELECTanon.init();ALTERDATABASEboutique_replicaSETanon.replica_maskingTOon;
Se reconnecter, puis accorder les droits de lecture à Pierre :
Le paramètre anon.replica_masking doit être activé avant la création de la souscription. Il indique à l’extension d’intercepter les données entrantes pour y appliquer les règles de masquage. La reconnexion est nécessaire pour que la session prenne en compte la nouvelle valeur du paramètre.
Pierre dispose ainsi d’un accès en lecture seule aux données anonymisées, sans jamais accéder à la base de production.
Côté réplica : créer les tables
Les tables doivent exister avant la souscription :
SECURITYLABELFORanonONCOLUMNcompany.nameIS'MASKED WITH FUNCTION anon.pseudo_company(id)';SECURITYLABELFORanonONCOLUMNcompany.vat_idIS'MASKED WITH FUNCTION anon.random_string(10)';SECURITYLABELFORanonONCOLUMNsupplier.contactIS'MASKED WITH VALUE $$CONFIDENTIAL$$';
Les règles de masquage sont déclarées sur le réplica, pas sur la source. C’est le réplica qui applique l’anonymisation au moment de la réception des données.
La pseudonymisation de company.name garantit que Pierre verra toujours le même nom fictif pour une même société, ce qui lui permet de faire des jointures cohérentes entre company et supplier.
SECURITYLABELFORanonONCOLUMNsupplier.phoneIS'MASKED WITH FUNCTION anon.partial(phone,0,$$XX-XXX-XXX$$,0)';
anon.start_replica_masking() installe le trigger logique qui intercepte les données entrantes.
Le mot de passe dans la chaîne de connexion peut être remplacé par une entrée dans le fichier .pgpass de l’utilisateur système qui exécute l’instance réplica.
id | name | vat_id
-----+-----------------+------------
952 | Wilkinson LLC | IKL88GJVT4
194 | Johnson PLC | VOOJ6UKR6H
346 | Young-Carpenter | DUR78F15VD
Contrairement au masquage dynamique, tous les utilisateurs voient les données anonymisées sur le réplica, car les données originales n’y sont jamais stockées.
\cboutiquepaulSELECT*FROMcompany;
id | name | vat_id
-----+---------------------+----------------
952 | Shadrach | FR62684255667
194 | Johnny's Shoe Store | CHE670945644
346 | Capitol Records | GB663829617823
Attention aux clés primaires
La réplication logique identifie les lignes par leur clé primaire
Ne pas masquer les colonnes de la clé primaire !
Sinon les UPDATE et DELETE ne se propagent plus
Les clés primaires techniques (séquences, UUID) ne sont généralement pas des données personnelles et n’ont pas besoin d’être masquées.
Exercices
Pour rappel, sauf précision contraire, toutes les commandes sont à exécuter en tant que paul (le propriétaire).
E501 - Créer une nouvelle instance
Vérifier que wal_level = logical est actif sur l’instance source.
Créer une seconde instance PostgreSQL sur le port 5433.
Créer la base boutique_replica sur cette instance.
Installer et initialiser l’extension anon dans boutique_replica.
E502 - Configurer le réplica anonymisé
Sur l’instance source, créer le rôle anon_replicator et la publication pour les tables company et supplier.
Sur le réplica, recréer le schéma des tables company et supplier.
Déclarer les règles de masquage sur le réplica (reprendre celles du masquage dynamique).
Activer le replica masking, lancer anon.start_replica_masking() et créer la souscription.
E503 - Vérifier la réplication anonymisée
Sur le réplica, vérifier que les données de company et supplier sont anonymisées.
Sur la source, insérer une nouvelle société et un nouveau fournisseur.
Sur le réplica, vérifier que les nouvelles données apparaissent et sont anonymisées.
Solutions
S501
-- Sur l'instance source
SHOWwal_level;
wal_level
-----------
logical
Si la valeur est replica, modifier postgresql.conf et redémarrer.
\c"port=5433 dbname=boutique_replica user=paul"CREATETABLEcompany(idSERIALPRIMARYKEY,nameTEXT,vat_idTEXTUNIQUE);CREATETABLEsupplier(idSERIALPRIMARYKEY,fk_company_idINTREFERENCEScompany(id),contactTEXT,phoneTEXT,job_titleTEXT);SECURITYLABELFORanonONCOLUMNcompany.nameIS'MASKED WITH FUNCTION anon.pseudo_company(id)';SECURITYLABELFORanonONCOLUMNcompany.vat_idIS'MASKED WITH FUNCTION anon.random_string(10)';SECURITYLABELFORanonONCOLUMNsupplier.contactIS'MASKED WITH VALUE $$CONFIDENTIAL$$';ALTERDATABASEboutique_replicaSETanon.replica_maskingTOon;GRANTUSAGEONSCHEMApublicTOpierre;GRANTSELECTONALLTABLESINSCHEMApublicTOpierre;