2026-05-22 04:58:47 +03:00
# pg_audit (#28) + pg_anonymizer (#29) — установка на боевом сервере
**Статус: ** ✅ установлены на боевом `liderra.ru` 22.05.2026. PostgreSQL 16.14, БД `liderra` .
2026-06-17 19:33:58 +03:00
> **Факт на 17.06.2026 (снятие расхождения).** Проверено `\dx` на проде по ssh:
> `anon 3.0.13` + `pgaudit 16.0` стоят. Прежний go-live аудит, отметивший «anon не
> установлен» (`pg_extension` → пусто), опрашивал **локальную dev-копию**, а не прод —
> на dev (native Windows PostgreSQL) расширений нет и быть не может (anon 3.x — Rust/pgrx,
> Windows-сборки нет; Docker/WSL невозможны — нет вложенной виртуализации). dev держит
> только демо-данные, реальных ПДн там нет.
>
> **Остаток B2 (закрывается этим документом).** На проде `anon` установлен, но
> `anon.pg_masking_rules` → **0 строк**: правил маскирования не задано, поэтому обычный
> `pg_dump` всё ещё выгружает ПДн открыто. Правила вынесены в `db/anon_masking_labels.sql`;
> рецепт маскированного дампа и план применения — в разделе «#29 …» ниже.
2026-05-22 04:58:47 +03:00
Это два расширения PostgreSQL фазы 3 (Compliance), которые нельзя было поставить на dev native-Windows PG (расширения там недоступны — см. `memory/project_phase1_strategy.md` ). Внедрены, когда появился боевой Linux-сервер.
Сервер: VM `liderra-test` (Ubuntu 24.04), `ssh -i ~/.ssh/liderra_deploy ubuntu@111.88.246.137` , кластер `16/main` (порт 5432), приложение `/var/www/liderra/app` .
---
## Бэкап перед работами (обязательно)
``` bash
sudo -u postgres pg_dump -Fc -d liderra > /home/ubuntu/backups/liderra-pre-<TS>.dump
```
Точка отката от 22.05.2026: `/home/ubuntu/backups/liderra-pre-pgaudit-anon-20260522-010441.dump` (custom format, 1170 объектов).
---
## #28 pg_audit — журнал аудита БД (152-ФЗ)
**Что даёт: ** server-side журнал DDL / изменений прав / записей данных в дополнение к прикладным `auth_log` , `pd_processing_log` , `incidents_log` .
**Установка (выполнено): **
``` bash
sudo apt-get install -y postgresql-16-pgaudit # из штатного репозитория Ubuntu
sudo -u postgres psql -c "ALTER SYSTEM SET shared_preload_libraries = 'pgaudit';"
sudo systemctl restart postgresql@16-main # ← единственный перезапуск (~2с простоя)
sudo -u postgres psql -d liderra -c "CREATE EXTENSION pgaudit;"
sudo -u postgres psql -c "ALTER SYSTEM SET pgaudit.log = 'ddl, role, write';"
sudo -u postgres psql -c "ALTER SYSTEM SET pgaudit.log_parameter = 'off';" # ПДн НЕ в логах
sudo -u postgres psql -c "ALTER SYSTEM SET pgaudit.log_catalog = 'off';"
sudo -u postgres psql -c "SELECT pg_reload_conf();"
```
**Важно: ** `pgaudit.log_parameter = off` — значения SQL-параметров (телефоны/почты лидов) НЕ попадают в логи, иначе аудит сам стал бы утечкой ПДн.
**Где логи: ** `/var/log/postgresql/postgresql-16-main.log` , строки вида `AUDIT: SESSION,...` .
**Проверка: ** `CREATE TABLE _smoke(id int); INSERT INTO _smoke VALUES (1); DROP TABLE _smoke;` → в логе три строки `AUDIT: ... DDL/WRITE/DDL` с `<not logged>` вместо значений.
---
## #29 pg_anonymizer (anon) — маскирование ПДн в выгрузках
**Что даёт: ** маскированные дампы базы (телефоны → `+7******XX` , почты → `iv***.ru` ), чтобы реальные ПДн не попадали в dev/staging. Правило §5.1 правил Claude.
**Версия: ** anon 3.0.13 — это **Rust/pgrx 0.18.0 ** расширение; готового пакета нет ни в Ubuntu, ни в PGDG → собрано из исходников.
**Сборка (выполнено, ~15 мин): **
``` bash
# build-deps (после — удалены, см. ниже)
sudo apt-get install -y build-essential postgresql-server-dev-16 pkg-config git
# Rust toolchain (в ~/.cargo, ~/.rustup — после удалены)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal
source " $HOME /.cargo/env "
cargo install cargo-pgrx --version 0.18.0 --locked # версия = pgrx из Cargo.lock
cargo pgrx init --pg16 /usr/lib/postgresql/16/bin/pg_config # системный PG, без скачивания
git clone --depth 1 https://gitlab.com/dalibo/postgresql_anonymizer.git /tmp/anon && cd /tmp/anon
make extension PG_CONFIG = /usr/lib/postgresql/16/bin/pg_config PGVER = pg16 # длинная компиляция
sudo make install PG_CONFIG = /usr/lib/postgresql/16/bin/pg_config PGVER = pg16 # просто cp (cargo root-у не нужен)
```
**Подключение (выполнено) — БЕЗ перезапуска: **
``` bash
sudo -u postgres psql -d liderra -c "CREATE EXTENSION anon CASCADE;"
sudo -u postgres psql -d liderra -c "SELECT anon.init();"
```
**Загрузка ПО ТРЕБОВАНИЮ (важно для производительности): ** anon НЕ подключён через `session_preload_libraries` на всю БД — иначе 9.6 МБ `anon.so` грузились бы при каждом подключении портала. В сессии маскирования библиотека загружается явно:
``` sql
LOAD ' anon ' ;
SELECT anon . partial ( ' +79161234567 ' , 2 , ' ****** ' , 2 ) ; -- → +7******67
```
2026-06-17 19:33:58 +03:00
### Правила маскирования (`SECURITY LABEL`) — добавлено 17.06.2026
До 17.06.2026 на проде был только `CREATE EXTENSION anon` + `anon.init()` , но **ни одного
правила маскирования** — поэтому `pg_dump` отдавал ПДн открыто. Правила вынесены в
репозиторий: [`db/anon_masking_labels.sql` ](../../db/anon_masking_labels.sql ). Файл
декларативно навешивает `SECURITY LABEL` на ПДн-колонки `users` , `deals` , `supplier_leads` ,
`pd_subject_requests` , `pd_processing_log` , `auth_log` , `tenants` .
2026-06-17 20:02:51 +03:00
* * ✅ ПРИМЕНЕНО НА ПРОД 17.06.2026.** `anon.pg_masking_rules` = **20 правил ** . Маскирование
проверено на живых данных через временную masked-роль (создана и удалена, данные не менялись):
`deals.phone` → `+7******01` , `contact_name` → подменённая фамилия, `users.email` →
`cl******@li******.test` , `supplier_leads.phone` → `79******59` . `pg_dump` под masked-ролью
больше не отдаёт ПДн открыто — блокер B2 закрыт.
> **Квирк anon 3.0.13 (важно для будущих правил).** `MASKED WITH VALUE` **не принимает
> каст** `…::jsonb` / `…::inet` (ни с `$$…$$`, ни с одинарными кавычками) — ошибка
> «is not a valid expression». Поэтому: nullable JSONB/INET (`deals.phones`,
> `pd_processing_log.ip_address`) маскируются в `MASKED WITH VALUE NULL`; NOT-NULL JSONB
> (`supplier_leads.raw_payload`) — через `MASKED WITH FUNCTION pg_catalog.jsonb_build_object()`
> (возвращает `{}`). Долларовые кавычки `$$…$$` внутри `MASKED WITH FUNCTION` (аргумент
> `anon.partial`) работают нормально.
2026-06-17 19:33:58 +03:00
**Почему применение безопасно для боевого портала: **
- `SECURITY LABEL ON COLUMN` активирует маскирование **только ** для ролей с меткой
`MASKED` . Роли приложения (`crm_app_user` , `crm_app_admin` , `crm_supplier_worker` ,
`crm_readonly` , `crm_migrator` ) продолжают видеть реальные данные — портал не затронут.
- Реальные данные **не переписываются ** (динамическое маскирование, не
`anon.anonymize_database()` — его на проде не запускаем).
- RLS не затрагивается: tenant-изоляция и маскирование независимы; `tenant_id` , `id` ,
ключи партиций и FK-ключи не маскируются → ни изоляция, ни целостность не нарушаются.
- `users.email` и прочие UNIQUE/контактные почты маскируются детерминированной
`anon.partial_email` (сохраняет уникальность по входу).
**ПЛАН ПРИМЕНЕНИЯ НА ПРОД (выполняет владелец, по бэкапу выше): **
2026-05-22 04:58:47 +03:00
``` bash
2026-06-17 19:33:58 +03:00
# 0) бэкап (см. раздел «Бэкап перед работами»)
# 1) применить правила (под postgres; роли приложения не трогаются)
sudo -u postgres psql -d liderra -c "LOAD 'anon';" -f /var/www/liderra/db/anon_masking_labels.sql
# 2) проверить, что правила зарегистрированы
sudo -u postgres psql -d liderra -c "SELECT relname, attname, masking_function FROM anon.pg_masking_rules ORDER BY relname, attname;"
# 3) выделенная роль для маскированных дампов (BYPASSRLS — чтобы выгрузить все тенанты)
sudo -u postgres psql -d liderra -c "CREATE ROLE anon_dumper LOGIN PASSWORD '<секрет-из-Lockbox>' BYPASSRLS;"
sudo -u postgres psql -d liderra -c "GRANT pg_read_all_data TO anon_dumper;"
sudo -u postgres psql -d liderra -c "SECURITY LABEL FOR anon ON ROLE anon_dumper IS 'MASKED';"
2026-05-22 04:58:47 +03:00
```
2026-06-17 19:33:58 +03:00
**Сделать маскированный дамп ** (anon боевые данные не меняет — только при явном
`anonymize_database()` , которого на проде не запускаем):
``` bash
# дамп под masked-ролью с прозрачным динамическим маскированием:
PGOPTIONS = "-c anon.transparent_dynamic_masking=on" \
pg_dump -U anon_dumper -h 127.0.0.1 -d liderra -Fc > /home/ubuntu/backups/liderra-masked.dump
# либо обёртка pg_dump_anon, если присутствует в сборке anon
```
**Критерий приёмки дампа: ** восстановить в одноразовую БД и убедиться, что
`deals.phone` имеет вид `+7******XX` , `users.email` частично замаскированы,
`supplier_leads.raw_payload` = `{}` , реальных телефонов/почт нет.
> **dev:** на native Windows PostgreSQL расширение `anon` не ставится (см. блок «Факт на
> 17.06.2026» в шапке). Доказательство маскированного дампа выполняется на проде по плану
> выше; dev держит демо-данные без реальных ПДн.
2026-05-22 04:58:47 +03:00
**Файлы: ** `anon.so` + `anon.control` в `/usr/lib/postgresql/16/lib/` и `/usr/share/postgresql/16/extension/` — это standalone-файлы, не принадлежат apt-пакету (сохраняются при autoremove). После **мажорного ** апгрейда PostgreSQL расширение нужно пересобрать (re-clone + rebuild).
---
## ⚠️ Незапланированный апгрейд PG + закрепление версии
Установка `postgresql-server-dev-16` из PGDG потянула апгрейд боевого `postgresql-16` **16.13 → 16.14 ** (сборка PGDG) с авто-перезапуском кластера. Минорный патч — данные целы, портал здоров. Закреплено против повтора:
``` bash
sudo mv /etc/apt/sources.list.d/pgdg.list /etc/apt/sources.list.d/pgdg.list.disabled
sudo apt-mark hold postgresql-16 postgresql-client-16 # в `dpkg -l` статус 'hi', не 'ii'
```
Для будущего патча PostgreSQL — `sudo apt-mark unhold postgresql-16 postgresql-client-16` осознанно.
## Очистка build-инструментов (выполнено)
``` bash
rm -rf ~/.cargo ~/.rustup ~/.pgrx /tmp/anon # Rust + исходники (~2.2 ГБ)
sudo apt-get purge -y build-essential postgresql-server-dev-16 pkg-config
sudo apt-get autoremove --purge -y # gcc/llvm/clang orphans
```
Расширения (`pgaudit.so` , `anon.so` ) — отдельные файлы, очистку build-тулчейна переживают.
---
## Серверный слой защиты — отдельно
WAF / anti-brute / DDoS / мониторинг / секреты / TLS-HSTS-CSP / бэкапы — это инфраструктура, не расширения БД. Открытые вопросы **SEC-1..SEC-7 ** (`docs/Открытые_вопросы_v8_3.md` ), привязка к Б-1.