Мониторинг целостности файлов (File Integrity Monitoring, FIM) помогает командам выявлять несанкционированные изменения чувствительных файлов и является важной частью любой системы информационной безопасности. Однако построить FIM-систему, которая надёжно работает в современной крупномасштабной инфраструктуре, сложнее, чем кажется.
Когда мы взялись за разработку FIM-системы, способной выдерживать реалии продуктовых сред Datadog, быстро выяснилось: существующие подходы не подходят. Например, периодическое сканирование файловой системы на бумаге выглядит простым и надёжным решением. Но на практике оно пропускает именно те события, которые для нас наиболее важны: если злоумышленник изменит файл и откатит изменения до следующего сканирования, то получится, будто ничего и не происходило. Даже когда сканирование всё же что-то обнаруживает, оно лишь сообщает, что файл изменился, — но не объясняет, как именно он изменился, почему это произошло и кто внёс изменения. Для инженерной команды, которая стремится давать специалистам по безопасности информацию, пригодную для действий, такой недостаток видимости был неприемлем.
Даже «классические» событийные технологии мониторинга в Linux имеют серьёзные ограничения. inotify не даёт нужного системного контекста, чтобы связывать файловые события с процессами и контейнерами, а auditd — хотя и предоставляет более полную картину — часто страдает от существенных накладных расходов и плохо масштабируется при высокой нагрузке на систему.
Чтобы получить нужный нам уровень контекста и масштабируемости, мы обратились к eBPF. Он дал нам возможность наблюдать за файловой активностью в реальном времени прямо из ядра — без ущерба для стабильности и безопасности. С eBPF мы видели не только факт изменения файла, но и то, какой процесс инициировал это изменение и в каком контейнере он выполнялся, — а значит, получали по-настоящему полезные данные для расследований в области безопасности.
Но за такую видимость пришлось заплатить. Теперь мы столкнулись с ошеломляющим объёмом данных: в обычный пятничный день по всей инфраструктуре Datadog фиксировалось более 10 миллиардов событий, связанных с файлами, в минуту. Обработать этот поток так, чтобы не терять события и не ухудшать производительность хоста, стало одной из самых сложных технических задач, которые нам пришлось решать.
В этой статье мы расскажем, как справились с этой задачей, в том числе:
Почему eBPF дал нам наблюдаемость, которой не хватало другим инструментам
Как мы масштабировались до миллиардов событий в минуту, не перегружая ни наши агенты, ни бэкенд
Какие приёмы мы использовали, чтобы предварительно отфильтровывать 94% событий прямо в ядре, не теряя важные сигналы
Управление нагрузкой на периферии: масштабирование мониторинга файлов на базе eBPF
После того как с помощью eBPF мы получили глубокую видимость активности файловой системы, перед нами встала следующая проблема — масштабирование. Сбор каждого файлового события по всей инфраструктуре Datadog приводил к колоссальному объёму данных, который было невозможно в разумных пределах передавать в бэкенд.
Каждое событие файловой активности содержит критически важный контекст: процесс, который инициировал действие, контейнер, в котором он выполнялся, а также другие метаданные. В сериализованном виде этот контекст вместе с информацией о файле занимает примерно 5 КБ на событие. При нашем масштабе — более 10 миллиардов событий в минуту — отправка всего этого потока наверх означала бы передачу по сети нескольких терабайт данных в секунду. При этом большинство событий не соответствовало бы ни одному правилу обнаружения и в итоге всё равно отбрасывалось, что делало затраты на обработку и хранение совершенно неоправданными.
Проблема не ограничивалась только бэкендом. На стороне агента сериализация и передача такого объёма данных приводили бы к резкому росту нагрузки на CPU и память, увеличивая риск потери событий — ровно того, чего мы старались избежать. Поддерживать исходящий трафик в сотни мегабит в секунду на каждом хосте исключительно ради мониторинга безопасности было попросту нереалистично.
Чтобы решить эту задачу, мы внедрили правила на стороне агента. Вместо того чтобы отправлять всё подряд, мы сопоставляли файловую активность с набором правил локально, на каждом хосте. Это позволило отбрасывать шум на раннем этапе и передавать только те события, которые действительно важны для расследований в области безопасности. Фильтрация на периферии инфраструктуры резко сократила объём данных, которые нужно сериализовать и передавать, — примерно до одного миллиона событий в минуту по всей инфраструктуре, при этом полнота обнаружения сохранялась.

Фильтрация 94% событий на уровне ядра
Когда мы взялись за создание решения для мониторинга целостности файлов на базе eBPF, быстро стало понятно, насколько тесно связаны производительность и полнота покрытия. На уровне агента eBPF дал нам мощный способ наблюдать за активностью системы, не вмешиваясь в её работу, но это также означало, что нам нужно успевать обрабатывать непрерывный поток событий. В среде Linux, где почти всё воспринимается как файл, объём системных вызовов и связанных с файлами событий оказался огромным. Обрабатывать этот поток данных в реальном времени стало одной из ключевых инженерных задач, которую нам предстояло решить.
Упрощённая архитектура eBPF-FIM
На первый взгляд архитектура базового решения FIM на eBPF выглядит не слишком сложной. Агент загружает eBPF-программы, которые подключаются к ключевым точкам в системе, чтобы наблюдать за активностью. Эти программы записывают данные в кольцевой буфер, а оттуда агент читает и анализирует каждое событие. В теории всё довольно просто.

Проблема масштаба
Реальность быстро дала о себе знать. Некоторые из наших наиболее чувствительных нагрузок генерируют до 5 000 релевантных системных вызовов в секунду. Это не фоновый шум — это события, связанные с безопасностью, которые необходимо анализировать. Агенту нужно было не просто разбирать тысячи событий в секунду, а делать это без потерь и без заметного влияния на производительность системы. Обработать такой объём само по себе несложно — настоящий вызов начинается, когда нужно делать это с нулевым воздействием и при этом сохранять полное покрытие по безопасности.
Первые бенчмарки наглядно показали масштаб проблемы. На части хостов агент не успевал за потоком. Поток событий, проходящий через кольцевой буфер, мог опережать возможности агента по обработке, что приводило к потере событий. Каждое пропущенное событие означало потенциальную «слепую зону» в нашем покрытии по безопасности — то, чего мы не могли себе позволить в системе, предназначенной для обнаружения вмешательств.
Перенос логики ближе к ядру
Разбирая узкие места производительности, мы пришли к очевидному выводу: чтобы выдерживать высокий объём событий и сохранять покрытие, нужно переосмыслить, где именно должна жить логика обработки. Мы сделали ключевой архитектурный шаг — перенесли как можно больше этапов оценки событий в сами eBPF-программы. Это позволило отфильтровывать нерелевантные события прямо в ядре и резко сократить объём данных, проходящих через кольцевой буфер. В пользовательское пространство попадали только действительно важные события.
Эта оптимизация дала нашему агенту решающее преимущество. Даже когда его вытеснял планировщик ядра, он всё равно мог выполнить вторую, более глубокую проверку перед тем, как решить, отправлять ли событие в бэкенд. Это было большой победой — но вместе с тем принесло и новые сложности.
Апруверы и дискардеры: предварительная фильтрация в ядре
При всей мощи eBPF, он намеренно ограничен — ради стабильности ядра. Эти ограничения, особенно вычислительные, защищают систему от неконтроллируемых программ, но одновременно усложняют выполнение сложной логики. На старых ядрах, где нет самых новых возможностей eBPF, эта проблема становится ещё заметнее.
Чтобы работать в рамках этих ограничений, мы выбрали двухэтапную модель оценки:
Фильтрация в ядре (in-kernel filtering): лёгкий этап, который быстро принимает решения с минимальными накладными расходами
Оценка в пользовательском пространстве (user-space evaluation): более глубокий этап, где выполняется полная проверка — с богатым контекстом, корреляциями и логикой, которую невозможно (или небезопасно) выполнять в ядре
Чтобы сделать фильтрацию на уровне ядра эффективнее, мы ввели два ключевых понятия: апруверы и дискардеры.
Апруверы (approvers) пропускают события, которые соответствуют определённым критериям.
Дискардеры (discarders) явно отсекают шум на раннем этапе.
Вместе они дают точный и эффективный способ управлять потоком событий, не перегружая ни агент, ни систему, на которой он работает.
Как работают апруверы
Апруверы — это статические фильтры, которые мы получаем при компиляции правил обнаружения. Анализируя условия каждого правила, мы можем выделить шаблоны или конкретные значения, из-за которых событие стоит отправить на более глубокую проверку в пользовательском пространстве.
Например:
open.file.path == "/etc/passwd" && open.flags & O_CREAT > 0
В этом случае имя файла passwd — значимое значение. Мы определяем его как апрувер и передаём в ядро через eBPF map. Такие карты (eBPF maps) позволяют быстро и с низкими накладными расходами фильтровать события в ядре и гарантировать, что в пользовательское пространство попадут только релевантные события для дальнейшего анализа.
Как дискардеры дополняют этот подход
Разумеется, долго всё простым не остаётся. Одно правило — это легко, но когда вы управляете сотнями правил, каждое из которых использует разные поля и условия для разных системных вызовов, задача быстро превращается в сложную оптимизационную проблему.
В зависимости от выражения правила движок правил не всегда способен сгенерировать апруверы. Например:
open.file.path == "/etc/*"
В этом случае невозможно извлечь конкретное имя файла для использования в качестве статического фильтра, поскольку подстановочный символ * соответствует любому файлу в каталоге /etc/. Заранее «одобрить» какое-то конкретное значение здесь нельзя.
Именно поэтому мы ввели дискардеры — динамическое дополнение к апруверам. Дискардеры создаются движком правил во время выполнения. Если он определяет, что некоторое значение в событии никогда не сможет совпасть ни с одним правилом, это значение помечается как кандидат на фильтрацию непосредственно в ядре.
Продолжая предыдущий пример, можно с уверенностью сказать, что любой доступ к файлам в каталоге /tmp никогда не совпадёт с правилом, ориентированным на /etc/*. В таком случае /tmp становится дискардером. Эти дискардеры загружаются в ядро через LRU eBPF maps, что позволяет эффективно их отфильтровывать и при этом удерживать потребление памяти под контролем.
Как и апруверы, дискардеры начинаются с простых случаев. Однако в реальной среде с большим и постоянно меняющимся набором правил определение того, что именно можно безопасно отбросить, требует нетривиальных алгоритмов.

Вместе апруверы и дискардеры позволяют предварительно отфильтровывать до 94% событий прямо в ядре. Это приводит к меньшему количеству событий для обработки в пользовательском пространстве, существенно более низкой нагрузке на CPU и — что важнее всего — отсутствию потерь событий. Именно эта многоуровневая, адаптивная модель фильтрации делает наше FIM-решение одновременно высокопроизводительным и обеспечивающим высокое покрытие даже при тяжёлых нагрузках.
За пределами обнаружения: обогащаем FIM контекстом
Построение мониторинга целостности файлов поверх eBPF стало для нас одновременно и вызовом, и возможностью. Выйдя за рамки традиционных подходов вроде периодического сканирования или inotify, мы получили возможность собирать точные, высокодетализированные данные о файловой активности в реальном времени — даже для короткоживущих процессов и эфемерных контейнеров. Необходимость справляться с огромным объёмом данных вынудила нас внедрять новые решения: фильтрацию в ядре, правила на стороне агента, а также концепции апруверов и дискардеров. В совокупности всё это позволило сохранить и производительность, и полноту покрытия на масштабе Datadog.
И FIM — лишь начало. Важно зафиксировать факт, что файл изменился, но именно понимание того, почему он изменился, какой процесс или пользователь стоял за этим, и что ещё происходило в системе в тот момент, превращает «сырые» события в полезные для работы выводы в области безопасности. Следующий шаг для нас — продолжать обогащать FIM максимально возможным контекстом, чтобы специалисты по безопасности не просто знали, что что-то случилось, а имели полную картину для расследования и эффективного реагирования.
Эта история про eBPF-телеметрию напоминает: наблюдаемость — это не «собрать всё», а уметь превратить шум в сигнал. На курсе «Observability: мониторинг, логирование, трейсинг» разбираем, как проектировать метрики, логи и трассы и собирать их в цельную систему на Prometheus/Grafana, ELK и Tempo с алертами, которые реально помогают. Пройдите вступительный тест, чтобы узнать, подойдет ли вам программа курса.
Для знакомства с форматом обучения и экспертами приходите на бесплатные демо-уроки:
21 января 20:00. «Мониторинг: как понять, что твой сервис болен». Записаться
22 января 19:00. «eBPF: рентгеновское зрение для production. Видим сеть, безопасность и узкие места на уровне ядра Linux». Записаться
27 января 20:00. «Docker hardening + IaC security: типовые ошибки и чек-лист безопасной инфраструктуры». Записаться