Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
13 KiB
pg_audit (#28) + pg_anonymizer (#29) — установка на боевом сервере
Статус: ✅ установлены на боевом liderra.ru 22.05.2026. PostgreSQL 16.14, БД liderra.
Факт на 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 …» ниже.
Это два расширения 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.
Бэкап перед работами (обязательно)
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.
Установка (выполнено):
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 мин):
# 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-у не нужен)
Подключение (выполнено) — БЕЗ перезапуска:
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 грузились бы при каждом подключении портала. В сессии маскирования библиотека загружается явно:
LOAD 'anon';
SELECT anon.partial('+79161234567', 2, '******', 2); -- → +7******67
Правила маскирования (SECURITY LABEL) — добавлено 17.06.2026
До 17.06.2026 на проде был только CREATE EXTENSION anon + anon.init(), но ни одного
правила маскирования — поэтому pg_dump отдавал ПДн открыто. Правила вынесены в
репозиторий: db/anon_masking_labels.sql. Файл
декларативно навешивает SECURITY LABEL на ПДн-колонки users, deals, supplier_leads,
pd_subject_requests, pd_processing_log, auth_log, tenants.
✅ ПРИМЕНЕНО НА ПРОД 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) работают нормально.
Почему применение безопасно для боевого портала:
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(сохраняет уникальность по входу).
ПЛАН ПРИМЕНЕНИЯ НА ПРОД (выполняет владелец, по бэкапу выше):
# 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';"
Сделать маскированный дамп (anon боевые данные не меняет — только при явном
anonymize_database(), которого на проде не запускаем):
# дамп под 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 держит демо-данные без реальных ПДн.
Файлы: 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) с авто-перезапуском кластера. Минорный патч — данные целы, портал здоров. Закреплено против повтора:
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-инструментов (выполнено)
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.