Перейти к основному содержанию

Аудит-логирование

Около 3 мин

Аудит-логирование

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_PATHRegExp через запятую — какие авторизованные пути исключить.*/heartbeat$ (служебный запрос сервиса)
AUDIT_INCLUDE_UNAUTH_PATHRegExp через запятую — какие неавторизованные пути логировать.*/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_URLJDBC-URL аудит-БД (TimescaleDB)jdbc:postgresql://timescale:5432/storm_audit
AUDIT_DB_USERNAMEПользователь аудит-БДaudit
AUDIT_DB_PASSWORDПарольpassword
AUDIT_DB_BODY_MAX_CHARSМаксимум символов в теле; сверх — маркер _originalChars16384
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

КолонкаТипОписание
timestamptimestamptzВремя запроса
emailtextПользователь (subject)
session_idtextID сессии
subject_iptextIP пользователя
methodtextHTTP-метод
pathtextПуть запроса
querytextQuery-строка
statusintegerHTTP-статус ответа
requestjsonbТело запроса
responsejsonbТело ответа

Тела хранятся как 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. Аудит-БД отдельная — её резервное копирование и обслуживание ведите независимо от бизнес-БД.