Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Audit log: журнал действий пользователей

Audit log: что писать в журнал действий и как его защищать

Журнал аудита: состав события, неизменяемость хранения, отличие от логов приложения, требования к расследованию и практическая схема в PostgreSQL.

Прямой ответ

Audit log — журнал значимых действий с указанием, кто, что, когда и над чем сделал, а также чем это закончилось. Он отличается от логов приложения назначением: логи приложения помогают разработчику понять поведение системы, журнал аудита — восстановить последовательность событий при расследовании и подтвердить соответствие требованиям.

Состав события аудита:

  • Кто: идентификатор субъекта (пользователь, сервис, API-ключ) и, при наличии, от чьего имени он действовал.
  • Что: действие и объект — refund:payment/12345, role.grant:admin.
  • Когда: временная метка в UTC с точностью до миллисекунд.
  • Откуда: IP-адрес, user agent, идентификатор сессии.
  • Результат: успешно или отказано, причина отказа.
  • Контекст: идентификатор запроса для связывания с логами приложения.

Что обязательно попадает в журнал:

  1. Аутентификация: успешные входы, неудачные попытки, выход, смена пароля, включение и отключение 2FA.
  2. Управление доступом: выдача и отзыв ролей, создание и отзыв API-ключей — см. RBAC и API-ключи.
  3. Доступ к чувствительным данным: чтение персональных данных, выгрузки, экспорт отчётов.
  4. Изменение значимых объектов: суммы, статусы, реквизиты, настройки безопасности.
  5. Административные операции: изменения конфигурации, массовые операции.

Принципы:

  • Журнал пишется до или вместе с операцией, а не «когда получится». Отказ записи аудита для критичных операций должен блокировать саму операцию.
  • Неизменяемость. Записи не изменяются и не удаляются приложением. Права на UPDATE/DELETE у приложения нет — только INSERT.
  • Журнал не содержит секретов. Пароли, токены, полные номера карт не пишутся ни в каком виде. Персональные данные минимизируются — см. маскирование PII.
  • Единый формат. Структурированные события удобнее разбирать; основы — в статье про структурированные логи.

Практика: схема и хранение

Таблица аудита в PostgreSQL:

CREATE TABLE audit_events (
  id           BIGSERIAL PRIMARY KEY,
  occurred_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  actor_id     BIGINT,
  actor_type   TEXT NOT NULL,             -- user | service | api_key
  on_behalf_of BIGINT,
  action       TEXT NOT NULL,             -- refund.approve
  resource     TEXT NOT NULL,             -- payment/12345
  result       TEXT NOT NULL,             -- allowed | denied
  reason       TEXT,
  ip           INET,
  user_agent   TEXT,
  request_id   TEXT,
  details      JSONB
);

-- приложение не должно иметь прав на изменение и удаление
REVOKE UPDATE, DELETE ON audit_events FROM app_role;
GRANT  INSERT, SELECT ON audit_events TO app_role;

CREATE INDEX audit_actor_time_idx ON audit_events (actor_id, occurred_at DESC);
CREATE INDEX audit_resource_idx   ON audit_events (resource, occurred_at DESC);

Запись из кода:

def audit(*, actor, action: str, resource: str, result: str,
          reason: str | None = None, request=None, details: dict | None = None):
    db.insert("audit_events",
              actor_id=actor.id, actor_type=actor.type, on_behalf_of=actor.on_behalf_of,
              action=action, resource=resource, result=result, reason=reason,
              ip=request.client_ip if request else None,
              user_agent=request.user_agent if request else None,
              request_id=request.id if request else None,
              details=redact(details or {}))

Рекомендации:

  1. Пишите отказ так же тщательно, как успех. Попытки несанкционированного доступа — самый ценный материал для расследования.
  2. Свяжите аудит с логами приложения через request_id. Это позволяет перейти от «кто изменил сумму» к полному стеку запроса.
  3. Разделяйте горячее и холодное хранение. Последние 90 дней — в основной БД для быстрых запросов, дальше — в объектном хранилище с проверкой целостности.
  4. Проверяйте целостность архива. Хеш-цепочка или подпись партии записей делает подмену заметной.
  5. Ограничьте доступ к журналу. Чтение аудита — привилегированная операция, которая сама логируется.
  6. Регулярно тестируйте чтение. Раз в квартал проходите типовой сценарий расследования: найти все действия пользователя за период и все изменения конкретного объекта.
  7. Опишите, как отвечать на типовые вопросы. Журнал проектируется под вопросы, которые придётся задавать: кто менял эту запись, кто имел доступ за период, кто выдал права. Если на вопрос нельзя ответить одним запросом, схема событий неполна.
  8. Учитывайте объём на старте. Рост числа событий легко превращает аудит в проблему производительности. Партиционирование по времени и отдельная схема под журнал дешевле, чем перенос данных под нагрузкой.

Практический сценарий: расследование подозрительного возврата

Контекст: финансовый отдел заметил серию возвратов на небольшие суммы, одобренных разными операторами.

  1. Первый запрос. Все события refund.approve за месяц с разбивкой по операторам. Журнал дал список из 47 возвратов, из них 19 — одним оператором за три дня.
  2. Углубление. По request_id перешли к логам приложения и увидели, что возвраты инициированы с одного IP и в нехарактерное время. По журналу доступа выяснилось, что роль оператора была выдана администратором за день до начала серии.
  3. Проверка прав. Событие role.grant в журнале содержало granted_by, но не содержало основания выдачи — процесс согласования не фиксировался. Это стало отдельной находкой.
  4. Действия. Роль отозвана, сессии завершены, возвраты оспорены; в форму выдачи роли добавлено обязательное поле основания.
  5. Профилактика. Добавлен алерт на аномальное число операций одного типа на пользователя и требование указывать основание при выдаче привилегированных ролей.

Вывод: ценность журнала определяется не объёмом записей, а тем, можно ли по нему восстановить цепочку «кто выдал права → кто ими воспользовался → что изменилось». События без субъекта и результата для этого непригодны.

FAQ

Чем журнал аудита отличается от обычных логов? Назначением и гарантиями. Логи приложения могут ротироваться и теряться, они нужны для отладки. Аудит-журнал неизменяем, имеет ограниченный круг читателей, хранится долго и используется как доказательство. Смешивать их в одном потоке — плохая практика.

Сколько хранить журнал? Определяется требованиями регулятора и договорами: часто от года до нескольких лет. Практика — горячий слой 90 дней для оперативных запросов и холодный архив на весь требуемый срок с периодической проверкой читаемости.

Нужно ли логировать чтение данных, если записей очень много? Для чувствительных категорий — да, иначе невозможно ответить, кто имел доступ к персональным данным. Объём контролируют агрегацией: фиксируют факт выборки с числом затронутых строк, а не каждую строку.

Как избежать записи персональных данных в журнал? Минимизация и маскирование: вместо полного значения — идентификатор и тип, вместо e-mail — хеш или маска. Правило простое: в журнале должно быть достаточно информации для расследования и достаточно мало для того, чтобы утечка журнала не стала утечкой данных.

Источники: OWASP Logging Cheat Sheet, PostgreSQL docs (privileges), Wikipedia (audit trail) — ссылки в шапке.