230 lines
14 KiB
Markdown
230 lines
14 KiB
Markdown
# Инструкция для жюри по проверке
|
||
|
||
Модуль принимает текст, находит в нём персональные данные, подменяет их и по тому же идентификатору возвращает исходный текст. Ниже — как это воспроизвести и где смотреть журнал и метрики.
|
||
|
||
Сервис слушает `http://localhost:8080`.
|
||
|
||
## Запуск
|
||
|
||
Нужны Java 21 и Maven 3.8+ (или обёртка `./mvnc`). Для дашборда — Docker Compose.
|
||
|
||
Локально:
|
||
|
||
```bash
|
||
./mvnc package -DskipTests
|
||
java -jar target/pd-guard-spring-1.0.0.jar
|
||
```
|
||
|
||
Контейнер, Prometheus и Grafana (`compose.yaml` ссылается на уже собранный образ `pd-guard-spring:jvm`, сам его не строит):
|
||
|
||
```bash
|
||
docker build -f src/main/docker/Dockerfile -t pd-guard-spring:jvm .
|
||
docker compose up -d
|
||
```
|
||
|
||
Если локальной Java нет — тот же образ собирается полностью внутри Docker, `mvn package` идёт в отдельной стадии сборки (дольше первого запуска, зато не требует ничего на хосте кроме Docker):
|
||
|
||
```bash
|
||
docker build -f src/main/docker/Dockerfile.build -t pd-guard-spring:jvm .
|
||
docker compose up -d
|
||
```
|
||
|
||
Готовность: `GET http://localhost:8080/health` отвечает `OK`.
|
||
|
||
Действующие системы читаются из `config/systems.json`. Это не встроенный файл в jar: при запуске из каталога проекта используется именно он.
|
||
|
||
## Как устроен запрос
|
||
|
||
`POST /process`
|
||
|
||
Тело:
|
||
|
||
```json
|
||
{"payload": "текст", "payload_id": "устойчивый-идентификатор"}
|
||
```
|
||
|
||
Заголовки:
|
||
|
||
| Заголовок | Смысл |
|
||
|-----------|--------|
|
||
| `X-System-Id` | имя системы из `config/systems.json`. Нет заголовка или имя неизвестно — применяется политика `default` |
|
||
| `X-System-Key` | нужен только если у системы в конфиге задано поле `key` |
|
||
|
||
Направление выбирается по `payload_id`, а не по отдельному флагу:
|
||
|
||
1. Идентификатор ещё не встречался — текст маскируется, пара «исходник ↔ маска» запоминается.
|
||
2. Пришёл ранее выданный текст маски и у системы включено `demask` — возвращается исходный текст.
|
||
3. Пришёл тот же исходный текст — возвращается та же маска, что и в первый раз.
|
||
|
||
Ответ: `{"result": "..."}`.
|
||
|
||
Коды: `200` успех, `400` нет `payload` или `payload_id`, `403` система выключена или неверный ключ, `429` перегрузка (заголовок `Retry-After: 1`). При внутреннем сбое сервис отвечает `200` и текстом `[обработка недоступна]`, чтобы исходные персональные данные не ушли наружу.
|
||
|
||
## Маскирование в браузере
|
||
|
||
Откройте `http://localhost:8080/`.
|
||
|
||
Четыре режима на странице — это четыре системы из конфига:
|
||
|
||
| Кнопка | Заголовок | Что увидите |
|
||
|--------|-----------|-------------|
|
||
| MASK | `default` | звёздочки с открытыми краями номера, ФИО инициалами |
|
||
| STRICT | `strict` | сплошные звёздочки, длина сохраняется |
|
||
| TOKEN | `crm` | токены вида `[FIO_1]`, `[PASSPORT_1]` |
|
||
| SYNTHETIC | `analytics` | правдоподобная подмена (вымышленное ФИО, номер, адрес) |
|
||
|
||
Вставьте текст, например:
|
||
|
||
```text
|
||
Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67
|
||
```
|
||
|
||
Нажмите «Обработать». Результат появится в блоке под кнопкой.
|
||
|
||
Страница каждый раз создаёт новый `payload_id`. Она показывает только маскирование. Демаскирование проверяется запросом к API с тем же идентификатором.
|
||
|
||
## Маскирование через API
|
||
|
||
Система `default` маскирует все известные типы и умеет демаскировать. Режим — звёздочки с открытыми краями номера (`MASK`).
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:8080/process \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-System-Id: default" \
|
||
-d "{\"payload\":\"Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67\",\"payload_id\":\"doc-1\"}"
|
||
```
|
||
|
||
Ожидаемый вид ответа: в `result` ФИО заменено на инициалы (`И. И. И.`), середина паспорта и телефона закрыта звёздочками, края видны.
|
||
|
||
Сплошные звёздочки — тот же запрос с заголовком `X-System-Id: strict`.
|
||
|
||
Токены вида `[FIO_1]`, `[PASSPORT_1]`, `[PHONE_1]` — тот же запрос с заголовком `X-System-Id: crm` (но у `crm` в политике нет `PASSPORT`, см. ниже).
|
||
|
||
Синтетика — `X-System-Id: analytics`. У этой системы обратное преобразование выключено.
|
||
|
||
Система `crm` маскирует только ФИО, телефон, email и части адреса (город, улица, дом, квартира). Паспорт в её политике не входит и в ответе останется открытым. Демаскирование у `crm` выключено.
|
||
|
||
Система `legacy-billing` выключена: тот же запрос с `X-System-Id: legacy-billing` даёт `403`.
|
||
|
||
## Демаскирование
|
||
|
||
Нужны три условия одновременно:
|
||
|
||
- тот же `payload_id`, что при маскировании (`doc-1` в примере выше);
|
||
- в `payload` — точная строка из поля `result` предыдущего ответа;
|
||
- система с `"demask": true`. Сейчас это `default` и `strict`. У `crm` и `analytics` демаскирование выключено намеренно.
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:8080/process \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-System-Id: default" \
|
||
-d "{\"payload\":\"<сюда строка result из маскирования>\",\"payload_id\":\"doc-1\"}"
|
||
```
|
||
|
||
В `result` вернётся исходная фраза с ФИО, паспортом и телефоном.
|
||
|
||
Соответствие живёт в памяти процесса 30 минут и пропадает после перезапуска. Повторный запрос после перезапуска будет обработан как новое маскирование.
|
||
|
||
Хранилище разделено по системам: маска, полученная от `default`, не раскрывается запросом от `strict` с тем же `payload_id`.
|
||
|
||
## Цепочка до модели
|
||
|
||
`POST /proxy` показывает, что ушло бы во внешнюю модель и что вернулось бы потребителю. Адрес модели по умолчанию пуст, поэтому отвечает заглушка: она возвращает присланный (уже замаскированный) текст. В модель подставляются токены, даже если у системы выбран другой режим: по звёздочкам однозначное восстановление невозможно.
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:8080/proxy \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-System-Id: default" \
|
||
-d "{\"prompt\":\"Клиент Иванов Иван Иванович, карта 4111 1111 1111 1111. Кратко опишите профиль.\"}"
|
||
```
|
||
|
||
В ответе:
|
||
|
||
| Поле | Смысл |
|
||
|------|--------|
|
||
| `prompt_masked` | текст, который ушёл бы в модель |
|
||
| `llm_response_masked` | ответ модели (или заглушки) ещё с токенами |
|
||
| `response` | текст потребителю; при `demask: true` токены заменены обратно |
|
||
| `replaced` | таблица «токен → исходное значение» |
|
||
| `llm` | `заглушка` либо имя модели |
|
||
|
||
В `prompt_masked` не должно остаться открытых ФИО и номера карты.
|
||
|
||
## Где смотреть логи
|
||
|
||
Отдельного файла журнала нет. Строки пишет процесс в стандартный вывод.
|
||
|
||
Локальный запуск — окно, где выполнена команда `java -jar`.
|
||
|
||
Docker:
|
||
|
||
```bash
|
||
docker compose logs -f pd-guard
|
||
```
|
||
|
||
На каждое маскирование есть строка уровня INFO. В ней идентификатор, длина текста и счётчики по типам. Сами значения персональных данных в журнал не пишутся:
|
||
|
||
```text
|
||
payload_id=doc-1 символов=72 найдено={FIO=1, PASSPORT=1, PHONE=1}
|
||
```
|
||
|
||
Рядом по смыслу, если они случаются:
|
||
|
||
- `Системе … обращение в модуль запрещено настройками` — выключенная система;
|
||
- `Системе … отказано: неверный ключ`;
|
||
- `демаскирование не нашло соответствие` — идентификатор и отпечаток маски неизвестны, текст обработан как новый;
|
||
- `Настройки систем перечитаны из …` — после правки `config/systems.json`.
|
||
|
||
Для прокси: `proxy: система=… заменено=… модель=…`. Число замен есть, значения — нет.
|
||
|
||
## Где смотреть метрики
|
||
|
||
Сырые метрики сервиса, без Docker:
|
||
|
||
| Адрес | Что это |
|
||
|-------|---------|
|
||
| `GET /actuator/prometheus` | все метрики в формате Prometheus |
|
||
| `GET /actuator/metrics` | список имён Micrometer |
|
||
| `GET /actuator/metrics/pdguard.process` | длительность обработки |
|
||
| `GET /actuator/metrics/pdguard.pd.detected` | сколько фрагментов найдено, в разрезе типа и системы |
|
||
| `GET /actuator/health` | проба Spring Boot |
|
||
|
||
Имеет смысл смотреть после одного-двух вызовов `/process`:
|
||
|
||
```bash
|
||
curl -s http://localhost:8080/actuator/prometheus | findstr pdguard
|
||
```
|
||
|
||
На Linux и macOS вместо `findstr` — `grep pdguard`.
|
||
|
||
Основные ряды:
|
||
|
||
| Метрика | О чём |
|
||
|---------|--------|
|
||
| `pdguard_process_seconds` | длительность, метки `direction` (`mask` / `unmask`) и `system` |
|
||
| `pdguard_pd_detected_total` | найденные фрагменты, метки `type` и `system` |
|
||
| `pdguard_requests_rejected_total` | отказы: `overload`, `malformed`, `system_disabled`, `internal_error` |
|
||
| `pdguard_concurrency_limit` | текущий потолок одновременных запросов |
|
||
| `pdguard_concurrency_in_flight` | сколько запросов в работе |
|
||
| `pdguard_store_chars` | объём хранилища соответствий в символах |
|
||
| `pdguard_tokens_processed_total` | оценка числа обработанных токенов |
|
||
| `pdguard_demask_unresolved_total` | демаскирование без найденного соответствия |
|
||
| `pdguard_ner_*` | вторая ступень (модель). В поставке без скачанных моделей ступень выключена, ряды почти не растут |
|
||
|
||
Дашборд появляется вместе с `docker compose`:
|
||
|
||
- Grafana: `http://localhost:3000` (анонимный просмотр включён). Дашборд «Модуль безопасности персональных данных»: `http://localhost:3000/d/pd-guard`. Обновление раз в 5 секунд. Блоки: обращения и типы ПДн, задержка и доля ответов быстрее 0,5 с, отказы, вторая ступень, ресурсы узла.
|
||
- Prometheus: `http://localhost:9090`. Цель сбора — `pd-guard:8080`, интервал 5 секунд.
|
||
|
||
## Что поменять без перезапуска
|
||
|
||
Отредактируйте `config/systems.json` и вызовите:
|
||
|
||
```bash
|
||
curl -s -X POST http://localhost:8080/admin/reload
|
||
```
|
||
|
||
Файл также перечитывается сам, если изменилось время модификации (проверка не чаще раза в секунду). Текущие политики: `GET /admin/config`. Список типов, которые умеет распознавать сборка: `GET /admin/types`.
|
||
|
||
Проверка набора типов: в политике `crm` нет `PASSPORT`. Текст с паспортом и телефоном под `X-System-Id: crm` скроет телефон и оставит номер паспорта. Под `default` скроет оба.
|