pg_anon#

pg_anon

pg_anon

pg_anon — Инструмент анонимизации для PostgreSQL

Введение

Большинство IT-компаний хранят и обрабатывают данные, представляющие собой коммерческую тайну или содержащие личную информацию пользователей. Эти две группы данных могут называться "чувствительными". Личные данные включают: контактные телефоны, паспортную информацию и т.д. "О персональных данных" Федеральный закон № 152-ФЗ от 27 июля 2006 года действует в Российской Федерации. Пункт 2 статьи 5 гласит, что обработка персональных данных должна быть ограничена достижением конкретных, заранее определенных и законных целей, а в соответствии со статьей 6 обработка персональных данных должна осуществляться с согласия субъекта персональных данных. Все это накладывает ряд ограничений на процессы разработки программного обеспечения и особые требования к обработке данных, хранящихся в промышленной среде.

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

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

Глоссарий

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

Словарь - файл с расширением .py, содержащий объект, описывающий таблицы, поля и методы для замены значений этих полей. Файл словаря может быть создан вручную разработчиком или сгенерирован автоматически.

Мета-словарь - файл .py, содержащий объект, описывающий правила поиска персональных (чувствительных) данных. Мета-словарь создается вручную пользователем. Затем, на основе мета-словаря, создается словарь. Процесс автоматического создания словаря называется разведкой.

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

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

Анонимизация (маскировка) - процесс клонирования базы данных, состоящий из этапов dump -> restor во время которого чувствительные данные заменяются случайными или хешированными значениями.

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

Краткое описание pg_anon

pg_anon - это автономная программа, написанная на Python, предназначенная для работы с PostgreSQL (начиная с версии 9.6 и выше), которая выполняет следующие задачи:

  • Создание схемы anon_funcs, которая содержит библиотеку функций для анонимизации

  • Поиск персональных (чувствительных) данных в базе данных Tantor BE на основе мета-словаря

  • Создание словаря на основе результатов поиска (рекогносцировка)

  • Сохранение и восстановление с использованием словаря. Для разных баз данных можно предоставить отдельные файлы словаря

  • Синхронизация содержимого или структуры указанных таблиц между исходной и целевой базами данных

Визуальное представление терминов

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

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

Когда каталог размещается на хосте с целевой базой данных, процесс восстановления должен быть запущен с указанными учетными данными целевой базы данных. Целевая база данных должна быть заранее подготовлена с помощью команды CREATE DATABASE и не должна содержать никаких объектов. Если в целевой базе данных есть пользовательские таблицы, процесс восстановления не будет запущен. Когда восстановление успешно завершено, база данных будет готова для разработки или тестирования задач, во время которых любое количество сотрудников может подключаться к базе данных без риска утечки конфиденциальных данных.

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

Есть два процесса создания словаря:

  • Создание словаря вручную

  • Автоматическое создание словаря

Диаграмма ниже показывает оба процесса создания словаря.

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

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

Как улучшить поиск конфиденциальных данных (PoC)

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

Тег :sens можно добавить в репозиторий проекта или непосредственно в базу данных. Важно отметить, что если тег был добавлен в репозиторий, приложение для развертывания миграций должно доставлять команды COMMENT ON COLUMN в промышленную среду.

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

Если таблица была полностью просканирована на основе мета-словаря и содержит более 1K строк, поля этой таблицы могут быть автоматически помечены комментариями :sens и :no_sens. Это позволит сканировать только новые поля без повторного сканирования всей базы данных.

Если поля уже содержат комментарии, требуемый тег :sens или :no_sens будет добавлен к существующим комментариям в следующем формате:

COMMENT ON COLUMN users.addr
IS 'This field contains email addresses:sens';

При повторном сканировании базы данных можно указать словарь, полученный из предыдущей итерации сканирования, что позволит пропустить значительное количество ранее просканированных объектов, удовлетворяющих условию "количество строк превышает 1K".

Первый запуск

Требования для запуска pg_anon:

  1. Операционная система Linux

  2. Установленная версия Python 3.8 и выше

  3. Установленные модули из файла requirements.txt

  4. asyncpg - драйвер PostgreSQL

  5. aioprocessing - предоставляет асинхронные и совместимые с asyncio версии множества блокирующих методов объектов из библиотеки multiprocessing. Используется для параллелизации работы сканирования базы данных при использовании мета-словаря.

  6. nest-asyncio - предоставляет возможность создания вложенного цикла событий. Это необходимо в режиме многопроцессорной работы для использования драйвера asyncpg

Установленные клиентские приложения Tantor BE (pg_dump и pg_restore устанавливаются менеджером пакетов с помощью apt -y install postgresql-15 postgresql-client-15). Клиентские приложения должны соответствовать версии исходной базы данных или быть более новыми

cd pg_anon

python3 --version
>>
    Python 3.8.10 # python3 >= 3.8 OK

pip3 install -r requirements.txt
python3 pg_anon.py --version

>>

    2022-10-09 15:45:46,260     INFO 324 - Version 22.10.7
    # version format: year.month.day

ls log
>>
    init.log
    cat log/init.log 
>>

    2022-10-09 15:45:46,260     INFO 324 - Version 22.10.7

Во время каждого запуска pg_anon лог-файлы регистрируются в директории log с добавлением параметров запуска в имя файла. Размер каждого лог-файла не превышает 10 МБ. Это значение можно изменить в конструкторе класса Context (файл pg_anon.py).

Структура проекта и каталогов

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

├── common.py       # common functions and classes
├── create_dict.py  # functionality of scanning the database based on 
meta-dictionaries
├── dict            # dictionaries and meta-dictionaries directory
│   ├── ...
├── docker          # files for building docker image
├── dump.py         # database dump functionality
├── init.sql        # library of anonymization functions
├── log             # log directory
│   └── init.log
├── output          # directory for for recording database files when dumping data
│   ├── ...
├── pg_anon.py      # main file (reading command line parameters)
├── requirements.txt    # list of modules that pg_anon depends on
├── restore.py      # restore functionality
├── test            # unit tests files directory
│   ├── full_test.py    # general testing script
│   ├── init_env.sql    # SQL commands for initializing the test environment
│   ├── __init__.py 
│   └── init_stress_env.sql # testing environment for stress testing (many objects, multiple data)

Словари не обязательно должны находиться в каталоге dict. Этот каталог используется по умолчанию, если путь к словарям не указан полностью.

Перед началом

Все режимы работы pg_anon покрыты тестами для обеспечения корректности функциональности в различных средах. Перед тем, как начать работу с новой версией pg_anon, рекомендуется проверить функциональность модульных тестов (это займет несколько минут):

psql -c "CREATE USER anon_test_user WITH PASSWORD '123456' SUPERUSER" -U postgres

set TEST_DB_USER=anon_test_user
set TEST_DB_USER_PASSWORD=123456
set TEST_DB_HOST=127.0.0.1
set TEST_DB_PORT=5432
set TEST_SOURCE_DB=test_source_db
set TEST_TARGET_DB=test_target_db
set TEST_SCALE=10   # number of test data. default value is 10
su - postgres       # running tests as postgres
python3 test/full_test.py -v
>>
    Ran N tests in ...
    OK

Данные для входа в тестовые базы данных указываются через переменные среды с префиксом TEST_, как показано выше. Скрипт тестирования предполагает, что базы данных "source" и "target" находятся на одном экземпляре, а pg_anon работает на том же хосте.

Успешное завершение всех тестов гарантирует правильную работу pg_anon в используемой среде (Python, Tantor BE, ОС). Если возникает следующая ошибка:

asyncpg.exceptions.ExternalRoutineError: program "gzip > .../test/...abcd.dat.gz" failed

это означает, что пользователь postgres не имеет достаточных прав для доступа к каталогу, в который записывает gzip. Другими словами, pg_anon был запущен определенным пользователем, затем был создан каталог вывода с этим пользователем в качестве владельца, а затем Tantor BE попытался записать файл в этот каталог от имени postgres. Ошибка может быть исправлена следующим образом:

usermod -a -G current_user_name postgres
chmod -R g+rw /home/current_user_name/Desktop/pg_anon
chmod g+x /home/current_user_name/Desktop/pg_anon/output/test
# authorization validation
su - postgres
touch /home/current_user_name/Desktop/pg_anon/test/1.txt

В тестовом скрипте количество данных определяется переменной сессии TEST_SCALE. В файле init_env.sql используется константа 1512 для генерации вставляемых данных. Если TEST_SCALE=10, то в каждую таблицу будет вставлено 151200 строк.

Также есть отдельный тест на нагрузку init_stress_env.sql, во время которого создается определенное количество таблиц. По умолчанию, тестирование на нагрузку выполняется на небольшом количестве таблиц и данных. Чтобы запустить полноценное тестирование на нагрузку на длительный период времени, вам следует вручную изменить количество итераций цикла и объем записываемых данных в init_stress_env.sql. Затем выполните запуск тестирования на нагрузку.

python3 test/full_test.py -v 
PGAnonDictGenStressUnitTest
>>
    Ran 1 tests in ...
    OK

Параметры и режимы работы pg_anon

Общие параметры для всех режимов работы* *:

--debug    # debug mode (default *false*)
--verbose = [debug, info, warning, error]   # verbosity level (default *info*). 
All messages are duplicated to stdout and to the log file. 
The *debug* level requires indicating *--debug* option of the same name
--threads  # the number of threads should not be higher than the number of available CPU cores, by default - 4 threads

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

Режимы работы определяются параметром ``--mode``:

--mode=init                 # initialize the function library for anonymization in the source database
--mode=create-dict          # scan db using the meta-dictionary
--mode=dump                 # dump the database using the dictionary
--mode=restore              # load anonymized data into the target database
--mode=sync-data-dump       # dump only the specified tables
--mode=sync-data-restore    # perform data-only restore
--mode=sync-struct-dump     # perform a dump of the database structure for only the specified tables
--mode=sync-struct-restore  # restore the database structure

Параметры подключения к БД (для каждого режима работы):

--db-host               # host for connecting to the database (source or target)
--db-port               # port
--db-name               # DB name of source or target
--db-user               # user with *SUPERUSER* privileges (required for executing COPY commands). 
The *COPY* command is only allowed for superusers of the database or users who have been granted one 
of the roles: *pg_read_server_files*, *pg_write_server_files*, or *pg_execute_server_program*, 
as it allows reading or writing any file or running a program to which the server has access rights
--db-user-password      # password for connecting to the database (source or target).
The password can also be passed through the standard *PGPASSWORD* variable. 
The *pg_dump* and *pg_restore utilities*, launched by the *pg_anon* program, 
accept the password in any case through the *PGPASSWORD* variable.
--db-passfile           # path to *.pgpass* file containing passwords for connection
--db-ssl-key-file       # private key of the server for encrypted connection. 
All key files for secure connection must have correct permissions - chmod 600
--db-ssl-cert-file      # server certificate for encrypted connection
--db-ssl-ca-file        # CA certificate file. Allows you to verify that 
the client's certificate is signed by a trusted certification authority

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

pg_anon развертывание: локально с базой данных

Самый удобный способ развертывания pg_anon - это метод, показанный на диаграмме:

pg_anon, при работе в режиме дампа, записывает данные в каталог output. В режиме дампа база данных записывает файлы *.dat.gz в тот же каталог с использованием команды COPY TO от имени пользователя postgres. Имя каталога основано на имени словаря, используемого для дампа, то есть если для дампа использовался словарь some_dict.py, данные будут записаны в каталог с именем some_dict. Данные записываются в каталог с помощью двух процессов: pg_anon и СУБД.

pg_anon развертывание: удаленно

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

Если pg_anon запущен для дампа на отдельном хосте, каталог разделяется при создании дампа, как показано на диаграмме:

Это означает, что база данных записывает данные в локальный каталог, в то время как pg_anon записывает в каталог на том же отдельном хосте. Поэтому оба хоста должны иметь одинаковый каталог.

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

Инициализация библиотеки функций для анонимизации

Перед выполнением первоначального дампа базы данных, библиотека функций для анонимизации должна быть инициализирована с использованием опции --mode=init. Ниже приведена диаграмма, показывающая пример запуска pg_anon в режиме инициализации. Пароль может быть установлен отдельно с помощью переменной окружения PGPASSWORD.

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

Описание библиотеки функций для анонимизации

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

# add noise to a floating-point number
SELECT anon_funcs.noise(100, 1.2)
>> 123
# add noise to a date or date and time
SELECT anon_funcs.dnoise('2020-02-02 10:10:10'::timestamp, interval '1 month')
>> 2020-03-02 10:10:10
# hash a string value with a specified hash function
SELECT anon_funcs.digest('text', 'salt', 'sha256')
>> '3353e....'
# keep only the first few characters (2nd argument) and the last few characters 
(4th argument) in a specified string, adding a constant (3rd argument) between them
SELECT anon_funcs.partial('123456789',1,'***',3)
>> 1***789
# mask the email address
SELECT anon_funcs.partial_email('[email protected]')
>> ex******@gm******.com
# generate a random string of specified length
SELECT anon_funcs.random_string(7)
>> H3ZVL5P
# generate a random postal code
SELECT anon_funcs.random_zip()
>> 851467
# generate a random date and time within a specified interval
SELECT anon_funcs.random_date_between(
    '2020-02-02 10:10:10'::timestamp,
    '2022-02-05 10:10:10'::timestamp
)
>> 2021-11-08 06:47:48.057

# generate a random date and time
SELECT anon_funcs.random_date()
>> 1911-04-18 21:54:13.139

# random int number within a specified interval
SELECT anon_funcs.random_int_between(100, 200)
>> 159

# random bigint number within a specified interval
SELECT anon_funcs.random_bigint_between(6000000000, 7000000000)
>> 6268278565

# generate a random phone number
SELECT anon_funcs.random_phone('+7')
>> +7297479867

# generate a random hash using a specified function
SELECT anon_funcs.random_hash('seed', 'sha512')
>> a06b3039...

# select random elements from an array
SELECT anon_funcs.random_in(array['a', 'b', 'c'])
>> a

# convert a hexadecimal value to decimal
SELECT anon_funcs.hex_to_int('8AB')
>> 2219

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

CREATE EXTENSION IF NOT EXISTS pgcrypto;

select encode((select encrypt('data', 'password', 'bf')), 'base64') 
>>
    cSMq9gb1vOw=

select decrypt(
(
select decode('cSMq9gb1vOw=', 'base64')
), 'password', 'bf')
>>
    data

Новые функции анонимизации добавляются путем добавления их в файл init.sql и последующего вызова pg_anon в режиме --mode=init.

create-dict режим операции: разведка

  1. создать мета-словарь

  2. выберите целевую базу данных, содержащую конфиденциальные данные

  3. начните сканирование, указав --mode=create-dict

python3 pg_anon.py \

    ... # DB connection parameters
    --scan-mode=full # [partial, full] full or partial scan
    --dict-file=meta_dict.py # meta-dictionary file
    --output-dict-file=out_dict.py # the resulting dictionary will be saved to the directory *dict/out_dict.py*
    --scan-partial-rows=10000 # number of rows to be processed within one operation
    --mode=create-dict
    ----threads=8     # number of child processes to process the data read in a single transaction      

Сканирование может быть полным или частичным, в зависимости от параметра --scan-mode. Если выбрано частичное сканирование, будет прочитано только первые --scan-partial-rows в каждой таблице в одной транзакции. Это позволяет ускорить сканирование. Если выбрано полное сканирование, --scan-partial-rows будет читаться в каждой транзакции до полного прочтения всей таблицы.

После завершения сканирования будет создан словарь с указанным именем в теге --output-dict-file. Если указано только имя файла, словарь будет сохранен в каталоге dict.

Режим операции create-dict: формат мета-словаря

Структура мета-словаря содержит следующие объекты:

{

   "field": {   # which fields to anonymize without scanning the content?
      "rules": [    # list of regular expressions to search for fields by name
         "^fld_5_em",
         "^amount"
      ],

      "constants": [   # list of constant field names
         "usd",
         "field_name"
      ]
   },

   "skip_rules": [   # list of schemas, tables and fields to ignore
      {...}   # perhaps some schema or table contains a lot of data that does not make sense to scan
Skipped objects will not be automatically included in the resulting dictionary
Masks are not supported in this object.
        "schema": "schm_mask_ext_exclude_2",  # Schema specification is mandatory
         "table": "card_numbers",    # Optional. If there is no "table", the entire schema will be skipped
         "fields": ["val_skip"]      # Optional. If there are no "fields", the entire table will be skipped
      }
    ],
   "data_regex": {     # List of regular expressions to search for sensitive data
      "rules": [
           # email
           """([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+""",
           # phone 7XXXXXXXXXX  
          "7?[\d]{10}"
      ]
   },
   "data_const": {   # List of lowercase constants that, when found, will add the field to the resulting dictionary.
    If a text field contains a value consisting of multiple words, the value will be split into words, converted to lowercase, and matched against the constants in this list.
    Words shorter than 5 characters are ignored. The search is performed using set.intersection  
      "constants": [  # When reading the meta-dictionary, the values in this list are placed in a set container
         "bank",
         "account",
         "email",
         "word"
      ]
   },
   "funcs": {...} # list of field types *(int, text, ...)* and functions for anonymization
              # If a field is found during scanning, the function listed in this list will be used according to its type.
      "text": "anon_funcs.digest(\"%s\", 'salt_word', 'md5')",
      "numeric": "anon_funcs.noise(\"%s\", 10)",
      "timestamp": "anon_funcs.dnoise(\"%s\",  interval '6 month')"
   }
}

Клонирование БД

Для выполнения анонимизации базы данных необходимо клонировать ее, последовательно вызывая pg_anon в режимах dump и restore.

режим операции dump

Чтобы записать анонимизированное содержимое базы данных в файлы, выполните команду pg_anon в режиме --mode=dump:

python3 pg_anon.py \
    ... # DB connection parameters
    --dict-file=some_dict.py # dictionary for anonymization
    --mode=dump
    --clear-output-dir             # clear the output directory *output/some_dict*
    --pg_dump=/usr/bin/pg_dump     # default value
    --copy-options='WITH binary'   # # by default, no options are passed.
 See the https:/www.postgresql.org/docs/current/sql-copy.html for a description of the options

По умолчанию, при запуске дампа, каталог вывода не очищается, пока не будет указана опция --clear-output-dir.

В режиме дампа pg_anon выполняет следующие действия:

  1. Запускает утилиту pg_dump в режиме pre-data, передает параметры подключения к базе данных и имя выходного файла pre_data.backup. Раздел pre-data включает определения схем, таблиц, представлений, исключая индексы, целостность, триггеры и правила.

  2. Запускает pg_dump с теми же учетными данными подключения в режиме post-data и записывает результат в файл post_data.backup. Раздел post-data содержит определения индексов, триггеров, правил и ограничений (за исключением проверочных ограничений, созданных без использования NOT VALID).

  3. Читает указанный словарь, затем подключается к исходной базе данных и создает список всех таблиц, исключая системные схемы anon_funcs и columnar_internal, список которых определен в классе Context.

  4. Каждая найденная таблица сопоставляется с словарем, и если таблица не найдена, ее содержимое записывается в файл "как есть" с использованием команды COPY. При чтении содержимого базы данных открывается транзакция в режиме изоляции repeatable read, в рамках которой выполняются несколько одновременных команд COPY в зависимости от параметра --threads. Другими словами, содержимое таблиц записывается полностью последовательно. Каждый записанный файл сжимается отдельной программой gzip и записывается в файл с расширением *.dat.gz. Имя файла использует хэш имени схемы и имени таблицы, так как исходные имена таблиц в базе данных могут содержать символы, не разрешенные в именах файлов.

  5. После копирования объектов создается файл metadata.json, который содержит метаданные о исходной базе данных и использованном словаре.

Генерация команды COPY в зависимости от соответствия таблицы правилам словаря:

# # General syntax of the COPY command
COPY { table_name [ ( column_name [, ...] ) ] | ( query ) }
    TO { 'filename' | PROGRAM 'command' | STDOUT }
    [ [ WITH ] ( option [, ...] ) ]

# # Copying the table "as is"
query = "COPY (SELECT * FROM %s %s) to PROGRAM 'gzip > %s' %s" % (
	table_name,
	(ctx.validate_limit if ctx.args.validate_full else ""),
	full_file_name,
	ctx.args.copy_options
)

# Copying the table if the raw_sql attribute is specified
query = "COPY (%s %s) to PROGRAM 'gzip > %s' %s" % (
	a_obj['raw_sql'],
	(ctx.validate_limit if ctx.args.validate_full else ""),
	full_file_name,
	ctx.args.copy_options
)

# Copying the table with sensitive data, the list of fields is generated based on the dictionary
query = "COPY (SELECT %s FROM %s %s) to PROGRAM 'gzip > %s' %s" % (
	sql_expr,
	table_name,
	(ctx.validate_limit if ctx.args.validate_full else ""),
	full_file_name,
	ctx.args.copy_options
)

Режим операции дампа: формат словаря

{
    "dictionary": [     # list of objects for anonymization
        {
            "schema":"schm_other_1", "table":"some_tbl", # specific table
            "fields": {"val":"'text const'"}  # the "field" object contains a function or SQL call
        },
        {
            "schema_mask": "^schm_mask_incl", "table_mask": "^some_t", # objects by mask
            # 
# it is allowed to use symbol *

"fields": {"val": "md5(val)"}

Режим операции dump: структура каталога вывода

python3 pg_anon.py \
    ...
    --mode=dump
/usr/share/pg_anon/output/some_dict
├── 0a7963b3...3b6b1.dat.gz	# anonymized data file
├── *.dat.gz			# 1 table = 1 dat.gz file
├── metadata.json		# metadata file (description of gz files, sequence states, software versions, statistics, etc.)
├── post_data.backup		# file created by pg_dump (foreign keys, indexes, ...)
└── pre_data.backup		#  file created by pg_dump (schemas, tables)

Формат файла metadata.json

Dumping also creates a metadata.json file that describes the directory contents

используется во время процесса восстановления для проверки версии целевой базы данных, версии утилиты pg_restore и доступного дискового пространства:

{
    "db_size": 44581667,    # source base size
    "created": "23/10/2022 15:48:17",  # date of dump creation
    "seq_lastvals": {       # sequence states for all tables that had them at the time of the dump
        "public.tbl_100_id_seq": {
            "schema": "public",
            "seq_name": "tbl_100_id_seq",
            "value": 15120
        }
    },

    "pg_version": "14.5",       # version of sourcedatabase
    "pg_dump_version": "14.5",  # version of *pg_dump* used to create the dump
    "dictionary_content_hash": "01f6f080...d10f6", # hash of the dictionary used when dumping
    "dict_file": "test.py",  # the name of the dictionary used in the dump
    "files": {   # object with a set of files, where attribute names are file names, one file per table 
        "0f1d55351607b9403a0350877da95c52.dat.gz": {
            "schema": "public",   # file description: scheme, table, number of rows
            "table": "tbl_100",
            "rows": "15120"
        }
    },

    "total_tables_size": 34029568,  # total table size used to estimate free space before restoring
    "total_rows": 317544    # number of rows of all tables
}

Полная и частичная проверка словаря

python3 pg_anon.py \
    ...         # Database connection parameters
    --mode=dump
    --validate-dict # check the dictionary by executing SQL queries with *LIMIT* without exporting data (the structure of the dictionary and the validity of SQL queries are checked)
    --validate-full # same as *validate-dict* + export to file

режим восстановления операции

python3 pg_anon.py \
    ...             # parameters for the DB connection
    --input-dir=some_dict   # directory with database files (or specify the full path)
    --mode=restore
    --pg_restore=/usr/bin/pg_restore   # default value
    --disable-checks        # disable various checks before uploading to the reciever
    --seq-init-by-max-value # initialize sequences based on maximum values
    --drop-custom-check-constr   # remove custom *check constraints*

Частичная синхронизация данных

Режим операции sync-data-dump

Задача : синхронизировать содержимое набора таблиц

python3 pg_anon.py \
    ...             # parameters for connecting to the database
    --dict-file=some_dict.py
    --mode=sync-data-dump

Режим операции sync-data-dump: формат словаря

Словарь указывает таблицы и поля для синхронизации структуры:

{
    "dictionary": [
        {
            "schema":"schm_other_2",
            "table":"exclude_tbl",
            "fields": {"val":"'text const modified'"}
        },
        {
            "schema":"schm_other_2",
            "table":"some_tbl",
            "raw_sql": "SELECT id, val || ' modified 2' as val FROM schm_other_2.some_tbl"
        }
    ],
    "dictionary_exclude": [{"schema_mask": "*", "table_mask": "*"}]
}

Операция восстановления данных синхронизации режима выполнения

Параметры запуска:

python3 pg_anon.py \
    ...             # DB connection parameters
    --input-dir=some_dict   # directory with database files (or specify the full path)
    --mode=restore
    --disable-checks        # disable various pre-boot checks before loading

Синхронизация структуры БД

Операция sync-struct-dump режима

Задача : синхронизация структуры указанного набора таблиц

python3 pg_anon.py \
    ...             # DB connection parameters 
    --dict-file=some_dict.py    # dictionary for transferring the database structure
    --mode=sync-struct-dump

sync-struct-dump режим операции: формат словаря

Словарь указывает только таблицы для синхронизации структуры:

{
    "dictionary": [
        {
            "schema":"schm_other_2",
            "table":"exclude_tbl"
        },
        {
            "schema":"schm_other_2",
            "table":"some_tbl"
        }
    ],
    "dictionary_exclude": [{"schema_mask": "*", "table_mask": "*"}]
}

Операция sync-struct-restore режима

Параметры запуска:

python3 pg_anon.py \
    ...             # the DB connection parameters
    --input-dir=some_dict   # directory with a partial database structure (or specify the full path)
    --mode=sync-struct-restore

Скрипты

Описание Действия

Клонировать всю базу данных (структуру + данные всех таблиц)

–mode=init (только при первом подключении к исходной базе данных)

–mode=create-dict (если вы хотите сгенерировать словарь, необязательно)

–mode=dump

–mode=restore

Клонировать часть базы данных (структура будет полностью перенесена, данные будут перенесены только для указанных таблиц)

–mode=init (только при первом подключении к исходной базе данных)

–mode=dump (указать словарь с dictionary_exclude * * разделом. Укажите некоторые таблицы в словаре)

–mode=restore

Клонирование части БД: определенный набор таблиц (только структура)

–mode=init (только при первом подключении к исходной базе данных)

-режим=синхронная-структура-дамп-

–mode=sync-struct-restore

Синхронизация данных определенного списка таблиц

–mode=init (только при первом подключении к исходной базе данных)

–mode=sync-data-dump

–mode=sync-data-restoree

Образ Docker

pg_anon может быть доставлен через docker. Это позволит избежать необходимости установки требуемой версии Python и используемых модулей.

cd pg_anon/docker
make PG_VERSION=15
docker tag $(docker images -q | head -n 1) pg_anon:pg15

Передача изображения

docker tag $(docker images -q | head -n 1) pg_anon:pg15
docker save -o pg_anon_22_9_12.tar pg_anon:pg15
docker load < pg_anon_22_9_12.tar    # download the image from the archive on the target host

Запуск

# If an error "The container name "/pg_anon" is already in use" occurs during startup
# docker rm -f pg_anon

docker run --name pg_anon -d pg_anon:pg14
docker exec -it pg_anon bash
python3 test/full_test.py -v
exit

# Run and mount directory from HOST to /usr/share/pg_anon_from_host
docker rm -f pg_anon
docker run --name pg_anon -v $PWD:/usr/share/pg_anon -d pg_anon:pg14

Отладка контейнеров

docker exec -it pg_anon bash
>>
    Error response from daemon: Container c876d... is not running

docker logs c876d...

# Fix errors in entrypoint.sh
# Set "ENTRYPOINT exec /entrypoint_dbg.sh" in Dockerfile

docker rm -f pg_anon
make PG_VERSION=14
docker tag $(docker images -q | head -n 1) pg_anon:pg14
docker run --name pg_anon -d pg_anon:pg14
docker exec -it pg_anon bash