|
Professor Seleznov
|
На первый взгляд задача кажется простой: нужно зафиксировать событие и предоставить интерфейс для его просмотра. Но на практике за этим стоит множество интересных инженерных решений: как отбирать события, хранить их, масштабировать систему, не потерять данные в случае аварии. И как сделать сервис полезным и для ИБ-специалистов, и для простых пользователей.
 Привет! Я Владимир Атасунц, руководитель направления Security Services в MWS Cloud Platform. В этой статье расскажу о сервисе аудитных логов — базовом инструменте облака для контроля действий с ресурсами и анализа изменений в инфраструктуре. Разберу:
- зачем конкретно нужен такой сервис и какие сценарии использования предусматривает;
- как он выглядит с точки зрения продуктовой модели;
- какие требования легли в основу системного дизайна;
- как жизненный цикл события нашел отражение в архитектуре и из каких компонентов она в итоге состоит.
Пойдём последовательно — от бизнес-сценариев к технической реализации. Что такое сервис аудитных логов Начнём с простого примера. Представьте, что вы — администратор облачного пространства. Утром вы создали виртуальную машину. Вечером возвращаетесь — а с ней что-то произошло: кто-то добавил ядра, кто-то её выключил. В команде есть коллеги, доступов много, действий — ещё больше. Возникают базовые вопросы: что именно произошло и кто это сделал? Ответ на это и дают аудитные логи. Аудитные логи — это запись о том:
- какое действие было выполнено;
- кто выполнил действие;
- когда это произошло;
- и чем всё закончилось (успехом или ошибкой).
Эта информация позволяет восстановить цепочку событий и понять, что происходило в инфраструктуре. Теперь поговорим про основных потребителей сервиса — по нашему мнению, это специалисты по информационной безопасности: ИБ-инженеры, аналитики, архитекторы, SOC-команды. Для них это рабочий инструмент для выполнения регуляторных требований, мониторинга событий безопасности, разбора инцидентов постфактум. При этом аудитные логи важны не только для ИБ. Ими пользуются и инженеры, которым нужно быстро понять причину изменений в инфраструктуре — например, кто и каким действием остановил кластер или изменил конфигурацию ресурса. Для нас как для облака, помимо внешних пользователей есть еще и внутренние потребители — команда, которая это облако развивает. Нам важно понимать, что происходит внутри инфраструктуры: для аналитики, выполнения внутренних требований, расследования инцидентов и оперативной диагностики. По сути, мы используем тот же инструмент для тех же задач, что и наши пользователи: чтобы видеть изменения, отслеживать подозрительные действия и быстрее разбираться в проблемах. Продуктовое видение: базовая модель сервиса аудитных логов Сегодня сервис аудитных логов — это must-have для любого облака. Такие решения есть у западных, азиатских и российских провайдеров. Мы не стали изобретать велосипед: посмотрели на существующие подходы, выбрали наиболее зрелую модель и адаптировали её под российские реалии, нашу архитектуру и продуктовые сценарии. В самом простом виде задача сервиса выглядит так: в облаке есть множество источников событий и их нужно куда-то собирать и где-то хранить. Внутри мы называем это common storage — общее хранилище, куда стекаются события со всего облака. Нюансы начинаются дальше — когда нужно дать пользователям управляемый доступ к этим событиям. Для этого мы ввели две отдельные сущности — коллектор и хранилище. Коллектор (Collector).Нужен, чтобы пользователь мог управлять тем, какие события собирать. Позволяет выбрать из всего объёма доступных событий только те, которые действительно интересны пользователю. В Google Cloud аналогичный ресурс называется Sink. Пример сценария — события создания виртуальных машин пользователю не важны, а вот удаление ВМ или Kubernetes-кластера — критично. Хранилище (Storage).Ресурс, в который попадают события после фильтрации. Именно он отвечает за то, где данные будут храниться или куда будут выгружаться дальше. Мы сознательно выделили хранилище в отдельную сущность и не стали объединять его с коллектором. Коллектор отвечает за отбор событий, а хранилище за их дальнейшее хранение или отгрузку. Такое разделение даёт гибкость: можно независимо развивать логику маршрутизации событий и способы их хранения. Через конфигурацию коллектора пользователь может сначала выбрать хранение данных в хранилище аудитных логов, а при желании через некоторое время переключить хранение на внешнюю систему. В Google Cloud аналог называется log bucket, но нам не понравилось пересечение с сущностью S3 bucket сервиса Object Storage — S3-совместимое хранилище облака MWS Cloud Platform, поэтому мы упростили нейминг. Требования к системному дизайну сервиса аудитных логов Приступив к проработке системного дизайна, мы опирались на технические требования, бизнес-сценарии, продуктовую модель. В архитектуре облака мы разделяем Control Plane и Data Plane.
- Control Plane отвечает за хранение конфигурации объектов, реализацию API и ресурсной модели.
- Data Plane отвечает за выполнение действий. Например, Control Plane сохраняет конфигурацию виртуальной машины и передаёт её в Data Plane, который уже создаёт ВМ.
Аудитные события могут генерироваться на обоих уровнях. Ниже рассмотрим требования к дизайну сервиса — от технических до потребности в централизованном хранилище. Технические требования Чтобы понимать, что происходит в инфраструктуре, события нужно надёжно генерировать, доставлять и хранить. У нас были следующие требования: Высокая производительность на запись. Поток событий большой и постоянно растёт. На старте мы заложили целевой показатель в 100k RPS. Для молодого облака это серьёзная цифра, но архитектуру хотелось строить с запасом. Адекватная производительность на чтение. Пользователь не должен ждать по пять минут, пока загрузятся события. Особенно в сценарии расследования инцидентов , где приходится постоянно фильтровать и уточнять выборку. Мы не фиксировали жёсткий SLA, но ориентировались на единицы секунд в типовом запросе — в идеале около пяти. В сложных случаях с большой глубиной хранения запросы могут занимать десятки секунд, но это нетипичный сценарий. Масштаб и низкая стоимость хранения. Источников событий много: сервисов много, пользовательских ресурсов ещё больше, пользователей — ещё больше. Все что-то делают. При этом хранить эти данные нужно долго, например для целей compliance. Значит, решение должно обеспечивать низкую стоимость хранения. Высокая надёжность. С учетом вышеописанных требований сервис должен обеспечить непрерывный поток записи событий, быть готовым к инфраструктурным проблемам и неожиданному росту нагрузки, легко масштабироваться. Ресурсная модель Следующий шаг — спроектировать ресурсную модель сервиса и понять, как объекты будут взаимодействовать между собой. Событие, главный объект. Их может быть очень много. В событии хранится вся необходимая информация: кто, что, когда и над каким ресурсом сделал. Хранилище. Я уже о нём писал, но здесь важно зафиксировать характеристики. Storage может иметь ограничение по объёму — а может не иметь. Это зависит от потребностей клиента. Также можно ограничить срок хранения. Например: максимум 10 ГБ в течение 10 дней. Коллектор — определяет, какие события нужно собрать и в какое хранилище их отправить. С ресурсной моделью самого сервиса разобрались — теперь надо понять, как она ложится на модель облака. Первый контейнер ресурсов, с которым сталкивается пользователь, — это организация. Важно понимать, что события могут происходить и на этом уровне. Например, добавление пользователя — это тоже событие, которое мы хотим фиксировать. Ниже находится проект — основной рабочий контейнер для пользователей. Именно внутри проекта разворачиваются виртуальные машины, Kubernetes-кластеры, а также создаются наши хранилища и коллекторы. Есть и сценарий выгрузки во внешнее хранилище. Например, в Object Storage нашего облака. Это достаточно простой вариант, и мы его поддерживаем.

Ресурсная модель. Базовый сценарий Потребность в централизованном хранилище Теперь рассмотрим более сложный вариант. Представим крупного клиента с десятком проектов. Создавать в каждом проекте отдельное хранилище и разбираться в них неудобно. Гораздо логичнее иметь единое централизованное хранилище, куда будут стекаться события со всех проектов и, при необходимости, с уровня организации. Например, можно создать отдельный проект безопасности — условный Project Sec — и разместить в нём общее хранилище. Туда будут отправляться события из других проектов. Доступ к этому проекту можно выдать только команде безопасности, которая занимается мониторингом и аналитикой. С точки зрения ресурсной модели это всё то же хранилище , просто в него попадает больше событий. При этом возникает вопрос кросс-проектного доступа. Не любой коллектор может читать события в соседнем проекте или писать в чужое хранилище. Поэтому у коллектора появляется дополнительная характеристика — сервисный аккаунт, от имени которого выполняются операции чтения и записи. Это позволяет корректно разграничить доступ. Логика аналогична Object Storage: писать может только тот, у кого есть права.

Ресурсная модель. Кросс-проектный сценарий Отображение жизненного цикла события в архитектуре сервиса аудитных логов Прорабатывая архитектуру, мы разделили жизненный цикл события на четыре этапа:
- Генерация.
- Сбор и доставка.
- Хранение.
- Доступ.
Генерация событий С генерации всё начинается. Кто-то должен сообщить сервису, что произошло событие. Причём делать это нужно в едином формате и с полным набором данных — это не два-три поля, а довольно большой объём информации. Поскольку инфраструктура не монолитная и состоит из множества сервисов, каждый из них должен уметь генерировать и передавать события. На этапе системного дизайна мы решили реализовать собственную библиотеку — по сути SDK, которую подключают сервисы облака. Можно было, например, пойти по классическому пути с REST API и отправкой событий напрямую. Но на решение повлияли особенности нашей инфраструктуры. Мы работаем в Kubernetes, сервисы развёрнуты в нескольких кластерах. Возможны аварии, сетевые проблемы, перезапуски нод. Нам нужно минимизировать влияние таких ситуаций на качество сервиса. Синхронная отправка событий — плохой вариант. Если в момент создания пользователем виртуальной машины возникнет проблема с сетью на уровне инфраструктуры облака и сервис начнёт с периодичностью повторять запись аудита, пользователю придется ждать. Фоновая отправка без буферизации — тоже риск. При перезапуске ноды событие может потеряться. В итоге мы выбрали подход с локальной записью событий на диск. Сервис через библиотеку записывает событие в файл, а отдельный компонент считывает эти файлы и отправляет данные дальше. Так мы отделяем пользовательскую операцию от сетевой доставки. Библиотека инкапсулирует всю эту логику — сервисам не нужно реализовывать её самостоятельно. Сбор и доставка событий После генерации событий возникает следующая задача — собрать данные из разных кластеров и доставить их в единое хранилище. У нас не один кластер и не один сервис. Сервисы распределены, кластеры изолированы друг от друга, а события генерируются повсюду. Важно аккуратно собрать весь этот поток в одной точке и при этом не перегрузить систему. Мы рассматривали разные варианты организации очереди. В том числе думали о собственной реализации, смотрели и на классические решения, и на нетипичные, но в итоге остановились на Kafka. Она позволила нам:
- аккумулировать поток событий со всех кластеров;
- выстроить контролируемую модель потребления;
- обеспечить высокую производительность записи.
Хранение событий Теперь нужно было решить, где и как хранить данные. Мы помнили про два ключевых требования: низкая стоимость хранения и высокая производительность на запись. Сразу стало понятно, что хранить всё на локальных дисках — дорого. Поэтому уже на этапе системного дизайна мы приняли решение строить хранилище поверх Object Storage нашего облака. Дальше начался выбор инструмента. Первым кандидатом был ClickHouse — привычный и понятный инструмент с поддержкой S3-совместимых хранилищ и зрелой экосистемой. Он позволяет строить таблицы с использованием Object Storage, однако в классическом self-managed сценарии ClickHouse копирует данные на диск, что не соответствовало нашей цели отказа от дисков. Мы также рассматривали zero-copy replication, где между репликами синхронизируются в основном метаданные, а не сами данные, но на момент нашего системного дизайна этот режим в документации был помечен как «not production ready». Рисковать мы не стали. Дальше мы посмотрели в сторону Apache Iceberg. Важно понимать, что Iceberg — это не полноценная база данных, а слой управления табличными данными поверх Object Storage. Он определяет, как хранить файлы, как вести метаданные и как обеспечивать консистентность. Архитектуру Iceberg можно условно разделить на четыре компонента.
- Первый — собственно Object Storage, где лежат файлы данных. Мы выбрали формат Parquet — колоночный, удобный и хорошо поддерживаемый.
- Второй — каталог метаданных. Он хранит информацию о таблицах, версиях и связях между файлами. Мы рассматривали несколько вариантов и в итоге выбрали Nessie — по нашему мнению, на момент проработки архитектуры это был наиболее зрелый и удобный для production-сценариев каталог.
- Третий — сервисы обслуживания таблиц. Это процессы, которые удаляют устаревшие файлы, объединяют мелкие файлы в более крупные и обновляют метаданные. Это важно как для экономии места, так и для производительности. Для этих задач мы используем Spark — проверенный инструмент, который хорошо подходит для фонового обслуживания таблиц и обработки данных в Iceberg.
- Четвёртый — движок для выполнения запросов, который работает с данными и метаданными Iceberg-таблиц. Здесь мы рассматривали три варианта: Trino, StarRocks и ClickHouse. Мы выбрали StarRocks, о причинах расскажу дальше.
Trino — мощный и зрелый query engine с поддержкой Iceberg и инструментами обслуживания таблиц. Но у него есть архитектурная особенность: координатор не масштабируется из коробки. Решения существуют, но они достаточно сложные. StarRocks — полноценная аналитическая база данных, которая умеет работать поверх Iceberg и хорошо масштабируется. В её архитектуре есть frontend-ноды, которые планируют выполнение запроса, и compute-ноды, которые выполняют его. При нехватке ресурсов архитектура позволяет горизонтально добавлять необходимые ноды. ClickHouse, несмотря на симпатию к инструменту, на момент проработки архитектуры не предоставлял нужного нам набора операций поверх Iceberg для реализации полного пайплайна. В результате мы остановились на StarRocks. Пользовательский доступ к данным Финальный этап жизненного цикла события — предоставить пользователю удобный доступ к данным. Для этого у нас есть Control Plane, написанный на Kotlin. Он реализует API, поддерживает ресурсную модель и позволяет выполнять запросы с заданными фильтрами. Пользователь взаимодействует именно с этим уровнем. Внутри Control Plane обращается к query engine и возвращает результат. Возможно, в будущем добавим собственный язык запросов — по аналогии с тем, как это реализовано в Google. Пока же мы ограничились фильтрами и преднастроенными возможностями поиска. Архитектура сервиса аудитных логов: реализация на практике Как я уже писал, архитектура сервиса отражает жизненный цикл события. Ниже расскажу, как она выглядит на практике и из каких компонентов состоит. Генерация аудитных событий Как я уже упоминал выше, у нас есть библиотека, с помощью которой сервисы записывают аудитные события. Backend, живущий в Kubernetes, подключает эту библиотеку, генерирует события и записывает их на диск. Библиотеку мы реализовали под основные языки нашего облака — в первую очередь под Kotlin и Go. Помимо базовой функциональности «записать событие», мы предусмотрели middleware, которую можно подключить в Control Plane на уровне обработчиков запросов. Middleware автоматически собирает большую часть данных, необходимых для аудита. Сервису остаётся передать только специфическую часть: какой ресурс, какая операция и чем она завершилась. События записываются в файл в каталоге на диске, подключённом через HostPath. Дальше возникает задача корректно прочитать эти данные и отправить их дальше. Для корректной обработки файлов, в которые идет активная запись, мы реализовали отдельный сервис, условно названный logrotate (это наш собственный компонент, просто одноимённый с известной утилитой). Этот компонент вместе с библиотекой выводит файлы из режима записи и помечает их как готовые к дальнейшей обработке. После этого файлы можно забирать на сбор. Logrotate развёрнут как DaemonSet во всех кластерах, поэтому обслуживает все сервисы.

Архитектура сервиса аудитных логов. Генерация событий Локальный сбор и архив данных Следующий компонент — сервис, который читает подготовленные файлы и отправляет их дальше. Этот сервис мы называем коллектором. Он выполняет две задачи:
- Отправляет данные дальше в пайплайн.
- Сохраняет сжатые данные во временный локальный архив.
Зачем нужен локальный архив? Ответ — надёжность. Теоретически сеть может упасть на час или два. В такой ситуации события не должны теряться. Поэтому локальный архив хранит данные до тех пор, пока они не будут успешно доставлены в целевое хранилище. Очевидная проблема — архив не бесконечный. Поэтому со старта разработки сервиса мы настроили метрики и алертинг и следим за потреблением архива, чтобы не допустить его переполнения. Коллекторы также запущены как DaemonSet во всех кластерах. В итоге все коллекторы отправляют данные в Kafka для дальнейшей обработки.

Архитектура сервиса аудитных логов. Локальный сбор данных Очередь событий Kafka в пайплайне сбора событий — это центральная точка агрегации. После этого начинается этап обработки, который делится на две части:
- внешняя выгрузка;
- внутренняя запись в наше хранилище.
Мы сознательно разделили эти потоки. Нам не хотелось сначала писать события во внутреннее хранилище, а затем читать их оттуда для внешней отправки. Это создавало бы лишнюю нагрузку и увеличивало задержку. Проще и эффективнее отправлять данные напрямую из Kafka. Внешняя выгрузка данных Внешний экспортер читает данные из Kafka. Дальше происходит следующее:
- События группируются по проектам и организациям.
- Экспортер обращается к Control Plane по внутреннему API.
- Получает информацию о коллекторах, работающих с этими проектами и организациями.
- Определяет:
- какие события нужно оставить;
- куда их выгружать;
- от имени какого сервисного аккаунта выполнять операцию.
После этого экспортер выгружает данные в сконфигурированные пользователем внешнее хранилище. Здесь мы подумали про надёжность и предусмотрели плохие сценарии, например:
- проблемы с сетью;
- некорректно настроенный сервисный аккаунт;
- удалённый пользователем бакет.
Поэтому мы сохраняем данные во временном хранилище и реализуем механизм повторной отправки в течение заданного времени.

Архитектура сервиса аудитных логов. Внешняя выгрузка данных Внутреннее хранилище данных Внутренний экспортер (Iceberg Exporter) также читает данные из Kafka. Он реализует два сценария:
- Запись полного набора событий для внутренних нужд в Common Storage.
- Запись в пользовательские хранилища.
В качестве Common Storage мы используем то же хранилище, который предоставляем пользователям. Разница лишь в конфигурации: для внутреннего хранения используется другой коллектор и другие параметры хранения.

Архитектура сервиса аудитных логов. Внутреннее хранилище

Архитектура сервиса аудитных логов. Полная схема Ниже расскажу про некоторые особенности нашего внутреннего хранилища данных: Прослойка для работы с Iceberg.При записи в Iceberg есть важная особенность: в одну таблицу можно выполнить только один commit за раз. Если два процесса попытаются сделать commit параллельно, один из них завершится неудачей, и придётся перечитывать файлы и пересобирать манифесты. Чтобы избежать этой проблемы, мы добавили прослойку-агрегатор, которую назвали Coordinator. Она накапливает данные в течение заданного интервала (например, двух минут) и выполняет единый commit. Это своего рода координатор записи. Хранилище внутри Iceberg.Внутри нашего Iceberg-хранилища пользовательское хранилище — это, по сути, отдельная таблица. Мы рассматривали другие варианты:
- использование view;
- комбинированную модель (общая таблица + view для каждого хранилища).
Но оба варианта не подошли. И вот почему:
- view дают худшую производительность;
- комбинированный вариант сложнее в реализации и затрудняет контроль объёма данных.
Таблица оказалась самым простым и предсказуемым решением. Кроме того, поскольку на этапе экспорта мы уже знаем, к каким коллекторам относятся события и в какие хранилища они должны попасть, запись в таблицы часто может выполняться параллельно — если это разные таблицы. Прослойка агрегации нужна только в случаях, когда несколько коллекторов пишут в одно хранилище. Ограничения и производительность чтения.Напомню, одно из требований — адекватная производительность на чтение. Событий может быть много, глубина хранения — большой. Технически обеспечить высокую производительность на больших объёмах сложно. Поэтому здесь вступает продуктовый подход. У хранилища есть две характеристики:
- максимальный объём;
- максимальное время хранения.
В инфраструктуре large enterprise десятки ГБ могут заполниться очень быстро — это неудобно. Поэтому объём решили не ограничивать. А вот срок хранения мы ограничили — 30 дней. По нашим исследованиям, этого достаточно для реализации основных сценариев: расследований, диагностики, мониторинга. Если требуется более длительное хранение, пользователь может настроить выгрузку в Object Storage и строить аналитику самостоятельно. Для внутренних хранилищ мы предоставляем интерфейс просмотра. Для внешних выгрузок (Object Storage и другие будущие направления) интерфейса просмотра не предусмотрено — в том числе из-за требований к производительности. Что в итоге Сервис аудитных логов — важный компонент облака, базовый инструмент контроля и анализа событий. «Посмотреть логи» — простая и понятная потребность пользователя, но за ней стоит большая и довольно интересная работа по проектированию: переложить жизненный цикл события на архитектуру сервиса и выбрать оптимальные решения для каждого его компонента. Сервис продолжит развиваться. Сейчас аудитные логи доступны в Preview — сервис предоставляется бесплатно и предназначен для тестирования и знакомства с его возможностями. В ближайших планах — кросс-проектная выгрузка событий, подключение новых сервисов как источников аудитных логов, интеграция с SIEM через отдельный Collector, а также развитие интерфейса и более гибких фильтров в UI. Попробуйте сервис и поделитесь с нами обратной связью в сообществе MWS Cloud Platform.-Источник
|