Files
pd-guard/README.md
T
Максименко Никита Владимирович 6afe3442f2 fix: устойчивость Redis-кластера под нагрузкой и код-ревью замечания
Redis timeout 200ms давал ложные срабатывания под пиковой нагрузкой на общем
хосте — подняли до 800ms и добавили cpu/mem лимиты сервисам в compose, чтобы
соседи не выедали CPU у Redis. Добавили метрику и WARN на случай, когда
демаскирование не находит соответствие ни по id, ни по отпечатку маски (раньше
тихо превращалось в повторное маскирование без единого следа в логах).

Кластерные узлы (node-a/node-b) получили обе NER-модели (WikiNEuRal для имён,
ruBERT для адресов) — раньше конфиг ссылался на несуществующие свойства и
вторая ступень молча не работала. lb (nginx) и volume для prometheus/grafana
данных зафиксированы в compose.

Плюс код-ревью фиксы: утечка нативных ONNX-ресурсов при ошибке загрузки модели
(BLOCKER), неверный HTTP-статус при сбое обработки, generic Exception в
LlmClient заменён на конкретные, лишние same-package импорты убраны.
2026-09-22 21:40:18 +03:00

239 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PD Guard — модуль безопасности персональных данных
Прокси-модуль между системой-потребителем и внешней языковой моделью (LLM).
Идентифицирует персональные данные (ПДН) в тексте, маскирует их перед отправкой
в модель и восстанавливает исходные значения в ответе. Ни один фрагмент ПДН не
покидает контур в открытом виде.
```
Система-потребитель → Модуль (идентификация → маскирование → LLM → демаскирование) → Потребитель
```
---
## Возможности
- **Идентификация ПДН** — 30+ типов: ФИО, паспорт РФ, загранпаспорт, водительское
удостоверение, военный билет, свидетельство о рождении, полис ОМС, ИНН, СНИЛС,
телефон, email, адрес (страна/индекс/город/улица/дом/квартира), дата рождения,
место рождения, гражданство, банковская карта, CVV, PIN, срок действия карты,
держатель карты, банковские реквизиты (счёт, БИК, ОГРН, ОГРНИП, КПП), доход,
биометрия.
- **Три режима маскирования** — звёздочки, токенизация, синтетические данные.
- **Демаскирование** — восстановление исходного текста по `payload_id`.
- **Гибкая настройка под систему** — список систем-потребителей, перечень типов
ПДН, факт маскирования и демаскирования настраиваются без правки кода.
- **Контекстное маскирование** — типы, опасные только в сочетании с другими ПДН
(PIN + номер карты), маскируются по настраиваемому правилу.
- **Устойчивость к вариациям** — регистр, форматы дат (числом и текстом),
разделяющие слова («серия … номер …»), уменьшительные формы имён.
- **Защита от ложных срабатываний** — «поэт Александр Пушкин» и адрес отделения
банка не считаются данными клиента.
- **Производительность** — адаптивный лимитер конкурентности, ~9 500 RPS на одном
узле при p95 ≈ 14 мс.
---
## Быстрый старт
### Требования
- Java 21
- Maven 3.8+ (или обёртка `./mvnc`)
- Docker + Docker Compose (для контейнерного запуска и мониторинга)
### Сборка и запуск
```bash
# сборка fat-jar
./mvnc package -DskipTests
# запуск
java -jar target/pd-guard-spring-1.0.0.jar
```
Сервис поднимется на `http://localhost:8080`. Проверка готовности:
```bash
curl http://localhost:8080/health # → OK
```
### Запуск через Docker Compose
```bash
# одиночный узел + Prometheus + Grafana
docker compose up -d --build
# кластер: 2 узла + nginx-балансировщик + Redis + мониторинг
docker compose --profile cluster up -d --build
```
---
## API
### Маскирование / демаскирование
`POST /process`
Направление определяется по `payload_id`: неизвестный идентификатор — маскируем,
ранее выданный текст маски — возвращаем исходный текст.
```bash
# маскирование
curl -X POST http://localhost:8080/process \
-H "Content-Type: application/json" \
-H "X-System-Id: crm" \
-d '{"payload":"Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67","payload_id":"doc-1"}'
# демаскирование — тот же payload_id и замаскированный текст
curl -X POST http://localhost:8080/process \
-H "Content-Type: application/json" \
-H "X-System-Id: crm" \
-d '{"payload":"Клиент [FIO_1], паспорт 45** ****56, тел +7 *** ***-**-**","payload_id":"doc-1"}'
```
Заголовки:
| Заголовок | Назначение |
|-----------|------------|
| `X-System-Id` | имя системы-потребителя; нет — применяется `default` |
| `X-System-Key` | общий секрет системы; проверяется, если задан в настройках |
Коды ответа: `200` — успех, `400` — невалидный запрос, `403` — система выключена
или неверный ключ, `429` — перегрузка (с `Retry-After`).
### Прокси к LLM (демо-плечо)
`POST /proxy` — показывает всю цепочку: что ушло в модель, что она вернула и что
получил потребитель с восстановленными значениями.
```bash
curl -X POST http://localhost:8080/proxy \
-H "Content-Type: application/json" \
-d '{"prompt":"Клиент Иванов Иван Иванович, карта 4111 1111 1111 1111. Кратко опишите профиль."}'
```
### Служебные
| Метод | Путь | Назначение |
|-------|------|------------|
| GET | `/health` | проба готовности |
| GET | `/admin/config` | действующие настройки систем |
| GET | `/admin/types` | список поддерживаемых типов ПДН |
| POST | `/admin/reload` | перечитать `config/systems.json` без перезапуска |
| GET | `/actuator/health` | Spring Boot health |
| GET | `/actuator/metrics` | метрики Micrometer |
| GET | `/actuator/prometheus` | метрики в формате Prometheus |
---
## Настройка систем-потребителей
Файл `config/systems.json` (путь задаётся свойством `pdguard.systems-file`).
Перечитывается на лету через `POST /admin/reload`.
```json
{
"default": {
"enabled": true,
"demask": true,
"maskMode": "MASK",
"types": ["*"],
"requireCompanion": ["CVV", "PIN", "DATE", "BIRTH_PLACE", "ADDRESS_COUNTRY",
"ACCOUNT_NUMBER", "BIK", "OGRN", "OGRNIP", "KPP", "INCOME", "BIOMETRIC"]
},
"crm": {
"enabled": true,
"demask": false,
"maskMode": "TOKEN",
"types": ["FIO", "PHONE", "EMAIL", "ADDRESS_CITY", "ADDRESS_STREET",
"ADDRESS_HOUSE", "ADDRESS_FLAT"]
},
"analytics": {
"enabled": true,
"demask": false,
"maskMode": "SYNTHETIC",
"types": ["*"]
}
}
```
Поля политики:
| Поле | Назначение |
|------|------------|
| `enabled` | разрешено ли системе обращаться в модуль |
| `demask` | выполнять ли обратное преобразование |
| `maskMode` | `MASK` (звёздочки), `TOKEN` (токены), `SYNTHETIC` (синтетика) |
| `types` | типы ПДН к маскированию; `"*"` — все известные |
| `requireCompanion` | типы, маскируемые только вместе с ПДН другого типа |
| `key` | общий секрет системы (проверяется через `X-System-Key`) |
Новые типы ПДН добавляются через справочник правил (`RuleRegistry`) без
переписывания ядра.
---
## Логирование и метрики
### Логи
В журнал попадают только идентификатор, типы ПДН и их количество. **Значения ПДН
не логируются ни на одном уровне.**
```
payload_id=doc-1 символов=64 найдено={FIO=1, PASSPORT=1, PHONE=1}
```
### Метрики (Prometheus, `/actuator/prometheus`)
| Метрика | Назначение |
|---------|------------|
| `pdguard.process` | длительность обработки (разрез по направлению и системе) |
| `pdguard.pd.detected` | счётчик найденных ПДН по типу и системе |
| `pdguard.requests.rejected` | отклонённые запросы (перегрузка/невалидные/запрет) |
| `pdguard.concurrency.limit` | текущий потолок конкурентности |
| `pdguard.concurrency.in.flight` | запросов в обработке |
| `pdguard.store.chars` | объём хранилища соответствий |
| `pdguard.tokens.processed` | оценка обработанных токенов (для TPS) |
Готовый дашборд Grafana и конфигурация Prometheus — в каталоге `monitoring/`.
---
## Производительность
Нагрузочное тестирование проведено инструментом k6 (профиль 500 → 1000 → 1500 →
2000 VU, одиночный узел, Docker):
| Метрика | Значение |
|---------|----------|
| Пропускная способность | ~9 500 RPS |
| Задержка p50 | 1.03 мс |
| Задержка p95 | 13.87 мс |
| Ошибки | 0.00 % |
Целевой уровень из задания (latency ≤ 0.5 с при RPS 1000) выполнен с большим
запасом. В кластерном режиме (2 узла + nginx) пропускная способность выше.
---
## Ограничения
- Хранилище соответствий по умолчанию — в памяти (`pdguard.store.backend=memory`),
сбрасывается при перезапуске. Для кластера используется Redis.
- NER-модель второй ступени (`models/rubert-ner`) не входит в репозиторий и
скачивается скриптом `tools/fetch-ner-model.sh`; без неё сервис работает на
правилах.
- Демаскирование доступно только системам с `demask: true` и корректным ключом.
---
## План развития
- Подключение NER-модели для распознавания имён и адресов в свободном тексте.
- Расширение перечня документов, удостоверяющих личность.
- Настраиваемые правила контекстного маскирования через конфиг.
- Шифрование хранилища соответствий.
- Интеграция с CI/CD и автоматический прогон нагрузочных тестов.