Аудит-логирование
Аудит-логирование
StormBPMN ведёт полный журнал действий пользователей: на каждую пару «запрос + ответ» пишется одна аудит-запись. Доставка журнала настраивается каналом (AUDIT_CHANNEL):
console— JSON-строки в лог приложения (stdout контейнера). Значение по умолчанию.syslog— отправка во внешнюю SIEM по протоколу Syslog.database— запись в выделенную базу TimescaleDB, отдельную от бизнес-БД.
Включение
| Переменная | Описание | Пример значения |
|---|---|---|
| AUDIT_ENABLED | Включение аудита | true |
| AUDIT_CHANNEL | Канал доставки журнала | console / syslog / database |
Аудит выключен по умолчанию
Без AUDIT_ENABLED=true журнал не ведётся вообще, даже если задан AUDIT_CHANNEL. Если канал не указан, используется console.
Что попадает в журнал
Пишется одна запись на пару «запрос + ответ». Предзапросы OPTIONS (CORS preflight) не логируются. Остальное фильтруется так:
- Авторизованные запросы (с заголовком
Authorization) — пишутся все, кроме путей изAUDIT_EXCLUDE_AUTH_PATH. - Неавторизованные запросы — пишутся только по путям из
AUDIT_INCLUDE_UNAUTH_PATH(по умолчанию — только вход).
| Переменная | Описание | По умолчанию |
|---|---|---|
| AUDIT_EXCLUDE_AUTH_PATH | RegExp через запятую — какие авторизованные пути исключить | .*/heartbeat$ (служебный запрос сервиса) |
| AUDIT_INCLUDE_UNAUTH_PATH | RegExp через запятую — какие неавторизованные пути логировать | .*/auth/signin$ (вход в систему) |
Тонкости при задании значений
- Экранируйте
$там, где значение проходит через Docker Compose. Вdocker-compose.yml(секцияenvironment:) и в Portainer Stacks$означает подстановку переменной, поэтому regex-якорь конца строки нужно удваивать —$$:AUDIT_EXCLUDE_AUTH_PATH=.*/heartbeat$$,.*/diagram/.*/autosave$$. Если же переменная задаётся «сырым» значением контейнера (поле Env у контейнера в Portainer,docker run -e) —$берётся буквально, удвоение не нужно. Не уверены — проверьте фактическое значение в контейнере:docker exec stormbpmn printenv AUDIT_EXCLUDE_AUTH_PATH. - Без пробелов вокруг запятых. Список разбивается строго по запятой; пробел попадёт внутрь шаблона и сломает совпадение.
- Шаблон сопоставляется со всем путём целиком, а не по вхождению — начинайте с
.*, чтобы поймать суффикс пути.
Маскирование
Перед записью значения чувствительных полей в теле и query-параметрах заменяются на XXX: password, client_secret, access_token, и т.п.
Оригинальный IP пользователя
IP берётся из заголовка X-Real-IP, а при его отсутствии — из X-Forwarded-For. Чтобы в журнале был реальный адрес клиента, а не адрес контейнера, вышестоящий прокси/балансировщик должен прокидывать один из этих заголовков.
Канал console (по умолчанию)
Самый простой режим: аудит-запись сериализуется в JSON и пишется в лог приложения (stdout контейнера). Удобно, когда логи контейнера и так собираются вашим стеком (Loki, ELK, и т.п.) — отдельная инфраструктура не нужна.
Структура записи: timestamp, email, method, path, query, request, response, status.
Канал syslog (SIEM)
Отправка журнала во внешнюю SIEM по протоколу Syslog.
| Переменная | Описание | Пример значения | По умолчанию |
|---|---|---|---|
| SYSLOG_SERVERS | Список syslog-серверов | localhost:514,192.168.78.53:601 | ❌ |
| SYSLOG_SOURCE | Название приложения в логах | stormbpmn | ✅ |
| SYSLOG_MESSAGE_FORMAT | Формат сообщения (RFC_3164 / RFC_5424 / RFC_5425) | RFC_3164 | ✅ |
Структура записи
{
"timestamp": "2007-12-03T10:15:30:55.000000",
"sessionId": "n8o7vty4o78cymruxjor84unrjo",
"source": "stormbpmn",
"subject": "user@example.com",
"subjectIP": "192.168.0.1",
"object": "41bc85d9-1bb5-4d9b-97bd-0193d9807b8b",
"resource": "diagram",
"action": "CHANGE",
"payload": {
"method": "POST",
"url": "/api/v1/diagram",
"request": { "..." },
"response": { "..." }
},
"result": "SUCCESSFUL"
}
Типы событий
| Действие | Описание |
|---|---|
| GET | Просмотр объекта |
| CREATE | Создание объекта |
| CHANGE | Изменение объекта |
| DELETE | Удаление объекта |
| Результат | Описание |
|---|---|
| SUCCESSFUL | Успешная операция |
| CLIENT_ERROR | Ошибка клиента |
| SERVER_ERROR | Ошибка сервера |
Канал database (TimescaleDB)
Запись журнала в отдельную базу данных TimescaleDB (по сути обычный PostgreSQL с расширением), изолированную от бизнес-БД: свой пул соединений, своя схема, свой жизненный цикл. Это рекомендуемый режим для самостоятельного хранения аудита: журнал лежит в структурированном виде, по нему можно делать SQL-запросы для форензики, а TimescaleDB сама сжимает и удаляет устаревшие данные.
Зачем TimescaleDB
Таблица аудита — это hypertable с партиционированием по времени, что даёт три встроенных механизма «из коробки»:
- Партиционирование по
timestamp(чанки по суткам) — быстрые выборки за период. - Компрессия чанков старше 2 дней (колоночное сжатие, сегментация по
email) — кратная экономия места и точечная форензика «что делал пользователь X». - Ретеншен: чанки старше 14 дней удаляются автоматически (мгновенный DROP, без тяжёлого DELETE).
Настройка
| Переменная | Описание | Пример значения | По умолчанию |
|---|---|---|---|
| AUDIT_DB_URL | JDBC-URL аудит-БД (TimescaleDB) | jdbc:postgresql://timescale:5432/storm_audit | ❌ |
| AUDIT_DB_USERNAME | Пользователь аудит-БД | audit | ❌ |
| AUDIT_DB_PASSWORD | Пароль | password | ❌ |
| AUDIT_DB_BODY_MAX_CHARS | Максимум символов в теле; сверх — маркер _originalChars | 16384 | ✅ |
| AUDIT_DB_QUEUE_CAPACITY | Ёмкость буфера записи | 10000 | ✅ |
| AUDIT_DB_BATCH_SIZE | Размер батча вставки | 500 | ✅ |
| AUDIT_DB_FLUSH_INTERVAL_MS | Максимальная задержка записи батча, мс | 1000 | ✅ |
Пример развёртывания базы данных
Нужна Community-сборка TimescaleDB
Используйте образ timescale/timescaledb Community-лицензии. Тег с суффиксом -oss не подходит — это Apache-сборка без компрессии и сегментации, на которой инициализация схемы не пройдёт и приложение не стартует.
Отдельный экземпляр TimescaleDB поднимается одним compose-файлом — образ сам создаёт пользователя, базу и расширение timescaledb, вручную в БД ничего делать не нужно:
name: storm-audit
services:
audit:
image: timescale/timescaledb:2.28.0-pg17 # Community-сборка, НЕ -oss
restart: unless-stopped
ports: ["5432:5432"]
environment:
POSTGRES_DB: storm_audit
POSTGRES_USER: <логин>
POSTGRES_PASSWORD: <пароль>
volumes: ["audit-data:/var/lib/postgresql/data"]
volumes:
audit-data:
docker compose up -d
Дальше пропишите приложению параметры подключения по таблице выше и перезапустите его — схему (таблицу, hypertable, политики компрессии и ретеншена) накатит Liquibase на старте. Приложение должно иметь сетевой доступ к этой базе по адресу из AUDIT_DB_URL (общая docker-сеть или доступный хост).
Структура таблицы audit_log
| Колонка | Тип | Описание |
|---|---|---|
| timestamp | timestamptz | Время запроса |
text | Пользователь (subject) | |
| session_id | text | ID сессии |
| subject_ip | text | IP пользователя |
| method | text | HTTP-метод |
| path | text | Путь запроса |
| query | text | Query-строка |
| status | integer | HTTP-статус ответа |
| request | jsonb | Тело запроса |
| response | jsonb | Тело ответа |
Тела хранятся как jsonb — по ним можно делать структурные запросы. Тело длиннее AUDIT_DB_BODY_MAX_CHARS не сохраняется целиком, а помечается маркером {"_originalChars": N} (где N — исходная длина), чтобы не раздувать журнал. Не-JSON тела заворачиваются в JSON-строку.
Пример форензик-запроса
«Что делал пользователь сегодня»:
SELECT timestamp, method, path, status
FROM audit_log
WHERE email = 'user@company.com'
AND timestamp >= current_date
ORDER BY timestamp DESC;
Надёжность
Запись в БД — асинхронная: поток запроса лишь кладёт запись в буфер и сразу отвечает пользователю, а отдельный воркер пишет батчами. Аудит никогда не блокирует и не роняет пользовательский запрос: если БД недоступна или буфер переполнен, записи отбрасываются и агрегированно логируются (ERROR), но приложение продолжает работать.
Настройка политик хранения
Чанки по 1 дню, компрессия старше 2 дней и ретеншен 14 дней — встроенные политики, заданные при инициализации схемы. Если нужен другой срок хранения, вы или ваш DBA можете изменить политику стандартными функциями TimescaleDB (add_retention_policy / remove_retention_policy и т.п.) на таблице audit_log. Аудит-БД отдельная — её резервное копирование и обслуживание ведите независимо от бизнес-БД.