G.1. pgpro_anonymizer — маскирование или замена конфиденциальных данных #
- G.1.1. Термины и определения
- G.1.2. Пример анонимизации
- G.1.3. Установка и подготовка
- G.1.4. Конфигурирование
- G.1.5. Объявление правил маскирования
- G.1.6. Функции маскирования
- G.1.7. Статическое маскирование
- G.1.8. Динамическое маскирование
- G.1.9. Выгрузка анонимизированных данных
- G.1.10. Обобщение
- G.1.11. Производительность
- G.1.12. Безопасность
- G.1.2. Пример анонимизации
pgpro_anonymizer — это расширение для маскирования или замены конфиденциальных коммерческих данных или информации, позволяющей установить личность (PII, персональные данные), в БД Postgres Pro.
В проекте используется декларативный подход к анонимизации. Это означает, что вы можете объявлять правила маскирования, используя язык описания данных (DDL), и задавать свою стратегию анонимизации внутри самого определения таблицы.
После определения правил маскирования вы можете получить доступ к анонимизированным данным следующими способами:
Выгрузка анонимизированных данных: простое экспортирование замаскированных данных в файл SQL.
Статическое маскирование: удаление персональных данных в соответствии с правилами.
Динамическое маскирование: скрытие персональных данных только от недоверенных пользователей.
Кроме того, доступны различные функции для реализации следующих методов маскирования: рандомизация, фальсификация, частичное скрытие, перестановка и искажение. Пользователи могут также разрабатывать свои функции.
Помимо маскирования также можно использовать ещё один подход, который называется обобщение и полезен для сбора статистики и анализа данных.
G.1.1. Термины и определения #
Используются следующие основные стратегии:
Динамическое маскирование изменяет представление реальных данных, не модифицируя их. Некоторые пользователи могут читать только замаскированные данные, а другие могут получить доступ к исходной версии.
Статическое маскирование полностью заменяет конфиденциальную информацию несвязанными данными. После такой обработки исходные данные не могут быть восстановлены.
Данные могут быть изменены несколькими способами:
Удаление просто удаляет данные.
Статическая замена последовательно заменяет данные одним и тем же значением. Например: замена всех значений столбца типа
text
на значение «КОНФИДЕНЦИАЛЬНО».Отклонение «сдвигает» даты и числовые значения. Например, после применения отклонения +/- 10% к столбцу зарплаты набор данных не потеряет смысл.
Обобщение снижает точность данных, заменяя их диапазоном значений. Вместо «Бобу 28 лет» можно сказать «Бобу от 20 до 30 лет». Этот метод полезен для аналитики, так как данные остаются верными.
Перестановка перемешивает значения в рамках столбца. Исходные данные могут быть восстановлены, если алгоритм перестановки будет расшифрован.
Рандомизация заменяет конфиденциальные данные случайными, но правдоподобными значениями. Цель состоит в том, чтобы исключить возможность какой-либо идентификации по записи данных, оставляя её пригодной для тестирования, анализа и обработки данных.
Частичное скрытие аналогично статической подстановке, но оставляет часть данных нетронутыми. Например: номер кредитной карты может быть заменён на «40XX XXXX XXXX XX96».
Пользовательские правила предназначены для изменения данных в соответствии с особыми требованиями (например, заменить одновременно почтовый индекс и название города случайными значениями, чтобы они оставались согласованными).
Псевдонимизация защищает персональные данные, скрывая их с помощью дополнительной информации. Шифрование и хеширование — два примера методов псевдонимизации. Псевдонимизированные данные, тем не менее, по-прежнему остаются связаны с исходными данными.
G.1.2. Пример анонимизации #
Предположим, что необходимо замаскировать адреса электронной почты и телефонные номера:
SELECT * FROM people; id | firstname | lastname | phone ----+-----------+----------+------------ T1 | Sarah | Connor | 0609110911
Активируйте механизм динамического маскирования:
SELECT anon.start_dynamic_masking();
Объявите недоверенного пользователя:
CREATE ROLE skynet LOGIN; SECURITY LABEL FOR anon ON ROLE skynet IS 'MASKED';
Объявите правила маскирования:
SECURITY LABEL FOR anon ON COLUMN people.lastname IS 'MASKED WITH FUNCTION anon.fake_last_name()'; SECURITY LABEL FOR anon ON COLUMN people.phone IS 'MASKED WITH FUNCTION anon.partial(phone,2,$$******$$,2)';
Подключитесь к серверу от имени недоверенного пользователя:
\connect - skynet SELECT * FROM people; id | firstname | lastname | phone ----+-----------+-----------+------------ T1 | Sarah | Stranahan | 06******11
G.1.3. Установка и подготовка #
Расширение pgpro_anonymizer
поставляется вместе с Postgres Pro Enterprise в виде отдельного пакета pgpro-anonymizer-ent-17
(подробные инструкции по установке приведены в Главе 17). Чтобы включить pgpro_anonymizer
, выполните следующие действия:
Добавьте имя библиотеки в переменную
shared_preload_libraries
в файлеpostgresql.conf
:shared_preload_libraries = 'anon'
Перезагрузите сервер баз данных, чтобы изменения вступили в силу.
Примечание
Чтобы убедиться, что библиотека установлена правильно, вы можете выполнить следующую команду:
SHOW shared_preload_libraries;
Создайте расширение, выполнив следующий запрос:
CREATE EXTENSION anon CASCADE;
Инициализируйте расширение:
SELECT anon.init();
Функция
init()
импортирует набор случайных данных по умолчанию (IBAN (международный номер банковского счёта), имена, города и т. д.). Это очень маленький набор данных на английском языке (1000 значений для каждой категории).
G.1.4. Конфигурирование #
У расширения есть несколько параметров, которые можно задать на уровне всего экземпляра БД (в postgresql.conf
или используя команду ALTER SYSTEM
).
Их также можно (и зачастую лучше) задать на уровне базы данных следующим образом:
ALTER DATABASE customers SET anon.algorithm = sha512;
Следующие параметры может изменить только суперпользователь:
anon.algorithm
#Метод хеширования, используемый функциями псевдонимизации. Используйте документацию pgcrypto для ознакомления со списком доступных параметров.
См.
anon.salt
, чтобы узнать, почему данный параметр является критическим для конфиденциальности данных.Тип:
text
По умолчанию:
sha256
Видимость: только для суперпользователей
anon.maskschema
#Схема (то есть пространство имён), в которой будут храниться представления динамического маскирования.
Тип:
text
По умолчанию:
mask
Видимость: для всех пользователей
anon.restrict_to_trusted_schemas
#Когда этот параметр включён (по умолчанию), правила маскирования должны определяться, используя функции, находящиеся в ограниченном списке схем. По умолчанию доверенными схемами считаются
pg_catalog
иanon
.Это повышает безопасность, не позволяя пользователям объявлять собственные фильтры маскирования.
Это также означает, что схема должна указываться явно в правилах маскирования. Например, приведённые ниже правила не сработают, потому что схема функции
lower
не объявлена.SECURITY LABEL FOR anon ON COLUMN people.name IS 'MASKED WITH FUNCTION lower(people.name)';
Правильный способ объявить эту функцию:
SECURITY LABEL FOR anon ON COLUMN people.name IS 'MASKED WITH FUNCTION pg_catalog.lower(people.name)';
Тип:
boolean
По умолчанию:
on
(вкл.)Видимость: для всех пользователей
anon.salt
#Соль (salt), которая используется функциями псевдонимизации. Очень важно задать пользовательскую соль для каждой базы данных следующим образом:
ALTER DATABASE foo SET anon.salt = 'This_Is_A_Very_Secret_Salt';
Если недоверенный пользователь сможет прочитать соль, он сможет использовать атаку полным перебором, чтобы получить исходные данные на основе следующих элементов:
Псевдонимизированные данные
Алгоритм хеширования (см.
anon.algorithm
)Значение соли
Соль и имя алгоритма хеширования должны быть защищены тем же уровнем безопасности, что и сами данные. Вот почему значение соли следует хранить непосредственно в базе данных, используя команду
ALTER DATABASE
.Тип:
text
По умолчанию: (не задан)
Видимость: только для суперпользователей
anon.sourceshema
#Схема (то есть пространство имён), в которой таблицы маскируются механизмом динамического маскирования.
Измените это значение перед запуском динамического маскирования.
ALTER DATABASE foo SET anon.sourceschema TO 'my_app';
Затем переподключитесь, чтобы изменение вступило в силу, и запустите модуль.
SELECT anon.start_dynamic_masking();
Тип:
text
По умолчанию:
public
Видимость: для всех пользователей
G.1.5. Объявление правил маскирования #
Основная идея данного расширения — предложить встроенную анонимность.
Правила маскирования данных должны быть написаны разработчиками приложения, потому что они лучше всех знают, как работает модель данных. Поэтому правила маскирования должны реализовываться непосредственно внутри схемы базы данных.
Расширение позволяет маскировать данные непосредственно внутри экземпляра Postgres Pro без использования внешнего инструмента и таким образом ограничивает раскрытие информации и уменьшает риск утечки данных.
Правила маскирования данных задаются посредством меток безопасности:
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 anon ON COLUMN player.name IS 'MASKED WITH FUNCTION anon.fake_last_name()'; SECURITY LABEL FOR anon ON COLUMN player.id IS 'MASKED WITH VALUE NULL';
Важно
Правила маскирования не наследуются. Если вы разбили таблицу на несколько секций, необходимо объявить правила маскирования для каждой секции.
G.1.5.1. Экранирование строковых литералов (констант) #
Как вы могли заметить, определения правил маскирования заключаются в одинарные кавычки. Поэтому, если в правиле маскирования нужно записать строку, экранируйте её символами доллара:
SECURITY LABEL FOR anon ON COLUMN player.name IS 'MASKED WITH VALUE $$CONFIDENTIAL$$';
G.1.5.2. Использование выражений #
Вы можете использовать более сложные выражения с синтаксисом MASKED WITH VALUE
:
SECURITY LABEL FOR anon ON COLUMN player.name IS 'MASKED WITH VALUE CASE WHEN name IS NULL THEN $$John$$ ELSE anon.random_string(LENGTH(name)) END';
G.1.5.3. Удаление правила маскирования #
Вы легко можете удалить правило маскирования следующим образом:
SECURITY LABEL FOR anon ON COLUMN player.name IS NULL;
Чтобы удалить все правила сразу, используйте оператор:
SELECT anon.remove_masks_for_all_columns();
G.1.6. Функции маскирования #
Данное расширение предоставляет функции для реализации следующих основных стратегий анонимизации:
В зависимости от ваших данных может потребоваться использовать разные стратегии для разных столбцов:
Для имён и других «прямых идентификаторов» лучше всего использовать фальсификацию.
Перестановка подходит для внешних ключей.
Добавление искажения лучше использовать для числовых значений и дат.
Частичное скрытие идеально подходит для адресов электронной почты и телефонных номеров.
G.1.6.1. Удаление #
Самый быстрый и безопасный способ анонимизировать данные — удалить их.
Во многих случаях лучший способ скрыть содержимое столбца — заменить все значения одним статическим значением.
Например, вы можете заменить весь столбец словом CONFIDENTIAL
, как указано ниже:
SECURITY LABEL FOR anon ON COLUMN users.address IS 'MASKED WITH VALUE $$CONFIDENTIAL$$';
G.1.6.2. Добавление искажения #
Этот способ также называется Отклонение. В его основе лежит «сдвиг» дат и числовых значений. Например, после применения отклонения +/- 10% к столбцу зарплаты набор данных не потеряет смысл.
-
anon.noise(
#noise_value
anyelement
,ratio
double precision
) Если параметр
ratio
= 0.33, все значения столбца будут случайным образом смещены с коэффициентом +/- 33%.-
anon.dnoise(
#noise_value
anyelement
,noise_range
interval
) Если
интервал
= «2 дня», возвращаемое значение будет исходным значением, сдвинутым случайным образом на +/- 2 дня.
Важно
Маскирующие функции noise()
уязвимы для атак повторного воспроизведения, особенно при использовании динамического маскирования. Недоверенный пользователь может угадать исходное значение, несколько раз запросив замаскированное значение, а затем просто использовать функцию avg()
, чтобы получить хорошее приближение. В целом, эти функции лучше всего подходят для выгрузки анонимизированных данных и статического маскирования, а при использовании динамического маскирования их следует избегать.
G.1.6.3. Рандомизация #
Данное расширение предоставляет большой выбор функций для генерации абсолютно случайных данных:
G.1.6.3.1. Основные случайные значения #
-
anon.random_date()
# Возвращает значение
date
.-
random_date_between(
#d1
date
,d2
date
) Возвращает значение
date
междуd1
иd2
.-
random_int_between(
#i1
integer
,i2
integer
) Возвращает значение
integer
междуi1
иi2
.-
random_bigint_between(
#b1
bigint
,b2
bigint
) Возвращает значение
bigint
междуb1
иb2
.-
anon.random_string(
#n
integer
) Возвращает значение типа
text
, содержащееn
букв.-
anon.random_zip()
# Возвращает 5-значный код.
-
anon.random_phone(
#phone_prefix
text
) Возвращает 8-значный номер телефона с префиксом
phone_prefix
.-
anon.random_in(ARRAY[1,2,3])
# Возвращает целое число между
1
и3
.-
anon.random_in(ARRAY['red','green','blue'])
# Возвращает случайное текстовое значение из массива ['red', 'green', 'blue'].
G.1.6.4. Фальсификация #
Идея фальсификации заключается в замене конфиденциальных данных случайными, но правдоподобными значениями. Цель состоит в том, чтобы избежать какой-либо идентификации по записи данных, оставляя её при этом пригодной для тестирования, анализа и обработки данных.
Предоставляются следующие функции фальсификации:
-
anon.fake_address()
# Возвращает полный почтовый адрес.
-
anon.fake_city()
# Возвращает название существующего города.
-
anon.fake_country()
# Возвращает название существующей страны.
-
anon.fake_company()
# Возвращает случайное название компании.
-
anon.fake_email()
# Возвращает действительный адрес электронной почты.
-
anon.fake_first_name()
# Возвращает случайное имя.
-
anon.fake_iban()
# Возвращает случайный действительный номер IBAN (международный номер банковского счёта).
-
anon.fake_last_name()
# Возвращает случайную фамилию.
-
anon.fake_postcode()
# Возвращает случайный действительный почтовый индекс.
Для столбцов типа text
и varchar
вы можете использовать классический генератор Lorem Ipsum:
-
anon.lorem_ipsum()
# Возвращает пять абзацев.
-
anon.lorem_ipsum(2)
# Возвращает два абзаца.
-
anon.lorem_ipsum( paragraphs := 4 )
# Возвращает четыре абзаца.
-
anon.lorem_ipsum( words := 20 )
# Возвращает 20 слов.
-
anon.lorem_ipsum( characters := LENGTH(table.column) )
# Возвращает то же количество символов, что и в исходной строке.
G.1.6.5. Расширенная фальсификация #
Генерация фальсифицированных данных — сложная тема. Представленные здесь функции ограничены базовым вариантом использования. Чтобы узнать о более продвинутых методах фальсификации, в частности, если вам нужны локализованные фальсифицированные данные, взгляните на PostgreSQL Faker, расширение, основанное на известной библиотеке Faker языка Python.
Данное расширение предоставляет расширенный механизм фальсификации с поддержкой локализации.
Например:
CREATE SCHEMA faker; CREATE EXTENSION faker SCHEMA faker; SELECT faker.faker('de_DE'); SELECT faker.first_name_female(); first_name_female ------------------- Mirja
G.1.6.6. Псевдонимизация #
Псевдонимизация аналогична фальсификации в плане создания реалистичных значений. Основное отличие состоит в том, что псевдонимизация является детерминированной: функции всегда будут возвращать одно и то же фальсифицированное значение на основе значения затравки и необязательного значения соли.
Предоставляются следующие функции псевдонимизации:
-
anon.pseudo_first_name(
#seed
anyelement
,salt
text
) Возвращает случайное имя.
-
anon.pseudo_last_name(
#seed
anyelement
,salt
text
) Возвращает случайную фамилию.
-
anon.pseudo_email(
#seed
anyelement
,salt
text
) Возвращает действительный адрес электронной почты.
-
anon.pseudo_city(
#seed
anyelement
,salt
text
) Возвращает название существующего города.
-
anon.pseudo_country(
#seed
anyelement
,salt
text
) Возвращает название существующей страны.
-
anon.pseudo_company(
#seed
anyelement
,salt
text
) Возвращает случайное название компании.
-
anon.pseudo_iban(
#seed
anyelement
,salt
text
) Возвращает случайный действительный номер IBAN (международный номер банковского счёта).
Второй аргумент (salt
) является необязательным. Вы можете вызывать каждую функцию с затравкой вида anon.pseudo_city('bob')
, без указания соли. Соль нужна, чтобы увеличить сложность и избежать атак по словарю и полным перебором. Если соль не указана, вместо неё используется случайное секретное значение соли (за подробным описанием обратитесь к разделу Универсальное хеширование).
Затравка может представлять собой любую информацию, относящуюся к субъекту. Например, можно постоянно генерировать один и тот же фальсифицированный адрес электронной почты для конкретного человека, используя его логин в качестве затравки:
SECURITY LABEL FOR anon ON COLUMN users.emailaddress IS 'MASKED WITH FUNCTION anon.pseudo_email(users.login)';
Примечание
Вы можете создавать уникальные значения, используя функцию псевдонимизации. Например, если вы хотите замаскировать столбец email
, объявленный как UNIQUE
, вам нужно инициализировать расширение с фальсифицированным набором данных, который намного больше, чем количество строк в таблице. В противном случае вы можете столкнуться с «коллизиями», то есть ситуациями, когда два разных исходных значения создают одно и то же псевдозначение.
Важно
Псевдонимизацию часто путают с анонимизацией, но на самом деле они служат двум разным целям: псевдонимизация — это способ защитить личную информацию, но псевдонимизированные данные по-прежнему «связаны» с реальными данными.
G.1.6.7. Общее хеширование #
Теоретически хеширование не является надёжным методом анонимизации, однако на практике иногда необходимо создать детерминированный хеш оригинальных данных.
Например когда пара первичный/внешний ключ является «естественным ключом», она может содержать актуальную информацию (номер клиента, содержащий дату рождения или что-то подобное).
Хеширование таких столбцов позволяет сохранить ссылочную целостность даже для относительно необычных исходных данных.
По умолчанию при инициализации расширения генерируется случайная секретная соль и используется алгоритм хеширования sha512
. Вы можете изменить эти параметры для всей базы данных, используя следующие функции:
Имейте в виду, что хеширование — это форма псевдонимизации. Это означает, что данные могут быть «деанонимизированы» с помощью хешированного значения и функции маскирования. Если злоумышленники получат доступ к этим двум элементам, они смогут идентифицировать некоторых людей, используя методы атаки brute force
(полным перебором) или dictionary
(по словарю). Поэтому соль и алгоритм, используемые для хеширования данных, нужно защищать так же надёжно, как и исходный набор данных.
В двух словах, мы рекомендуем вам использовать функцию anon.hash()
, а не anon.digest()
, потому что тогда соль не будет отображаться в правиле маскирования открытым текстом.
Кроме того, на практике хеш-функция вернёт длинную строку символов, например:
SELECT anon.hash('bob'); hash ---------------------------------------------------------------------------------------------------------------------------------- 95b6accef02c5a725a8c9abf19ab5575f99ca3d9997984181e4b3f81d96cbca4d0977d694ac490350e01d0d213639909987ef52de8e44d6258d536c55e427397
Для некоторых столбцов эта строка может быть слишком длинной, и вам, возможно, придётся вырезать некоторые части хеша, чтобы она поместилась в столбец. Например, если у вас есть внешний ключ на основе номера телефона, а столбец представляет собой varchar(12)
, вы можете преобразовать данные следующим образом:
SECURITY LABEL FOR anon ON COLUMN people.phone_number IS 'MASKED WITH FUNCTION pg_catalog.left(anon.hash(phone_number),12)'; SECURITY LABEL FOR anon ON COLUMN call_history.fk_phone_number IS 'MASKED WITH FUNCTION pg_catalog.left(anon.hash(fk_phone_number),12)';
Конечно, сокращение хеш-значения до 12 символов повысит риск «коллизии» (два разных значения имеют один и тот же фальсифицированный хеш). Решение о принятии такого риска остаётся за вами.
G.1.6.8. Частичное скрытие #
Частичное скрытие оставляет часть данных нетронутыми. Например: номер кредитной карты может быть заменён на «40XX XXXX XXXX XX96».
-
anon.partial(
#input
text
,prefix
int
,padding
text
,suffix
int
) Частично заменяет заданный текст. Например,
anon.partial('abcdefgh',1,'xxxx',3)
возвращаетaxxxxfgh
.-
anon.partial_email(
#email
text
) Частично заменяет указанный адрес электронной почты. Например,
anon.partial_email('daamien@gmail.com')
возвращаетda******@gm******.com
.
G.1.6.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, anon.generalize_int4range(zipcode,100) AS zipcode, anon.generalize_tsrange(birth,'decade') AS birth, disease FROM patient;
Анонимизированная таблица теперь выглядит так:
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
Обобщенные значения по-прежнему полезны для статистики, поскольку они остаются верными, но они менее точны и, следовательно, снижают риск идентификации.
Postgres Pro предлагает несколько диапазонных типов данных, которые идеально подходят для дат и числовых значений.
Для числовых значений доступны следующие функции:
-
generalize_int4range(
value
,step
)
generalize_int8range(
value
,step
)
generalize_numrange(
#value
,step
) где
value
— данные, которые будут обобщены, аstep
— размер каждого диапазона.
G.1.6.10. Создание собственных масок #
Вы также можете использовать свою собственную функцию в качестве маски. Функция должна быть либо деструктивной (например, частичное скрытие), либо вносить некоторые случайные значения в набор данных (например, фальсификация).
Например, если вы написали функцию foo()
внутри схемы bar
, вы можете применить её следующим образом:
SECURITY LABEL FOR anon ON SCHEMA bar IS 'TRUSTED'; SECURITY LABEL FOR anon ON COLUMN player.score IS 'MASKED WITH FUNCTION bar.foo()';
Примечание
Схема bar
должна быть объявлена суперпользователем как TRUSTED
.
G.1.6.10.1. Создание функции маскирования для столбца JSONB #
Для сложных типов данных вам, возможно, придётся создать собственную функцию. Этим приёмом придётся часто пользоваться, если вам нужно скрыть определённые части поля 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, вы можете просмотреть ключи и при необходимости заменить конфиденциальные значения.
SECURITY LABEL FOR 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(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 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 и производительности, которую вы хотите достичь.
G.1.7. Статическое маскирование #
Иногда полезно напрямую преобразовать исходный набор данных. Сделать это можно разными способами:
Все эти способы уничтожают исходные данные. Используйте их с осторожностью.
G.1.7.1. Применение правил маскирования #
Вы можете навсегда применить правила маскирования базы данных, используя функцию anon.anonymize database()
.
Рассмотрим простой пример:
CREATE TABLE customer ( id SERIAL, full_name TEXT, birth DATE, employer TEXT, zipcode TEXT, fk_shop INTEGER ); INSERT INTO customer VALUES (911,'Chuck Norris','1940-03-10','Texas Rangers', '75001',12), (312,'David Hasselhoff','1952-07-17','Baywatch', '90001',423) ; SELECT * FROM customer; id | full_name | birth | employer | zipcode | fk_shop -----+------------------+------------+---------------+---------+--------- 911 | Chuck Norris | 1940-03-10 | Texas Rangers | 75001 | 12 112 | David Hasselhoff | 1952-07-17 | Baywatch | 90001 | 423
Объявите правила маскирования:
SECURITY LABEL FOR anon ON COLUMN customer.full_name IS 'MASKED WITH FUNCTION anon.fake_first_name() || '' '' || anon.fake_last_name()'; SECURITY LABEL FOR anon ON COLUMN customer.employer IS 'MASKED WITH FUNCTION anon.fake_company()'; SECURITY LABEL FOR anon ON COLUMN customer.zipcode IS 'MASKED WITH FUNCTION anon.random_zip()';
Замените исходные данные в замаскированных столбцах:
SELECT anon.anonymize_database(); SELECT * FROM customer; id | full_name | birth | employer | zipcode | fk_shop -----+-------------+------------+---------------------+---------+--------- 911 | Jesse Kosel | 1940-03-10 | Marigold Properties | 62172 | 12 312 | Leolin Bose | 1952-07-17 | Inventure | 20026 | 423
Вы также можете использовать функции anonymize_table()
и anonymize_column()
для удаления данных из подмножества строк базы данных:
SELECT anon.anonymize_table('customer'); SELECT anon.anonymize_column('customer','zipcode');
Важно
Статическое маскирование — медленный процесс. Принцип статического маскирования заключается в обновлении всех строк всех таблиц, содержащих хотя бы один маскируемый столбец. В целом это означает, что сервер перезапишет все данные на диске. В зависимости от размера базы данных, аппаратного обеспечения и конфигурации экземпляра может оказаться быстрее экспортировать анонимизированные данные (см. Выгрузка анонимизированных данных) и повторно загрузить их в базу данных.
G.1.7.2. Перестановка #
Перестановка перемешивает значения в пределах столбца.
-
anon.shuffle_column(
#shuffle_table
regclass
,shuffle_column
name
,primary_key
name
) Переставляет все значения в заданном столбце. Вам необходимо указать первичный ключ таблицы.
Этот способ полезен для внешних ключей, поскольку будет сохранена ссылочная целостность.
Важно
shuffle_column()
не является маскирующей функцией, потому что она работает «вертикально»: она изменит все значения столбца сразу.
G.1.7.3. Добавление искажения в столбец #
Есть также некоторые функции, которые могут добавить искажение во все значения столбца:
-
anon.add_noise_on_numeric_column(
#table
regclass
,column
text
,ratio
float
) Если параметр
ratio
= 0.33, все значения столбца будут случайным образом смещены с коэффициентом +/- 33%.-
anon.add_noise_on_datetime_column(
#table
regclass
,column
text
,interval
interval
) Если параметр
interval
=2 дня
, все значения столбца будут случайным образом сдвинуты на +/- 2 дня.
Важно
Эти функции искажения уязвимы для атак повторного воспроизведения.
G.1.8. Динамическое маскирование #
Вы можете скрыть некоторые данные от роли, объявив эту роль как «MASKED». Другие роли по-прежнему будут иметь доступ к исходным данным.
CREATE TABLE people (id TEXT, firstname TEXT, lastname TEXT, phone TEXT); INSERT INTO people VALUES ('T1','Sarah', 'Connor','0609110911'); SELECT * FROM people; SELECT * FROM people; id | firstname | lastname | phone ----+----------+----------+------------ T1 | Sarah | Connor | 0609110911 (1 row)
Активируйте механизм динамического маскирования:
SELECT anon.start_dynamic_masking();
Объявите недоверенного пользователя:
CREATE ROLE skynet LOGIN; SECURITY LABEL FOR anon ON ROLE skynet IS 'MASKED';
Объявите правила маскирования:
SECURITY LABEL FOR anon ON COLUMN people.lastname IS 'MASKED WITH FUNCTION anon.fake_last_name()'; SECURITY LABEL FOR anon ON COLUMN people.phone IS 'MASKED WITH FUNCTION anon.partial(phone,2,$$******$$,2)';
Подключитесь к серверу от имени недоверенного пользователя:
\c - skynet SELECT * FROM people; id | firstname | lastname | phone ----+----------+-----------+------------ T1 | Sarah | Stranahan | 06******11 (1 row)
G.1.8.1. Изменение типа замаскированного столбца #
Когда активировано динамическое маскирование, запрещено изменять тип данных столбца, если на нём есть маска.
Чтобы изменить замаскированный столбец, вам нужно временно отключить механизм маскирования следующим образом:
BEGIN; SELECT anon.stop_dynamic_masking(); ALTER TABLE people ALTER COLUMN phone TYPE VARCHAR(255); SELECT anon.start_dynamic_masking(); COMMIT;
G.1.8.2. Удаление замаскированной таблицы #
Механизм динамического маскирования будет строить маскирующие представления для замаскированных таблицах. Это означает, что невозможно напрямую удалить замаскированную таблицу. Вы получите такую ошибку:
DROP TABLE people; psql: ERROR: cannot drop table people because other objects depend on it DETAIL: view mask.company depends on table people
Для эффективного удаления таблицы необходимо добавить параметр CASCADE
, чтобы маскирующее представление тоже удалялось:
DROP TABLE people CASCADE;
G.1.8.3. Снятие маски с роли #
Просто удалить защитную метку можно следующим образом:
SECURITY LABEL FOR anon ON ROLE bob IS NULL;
Сделать сразу все недоверенные роли обычными можно так:
SELECT anon.remove_masks_for_all_roles();
G.1.8.4. Ограничения #
G.1.8.4.1. Отображение списка таблиц #
Из-за особенностей работы механизма динамического маскирования, если недоверенная роль попытается отобразить таблицы в psql с помощью команды \dt
, psql не покажет никаких таблиц.
Это связано с тем, что расширение подменяет search_path
для недоверенных ролей.
Вы можете попробовать явно добавить схему, которую хотите найти, например:
\dt *.* \dt public.*
G.1.8.4.2. Поддержка только одной схемы #
Система динамического маскирования работает только с одной схемой (по умолчанию — public
). Когда вы запускаете механизм маскирования с помощью start_dynamic_masking()
, вы можете указать схему, которая будет маскироваться, следующим образом:
ALTER DATABASE foo SET anon.sourceschema TO 'sales';
Затем откройте новый сеанс с базой данных и введите:
SELECT start_dynamic_masking();
Однако статическое маскирование функцией anon.anonymize()
и анонимный экспорт функцией anon.dump()
могут работать с несколькими схемами.
G.1.8.4.3. Производительность #
Известно, что динамическое маскирование работает очень медленно с некоторыми запросами, особенно при попытке соединить две таблицы по замаскированному ключу, используя хеширование или псевдонимизацию.
G.1.8.4.4. Графические инструменты #
Когда вы используете недоверенную роль с графическим интерфейсом, таким как DBeaver или pgAdmin, панель data
может выдавать следующую ошибку при попытке отобразить содержимое замаскированной таблицы с именем foo
:
SQL Error [42501]: ERROR: permission denied for table foo
Это связано с тем, что большинство этих инструментов будут напрямую запрашивать таблицу public.foo
вместо того, чтобы «перенаправлять» механизм маскирования на представление mask.foo
.
Чтобы просмотреть замаскированные данные с помощью графического инструмента, вы можете:
Открыть панель запросов SQL и ввести SELECT * FROM foo
Перейти к Database > Schemas > mask > Views > foo
G.1.9. Выгрузка анонимизированных данных #
Из-за особенностей, заложенных при создании данного расширения, недоверенные пользователи не могут использовать pg_dump. Если вы хотите экспортировать всю базу данных с анонимизированными данными, вы должны использовать скрипт pg_dump_anon.sh
.
G.1.9.1. pg_dump_anon.sh #
Скрипт pg_dump_anon.sh
поддерживает большинство параметров обычной команды pg_dump
. Также поддерживаются переменные среды (PGHOST
, PGUSER
и т. д.) и файлы .pgpass
.
G.1.9.2. Пример #
Пользователь с именем bob
может экспортировать анонимный архив базы данных app
следующим образом:
/opt/pgpro/ent-17/bin/pg_dump_anon.sh -h localhost -U bob --password --file=anonymous_dump.sql app
Важно
Имя базы данных должно быть последним параметром.
Чтобы получить дополнительные сведения о поддерживаемых параметрах, просто введите ./pg_dump_anon.sh --help
.
G.1.9.3. Ограничения #
Пароль пользователя запрашивается автоматически. Это означает, что вы должны либо добавить параметр
--password
, чтобы задавать его интерактивно, либо объявить его в переменнойPGPASSWORD
, либо поместить его в файл.pgpass
(однако в Windows переменнаяPGPASSFILE
должна быть указана явно)Единственным поддерживаемым форматом является
plain
. Другие форматы (custom
,dir
иtar
) не поддерживаются.
G.1.10. Обобщение #
G.1.10.1. Снижение точности конфиденциальных данных #
Идея обобщения состоит в том, чтобы заменить данные более широкими и менее точными значениями. Например, вместо «Бобу 28 лет» можно сказать «Бобу от 20 до 30 лет». Это полезно для аналитики, потому что данные остаются верными, избегая при этом риска идентификации.
Обобщение — это способ достижения k-анонимности.
Postgres Pro может легко использовать обобщение благодаря диапазонным типам данных, которые являются очень эффективным способом хранения и работы с набором значений, содержащихся между нижней и верхней границей.
G.1.10.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, anon.generalize_int4range(zipcode,1000) AS zipcode, 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)
G.1.10.3. Функции обобщения #
Расширение pgpro_anonymizer
предоставляет функции обобщения для всех встроенных диапазонных типов. Как правило, эти функции принимают маскируемое значение в качестве первого параметра, а второй параметр является длиной шага.
Для числовых значений:
-
anon.generalize_int4range(
#value
integer
,step
integer
) Например,
anon.generalize_int4range(42,5)
возвращает диапазон[40,45)
.-
anon.generalize_int8range(
#value
integer
,step
integer
) Например,
anon.generalize_int8range(12345,1000)
возвращает диапазон[12000,13000)
.-
anon.generalize_numrange(
#value
integer
,step
integer
) Например,
anon.generalize_numrange(42.32378,10)
возвращает диапазон[40,50)
.
Для значений времени:
-
anon.generalize_tsrange(
#value
integer
,step
integer
) Например,
anon.generalize_tsrange('1904-11-07','year')
возвращает['1904-01-01','1905-01-01')
.-
anon.generalize_tstzrange(
#value
integer
,step
integer
) Например,
anon.generalize_tstzrange('1904-11-07','week')
возвращает['1904-11-07','1904-11-14')
.-
anon.generalize_daterange(
#value
integer
,step
integer
) Например,
anon.generalize_daterange('1904-11-07','decade')
возвращает[1900-01-01,1910-01-01)
.
Возможные значения шагов: микросекунда, миллисекунда, секунда, минута, час, день, неделя, месяц, год, десятилетие, столетие и тысячелетие.
G.1.10.4. Ограничения #
G.1.10.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
В этой таблице мы видим, что у конкретного сотрудника очень высокая зарплата, гораздо выше средней. Следовательно, этот человек, вероятно, является генеральным директором компании.
При обобщении это важно, поскольку размер диапазона («шаг») должен быть достаточно широким, чтобы предотвратить идентификацию отдельного человека.
Достичь этого можно посредством реализации принципа k-анонимности.
G.1.10.4.2. Обобщение несовместимо с динамическим маскированием #
По определению, при обобщении данные остаются верными, но изменяется тип столбца.
Это означает, что преобразование не является прозрачным, и поэтому его нельзя использовать с динамическим маскированием.
G.1.10.5. k-анонимность #
k-анонимность — это стандартный отраслевой термин, используемый для описания свойства анонимизированного набора данных. Принцип k-анонимности гласит, что в заданном наборе данных любого анонимизированного человека нельзя отличить как минимум от k-1
других людей. Другими словами, k-анонимность можно описать как гарантию «укрытия в толпе». Низкое значение k
увеличивает риск идентификации на основании связи с другими источниками данных.
-
anon.k_anonymity(
#id
regclass
) Вы можете оценить k-фактор анонимности таблицы следующим образом:
Определите столбцы, которые являются косвенными идентификаторами (также известными как квазиидентификаторы), например:
SECURITY LABEL FOR anon ON COLUMN patient.firstname IS 'INDIRECT IDENTIFIER'; SECURITY LABEL FOR anon ON COLUMN patient.zipcode IS 'INDIRECT IDENTIFIER'; SECURITY LABEL FOR anon ON COLUMN patient.birth IS 'INDIRECT IDENTIFIER';
После определения косвенных идентификаторов:
SELECT anon.k_anonymity('generalized_patient')
Чем выше значение, тем лучше.
G.1.11. Производительность #
Любой процесс анонимизации имеет свою цену, состоящую из процессорного времени, места в оперативной памяти и, возможно, большого количества дисковых операций ввода-вывода. Ниже приведён краткий обзор в зависимости от того, какую стратегию вы используете.
В двух словах, эффективность анонимизации в основном будет зависеть от следующих важных факторов:
Размера базы данных
Количества правил маскирования
G.1.11.1. Статическое маскирование #
По сути, статическое маскирование полностью перезаписывает замаскированные таблицы на диске. Это может занимать много времени в зависимости от вашей среды. И во время этого процесса таблицы будут заблокированы.
Примечание
В этом случае стоимость анонимизации «оплачивается» всеми пользователями, но оплачивается раз и навсегда.
G.1.11.2. Динамическое маскирование #
При динамическом маскировании реальные данные заменяются «на лету» каждый раз, когда недоверенный пользователь отправляет запрос в базу данных. Это означает, что у недоверенных пользователей будет более медленный отклик, чем у обычных (доверенных). Обычно это нормально, потому что недоверенные пользователи как правило считаются менее важными, чем обычные.
Если вы примените к таблице три или четыре правила, время отклика для недоверенных пользователей должно быть примерно на 20-30% больше, чем для обычных.
Поскольку правила маскирования применяются для каждого запроса со стороны недоверенных пользователей, динамическое маскирование уместно, когда у вас есть ограниченное количество недоверенных пользователей, которые подключаются к базе данных только время от времени. Например, аналитик данных подключается раз в неделю для создания бизнес-отчёта.
Если есть несколько недоверенных пользователей или если недоверенный пользователь очень активен, рекомендуется один раз в неделю экспортировать замаскированные данные на дополнительный экземпляр и позволить этим пользователям подключаться к этому дополнительному экземпляру.
Примечание
В этом случае стоимость анонимизации «оплачивается» только недоверенными пользователями.
G.1.11.3. Выгрузка анонимизированных данных #
Если процесс резервного копирования вашей базы данных занимает один час с использованием pg_dump
, то анонимизация и экспорт всей базы данных с использованием pg_dump_anon.sh
ориентировочно займёт два часа.
Примечание
В этом случае стоимость анонимизации «оплачивает» пользователь, запрашивающий анонимизированный экспорт. Другие пользователи базы данных не будут затронуты.
G.1.11.4. Ускорение процесса #
G.1.11.4.1. Используйте MASKED WITH VALUE
, когда это возможно #
Заменить исходные данные статическим значением всегда быстрее, чем вызывать функции маскирования.
G.1.11.4.2. Материализованные представления #
Динамическое маскирование требуется не всегда. В некоторых случаях более эффективно создавать материализованные представления.
Например:
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;
G.1.12. Безопасность #
G.1.12.1. Разрешения #
Ниже приведён обзор возможных действий пользователей в зависимости от имеющихся у них прав:
Таблица G.1. Права
Действие | Суперпользователь | Владелец | Недоверенная роль |
---|---|---|---|
Создать расширение | Да | ||
Удалить расширение | Да | ||
Инициализировать расширение | Да | ||
Сбросить параметры расширения | Да | ||
Конфигурировать расширение | Да | ||
Сделать роль недоверенной | Да | ||
Начать динамическое маскирование | Да | ||
Остановить динамическое маскирование | Да | ||
Создать таблицу | Да | Да | |
Объявить правило маскирования | Да | Да | |
Добавить, удалить, изменить строку | Да | Да | |
Использовать статическое маскирование | Да | Да | |
Получить реальные данные | Да | Да | |
Выгружать данные без анонимизации | Да | Да | |
Выгружать анонимизированные данные | Да | Да | |
Использовать маскирующие функции | Да | Да | Да |
Получить замаскированные данные | Да | Да | Да |
Посмотреть правила маскирования | Да | Да | Да |
G.1.12.2. Ограничение маскирующих фильтров доверенными схемами #
Владельцам баз данных разрешено объявлять правила маскирования. Они также могут создавать функции, содержащие произвольный код, и использовать эти функции внутри правил маскирования. В определённых обстоятельствах владелец базы данных может «обмануть» суперпользователя, заставив его запросить замаскированную таблицу и тем самым выполнить произвольный код.
Чтобы предотвратить это, суперпользователи могут настроить следующий параметр:
anon.restrict_to_trusted_schemas = on
С этим параметром владелец базы данных сможет писать правила маскирования только с функциями, расположенными в доверенных схемах, которые контролируются суперпользователями.
G.1.12.3. Контекст безопасности функций #
Большинство функций данного расширения объявлены с тегом SECURITY INVOKER
. Это означает, что эти функции выполняются с правами пользователя, который их вызывает. Это важное ограничение.
Данное расширение содержит ещё несколько функций, объявленных с использованием тега SECURITY DEFINER
.