F.62. transp_anon#
F.62. transp_anon #
- F.62.1. Определения терминов, используемых в этом проекте
- F.62.2. Анонимизация & Маскирование данных для PostgreSQL
- F.62.3. Конфигурация
- F.62.4. Объявить правила маскировки
- F.62.5. Функции маскирования
- F.62.6. Динамическое маскирование
- F.62.7. Обобщение
- F.62.8. Пользовательские поддельные данные
- F.62.9. Производительность
- F.62.10. Безопасность
- F.62.11. Демонстрации
transp_anon
(прозрачная анонимизация)
является расширением для маскировки или замены персонально идентифицируемой
информации или коммерчески чувствительных данных из базы данных PostgreSQL.
F.62.1. Определения терминов, используемых в этом проекте #
Данные могут быть изменены с помощью нескольких методов:
Удаление или Обнуление просто удаляет данные.
Статическая замена последовательно заменяет данные на общее значение. Например: замена всех значений столбца TEXT на значение “КОНФИДЕНЦИАЛЬНО”.
Вариация — это действие “сдвига” дат и числовых значений. Например, применяя вариацию +/- 10% к столбцу зарплаты, набор данных останется значимым.
Обобщение снижает точность данных, заменяя их диапазоном значений. Вместо того чтобы сказать “Бобу 28 лет”, вы можете сказать “Бобу от 20 до 30 лет”. Это полезно для аналитики, потому что данные остаются верными.
Перемешивание смешивает значения в пределах одних и тех же столбцов. Этот метод может быть обратимым, если алгоритм перемешивания может быть расшифрован.
Рандомизация заменяет конфиденциальные данные случайными, но правдоподобными значениями. Цель состоит в том, чтобы избежать любой идентификации из записи данных, оставаясь при этом пригодной для тестирования, анализа данных и обработки данных.
Частичное перемешивание похоже на статическую замену, но исключает часть данных. Например: номер кредитной карты может быть заменен на “40XX XXXX XXXX XX96”
Пользовательские правила предназначены для изменения данных в соответствии с конкретными потребностями. Например, одновременная рандомизация почтового индекса и названия города при сохранении их согласованности.
Псевдонимизация — это способ защиты личной информации путем ее сокрытия с использованием дополнительной информации. Шифрование и Хеширование — два примера техник псевдонимизации. Однако псевдонимизированные данные все еще связаны с исходными данными.
F.62.2. Анонимизация & Маскирование данных для PostgreSQL #
Проект имеет декларативный подход к анонимизации. Это означает, что вы можете объявить правила маскировки с использованием языка определения данных PostgreSQL (DDL) и указать свою стратегию анонимизации непосредственно в определении таблицы.
Как только правила маскировки определены, вы можете получить доступ к анонимизированным данным, используя: * Динамическая маскировка: Скрыть личную информацию только для замаскированных пользователей
Кроме того, доступны различные Функции маскировки: рандомизация, подделка, частичное перемешивание, перемешивание, шум или ваша собственная пользовательская функция!
Помимо маскировки, также возможно использовать четвертый подход, называемый Обобщение, который идеально подходит для статистики и анализа данных.
F.62.2.1. Пример #
=# SELECT * FROM people; id | firstname | lastname | phone ----+-----------+----------+------------ T1 | Sarah | Conor | 0609110911
Шаг 1 : Активируйте движок динамического маскирования
=# CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; =# SELECT transp_anon.init();
Шаг 2 : Объявите маскированного пользователя и предоставьте ему разрешения на выбор данных
=# CREATE ROLE skynet LOGIN; =# GRANT SELECT ON TABLE people TO skynet; =# SECURITY LABEL FOR transp_anon ON ROLE skynet IS 'MASKED';
Шаг 3 : Объявите правила маскировки
=# SECURITY LABEL FOR transp_anon ON COLUMN people.lastname -# IS 'MASKED WITH FUNCTION transp_anon.fake_last_name()'; =# SECURITY LABEL FOR transp_anon ON COLUMN people.phone -# IS 'MASKED WITH FUNCTION transp_anon.partial(phone,2,$$******$$,2)';
Шаг 4 : Подключитесь от имени маскированного пользователя
=# \connect - skynet => SELECT * FROM people; id | firstname | lastname | phone ----+-----------+-----------+------------ T1 | Sarah | Stranahan | 06******11
F.62.3. Конфигурация #
Расширение в настоящее время имеет несколько параметров, которые можно определить для всей инстанции (
внутри postgresql.conf
или с помощью ALTER SYSTEM
).
Также возможно и часто хорошей идеей является определение их на уровне базы данных следующим образом:
ALTER DATABASE people SET transp_anon.restrict_to_trusted_schemas = on;
Только суперпользователь может изменить следующие параметры:
F.62.3.1. transp_anon.enabled #
Тип | Логический |
Значение по умолчанию | “true” |
Видимый | только для суперпользователей |
Позволяет глобально включать или отключать transp_anon.
F.62.3.2. transp_anon.inherit_labels #
Тип | Логический |
Значение по умолчанию | “true” |
Видимый | только для суперпользователей |
Наследовать метки безопасности от родительских отношений (секционированные таблицы и таблицы наследования), если таковые имеются.
F.62.3.3. transp_anon.algorithm #
Тип | Текст |
Значение по умолчанию | “sha256” |
Видимый | только для суперпользователей |
Это метод хеширования, используемый псевдонимизирующими функциями. Ознакомьтесь с pgcrypto документацией для списка доступных опций.
См. transp_anon.salt
, чтобы узнать, почему этот параметр является очень
конфиденциальной информацией.
F.62.3.4. transp_anon.restrict_to_trusted_schemas #
Тип | Логический |
Значение по умолчанию | выкл |
Видимый | для всех пользователей |
Включив этот параметр, правила маскирования должны быть определены с использованием функций, расположенных
в ограниченном списке пространств имен. По умолчанию доверяется только схема transp_anon
.
Это улучшает безопасность, предотвращая пользователей от объявления своих собственных фильтров маскировки. Это также означает, что схема должна быть явной внутри правил маскировки.
Для получения дополнительной информации, ознакомьтесь с разделом Напишите свои собственные маски главы Функции маскирования.
F.62.3.5. transp_anon.salt #
Тип | Текст |
Значение по умолчанию | (пусто) |
Видимый | только для суперпользователей |
Это соль, используемая псевдонимизирующими функциями. Очень важно определить пользовательскую соль для каждой базы данных следующим образом:
ALTER DATABASE foo SET transp_anon.salt = 'This_Is_A_Very_Secret_Salt';
Если замаскированный пользователь может прочитать соль, он/она может провести атаку методом перебора, чтобы восстановить исходные данные на основе 3 элементов:
Псевдонимизированные данные
Алгоритм хеширования (см.
transp_anon.algorithm
)Соль
Соль и название алгоритма хеширования должны быть защищены с тем же уровнем безопасности, что и сами данные. Именно поэтому вы должны хранить соль непосредственно в базе данных с помощью ALTER DATABASE
.
F.62.4. Объявить правила маскировки #
Основная идея этого расширения заключается в предложении анонимизации по замыслу.
Правила маскирования данных должны быть написаны людьми, которые разрабатывают приложение, потому что они лучше всего знают, как работает модель данных. Поэтому правила маскирования должны быть реализованы непосредственно внутри схемы базы данных.
Это позволяет маскировать данные непосредственно внутри экземпляра PostgreSQL без использования внешнего инструмента, тем самым ограничивая воздействие и риски утечки данных.
Правила маскирования данных объявляются просто с использованием SECURITY LABEL:
CREATE TABLE player( id SERIAL, name TEXT, points INT); INSERT INTO player VALUES ( 1, 'Kareem Abdul-Jabbar', 38387), ( 5, 'Michael Jordan', 32292 ); SECURITY LABEL FOR transp_anon ON COLUMN player.name IS 'MASKED WITH FUNCTION transp_anon.fake_last_name()'; SECURITY LABEL FOR transp_anon ON COLUMN player.id IS 'MASKED WITH VALUE NULL';
F.62.4.1. Экранирование строковых литералов #
Как вы могли заметить, определения правил маскировки заключены в одинарные кавычки. Поэтому, если вам нужно использовать строку внутри правила маскировки, вам нужно использовать C-Style escapes следующим образом:
SECURITY LABEL FOR transp_anon ON COLUMN player.name IS E'MASKED WITH VALUE \'CONFIDENTIAL\'';
Или используйте долларовое цитирование, которое легче читать:
SECURITY LABEL FOR transp_anon ON COLUMN player.name IS 'MASKED WITH VALUE $$CONFIDENTIAL$$';
F.62.4.2. Перечисление правил маскировки #
Чтобы отобразить все правила маскировки, объявленные в текущей базе данных, ознакомьтесь с
transp_anon.pg_masking_rules
:
SELECT * FROM transp_anon.pg_masking_rules;
F.62.4.3. Отладка правил маскирования #
Когда возникает ошибка из-за неправильного правила маскировки, вы можете получить более подробную
информацию о проблеме, установив client_min_messages
в
DEBUG
, и вы получите полезные детали
SET client_min_messages=DEBUG;
F.62.4.4. Удаление правила маскировки #
Вы можете просто удалить правило маскировки следующим образом:
SECURITY LABEL FOR transp_anon ON COLUMN player.name IS NULL;
Чтобы удалить все правила сразу, вы можете использовать:
SELECT transp_anon.remove_masks_for_all_columns();
F.62.4.5. Ограничения #
Правила маскирования запрещены для представлений CREATE VIEW
Выборка не работает методы TABLESAMPLE
F.62.5. Функции маскирования #
Расширение предоставляет функции для реализации следующих основных стратегий анонимизации:
Также вы можете использовать дополнительные функции маскирования или написать свои собственные:
В зависимости от ваших данных, вам может потребоваться использовать разные стратегии для разных столбцов :
Для имен и других “прямых идентификаторов”, Фальсификация часто бывает полезной
Перемешивание удобно для внешних ключей
Добавление шума интересно для числовых значений и дат
Частичное перемешивание идеально подходит для адресов электронной почты и номеров телефонов
etc.
F.62.5.1. Уничтожение #
Прежде всего, самый быстрый и безопасный способ анонимизировать данные — уничтожить их.
Во многих случаях лучшим подходом для скрытия содержимого столбца является замена всех значений на одно статическое значение.
Например, вы можете заменить целую колонку словом “КОНФИДЕНЦИАЛЬНО” вот так:
SECURITY LABEL FOR transp_anon ON COLUMN users.address IS 'MASKED WITH VALUE ''CONFIDENTIAL'' ';
F.62.5.2. Добавление шума #
Это также называется дисперсия. Идея заключается в “сдвиге” дат и числовых значений. Например, применяя дисперсию +/- 10% к столбцу зарплат, набор данных останется значимым.
transp_anon.noise(original_value,ratio)
, где original_value может бытьinteger
,bigint
илиdouble precision
. Если ratio равно 0.33, возвращаемое значение будет исходным значением, случайным образом смещенным на +/- 33%transp_anon.dnoise(original_value, interval)
, где original_value может быть датой, временной меткой или временем. Если interval = “2 days”, возвращаемое значение будет исходным значением, случайным образом сдвинутым на +/- 2 дня
ПРЕДУПРЕЖДЕНИЕ : Функции маскировки noise()
уязвимы к форме повторной атаки, особенно при использовании Динамической Маскировки. Маскированный пользователь может угадать исходное значение, запрашивая его маскированное значение несколько раз, а затем просто использовать функцию AVG()
для получения близкого приближения. (См. Атака на снижение шума для получения более подробной информации). В двух словах, эти функции лучше всего подходят для Анонимных Снимков и Статической Маскировки. Их следует избегать при использовании Динамической Маскировки.
F.62.5.3. Рандомизация #
Расширение предоставляет большой выбор функций для генерации чисто случайных данных:
F.62.5.3.1. Основные случайные значения #
transp_anon.random_date()
возвращает датуtransp_anon.random_string(n)
возвращает значение типа TEXT, содержащееn
буквtransp_anon.random_zip()
возвращает 5-значный кодtransp_anon.random_phone(p)
возвращает 8-значный телефонный номер с префиксомp
transp_anon.random_hash(seed)
возвращает хэш случайной строки для заданного seed
F.62.5.3.2. Случайное значение между #
Чтобы выбрать любое значение между двумя границами:
transp_anon.random_date_between(d1,d2)
возвращает дату междуd1
иd2
transp_anon.random_int_between(i1,i2)
возвращает целое число междуi1
иi2
transp_anon.random_bigint_between(b1,b2)
возвращает bigint междуb1
иb2
ПРИМЕЧАНИЕ: С этими функциями нижние и верхние границы включены. Например, transp_anon.random_int_between(1,3)
возвращает либо 1, 2 или 3.
Для более продвинутых описаний интервалов, ознакомьтесь с разделом Случайное значение в диапазоне.
F.62.5.3.3. Случайный элемент в массиве #
Функция random_in
возвращает элемент заданного массива
Например:
transp_anon.random_in(ARRAY[1,2,3])
возвращает int между 1 и 3transp_anon.random_in(ARRAY['red','green','blue'])
возвращает текст
F.62.5.3.4. Случайный в Enum #
Это особенно полезно при работе с типами ENUM
!
transp_anon.random_in_enum(variable_of_an_enum_type)
возвращает любое значение
CREATE TYPE card AS ENUM ('visa', 'mastercard', ‘amex’); SELECT transp_anon.random_in_enum(NULL::CARD); random_in_enum ---------------- mastercard CREATE TABLE customer ( id INT, ... credit_card CARD ); SECURITY LABEL FOR transp_anon ON COLUMN customer.creditcard IS 'MASKED WITH FUNCTION transp_anon.random_in_enum(creditcard)'
F.62.5.3.5. Случайное в диапазоне #
Типы RANGE являются мощным способом описания интервала значений, где можно задать включительные или исключительные границы, см. примеры.
Существует функция для каждого подтипа диапазона:
transp_anon.random_in_int4range('[5,6)')
возвращает INT со значением 5transp_anon.random_in_int8range('(6,7]')
возвращает BIGINT со значением 7`transp_anon.random_in_numrange(“[0.1,0.9]”) возвращает NUMERIC между 0.1 и 0.9
transp_anon.random_in_daterange('[2001-01-01, 2001-12-31)')
возвращает дату в 2001 годуtransp_anon.random_in_tsrange('[2022-10-01,2022-10-31]')
возвращает TIMESTAMP в октябре 2022transp_anon.random_in_tstzrange('[2022-10-01,2022-10-31]')
возвращает TIMESTAMP WITH TIMEZONE в октябре 2022
ПРИМЕЧАНИЕ: Невозможно получить случайное значение из ДИАПАЗОНА
с бесконечной границей. Например,
transp_anon.random_in_int4range('[2022,)')
возвращает NULL.
F.62.5.4. Подделка #
Идея Фальсификации заключается в замене конфиденциальных данных случайными, но правдоподобными значениями. Цель состоит в том, чтобы избежать любой идентификации из записи данных, оставаясь при этом пригодными для тестирования, анализа данных и обработки данных.
Для использования поддельных функций, сначала необходимо init()
расширение в вашей базе данных:
SELECT transp_anon.init();
Функция init()
импортирует набор данных по умолчанию, состоящий из случайных
данных (iban, имена, города и т.д.).
Примечание
Этот набор данных на русском языке и очень маленький (1000 значений для каждой категории). Если вы хотите использовать локализованные данные или загрузить определенный набор данных, пожалуйста, прочитайте раздел Пользовательские поддельные данные.
После загрузки фальшивых данных у вас есть доступ к следующим функциям подделки:
transp_anon.fake_address()
возвращает полный почтовый адресtransp_anon.fake_city()
возвращает существующий городtransp_anon.fake_country()
возвращает странуtransp_anon.fake_company()
возвращает общее название компанииtransp_anon.fake_email()
возвращает действительный адрес электронной почтыtransp_anon.fake_first_name()
возвращает общее имяtransp_anon.fake_iban()
возвращает действительный IBANtransp_anon.fake_last_name()
возвращает обобщенную фамилиюtransp_anon.fake_postcode()
возвращает действительный почтовый индекс
Для столбцов TEXT и VARCHAR вы можете использовать классический Lorem Ipsum генератор:
transp_anon.lorem_ipsum()
возвращает 5 абзацевtransp_anon.lorem_ipsum(2)
возвращает 2 абзацаtransp_anon.lorem_ipsum( paragraphs := 4 )
возвращает 4 абзацаtransp_anon.lorem_ipsum( words := 20 )
возвращает 20 словtransp_anon.lorem_ipsum( characters := 7 )
возвращает 7 символовtransp_anon.lorem_ipsum( characters := transp_anon.length(table.column) )
возвращает то же количество символов, что и исходная строка
F.62.5.5. Псевдонимизация #
Псевдонимизация аналогична Фальсификации в том смысле, что она генерирует реалистичные значения. Основное отличие заключается в том, что псевдонимизация является детерминированной: функции всегда будут возвращать одно и то же поддельное значение на основе начального значения и необязательной соли.
Для использования поддельных функций, сначала необходимо init()
расширение в вашей базе данных:
SELECT transp_anon.init();
После загрузки фиктивных данных у вас есть доступ к 10 псевдо функциям:
transp_anon.pseudo_first_name(seed,salt)
возвращает общее имяtransp_anon.pseudo_last_name(seed,salt)
возвращает общее фамилияtransp_anon.pseudo_email(seed,salt)
возвращает действительный адрес электронной почтыtransp_anon.pseudo_city(seed,salt)
возвращает существующий городtransp_anon.pseudo_country(seed,salt)
возвращает странуtransp_anon.pseudo_company(seed,salt)
возвращает общее название компанииtransp_anon.pseudo_iban(seed,salt)
возвращает действительный IBAN
Второй аргумент (salt
) является необязательным. Вы можете вызвать каждую
функцию только с затравкой, например, так: transp_anon.pseudo_city('bob')
.
Соль используется для увеличения сложности и предотвращения атак словарем и методом
перебора (см. предупреждение ниже). Если конкретная соль не указана, используется значение параметра GUC
transp_anon.salt
(см. раздел Общее хеширование для получения
дополнительных сведений).
Затравкой может служить любая информация, связанная с предметом. Например, мы можем последовательно генерировать один и тот же фальсифицированный адрес электронной почты для данного человека, используя его логин в качестве затравки:
SECURITY LABEL FOR transp_anon ON COLUMN users.emailaddress IS 'MASKED WITH FUNCTION transp_anon.pseudo_email(users.login) ';
ПРИМЕЧАНИЕ : Возможно, вам потребуется создавать уникальные значения с использованием
функции псевдонимизации. Например, если вы хотите замаскировать столбец email
,
который объявлен как UNIQUE
. В этом случае вам нужно будет
инициализировать расширение с фальшивым набором данных, который значительно больше
количества строк в таблице. В противном случае вы можете столкнуться с “коллизиями”,
т.е. два разных исходных значения могут производить одно и то же псевдозначение.
ПРЕДУПРЕЖДЕНИЕ : Псевдонимизация часто путается с анонимизацией,
но на самом деле они служат двум разным целям: pseudonymization
является
способом защитить личную информацию, но псевдонимизированные
данные все еще “связаны” с реальными данными. GDPR ясно указывает,
что личные данные, прошедшие псевдонимизацию, все еще относятся к
человеку. (см. GDPR
Recital 26)
F.62.5.6. Общее хеширование #
В теории хеширование не является допустимой техникой анонимизации, однако на практике иногда необходимо сгенерировать детерминированный хеш исходных данных.
Например, когда пара первичный ключ / внешний ключ является “естественным ключом”, она может содержать фактическую информацию (например, номер клиента, содержащий дату рождения или что-то подобное).
Хеширование таких столбцов позволяет сохранить целостность ссылок даже для относительно необычных исходных данных. Поэтому,
transp_anon.digest(value,salt,algorithm)
позволяет вам выбрать соль и алгоритм хеширования из предопределенного спискаtransp_anon.hash(value)
вернет текстовый хеш значения, используя секретный соль (определяемый параметромtransp_anon.salt
) и алгоритм хеширования (определяемый параметромtransp_anon.algorithm
). Значение по умолчанию дляtransp_anon.algorithm
—sha256
, и возможные значения: md5, sha1, sha224, sha256, sha384 или sha512. Значение по умолчанию дляtransp_anon.salt
— пустая строка. Вы можете изменить эти значения с помощью:ALTER DATABASE foo SET transp_anon.salt TO 'xsfnjefnjsnfjsnf'; ALTER DATABASE foo SET transp_anon.algorithm TO 'sha384';
Имейте в виду, что хеширование является формой псевдонимизации. Это означает, что
данные могут быть “деанонимизированы” с использованием хешированного значения и функции маскировки. Если злоумышленник получит доступ к этим двум элементам, он или она сможет
повторно идентифицировать некоторых лиц, используя brute force
или dictionary
атаки. Поэтому, дополнение к паролю и алгоритм, используемые для хеширования
данных, должны быть защищены с тем же уровнем безопасности, что и
исходный набор данных.
В двух словах, мы рекомендуем использовать функцию transp_anon.hash()
вместо transp_anon.digest()
, потому что соль не будет явно видна в правиле маскировки.
Более того: на практике хэш-функция вернет длинную строку символов, подобную этой:
SELECT transp_anon.hash('bob'); hash ---------------------------------------------------------------------------------------------------------------------------------- 95b6accef02c5a725a8c9abf19ab5575f99ca3d9997984181e4b3f81d96cbca4d0977d694ac490350e01d0d213639909987ef52de8e44d6258d536c55e427397
Для некоторых столбцов это может быть слишком длинным, и вам, возможно, придется обрезать некоторые части хэша, чтобы он поместился в столбец. Например, если у вас есть внешний ключ, основанный на номере телефона, и столбец является VARCHAR(12), вы можете преобразовать данные следующим образом:
SECURITY LABEL FOR transp_anon ON COLUMN people.phone_number IS 'MASKED WITH FUNCTION transp_anon.left(transp_anon.hash(phone_number),12)'; SECURITY LABEL FOR transp_anon ON COLUMN call_history.fk_phone_number IS 'MASKED WITH FUNCTION transp_anon.left(transp_anon.hash(fk_phone_number),12)';
Конечно, сокращение хеш-значения до 12 символов увеличит риск “ коллизии” (2 разных значения имеют один и тот же фальшивый хеш). В таком случае, вам нужно оценить этот риск.
F.62.5.7. Частичное Перемешивание #
Частичное перемешивание исключает часть данных. Например: номер кредитной карты может быть заменен на “40XX XXXX XXXX XX96”.
Доступны 2 функции:
transp_anon.partial('abcdefgh',1,'xxxx',3)
вернет “ axxxxfgh”;transp_anon.partial_email('[email protected]')
станет “da******@gm******.com”
F.62.5.8. Условное Маскирование #
В некоторых ситуациях вы можете захотеть применить фильтр маскирования только для некоторых значений или для ограниченного количества строк в таблице.
Например, если вы хотите “сохранить значения NULL”, т.е. маскировать только строки, содержащие значение, вы можете использовать функцию transp_anon.ternary
, которая работает как оператор CASE WHEN x THEN y ELSE z
:
SECURITY LABEL FOR transp_anon ON COLUMN player.score IS 'MASKED WITH FUNCTION transp_anon.ternary(score IS NULL, NULL, transp_anon.random_int_between(0,100));
Вы также можете захотеть исключить некоторые строки в таблице. Например, сохранить пароли некоторых пользователей, чтобы они все еще могли подключаться к тестовому развертыванию вашего приложения:
SECURITY LABEL FOR transp_anon ON COLUMN account.password IS 'MASKED WITH FUNCTION transp_anon.ternary( id > 1000, NULL::TEXT, password)';
ПРЕДУПРЕЖДЕНИЕ : Условное маскирование может создать частично детерминированную “связь” между исходными данными и замаскированными данными. И эту связь можно использовать для извлечения личной информации из замаскированных данных. Например, если значения NULL сохраняются для столбца “deceased_date”, это покажет, какие люди на самом деле еще живы… В двух словах: условное маскирование часто может создавать набор данных, который не полностью анонимизирован и, следовательно, технически все еще содержит личную информацию.
F.62.5.9. Обобщение #
Обобщение — это принцип замены исходного значения диапазоном, содержащим это значение. Например, вместо того чтобы сказать “Полу 42 года”, вы бы сказали “Полу от 40 до 50 лет”.
Примечание
Функции обобщения являются преобразованием типа данных. Поэтому их невозможно использовать с динамическим механизмом маскировки. Однако они полезны для создания анонимизированных представлений. См. пример ниже.
Давайте представим таблицу, содержащую информацию о здоровье:
SELECT * FROM patient; id | name | zipcode | birth | disease ----+----------+----------+------------+--------------- 1 | Alice | 47678 | 1979-12-29 | Heart Disease 2 | Bob | 47678 | 1959-03-22 | Heart Disease 3 | Caroline | 47678 | 1988-07-22 | Heart Disease 4 | David | 47905 | 1997-03-04 | Flu 5 | Eleanor | 47909 | 1999-12-15 | Heart Disease 6 | Frank | 47906 | 1968-07-04 | Cancer 7 | Geri | 47605 | 1977-10-30 | Heart Disease 8 | Harry | 47673 | 1978-06-13 | Cancer 9 | Ingrid | 47607 | 1991-12-12 | Cancer
Мы можем создать представление на основе этой таблицы, чтобы скрыть некоторые столбцы (SSN
и name
) и обобщить почтовый индекс и дату рождения следующим образом:
CREATE VIEW anonymized_patient AS SELECT 'REDACTED' AS lastname, transp_anon.generalize_int4range(zipcode,100) AS zipcode, transp_anon.generalize_tsrange(birth,'decade') AS birth disease FROM patients;
Анонимизированная таблица теперь выглядит так:
SELECT * FROM anonymized_patient; lastname | zipcode | birth | disease ----------+---------------+-----------------------------+--------------- REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Heart Disease REDACTED | [47600,47700) | ["1950-01-01","1960-01-01") | Heart Disease REDACTED | [47600,47700) | ["1980-01-01","1990-01-01") | Heart Disease REDACTED | [47900,48000) | ["1990-01-01","2000-01-01") | Flu REDACTED | [47900,48000) | ["1990-01-01","2000-01-01") | Heart Disease REDACTED | [47900,48000) | ["1960-01-01","1970-01-01") | Cancer REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Heart Disease REDACTED | [47600,47700) | ["1970-01-01","1980-01-01") | Cancer REDACTED | [47600,47700) | ["1990-01-01","2000-01-01") | Cancer
Обобщенные значения по-прежнему полезны для статистики, потому что они остаются верными, но они менее точны, и поэтому снижают риск повторной идентификации.
PostgreSQL предлагает несколько типов ДИАПАЗОНОВ которые идеально подходят для дат и числовых значений.
Для числовых значений доступны 3 функции:
generalize_int4range(value, step)
generalize_int8range(value, step)
generalize_numrange(value, step)
…где value
— это данные, которые будут обобщены, а
step
— это размер каждого диапазона.
F.62.5.10. Перемешивание #
Перемешивание смешивает значения в пределах одних и тех же столбцов.
anon.shuffle_column(shuffle_table, shuffle_column, primary_key)
перемешает все значения в указанном столбце. Вам нужно указать первичный ключ таблицы.
Это полезно для внешних ключей, потому что будет сохранена ссылочная целостность.
F.62.5.11. Использование функций pg_catalog
#
Начиная с версии 1.3, схема pg_catalog
по умолчанию не является доверенной. Это мера безопасности, предназначенная для предотвращения использования пользователями сложных функций в правилах маскировки (таких как pg_catalog.query_to_xml
, pg_catalog.ts_stat
или функции системного администрирования), которые не должны использоваться в качестве функций маскировки.
Однако расширение предоставляет привязки к некоторым полезным и безопасным функциям из
схемы pg_catalog
для вашего удобства:
transp_anon.concat(TEXT,TEXT)
transp_anon.concat(ТЕКСТ,ТЕКСТ, ТЕКСТ)
transp_anon.date_add(TIMESTAMP WITH TIME ZONE,ИНТЕРВАЛ)
transp_anon.date_part(TEXT,TIMESTAMP)
transp_anon.date_part(TEXT,INTERVAL)
transp_anon.date_subtract(TIMESTAMP WITH TIME ZONE, ИНТЕРВАЛ )
transp_anon.date_trunc(TEXT,TIMESTAMP)
transp_anon.date_trunc(ТЕКСТ,МЕТКА ВРЕМЕНИ С ЧАСОВЫМ ПОЯСОМ,ТЕКСТ)
transp_anon.date_trunc(TEXT,INTERVAL)
transp_anon.left(TEXT,INTEGER)
transp_anon.length(TEXT)
transp_anon.lower(TEXT)
transp_anon.make_date(INT,INT,INT )
transp_anon.make_time(INT,INT,DOUBLE PRECISION)
transp_anon.md5(TEXT)
transp_anon.random()
transp_anon.replace(TEXT,TEXT,TEXT)
transp_anon.regexp_replace(TEXT,TEXT,TEXT)
transp_anon.regexp_replace(TEXT,TEXT,TEXT,TEXT)
transp_anon.right(TEXT,INTEGER)
transp_anon.substr(TEXT,INTEGER)
transp_anon.substr(TEXT,INTEGER,INTEGER)
transp_anon.upper(TEXT)
Если вам нужно больше привязок, вы можете либо
Напишите свою собственную функцию отображения в доверенной схеме (см. ниже)
Установите схему
pg_catalog
какTRUSTED
(не рекомендуется)открыть проблему
F.62.5.12. Напишите свои собственные Маски #
Вы также можете использовать свою собственную функцию в качестве маски. Функция должна быть либо деструктивной (как Частичное Перемешивание), либо вносить некоторую случайность в набор данных (как Подделка).
Особенно для сложных типов данных, вам, возможно, придется написать свою собственную функцию. Это будет обычным случаем, если вам нужно скрыть определенные части поля JSON.
Например:
CREATE TABLE company ( business_name TEXT, info JSONB )
Поле info
содержит неструктурированные данные, такие как:
SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green'; jsonb_pretty ---------------------------------- { "employees": [ { "lastName": "Doe", "firstName": "John" }, { "lastName": "Smith", "firstName": "Anna" }, { "lastName": "Jones", "firstName": "Peter" } ] } (1 row)
Используя функции и операторы JSON в PostgreSQL, вы можете пройти по ключам и заменить конфиденциальные значения по мере необходимости.
CREATE SCHEMA custom_masks; -- This step requires superuser privilege SECURITY LABEL FOR transp_anon ON SCHEMA custom_masks IS 'TRUSTED'; CREATE FUNCTION custom_masks.remove_last_name(j JSONB) RETURNS JSONB VOLATILE LANGUAGE SQL AS $func$ SELECT json_build_object( 'employees' , array_agg( jsonb_set(e ,'{lastName}', to_jsonb(transp_anon.fake_last_name())) ) )::JSONB FROM jsonb_array_elements( j->'employees') e $func$;
Затем проверьте, что функция работает правильно:
SELECT custom_masks.remove_last_name(info) FROM company;
Когда это будет в порядке, вы можете объявить эту функцию как маску для поля info
:
SECURITY LABEL FOR transp_anon ON COLUMN company.info IS 'MASKED WITH FUNCTION custom_masks.remove_last_name(info)';
Выберите данные:
# SELECT anonymize_table('company'); # SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green'; jsonb_pretty ------------------------------------- { "employees": [ + { + "lastName": "Prawdzik",+ "firstName": "John" + }, + { + "lastName": "Baltazor",+ "firstName": "Anna" + }, + { + "lastName": "Taylan", + "firstName": "Peter" + } + ] + } (1 row)
Как вы можете видеть, манипулирование сложной структурой JSON с помощью SQL возможно, но поначалу это может быть сложно! Существует несколько способов обхода ключей и обновления значений. Вероятно, вам придется попробовать разные подходы, в зависимости от ваших реальных данных JSON и производительности, которую вы хотите достичь.
F.62.6. Динамическое маскирование #
Вы можете скрыть некоторые данные от роли, объявив эту роль как MASKED
.
Другие роли по-прежнему будут иметь доступ к оригинальным данным.
Пример:
CREATE TABLE people ( id TEXT, firstname TEXT, lastname TEXT, phone TEXT); INSERT INTO people VALUES ('T1','Sarah', 'Conor','0609110911'); SELECT * FROM people; =# SELECT * FROM people; id | firstname | lastname | phone ----+----------+----------+------------ T1 | Sarah | Conor | 0609110911 (1 row)
Шаг 1 : Активируйте движок динамического маскирования
=# CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; =# SELECT transp_anon.init();
Шаг 2 : Объявить замаскированного пользователя
=# CREATE ROLE skynet LOGIN; =# GRANT SELECT ON TABLE people TO skynet; =# SECURITY LABEL FOR transp_anon ON ROLE skynet IS 'MASKED';
Шаг 3 : Объявите правила маскировки
SECURITY LABEL FOR transp_anon ON COLUMN people.lastname IS 'MASKED WITH FUNCTION transp_anon.fake_last_name()'; SECURITY LABEL FOR transp_anon ON COLUMN people.phone IS 'MASKED WITH FUNCTION transp_anon.partial(phone,2,$$******$$,2)';
Шаг 4 : Подключитесь от имени маскированного пользователя
=# \c - skynet => SELECT * FROM people; id | firstname | lastname | phone ----+----------+-----------+------------ T1 | Sarah | Stranahan | 06******11 (1 row)
F.62.6.1. Как снять маску с роли #
Просто удалите метку безопасности следующим образом:
SECURITY LABEL FOR transp_anon ON ROLE bob IS NULL;
Чтобы снять маскировку со всех замаскированных ролей сразу, вы можете ввести:
SELECT transp_anon.remove_masks_for_all_roles();
F.62.7. Обобщение #
F.62.7.1. Снижение точности конфиденциальных данных #
Идея обобщения заключается в замене данных на более широкое, менее точное значение. Например, вместо того чтобы сказать “Бобу 28 лет”, можно сказать “Бобу от 20 до 30 лет”. Это интересно для аналитики, потому что данные остаются верными, избегая при этом риска повторной идентификации.
PostgreSQL может легко справляться с обобщением с помощью типов RANGE, очень мощного способа хранения и манипулирования набором значений, заключенных между нижней и верхней границей.
F.62.7.2. Пример #
Вот основная таблица, содержащая медицинские данные:
# SELECT * FROM patient; ssn | firstname | zipcode | birth | disease -------------+-----------+---------+------------+--------------- 253-51-6170 | Alice | 47012 | 1989-12-29 | Heart Disease 091-20-0543 | Bob | 42678 | 1979-03-22 | Allergy 565-94-1926 | Caroline | 42678 | 1971-07-22 | Heart Disease 510-56-7882 | Eleanor | 47909 | 1989-12-15 | Acne 098-24-5548 | David | 47905 | 1997-03-04 | Flu 118-49-5228 | Jean | 47511 | 1993-09-14 | Flu 263-50-7396 | Tim | 47900 | 1981-02-25 | Heart Disease 109-99-6362 | Bernard | 47168 | 1992-01-03 | Asthma 287-17-2794 | Sophie | 42020 | 1972-07-14 | Asthma 409-28-2014 | Arnold | 47000 | 1999-11-20 | Diabetes (10 rows)
Мы хотим, чтобы анонимные данные оставались достоверными, потому что они будут использоваться для статистики. Мы можем создать представление на основе этой таблицы, чтобы удалить ненужные столбцы и обобщить косвенные идентификаторы:
CREATE MATERIALIZED VIEW generalized_patient AS SELECT 'REDACTED'::TEXT AS firstname, transp_anon.generalize_int4range(zipcode,1000) AS zipcode, transp_anon.generalize_daterange(birth,'decade') AS birth, disease FROM patient;
Это даст нам менее точное представление о данных:
# SELECT * FROM generalized_patient; firstname | zipcode | birth | disease -----------+---------------+-------------------------+--------------- REDACTED | [47000,48000) | [1980-01-01,1990-01-01) | Heart Disease REDACTED | [42000,43000) | [1970-01-01,1980-01-01) | Allergy REDACTED | [42000,43000) | [1970-01-01,1980-01-01) | Heart Disease REDACTED | [47000,48000) | [1980-01-01,1990-01-01) | Acne REDACTED | [47000,48000) | [1990-01-01,2000-01-01) | Flu REDACTED | [47000,48000) | [1990-01-01,2000-01-01) | Flu REDACTED | [47000,48000) | [1980-01-01,1990-01-01) | Heart Disease REDACTED | [47000,48000) | [1990-01-01,2000-01-01) | Asthma REDACTED | [42000,43000) | [1970-01-01,1980-01-01) | Asthma REDACTED | [47000,48000) | [1990-01-01,2000-01-01) | Diabetes (10 rows)
F.62.7.3. Функции обобщения #
PostgreSQL Anonymizer предоставляет 6 функций обобщения. По одной для каждого типа RANGE. Обычно эти функции принимают исходное значение в качестве первого параметра и второй параметр для длины каждого шага.
Для числовых значений :
transp_anon.generalize_int4range(42,5)
возвращает диапазон[40,45)
transp_anon.generalize_int8range(12345,1000)
возвращает диапазон[12000,13000)
transp_anon.generalize_numrange(42.32378,10)
возвращает диапазон[40,50)
Для значений времени :
transp_anon.generalize_tsrange('1904-11-07','year')
возвращает['1904-01-01','1905-01-01')
transp_anon.generalize_tstzrange('1904-11-07','week')
возвращает['1904-11-07','1904-11-14')
transp_anon.generalize_daterange('1904-11-07','десятилетие')
возвращает[1900-01-01,1910-01-01)
Возможные шаги: микросекунды, миллисекунды, секунда, минута, час, день, неделя, месяц, год, десятилетие, век и тысячелетие.
F.62.7.4. Ограничения #
F.62.7.4.1. Выделение и экстремальные значения #
“Выделение” - это возможность изолировать отдельного индивидуума в наборе данных, используя экстремальные значения или исключительные значения.
Например:
# SELECT * FROM employees; id | name | job | salary ------+----------------+------+-------- 1578 | xkjefus3sfzd | NULL | 1498 2552 | cksnd2se5dfa | NULL | 2257 5301 | fnefckndc2xn | NULL | 45489 7114 | npodn5ltyp3d | NULL | 1821
В этой таблице мы видим, что у конкретного сотрудника очень высокая зарплата, значительно превышающая среднюю зарплату. Поэтому, вероятно, этот человек является генеральным директором компании.
С обобщением это важно, потому что размер диапазона (“шаг”) должен быть достаточно широким, чтобы предотвратить идентификацию одного конкретного человека.
ПРЕДУПРЕЖДЕНИЕ : По определению, при обобщении данные остаются верными, но тип столбца изменяется. Это означает, что преобразование не является прозрачным.
F.62.8. Пользовательские поддельные данные #
Это расширение поставляется с небольшим набором фиктивных данных по умолчанию. Для каждой фиктивной
функции (fake_email()
, fake_first_name()
) мы
предоставляем только 1000 уникальных значений, и они только на русском языке.
Вот как вы можете создать свой собственный набор фальшивых данных!
F.62.8.1. Создайте свой собственный фальшивый набор данных #
В качестве примера, вот скрипт на Python, который сгенерирует для вас фальшивые данные:
$TANTOR_DIR/utils/data_generator/data_generator.py
Этот скрипт требует библиотеку Faker. Вы можете установить её с помощью:
pip3 install faker
Чтобы создать 5000 писем на русском и английском языках, вы бы вызвали скрипты следующим образом:
data_generator.py --table email --locales ru_RU,en --lines 5000
Это выведет фальшивые данные в формате CSV
.
Используйте data_generator.py --help
для получения более подробной информации о параметрах скрипта.
Вы можете загрузить фиктивные данные непосредственно в расширение следующим образом:
TRUNCATE transp_anon.email; COPY transp_anon.email FROM PROGRAM 'data_generator.py --table email --locales ru_RU,en --lines 5000'; SELECT setval('transp_anon.email_oid_seq', max(oid)) FROM transp_anon.email; CLUSTER transp_anon.email;
F.62.8.2. Загрузите свои собственные фиктивные данные #
Если вы хотите использовать свой собственный набор данных, вы можете импортировать пользовательские CSV файлы с :
SELECT transp_anon.init('/path/to/custom_csv_files/')
Посмотрите в папку data
, чтобы найти формат файлов CSV.
F.62.9. Производительность #
Любой процесс анонимизации имеет свою цену, так как он будет потреблять время ЦП, пространство ОЗУ и, вероятно, значительное количество операций ввода-вывода на диск… Вот краткий обзор вопроса в зависимости от используемой вами стратегии….
В двух словах, производительность анонимизации будет в основном зависеть от 2 важных факторов:
Размер базы данных
Количество правил маскировки
F.62.9.1. Динамическое Маскирование #
С динамическим маскированием реальные данные заменяются на лету каждый раз, когда замаскированный пользователь отправляет запрос в базу данных. Это означает, что у пользователей с маскированием будет более медленное время отклика по сравнению с обычными (незамаскированными) пользователями. Это, как правило, приемлемо, потому что обычно замаскированные пользователи не считаются такими важными, как обычные.
Если вы примените 3 или 4 правила к таблице, время отклика для маскированных пользователей должно быть примерно на 20% - 30% медленнее, чем для обычных пользователей.
Поскольку правила маскировки применяются к каждому запросу маскированных пользователей, динамическая маскировка подходит, когда у вас ограниченное количество маскированных пользователей, которые подключаются к базе данных только время от времени. Например, аналитик данных, подключающийся раз в неделю для создания бизнес-отчета.
Если есть несколько замаскированных пользователей или если замаскированный пользователь очень активен, вам, вероятно, следует экспортировать замаскированные данные раз в неделю на вторичную инстанцию и позволить этим пользователям подключаться к этой вторичной инстанции.
Примечание
В этом случае стоимость анонимизации “оплачивается” только замаскированными пользователями.
F.62.9.2. Как ускорить процесс? #
F.62.9.2.1. Предпочитайте MASKED WITH VALUE
всякий раз, когда это возможно #
Всегда быстрее заменить исходные данные статическим значением, чем вызывать функцию маскировки.
F.62.9.2.2. Выборка #
Если вам нужно анонимизировать данные для тестирования, скорее всего, будет достаточно меньшего подмножества вашей базы данных. В этом случае вы можете легко ускорить процесс анонимизации, уменьшив объем данных.
Ознакомьтесь с разделом Выборка для получения дополнительной информации.
F.62.9.2.3. Материализованные представления #
Динамическое маскирование не всегда требуется! В некоторых случаях более эффективно создать CREATE MATERIALIZED VIEW вместо этого.
Например:
CREATE MATERIALIZED VIEW masked_customer AS SELECT id, anon.random_last_name() AS name, anon.random_date_between('1920-01-01'::DATE,now()) AS birth, fk_last_order, store_id FROM customer;
F.62.10. Безопасность #
F.62.10.1. Разрешения #
Вот обзор того, что пользователи могут делать в зависимости от имеющихся у них привилегий:
Действие | Суперпользователь | Владелец | Маскированная роль |
---|---|---|---|
Создать расширение | Да | ||
Удалить расширение | Да | ||
Запустить расширение | Да | ||
Сбросить расширение | Да | ||
Настроить расширение | Да | ||
Замаскировать роль | Да | ||
Начать динамическое маскирование | Да | ||
Остановить динамическое маскирование | Да | ||
Создать таблицу | Да | Да | |
Объявить правило маскировки | Да | Да | |
Вставить, удалить, обновить строку | Да | Да | |
Статическое маскирование | Да | Да | |
Выбрать реальные данные | Да | Да | |
Обычная выгрузка | Да | Да | |
Анонимный дамп | Да | Да | |
Использовать функции маскировки | Да | Да | Да |
Выбрать замаскированные данные | Да | Да | Да |
Посмотреть правил маскировки | Да | Да | Да |
F.62.10.2. Ограничьте фильтры маскировки только доверенными схемами #
По умолчанию владелец базы данных может писать правила маскировки только с функциями, которые находятся в доверенных схемах, контролируемых суперпользователями.
Из коробки только схема transp_anon
объявлена
доверенной. Это означает, что по умолчанию функции из pg_catalog
не могут использоваться в правилах маскирования.
Для получения дополнительной информации прочитайте раздел Использование функций pg_catalog.
F.62.10.3. Контекст безопасности функций #
Большинство функций этого расширения объявлены с тегом SECURITY
INVOKER
. Это означает, что эти функции выполняются с
привилегиями пользователя, который их вызывает. Это важное ограничение.
Это расширение содержит еще несколько функций, объявленных с тегом SECURITY
DEFINER
.
F.62.11. Демонстрации #
Демонстрации показывают разные случаи.
F.62.11.1. Добавить больше электронных писем #
Добавить значения в transp_anon.email
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); SELECT count(*) FROM transp_anon.email; CREATE TEMPORARY TABLE tmp_email AS SELECT * FROM transp_anon.email; TRUNCATE transp_anon.email; INSERT INTO transp_anon.email SELECT ROW_NUMBER() OVER (), concat(u.username,'@', d.domain) FROM ( SELECT split_part(val,'@',1) AS username FROM tmp_email ORDER BY RANDOM() LIMIT 10 ) u, ( SELECT split_part(val,'@',2) AS domain FROM tmp_email ORDER BY RANDOM() LIMIT 5 ) d ; SELECT count(*) FROM transp_anon.email; ROLLBACK;
F.62.11.2. Динамическая анонимизация для другой роли #
В этой демонстрации мы создаем маскированную роль skynet
. Операции выбора
будут возвращать анонимные данные для этой роли.
-- STEP 1 : Activate the masking engine CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); -- STEP 2 : Declare a masked user CREATE ROLE skynet LOGIN; SECURITY LABEL FOR transp_anon ON ROLE skynet IS 'MASKED'; -- STEP 3 : Declare the masking rules CREATE TABLE people ( id TEXT, firstname TEXT, lastname TEXT, phone TEXT); INSERT INTO people VALUES ('T1','Sarah', 'Conor','0609110911'); SELECT * FROM people; -- STEP 3 : Declare the masking rules SECURITY LABEL FOR transp_anon ON COLUMN people.lastname IS 'MASKED WITH FUNCTION transp_anon.fake_last_name()'; SECURITY LABEL FOR transp_anon ON COLUMN people.phone IS 'MASKED WITH FUNCTION transp_anon.partial(phone,2,$$******$$,2)'; -- STEP 4 : Connect with the masked user \! psql demo -U skynet -c 'SELECT * FROM people;' -- STEP 5 : Clean up DROP EXTENSION anon CASCADE; REASSIGN OWNED BY skynet TO postgres; DROP OWNED BY skynet; DROP ROLE skynet;
F.62.11.3. Объявить правила маскирования для столбца #
В этой демонстрации правила маскировки объявлены для столбца player.name
.
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); CREATE TABLE player( id SERIAL, name TEXT, points INT); INSERT INTO player VALUES ( 1, 'Kareem Abdul-Jabbar', 38387), ( 5, 'Michael Jordan', 32292); SECURITY LABEL FOR transp_anon ON COLUMN player.name IS 'MASKED WITH FUNCTION transp_anon.fake_last_name()'; ROLLBACK;
F.62.11.4. Обобщение #
Демонстрация использования функций обобщения.
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; CREATE TABLE patient ( ssn TEXT, firstname TEXT, zipcode INTEGER, birth DATE, disease TEXT ); INSERT INTO patient VALUES ('253-51-6170','Alice',47012,'1989-12-29','Heart Disease'), ('091-20-0543','Bob',42678,'1979-03-22','Allergy'), ('565-94-1926','Caroline',42678,'1971-07-22','Heart Disease'), ('510-56-7882','Eleanor',47909,'1989-12-15','Acne'), ('098-24-5548','David',47905,'1997-03-04','Flu'), ('118-49-5228','Jean',47511,'1993-09-14','Flu'), ('263-50-7396','Tim',47900,'1981-02-25','Heart Disease'), ('109-99-6362','Bernard',47168,'1992-01-03','Asthma'), ('287-17-2794','Sophie',42020,'1972-07-14','Asthma'), ('409-28-2014','Arnold',47000,'1999-11-20','Diabetes') ; SELECT * FROM patient; CREATE MATERIALIZED VIEW generalized_patient AS SELECT 'REDACTED' AS firstname, transp_anon.generalize_int4range(zipcode,1000) AS zipcode, transp_anon.generalize_daterange(birth,'decade') AS birth, disease FROM patient; SELECT * FROM generalized_patient; ROLLBACK;
F.62.11.5. Хеширование #
Это демонстрация функции хеширования Мы попытаемся псевдонимизировать естественный внешний ключ
BEGIN; -- We have a simplistic customer table CREATE TABLE customer ( id SERIAL, name TEXT, phone_number TEXT UNIQUE NOT NULL ); INSERT INTO customer VALUES (2046,'Omar Little','410-719-9009'), (8123,'Russell Bell','410-617-7308'), (3456,'Avon Barksdale','410-385-2983'); -- We have a log of their calls to our customer service hotline CREATE TABLE hotline_call_history ( id SERIAL, fk_phone_number TEXT REFERENCES customer(phone_number) DEFERRABLE, call_start_time TIMESTAMP, call_end_time TIMESTAMP ); INSERT INTO hotline_call_history VALUES (834,'410-617-7308','2004-05-17 09:41:01','2004-05-17 09:44:24'), (835,'410-385-2983','2004-05-17 11:22:55','2004-05-17 11:34:18'), (839,'410-719-9009','2004-05-18 16:02:03','2004-05-18 16:22:56'), (878,'410-385-2983','2004-05-20 13:13:34','2004-05-18 13:51:00'); -- We can get a details view of the calls like this SELECT c.id AS customer_id, c.name AS customer_name, h.call_start_time, h.call_end_time FROM hotline_call_history h JOIN customer c ON h.fk_phone_number = c.phone_number WHERE extract(year from h.call_start_time) = 2004 ORDER BY h.call_start_time; -- Now let's pseudonymize this ! CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; -- Init SELECT transp_anon.init(); SET transp_anon.salt = '4ag3b803de6180361'; SET transp_anon.algorithm = 'md5'; -- SELECT transp_anon.set_secret_salt('4ag3b803de6180361'); -- SELECT transp_anon.set_secret_algorithm('md5'); -- Masking Rules SECURITY LABEL FOR transp_anon ON COLUMN customer.name IS 'MASKED WITH FUNCTION transp_anon.concat( transp_anon.pseudo_first_name(name), $$ $$, transp_anon.pseudo_last_name(name))'; SECURITY LABEL FOR transp_anon ON COLUMN customer.phone_number IS 'MASKED WITH FUNCTION transp_anon.hash(phone_number)'; SECURITY LABEL FOR transp_anon ON COLUMN hotline_call_history.fk_phone_number IS 'MASKED WITH FUNCTION transp_anon.hash(fk_phone_number)'; -- Apply the masking rules SET CONSTRAINTS ALL DEFERRED; SELECT current_user \gset SECURITY LABEL FOR transp_anon ON ROLE :current_user IS 'MASKED'; -- Launch the same request SELECT c.id AS customer_id, c.name AS customer_name, h.call_start_time, h.call_end_time FROM hotline_call_history h JOIN customer c ON h.fk_phone_number = c.phone_number WHERE extract(year from h.call_start_time) = 2004 ORDER BY h.call_start_time; -- ROLLBACK;
F.62.11.6. Наследование правил маскирования #
Демонстрация анонимизации при наследовании
BEGIN; CREATE TABLE public.invoice ( name text, amount float, published_date date ); CREATE TABLE public.invoice_2020 ( CHECK ( published_date >= DATE '2020-01-01' AND published_date <= DATE '2020-12-31' ) ) INHERITS (invoice); INSERT INTO invoice_2020 VALUES('John DOE', 236.25, '2020-12-09'); CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); SELECT current_user \gset SECURITY LABEL FOR transp_anon ON ROLE :current_user IS 'MASKED'; -- -- Put mask on the mother table -- SECURITY LABEL FOR transp_anon ON COLUMN public.invoice.name IS 'MASKED WITH VALUE $$CONFIDENTIAL$$'; SELECT public.invoice.name, public.invoice_2020.name FROM public.invoice, public.invoice_2020; -- name | name -- --------------+-------------- -- CONFIDENTIAL | CONFIDENTIAL -- -- Put mask on the child table -- SECURITY LABEL FOR transp_anon ON COLUMN public.invoice_2020.name IS 'MASKED WITH VALUE $$DELETED$$'; SELECT public.invoice.name, public.invoice_2020.name FROM public.invoice, public.invoice_2020; -- name | name -- --------------+--------- -- CONFIDENTIAL | DELETED ROLLBACK;
F.62.11.7. Атака на снижение шума #
Это базовый пример повторных атак на функции маскировки noise()
, когда они используются с динамической маскировкой. Мы продемонстрируем, как роль с маскировкой может угадать реальные данные, используя только представление маскировки
BEGIN; -- STEP 1 : Activate the masking engine CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); -- STEP 2: Declare a masked user CREATE ROLE attacker LOGIN; SECURITY LABEL FOR transp_anon ON ROLE attacker IS 'MASKED'; -- STEP 3: Load the data DROP TABLE IF EXISTS people CASCADE; CREATE TABLE people ( id INT, name TEXT, age INT); INSERT INTO people VALUES (157, 'Mike Ehrmantraut' , 63); INSERT INTO people VALUES (482, 'Jimmy McGill', 47); -- STEP 4: Declare the masking rules SECURITY LABEL FOR transp_anon ON COLUMN people.name IS 'MASKED WITH VALUE $$CONFIDENTIAL$$ '; SECURITY LABEL FOR transp_anon ON COLUMN people.age IS 'MASKED WITH FUNCTION transp_anon.noise(age, 0.33)'; -- STEP 5: Now let's connect with the masked user SELECT current_user \gset SECURITY LABEL FOR transp_anon ON ROLE :current_user IS 'MASKED'; -- When attacker asks for the age of person #157, he/she gets a "noised" value SELECT age FROM people WHERE id = 157; -- Now let's do this 10000 times and get the average DO $$ DECLARE v iNT; a int[]; BEGIN FOR i in 1..10000 LOOP SELECT age into v FROM people WHERE id=157; a:=array_append(a,v); END LOOP; SELECT avg(u) into v FROM unnest(a) u; RAISE NOTICE 'Age of Person 157: %', v; END $$; ROLLBACK;
F.62.11.8. Возвращает NULL или фиктивное значение #
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); CREATE OR REPLACE FUNCTION null_or_random_date(val TIMESTAMPTZ) RETURNS TIMESTAMPTZ AS $$ SELECT transp_anon.random_date(); $$ LANGUAGE SQL VOLATILE RETURNS NULL ON NULL INPUT; SELECT null_or_random_date(NULL); SELECT null_or_random_date('2020-05-05'); ROLLBACK;
F.62.11.9. Частичное #
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.partial('abcdefgh',1,'xxxx',3); SELECT transp_anon.partial('+33142928100',4,'******',2); SELECT transp_anon.partial_email('[email protected]'); ROLLBACK;
F.62.11.10. Различие фальшивых функций #
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); --let's use `TEMPORARY` instead of `pg_temp` for clarity --SET search_path TO pg_temp, public; CREATE TEMPORARY TABLE customer( id SERIAL, full_name TEXT, hair_color TEXT, birth DATE, employer TEXT, zipcode TEXT, fk_shop INTEGER ); INSERT INTO customer VALUES (911,'Chuck Norris','brown','1940-03-10','Texas Rangers', '75001',12), (312,'David Hasselhoff','black','1952-07-17','Baywatch', '90001',423) ; SELECT * FROM customer; UPDATE customer SET full_name=transp_anon.fake_first_name() || ' ' || transp_anon.fake_last_name(), hair_color=transp_anon.random_in(ARRAY['black','blonde','brown','red',NULL]), birth=transp_anon.random_date_between('1920-01-01'::DATE,now()), employer=transp_anon.fake_company(), zipcode=transp_anon.random_zip() ; SELECT * FROM customer; ROLLBACK;
F.62.11.11. Напишите свою маску #
BEGIN; CREATE EXTENSION IF NOT EXISTS transp_anon CASCADE; SELECT transp_anon.init(); CREATE TABLE company ( business_name TEXT, info JSONB ); INSERT INTO company VALUES ( 'Soylent Green', '{"employees":[ {"firstName":"John", "lastName":"Doe"}, {"firstName":"Anna", "lastName":"Smith"}, {"firstName":"Peter", "lastName":"Jones"} ]}'); SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green'; CREATE FUNCTION transp_anon.anonymize_last_name(j JSONB) RETURNS JSONB VOLATILE LANGUAGE SQL AS $func$ SELECT json_build_object( 'employees' , array_agg( jsonb_set(e ,'{lastName}', to_jsonb(transp_anon.fake_last_name())) ) )::JSONB FROM jsonb_array_elements( j->'employees') e $func$; SELECT transp_anon.anonymize_last_name(info) FROM company; SECURITY LABEL FOR transp_anon ON COLUMN company.info IS 'MASKED WITH FUNCTION transp_anon.anonymize_last_name(info)'; SELECT current_user \gset SECURITY LABEL FOR transp_anon ON ROLE :current_user IS 'MASKED'; SELECT jsonb_pretty(info) FROM company WHERE business_name = 'Soylent Green'; ROLLBACK;