Перейти к основному содержимому

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

Описание

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

Функциональность позволяет:

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

Описываемое решение основано на свободно распространяемом расширении postgresql_anonymizer с доработками внутри продукта Platform V Pangolin DB.

Для выгрузки и записи дампа используются стандартные инструменты СУБД – 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 для отношений происходит до загрузки данных в таблицу.

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

Настройка

Обозначения

Далее в разделе:

  • <database_name>: название базы данных;
  • <locale>: локаль инициализации таблиц подстановок;
  • <sensitive_schema>: схема, содержащая сенситивные данные. Определяется с помощью GUC anon.sourceschema;
  • <sensitive_rel>: отношение (таблица) с сенситивными данными;
  • <sensitive_attr>: атрибут (колонка) с сенситивными данными;
  • <sensitive_user>: пользователь или роль БД, от которых скрываются сенситивные данные;
  • <const_value>: константа, соответствующая типу маскируемого поля;
  • <function_name>: функция, соответствующая типу маскируемого поля и стратегии маскирования.

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

  1. Установите rpm/deb-пакет расширения из поставляемой 3rd Party части дистрибутива:

    sudo dnf install pangolin-pg-anon-{version_component}-{OS}.x86_64.rpm
    Подсказка

    Пример заполненной команды:

    cd $PGHOME/3rdparty/pg_anon

    sudo dnf install pangolin-pg-anon-1.3.1-sberlinux8.9.x86_64.rpm
  2. Определите необходимые конфигурационные параметры. Пример:

    ALTER DATABASE <database_name> SET session_preload_libraries='anon';
    ALTER DATABASE <database_name> SET anon.default_locale='<locale>'; -- en_US,fr_FR,ru_RU
    ALTER DATABASE <database_name> SET anon.sourceschema='sensitive_schema';
    ALTER DATABASE <database_name> SET anon.maskschema='mask';
    Подсказка

    Параметры функциональности описаны далее в разделе «Конфигурационные параметры». Применение изменений данных параметров требует перечитывания конфигурации (SELECT pg_reload_conf()). Параметры можно определить для всего экземпляра (в файле конфигурации postgresql.conf или с помощью ALTER SYSTEM), либо на уровне базы данных (ALTER DATABASE).

  3. Активируйте расширение anon для нужной БД. Используйте команду:

    \c <database_name>
    CREATE EXTENSION anon;
  4. С помощью функции anon.init() инициализируйте расширение anon:

    SELECT * FROM anon.init();

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

  5. Задайте правило маскирования с маскирующей функцией при выгрузке данных из <database_name>:

    SECURITY LABEL FOR anon ON DATABASE <database_name> IS 'EXPORTED WITH FUNCTION anon.mask_select';
    Подсказка

    Правила маскирования объявляются с помощью SECURITY LABEL. Для определения правил маскирования доступны различные функции маскировки.

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

Использование динамического маскирования

С помощью функции anon.start_dynamic_masking инициализируется старт динамического маскирования, при котором:

  1. Инициализируется схема данных для псевдонимизации (если она не была инициализирована ранее).
  2. Отзываются права пользователя <sensitive_user> на схему <sensitive_schema>.
  3. Создается схема для генерации анонимизированных данных, определенная параметром anon.maskschema.
  4. Создается представление, соответствующее имени <sensitive_rel> и содержащее правила маскирования.

Далее определяются правила маскирования путем указания меток с заданными функциями.

Использование статического маскирования

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

SELECT anon.<function_name>();

В <function_name> должна быть определена функция статического метода.

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

ПараметрЗначение по умолчаниюОписание
anon.algorithmsha256Алгоритм хеширования, применяемый методом псевдонимизации. Поддерживаются md5, sha1, sha224, sha256, sha384 и sha512
anon.allow_constraints_maskingfalseФлаг, допускающий обфускацию для атрибутов, входящих в ограничение целостности
anon.default_localeen_USЛокаль инициализации таблиц подстановок, применяемых в методах фальсификации и псевдонимизации
anon.k_anonymity_providerk_anonymityИмя SECURITY PROVIDER, используемого для метода K-Anonimity
anon.masking_policiesanonСхема БД, используемая расширением обфускации данных и содержащая таблицы и функции подстановок
anon.maskschemamaskСхема БД, используемая для генерации анонимизированных данных
anon.privacy_by_defaultoffВыбор стратегии маскирования атрибутов, не имеющих метки SECURITY LABEL. При включении параметра — для всех атрибутов, имеющих значения по умолчанию — будут использоваться они. Для атрибутов, допускающих NULL — подставлен NULL
anon.restrict_to_trusted_schemasonПризнак использования функций маскирования, расположенных только в схемах с атрибутом TRUSTED
anon.saltОтсутствуетСоль, используемая для метода псевдонимизации
anon.sourceschemapublicСхема БД по умолчанию, содержащая исходные данные. В случае использования нескольких схем — каждая из них должна быть помечена атрибутом TRUSTED
anon.strict_modeonТребования соответствия типа подстановочного значения типу исходного значения
anon.transparent_dynamic_maskingoffРежим динамического маскирования

Объекты функциональности

Таблицы подстановок

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

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
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.partialov text, prefix integer, padding text, suffix integertextЧастичное маскирование текстовых данных
FUNCTION anon.partial_emailov texttextЧастичное маскирование e-mail
Искажение

Функции метода искажения:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.noisenoise_value anyelement, ratio double precisionanyelementИскажение с числовым интервалом
FUNCTION anon.dnoisenoise_value anyelement, noise_range intervalanyelementИскажение с интервалом дата/время
Обобщение

Функции обобщения:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.generalize_daterangeval date, step text DEFAULT 'decade'::textdaterangeОбобщение для типа данных date
FUNCTION anon.generalize_int4rangeval integer, step integer DEFAULT 10int4rangeОбобщение для типа данных integer
FUNCTION anon.generalize_int8rangeval bigint, step bigint DEFAULT 10int8rangeОбобщение для типа данных bigint
FUNCTION anon.generalize_numrangeval numeric, step integer DEFAULT 10numrangeОбобщение для типа данных numeric
FUNCTION anon.generalize_tsrangeval timestamp without time zone, step text DEFAULT 'decade'::texttsrangeОбобщение для типа данных timestamp
FUNCTION anon.generalize_tstzrangeval timestamp with time zone, step text DEFAULT 'decade'::texttstzrangeОбобщение для типа данных timestamptz
Рандомизация

Функции метода рандомизации:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.random_bigint_betweenint_start bigint, int_stop bigintbigintРандомизация для типа данных bigint
FUNCTION anon.random_date-timestamp with time zoneГенерация случайной даты
FUNCTION anon.random_date_betweendate_start timestamp with time zone, date_end timestamp with time zonetimestamp with time zoneГенерация случайной даты в диапазоне
FUNCTION anon.random_hashseed texttextГенерация случайного хеша
FUNCTION anon.random_ina anyarrayanyelementГенерация случайного значения из массива
FUNCTION anon.random_in_dateranger daterangedateГенерация случайной даты из диапазона
FUNCTION anon.random_in_enumelement anyelementanyelementГенерация случайного значения ENUM
FUNCTION anon.random_in_int4ranger int4rangeintegerГенерация случайного значения в диапазоне integer
FUNCTION anon.random_in_int8ranger int8rangebigintГенерация случайного значения в диапазоне bigint
FUNCTION anon.random_in_numranger numrangenumericГенерация случайного значения в диапазоне numeric
FUNCTION anon.random_in_tsranger tsrangetimestamp without time zoneГенерация случайного значения в диапазоне tsrange
FUNCTION anon.random_in_tstzranger tstzrangetimestamp with time zoneГенерация случайного значения в диапазоне tstzrange
FUNCTION anon.random_inn10-textГенерация случайного значения ИНН юридического лица (с вычислением контрольной суммы)
FUNCTION anon.random_inn12-textГенерация случайного значения ИНН физического лица (с вычислением контрольной суммы)
FUNCTION anon.random_int_betweenint_start integer, int_stop integerintegerГенерация случайного значения в диапазоне integer
FUNCTION anon.random_ogrn-textГенерация случайного значения ОГРН юридического лица (с вычислением контрольной суммы)
FUNCTION anon.random_ogrnip-textГенерация случайного значения ОГРН физического лица (с вычислением контрольной суммы)
FUNCTION anon.random_phonephone_prefix text DEFAULT '0'::texttextГенерация случайного значения телефонного номера с заданным префиксом
FUNCTION anon.random_snils-textГенерация случайного значения СНИЛС физического лица (с вычислением контрольной суммы)
FUNCTION anon.random_stringl integertextГенерация случайного значения слова заданной длины (заглавная латиница + цифры)
FUNCTION anon.random_zip-textГенерация случайного значения почтового индекса
FUNCTION anon.lorem_ipsumparagraphs integer DEFAULT 5, words integer DEFAULT 0, characters integer DEFAULT 0textГенерация случайного значения текста заданной длины
Фальсификация

Функции метода фальсификации:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
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_namemask text DEFAULT '%1$s %2$s %3$s'::texttextФальсифицированное ФИО (по маске, где: %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_addressseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный адрес
FUNCTION anon.pseudo_cityseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный город
FUNCTION anon.pseudo_companyseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированная компания
FUNCTION anon.pseudo_countryseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированная страна
FUNCTION anon.pseudo_emailseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный e-mail
FUNCTION anon.pseudo_first_nameseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированное имя
FUNCTION anon.pseudo_ibanseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный IBAN
FUNCTION anon.pseudo_innseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный ИНН
FUNCTION anon.pseudo_last_nameseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированная фамилия
FUNCTION anon.pseudo_middle_nameseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированное отчество
FUNCTION anon.pseudo_ogrnseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный ОГРН
FUNCTION anon.pseudo_personal_nameseed anyelement, salt text DEFAULT NULL::text, mask text DEFAULT '%1$s %2$s %3$s'::texttextПсевдонимизированное ФИО (по маске, где: %1 - Имя, %2 - Фамилия, %3 - Отчество)
FUNCTION anon.pseudo_postcodeseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный почтовый индекс
FUNCTION anon.pseudo_siretseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный SIRET
FUNCTION anon.pseudo_snilsseed anyelement, salt text DEFAULT NULL::texttextПсевдонимизированный СНИЛС
Хеширование текстовых данных

Функции метода хеширования тестовых данных:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.hashseed texttextХеш текста
FUNCTION anon.digestval text, salt text, algorithm texttextХеш текста (с выбором соли и алгоритма)
Статическое маскирование

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

Перестановка

Функции метода перестановки:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.shuffle_columnshuffle_table regclass, shuffle_column name, primary_key namebooleanГенерация колонки с перестановкой элементов
Искажение

Функции метода искажения:

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.add_noise_on_datetime_columnnoise_table regclass, noise_column text, variation intervalbooleanИскажение временного типа
FUNCTION anon.add_noise_on_numeric_columnnoise_table regclass, noise_column text, ratio double precisionbooleanИскажение числового типа
Реализация правил динамического маскирования в статическом режиме
ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.anonymize_columntablename regclass, colname namebooleanМаскирование атрибута
FUNCTION anon.anonymize_database-booleanМаскирование базы данных
FUNCTION anon.anonymize_tabletablename regclassbooleanМаскирование таблицы
Служебные функции и процедуры

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

ОбъектАргументы, атрибутыВозвращаемое значениеОписание
FUNCTION anon.build_anonymize_column_assignmenttablename regclass, colname nametextСоздание маппинга имя колонки = замена
FUNCTION anon.column_existstable_relid regclass, column_name namebooleanАналог функции concat. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.concattext, texttextАналог функции date_add. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_addtimestamp with time zone, intervaltimestamp with time zoneАналог функции date_part. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_parttext, timestamp without time zonedouble precisionАналог функции date_part. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_parttext, intervaldouble precisionАналог функции date_subtract. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_subtracttimestamp with time zone,intervaltimestamp with time zoneАналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_trunctext, timestamp without time zonetimestamp without time zoneАналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_trunctext, intervalintervalАналог функции date_trunc. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.date_trunctext, timestamp with time zone, texttimestamp with time zoneАвтодетектирование масок анонимизации для предопределенной локали. Используются атрибуты всех отношений текущей БД, за исключением служебных схем и схемы, определенной GUC anon.maskschema. Поиск ведется по соответствию имени атрибута, локали в anon.identifier. Категория и маскирующая функция определяется по anon.identifier_category
FUNCTION anon.detectdict_lang text DEFAULT 'en_US'::textTABLE(table_name regclass, column_name name, identifiers_category text, direct boolean)-
FUNCTION anon.get_attrdefrelid integer, num integertext-
FUNCTION anon.get_function_schematexttext-
FUNCTION anon.get_relnamet texttext-
FUNCTION anon.get_schemat texttext-
FUNCTION anon.get_tablesample_ratiorelid oidtext-
FUNCTION anon.hasmaskrole regrole, masking_policy text DEFAULT 'anon'::textboolean-
FUNCTION anon.hex_to_inthexval textinteger-
FUNCTION anon.init-booleanИнициализация расширения. Локаль инициализации должна быть выбрана перед запуском. При инициализации загружаются таблицы псевдонимизации, случайно генерируются заполнение таблиц ИНН, ОГРН, СНИЛС. При инициализации случайно выбираются до 2500 значений ФИО по соответствию пола
FUNCTION anon.initdatapath textbooleanИнициализация расширения с возможностью задания собственного пути расположения csv-данных таблиц псевдонимизации
FUNCTION anon.init_masking_policies-boolean-
FUNCTION anon.is_initialized-boolean-
FUNCTION anon.k_anonymityrelid regclassintegerПолучение идентификаторов K-Anonymity для отношения. Процедура производит оценку стойкости идентификаторов для отношения
FUNCTION anon."left"texttextАналог функции left. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.load-boolean-
FUNCTION anon.loadtextboolean-
FUNCTION anon.load_csvdest_table regclass, csv_file textboolean-
FUNCTION anon.lowertexttextАналог функции lower. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.make_dateinteger, integer, integerdateАналог функции make_date. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.mask_columnssource_relid oidTABLE(attname name, masking_filter text, format_type text)-
FUNCTION anon.mask_create_viewrelid oidbooleanСоздание маскированного представления для динамических методов
FUNCTION anon.mask_drop_viewrelid oidbooleanУдаление маскированного представления для динамических методов
FUNCTION anon.mask_filtersrelid oidtextФормирования тела запроса, используемого для mask_create_view
FUNCTION anon.mask_rolemaskedrole regrolebooleanДействия, накладываемые на маскируемую роль. Изменение функции может быть полезно в случае использования собственной ролевой модели
FUNCTION anon.mask_selectrelid oidtextФормирование SELECT запроса для динамических методов. Метод также используется для логического дампа
FUNCTION anon.mask_update-booleanФункция используется для обновления правил маскирования, вызывается после каждой из команд SECURITY LABEL FOR anon
FUNCTION anon.masking_expressions_for_tableoid, texttext-
FUNCTION anon.masking_value_for_columnoid, integer, texttext-
FUNCTION anon.md5text, integertext-
FUNCTION anon.notice_if_not_init-text-
FUNCTION anon.projection_to_oidseed anyelement, salt text, last_oid bigintinteger-
FUNCTION anon.register_masking_policypolicy textboolean-
FUNCTION anon.remove_masks_for_all_columns-booleanОчистка масок всех атрибутов
FUNCTION anon.remove_masks_for_all_roles-booleanОчистка масок всех ролей
FUNCTION anon.reset-booleanОчистка таблиц псевдонимизации
FUNCTION anon."right"text, integertextАналог функции right. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.start_dynamic_maskingautoload boolean DEFAULT truebooleanСтарт динамического маскирования: загрузка данных для псевдонимизации, создание схемы для маскирования (anon.maskschema), обновление масок
FUNCTION anon.stop_dynamic_masking-booleanОстановка динамического маскирования
FUNCTION anon.substrtext, integertextАналог функции substr. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.substrtext, integer, integertextАналог функции substr. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.ternarycondition boolean, then_val anyelement, else_val anyelementanyelementАналог функции 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_rolemaskedrole regroleboolean-
FUNCTION anon.uppertexttextАналог функции upper. Перенос функции связан с ограничением anon.restriict_to_trusted_schemas
FUNCTION anon.version-textВерсия расширения

Управление

Назначение прав

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

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>])]';
примечание

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

Вывод правил маскирования

Чтобы отобразить все правила маскирования, объявленные в текущей базе данных, воспользуйтесь представлением anon.pg_masking_rules:

SELECT * FROM anon.pg_masking_rules;

Удаление правил маскирования

Удаление правила маскирования происходит следующим образом:

SECURITY LABEL FOR anon ON COLUMN <column_name> IS NULL;

Для удаления всех правил сразу, необходимо использовать:

SELECT anon.remove_masks_for_all_columns();

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

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

Был добавлен аргумент --no-extension для указания расширений, которые не требуется добавлять в выгрузку резервной копии:

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

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

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

Для работы утилит 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';.
  1. Пример вывода информации об исходной таблице:

    \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
  2. Пример вывода информации о 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 │ │ │ │ │ │
    └──────────┴────────┴──────────────┴──────────┴──────────┴─────────────┴────────────────────┴──────────────────┴───────────────┴──────────┴────────────────┴────────────────┘
  3. Результат работы функции:

    SELECT * FROM anon.mask_select('public.people01'::regclass);

    ┌────────────────────────────────────────────────────────────────────────────────────────┐
    │ mask_select │
    ├────────────────────────────────────────────────────────────────────────────────────────┤
    │ SELECT id,firstname,CAST(NULL AS text) AS lastname,phone,balance FROM public.people01 │
    └────────────────────────────────────────────────────────────────────────────────────────┘

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

Команда для выгрузки анонимизированного дампа БД с обфусцированными данными:

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

Параметры:

  • -Fc - выгрузка базы данных в специальном формате;
  • -U <sensitive_user> - пользователь, под которым производится дамп с применением обфускации;
  • --no-security-labels - удаление правила маскировки из анонимного дампа, поскольку «замаскированные» пользователи не должны иметь доступа к политике маскировки;
  • --exclude-schema=anon,mask - исключение схемы anon и mask так как они системные, содержащие таблицы подстановок и данные для генерации анонимизированных данных;
  • --f <database_name>.pgdmp - наименование файла дампа;
  • <database_name> - наименование базы данных для выгрузки.

Схема процесса выгрузки анонимизированного дампа:

Выгрузка анонимизированного дампа

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

Обозначения

Далее в примере:

  • database_name - имя базы данных;
  • role_name - роль СУБД;
  • schema_name - схема БД;
  • relation_name - отношение (таблица, материализованное представление);
  • column_name - атрибут (колонка);
  • k_anonimity_provider - имя провайдера K-Anonimity.

Значения указанные в квадратных скобках [] через вертикальную черту | обозначают выбор одного из перечисленных значений, это эквивалентно логическому оператору «ИЛИ».

Работа с базой данных

Синтаксис:

SECURITY LABEL FOR anon ON DATABASE database_name IS 'anon_database_security_label';

anon_database_security_label :=
[ TABLESAMPLE tablesample_method(tablesample_value) |
EXPORTED WITH FUNCTION <function> |
NULL
]

Описание:

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

Работа с ролью

Синтаксис:

SECURITY LABEL FOR anon ON ROLE role_name IS 'anon_role_security_label';

anon_role_security_label :=
[ MASKED |
MASKED DUMP |
NULL
]

Описание:

  1. Задание признака маскирования для роли.
  2. Задания признака маскирования данных при экспорте для роли.
  3. Удаление признака маскирования для роли.

Работа со схемой

Синтаксис:

SECURITY LABEL FOR anon ON SCHEMA schema_name IS 'anon_schema_security_label';

anon_schema_secuirty_label :=
[ TRUSTED |
NULL
]

Описание:

  1. Задание признака доверенной схемы (данные должны быть скрыты для пользователя с признаком MASKED). По умолчанию устанавливается на схемы anon, pg_catalog, information_schema. Установка на схему, обозначенную параметром anon.maskedschema не допускается.
  2. Удаление признака доверенной схемы.

Работа с отношением

Синтаксис:

SECURITY LABEL FOR anon ON TABLE [schema_name.]relation_name IS 'anon_relation_security_label';

anon_relation_secuirty_label :=
[ TABLESAMPLE tablesample_method(tablesample_value) |
NULL
]

Описание:

  1. Задание процента выборки отношения при выполнении статического маскирования.
  2. Удаление процента выборки отношения при выполнении статического маскирования.

Работа с атрибутами

Синтаксис:

SECURITY LABEL FOR anon ON COLUMN [[schema_name.]relation_name.]column_name IS 'anon_column_security_label';

anon_relation_secuirty_label :=
[ NOT MASKED |
MASKED WITH FUNCTION function_name(function_args) |
MASKED WITH VALUE $$value$$ |
NULL
]

Описание:

  1. Задание признака «атрибут не маскируется». В отличие от отсутствия признака — атрибут не будет маскироваться при настройке anon.privacy_by_default=true.
  2. Устанавливаются признаки маскирования по функции.
  3. Признак маскирования по значению. Допускается использование immutable-функции по имени атрибута, например: MASKED WITH VALUE substring(<column>,1,2)||'****'.
  4. Удаление признака маскирования.

Работа с атрибутами

SECURITY LABEL FOR k_anonymity_provider ON COLUMN [[schema_name.]relation_name.]column_name IS 'k-anonimity_column_security_label';

k-anonimity_relation_secuirty_label :=
[ QUASI IDENTIFIER | INDIRECT IDENTIFIER |
NULL
]

Описание:

  1. Задание атрибута k-anonimity: quasi identifier (indirect identifier).
  2. Удаление атрибута k-anonimity.

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

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

generator.py

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

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 - количество генерируемых элементов):

  • Генерация ИНН:

    INSERT INTO anon.inn(val) SELECT anon.random_inn10() FROM generate_series(1,:nr_fake_records) id;
    Пример

    Генерируется только ИНН юридического лица. При необходимости генерировать ИНН физического лица используйте функцию anon.random_inn12().

  • Генерация СНИЛС:

    INSERT INTO anon.snils(val) SELECT anon.random_snils() FROM generate_series(1,:nr_fake_records) id;
  • Генерация ОГРН:

    INSERT INTO anon.ogrn(val) SELECT anon.random_ogrn() FROM generate_series(1,:nr_fake_records) id;
    Пример

    Генерируется только ОГРН юридического лица. При необходимости генерировать ОГРН физического лица используйте функцию anon.random_ogrnip().

  • Генерация ФИО:

    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;

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

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

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

  • anon.anonymize_column();
  • anon.anonymize_table();
  • anon.anonymize_database().

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

Обозначения

Далее в примере:

  • anon_feat - база данных;
  • anon_user - пользователь для обфускации;
  • anon_admin - пользователь для логического дампа.
  1. Настройте базу данных для использования функциональности обфускации:

    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;
  2. Создайте пользователей для работы с динамической типизацией и для работы с логическими копиями:

    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 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();
  3. Создайте схему. Пометьте схему, как TRUSTED. Только функции из схемы с атрибутом TRUSTED могут быть использованы для обфускации:

    CREATE SCHEMA IF NOT EXISTS my_anon;
    SECURITY LABEL FOR anon ON SCHEMA my_anon IS 'TRUSTED';
  4. Создайте функцию обфускации (elem - массив символов для генерации и max_len - длина генерируемого массива):

    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
    PARALLEL RESTRICTED
    SECURITY INVOKER
    SET search_path=''
    ;
  5. Выполните проверку работоспособности:

    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)
  6. Создайте тестовые данные для проверки

    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;
  7. Назначьте вновь созданную функцию для обфускации:

    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 │ -3180{major_version}.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 │
    │ 90{major_version}49 │ Ладислав │ wЮIбnиCцD6 │ +7 9042466198 │ 39378.97 │
    └───────┴───────────┴────────────┴───────────────┴───────────┘
    (10 rows)
    RESET ROLE;
  8. На основе функции обфускации создайте функцию псевдонимизации и таблицу подстановок для нее (seed anyelement - значение для подстановки. Одинаковое значение генерирует одинаковый hash; salt - для хеширования, при необходимости):

    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,
    salt text DEFAULT NULL::text
    )
    RETURNS text
    AS $$
    SELECT COALESCE(val,anon.notice_if_not_init())
    FROM my_anon.noise
    WHERE oid = anon.projection_to_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 ''
    ;
    примечание

    Функция anon.projection_to_oid генерирует остаток от деления вычисляемого хеша на количество элементов подстановок. Из таблицы подстановок выбирается элемент с oid, соответствующего остатку.

  9. Назначьте вновь созданную функцию для обфускации:

    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;
    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 │ -3180{major_version}.35 │
    │ 26149 │ Прохор │ lii5ыцcY1Р │ +7 2612167258 │ -22276.37 │
    │ 20473 │ Марфа │ кkCбXээж5_ │ +7 2053565418 │ -26932.25 │
    │ 89709 │ Полина │ yjьЮЩЁ<Dgи │ +7 8949979071 │ 41891.58 │
    │ 90{major_version}49 │ Ладислав │ 50jY3ВвЛЬХ │ +7 9042466198 │ 39378.97 │
    └───────┴───────────┴────────────┴───────────────┴───────────┘
    (10 rows)

    Вывод второго SELECT аналогичен первому. Псевдономизация работает.

  10. Пример комбинированного использования функций:

    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)';

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

  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.

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

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

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;
Внимание!

Команда RESET session_preload_libraries используется в случае, если session_preload_libraries для БД содержит только расширение anon. В ином случае — убрать расширение из списка предзагружаемых библиотек.

Для удаления rpm/deb-пакета расширения anon используйте соответствующий пакетный менеджер вашей операционной системы. Пример команды для ОС SberLinux:

sudo dnf remove pangolin-pg-anon.x86_64

Сценарии использования

Обозначения

Далее в примере:

  • anon_feat - основная база данных;
  • anon_feat_load_dump - база данных для загрузки дампа;
  • anon_user - пользователя для обфускации;
  • anon_dump - пользователя для логического дампа.

Подготовка

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

  2. Создайте\определите пользователей для работы с сенситивными данными. Установите метки маскирования:

    CREATE USER anon_user;
    SECURITY LABEL FOR anon ON ROLE anon_user IS 'MASKED';
  3. Создайте таблицу и сгенерируйте в нее данные из таблиц подстановок:

    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() * 50000)::numeric(17,2)
    FROM fname
    NATURAL JOIN lname
    ORDER BY random() LIMIT 1e5;

    Пример вывода первых 10 записей:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  |  lastname   |    phone     | balance
    ----+-------------+-------------+--------------+----------
    1 | Евстигней | Сазонов | +7 3608378 | 833.42
    2 | Парфен | Журавлев | +7 13157505 | 9589.34
    3 | Пантелеймон | Матвеев | +7 57541811 | 8597.14
    4 | Лука | Никифоров | +7 69390590 | 9094.27
    5 | Бронислав | Архипов | +7 84119655 | 17099.09
    6 | Ярополк | Сафонов | +7 85255937 | 28892.27
    7 | Орест | Белоусов | +7 87013575 | 46044.29
    8 | Болеслав | Назаров | +7 87945946 | 10744.86
    9 | Фрол | Андреев | +7 93773071 | 5445.29
    10 | Нонна | Панова | +7 108257250 | 5093.01
    (10 rows)

Использование динамического маскирования методом искажения

  1. Выполните подготовку.

  2. Настройте использование динамического маскирования:

    SELECT anon.start_dynamic_masking();
  3. Задайте правило метода искажения. Пример правила:

    SECURITY LABEL FOR anon ON COLUMN public.people.balance IS 'MASKED WITH FUNCTION anon.noise(balance,0.25)';

    Вывод под пользователем с меткой MASKED:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  | lastname  |    phone     | balance
    ----+-------------+-----------+--------------+----------
    1 | Евстигней | Сазонов | +7 3608378 | 879.99
    2 | Парфен | Журавлев | +7 13157505 | 9647.95
    3 | Пантелеймон | Матвеев | +7 57541811 | 8729.25
    4 | Лука | Никифоров | +7 69390590 | 10491.67
    5 | Бронислав | Архипов | +7 84119655 | 14333.70
    6 | Ярополк | Сафонов | +7 85255937 | 33957.44
    7 | Орест | Белоусов | +7 87013575 | 36534.12
    8 | Болеслав | Назаров | +7 87945946 | 9372.26
    9 | Фрол | Андреев | +7 93773071 | 5221.42
    10 | Нонна | Панова | +7 108257250 | 6090.74
    (10 rows)

    Как можно заметить по выводу, данные из столбца balance подверглись искажению и имеют не исходный вид.

Пример

Для отмены\удаления заданного правила воспользуйтесь командой SECURITY LABEL FOR anon ON COLUMN public.people.balance IS NULL;

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

  1. Выполните подготовку.

  2. Настройте использование динамического маскирования (описание процесса настройки находится в одноименном разделе данного документа).

  3. Задайте правило метода частичного маскирования. Пример правила:

    SECURITY LABEL FOR anon ON COLUMN public.people.phone IS 'MASKED WITH FUNCTION anon.concat(anon.substr(phone, 1, 3), $$*******$$)';

    Вывод под пользователем с меткой MASKED:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  | lastname  |   phone    | balance
    ----+-------------+-----------+------------+----------
    1 | Евстигней | Сазонов | +7 ******* | 833.42
    2 | Парфен | Журавлев | +7 ******* | 9589.34
    3 | Пантелеймон | Матвеев | +7 ******* | 8597.14
    4 | Лука | Никифоров | +7 ******* | 9094.27
    5 | Бронислав | Архипов | +7 ******* | 17099.09
    6 | Ярополк | Сафонов | +7 ******* | 28892.27
    7 | Орест | Белоусов | +7 ******* | 46044.29
    8 | Болеслав | Назаров | +7 ******* | 10744.86
    9 | Фрол | Андреев | +7 ******* | 5445.29
    10 | Нонна | Панова | +7 ******* | 5093.01
    (10 rows)

    Как можно заметить по выводу, данные из столбца telephone подверглись частичной маскировке и имеют не исходный вид.

Пример

Для отмены\удаления заданного правила воспользуйтесь командой SECURITY LABEL FOR anon ON COLUMN public.people.balance IS NULL;

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

Внимание!

Статическое маскирование предполагает безвозвратное изменение исходных данных. При наличии желания сохранить исходную информацию — выполните резервную копию информации (pg_dump).

  1. Выполните подготовку.

  2. Отключите использование динамического маскирования, если настраивали ранее:

    SELECT anon.stop_dynamic_masking();
    SELECT anon.remove_masks_for_all_columns();
  3. Задайте правило метода перетасовки. Пример правила:

    SELECT anon.shuffle_column('people','lastname','id');

    Вывод под пользователем с меткой MASKED:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  |  lastname   |    phone     | balance
    ----+-------------+-------------+--------------+----------
    1 | Евстигней | Селиверстов | +7 3608378 | 833.42
    2 | Парфен | Сидоров | +7 13157505 | 9589.34
    3 | Пантелеймон | Шилова | +7 57541811 | 8597.14
    4 | Лука | Попова | +7 69390590 | 9094.27
    5 | Бронислав | Калашников | +7 84119655 | 17099.09
    6 | Ярополк | Кудряшов | +7 85255937 | 28892.27
    7 | Орест | Елисеев | +7 87013575 | 46044.29
    8 | Болеслав | Куликов | +7 87945946 | 10744.86
    9 | Фрол | Гришин | +7 93773071 | 5445.29
    10 | Нонна | Ермакова | +7 108257250 | 5093.01
    (10 rows)

    Как можно заметить по выводу, данные из столбца lastname подверглись перетасовке и имеют не исходный вид.

Выгрузка логического дампа БД с обфусцированными данными

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

  2. Создайте пользователя и назначьте метку маскирования для выполнения выгрузки данных:

    CREATE USER anon_dump SUPERUSER;
    SECURITY LABEL FOR anon ON ROLE anon_dump IS 'MASKED DUMP';
  3. Создайте таблицу и сгенерируйте в нее данные из таблиц подстановок:

    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() * 50000)::numeric(17,2)
    FROM fname
    NATURAL JOIN lname
    ORDER BY random() LIMIT 1e5;

    Пример вывода первых 10 записей:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  |  lastname  |    phone     | balance
    ----+-------------+------------+--------------+---------
    1 | Мефодий | Савин | +7 13941577 | 6.97
    2 | Матвей | Туров | +7 18799976 | 9.40
    3 | Валерия | Симонова | +7 21774514 | 10.89
    4 | Бронислав | Никифоров | +7 28985738 | 14.49
    5 | Еремей | Власов | +7 32768119 | 16.38
    6 | Арсений | Пономарев | +7 33750744 | 16.88
    7 | Агафон | Ефимов | +7 51421805 | 25.71
    8 | Архип | Овчинников | +7 62573511 | 31.29
    9 | Радим | Смирнов | +7 65450{major_version}54 | 32.73
    10 | Влас | Богданов | +7 7160{major_version}211 | 35.80
  4. Примените правило динамического маскирования метода подмены константой:

    ALTER DATABASE anon_feat SET anon.transparent_dynamic_masking='true';
    SELECT anon.start_dynamic_masking();
    SECURITY LABEL FOR anon ON COLUMN public.people.lastname IS 'MASKED WITH VALUE $$*PVT*$$';
  5. Выполните выгрузку. Пример запроса:

    pg_dump -U anon_dump --password --no-security-labels --exclude-schema=anon,mask -f anon_feat_dump_1.sql anon_feat
  6. Для проверки выполните создание базы данных и загрузку выполненного дампа:

    createdb anon_feat_load_dump
    psql --table=people -f anon_feat_dump_1.sql anon_feat_load_dump

    Вывод из таблицы базы данных anon_feat_load_dump:

    SELECT * FROM people ORDER BY id LIMIT 10;
     id |  firstname  | lastname |    phone     | balance
    ----+-------------+----------+--------------+---------
    1 | Мефодий | *PVT* | +7 13941577 | 6.97
    2 | Матвей | *PVT* | +7 18799976 | 9.40
    3 | Валерия | *PVT* | +7 21774514 | 10.89
    4 | Бронислав | *PVT* | +7 28985738 | 14.49
    5 | Еремей | *PVT* | +7 32768119 | 16.38
    6 | Арсений | *PVT* | +7 33750744 | 16.88
    7 | Агафон | *PVT* | +7 51421805 | 25.71
    8 | Архип | *PVT* | +7 62573511 | 31.29
    9 | Радим | *PVT* | +7 65450{major_version}54 | 32.73
    10 | Влас | *PVT* | +7 7160{major_version}211 | 35.80

    Как можно заметить по выводу, данные из столбца lastname подверглись подмене и имеют не исходный вид.