Журнал аудиту
Коротко: Безперервний запис значущих для безпеки й адміністрування дій (хто що і коли зробив і з ким) — слугує подальшій простежуваності, наприклад під час призначення ролей чи інших втручань адміністратора.
Докладніше: Журнал аудиту потрібно писати навмисно відмовостійко — помилка журналювання ніколи не повинна блокувати чи обвалювати саму дію. Типова структура: хто діяв (актор), що зроблено (дія), над яким об’єктом (ціль), плюс необов’язкові структуровані деталі.
Наш контекст: 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