13 KiB
Схема архитектуры и настройки
Модуль стоит между системой-потребителем и внешней языковой моделью. Потребитель отдаёт текст один раз на вход и один раз на выход. В модель уходит уже подменённый текст, потребителю возвращается текст с восстановленными значениями.
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.
Что происходит с одним текстом
- По заголовку
X-System-Idвыбирается политика. Неизвестное имя получает политикуdefault. Выключенная система и неверныйX-System-Keyполучают403до обработки текста. - Если
payload_idуже есть в хранилище этой системы, направление определяется сравнением текста с сохранённой маской и с исходником. - Иначе текст проходит детекцию. Сначала правила: контрольные суммы (карта, ИНН, СНИЛС, ОГРН), однозначные форматы (email, телефон), затем шаблоны с якорным словом (документ, адрес, дата, ФИО). Цифровые значения с нестандартными разделителями дополнительно собираются в кластер и проверяются той же контрольной суммой.
- Если включена вторая ступень, модель смотрит только непокрытые кандидаты: цепочки слов с заглавной буквы и, для юридической модели, цифровые кластеры. В поставке по умолчанию ступень выключена (
pdguard.ner.*-engine: off). Вcompose.yamlона включается, если в каталогmodels/положены веса. - Ложные срабатывания снимаются до маски: имя в составе организации и на вывеске, общеизвестное имя без других персональных данных рядом, адрес отделения, типы-спутники без самостоятельного персонального данного.
- Оставшиеся фрагменты заменяются по
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.