Реалізація аудиту дій користувача у корпоративному мобільному додатку
Security audit trail — це не аналітика і не UX-дослідження. Це юридично значимі логи, які при інциденті покажуть: хто, коли, з якого пристрою відкрив документ, змінив запис, експортував файл. Без цього розслідувати витік неможливо.
Що обов'язково логувати
Питання не в тому, як логувати — а в тому, які eventi мають вагу при розборі інциденту. Типовий корпоративний мінімум:
- Вхід та вихід (включаючи автовихід за таймаутом)
- Доступ до документів або записів із класифікацією вище «Internal»
- Зміна, створення, видалення даних
- Експорт, друк, відправка — будь-яке виведення даних за периметр додатку
- Невдалі спроби аутентифікації (з лічильником)
- Зміна параметрів безпеки (PIN, біометрія)
- Команди Remote Wipe та їх виконання
Логувати «користувач натиснув кнопку назад» — це не аудит, це шум.
Архітектура audit trail
Головна вимога до audit trail: логи не мають втрачатися і не мають бути доступні для видалення користувачем. Це дві різні технічні вимоги.
Для надійності доставки — локальна черга з гарантованою відправкою. На Android — WorkManager з BackoffPolicy.EXPONENTIAL, на iOS — BGProcessingTask. Логи записуються спочатку в локальну таблицю SQLite, потім фонова задача відправляє їх на сервер і видаляє тільки після підтвердження.
// Модель eventi аудиту
data class AuditEvent(
val id: String = UUID.randomUUID().toString(),
val timestamp: Long = System.currentTimeMillis(),
val userId: String,
val deviceId: String,
val action: AuditAction,
val resourceId: String?,
val resourceType: String?,
val metadata: Map<String, String> = emptyMap(),
val synced: Boolean = false
)
enum class AuditAction {
LOGIN, LOGOUT, DOCUMENT_VIEW, DOCUMENT_EXPORT,
RECORD_CREATE, RECORD_UPDATE, RECORD_DELETE,
AUTH_FAILURE, SETTINGS_CHANGE, WIPE_RECEIVED
}
// DAO для локальної черги
@Dao
interface AuditEventDao {
@Insert
suspend fun insert(event: AuditEvent)
@Query("SELECT * FROM audit_events WHERE synced = 0 ORDER BY timestamp ASC LIMIT 50")
suspend fun getUnsynced(): List<AuditEvent>
@Query("UPDATE audit_events SET synced = 1 WHERE id IN (:ids)")
suspend fun markSynced(ids: List<String>)
}
Задача синхронізації запускається при появленні сети та при запуску додатку. Батчева відправка по 50 събитий — баланс між навантаженням на сервер і швидкістю доставки.
Цілісність логів
Якщо додаток працює на пристрої з root/jailbreak, користувач може видалити локальну SQLite. Для високого рівня вимог кожна подія підписується HMAC з ключем з Android Keystore / iOS Secure Enclave:
fun signEvent(event: AuditEvent): String {
val keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) }
val privateKey = keyStore.getKey("audit_signing_key", null)
val signature = Signature.getInstance("SHA256withECDSA")
signature.initSign(privateKey as PrivateKey)
signature.update(event.toCanonicalBytes())
return Base64.encodeToString(signature.sign(), Base64.NO_WRAP)
}
Сервер верифікує підпис публічним ключем. Підробити лог без доступу до Secure Enclave неможливо.
Обогащення контекстом
Голий userId + action + timestamp — мінімум. Корисні доповнення:
-
deviceId— привʾязка до конкретного пристрою, а не до аккаунту -
appVersion— розуміти, на якій версії сталася інцидент -
networkType(WiFi/LTE/VPN) — видно, був ли активний корпоративний VPN -
jailbreak/root detected— прапор з SafetyNet / DeviceCheck
На Android deviceId — Settings.Secure.ANDROID_ID (унікальний для комбінації пристрій+користувач+додаток з Android 8). На iOS — UIDevice.current.identifierForVendor.
Зберігання на сервері
Audit logs — не те, що видаляють через 30 днів. Юридичні вимоги (залежно від галузі): від 1 року (стандарт) до 7 років (фінансові організації за ФЗ-115). Зберігати в append-only сховищі — PostgreSQL із таблицею тільки INSERT і запретом на UPDATE/DELETE через Row Level Security, або окремий SIEM (Splunk, ELK з ILM).
Що перевірити при аудиті додатку
Часто виявляємо, що у додатку вже є «яке-небудь логування» — але воно пише в Logcat або файл у cacheDir, який очищається при нехватці місця. Це не audit trail, це сміття.
Терміни: 2–3 дні на базову реалізацію з локальною чергою та серверним endpoint. З HMAC-підписами та інтеграцією в SIEM — 4–6 днів.







