Files
pd-guard/docs/03-architecture.md
T

13 KiB
Raw Blame History

Схема архитектуры и настройки

Модуль стоит между системой-потребителем и внешней языковой моделью. Потребитель отдаёт текст один раз на вход и один раз на выход. В модель уходит уже подменённый текст, потребителю возвращается текст с восстановленными значениями.

flowchart LR
  consumer["Система-потребитель"]
  api["POST /process и POST /proxy"]
  policy["Политика системы\nconfig/systems.json"]
  rules["Правила и словари"]
  ner["Вторая ступень\nNER, если включена"]
  filters["Отсев ложных\nсрабатываний"]
  masker["Маскирование\nMASK / STRICT / TOKEN / SYNTHETIC"]
  store["Хранилище соответствий\nпамять, AES-GCM"]
  llm["Внешняя LLM\nили заглушка"]

  consumer --> api --> policy --> rules --> ner --> filters --> masker --> store
  masker -->|"только /proxy"| llm
  llm -->|"демаскирование по токенам"| consumer
  store -->|"демаскирование по payload_id"| consumer

POST /process — контракт для системы-потребителя: маскирование и демаскирование. POST /proxy — демонстрация всей цепочки до модели и обратно; проверяющий контур может его не использовать.

Настройки систем, перечень типов и вид маски задаются файлом config/systems.json. Код для смены политики пересобирать не нужно. Файл перечитывается при изменении и по POST /admin/reload.

Что происходит с одним текстом

  1. По заголовку X-System-Id выбирается политика. Неизвестное имя получает политику default. Выключенная система и неверный X-System-Key получают 403 до обработки текста.
  2. Если payload_id уже есть в хранилище этой системы, направление определяется сравнением текста с сохранённой маской и с исходником.
  3. Иначе текст проходит детекцию. Сначала правила: контрольные суммы (карта, ИНН, СНИЛС, ОГРН), однозначные форматы (email, телефон), затем шаблоны с якорным словом (документ, адрес, дата, ФИО). Цифровые значения с нестандартными разделителями дополнительно собираются в кластер и проверяются той же контрольной суммой.
  4. Если включена вторая ступень, модель смотрит только непокрытые кандидаты: цепочки слов с заглавной буквы и, для юридической модели, цифровые кластеры. В поставке по умолчанию ступень выключена (pdguard.ner.*-engine: off). В compose.yaml она включается, если в каталог models/ положены веса.
  5. Ложные срабатывания снимаются до маски: имя в составе организации и на вывеске, общеизвестное имя без других персональных данных рядом, адрес отделения, типы-спутники без самостоятельного персонального данного.
  6. Оставшиеся фрагменты заменяются по maskMode системы. Пара «исходный текст ↔ маска» пишется в хранилище этой системы. Исходный текст в хранилище шифруется AES-GCM, ключ — pdguard.store.encryption-key.

Демаскирование не запускает детектор заново: по payload_id (и запасным отпечатком маски) достаётся сохранённый исходник. Поэтому звёздочки тоже обратимы, пока жива запись.

Системы-потребители

Файл config/systems.json, путь переопределяется свойством pdguard.systems-file.

Система Включена Демаскирование Режим Типы
default да да MASK все (*)
crm да нет TOKEN ФИО, телефон, email, город, улица, дом, квартира
analytics да нет SYNTHETIC все (*)
strict да да STRICT все (*)
legacy-billing нет нет MASK все (*), запросы отклоняются

Запрос без заголовка идёт в default. Хранилище у каждой системы своё: одна система не читает соответствия другой.

Поля политики:

Поле Назначение
enabled false — модуль отвечает 403 и текст не обрабатывает
demask разрешено ли обратное преобразование
maskMode чем заменяется найденное значение
types какие типы маскировать; "*" — все, которые знает сборка
requireCompanion типы, которые маскируются только рядом с самостоятельным персональным данным
key общий секрет; если задан, заголовок X-System-Key обязан совпасть. Только ASCII

Пример добавления системы — дописать объект и перечитать файл:

"dms": {
  "enabled": true,
  "demask": true,
  "maskMode": "MASK",
  "types": ["FIO", "PHONE", "MEDICAL_POLICY", "BIRTH_DATE"],
  "key": "dms-secret"
}

После POST /admin/reload запросы с X-System-Id: dms и X-System-Key: dms-secret маскируют только перечисленные типы, звёздочками, и умеют демаскировать.

Новый тип персональных данных добавляется правилом в реестре (DocumentRules, FinanceRules, DateRules, FioRules, ContactRules, AddressRules) и именем в PdTypes. Политики, где указано "*", подхватывают его без правки конфига. Политика с явным списком — только если имя типа туда добавить.

Типы персональных данных

Список отдаёт GET /admin/types. Группы:

Группа Типы
Человек FIO, CARDHOLDER
Документы PASSPORT, PASSPORT_ISSUER, PASSPORT_DATE, DEPT_CODE, FOREIGN_PASSPORT, DRIVER_LICENSE, MILITARY_ID, BIRTH_CERTIFICATE, MEDICAL_POLICY
Контакты PHONE, EMAIL
Адрес ADDRESS_COUNTRY, ADDRESS_POSTCODE, ADDRESS_CITY, ADDRESS_STREET, ADDRESS_HOUSE, ADDRESS_FLAT. ADDRESS_REGION и ADDRESS_DISTRICT размечает только модель второй ступени
Даты и гражданство BIRTH_DATE, BIRTH_PLACE, DATE, CITIZENSHIP
Платёжные данные CARD, CARD_EXPIRY, CVV, PIN
Реквизиты INN, SNILS, ACCOUNT_NUMBER, BIK, OGRN, OGRNIP, KPP
Прочее INCOME, BIOMETRIC

Правила устойчивы к регистру, к дате числом и словами, к вставке слов между серией и номером документа, к уменьшительным формам имён. Карта, ИНН, СНИЛС, ОГРН и ОГРНИП без верной контрольной суммы не маскируются.

Режимы маскирования

Режим задаётся полем maskMode и действует на все типы, которые политика разрешила.

Режим Пример для Иванов Иван Иванович и паспорта 4509 123456 Когда уместен
MASK И. И. И., паспорт 45** ****56 человеку остаётся узнаваемый контур, середина закрыта. Края коротких серий (загранпаспорт, военный билет, свидетельство о рождении) не открываются. CVV и PIN закрываются целиком
STRICT сплошные звёздочки на всю длину, включая ФИО ничего из исходных знаков не остаётся
TOKEN [FIO_1], [PASSPORT_1] однозначная обратимая подстановка, удобная и для демаскирования ответа модели
SYNTHETIC вымышленные ФИО и номер той же формы модель видит правдоподобный текст. Для типов без своей подстановки остаётся токен

Одинаковое исходное значение внутри одного текста получает одну и ту же замену.

В POST /proxy режим системы для отправки в модель заменяется на TOKEN: одинаковые звёздочки нельзя однозначно вернуть на место в ответе модели.

Контекстное правило

Типы из requireCompanion сами по себе персональными данными не считаются. Они маскируются, только если в том же тексте есть находка самостоятельного типа.

В политиках default и strict спутники такие: CVV, PIN, DATE, BIRTH_PLACE, ADDRESS_COUNTRY, ACCOUNT_NUMBER, BIK, OGRN, OGRNIP, KPP, INCOME, BIOMETRIC.

Два спутника друг друга не подтверждают. Дата рядом с ОГРН без имени и документа человека не маскируется. PIN рядом с номером карты — маскируется. Список спутников у каждой системы свой; пустой список означает, что маскируется всё найденное из types.

Отдельно от этого списка снимаются ложные ФИО и адреса: «Александр Пушкин» без других персональных данных, «Институт Склифосовского», адрес отделения банка. Если рядом с общеизвестным именем есть другой тип персональных данных, имя остаётся замаскированным: однофамилец защиту не теряет.

Состояние и наблюдаемость

Хранилище соответствий по умолчанию — память процесса (pdguard.store.backend=memory): потолок pdguard.store.max-chars (512 МБ символов), срок жизни записи pdguard.store.ttl-minutes (30 минут). При backend=redis та же запись дублируется в Redis, чтобы демаскирование попало на другой узел. В текущем compose.yaml поднят один узел, Redis не используется.

Одновременные запросы ограничивает адаптивный предел: он растёт, пока задержка укладывается в pdguard.target-latency-ms (200 мс), и сжимается, когда перестаёт. Лишние запросы получают 429, очередь не копится.

Метрики отдаёт Micrometer на /actuator/prometheus. Готовые Prometheus и Grafana лежат в monitoring/ и поднимаются тем же docker compose.