Разработчику нужен дамп прода для дебага. Прод содержит PII (email, телефоны, ФИО, документы). Передавать как есть — нарушение GDPR / 152-ФЗ. Эта статья — что и как маскировать.
Правила пронумерованы кодами PG-AN-NNN.
1. Базовая позиция
PG-AN-001 — Никогда не передавай дамп прода разработчику без анонимизации
Даже если «он надёжный».
- Юридически — нарушение закона о персональных данных.
- Безопасно — каждая копия данных = новая поверхность атаки.
- Этически — пользователи не подписывались на это.
PG-AN-002 — Default для разработчика — синтетический dataset
Сгенерированные данные через scripts / faker. Покрывает большинство кейсов.
PG-AN-003 — Если нужен реальный дамп для воспроизведения проблемы:
- Анонимизация PII.
- Минимальный объём (одна сущность, не вся БД).
- Подписан DPA (Data Processing Agreement) или внутренний регламент.
2. Что считается PII
PG-AN-010 — Прямые идентификаторы:
- Имя, фамилия, отчество.
- Email.
- Телефон.
- Адрес (физический).
- Паспорт, СНИЛС, ИНН, водительское удостоверение.
- Дата рождения.
- IP-адрес.
- Номер банковской карты, счёта.
PG-AN-011 — Косвенные идентификаторы:
- Геолокация (точность > населённого пункта).
- Уникальные комбинации (профессия + город + возраст могут идентифицировать).
- User-Agent / fingerprint.
- Связи в графе пользователей.
PG-AN-012 — Бизнес-секреты, не PII, но всё равно чувствительные:
- Цены на B2B-контракты.
- Внутренние комментарии модераторов.
- Логи действий внутренних сотрудников.
3. Стратегии анонимизации
PG-AN-020 — Несколько подходов, выбираются под задачу:
| Стратегия | Что делает | Когда оправдана |
|---|---|---|
| Удаление | UPDATE t SET email = NULL | Поле не нужно для дебага |
| Замена константой | email = 'masked@example.com' | Тестируем структуру, не содержимое |
| Псевдонимизация | детерминированно email = 'user' || md5(email) || '@test' | Уникальность сохранилась, обратимости нет |
| Faker-замена | сгенерить случайное имя/email/телефон | Реалистичный dataset для тестов |
| Shuffle | переставить значения между строками | Сохранение распределения, потеря связи |
| Generalization | возраст → возрастная группа | Снижение точности для аналитики |
| Хэширование | SHA-256 от email | Только для использования as opaque ID |
4. Маскирование одного поля
PG-AN-030 — Email — псевдонимизация с хэшем:
UPDATE customer
SET email = 'user' || substring(md5(email), 1, 8) || '@example.test'
WHERE email IS NOT NULL;
Уникальность сохранилась (одинаковый email → одинаковый псевдоним). Восстановить нельзя без оригинала.
PG-AN-031 — Телефон — заменить на формат с шумом:
UPDATE customer
SET phone = '+7000' || lpad((random() * 10000000)::int::text, 7, '0')
WHERE phone IS NOT NULL;
PG-AN-032 — Имя — fake-генерация:
Через extension postgresql_anonymizer:
CREATE EXTENSION anon CASCADE;
SELECT anon.init();
UPDATE customer SET
first_name = anon.fake_first_name(),
last_name = anon.fake_last_name();
Без extension — бить по фейк-словарю на стороне Java/Python (faker.js, Faker).
PG-AN-033 — Дата рождения — generalize до года:
UPDATE customer SET
born_on = date_trunc('year', born_on)::date;
Возраст плюс-минус сохранён, конкретный день — нет.
PG-AN-034 — Адрес — оставить только город или область:
UPDATE customer SET
address = NULL, -- или 'г. ' || city
apartment = NULL;
5. Полный сценарий
PG-AN-040 — Стандартный flow:
-
Скопировать прод-БД в staging-anonymizer:
pg_dump prod | psql anonymizer -
Запустить анонимизирующий SQL-скрипт:
BEGIN; -- email UPDATE customer SET email = 'user' || substring(md5(email), 1, 8) || '@example.test' WHERE email IS NOT NULL; -- phone UPDATE customer SET phone = '+7000' || lpad((random()*10000000)::int::text, 7, '0') WHERE phone IS NOT NULL; -- имена UPDATE customer SET first_name = 'Имя' || id, last_name = 'Фамилия' || id; -- адреса UPDATE customer_address SET street = NULL, building = NULL, apartment = NULL; -- паспорта UPDATE customer_document SET number = '0000' || lpad(id::text, 6, '0'), issued_by = 'TEST'; -- payment cards (не должно быть в БД вообще, но на всякий) UPDATE payment SET card_last4 = '0000', card_holder_name = 'TEST'; -- audit log с PII DELETE FROM audit_log WHERE created_at < now() - interval '7 days'; COMMIT; -
Дамп анонимизированной БД:
pg_dump anonymizer -Fc > prod-anon-$(date +%F).dump -
Передача дампа разработчику.
-
Удалить anonymizer-БД (не оставляй полу-обработанные данные).
PG-AN-041 — Скрипт хранится в репозитории
, версионируется, ревьюется. Не однострочник «по памяти».
6. PostgreSQL Anonymizer — extension
PG-AN-050 — postgresql_anonymizer — специализированное расширение
Возможности:
- Декларативные правила: «колонка
emailмаскируется как fake email». - Динамическое маскирование (вид с маскированием для определённых ролей).
- Готовые функции:
anon.fake_first_name(),anon.fake_email(),anon.partial(text, 2, '*****', 2)etc.
CREATE EXTENSION anon CASCADE;
SELECT anon.init();
SECURITY LABEL FOR anon ON COLUMN customer.email
IS 'MASKED WITH FUNCTION anon.fake_email()';
SECURITY LABEL FOR anon ON COLUMN customer.last_name
IS 'MASKED WITH FUNCTION anon.fake_last_name()';
-- маскированный дамп:
SELECT anon.dump('customer'); -- или специальный command
PG-AN-051 — Альтернатива — Greenmask, ARX, ad-hoc-скрипты
Выбор зависит от стека.
7. Что нельзя замаскировать
PG-AN-060 — Идентификаторы для бизнес-связности оставлять как есть:
customer.id,order.id— связи между таблицами должны работать.- Технические FK.
PG-AN-061 — Бизнес-данные, не PII:
- Названия продуктов, цены — обычно сохраняем.
- Статусы, типы — сохраняем.
- Текст описаний/комментариев — спорно (может содержать PII), решать на каждый кейс.
PG-AN-062 — Free-text поля (комментарии, описания) — сложный случай
Пользователь мог вписать туда email, телефон, ФИО. Варианты:
- Удалить целиком (
SET comment = NULL). - Заменить на placeholder (
SET comment = '[REDACTED]'). - ML-fly через
pg_anonymizer.partial_text()или внешний NER.
8. Re-identification — реальная угроза
PG-AN-070 — Анонимизация имени и email недостаточна, если можно идентифицировать через комбинацию полей
Пример: если в дампе остался "профессия + дата рождения + город" — для маленького города это уникально идентифицирует.
PG-AN-071 — k-anonymity
— каждый объект неразличим от ≥k других по идентифицирующим полям. Сложная техника, для большинства проектов overkill, но знать стоит.
PG-AN-072 — Lite-вариант — обобщение полей-идентификаторов
(дата рождения → год, город → область).
9. Антипаттерны
PG-AN-080 Дамп прода передан разработчику как есть «он надёжный».
PG-AN-081 Анонимизация только email, оставляя ФИО и телефон.
PG-AN-082 Скрипт анонимизации руками, не в репозитории.
PG-AN-083 Использование random() для генерации, потом сравнение результатов разных дампов — каждый раз разные значения.
PG-AN-084 Удаление PII через DELETE вместо UPDATE — теряется бизнес-связность.
PG-AN-085 Хэширование email через MD5 без соли — для популярных доменов rainbow tables может «развернуть» обратно.
Чек-лист
- [ ] Скрипт анонимизации в репозитории, ревью.
- [ ] Маскированы все PII-колонки: email, phone, имена, адреса, документы, карты.
- [ ] Псевдонимизация (если уникальность нужна) детерминированная.
- [ ] Free-text комментарии — либо удалены, либо проверены NER.
- [ ] Дата рождения generalize до года или удалена.
- [ ] Геолокация generalize до города/области.
- [ ] Анонимизация выполняется на отдельной БД, не на проде.
- [ ] Промежуточные результаты удалены после генерации финального дампа.
- [ ] Передача дампа документирована (кто получил, когда, для какой задачи).
Связанные
- Backup и restore — откуда берётся исходный дамп.
- Расширения —
pgcryptoдля хэшей. - Multi-tenancy — анонимизация per-tenant.