Обфускация данных#

Под обфускацией понимается маскирование с деперсонификацией данных. Выделенные SECURITY LABEL-метками данные, скопированные в другую схему базы данных могут быть представлены как таблицами, так и представлениями.

Описываемое решение основано на свободно распространяемом расширении postgresql_anonymizer с доработками команды Pangolin. Функциональность позволяет маскировать данные непосредственно внутри экземпляра СУБД без использования внешнего инструмента. Реальные данные заменяются каждый раз, когда утилита выгрузки данных обращается к СУБД. Функциональность позволяет реализовать обфускацию атрибутов отношений (столбцов) применительно к набору ролей базы данных.

Поддерживается использование узла реплики в качестве источника для экспорта.

Для выгрузки и записи дампа используются стандартные инструменты СУБД – pg_dump, pg_dumpall, pg_restore, psql, поддерживающие:

  • форматы дампа: plain, custom, dir и tar;

  • выгрузку данных в несколько потоков.

Реализованные методы маскирования#

Методы статического маскирования:

  • искажение (добавление погрешности в атрибуты с итерируемыми типами данных);

  • перестановка (рандомизация значений атрибутов в пределах отношения).

Методы динамического маскирования:

  • Удаление атрибута.

  • Подмена атрибута константой (например, CONFIDENTIAL).

  • Частичное маскирование (например, номер телефона в виде 79** *** ** 88).

  • Искажение (применяется только для числовых типов и типов, основанных на дате/времени. Подменяемое значение генерируется в пределах определенного пользователем диапазона, относительно исходного значения).

  • Обобщение (применяется для числовых значений и типов, основанных на дате/времени. Подменяемое значение генерируется как диапазон, содержащий исходное значения. Начало диапазона определяется как целое от деления исходного значения на ширину диапазона).

  • Рандомизация (генерация случайного значения атрибута).

  • Фальсификация (выбор случайного значения из таблицы подстановок).

  • Псевдономизация (алгоритм предполагает вычисление хеша исходного значения с учетом соли (salted hash) с последующим выбором фальсифицированного значения на основе остатка от деления хеша на размер таблицы подстановок. Поддерживаются все атрибуты, перечисленные в алгоритме фальсификации, а также генератор текста Lorem Ipsum определенной длины).

  • Хеширование текстовых данных (алгоритм предполагает вычисление хеша исходного значения с учетом соли (salted hash). Метод не является криптографически устойчивым для словарных атак).

В функциональности предусмотрена генерация следующих атрибутов:

  • целое значение в пределах заданного диапазона;

  • дата;

  • дата в указанном диапазоне;

  • целое число в указанном диапазоне;

  • хеш текста;

  • значение массива;

  • интервал времени;

  • интервал целого числа;

  • ENUM-значение;

  • ИНН (юридического лица, физического лица) с проверкой контрольной суммы;

  • ОГРН (юридического лица или индивидуального предпринимателя) с проверкой контрольной суммы;

  • номер телефона;

  • СНИЛС;

  • строка;

  • почтовый индекс.

В решении реализована фальсификация следующих атрибутов:

  • адрес;

  • город;

  • компания (юридическое или физическое лицо);

  • страна (полное название);

  • e-mail;

  • ФИО (допускается маска, позволяющая выбрать комбинацию фамилии, имени, отчества в любом составе и порядке. Возможна отдельная генерация фамилии, имени, отчества отдельно из более полного набора подстановок);

  • почтовый индекс;

  • SIRET;

  • IBAN;

  • СНИЛС;

  • ИНН;

  • ОГРН;

  • СНИЛС.

Важная информация:

  1. При выборе алгоритма маскирования следует обращать внимание на типизацию атрибута. Например, не следует использовать подмену константой CONFIDENTIAL для атрибута с типом timestamp.

  2. Алгоритм «Смещение» не является статистически устойчивым. Многократное применение алгоритма к одному и тому же значению позволяет предсказать искомое значение, как среднее по выборке.

  3. Алгоритмы «Смещение» и «Обобщение» предполагают применение смещения к исходному значению. При этом, применение отклонения к таким значениям как: NULL, NaN, +Infinity, -Infinity и т.п. фактически не изменят исходное значение и не приводит к ошибке.

  4. Алгоритм «Обобщение» не совместим с функциональностью снятия логических дампов, поскольку меняет тип данных атрибута.

  5. Алгоритм «Перестановка» является необратимым и приводит к фактическому изменению значений атрибута. Ценность алгоритма в сохранении ссылочной целостности.

  6. Алгоритмы «Рандомизация», «Фальсификация» используют предопределенные значения для локалей en_US, fr_FR, ru_RU. Выбор локали осуществляется перед инициализацией расширения. Допускается дополнение списков элементов для этих алгоритмов. Кроме того, в составе решения поставляется пример генератора данных на основе библиотеки Faker.

  7. Алгоритмы маскирования, определенные для партиционированных таблиц не наследуются от партиционированной таблицы к секциям.

  8. При выгрузке данных в логический дамп, создание метки SECURITY LABEL для отношений происходит до загрузки данных в таблицу. Валидация функции анонимизации на атрибут происходит в момент создания метки для атрибута, что может привести к ситуации, когда тип данных в таблице не соответствует как типу аргументов маскирующей функции, так и ее результату.

Конфигурационные параметры#

Параметр

Значение по умолчанию

Контекст

Описание

anon.algorithm

sha256

superuser

Алгоритм хеширования, применяемый методом псевдонимизации. Поддерживаются md5, sha1, sha224, sha256, sha384 и sha512

anon.allow_constraints_masking

false

superuser

Флаг, допускающий обфускацию для атрибутов, входящих в ограничение целостности

anon.default_locale

en_US

superuser

Локаль инициализации таблиц подстановок, применяемых в методах фальсификации и псевдонимизации

anon.k_anonymity_provider

k_anonymity

superuser

Имя SECURITY PROVIDER, используемого для метода K-Anonimity

anon.masking_policies

anon

superuser

Схема БД, используемая расширением обфускации данных и содержащая таблицы и функции подстановок

anon.maskschema

mask

superuser

Схема БД, используемая для генерации анонимизированных данных

anon.privacy_by_default

off

superuser

Выбор стратегии маскирования атрибутов, не имеющих метки SECURITY LABEL. При включении параметра - для всех атрибутов, имеющих значения значения по умолчанию - будут использоваться они. Для атрибутов, допускающих NULL - подставлен NULL

anon.restrict_to_trusted_schemas

on

superuser

Признак использования функций маскирования, расположенных только в схемах с атрибутом TRUSTED

anon.salt

superuser

Соль, используемая для метода псевдонимизации

anon.sourceschema

public

superuser

Схема БД по умолчанию, содержащая исходные данные. В случае использования нескольких схем - каждая из них должна быть помечена атрибутом TRUSTED

anon.strict_mode

on

superuser

Требования соответствия типа подстановочного значения типу исходного значения

anon.transparent_dynamic_masking

off

superuser

Режим динамического маскирования

Описание объектов#

Таблицы подстановок (для использования в алгоритмах фальсификации и псевдонимизации)

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

anon.address

-

-

Адрес

anon.city

-

-

Город

anon.company

-

-

Компания

anon.country

-

-

Страна

anon.email

-

-

e-mail

anon.first_name

-

-

Имя физического лица

anon.iban

-

-

IBAN

anon.identifier

-

-

Идентификатор атрибута для автоматической разметки БД в целях анонимизации

anon.identifiers_category

-

-

Категория идентификатора для автоматической разметки БД в целях анонимизации

anon.inn

-

-

ИНН

anon.last_name

-

-

Фамилия физического лица

anon.lorem_ipsum

-

-

Текстовые данные для генератора текстовых данных

anon.middle_name

-

-

Отчество физического лица

anon.ogrn

-

-

ОГРН

anon.personal_name

-

-

ФИО физического лица

anon.postcode

-

-

Почтовый индекс

anon.siret

-

-

SIRET

anon.snils

-

-

СНИЛС

Представления

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

pg_identifiers

-

attrelid::oid, attnum::integer, relname::name, attname::name, format_type::text, col_description::text, indirect_identifier::boolean, priority::integer

Список автоматически созданных правил маскировки, при использовании функции anon.detect()

pg_masked_roles

-

rolname::name, rolsuper::boolean, rolinherit::boolean, rolcreaterole::boolean, rolcreatedb::boolean, rolcanlogin::boolean, rolreplication::boolean, rolconnlimit::integer, rolpassword::text, rolvaliduntil::timestamp with time zone, rolbypassrls::boolean, rolconfig::text[], oid::oid, grace_period::interval, grace_period_source::text, grace_time_left::interval, rolprevpassword::text, hasmask::boolean

Список ролей инстанса, дополненный признаком маскирования роли

pg_masking_rules

-

attrelid::oid, attnum::integer, relnamespace::regnamespace, relname::name, attname::name, format_type::text, col_description::text, masking_function::text, masking_value::text, priority::integer, masking_filter::text, trusted_schema::boolean

Список правил маскировки

pg_masks

-

attrelid::oid, attnum::integer, relnamespace::regnamespace, relname::name, attname::name, format_type::text, col_description::text, masking_function::text, masking_value::text, priority::integer, masking_filter::text, trusted_schema::boolean

Спиоск правил маскировки (deprecated)

Функции и процедуры. Динамическое маскирование. Частичное маскирование

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.partial

ov text, prefix integer, padding text, suffix integer

text

Частичное маскирование текстовых данных

FUNCTION anon.partial_email

ov text

text

Частичное маскирование e-mail

Функции и процедуры. Динамическое маскирование. Искажение

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.noise

noise_value anyelement, ratio double precision

anyelement

Искажение с числовым интервалом

FUNCTION anon.dnoise

noise_value anyelement, noise_range interval

anyelement

Искажение с интервалом дата/время

Функции и процедуры. Динамическое маскирование. Обобщение

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.generalize_daterange

val date, step text DEFAULT „decade“::text

daterange

Обобщение для типа данных date

FUNCTION anon.generalize_int4range

val integer, step integer DEFAULT 10

int4range

Обобщение для типа данных integer

FUNCTION anon.generalize_int8range

val bigint, step bigint DEFAULT 10

int8range

Обобщение для типа данных bigint

FUNCTION anon.generalize_numrange

val numeric, step integer DEFAULT 10

numrange

Обобщение для типа данных numeric

FUNCTION anon.generalize_tsrange

val timestamp without time zone, step text DEFAULT „decade“::text

tsrange

Обобщение для типа данных timestamp

FUNCTION anon.generalize_tstzrange

val timestamp with time zone, step text DEFAULT „decade“::text

tstzrange

Обобщение для типа данных timestamptz

Функции и процедуры. Динамическое маскирование. Рандомизация

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.random_bigint_between

int_start bigint, int_stop bigint

bigint

Рандомизация для типа данных bigint

FUNCTION anon.random_date

-

timestamp with time zone

Генерация случайной даты

FUNCTION anon.random_date_between

date_start timestamp with time zone, date_end timestamp with time zone

timestamp with time zone

Генерация случайной даты в диапазоне

FUNCTION anon.random_hash

seed text

text

Генерация случайного хеша

FUNCTION anon.random_in

a anyarray

anyelement

Генерация случайного значения из массива

FUNCTION anon.random_in_daterange

r daterange

date

Генерация случайной даты из диапазона

FUNCTION anon.random_in_enum

element anyelement

anyelement

Генерация случайного значения ENUM

FUNCTION anon.random_in_int4range

r int4range

integer

Генерация случайного значения в диапазоне integer

FUNCTION anon.random_in_int8range

r int8range

bigint

Генерация случайного значения в диапазоне bigint

FUNCTION anon.random_in_numrange

r numrange

numeric

Генерация случайного значения в диапазоне numeric

FUNCTION anon.random_in_tsrange

r tsrange

timestamp without time zone

Генерация случайного значения в диапазоне tsrange

FUNCTION anon.random_in_tstzrange

r tstzrange

timestamp with time zone

Генерация случайного значения в диапазоне tstzrange

FUNCTION anon.random_inn10

-

text

Генерация случайного значения ИНН юридического лица (с вычислением контрольной суммы)

FUNCTION anon.random_inn12

-

text

Генерация случайного значения ИНН физического лица (с вычислением контрольной суммы)

FUNCTION anon.random_int_between

int_start integer, int_stop integer

integer

Генерация случайного значения в диапазоне integer

FUNCTION anon.random_ogrn

-

text

Генерация случайного значения ОГРН юридического лица (с вычислением контрольной суммы)

FUNCTION anon.random_ogrnip

-

text

Генерация случайного значения ОГРН физического лица (с вычислением контрольной суммы)

FUNCTION anon.random_phone

phone_prefix text DEFAULT „0“::text

text

Генерация случайного значения телефонного номера с заданным префиксом

FUNCTION anon.random_snils

-

text

Генерация случайного значения СНИЛС физического лица (с вычислением контрольной суммы)

FUNCTION anon.random_string

l integer

text

Генерация случайного значения слова заданной длины (заглавная латиница + цифры)

FUNCTION anon.random_zip

-

text

Генерация случайного значения почтового индекса

FUNCTION anon.lorem_ipsum

paragraphs integer DEFAULT 5, words integer DEFAULT 0, characters integer DEFAULT 0

text

Генерация случайного значения текста заданной длины

Функции и процедуры. Динамическое маскирование. Фальсификация

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.fake_address

-

text

Фальсифицированный адрес

FUNCTION anon.fake_city

-

text

Фальсифицированный город

FUNCTION anon.fake_company

-

text

Фальсифицированная компания

FUNCTION anon.fake_country

-

text

Фальсифицированная страна

FUNCTION anon.fake_email

-

text

Фальсифицированный e-mail

FUNCTION anon.fake_first_name

-

text

Фальсифицированное имя

FUNCTION anon.fake_iban

-

text

Фальсифицированный IBAN

FUNCTION anon.fake_inn

-

text

Фальсифицированный ИНН

FUNCTION anon.fake_last_name

-

text

Фальсифицированная фамилия

FUNCTION anon.fake_middle_name

-

text

Фальсифицированное отчество

FUNCTION anon.fake_ogrn

-

text

Фальсифицированный ОГРН

FUNCTION anon.fake_personal_name

mask text DEFAULT „%1$s %2$s %3$s“::text

text

Фальсифицированное ФИО (по маске, где: %1 - Имя, %2 - Фамилия, %3 - Отчество )

FUNCTION anon.fake_postcode

-

text

Фальсифицированный почтовый индекс

FUNCTION anon.fake_siret

-

text

Фальсифицированный SIRET

FUNCTION anon.fake_snils

-

text

Фальсифицированный СНИЛС

Динамическое маскирование. Псевдономизация

Для всего блока:

  • seed - исходное значение;

  • salt - соль. Если не задана- используется GUC anon.salt. иначе - null.

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.pseudo_address

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный адрес

FUNCTION anon.pseudo_city

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный город

FUNCTION anon.pseudo_company

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированная компания

FUNCTION anon.pseudo_country

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированная страна

FUNCTION anon.pseudo_email

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный e-mail

FUNCTION anon.pseudo_first_name

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированное имя

FUNCTION anon.pseudo_iban

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный IBAN

FUNCTION anon.pseudo_inn

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный ИНН

FUNCTION anon.pseudo_last_name

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированная фамилия

FUNCTION anon.pseudo_middle_name

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированное отчество

FUNCTION anon.pseudo_ogrn

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный ОГРН

FUNCTION anon.pseudo_personal_name

seed anyelement, salt text DEFAULT NULL::text, mask text DEFAULT „%1$s %2$s %3$s“::text

text

Псевдономизированное ФИО (по маске, где: %1 - Имя, %2 - Фамилия, %3 - Отчество)

FUNCTION anon.pseudo_postcode

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный почтовый индекс

FUNCTION anon.pseudo_siret

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный SIRET

FUNCTION anon.pseudo_snils

seed anyelement, salt text DEFAULT NULL::text

text

Псевдономизированный СНИЛС

Динамическое маскирование. Хеширование текстовых данных

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.hash

seed text

text

Хеш текста

FUNCTION anon.digest

val text, salt text, algorithm text

text

Хеш текста (с выбором соли и алгоритма)

Статическое маскирование. Перестановка

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.shuffle_column

shuffle_table regclass, shuffle_column name, primary_key name

boolean

Генерация колонки с перестановкой элементов

Статическое маскирование. Искажение

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.add_noise_on_datetime_column

noise_table regclass, noise_column text, variation interval

boolean

Искажение временного т

FUNCTION anon.add_noise_on_numeric_column

noise_table regclass, noise_column text, ratio double precision

boolean

Искажение числового типа

Статическое маскирование. Статическое маскирование. Реализация правил динамического маскирования в статическом режиме

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.anonymize_column

tablename regclass, colname name

boolean

Маскирование атрибута

FUNCTION anon.anonymize_database

-

boolean

Маскирование базы данных

FUNCTION anon.anonymize_table

tablename regclass

boolean

Маскирование таблицы

Служебные функции и процедуры

Для всего блока приводятся описания только объектов, предназначенных для пользовательского пространства.

Объект

Аргументы, атрибуты

Возвращаемое значение

Описание

FUNCTION anon.build_anonymize_column_assignment

tablename regclass, colname name

text

Создание маппинга имя колонки = замена

FUNCTION anon.column_exists

table_relid regclass, column_name name

boolean

Аналог функции concat. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.concat

text, text

text

Аналог функции date_add. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_add

timestamp with time zone, interval

timestamp with time zone

Аналог функции date_part. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_part

text, timestamp without time zone

double precision

Аналог функции date_part. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_part

text, interval

double precision

Аналог функции date_subtract. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_subtract

timestamp with time zone, interval

timestamp with time zone

Аналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_trunc

text, timestamp without time zone

timestamp without time zone

Аналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_trunc

text, interval

interval

Аналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.date_trunc

text, timestamp with time zone, text

timestamp with time zone

Автодетектирование масок анонимизации для предопределенной локали. Используются атрибуты всех отношений текущей БД, за исключением служебных схем и схемы, определенной GUC anon.maskschema. Поиск ведется по соответствию имени атрибута, локали в anon.identifier. Категория и маскирующая функция определяется по anon.identifier_category

FUNCTION anon.detect

dict_lang text DEFAULT „en_US“::text

TABLE(table_name regclass, column_name name, identifiers_category text, direct boolean)

-

FUNCTION anon.get_attrdef

relid integer, num integer

text

-

FUNCTION anon.get_function_schema

text

text

-

FUNCTION anon.get_relname

t text

text

-

FUNCTION anon.get_schema

t text

text

-

FUNCTION anon.get_tablesample_ratio

relid oid

text

-

FUNCTION anon.hasmask

role regrole, masking_policy text DEFAULT „anon“::text

boolean

-

FUNCTION anon.hex_to_int

hexval text

integer

-

FUNCTION anon.init

-

boolean

Инициализация расширения. Локаль инициализации должна быть выбрана перед запуском. При инициализации загружаются таблицы псевдонимизации, случайно генерируются заполнение таблиц ИНН, ОГРН, СНИЛС При инициализации случайно выбираются до 2500 значений ФИО по соответствию пола

FUNCTION anon.init

datapath text

boolean

Инициализация расширения с возможностью задания собственного пути расположения csv-данных таблиц псевдонимизации

FUNCTION anon.init_masking_policies

-

boolean

-

FUNCTION anon.is_initialized

-

boolean

-

FUNCTION anon.k_anonymity

relid regclass

integer

Получение идентификаторов K-Anonymity для отношения. Процедура производит оценку стоийкости идентификаторов для отношения

FUNCTION anon.»left»

text

text

Аналог функции left. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.load

-

boolean

-

FUNCTION anon.load

text

boolean

-

FUNCTION anon.load_csv

dest_table regclass, csv_file text

boolean

-

FUNCTION anon.lower

text

text

Аналог функции lower. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.make_date

integer, integer, integer

date

Аналог функции make_date. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.mask_columns

source_relid oid

TABLE(attname name, masking_filter text, format_type text)

-

FUNCTION anon.mask_create_view

relid oid

boolean

Создание маскированного представления для динамических методов

FUNCTION anon.mask_drop_view

relid oid

boolean

Удаление маскированного представления для динамических методов

FUNCTION anon.mask_filters

relid oid

text

Формирования тела запроса, используемого для mask_create_view

FUNCTION anon.mask_role

maskedrole regrole

boolean

Действия, накладываемые на маскируюмую роль. Изменение функции может быть полезно в случае использования собственной ролевой модели

FUNCTION anon.mask_select

relid oid

text

Формирование SELECT-запроса для динамических методов. Метод также используется для логического дампа

FUNCTION anon.mask_update

-

boolean

Функция используется для обновления правил маскирования, вызывается после каждой из команд SECURITY LABEL FOR anon

FUNCTION anon.masking_expressions_for_table

oid, text

text

-

FUNCTION anon.masking_value_for_column

oid, integer, text

text

-

FUNCTION anon.md5

text, integer

text

-

FUNCTION anon.notice_if_not_init

-

text

-

FUNCTION anon.projection_to_oid

seed anyelement, salt text, last_oid bigint

integer

-

FUNCTION anon.register_masking_policy

policy text

boolean

-

FUNCTION anon.remove_masks_for_all_columns

-

boolean

Очистка масок всех атрибутов

FUNCTION anon.remove_masks_for_all_roles

-

boolean

Очистка масок всех ролей

FUNCTION anon.reset

-

boolean

Очистка таблиц псевдонимизации

FUNCTION anon.»right»

text, integer

text

Аналог функции right. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.start_dynamic_masking

autoload boolean DEFAULT true

boolean

Старт динамического маскирования: загрузка данных для псевдонимизации, создание схемы для маскирования (anon.maskschema), обновление масок

FUNCTION anon.stop_dynamic_masking

-

boolean

Остановка динамического маскирования

FUNCTION anon.substr

text, integer

text

Аналог функции substr. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.substr

text, integer, integer

text

Аналог функции substr. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.ternary

condition boolean, then_val anyelement, else_val anyelement

anyelement

Аналог функции CASE WHEN THEN. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.trg_check_trusted_schemas

-

event_trigger

-

FUNCTION anon.trg_mask_update

-

event_trigger

-

FUNCTION anon.unload

-

boolean

Выгрузка расширения

FUNCTION anon.unmask_role

maskedrole regrole

boolean

-

FUNCTION anon.upper

text

text

Аналог функции upper. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas

FUNCTION anon.version

-

text

Версия расширения

Дополнительный синтаксис#

  1. Задание процента семплирования БД при выгрузке в логический дамп или задание процента семплирования при применении статических методов. Маскирующая функция при выгрузке задается с использованием pg_dump, pg_dumpall. В конце метка SECURITY LABEL удаляется.

    Область действия: база данных.

    Синтаксис:

    SECURITY LABEL FOR anon ON DATABASE database_name IS 'anon_database_security_label';
    database_name := имя базы данных
    anon_database_security_label :=
    [ TABLESAMPLE tablesample_method(tablesample_value) |
    EXPORTED WITH FUNCTION <function> |
    NULL
    ]
    
  2. Задание признака маскирования для роли и при ее экспорте с последующим удалением признака маскирования.

    Область действия: роль.

    Синтаксис:

    SECURITY LABEL FOR anon ON ROLE role_name IS 'anon_role_security_label';
    role_name := роль СУБД
    anon_role_security_label :=
    [ MASKED |
    MASKED DUMP |
    NULL
    ]
    
  3. Задание признака доверенной схемы (данные должны быть скрыты для пользователя с признаком MASKED). По умолчанию устанавливается на схемы anon, pg_catalog, information_schema. Установка на схему, обозначенную параметром anon.maskedschema не допускается. В конце признак доверенной схемы должен быть удален.

    Область действия: схема.

    Синтаксис:

    SECURITY LABEL FOR anon ON SCHEMA schema_name IS 'anon_schema_security_label';
    schema_name := схема БД
    anon_schema_secuirty_label :=
    [ TRUSTED |
    NULL
    ]
    
  4. Задание процента семплирования отношения при выполнении статического маскирования с последующим удалением процента.

    Область действия: отношение.

    Синтаксис:

    SECURITY LABEL FOR anon ON TABLE [schema_name.]relation_name IS 'anon_relation_security_label';
    schema_name := схема БД
    relation_name := отношение (таблица, материализованное представление)
    anon_relation_secuirty_label :=
    [ TABLESAMPLE tablesample_method(tablesample_value) |
    NULL
    ]
    
  5. Задание признака атрибут не маскируется. В отличие от отсутствия признака - атрибут не будет маскироваться при настройке anon.privacy_by_default=true. Устанавливаются признаки маскирования по функции и значению. Допускается использование immutable-функции по имени атрибута, например: MASKED WITH VALUE substring(<column>,1,2)||'****'.

    Область действия: атрибут.

    Синтаксис:

    SECURITY LABEL FOR anon ON COLUMN [[schema_name.]relation_name.]column_name IS 'anon_column_security_label';
    schema_name := схема БД
    relation_name := отношение (таблица, материализованное представление)
    column_name := атрибут (колонка)
    anon_relation_secuirty_label :=
    [ NOT MASKED |
    MASKED WITH FUNCTION function_name(function_args) |
    MASKED WITH VALUE $$value$$ |
    NULL
    ]
    
  6. Задание атрибута k-anonimity: quasi identifier (он же indirect identifier) с его последующим удалением.

    Область действия: атрибут.

    Синтаксис:

    SECURITY LABEL FOR k_anonymity_provider ON COLUMN [[schema_name.]relation_name.]column_name IS 'k-anonimity_column_security_label';
    k_anonimity_provider := имя провайдера K-Anonimity
    schema_name := схема БД
    relation_name := отношение (таблица, материализованное представление)
    column_name := атрибут (колонка)
    k-anonimity_relation_secuirty_label :=
    [ QUASI IDENTIFIER | INDIRECT IDENTIFIER |
    NULL
    ]
    

Изменения утилит командной строки#

Изменения в утилитах создания логических дампов связаны с возможностью их применения на системах, не имеющих в своем составе расширения anon. В частности, для использования в продукте под лицензией Standart используются pg_dump и pg_dumpall:

# Дополнительные ключи командной строки для pg_dump
pg_dump --help
 --no-extension=PATTERN                 do not dump specified extension
 
# Дополнительные ключи командной строки для pg_dump
pg_dumpall --help
  --no-extension=PATTERN                 do not dump specified extension

Использование функциональности#

Обозначения

Здесь и далее в примерах:

  • <database_name>: название базы данных;

  • <sensitive_schema>: схема, содержащая сенситивные данные. Определяется с помощью GUC anon.sourceschema;

  • <sensitive_rel>: отношение (таблица) с сенситивными данными;

  • <sensitive_attr>: атрибут (колонка) с сенситивными данными;

  • <sensitive_user>: пользователь/роль БД, от которого скрываются сенситивные данные;

  • <const_value>: константа, соответствующая типу маскируемого поля;

  • <function_name>: функция, соответствующая типу маскируемого поля и стратегии маскирования.

Установка расширения:

ALTER DATABASE <database_name> SET session_preload_libraries='anon';
ALTER DATABASE <database_name> SET anon.default_locale='<locale>'; -- en_US,fr_FR,ru_RU
\c <database_name>
CREATE EXTENSION anon;
SELECT * FROM anon.init();
SECURITY LABEL FOR anon ON DATABASE <database_name> IS 'EXPORTED WITH FUNCTION anon.mask_select';

Назначение прав пользователю, под которым ведется работа с сенситивными данными:

SECURITY LABEL FOR anon ON ROLE <sensitive_user> IS 'MASKED';

Назначение прав пользователю, под которым производятся логические дампы с применением обфускации:

SECURITY LABEL FOR anon ON ROLE <sensitive_user> IS 'MASKED DUMP';

Назначение правил маскирования на данные:

SECURITY LABEL FOR anon ON SCHEMA <sensitive_schema> IS 'TRUSTED';
SECURITY LABEL FOR anon ON COLUMN <sensitive_schema>.<sensitive_rel>.<sensitive_attr> IS 'MASKED WITH [VALUE $$<const_value>$$ | MASKED WITH FUNCTION <function_name>([<function_args>])];

Выгрузка логического дампа БД:

pg_dump -Fc -U<sensitive_user> --no-security-labels --exclude-schema=anon,mask ---f <database_name>.pgdmp <database_name

Динамическое маскирование#

При динамическом маскировании:

  1. Инициализируется схема данных для псевдонимизации (если она не была инициализирована ранее).

  2. Отзываются права пользователя <sensitive_user> на схему <sensitive_schema>.

  3. Создается схема, определенная параметром anon.maskschema.

  4. Создается представление, соответствующее имени <sensitive_rel> и содержащее правила маскирования.

SELECT anon.start_dynamic_masking();

Статическое маскирование#

При статическом маскировании безвозвратно заменяются исходные данные.

SELECT anon.<function_name>(); -- где <function_name> - статическая функция

Изменения данных для псевдонимизации, ненастоящих данных#

Для изменения таблиц подстановочных данных может быть использован генератор, основанный на модуле Faker.

generator.py:

#!/usr/bin/python3
 
import sys
import csv
import argparse
import random
import faker
import importlib
 
 
def address():
    return [[oid, f.unique.address().replace('\n', ', ')]
            for oid in range(lines)]
 
 
def city():
    return [[oid, f.unique.city()] for oid in range(lines)]
 
 
def company():
    return [[oid, f.unique.company()] for oid in range(lines)]
 
 
# The dataset is too small, we're extracting the values directly
def country():
    values = []
    for loc in locales:
        m = importlib.import_module('faker.providers.address.'+loc)
        values += list(set(m.Provider.countries))
    random.shuffle(values)
    return [[oid, values[oid]]
            for oid in range(min(len(values), lines))]
 
 
def email():
    return [[oid, f.unique.email()] for oid in range(lines)]
 
 
# The dataset is too small, we're extracting the values directly
def first_name():
    values = []
    for loc in locales:
        m = importlib.import_module('faker.providers.person.'+loc)
        values += list("m"+fn for fn in m.Provider.first_names_male)
        values += list("f"+fn for fn in m.Provider.first_names_female)
    random.shuffle(values)
    return [[oid, values[oid][:1], values[oid][1:]]
            for oid in range(min(len(values), lines))]
 
# The dataset is too small, we're extracting the values directly
def middle_name():
    values = []
    for loc in locales:
        if loc == 'ru_RU':
            m = importlib.import_module('faker.providers.person.ru_RU')
            values += list("m"+fn for fn in m.Provider.middle_names_male)
            values += list("f"+fn for fn in m.Provider.middle_names_female)
    random.shuffle(values)
    return [[oid,  values[oid][:1], values[oid][1:]]
            for oid in range(min(len(values), lines))]
 
def iban():
    return [[oid, f.unique.iban()] for oid in range(lines)]
 
 
# The dataset is too small, we're extracting the values directly
def last_name():
    values = []
    for loc in locales:
        m = importlib.import_module('faker.providers.person.'+loc)
        if hasattr(m.Provider,'last_names_male'):
            values += list("m"+fn for fn in m.Provider.last_names_male)
            values += list("f"+fn for fn in m.Provider.last_names_female)
        else:
            values += list("m"+fn for fn in m.Provider.last_names)
            values += list("f"+fn for fn in m.Provider.last_names)           
    random.shuffle(values)
    return [[oid, values[oid][:1], values[oid][1:]]
            for oid in range(min(len(values), lines))]
 
 
def lorem_ipsum():
    return [[oid, f.unique.paragraph(nb_sentences=8)] for oid in range(lines)]
 
 
def postcode():
    return [[oid, f.unique.postcode()] for oid in range(lines)]
 
 
def siret():
    # override the locales this data is only relevant in France
    french_faker = faker.Faker('fr_FR')
    return [[oid, french_faker.unique.siret()] for oid in range(lines)]
 
 
generator_methods = [
  'address', 'city', 'company', 'country', 'email', 'first_name', 'iban',
  'last_name', 'middle_name', 'lorem_ipsum', 'postcode', 'siret'
]
 
 
# Input
parser = argparse.ArgumentParser()
parser.add_argument(
    '--table',
    help='Type of data ({})'.format(generator_methods),
    choices=generator_methods,
    required=True
)
parser.add_argument(
    '--locales',
    help='Localization of the fake data (comma separated list)',
    default='en'
)
parser.add_argument(
    '--lines',
    help='Number of rows to add to the table',
    type=int,
    default=1000
)
parser.add_argument(
    '--seed',
    help='Initializes the random generator'
)
args = parser.parse_args()
 
locales = [loc.strip() for loc in args.locales.split(' ')]
lines = args.lines
# Generator
f = faker.Faker(locales)
if args.seed:
    random.seed(args.seed)
    faker.Faker.seed(args.seed)
 
for row in locals().get(args.table)():
    csv.writer(sys.stdout, delimiter='\t').writerow(row)

Примечание:

Для таблиц country, first_name, middle_name, last_name используются все значения, используемые модулем Faker, без ограничения количества.

Таблица middle_name для локалей en_US, fr_FR не заполняется. Для генерации personal_name таблица должна содержать одну строку с oid=0 и пустым значением middle_name.

generator.py --help:

bash$ generator.py --help
usage: generator.py [-h] --table
                    {address,city,company,country,email,first_name,iban,last_name,middle_name,lorem_ipsum,postcode,siret}
                    [--locales LOCALES] [--lines LINES] [--seed SEED]
 
optional arguments:
  -h, --help            show this help message and exit
  --table {address,city,company,country,email,first_name,iban,last_name,middle_name,lorem_ipsum,postcode,siret}
                        Type of data (['address', 'city', 'company',
                        'country', 'email', 'first_name', 'iban', 'last_name',
                        'middle_name', 'lorem_ipsum', 'postcode', 'siret'])
  --locales LOCALES     Localization of the fake data (comma separated list)
  --lines LINES         Number of rows to add to the table
  --seed SEED           Initializes the random generator

Пример использования генератора:

bash$
cd $PGHOME/share/extension/anon/ru_RU/fake
for file in *.csv; do echo $file; generator.py --locales=ru_RU --lines=3000 --table $(basename $file .csv) 1>$file; done

Генерация данных ИНН, ОГРН, СНИЛС и ФИО производится динамически при инициализации расширения:

-- Параметр :nr_fake_records - количество генерируемых элементов
-- ИНН
-- !! Генерируется только ИНН юр. лица. При необходимости генерировать ИНН физ. лица используйте функцию anon.random_inn12()
  INSERT INTO anon.inn(val) SELECT anon.random_inn10() FROM generate_series(1,:nr_fake_records) id;
-- СНИЛС
  INSERT INTO anon.snils(val) SELECT anon.random_snils() FROM generate_series(1,:nr_fake_records) id;
-- ОГРН
-- !! Генерируется только ОГРН юр. лица. При необходимости генерировать ОГРН физ. лица используйте функцию anon.random_ogrnip()
  INSERT INTO anon.ogrn(val) SELECT anon.random_ogrn() FROM generate_series(1,:nr_fake_records) id;
-- ФИО
  WITH fname AS (SELECT gender,COALESCE(val,'') first_name FROM anon.first_name),
    mname AS (SELECT gender,COALESCE(val,'') middle_name FROM anon.middle_name),
    lname AS (SELECT gender,COALESCE(val,'') last_name FROM anon.last_name)
  INSERT INTO anon.personal_name(gender,first_name,middle_name,last_name)
  SELECT fname.gender,first_name,middle_name,last_name
  FROM fname
      NATURAL JOIN mname
      NATURAL JOIN lname
  ORDER BY random() LIMIT :nr_fake_records;

Внимание!

При использовании собственных данных для анонимизации следует заполнять поле oid подменных (фейковых) таблиц, начиная со значения 0, поскольку алгоритм псевдонимизации основан на вычислении остатка от деления хеша на количество записей.

Обфускация экспортируемых данных#

Для работы утилит pg_dump, pg_dumpall на уровне каждой базы данных должна быть задана функция обфускации. К функции предъявляются требования:

  • Функция должна быть объявлена на уровне базы данных с помощью SECURITY LABEL FOR anon ON DATABASE <database_name> IS 'EXPORTED WITH FUNCTION anon.mask_select';.

  • Функция должна принимать единственный аргумент - oid::regclass экспортируемого объекта.

  • Функция должна возвращать форматированный запрос, соответствующий выборке обфусцированных данных из объекта.

  • Для автоматического использования функции при экспорте пользователю необходимо назначить флаг SECURITY LABEL FOR anon ON ROLE <role_name> IS 'MASKED DUMP';.

-- Исходная таблицы
\d+ public.people01
                                                             Table "public.people01"
┌───────────┬───────────────┬───────────┬──────────┬──────────────────────────────────────┬──────────┬─────────────┬──────────────┬─────────────┐
│  Column   │     Type      │ Collation │ Nullable │               Default                │ Storage  │ Compression │ Stats target │ Description │
├───────────┼───────────────┼───────────┼──────────┼──────────────────────────────────────┼──────────┼─────────────┼──────────────┼─────────────┤
│ id        │ integer       │           │ not null │ nextval('people01_id_seq'::regclass) │ plain    │             │              │             │
│ firstname │ text          │           │          │                                      │ extended │             │              │             │
│ lastname  │ text          │           │          │                                      │ extended │             │              │             │
│ phone     │ text          │           │          │                                      │ extended │             │              │             │
│ balance   │ numeric(17,2) │           │          │                                      │ main     │             │              │             │
└───────────┴───────────────┴───────────┴──────────┴──────────────────────────────────────┴──────────┴─────────────┴──────────────┴─────────────┘
Indexes:
    "people01_pkey" PRIMARY KEY, btree (id)
Access method: heap
-- SECURITY LABELS таблицы
select * from anon.pg_masking_rules where relname='people01';
┌──────────┬────────┬──────────────┬──────────┬──────────┬─────────────┬────────────────────┬──────────────────┬───────────────┬──────────┬────────────────┬────────────────┐
│ attrelid │ attnum │ relnamespace │ relname  │ attname  │ format_type │  col_description   │ masking_function │ masking_value │ priority │ masking_filter │ trusted_schema │
├──────────┼────────┼──────────────┼──────────┼──────────┼─────────────┼────────────────────┼──────────────────┼───────────────┼──────────┼────────────────┼────────────────┤
│    51909 │      3 │ public       │ people01 │ lastname │ text        │ MASKED WITH VALUE …│ NULL             │ NULL          │      100 │ NULL           │ f              │
│          │        │              │          │          │             │…NULL               │                  │               │          │                │                │
└──────────┴────────┴──────────────┴──────────┴──────────┴─────────────┴────────────────────┴──────────────────┴───────────────┴──────────┴────────────────┴────────────────┘
 -- Результат работы функции
select * from anon.mask_select('public.people01'::regclass);
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                                      mask_select                                       │
├────────────────────────────────────────────────────────────────────────────────────────┤
│ SELECT id,firstname,CAST(NULL AS text) AS lastname,phone,balance FROM public.people01  │
└────────────────────────────────────────────────────────────────────────────────────────┘

Расширение функциональности#

При необходимости список алгоритмов обфускации может быть расширен, как и состав данных для псевдонимизации.

Поскольку статические методы необратимо меняют данные, в качестве примера рассматривается любое обновление (UPDATE) таблицы. Кроме того, возможно применение любого динамического метода в статическом режиме с помощью функций:

  • anon.anonymize_column();

  • anon.anonymize_table();

  • anon.anonymize_database().

В качестве примера добавим собственный динамический метод - генерацию слова из заданных символов c длиной, не превышающей заданный параметр. Метод относится к группе алгоритмов фальсификации и может быть использован для псевдонимизации:

-- !! Здесь и далее - используем:
--    БД anon_feat
--    Пользователя для обфускации anon_user
--    Пользователя для логического дампа anon_admin
/*
-- database
DROP DATABASE IF EXISTS anon_feat;
CREATE DATABASE anon_feat;
ALTER DATABASE anon_feat SET session_preload_libraries='anon';
ALTER DATABASE anon_feat SET anon.default_locale='ru_RU';
ALTER DATABASE anon_feat SET anon.sourceschema='public';
ALTER DATABASE anon_feat SET anon.maskschema='mask';
ALTER DATABASE anon_feat SET anon.transparent_dynamic_masking='true';
 \c anon_feat
CREATE EXTENSION IF NOT EXISTS anon;
SELECT * FROM anon.init();
SET search_path=mask,public;
 
-- users
DROP USER IF EXISTS anon_user;
CREATE USER anon_user;
SECURITY LABEL FOR anon ON ROLE anon_user IS 'MASKED';
ALTER USER anon_user SET search_path='mask','public';
DROP USER IF EXISTS anon_dump;
CREATE USER anon_dump SUPERUSER;
SECURITY LABEL FOR anon ON ROLE anon_dump IS 'MASKED DUMP';
SECURITY LABEL FOR anon ON DATABASE anon_feat IS 'EXPORTED WITH FUNCTION anon.mask_select';
-- create masked schema
CREATE SCHEMA IF NOT EXISTS mask;
GRANT USAGE ON SCHEMA mask TO anon_user;
ALTER DEFAULT PRIVILEGES IN SCHEMA mask GRANT ALL ON TABLES TO anon_user;
SELECT anon.start_dynamic_masking();
*/
 
 
 
-- создаем схему
CREATE SCHEMA IF NOT EXISTS my_anon;
-- помечаем схему, как TRUSTED. Только функции из схемы с атрибутом TRUSTED могут быть использованы для обфускации
SECURITY LABEL FOR anon ON SCHEMA my_anon IS 'TRUSTED';
-- создаем функцию обфускации
CREATE OR REPLACE FUNCTION my_anon.fake_noise
(
-- массив символов для генерации
elem TEXT DEFAULT 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзиклмнопрстуфхцчшщьыъэюя0123456789_<>',
-- длина генерируемого массива
max_len integer DEFAULT 10
)
RETURNS text
AS $$
  SELECT array_to_string(
    array(
        select substr(elem,
                      ((random()*(length(elem)-1)+1)::integer)
                      ,1)
        from generate_series(1,(max_len))
    ),''
  );
$$
  LANGUAGE SQL
  VOLATILE            -- обязательно. Используем random() - каждое обращение к функции должно генерировать новый результат
  PARALLEL RESTRICTED
  SECURITY INVOKER
  SET search_path=''
;
-- проверка работоспособности
SELECT id, my_anon.fake_noise(max_len => (32*random())::int4+10)
FROM generate_series(1,10) id;
-- пример вывода:
-- ┌────┬──────────────────────────────────────────┐
-- │ id │                fake_noise                │
-- ├────┼──────────────────────────────────────────┤
-- │  1 │ БEpЯДvю13иUв4V5ш7ршC775qgе               │
-- │  2 │ sJФ8Ж6<FEHэзO                            │
-- │  3 │ Ш7ЙЛ1BJЖ1ЗХОxзnчЯJQD5ВегVСТс2cyy         │
-- │  4 │ лЪЭkVЖРЕРFBХДUК8ZНAВmмпзQkj9пoьWгSбa     │
-- │  5 │ kУ_zЛ3iЗяуВхSЦoрзfОj                     │
-- │  6 │ tЙГvУбl4XНп2Х9гПShJФCН6хIТ6вUyAП         │
-- │  7 │ ыAнtaЪ1lояtм3инжшaщ9ЁIRыЭPVcаууvяО1эдZДБ │
-- │  8 │ ОЪg95ЯoАъХз1YЁЮЮщ7рUxWTZkj               │
-- │  9 │ ЭQQlZPШщхЬRucЭэВMJeПЛoЬUXaSzуVdKu        │
-- │ 10 │ SъGKaqaвKUsоюьщюъжSоУщhzjёмшz1схгщясcА   │
-- └────┴──────────────────────────────────────────┘
-- (10 rows)
-- Создадим тестовые данные для проверки
DROP TABLE IF EXISTS public.people;
CREATE TABLE public.people(
id serial NOT NULL PRIMARY key,
firstname TEXT,
lastname TEXT,
phone TEXT,
balance numeric(17,2)
);
WITH fname AS (SELECT gender,COALESCE(val,'') first_name FROM anon.first_name),
  lname AS (SELECT gender,COALESCE(val,'') last_name FROM anon.last_name)
INSERT INTO public.people(firstname,lastname,phone,balance)
SELECT
  first_name
  ,last_name
  ,format('+7 %1$s',substring(ceil(random()*1e12)::TEXT,1,10))
  ,(random()*1e5-5e4)::numeric(17,2)
FROM fname
  NATURAL JOIN lname
ORDER BY random() LIMIT 1e5;
-- назначим вновь созданную функцию для обфускации
SECURITY LABEL FOR anon ON COLUMN public.people.balance IS NULL;
SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS NULL;
SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS 'MASKED WITH FUNCTION my_anon.fake_noise()';
-- проверим работоспособность
SET ROLE anon_user;
SELECT * FROM mask.people LIMIT 10;
-- Пример вывода:
-- ┌───────┬───────────┬────────────┬───────────────┬───────────┐
-- │  id   │ firstname │  lastname  │     phone     │  balance  │
-- ├───────┼───────────┼────────────┼───────────────┼───────────┤
-- │ 15688 │ Алла      │ ЛееъyЩehUщ │ +7 1586788872 │ -34703.93 │
-- │ 84143 │ Валерьян  │ ДТноеRлАU6 │ +7 8393111953 │  35820.20 │
-- │ 14596 │ Эрнст     │ НRрВмбЕsW_ │ +7 1476835007 │ -34000.72 │
-- │ 81187 │ Маргарита │ 5Фж7ЖтV4ЩV │ +7 8096235724 │  33986.22 │
-- │ 89740 │ Порфирий  │ пХZw>цУщН> │ +7 8952409074 │  42892.93 │
-- │ 17042 │ Никифор   │ ХZювГGХ4TУ │ +7 1718701798 │ -31806.35 │
-- │ 26149 │ Прохор    │ сAd_ВЛМЗwY │ +7 2612167258 │ -22276.37 │
-- │ 20473 │ Марфа     │ 4_PкG_СNkУ │ +7 2053565418 │ -26932.25 │
-- │ 89709 │ Полина    │ чHXnнсрmЬn │ +7 8949979071 │  41891.58 │
-- │ 90649 │ Ладислав  │ wЮIбnиCцD6 │ +7 9042466198 │  39378.97 │
-- └───────┴───────────┴────────────┴───────────────┴───────────┘
-- (10 rows)
RESET ROLE;
 
-- на основе функции обфускации создаем функцию псевдонимизации и таблицу подстановок для нее
-- таблица
CREATE TABLE my_anon.noise (
 oid serial,
 val text
);
INSERT INTO my_anon.noise (val) SELECT my_anon.fake_noise() FROM generate_series(1,2500) id;
-- функция
CREATE OR REPLACE FUNCTION my_anon.pseudo_noise(
  seed anyelement,              -- значение для подстановки. Одинаковое значение генерирует одинаковый hash.
  salt text DEFAULT NULL::text  -- salt для хеширования (при необходимости)
)
 RETURNS text
AS $$
  SELECT COALESCE(val,anon.notice_if_not_init())
  FROM my_anon.noise
  WHERE oid = anon.projection_to_oid( -- функция anon.projection_to_oid генерирует остаток от деления вычисляемого хеша на количество элементов подстановок. Из таблицы подстановок выбираем элемент c oid, соответствующего остатку
    seed,
    COALESCE(salt, pg_catalog.current_setting('anon.salt')),
    (SELECT last_value FROM my_anon.noise_oid_seq)
  );
$$
 LANGUAGE sql
 STABLE
 PARALLEL SAFE
 SECURITY DEFINER
 SET search_path TO ''
;
-- назначим вновь созданную функцию для обфускации
SECURITY LABEL FOR anon ON COLUMN public.people.balance IS NULL;
SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS NULL;
SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS 'MASKED WITH FUNCTION my_anon.pseudo_noise(lastname)';
-- проверим работоспособность
SET ROLE anon_user;
SELECT * FROM mask.people LIMIT 10;
SELECT * FROM mask.people LIMIT 10;
RESET ROLE;
-- Пример вывода:
-- ┌───────┬───────────┬────────────┬───────────────┬───────────┐
-- │  id   │ firstname │  lastname  │     phone     │  balance  │
-- ├───────┼───────────┼────────────┼───────────────┼───────────┤
-- │ 15688 │ Алла      │ пqсЦмжPTАE │ +7 1586788872 │ -34703.93 │
-- │ 84143 │ Валерьян  │ лГvИxJ4ИцЕ │ +7 8393111953 │  35820.20 │
-- │ 14596 │ Эрнст     │ ВeysM<gvRЁ │ +7 1476835007 │ -34000.72 │
-- │ 81187 │ Маргарита │ IkР_y7ЪvQZ │ +7 8096235724 │  33986.22 │
-- │ 89740 │ Порфирий  │ жяQrъDpn6к │ +7 8952409074 │  42892.93 │
-- │ 17042 │ Никифор   │ tтcВМeТ5мО │ +7 1718701798 │ -31806.35 │
-- │ 26149 │ Прохор    │ lii5ыцcY1Р │ +7 2612167258 │ -22276.37 │
-- │ 20473 │ Марфа     │ кkCбXээж5_ │ +7 2053565418 │ -26932.25 │
-- │ 89709 │ Полина    │ yjьЮЩЁ<Dgи │ +7 8949979071 │  41891.58 │
-- │ 90649 │ Ладислав  │ 50jY3ВвЛЬХ │ +7 9042466198 │  39378.97 │
-- └───────┴───────────┴────────────┴───────────────┴───────────┘
-- (10 rows)
-- Вывод второго SELECT аналогичен первому. Псевдономизация работает
 
 
-- Пример комбинированного использоания функций
SECURITY LABEL FOR anon ON COLUMN public.people.balance IS NULL;
SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS NULL;
-- если balance >0, то сгененировать рандом в диапазоне [0,50000], иначе null
SECURITY LABEL FOR anon ON COLUMN public.people.balance IS 'MASKED WITH FUNCTION anon.ternary(balance > 0,anon.random_in_numrange($$[0,50000]$$),null)';

Отключение функциональности#

Для отключения функциональности для каждой БД, ее использующей, следует выполнить:

SELECT anon.stop_dynamic_masking();
SELECT anon.remove_masks_for_all_columns();
SELECT anon.remove_masks_for_all_roles();
SELECT anon.unload();
DROP EXTENSION anon;
ALTER DATABASE <database name> RESET session_preload_libraries; -- В случае, если session_preload_libraries для БД содержит только расширение anon. В ином случае - убрать расширение из списка предзагружаемых библиотек.

Ограничения функциональности#

  1. Требование по объему: 2…10 ТБ данных. По продолжительности выгрузки - до 12 ч. Обеспечивается скорость выгрузки порядка 100…300 МБ/с, в зависимости от производительности СУБД, дисковой подсистемы, свободных процессорных ресурсов и т.д.

  2. В одной базе механизм динамического маскирования предполагает только одну схему для маскируемых объектов. Маскируемые таблицы отображаются в схему, определенную параметром anon.maskschema, при этом в случае наличия одноименных таблиц в разных схемах маскируется только одна из них, выбранная случайно.

  3. Выполнение модификации данных на восстановленной копии предполагает одну полную перезапись маскируемых таблиц. Для объемных таблиц эта операция занимает много времени и требует большого количества системных ресурсов. К тому же, таблица будет на время перезаписи заблокирована.

  4. При доступе от имени недоверенного пользователя (динамическом маскировании):

    • механизм маскирования работает только с одной схемой;

    • разделение правил маскирования для разных ролей не осуществляется;

    • применение множества правил маскирования для одного атрибута не осуществляется, в таком случае может существовать не более одного правила маскирования;

    • при использовании динамического маскирования для множества схем следует учитывать наличие отношений с одинаковыми именами в разных схемах. В случае наличия - динамическое маскирование будет применяться только для одного из отношений;

    • показ планов исполнения (EXPLAIN) для маскируемой роли запрещен;

    • при применении SECURITY LABEL для атрибута производится проверка возвращаемого типа. Данная операция попадает в список pg_stat_statements;

    • повышается время отклика: простейшие запросы при множестве активных правил могут у недоверенных пользователей выполняться с замедлением, линейно коррелирующим с количеством строк исходной таблицы;

    • могут существенно медленнее выполняться планы запросов, предполагающие соединение таблицы по замаскированному ключу с хешированием или псевдонимизацией. При этом, показ планов запросов (EXPLAIN) не осуществляется;

    • расширение будет изменять search_path по своему усмотрению, поэтому:

      • psql может не отображать описания таблиц без явного указания схемы(\dt my_application.*);

      • графические клиенты могут отказываться выбирать данные, обращаясь с полным указанием схемы к заблокированной таблице - их потребуется перенаправить на замаскированное представление.

  5. Применение разных правил маскирования для разных пользователей не осуществляется.

  6. Применение множества правил маскирования для одного атрибута каждого отношения не осуществляется.

  7. При использовании динамического маскирования для множества схем следует учитывать наличие отношений с одинаковыми именами в разных схемах. В случае наличия - динамическое маскирование будет применяться только для одного из отношений.

  8. Показ планов запросов для маскируемой роли запрещен.

  9. При применении SECURITY LABEL для атрибута производится проверка возвращаемого типа. Данная операция попадает в список pg_stat_statements.