Project: YugabyteDB (App Definition and Development / Database)
File: 1-static_masking.md

Static Masking is the simplest way to hide personal information! This idea is simply to destroy the original data or replace it with an artificial one. Over the years, Paul has collected data about his customers and their purchases in a simple database. He recently installed a brand new sales application and the old database is now obsolete. He wants to save it and he would like to remove all personal information before archiving it. In this section, we will learn: How to write simple masking rules The advantage and limitations of static masking The concept of "Singling Out" a person ``` { .run-postgres parse_query=False } 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 ); ``` Insert a few persons: ``` { .run-postgres } 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') ; ``` ``` { .run-postgres } SELECT * FROM customer; ``` Sales are tracked in a simple table: ``` { .run-postgres parse_query=False } CREATE TABLE payout ( id SERIAL PRIMARY KEY, fkcustomerid INT REFERENCES customer(id), order_date DATE, payment_date DATE, amount INT ); ``` Let\'s add some orders: ``` { .run-postgres } 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') ; ``` ``` { .run-postgres } CREATE EXTENSION IF NOT EXISTS anon CASCADE; SELECT anon.init(); SELECT setseed(0); ``` Paul wants to hide the last name and the phone numbers of his clients. He will use the `fakelastname()` and `partial()` functions for that: ``` { .run-postgres } SECURITY LABEL FOR anon ON COLUMN customer.lastname IS 'MASKED WITH FUNCTION anon.fakelastname()'; SECURITY LABEL FOR anon ON COLUMN customer.phone IS 'MASKED WITH FUNCTION anon.partial(phone,2,$$X-XXX-XX$$,2)'; ``` ``` { .run-postgres } SELECT anon.anonymize_table('customer'); ``` ``` { .run-postgres } SELECT id, firstname, lastname, phone FROM customer; ``` This is called `Static Masking` because the real data has been permanently replaced. We\'ll see later how we can use dynamic anonymization or anonymous exports. Declare a new masking rule and run the static anonymization function again. Paul realizes that the postcode gives a clear indication of where his customers live. However he would like to have statistics based on their \"postcode area\". Add a new masking rule to replace the last 3 digits by 'x'. Aggregate the customers based on their anonymized postcode. Paul wants age-based statistic. But he also wants to hide the real birth date of the customers. Replace all the birth dates by January 1rst, while keeping the real

::: tip HINT: You can use the function ! ::: Even if the \"customer\" is properly anonymized, we can still isolate a given individual based on data stored outside of the table. For instance, we can identify the best client of Paul\'s boutique with a query like this: ``` { .run-postgres } WITH best_client AS ( SELECT SUM(amount), fkcustomerid FROM payout GROUP BY fkcustomerid ORDER BY 1 DESC LIMIT 1 ) SELECT c.* FROM customer c JOIN bestclient b ON (c.id = b.fkcustomer_id) ``` ::: important This is called a person. ::: We need to anonymize even further by removing the link between a person and its company. In the \"order\" table, this link is materialized by a foreign key on the field \"fkcompanyid\". However we can\'t remove values from this column or insert fake identifiers because if would break the foreign key constraint. How can we separate the customers from their payouts while respecting the integrity of the data? Find a function that will shuffle the column \"fkcompanyid\" of the \"payout\" table ::: tip HINT: Check out the section of the ::: ``` { .run-postgres } SECURITY LABEL FOR anon ON COLUMN customer.firstname IS 'MASKED WITH FUNCTION anon.fakefirstname()'; SELECT anon.anonymize_table('customer'); SELECT id, firstname, lastname FROM customer; ``` ``` { .run-postgres } SECURITY LABEL FOR anon ON COLUMN customer.postcode IS 'MASKED WITH FUNCTION anon.partial(postcode,2,$$xxx$$,0)'; SELECT anon.anonymize_table('customer'); SELECT id, firstname, lastname, postcode FROM customer; ``` ``` { .run-postgres } SELECT postcode, COUNT(id) FROM customer GROUP BY postcode; ``` ``` { .run-postgres } SECURITY LABEL FOR anon ON COLUMN customer.birth IS 'MASKED WITH FUNCTION make_date(EXTRACT(YEAR FROM birth)::INT,1,1)'; SELECT anon.anonymize_table('customer'); SELECT id, firstname, lastname, birth FROM customer; ``` Let\'s mix up the values of the fkcustomerid: ``` { .run-postgres } SELECT anon.shufflecolumn('payout','fkcustomer_id','id'); ``` Now let\'s try to single out the best client again : ``` { .run-postgres } WITH best_client AS ( SELECT SUM(amount), fkcustomerid FROM payout GROUP BY fkcustomerid ORDER BY 1 DESC LIMIT 1 ) SELECT c.* FROM customer c JOIN bestclient b ON (c.id = b.fkcustomer_id); ``` WARNING Note that the link between a `customer` and its `payout` is now completely false. For instance, if a customer A had 2 payouts. One of these payout may be linked to a customer B, while the second one is linked to a customer C. In other words, this shuffling method with respect the foreign key constraint (aka the referential integrity) but it will break the data integrity. For some use case, this may be a problem. In this case, Pierre will not be able to produce a BI report with the shuffle data, because the links between the customers and their payments are fake.