pg_anon#

pg_anon

pg_anon

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

Обзор

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

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

О pg_anon

Версия: 1.5.0

GitHub

Терминология

Термин Описание
Исходная база данных (исходная БД) База данных, содержащая поля, которые необходимо анонимизировать.
Целевая база данных (целевая БД) Пустая база данных для --mode=restore. Она также может содержать структуру для --mode=sync-data-restore.
Персональные (конфиденциальные) данные Данные, содержащие информацию, которая не должна передаваться другим получателям хранения или третьим лицам и которая составляет коммерческую тайну.
Подготовленный файл словаря с конфиденциальными данными Файл с расширением *.py, содержащий объект, который описывает таблицы, поля и методы для замены значений этих полей. Файл словаря может быть написан вручную разработчиком или сгенерирован автоматически.
Подготовленный файл словаря без конфиденциальных данных Файл *.py содержит список объектов, структурированных как схема, таблица и поля. Этот словарь используется для повторного сканирования в режиме create-dict и может быть создан вручную разработчиком или сгенерирован автоматически. Использование этого словаря в режиме create-dict может значительно ускорить последующие сканирования.
Мета-словарь Файл с расширением *.py, содержащий объект, который описывает правила для идентификации персональных (конфиденциальных) данных. Мета-словарь создается вручную пользователем. На основе мета-словаря затем создается подготовленный файл конфиденциального словаря. Процесс автоматического создания словаря называется разведка или --mode=create-dict.
Создание словаря (или сканирование) Процесс сканирования исходной базы данных и поиска конфиденциальных полей на основе мета-словаря. В результате создается подготовленный файл sens dict и, при необходимости, подготовленный файл no sens dict.
Дамп Процесс записи содержимого исходной базы данных в файлы с использованием указанного словаря. Дамп может быть частичным или полным. На самом деле, это этап, на котором происходит маскирование.
Восстановление Процесс загрузки данных из файлов в целевую базу данных. Целевая база данных не должна содержать никаких объектов.
Анонимизация (маскирование) Процесс клонирования базы данных, состоящий из шагов dump -> restore, в ходе которого конфиденциальные данные будут заменены случайными или хешированными значениями.
Функция анонимизации Встроенная функция PostgreSQL или функция из схемы anon_funcs, которая изменяет входное значение на случайное или хешированное. Схема anon_funcs содержит готовую библиотеку функций. В эту схему можно добавлять новые функции для преобразования полей, подлежащих анонимизации, для последующего использования в словарях.

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

Анонимизация (маскировка)

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

Диаграмма G.2. Dump-Resore-Terms.drawio.png

Dump-Resore-Terms.drawio.png

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

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

P.S. --mode=sync-data-restore можно запустить в непустую целевую БД с подготовленной структурой.

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

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

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

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

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

Диаграмма G.3. Create-dict-Terms.drawio.png

Create-dict-Terms.drawio.png

Какую работу выполняет pg_anon во время дампа и восстановления? Самое простое представление.

Например, у нас есть данные, которые мы хотим анонимизировать:

  1. Создайте исходную таблицу:

    create table users (
        id bigserial,
        email text,
        login text
    );
    
    -- Checking the contents of the source table
    select * from users;
    
    >>
        id |  email  | login 
       ----+---------+-------
    
  2. Заполните исходную таблицу:

    insert into users (email, login)
    select
     'user' || generate_series(1001, 1020) || '@example.com',
     'user' || generate_series(1001, 1020);
    
    -- Checking the contents of the source table
    select * from users;
    
    >>
        id |       email          |  login   
       ----+----------------------+----------
         1 | [email protected] | user1001
         2 | [email protected] | user1002
        ...
    

Поле «email» содержит конфиденциальные данные. Нам нужно анонимизировать их.

Каков процесс создания дампа с маскированием?

  1. Создайте дамп данных из исходной таблицы в CSV файл (без маскирования):

    copy (
        select *
        from users
    ) to '/tmp/users.csv' with csv;
    
    cat /tmp/users.csv
    >>
       1,[email protected],user1001
       2,[email protected],user1002
       ...
    
  2. Запустите маскирование содержимого исходной таблицы:

    select
        id,
        md5(email) || '@abc.com' as email, -- hashing the email (masking rule in prepared sens dict file)
        login
    from users;
    
    >>
        id |                email                     |  login   
       ----+------------------------------------------+----------
         1 | [email protected] | user1001
         2 | [email protected] | user1002
        ...
    
  3. Создайте дамп данных из исходной таблицы в CSV файл (с маскированием):

    copy (
      select
        id,
        md5(email) || '@abc.com' as email, -- hashing the email (masking rule in prepared sens dict file)
        login
      from users
    ) to '/tmp/users_anonymized.csv' with csv;
    
    cat /tmp/users_anonymized.csv
    >>
       1,[email protected],user1001
       2,[email protected],user1002
       ...
    

Файл подготовленного sens dict содержит правила маскировки, такие как хеширование

Каков процесс восстановления замаскированного дампа?

  1. Воспроизведите структуру. Создайте целевую таблицу:

    create table users_anonymized (
        id bigserial,
        email text,
        login text
    );
    
    -- Checking the contents of the target table
    select * from users_anonymized;
    
    >>
        id |  email  | login 
       ----+---------+-------
    
  2. Загрузите данные исходной таблицы дампа данных (CSV файл) в целевую таблицу:

    copy users_anonymized
    from '/tmp/users_anonymized.csv'
    with csv;
    
    -- Checking the contents of the target table
    select * from users_anonymized;
    
    >>
        id |                email                     |  login   
       ----+------------------------------------------+----------
         1 | [email protected] | user1001
         2 | [email protected] | user1002
        ...
    

Различия между оригинальной работой pg_anon и этим представлением:

  • pg_anon работает со всей базой данных (а не только с одной таблицей)

  • pg_anon использует файлы .bin.gz для сохранения данных (а не csv)

  • Правила маскирования предоставляются pg_anon через подготовленный файл словаря чувствительных данных

Особенности

pg_anon работает в нескольких режимах:

  • init — создает схему anon_funcs с функциями анонимизации.

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

  • view-fields — отображает таблицу с полями, которые будут анонимизированы, и какие правила будут использоваться для этого. Таблица содержит schema, table, field, type, dict_file_name, rule поля, которые основаны на подготовленном чувствительном словаре.

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

  • дамп — создает дамп структуры базы данных с использованием инструмента pg_dump Postgres, и дампы данных с использованием запросов COPY ... с функциями анонимизации. Шаг дампа данных сохраняет данные локально в формате *.bin.gz. Во время этого шага данные анонимизируются на стороне базы данных с помощью anon_funcs.

  • восстановление — восстанавливает структуру базы данных с помощью инструмента pg_restore Postgres и данных из дампа в целевую БД. Режим восстановление может отдельно восстанавливать структуру базы данных или данные.

  • sync-struct-dump — создает дамп структуры базы данных с использованием инструмента Postgres pg_dump.

  • sync-data-dump — создает дамп данных базы данных с использованием запросов COPY ... с функциями анонимизации. Шаг создания дампа данных сохраняет данные локально в формате *.bin.gz. Во время этого шага данные анонимизируются на стороне базы данных с помощью anon_funcs.

  • sync-struct-restore — восстанавливает структуру базы данных с использованием инструмента Postgres pg_restore.

  • sync-data-restore — восстанавливает данные базы данных из дампа в целевую БД.

Требования и зависимости

pg_anon основан на Python3 и также требует сторонние библиотеки, перечисленные в requirements.txt.

Он использует следующие инструменты:

  • Tantor SE pg_dump инструмент для дампа структуры базы данных.

  • Tantor SE pg_restore инструмент для восстановления структуры базы данных.

Руководство по установке

Предварительные условия

Инструмент поддерживает Python3.11 и более новые версии. Код размещен в следующем репозитории: репозиторий pg_anon на Github.

Инструкции по установке

Процессы установки немного различаются в зависимости от вашей операционной системы.

macOS

  1. Установите Python3, если он не установлен:

  2. Клонируйте репозиторий: git clone https://github.com/TantorLabs/pg_anon.git.

  3. Перейдите в каталог проекта: cd pg_anon.

  4. Настройте виртуальную среду:

    • Установите виртуальную среду: python3 -m venv venv.

    • Активируйте виртуальную среду: source venv/bin/activate.

  5. Установите зависимости: pip install -r requirements.txt.

Ubuntu/Redhat/CentOS

  1. Установите Python3, если он не установлен: sudo apt-get install python3.11 (для Ubuntu), sudo yum install python311 (для Redhat/Centos).

  2. Клонируйте репозиторий: git clone https://github.com/TantorLabs/pg_anon.git.

  3. Перейдите в каталог проекта: cd pg_anon.

  4. Настройте виртуальную среду:

    • Установите виртуальную среду: python3 -m venv venv.

    • Активируйте виртуальную среду: source venv/bin/activate.

  5. Установите зависимости: pip install -r requirements.txt.

Windows 7/Windows 11

  1. Установите Python3, если он не установлен: Скачайте его с официального сайта Python.

  2. Клонируйте репозиторий: git clone https://github.com/TantorLabs/pg_anon.git.

  3. Перейдите в каталог проекта: cd pg_anon.

  4. Настройте виртуальную среду:

    • Установите виртуальную среду: py -m venv venv.

    • Активируйте виртуальную среду: .\venv\Scripts\activate.

  5. Установите зависимости: pip install -r requirements.txt.

Тестирование

Чтобы протестировать pg_anon, вам необходимо иметь установленную локальную базу данных. Этот раздел охватывает установку postgres и запуск набора тестов.

Настройка PostgreSQL

Чтобы облегчить тестирование, вот инструкции по установке PostgreSQL на Ubuntu:

  1. Добавьте конфигурацию репозитория:

    echo "deb [arch=amd64] http://apt.postgresql.org/pub/repos/apt focal-pgdg main" >> /etc/apt/sources.list.d/pgdg.list
    wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
    
  2. Обновите пакеты и установите PostgreSQL:

    apt -y install postgresql-15 postgresql-client-15
    
  3. Разрешите подключения к серверу PostgreSQL:

    sed -i  '/listen_addresses/s/^#//g' /etc/postgresql/15/main/postgresql.conf
    sed -ie "s/^listen_addresses.*/listen_addresses = '127.0.0.1'/" /etc/postgresql/15/main/postgresql.conf
    sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/${PG_VERSION}/main/pg_hba.conf
    
  4. Перезапустите экземпляр PostgreSQL, чтобы изменения вступили в силу:

    pg_ctlcluster 15 main restart
    
  5. Создайте тестового пользователя с правами суперпользователя, чтобы разрешить выполнение команд COPY:

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

Выполнение тестов

Чтобы убедиться, что ваша настройка работает правильно, запустите модульные тесты:

export PYTHONPATH=$(pwd)
python tests/test_full.py -v

При успешном выполнении вывод должен выглядеть следующим образом:

Ran N tests in ...
OK

Если все тесты пройдены, приложение готово к использованию.

Чтобы запустить конкретный тестовый случай, используйте следующий шаблон:

export PYTHONPATH=$(pwd)
python tests/test_full.py -v PGAnonValidateUnitTest

Конфигурация тестовой базы данных

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

set TEST_DB_USER=anon_test_user
set TEST_DB_USER_PASSWORD=mYy5RexGsZ
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

Использование

Чтобы отобразить сообщение справки для CLI, выполните:

python pg_anon.py --help

Общие параметры pg_anon:

ОпцияОписание
--debug Включает режим отладки (по умолчанию false)
--verbose Настройка подробного режима: [info, debug, error] (по умолчанию info)
--db-connections-per-process Количество подключений для операций ввода-вывода для каждого процесса (по умолчанию 4)
--processes Количество процессов для многопроцессорных операций (по умолчанию 4)

Параметры конфигурации базы данных:

ОпцияОписание
--db-host Указывает ваш хост базы данных
--db-port Указывает порт вашей базы данных
--db-name Указывает имя вашей базы данных
--db-user Указывает вашего пользователя базы данных
--db-user-password Указывает пароль пользователя базы данных
--db-passfile Путь к файлу, содержащему пароль, который будет использоваться при подключении к базе данных
--db-ssl-key-file Путь к файлу ключа SSL клиента для безопасных подключений к базе данных
--db-ssl-cert-file Путь к файлу клиентского SSL-сертификата для безопасных подключений к базе данных
--db-ssl-ca-file Путь к корневому файлу SSL-сертификата. Этот сертификат используется для проверки сертификата сервера

Запустить режим инициализации

Чтобы инициализировать схему anon_funcs, запустите pg_anon в режиме ‘init’:

python pg_anon.py --mode=init \
                  --db-user=postgres \
                  --db-user-password=postgres \
                  --db-name=test_source_db

Запустить режим create_dict

Предварительные условия:

  • Сгенерированный или созданный вручную словарь *.py файл с профилем анонимизации

  • anon_funcs создан в режиме инициализации

Чтобы создать словарь:

python pg_anon.py --mode=create-dict \
                  --db-user=postgres \
                  --db-user-password=postgres \
                  --db-name=test_source_db \
                  --meta-dict-file=test_meta_dict.py \
                  --prepared-sens-dict-file=test_sens_dict_output_previous_use.py \
                  --prepared-no-sens-dict-file=test_no_sens_dict_output_previous_use.py \
                  --output-sens-dict-file=test_sens_dict_output.py \
                  --output-no-sens-dict-file=test_no_sens_dict_output.py \
                  --processes=2
ОпцияОписание
--meta-dict-file Входной файл или список файлов с правилами сканирования чувствительных и нечувствительных полей. В случае конфликта приоритет имеет первый файл в списке
--prepared-sens-dict-file Входной файл или список файлов с конфиденциальными полями, который был получен при предыдущем использовании опции --output-sens-dict-file или подготовлен вручную (необязательно)
--prepared-no-sens-dict-file Входной файл или список файлов с не чувствительными полями, который был получен при предыдущем использовании опции --output-no-sens-dict-file или подготовлен вручную (необязательно)
--output-sens-dict-file Файл вывода с конфиденциальными полями будет сохранен в это значение
--output-no-sens-dict-file Файл вывода с несекретными полями будет сохранен в это значение (необязательно)
--scan-mode Определяет, сканировать ли все данные или только их часть [full, partial] (по умолчанию partial)
--scan-partial-rows В --scan-mode partial определяет количество строк для сканирования (по умолчанию 10000)

Требования к вводу --meta-dict-file (metadict):

Входной файл metadict.py должен содержать такую структуру:

var = {
    "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",
            "name"
        ]
    },
    "skip_rules": [  # List of schemas, tables, and fields to skip
        {
            # possibly some schema or table contains a lot of data that is not worth scanning. 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.
        }
    ],
    "include_rules": [ # List of schemas, tables, and fields which will be scanning
        {
            # possibly you need specific fields for scanning or you can debug some functions on specific field
            "schema": "schm_other_2", # Required. Schema specification is mandatory
            "table": "tbl_test_anon_functions", # Optional. If there is no "table", the entire schema will be included.
            "fields": ["fld_5_email"] # Optional. If there are no "fields", the entire table will be included.             
        }
    ],
    "data_regex": {  # List of regular expressions to search for sensitive data
        "rules": [
            """[A-Za-z0-9]+([._-][A-Za-z0-9]+)*@[A-Za-z0-9-]+(\.[A-Za-z]{2,})+""",  # email
            "7?[\d]{10}"  # phone 7XXXXXXXXXX 
        ]
    },
    "data_const": {
        # List of constants in lowercase, upon detection of which the field will be included in the resulting dictionary. If a text field contains a value consisting of several words, this value will be split into words, converted to lowercase, and matched with the constants from this list. Words shorter than 5 characters are ignored. Search is performed using set.intersection
        "constants": [  # When reading the meta-dictionary, the values of this list are placed in a set container
            "simpson",
            "account"
        ],
        # List of partial constants. If field value has substring from this list, it will considered as sensitive
        "partial_constants": [ # When reading the meta-dictionary, the values of this list are placed in a set container
            "@example.com",
            "login_"
        ]
    },
    "data_func": { # List of functions for specific field types  
        "text": [ # Field type, which will be checked by functions bellow. Can use custom types. Also, can use common type "anyelement" for all field types. Rules for "anyelement" will be added for all types rules after their own rules.
            {
                "scan_func": "my_custom_functions.check_by_users_table", # Function for scanning field value. Scan function has fixed call signature: (value, schema_name, table_name, field_name). Also , this function must return boolean result. 
                "anon_func": "anon_funcs.digest(\"%s\", 'salt_word', 'md5')", # Function will be called for anonymization in dump step
                "n_count": 100, # How many times "scan_func" have to returns "True" by values in one field. If this count will be reached, then this field will be anonymized by "anon_func" 
            },
        ],
    },
    "data_sql_condition": [ # List of rules for define data sampling for specific tables by custom conditions
        {
           "schema": "schm_mask_ext_exclude_2", # Can use "schema" for full name matching or "schema_mask" for regexp matching. Required one of them
           "table_mask": "*", # Can use "table" for full name matching or "table_mask" for regexp matching. Required one of them
           "sql_condition": # Condition in raw SQL format. For example, we need data sample created by 2024 year
           """
           WHERE created > '2024-01-01' AND created < '2024-12-31'
           """
        }
    ],
    "sens_pg_types": [
        # List of field types which should be checked (other types won't be checked). If this massive is empty program set default SENS_PG_TYPES = ["text", "integer", "bigint", "character", "json"]
        "text",
        "integer",
        "bigint",
        "varchar",  # better write small names, because checker find substrings in original name. For example types varchar(3) contains varchar, so varchar(3) will be checked in program.
        "json"
    ],
    "funcs": {  # List of field types (int, text, ...) and functions for anonymization
        # If a certain field is found during scanning, a 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')"
    }
}

Запустить режим дампа

Предварительные условия:

  • anon_funcs схема с функциями анонимизации должна быть создана. См. --mode init пример.

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

См. --mode create-dict.

Режимы дампа:

  1. Чтобы создать дамп структуры и дамп данных:

    python pg_anon.py --mode=dump \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_source_db \
                      --prepared-sens-dict-file=test_sens_dict_output.py
    
  2. Чтобы создать дамп только структуры:

    python pg_anon.py --mode=sync-struct-dump \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_source_db \
                      --output-dir=test_sync_struct_dump \
                      --prepared-sens-dict-file=test_sens_dict_output.py
    
  3. Чтобы создать только дамп данных:

    python pg_anon.py --mode=sync-data-dump \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_source_db \
                      --output-dir=test_sync_data_dump \
                      --prepared-sens-dict-file=test_sens_dict_output.py
    

    Этот режим может быть полезен для планирования синхронизации базы данных, например, с cron.

Возможные параметры в mode=dump:

ОпцияОписание
--prepared-sens-dict-file Входной файл или список файлов с конфиденциальными полями, который был получен при предыдущем использовании опции --output-sens-dict-file или подготовлен вручную
--dbg-stage-1-validate-dict Проверить словарь, показать таблицы и выполнить SQL запросы без экспорта данных (по умолчанию false)
--dbg-stage-2-validate-data Проверить данные, показать таблицы и выполнить SQL-запросы с экспортом данных в подготовленную базу данных (по умолчанию false)
--dbg-stage-3-validate-full Выполняет всю логику с limit в SQL запросах (по умолчанию false)
--clear-output-dir В режиме дампа очищает выходной каталог от предыдущего дампа или других файлов (по умолчанию true)
--pg-dump Путь к инструменту Postgres pg_dump (по умолчанию /usr/bin/pg_dump)
--output-dir Каталог для файлов дампа (по умолчанию "")

Запустить режим восстановления

Предварительные условия:

  • Каждый режим требует дампа для восстановления.

Режимы восстановления:

  1. Чтобы восстановить структуру и данные:

    Этот режим требует вывода дампа, созданного в --mode=dump.

    python pg_anon.py --mode=restore \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_target_db \
                      --input-dir=test_dict_output \
                      --verbose=debug
    
  2. Чтобы восстановить только структуру:

    Этот режим требует вывода дампа, созданного в --mode=sync-struct-dump.

    python pg_anon.py --mode=sync-struct-restore \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_target_db \
                      --input-dir=test_sync_struct_dump \
                      --verbose=debug
    
  3. Чтобы восстановить только данные:

    Этот режим требует вывода дампа, созданного в --mode=sync-data-dump.

    python pg_anon.py --mode=sync-data-restore \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_target_db \
                      --input-dir=test_sync_data_dump \
                      --verbose=debug
    

Возможные параметры в --mode restore:

ОпцияОписание
--input-dir Входной каталог с файлами дампа, созданными в режиме дампа
--disable-checks Отключает проверки дискового пространства и версии PostgreSQL (по умолчанию false)
--seq-init-by-max-value Инициализирует последовательности на основе максимальных значений. В противном случае последовательности будут инициализированы на основе значений исходной базы данных.
--drop-custom-check-constr Удаляет все ограничения CHECK, содержащие пользовательские процедуры, чтобы избежать снижения производительности на этапе загрузки данных.
--pg-restore Путь к инструменту Postgres pg_dump.

Запустить режим просмотра полей

Предварительные условия:

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

См. --mode create-dict.

Чтобы увидеть поля в базе данных и правила анонимизации по подготовленному словарю:

   python pg_anon.py --mode=view-fields \
                     --db-host=127.0.0.1 \
                     --db-user=postgres \
                     --db-user-password=postgres \
                     --db-name=test_source_db \
                     --prepared-sens-dict-file=test_sens_dict_output.py

Предупреждение

  • Этот режим может обрабатывать только ограниченное количество полей без фильтров, для повышения производительности. Он задается опцией --fields-count с значением по умолчанию = 5000 полей. Чтобы избежать этого, используйте фильтры (--schema-name, --schema-mask, --table-name, --table-mask) или опцию --fields-count.

  • Если вывод будет обрезан, вы получите уведомление об этом. Но с опцией --json это уведомление будет отключено.

ОпцияОписание
--prepared-sens-dict-file Входной файл или список файлов с конфиденциальными полями, который был получен при предыдущем использовании опции --output-sens-dict-file или подготовлен вручную
--view-only-sensitive-fields Для возврата только чувствительных полей. По умолчанию результаты содержат все поля базы данных
--json Для возврата результатов в формате JSON. По умолчанию используется табличный вывод
--fields-count Укажите, сколько полей будет обработано для вывода. По умолчанию = 5000
--schema-name Фильтр по имени схемы
--schema-mask Фильтр по маске схемы. Может принимать регулярные выражения
--table-name Фильтр по имени таблицы
--table-mask Фильтр по маске таблицы. Может принимать регулярные выражения

Запустить режим просмотра данных

Предварительные условия:

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

См. --mode create-dict.

Чтобы увидеть таблицу в базе данных с полями анонимизации по подготовленному словарю:

python pg_anon.py --mode=view-data \
                  --db-host=127.0.0.1 \
                  --db-user=postgres \
                  --db-user-password=postgres \
                  --db-name=test_source_db \
                  --prepared-sens-dict-file=test_prepared_sens_dict_result_expected.py \
                  --schema-name=public \
                  --table-name=contracts \
                  --limit=10 \
                  --offset=0

Предупреждение

  • Этот режим может обрабатывать только ограниченное количество полей, для повышения производительности. Это указывается с помощью --limit со значением по умолчанию = 100 и --offset со значением по умолчанию = 0.

  • В этом режиме вы должны указать имя схемы --schema-name и таблицы --table-name.

  • Если вывод будет обрезан, вы получите уведомление об этом. Но с опцией --json это уведомление будет отключено.

ОпцияОписание
--prepared-sens-dict-file Входной файл или список файлов с конфиденциальными полями, который был получен при предыдущем использовании опции --output-sens-dict-file или подготовлен вручную
--json Для возврата результатов в формате JSON. По умолчанию используется табличный вывод
--schema-name Имя схемы
--table-name Имя таблицы
--limit Сколько строк будет показано. По умолчанию limit=100
--offset Какая часть данных будет показана. По умолчанию offset=0

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

Если у вас есть таблица, содержащая объекты и поля для анонимизации, вы можете использовать этот SQL-запрос для создания словаря в формате json:

select
    jsonb_pretty(
        json_agg(json_build_object('schema', T.schm, 'table', T.tbl, 'fields', flds ))::jsonb
    )
from (
    select
        T.schm,
        T.tbl,
        JSON_OBJECT_AGG(fld, mrule) as flds
    from (
        select 'schm_1' as schm, 'tbl_a' as tbl, 'fld_1' as fld, 'md5(fld_1)' as mrule
        union all
        select 'schm_1', 'tbl_a', 'fld_2', 'md5(fld_2)'
        union all
        select 'schm_1','tbl_b', 'fld_1', 'md5(fld_1)'
        union all
        select 'schm_1','tbl_b', 'fld_2', 'md5(fld_2)'
    ) T
    group by schm, tbl
) T
>>
    [
        {
            "table": "tbl_b",
            "fields": {
                "fld_1": "md5(fld_1)",
                "fld_2": "md5(fld_2)"
            },
            "schema": "schm_1"
        },
        {
            "table": "tbl_a",
            "fields": {
                "fld_1": "md5(fld_1)",
                "fld_2": "md5(fld_2)"
            },
            "schema": "schm_1"
        }
    ]

Этапы отладки в режимах дампа и восстановления

Этапы отладки:

  1. Этап 1: проверка словаря

    Этот этап проверяет словарь, показывает таблицы и выполняет SQL-запросы без экспорта данных на диск или в базу данных. Таким образом, если программа работает без ошибок => этап пройден.

    Диаграмма G.4. dbg-stage-1.png

    dbg-stage-1.png

    python pg_anon.py --mode=dump \
                      --db-host=127.0.0.1 \
                      --db-user=postgres \
                      --db-user-password=postgres \
                      --db-name=test_source_db \
                      --output-dir=test_dbg_stages \
                      --prepared-sens-dict-file=test_dbg_stages.py \
                      --clear-output-dir \
                      --verbose=debug \
                      --debug \
                      --dbg-stage-1-validate-dict
    
  2. Этап 2: проверка данных

    Проверка данных, отображение таблиц и выполнение SQL-запросов с экспортом данных и ограничением 100 в подготовленной базе данных. Этот этап требует базу данных со всей структурой только с условием предварительных данных, которое описано в --prepared-sens-dict-file.

    • Если вы хотите создать базу данных с необходимой структурой, просто выполните:

      Одноразовый дамп структуры:

      python pg_anon.py --mode=sync-struct-dump \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_source_db \
                        --output-dir=test_stage_2 \
                        --prepared-sens-dict-file=test_dbg_stages.py \
                        --clear-output-dir \
                        --verbose=debug \
                        --debug \
                        --dbg-stage-3-validate-full
      

      И затем столько раз, сколько вы хотите восстановить структуру:

      su - postgres -c "psql -U postgres -d postgres -c \"DROP DATABASE IF EXISTS test_target_db_7\""
      su - postgres -c "psql -U postgres -d postgres -c \"CREATE DATABASE test_target_db_7\""
      python pg_anon.py --mode=sync-struct-restore \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_target_db_7 \
                        --input-dir=test_stage_2 \
                        --verbose=debug \
                        --debug 
      
    • Проверка данных на этапе дампа:

      Диаграмма G.5. dbg-stage-2.png

      dbg-stage-2.png

      python pg_anon.py --mode=dump \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_source_db \
                        --output-dir=test_dbg_stages \
                        --prepared-sens-dict-file=test_dbg_stages.py \
                        --clear-output-dir \
                        --verbose=debug \
                        --debug \
                        --dbg-stage-2-validate-data
      
    • Проверка данных на этапе восстановления данных:

      python pg_anon.py --mode=sync-data-restore \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_target_db_7 \
                        --input-dir=test_dbg_stages \
                        --verbose=debug \
                        --debug 
      
      # And for example view all data in every table:
      su - postgres -c "psql -U postgres -d test_target_db_7 -c \"SELECT * FROM public.contracts\""
      
  3. Этап 3: полная проверка

    Диаграмма G.6. dbg-stage-3.png

    dbg-stage-3.png

    Делает всю логику с limit 100 в SQL запросах. На этом этапе вам не нужна подготовленная база данных, просто выполните:

    su - postgres -c "psql -U postgres -d postgres -c \"DROP DATABASE IF EXISTS test_target_db_8\""
    su - postgres -c "psql -U postgres -d postgres -c \"CREATE DATABASE test_target_db_8\""
    
    • Проверка полной стадии в дампе:

      python pg_anon.py --mode=dump \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_source_db \
                        --output-dir=test_dbg_stages \
                        --prepared-sens-dict-file=test_dbg_stages.py \
                        --clear-output-dir \
                        --verbose=debug \
                        --debug \
                        --dbg-stage-3-validate-full
      
    • Проверка полного этапа восстановления:

      python pg_anon.py --mode=restore \
                        --db-host=127.0.0.1 \
                        --db-user=postgres \
                        --db-user-password=postgres \
                        --db-name=test_target_db_8 \
                        --input-dir=test_dbg_stages \
                        --verbose=debug \
                        --debug 
      
      # And for example view all data in every table:
      su - postgres -c "psql -U postgres -d test_target_db_8 -c \"SELECT * FROM public.contracts\""
      

Как экранировать/разэкранировать сложные имена объектов

import json
j = {"k": "_TBL.$complex#имя;@&* a'2"}
json.dumps(j)
>>
    '{"k": "_TBL.$complex#\\u0438\\u043c\\u044f;@&* a\'2"}'
s = '{"k": "_TBL.$complex#\\u0438\\u043c\\u044f;@&* a\'2"}'
u = json.loads(s)
print(u['k'])
>>
    _TBL.$complex#имя;@&* a'2

Вклад

Зависимости

pg_anon использует Poetry инструмент управления зависимостями для управления зависимостями и создания пакетов. Для добавления новых зависимостей установите Poetry и выполните команду:

poetry add <package_name>

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

poetry lock --no-update

Дополнительно, экспортируйте последние пакеты в requirements.txt с помощью плагина poetry export:

poetry export -f requirements.txt --output requirements.txt

Сборка пакета

Для сборки пакета используйте команду:

poetry build

Дополнительно пакет можно собрать с использованием setuptools:

python3 setup.py sdist

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

Основные каталоги:

  • dict/ — каталог с meta-dict, prepared-sens-dict-file и prepared-no-sens-dict-file файлами.

  • docker/ — каталог с docker.

  • pg_anon/ — основные модули Python.

  • tests/ — содержит test_full.py - основной модуль тестирования.

Основная логика pg_anon содержится в следующих модулях Python:

  • pg_anon/pg_anon.py — создает экземпляр класса Context и направляет программу в соответствующий модуль.

  • pg_anon/context.py — содержит класс Context (ctx).

  • pg_anon/create_dict.py — логика для --mode=create-dict.

  • pg_anon/dump.py — логика для --mode=dump, --mode=sync-struct-dump и --mode=sync-data-dump.

  • pg_anon/restore.py — логика для --mode=restore, --mode=sync-struct-restore и --mode=sync-data-restore.

  • pg_anon/view_fields.py — логика для --mode=view-fields.

  • pg_anon/view_data.py — логика для --mode=view-data.

tree pg_anon/ -L 3
pg_anon/
├── dict  # Dir with meta-dict, prepared-sens-dict-file and prepared-no-sens-dict-file files.
│   ├── mask_test.py
│   ├── test_dbg_stages.py
│   ├── test_empty_dictionary.py
│   ├── test_empty_meta_dict.py
│   ├── test_exclude.py
│   ├── test_meta_dict.py
│   ├── test_prepared_no_sens_dict_result_expected.py
│   ├── test_prepared_sens_dict_result_expected.py
│   ├── test.py
│   ├── test_sync_data_2.py
│   ├── test_sync_data.py
│   └── test_sync_struct.py
├── docker  # Dir with docker
│   ├── Dockerfile
│   ├── entrypoint_dbg.sh
│   ├── entrypoint.sh
│   ├── Makefile
│   ├── motd
│   └── README.md
├── examples
│   ├── 1.test_db_create.sql
│   ├── 2.test_db_str.sql
│   ├── 3.test_db_data.sql
│   └── README.md
├── __init__.py
├── init.sql
├── pg_anon  # Main python modules
│   ├── common
│   │   ├── db_queries.py
│   │   ├── db_utils.py
│   │   ├── dto.py
│   │   ├── enums.py
│   │   ├── __init__.py
│   │   └── utils.py
│   ├── context.py
│   ├── create_dict.py
│   ├── dump.py
│   ├── __init__.py
│   ├── __main__.py
│   ├── pg_anon.py
│   ├── restore.py
│   ├── version.py
│   ├── view_data.py
│   └── view_fields.py
├── pg_anon.py
├── poetry.lock
├── pyproject.toml
├── README.md
├── requirements.txt
├── setup.py
├── tests  # Contains test_full.py - main testing module
│   ├── init_env.sql
│   ├── __init__.py
│   ├── init_stress_env.sql
│   ├── PGAnonMaskUnitTest_source_tables.result
│   ├── PGAnonMaskUnitTest_target_tables.result
│   └── test_full.py
└── tools
    └── metadata_history.sql

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

Все функции содержатся в файле init.sql. После выполнения команды --mode=init, они будут находиться в схеме anon_funcs в исходной базе данных. Если вы хотите написать новую функцию, просто создайте ее в схеме anon_funcs в вашей исходной базе данных.

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

  1. Добавить шум к вещественному числу:

    SELECT anon_funcs.noise(100, 1.2);
    >> 123
    
  2. Добавить шум к дате или временной метке:

    SELECT anon_funcs.dnoise('2020-02-02 10:10:10'::timestamp, interval '1 month');
    >> 2020-03-02 10:10:10
    
  3. Хэшировать строковое значение с указанной хэш-функцией:

    SELECT anon_funcs.digest('text', 'salt', 'sha256');
    >> '3353e....'
    
  4. Оставить первые несколько символов (2-й аргумент) и последние несколько символов (4-й аргумент) указанной строки, добавив константу (3-й аргумент) между ними:

    SELECT anon_funcs.partial('123456789', 1, '***', 3);
    >> 1***789
    
  5. Замаскировать адрес электронной почты:

    SELECT anon_funcs.partial_email('[email protected]');
    >> ex*****@gm*****.com
    
  6. Сгенерировать случайную строку указанной длины:

    SELECT anon_funcs.random_string(7);
    >> H3ZVL5P
    
  7. Сгенерировать случайный почтовый индекс:

    SELECT anon_funcs.random_zip();
    >> 851467
    
  8. Сгенерировать случайные дату и время в указанном диапазоне:

    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
    
  9. Сгенерировать случайные дату и время:

    SELECT anon_funcs.random_date();
    >> 1911-04-18 21:54:13.139
    
  10. Сгенерировать случайное целое число в указанном диапазоне:

    SELECT anon_funcs.random_int_between(100, 200);
    >> 159
    
  11. Сгенерировать случайный bigint в указанном диапазоне:

    SELECT anon_funcs.random_bigint_between(6000000000, 7000000000);
    >> 6268278565
    
  12. Сгенерировать случайный номер телефона:

    SELECT anon_funcs.random_phone('+7');
    >> +7297479867
    
  13. Сгенерировать случайный хеш, используя указанную функцию:

    SELECT anon_funcs.random_hash('seed', 'sha512');
    >> b972f895ebea9cf2f65e19abc151b8031926c4a332471dc5c40fab608950870d6dbddcd18c7e467563f9b527e63d4d13870e4961c0ff2a62f021827654ae51fd
    
  14. Выбрать случайный элемент из массива:

    SELECT anon_funcs.random_in(array['a', 'b', 'c']);
    >> a
    
  15. Преобразовать шестнадцатеричное значение в десятичное:

    SELECT anon_funcs.hex_to_int('8AB');
    >> 2219
    

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

Пример использования шифрования с кодированием base64 для хранения зашифрованного значения в текстовом поле:

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.

Дополнительные материалы: