14 KiB
Инструкция для жюри по проверке
Модуль принимает текст, находит в нём персональные данные, подменяет их и по тому же идентификатору возвращает исходный текст. Ниже — как это воспроизвести и где смотреть журнал и метрики.
Сервис слушает http://localhost:8080.
Запуск
Нужны Java 21 и Maven 3.8+ (или обёртка ./mvnc). Для дашборда — Docker Compose.
Локально:
./mvnc package -DskipTests
java -jar target/pd-guard-spring-1.0.0.jar
Контейнер, Prometheus и Grafana (compose.yaml ссылается на уже собранный образ pd-guard-spring:jvm, сам его не строит):
docker build -f src/main/docker/Dockerfile -t pd-guard-spring:jvm .
docker compose up -d
Если локальной Java нет — тот же образ собирается полностью внутри Docker, mvn package идёт в отдельной стадии сборки (дольше первого запуска, зато не требует ничего на хосте кроме Docker):
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
Тело:
{"payload": "текст", "payload_id": "устойчивый-идентификатор"}
Заголовки:
| Заголовок | Смысл |
|---|---|
X-System-Id |
имя системы из config/systems.json. Нет заголовка или имя неизвестно — применяется политика default |
X-System-Key |
нужен только если у системы в конфиге задано поле key |
Направление выбирается по payload_id, а не по отдельному флагу:
- Идентификатор ещё не встречался — текст маскируется, пара «исходник ↔ маска» запоминается.
- Пришёл ранее выданный текст маски и у системы включено
demask— возвращается исходный текст. - Пришёл тот же исходный текст — возвращается та же маска, что и в первый раз.
Ответ: {"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 |
правдоподобная подмена (вымышленное ФИО, номер, адрес) |
Вставьте текст, например:
Клиент Иванов Иван Иванович, паспорт 4509 123456, тел +7 916 123-45-67
Нажмите «Обработать». Результат появится в блоке под кнопкой.
Страница каждый раз создаёт новый payload_id. Она показывает только маскирование. Демаскирование проверяется запросом к API с тем же идентификатором.
Маскирование через API
Система default маскирует все известные типы и умеет демаскировать. Режим — звёздочки с открытыми краями номера (MASK).
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демаскирование выключено намеренно.
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 показывает, что ушло бы во внешнюю модель и что вернулось бы потребителю. Адрес модели по умолчанию пуст, поэтому отвечает заглушка: она возвращает присланный (уже замаскированный) текст. В модель подставляются токены, даже если у системы выбран другой режим: по звёздочкам однозначное восстановление невозможно.
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:
docker compose logs -f pd-guard
На каждое маскирование есть строка уровня INFO. В ней идентификатор, длина текста и счётчики по типам. Сами значения персональных данных в журнал не пишутся:
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:
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 и вызовите:
curl -s -X POST http://localhost:8080/admin/reload
Файл также перечитывается сам, если изменилось время модификации (проверка не чаще раза в секунду). Текущие политики: GET /admin/config. Список типов, которые умеет распознавать сборка: GET /admin/types.
Проверка набора типов: в политике crm нет PASSPORT. Текст с паспортом и телефоном под X-System-Id: crm скроет телефон и оставит номер паспорта. Под default скроет оба.