EMZETT.
Войти

Журнал аудита

Коротко: Непрерывная запись значимых для безопасности и администрирования действий (кто что и когда сделал и с кем) — служит последующей прослеживаемости, например при назначении ролей или других вмешательствах администратора.

Подробнее: Журнал аудита нужно писать намеренно отказоустойчиво — ошибка журналирования никогда не должна блокировать или обрушивать само действие. Типичная структура: кто действовал (актор), что сделано (действие), над каким объектом (цель), плюс необязательные структурированные детали.

Наш контекст: app/utils/auditLog.ts в Emzett, виден в панели администратора в разделе «Журнал аудита» (о контроле доступа к нему см. Динамическая ролевая система (гранулярный RBAC)). Запись защищена try/catch, чтобы ошибка БД при журналировании не помешала, скажем, назначению роли.

Подробности

Журнал аудита отличается от обычного журналирования приложения (вывод ошибок/отладки, который в какой-то момент ротируется/удаляется) назначением и требованиями: он должен быть полным, постоянным и защищённым от подделки, потому что при сомнении служит доказательством — кто когда менял какое право, кто получал доступ к каким данным. Поэтому журнал аудита обычно оказывается в отдельной, как правило, только добавляющей таблице (записи никогда не меняются и не удаляются, только добавляются), часто со стро́же ограниченными правами доступа, чем остальная база данных — в идеале даже администратор не может позже удалить отдельные записи, чтобы это в свою очередь не было записано.

Структура записи

Типичная запись журнала аудита следует схеме актор–действие–цель:

  • Актор — кто действовал (ID пользователя, но системные процессы, например cron-задача, тоже считаются акторами)
  • Действие — что сделано, обычно как описательная строка (role.grant, user.delete, settings.update)
  • Цель — над каким объектом (ID затронутой записи)
  • Детали — необязательная структурированная дополнительная информация (например, старое и новое значение при изменении)
  • Метка времени и часто также IP-адрес актора или ID сессии, чтобы инциденты можно было позже отнести к конкретной сессии

Для критичных для безопасности изменений (например, смены прав) стоит хранить и старое, и новое значение ({ from: "user", to: "admin" }), а не только новое — иначе потом нельзя восстановить, каким было прежнее состояние.

Отказоустойчивость как основной принцип

Принцип «писать отказоустойчиво» решающий: запись в журнал аудита — побочный эффект самого действия, а не оно само. Если бы ошибка журналирования блокировала основное действие, сломанная система журналирования могла бы случайно парализовать всё приложение — классический пример «контроль не должен стать важнее контролируемого». В то же время неудавшаяся запись в журнал аудита не должна просто молча пропасть — принято хотя бы записать ошибку в обычное журналирование ошибок (например, Sentry), чтобы пробелы в журнале аудита замечались, даже если они не блокируют основное действие.

async function grantRole(userId: string, role: string) {
  await db.update(users).set({ role }).where(eq(users.id, userId))
  try {
    await logAudit({ actor: currentUser.id, action: "role.grant", target: userId, details: { role } })
  } catch (err) {
    console.error("Audit log failed:", err) // назначение роли всё равно прошло
  }
}

Отличие от родственных понятий

Журнал аудита часто путают с журналом событий или лентой активности, но он служит другой цели: лента активности (например, «X прокомментировал Y») предназначена для пользователей и вполне может быть неполной или сокращённой; журнал аудита предназначен для прослеживаемости/соответствия и должен быть полным. Журнал изменений базы данных (например, через расширение Postgres pg_audit или таблицы истории на триггерах) автоматически фиксирует КАЖДОЕ изменение на уровне базы данных независимо от логики приложения — это полнее, но и значительно шумнее, потому что не даёт семантической классификации вроде «это было назначение роли», только сырые изменения строк.

С юридической точки зрения журнал аудита важен и в контексте GDPR: он документирует, кто имел доступ к персональным данным, что может быть решающим при запросе доступа или утечке данных (ст. 33 GDPR, обязанность сообщить в течение 72 часов), чтобы вообще суметь очертить масштаб инцидента.

См. также: Динамическая ролевая система (гранулярный RBAC), Протокол, GDPR