Разработчику нужен дамп прода для дебага. Прод содержит 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:

  1. Скопировать прод-БД в staging-anonymizer:

    pg_dump prod | psql anonymizer
    
  2. Запустить анонимизирующий 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;
    
  3. Дамп анонимизированной БД:

    pg_dump anonymizer -Fc > prod-anon-$(date +%F).dump
    
  4. Передача дампа разработчику.

  5. Удалить anonymizer-БД (не оставляй полу-обработанные данные).

PG-AN-041 — Скрипт хранится в репозитории

, версионируется, ревьюется. Не однострочник «по памяти».

6. PostgreSQL Anonymizer — extension

PG-AN-050postgresql_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.