Представьте, что вам достался проект с БД PostgreSQL, предыдущие разработчики – уже не в команде, документация – комментарии в коде (не везде), а продакшн уже вовсю обрабатывает реальных пользователей. Сразу нужно понять – есть ли в БД персональные данные, подпадающие под 152-ФЗ? Если есть – в каких таблицах и колонках? Защищены ли они от утечки при компрометации физического сервера? Кто и когда обращался к конфиденциальной информации?

Ручной анализ схемы БД из 50+ таблиц займёт часы, а риск что-то упустить останется высоким. Каждый день промедления – потенциальное нарушение требований регуляторов.

Сначала приходится вручную пробираться сквозь лабиринт таблиц и связей. Для всех сущностей нужно изучить структуру колонок, понять семантику полей по названиям (часто неинформативным), проверить типы данных и примерное содержимое, затем сопоставить с перечнем ПДн из 152‑ФЗ. И даже если вы справились с идентификацией ПДн, впереди - не менее трудоёмкие этапы:

  1. Организация защитного преобразования. В "ванильном" PostgreSQL нет встроенного прозрачного защитного преобразования данных (TDE). Придётся вручную выбирать алгоритм, реализовывать такое преобразование на уровне приложения или триггеров, управлять ключами (отдельная головная боль) и тестировать влияние на производительность;

  2. Настройка аудита. Стандартный pgaudit требует тонкой настройки: нужно определить, какие операции логировать, настроить фильтры по пользователям/таблицам, предусмотреть хранение и ротацию логов и разработать систему мониторинга аномалий;

  3. В части контроля доступа нужно вручную пересмотреть все роли и привилегии, проверить pg_hba.conf на уязвимости, настроить SSL для всех соединений и реализовать принцип наименьших привилегий.

  4. Проверка конфигурации. Без автоматизированных инструментов придётся сверяться с чек‑листами безопасности, проверять параметры postgresql.conf, мониторить открытые порты и службы и регулярно повторять аудит после любых изменений.

Стоит ли говорить, что на полный аудит и настройку может уйти неделя работы опытного DBA? Если еще взять во внимание возможный пропуск колонок с ПДн, некорректные права доступа, неработающее шифрование, риски поддержки (каждое изменение в схеме БД требует повторного аудита) и регуляторные (штрафы по 152‑ФЗ до 6 % от выручки и репутационные потери), – здесь действительно будет над чем задуматься.

Как все это выглядит с использованием средств СУБД Tantor Certified?

Сертифицированная редакция СУБД Tantor Postgres 17 включает особый набор инструментов, автоматизирующих весь цикл защиты данных:

  • pg_anon для сканирования структуры БД и идентификации столбцов с ПДн по шаблонам и эвристикам;  

  • pg_tde для обеспечения прозрачного защитного преобразования данных на диске (Transparent Data Encryption);  

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

  • pg_sec_check для проверки конфигурации на соответствие стандартам безопасности.

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

В качестве учебного примера возьмем демонстрационную БД postgres_air (https://github.com/hettie-d/postgres_air) – реалистичную БД авиакомпании с такой структурой: клиенты и бронирования (таблицы с e-mail, телефонами, ФИО), данные программы лояльности (номера карт, персональная информация), рейсы и билеты (данные о перелетах и пассажирах). Такая БД идеально подходит для демонстрации: в ней представлены типичные виды конфиденциальных данных, встречающихся в реальных проектах.

Этап 1. Идентификация конфиденциальных данных

Вместо ручного просмотра всех таблиц и колонок (для больших БД это займет дни) воспользуемся pg_anon – инструментом для автоматического обнаружения и анонимизации конфиденциальных данных, сканирования структуры БД и создания политик анонимизации. Инструментом можно пользоваться как посредством консоли, так и с помощью встроенного в «Платформу Тантор» графического интерфейса (это не требует установки дополнительных пакетов на сервер СУБД). Для теста воспользуемся консольным исполнением.

Установка и настройка pg_anon

Загрузим с гита:

git   clone https://github.com/TantorLabs/pg_anon.git pg_anon

Перейдем в каталог pg_anon:

cd   pg_anon

Установим виртуальное окружение для Python:

apt   install python3.11-venv

Активируем окружение:

python3   -m venv venv && source venv/bin/activate

Установим зависимости:

pip   install -r requirements.txt

Если все выполнено корректно - следующая команда нам покажет справочную информацию по расширению:

python3   pg_anon.py --help

Инициируем подключение к нашей БД: 

python3   pg_anon.py --mode=init --db-user=postgres --db-name=postgres_air

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

  • Автоматизация поиска: на основе регулярных выражений и правил, прописанных в мета-словаре, pg_anon сканирует структуру БД и находит поля с конфиденциальными данными (ФИО, email, телефоны и т.д.);

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

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

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

Пример мета-словаря:

{
    "field": {
        # Правила поиска по именам полей
        "rules": [
            "^amount",      # Поля, начинающиеся с "amount"
            "price",        # Поля, содержащие "price"
            "name$",        # Поля, заканчивающиеся на "name"
            "date$",        # Даты
            "phone",        # Телефоны
            "card",         # Номера карт
            "quantity$",    # Количество
        ],
        "constants": [],    # Точные совпадения имён полей
    },
    "data_regex": {
        # Регулярные выражения для поиска данных внутри таблиц
        "rules": [
            # Email (RFC 5322 упрощённый)
            """([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+""",
           
            # Российские телефоны (10 цифр, может начинаться с 7)
            "7?[\d]{10}",
           
            # Международные телефоны (10-14 цифр с разделителями)
            "(\s*)?(\+)?([- _():=+]?\d[- _():=+]?){10,14}(\s*)?",
           
            # SSN (США): 123-45-6789
            """[0-9]{3}-[0-9]{2}-[0-9]{4}""",
           
            # Номера паспортов (формат AAA 1234)
            """\b[0-9A-Z]{3}([^ 0-9A-Z]|\s)?[0-9]{4}\b""",
           
            # IPv4 адреса
            """^\d{1,3}[.]\d{1,3}[.]\d{1,3}[.]\d{1,3}$""",
           
            # Даты в различных форматах
            "\d{2}(-|\/|\.|\s)\d{2}(-|\/|\.|\s)\d{4}",
            "\d{4}(-|\/|\.|\s)\d{2}(-|\/|\.|\s)\d{2}",
           
            # Mastercard (начинается с 51-55, 2221-2720, 16 цифр)
            """^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$""",
           
            # Visa (начинается с 4, 16 цифр с разделителями или без)
            """\b([4]\d{3}[\s]\d{4}[\s]\d{4}[\s]\d{4}|[4]\d{3}[-]\d{4}[-]\d{4}[-]\d{4}|[4]\d{3}[.]\d{4}[.]\d{4}[.]\d{4})\b""",
           
            # МИР с разделителями
            """^(?:220[0-4](?:[\s\-\\.]?\d){12}|(?:2205[0-5](?:[\s\-\\.]?\d){11})$""",

            # Универсальный формат карты (4 группы по 4 цифры)
            """[0-9]{4}-[0-9]{4}-[0-9]{4}-[0-9]{4}""",
           
            # URL
            """(?i)\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()]+|\(([^\s()]+|(\([^\s()]+\)))*\))+(?:\(([^\s()]+|(\([^\s()]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»""'']))"""
        ]
    },
    "data_const": {
        # Константные паттерны
        "constants": [],
        "partial_constants": [
            "@example.com",     # Тестовые email
            "login_"            # Поля с префиксом login_
        ]
    },
    # Типы данных PostgreSQL, которые будем сканировать
    "sens_pg_types": [
        "text",
        "integer",
        "bigint",
        "character",
        "json"
    ],
    # Функции анонимизации (не используем в этой статье, но требуются для словаря)
    "funcs": {
        "text": "anon_funcs.digest(\"%s\", 'salt_word', 'md5')",
        "numeric": "anon_funcs.noise(\"%s\", 10)",
        "timestamp": "anon_funcs.dnoise(\"%s\", interval '6 month')",
        "bigint": "anon_funcs.random_inn()",
        "integer": "anon_funcs.random_int_between(1, 10)",
        "mvarchar": "anon_funcs.digest(\"%s\"::text, 'salt_word', 'md5')",
        "datetime": "anon_funcs.random_date()"
    }
}

Разбор регулярных выражений

Email: ([A-Za-z0-9]+[.-_])*[A-Za-z0-9]+@[A-Za-z0-9-]+(\.[A-Z|a-z]{2,})+

  • Ищет стандартные email-адреса с доменами (.com, .org и т.д.)

Mastercard: ^(?:5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}$

  • Проверяет BIN коды Mastercard (51-55, 2221-2720)

  • Всего 16 цифр

Visa: \b([4]\d{3}[\s]\d{4}[\s]\d{4}[\s]\d{4}|[4]\d{3}[-]\d{4}[-]\d{4}[-]\d{4}|...)\b

  • Начинается с 4

  • Разделители: пробелы, дефисы или точки

  • Всего 16 цифр

Мир: ^(?:220[0-4](?:[\s\-\\.]?\d){12}|(?:2205[0-5](?:[\s\-\\.]?\d){11})$

  • Начинается с 220

  • Разделители: пробелы, дефисы или точки

  • Всего 16 или 17 цифр

Эти правила можно модифицировать под ваши нужды, например, добавить поиск ИНН, СНИЛС или номеров водительских прав.

Теперь на основе мета-словаря создадим словарь конфиденциальных данных – конкретный список таблиц и колонок для нашей БД:

python3   pg_anon.py --mode=create-dict --db-user=postgres --db-name=postgres_air --meta-dict-file=meta_dict.py   --output-sens-dict-file=sens_dict.py

Происходит здесь вот что: pg_anon подключается к БД postgres_air, сканирует все схемы и таблицы, проверяет имена колонок по правилам из field.rules, сканирует данные в колонках по регулярным выражениям из data_regex.rules и генерирует файл sens_dict.py с найденными полями. Посмотрим на этот вновь созданный словарь конфиденциальных данных:

cat pg_anon/sens_dict.py
{
    "dictionary": [
        {
            "schema": "postgres_air",
            "table": "frequent_flyer",
            "fields": {
                "email": "anon_funcs.digest(\"email\", 'salt_word', 'md5')",
                "card_num": "anon_funcs.digest(\"card_num\", 'salt_word', 'md5')",
                "first_name": "anon_funcs.digest(\"first_name\", 'salt_word', 'md5')",
                "last_name": "anon_funcs.digest(\"last_name\", 'salt_word', 'md5')",
                "phone": "anon_funcs.digest(\"phone\", 'salt_word', 'md5')"
            }
        },
        {
            "schema": "postgres_air",
            "table": "account",
            "fields": {
                "login": "anon_funcs.digest(\"login\", 'salt_word', 'md5')",
                "first_name": "anon_funcs.digest(\"first_name\", 'salt_word', 'md5')",
                "last_name": "anon_funcs.digest(\"last_name\", 'salt_word', 'md5')"
            }
        },
        {
            "schema": "postgres_air",
            "table": "booking",
            "fields": {
                "email": "anon_funcs.digest(\"email\", 'salt_word', 'md5')",
                "booking_name": "anon_funcs.digest(\"booking_name\", 'salt_word', 'md5')",
                "phone": "anon_funcs.digest(\"phone\", 'salt_word', 'md5')",
                "price": "anon_funcs.noise(\"price\", 10)"
            }
        },
        {
            "schema": "postgres_air",
            "table": "airport",
            "fields": {
                "airport_name": "anon_funcs.digest(\"airport_name\", 'salt_word', 'md5')"
            }
        },
        {
            "schema": "postgres_air",
            "table": "passenger",
            "fields": {
                "first_name": "anon_funcs.digest(\"first_name\", 'salt_word', 'md5')",
                "last_name": "anon_funcs.digest(\"last_name\", 'salt_word', 'md5')"
            }
        },
        {
            "schema": "postgres_air",
            "table": "phone",
            "fields": {
                "phone": "anon_funcs.digest(\"phone\", 'salt_word', 'md5')",
                "phone_type": "anon_funcs.digest(\"phone_type\", 'salt_word', 'md5')",
                "primary_phone": "anon_funcs.digest(\"primary_phone\", 'salt_word', 'md5')"
            }
        }
    ]
}

Видим, что все таблицы, в которых есть конфиденциальные данные, расположены в схеме postgres_air. Отфильтруем по слову table и получим только перечень таблиц:

cat   pg_anon/sens_dict.py | grep table

            "table":   "frequent_flyer",

            "table":   "account",

            "table":   "booking",

            "table":   "airport",

            "table":   "passenger",

            "table":   "phone",

В итоге мы автоматически обнаружили 6 таблиц с конфиденциальными данными:

  • frequent_flyer – программа лояльности (email, телефоны, номера карт, ФИО);

  • account – учётные записи (логины, ФИО);

  • booking – бронирования (email, телефоны, имена, цены);

  • airport – аэропорты (названия аэропортов могут содержать персональные данные в комментариях);

  • passenger – пассажиры (ФИО);

  • phone – телефоны.

Все эти таблицы расположены в схеме postgres_air.

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

Этап 2. Защитное преобразование данных в состоянии покоя (TDE)

Защитное преобразование данных на диске нужно ввиду того, что даже если доступ к БД защищён паролями и правами, злоумышленник может получить физический доступ к серверу, скопировать файлы БД через уязвимость в ОС и извлечь данные из бэкапов. Расширение pg_tde (Transparent Data Encryption) решает эту проблему: напомним, к его основным возможностям относится защитное преобразование данных на диске с использованием современных алгоритмов, прозрачное обратное преобразование при чтении данных, управление ключами преобразования и защита от несанкционированного доступа к данным.

Архитектура ключей

Хранилище ключей: демо vs production

В этой статье для упрощения используется локальное файловое хранилище ключей (file-provider). Это позволяет быстро развернуть тестовый стенд, но категорически не подходит для production, поскольку локальное хранилище небезопасно: ключи хранятся на том же сервере, что и преобразованные данные: при получении root-доступа злоумышленник получает всё, отсутствует централизованный аудит операций с ключами, а резервные копии сервера содержат и данные, и ключи (копия backup = копия всех секретов).

Production-архитектура требует:

  1. Внешнее хранилище секретов – HashiCorp Vault, KMIP, Fortanix DSM, Thales CipherTrust Manager, OpenBao. Здесь будет раздельный доступ: DBA управляет СУБД, Security-команда – ключами, будет централизованный аудит всех операций с секретами и возможность мгновенного отзыва доступа.

  2. Регламентированную ротацию ключей, обычно каждые 90-365 дней в зависимости от политики безопасности. Должен быть автоматизирован процесс ротации с их zero-downtime сменой, а также версионирование ключей для disaster recovery.

  3. Процедуры disaster recovery, т.е. что делать при потере доступа к провайдеру ключей. Резервное копирование основных ключей в защищённом хранилище, процедуры восстановления доступа и регулярное тестирование recovery-сценариев.

Важно: Настройка интеграции с Vault, автоматизация ротации ключей и проектирование disaster recovery – это объёмные задачи, каждая из которых заслуживает отдельной статьи. В рамках этого материала мы фокусируемся на базовом workflow идентификации и защиты данных. Для production-развёртывания обязательно изучите:

  • Документацию pg_tde: Key Management

  • Best practices вашей организации по управлению криптографическими ключами

  • Требования регуляторов (ФСТЭК, PCI DSS и т.д.) к хранению ключей

Настройка pg_tde

В конфигурационном файле postgresql.conf для параметра shared_preload_libraries укажем значение 'pg_tde':

shared_preload_libraries   = 'pg_tde'             # (change requires restart)

Перезапустим СУБД:

systemctl   restart tantor-certified-server-17

Подключимся к целевой БД и установим расширение pg_tde:

psql   -d postgres_air

psql   (17.7)

Введите "help", чтобы получить справку.

 

postgres_air=#   CREATE EXTENSION pg_tde;

CREATE   EXTENSION

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

mkdir   /var/lib/postgresql/tantor-certified-17/keys

chown   postgres:postgres /var/lib/postgresql/tantor-certified-17/keys

Добавим провайдер поставщика ключей в целевой БД:

SELECT   pg_tde_add_database_key_provider_file('file-provider',   '/var/lib/postgresql/tantor-certified-17/keys/test-file-provider');

 pg_tde_add_database_key_provider_file

---------------------------------------

 

(1   строка)

Создадим основной ключ:

SELECT   pg_tde_create_key_using_database_key_provider('test-key',   'file-provider');

 pg_tde_create_key_using_database_key_provider

-----------------------------------------------

 

(1   строка)

Укажем основной ключ для использования:

SELECT   pg_tde_set_key_using_database_key_provider('test-key',   'file-provider');

 pg_tde_set_key_using_database_key_provider

--------------------------------------------

 

(1   строка)

Преобразуем таблицы, содержащие чувствительные данные:

postgres_air=#   ALTER TABLE postgres_air.frequent_flyer SET ACCESS METHOD tde_heap;

ALTER   TABLE

postgres_air=#   ALTER TABLE postgres_air.account SET ACCESS METHOD tde_heap;

ALTER   TABLE

postgres_air=#   ALTER TABLE postgres_air.booking SET ACCESS METHOD tde_heap;

ALTER   TABLE

postgres_air=#   ALTER TABLE postgres_air.airport SET ACCESS METHOD tde_heap;

ALTER   TABLE

postgres_air=#   ALTER TABLE postgres_air.passenger SET ACCESS METHOD tde_heap;

ALTER   TABLE

postgres_air=#   ALTER TABLE postgres_air.phone SET ACCESS METHOD tde_heap;

ALTER   TABLE

Что здесь происходит: СУБД перестраивает таблицу с новым методом доступа, данные преобразуются в процессе перестройки, а старые непреобразованные файлы удаляются.

 

Проверим, что необходимые таблицы преобразованы:

postgres_air=#   \dt+ postgres_air.*

                                           Список отношений

    Схема       |      Имя       |   Тип   | Владелец   |  Хранение  | Метод доступа | Размер  | Описание

--------------+----------------+---------+----------+------------+---------------+---------+----------

 postgres_air | account        | таблица | postgres | постоянное | tde_heap      | 84 MB   |

 postgres_air | aircraft       | таблица   | postgres | постоянное | heap          | 16 kB   |

 postgres_air | airport        | таблица   | postgres | постоянное | tde_heap      | 136 kB  |

 postgres_air | boarding_pass  | таблица | postgres | постоянное | heap          | 2433 MB |

 postgres_air | booking        | таблица   | postgres | постоянное | tde_heap      | 720 MB  |

 postgres_air | booking_leg    | таблица | postgres | постоянное | heap          | 1069 MB |

 postgres_air | flight         |   таблица | postgres | постоянное | heap          | 67 MB   |

 postgres_air | frequent_flyer | таблица | postgres | постоянное | tde_heap      | 14 MB   |

 postgres_air | passenger      | таблица |   postgres | постоянное | tde_heap      | 1761 MB |

 postgres_air | phone          |   таблица | postgres | постоянное | tde_heap      | 43 MB   |

(10   строк)

Для таблиц в столбце «Метод доступа» должно быть установлено «tde_heap».

Проверить, преобразована ли конкретная таблица, можно также командой:

postgres_air=#   select pg_tde_is_encrypted('postgres_air.account');

 pg_tde_is_encrypted

---------------------

 t

(1   строка)

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

postgres_air=#   select pg_tde_get_algorithm('postgres_air.account');

 pg_tde_get_algorithm

----------------------

 aes

(1   строка)

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

  • AES (файлы базы - AES-128-CBC, wal-файлы - AES-128-CTR)

  • ChaCha-20

  • Magma

  • Kuznyechik

Защитное преобразование WAL-файлов

WAL, или write-ahead log – это журнал упреждающей записи. Все изменения в БД сначала записываются в WAL и только затем применяются к таблицам, и даже если таблицы преобразованы, WAL файлы могут содержать данные в открытом виде. Поэтому защитное преобразование необходимо также и для WAL.

Защитное преобразование WAL-файлов включается добавлением глобального провайдера ключей, установкой ключа с использованием глобального провайдера, установкой параметра для включения защитного преобразования и перезагрузкой СУБД.

postgres_air=#   SELECT pg_tde_add_global_key_provider_file('file-provider',   '/var/lib/postgresql/tantor-certified-17/keys/test-file-provider');

 pg_tde_add_global_key_provider_file

-------------------------------------

 

(1   строка)

 

postgres_air=#   SELECT pg_tde_set_server_key_using_global_key_provider('test-key',   'file-provider');

 pg_tde_set_server_key_using_global_key_provider

------------------------------------------

 

(1   строка)

 

postgres_air=#   ALTER SYSTEM SET pg_tde.wal_encrypt = on;

ALTER   SYSTEM

 

systemctl   restart tantor-certified-server-17

Проверка работы pg_tde

Для проверки результата работы pg_tde сравним содержимое преобразованной таблицы с непреобразованной. Посмотрим расположение преобразованной таблицы с помощью pg_relation_filepath().

postgres@astra-ib:~$   psql -d postgres_air

psql (17.7)

Введите "help", чтобы получить справку.

 

postgres_air=#   select pg_relation_filepath('postgres_air.account');

 pg_relation_filepath

----------------------

 base/16388/16740

(1   строка)

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

postgres@astra-ib:~$ strings /var/lib/postgresql/tantor-certified-17/data/base/16388/16740 | head -20
4LpX
mc k
6!  T
Sj/O
('n3
q)v[}
6Eu^
X!-%
d(RI
^NWX.
WUg\V
57GL
 L>1
,</+m
"J',
j1x,
Jff/NS
B]*LG
?SiBP
SlP9C"OT

Видим, что содержимое нечитаемо. Теперь посмотрим расположение непреобразованной таблицы:

postgres@astra-ib:~$ psql -d postgres_air
psql (17.7)
Введите "help", чтобы получить справку.
 
postgres_air=# select pg_relation_filepath('postgres_air.aircraft');
 pg_relation_filepath
----------------------
 base/16388/16427
(1 строка)

Попробуем прочитать содержимое:

postgres@astra-ib:~$ strings /var/lib/postgresql/tantor-certified-17/data/base/16388/16427 | head -20
'Cessna 208 Caravan
        CN1
)Sukhoi SuperJet-100
        SU9
'Bombardier CRJ-200
        CR2
Boeing 737-300
        733
Boeing 767-300
        763
!Airbus A321-200
        321
!Airbus A320-200
        320
!Airbus A319-100
        319
Airbus 330
        330
Boeing 777-300
        773

Видим, что содержимое можно просмотреть, и оно прекрасно читается. Теперь посмотрим на текущий WAL-файл с помощью pg_walfile_name() и pg_current_wal_lsn():

postgres@astra-ib:~$ psql -d postgres_air
psql (17.7)
Введите "help", чтобы получить справку.
 
postgres_air=# select pg_walfile_name(pg_current_wal_lsn());
     pg_walfile_name
--------------------------
 00000001000000030000004F
(1 строка)

Попробуем прочесть содержимое:

postgres@astra-ib:~$ strings /var/lib/postgresql/tantor-certified-17/data/pg_wal/00000001000000030000004F | head -20
ihys9M
QQrl
a]>Dh%i
Pd;DpA
RWaa
.Z.:
:vS3
sG+z
}T%&
9E~G;
`jr<>
PDccf
eLf19
uH-(
qu=x
Fe{1
9Mh%M
0uG3U
<scLn]
S],1

Теперь, если злоумышленник получит доступ к диску, он не сможет прочитать конфиденциальные данные.

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

В части производительности pg_tde создаёт накладные вычислительные расходы, поэтому:

  • Преобразовывать лучше не всю БД, а только выборочно таблицы с ПДн (результат сканирования pg_anon);

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

  • Сопоставляйте метрики «до и после» – CPU usage, query latency, I/O wait;

  • Проверяйте hardware – наличие AES-NI в CPU снижает overhead в 2-3 раза.

В нашем примере преобразовано 6 из 10 таблиц - только те, где pg_anonобнаружил конфиденциальные данные. Таблицы с публичной информацией (расписания рейсов, справочник аэропортов) остаются непреобразованными. Не стоит включать TDEвезде "для надёжности", это необдуманно увеличит нагрузку без реальной пользы для безопасности.

Этап 3. Аудит операций с данными

pg_tde защищает данные от похищения, но не контролирует, кто и когда к ним обращается. Эту задачу решает pgaudit – это расширение СУБД Tantor Postgres, предназначенное для отслеживания и протоколирования действий пользователей и системных событий в базе данных. Pgaudit позволяет сохранять журналы в CSV-формате, что облегчает возможную интеграцию с SIEM-системами. К основным возможностям расширения относятся журналирование SQL-запросов, отслеживание изменений данных, аудит системных событий и гибкая настройка правил протоколирования.

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

Архитектура логирования

Настройка pgaudit

В конфигурационный файл postgresql.conf добавим следующие настройки:

#pgaudit
pgaudit.log = 'ddl'
pgaudit.log_level = 'info'
pgaudit.marking_rules_enabled = on
pgaudit.marking_rules_max = 1000
pgaudit.marking_log_directory = ‘/var/lib/postgresql/tantor-certified-17/data/log_pgaudit’

и скорректируем shared_preload_libraries:

shared_preload_libraries   = 'pg_tde, pgaudit'

Создадим каталог для журналов pgaudit:

mkdir   ./tantor-certified-17/data/log_pgaudit

Создадим файл с правилами pgaudit, используя следующую структуру:

[БД]    [Тип операции]    [Тип объекта]    [Имя объекта]    [Роль]    [Метка]

nano ./tantor-certified-17/data/pgaudit_rules.conf
 
NULL            ALL_ROLE        NULL            NULL                            NULL            "logging roles changes"
NULL            AUTHENTICATE    NULL            NULL                            NULL            "authenticate all users"
NULL            DISCONNECT      NULL            NULL                            NULL            "disconnect all users"
NULL            ALL_DDL         NULL            NULL                            NULL            "DDL changes"
postgres_air    ALL_DML         TABLE           postgres_air.airport            NULL            "airport DML changes"
postgres_air    ALL_DML         TABLE           postgres_air.frequent_flyer     NULL            "frequent_flyer DML changes>
postgres_air    ALL_DML         TABLE           postgres_air.account            NULL            "account DML changes"
postgres_air    ALL_DML         TABLE           postgres_air.booking            NULL            "booking DML changes"
postgres_air    ALL_DML         TABLE           postgres_air.passenger          NULL            "passenger DML changes"
postgres_air    ALL_DML         TABLE           postgres_air.phone              NULL            "phone DML changes"

Перезапустим СУБД:

systemctl   restart tantor-certified-server-17

Создадим расширение:

CREATE   EXTENSION pgaudit;

Проверка работы pgaudit

Подключимся к БД и выведем список правил:

postgres_air=# select pgaudit_marking.show_rules();
                                        show_rules
------------------------------------------------------------------------------------------
 ("",AUTHENTICATE,"","","","authenticate all users")
 (postgres_air,ALL_DML,TABLE,postgres_air.frequent_flyer,"","frequent_flyer DML changes")
 (postgres_air,ALL_DDL,"","","","DDL changes")
 ("",ALL_ROLE,"","","","logging roles changes")
 (postgres_air,ALL_DML,TABLE,postgres_air.booking,"","booking DML changes")
 (postgres_air,ALL_DML,TABLE,postgres_air.phone,"","phone DML changes")
 (postgres_air,ALL_DML,TABLE,postgres_air.account,"","account DML changes")
 ("",DISCONNECT,"","","","disconnect all users")
 (postgres_air,ALL_DML,TABLE,postgres_air.passenger,"","passenger DML changes")
 (postgres_air,ALL_DML,TABLE,postgres_air.airport,"","airport DML changes")
(10 строк)

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

postgres_air=# select * from postgres_air.account limit 3;
 account_id |        login         | first_name | last_name | frequent_flyer_id | update_ts
------------+----------------------+------------+-----------+-------------------+-----------
     662339 | IF662339@magic.email | Ana        | If        |                   |
     677372 | BY677372@magic.email | LEO        | BY        |                   |
     735827 | CO735827@magic.email | Eli        | Co        |                   |
(3 строки)
 
postgres_air=# select * from postgres_air.frequent_flyer limit 3;
 frequent_flyer_id | first_name | last_name | title | card_num | level | award_points |         email         |   phone
  |           update_ts
-------------------+------------+-----------+-------+----------+-------+--------------+-----------------------+---------
--+-------------------------------
              2064 | EVA        | AMOS      | M     | 32421849 |     3 |        78648 | JENNA2064@magic.email | 78052569
3 | 2024-05-29 11:20:58.318468+03
             86682 | KAI        | HYDE      | M     | 12506467 |     1 |         9248 | ELSA86682@magic.email | 65850463
2 | 2024-06-19 03:32:07.441393+03
              2955 | GAEL       | JAN       | M     | 32422740 |     3 |        77649 | EMBER2955@magic.email | 42270777
5 | 2024-07-25 11:13:15.330254+03
(3 строки)

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

postgres@astra-ib:~$ tail -4 ./tantor-certified-17/data/log_pgaudit/pgaudit_mark-2026-02-04.csv
2026-02-04 10:30:05.591178+03,"postgres","postgres_air",6107,"LOG",0,0,"AUTHENTICATE","","","OK","","authentication: status=0 user=postgres database=postgres_air pid=6107 ","<not logged>","postgres","6107-0-0-823505405591178",,""
2026-02-04 10:30:52.115766+03,"postgres","postgres_air",6107,"LOG",0,0,"SELECT","TABLE","postgres_air.account","OK","","select * from postgres_air.account limit 3;","<not logged>","postgres","6107-0-0-823505452115766",,"4/3"
2026-02-04 10:31:20.09319+03,"postgres","postgres_air",6107,"LOG",0,0,"SELECT","TABLE","postgres_air.frequent_flyer","OK","","select * from postgres_air.frequent_flyer limit 3;","<not logged>","postgres","6107-0-0-823505480093190",,"4/4"
2026-02-04 10:31:50.231594+03,"postgres","postgres_air",6107,"LOG",0,0,"DISCONNECT","","","OK","disconnection: exit_code=0 user=postgres database=postgres_air pid=6107","DISCONNECT","<not logged>","postgres","6107-0-0-823505510231594",,""

Структура записи в логе pgaudit:

timestamp,user,database,pid,level,error_code,sql_state,operation_type,object_type,object_name,status,details,query,<не используется>,session_id,transaction_id,query_id

Пример разбора:

  • 2026-02-04 10:30:52.115766+03 – время выполнения;

  • postgres – пользователь;

  • postgres_air – БД;

  • SELECT – тип операции;

  • TABLE – тип объекта;

  • postgres_air.account – имя объекта;

  • select * from ... limit 3; – полный текст запроса.

Таким образом, в журнале pgaudit будут фиксироваться следующие события:

  • создание/изменение/удаление ролей;

  • аутентификация;

  • отключения;

  • DDL-команды во всех БД;

  • DML-команды в таблицах, содержащих конфиденциальные данные, БД postgres_air.

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

В итоге: все операции с конфиденциальными данными фиксируются, DDL-изменения – логируются, аутентификация и отключения – контролируются. Мы идентифицировали конфиденциальные данные, преобразовали их на уровне хранения и настроили аудит. Но останавливаться на этом нельзя.

Этап 4. Проверка конфигурации безопасности

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

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

root@astra-ib:/opt/tantor/db/17/tools/pg_sec_check# ./pg_sec_check --host /var/run/postgresql/ --port 5432 --output audit_result --format Full --config ./config.json --user postgres --database postgres -D /var/lib/postgresql/tantor-certified-17/data

Результаты сохраняются в audit_result.json – детальный JSON-отчёт – и audit_result_web/index.html – HTML-отчёт для браузера.

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

Для примера посмотрим проверку входа с хоста:

В итоге мы получили детальный отчет о текущем состоянии конфигурации и рекомендации по устранению найденных проблем. Для их устранения подойдут дополнительные инструменты из состава СУБД Tantor Certified 17:

  • credcheck — специализированный инструмент для проверки безопасности учетных данных и аутентификации в Tantor. Позволяет проводить общие проверки учетных данных, которые будут применяться при создании или переименовании пользователя, а также изменении пароля. Используя это расширение, можно определить набор правил: разрешить определенный набор учетных данных, отклонить какой‑либо тип учетных данных, отклонять слишком простые пароли, обеспечивать использование срока действия пароля с минимальным сроком в дни, определить политику повторного использования паролей и определить количество неудачных попыток аутентификации, допустимых до блокировки пользователя;

  • pg_integrity_verifier — специализированный инструмент, предназначенный для мониторинга целостности БД. Приложение предназначено для помощи DBA или специалистам по безопасности в контроле целостности БД. Оно сохраняет записи состояний баз данных в указанной директории, а в случае каких‑либо изменений в наблюдаемых базах данных создает снимок в формате sn_{database}_{datetime}.diff. Большой плюс инструмента — возможность контролировать все изменения в конфигурационных файлах postgresql.conf и pg_hba.conf с сохранением информации, когда внесено изменение, какой параметр был изменен, старое и новое значения измененного параметра.

Заключение

Итак, «поднятие» унаследованного Postgres без специнструментов быстро превращается в головную боль. Ручной разбор схем, перелопачивание десятков таблиц и прочая невеселая археология: где лежат персональные данные, что за колонки, как это всё соотносится с 152-ФЗ… Один неверный шаг — и можно запросто упустить что‑то важное. Встроенного защитного преобразования данных на диске нет, приходится либо городить огород на уровне приложений, либо создавать триггеры. Хранить ключи, тестировать производительность, поддерживать это всё, руками выставлять фильтры, думать, куда писать логи и как следить за аномалиями. Всё, что связано с безопасностью — проверять вручную. Любое изменение схемы — снова садись и аудируй заново. Времени уходить будет очень много, и неизвестно, какие грабли вылезут.

В СУБД Tantor Certified то, что обычно делается на коленке, превращается в понятный и безопасный процесс: модуль pg_anon сам проходится по базе и находит колонки с персональными данными, pg_tde обеспечивает TDE «из коробки» — данные на диске защищаются без изменения приложения, управление ключами интегрировано, нагрузка на производительность минимальна. pgaudit записывает всё, что происходит с защищёнными данными. Настраиваемые фильтры и ротация логов позволяют контролировать события без избыточной нагрузки на инфраструктуру. pg_sec_check автоматически проверяет параметры БД на соответствие стандартам безопасности, выявляет уязвимости и формирует план действий для их устранения.

В статье мы прошли быстрый путь от унаследованной базы без документации до защищённой системы с защитным преобразованием, аудитом и контролем конфигурации с использованием инструментов СУБД  Tantor Certified 17.

Исходники и документация: