1. Архитектура Runtime Security в Kubernetes: уровни абстракции и векторы атак
Архитектура Runtime Security в Kubernetes: уровни абстракции и векторы атак
Представьте, что злоумышленник успешно эксплуатировал уязвимость удаленного выполнения кода (RCE) в веб-приложении, запущенном внутри пода Kubernetes. Статический анализ образа не выявил проблем, сетевые политики разрешают HTTP-трафик, а Admission Controller пропустил контейнер, так как его конфигурация формально безопасна. С этого момента начинается «слепая зона» традиционных средств защиты. Злоумышленник находится внутри доверенного периметра, и единственное, что может его остановить — это система Runtime Security, способная в реальном времени заметить аномальный системный вызов execve() или попытку чтения /etc/shadow процессом, который обычно занимается только обработкой JSON-запросов.
Концепция Runtime Security в эфемерных средах
В отличие от классических серверов, где жизненный цикл системы измеряется месяцами, контейнеры в Kubernetes эфемерны. Это создает парадокс: с одной стороны, неизменяемость (immutability) инфраструктуры упрощает защиту (любое изменение в работающем контейнере подозрительно), с другой — динамичность среды делает традиционные инструменты мониторинга бесполезными.
Runtime Security — это процесс непрерывного наблюдения за активностью запущенных приложений и инфраструктуры для обнаружения и предотвращения угроз в момент их возникновения. В контексте Kubernetes мы работаем не просто с процессами, а с многослойным пирогом абстракций, где каждая прослойка вносит свои риски и накладывает ограничения на методы сбора данных.
Основная сложность заключается в том, что безопасность во время выполнения должна быть «прозрачной» для приложения. Мы не можем позволить себе внедрять тяжелые агенты в каждый контейнер (Sidecar-подход) из-за огромных накладных расходов на память и CPU, а также из-за сложности управления тысячами таких агентов. Именно здесь на сцену выходит eBPF (extended Berkeley Packet Filter), позволяющий перенести логику наблюдения на уровень ядра, сохраняя при этом контекст Kubernetes.
Иерархия уровней наблюдения и изоляции
Чтобы спроектировать эффективную систему защиты, необходимо четко разделять уровни, на которых происходят события. В Kubernetes архитектура безопасности строится вокруг четырех ключевых уровней.
Уровень L1: Аппаратное обеспечение и ядро (The Foundation)
Ядро Linux является единой точкой отказа и одновременно главным источником правды. Все контейнеры на узле разделяют одно и то же ядро. Если злоумышленник совершает побег из контейнера (container escape), он оказывается в контексте ядра хоста. На этом уровне Runtime Security оперирует системными вызовами (syscalls). Любое действие процесса — открытие файла, создание сетевого соединения, запуск дочернего процесса — проходит через интерфейс системных вызовов.Уровень L2: Среда выполнения контейнеров (Container Runtime)
CRI (Container Runtime Interface), такой как containerd или CRI-O, отвечает за управление жизненным циклом контейнеров. Здесь важна видимость событий жизненного цикла: создание, запуск, остановка и удаление. Ошибки в конфигурации Runtime (например, запуск с флагом--privileged) часто становятся входным вектором для атак.Уровень L3: Оркестрация (Kubernetes Control Plane)
Здесь события превращаются в высокоуровневые объекты: Pod, Deployment, ServiceAccount. Для системы безопасности критически важно сопоставить низкоуровневый системный вызов (например, процесс с PID 4502 открыл сокет) с метаданными Kubernetes (это подnginx-auth, запущенный в пространстве имен production). Без этой связи поток системных событий превращается в нечитаемый шум.Уровень L4: Прикладная логика (Application Space)
Верхний уровень, где выполняются бизнес-функции. Здесь атаки могут быть специфичными для языка (Java десериализация, Python code injection). Runtime Security на этом уровне часто требует интеграции с прикладными библиотеками или анализа L7-трафика.Векторы атак в Kubernetes и их проявления в Runtime
Понимание того, как атакуют Kubernetes, определяет, какие события мы должны собирать через eBPF. Рассмотрим наиболее опасные сценарии.
Побег из контейнера (Container Escape)
Это «святой грааль» для атакующего. Цель — выйти за пределы ограничений пространства имен (namespaces) и контрольных групп (cgroups).CAP_SYS_ADMIN, атакующий может смонтировать файловую систему хоста.runc (как в случае с CVE-2019-5736) позволяет перезаписать бинарный файл рантайма на хосте изнутри контейнера.mount(), pivot_root(), попытки доступа к /proc/sysrq-trigger или запись в чувствительные директории хоста.Компрометация цепочки поставок и «отравленные» образы
Злоумышленник внедряет вредоносный код в базовый образ. В момент запуска контейнер начинает выполнять полезную нагрузку, например, майнинг криптовалюты или сканирование внутренней сети.curl или wget в минималистичном образе на базе Distroless), сетевая активность в сторону известных пулов для майнинга, высокая загрузка CPU сразу после старта.Lateral Movement (Горизонтальное перемещение)
Попав в один под, атакующий пытается захватить другие части кластера. В Kubernetes это часто реализуется через кражу токенов ServiceAccount, которые по умолчанию монтируются в каждый под по пути/var/run/secrets/kubernetes.io/serviceaccount/token.
Анатомия системного вызова как атомарной единицы безопасности
Системные вызовы — это основной интерфейс между пользовательским пространством (User Space) и ядром (Kernel Space). Для Runtime Security они являются первичными данными. Однако их анализ сопряжен с рядом сложностей.
Рассмотрим типичный процесс запуска вредоносного скрипта:
execve("/bin/sh", ["sh", "-c", "curl ..."], ...) — запуск оболочки.socket(AF_INET, SOCK_STREAM, 0) — создание сетевого соединения.connect(...) — установка связи с внешним сервером.Если мы отслеживаем только execve, мы увидим факт запуска. Но если злоумышленник использует технику Fileless Execution (выполнение в памяти через memfd_create), классические инструменты мониторинга файлов ничего не заметят.
Проблема «шума» системных вызовов:
Среднестатистический микросервис генерирует тысячи вызовов в секунду. Простое логирование всех read() и write() приведет к деградации производительности системы на 30–50% и создаст терабайты бесполезных данных.
> Критический инсайт: Эффективная архитектура Runtime Security должна фильтровать события максимально близко к источнику — в ядре, используя eBPF, — и передавать в User Space только те события, которые соответствуют подозрительным паттернам или выходят за рамки предварительно построенного профиля поведения.
Сравнение подходов к сбору данных: ptrace, Auditd, LD_PRELOAD и eBPF
До появления eBPF существовало несколько способов перехвата событий, каждый из которых имел фатальные недостатки для высоконагруженных сред Kubernetes.
1. ptrace (Process Trace)
Используется такими инструментами, какstrace. Позволяет останавливать процесс при каждом системном вызове.
2. Linux Audit Framework (auditd)
Зрелая система логирования событий ядра.auditd может стать узким местом системы.3. LD_PRELOAD (Библиотечный перехват)
Подмена стандартных функций библиотек (например,libc).
4. eBPF (Extended Berkeley Packet Filter)
Современный стандарт. Позволяет загружать небольшие программы в ядро, которые выполняются при наступлении определенных событий (kprobes, tracepoints).Архитектурные вызовы: Проблема TOCTOU и потеря контекста
Разработка системы Runtime Security — это не только написание eBPF-кода, но и решение фундаментальных проблем компьютерных наук в контексте безопасности.
TOCTOU (Time-of-Check to Time-of-Use)
Это класс уязвимостей, возникающих из-за разрыва во времени между проверкой ресурса и его использованием. В контексте мониторинга системных вызовов это выглядит так:open("/etc/config.json", ...)./etc/shadow./etc/shadow, хотя система безопасности «думает», что открыт разрешенный файл.Для борьбы с этим современные системы (например, Tetragon) используют перехват не на входе в системный вызов, а в глубоких структурах ядра (LSM hooks — Linux Security Modules), где данные уже скопированы в пространство ядра и не могут быть изменены пользователем.
Обогащение контекстом (Context Enrichment)
Для инженера безопасности событие «Процесс 1234 открыл файл X» бесполезно. Ему нужно знать:Архитектура системы должна включать в себя Enricher — компонент в User Space, который слушает события от Kubernetes API и сопоставляет их с потоком событий от eBPF-зондов. Это требует эффективного кэширования, так как обращение к API-серверу на каждое событие создаст недопустимую нагрузку.
Модель данных и пайплайн обработки событий
Типичный путь события в Runtime Security системе выглядит следующим образом:
execve. Программа извлекает аргументы и PID.SIGKILL процессу или удаление пода через Kubernetes API).Сравнительный обзор существующих решений
На рынке и в Open Source представлены три основных игрока, каждый из которых реализует свою философию Runtime Security.
| Характеристика | Falco | Tetragon (Cilium) | Aqua Tracee | | :--- | :--- | :--- | :--- | | Основной механизм | kprobes / ptrace / драйвер ядра | eBPF (LSM hooks) | eBPF (kprobes / tracepoints) | | Фокус | Детекция (Detection) | Предотвращение (Prevention) | Глубокая трассировка и форензика | | Сильные стороны | Огромная библиотека готовых правил, стандарт индустрии | Глубокая интеграция с сетью, минимальный TOCTOU | Простота написания сигнатур на Go/Rego | | Слабые стороны | Возможен оверхед при сложных правилах | Требует свежих версий ядра (5.10+) | Менее зрелая экосистема правил по сравнению с Falco |
Falco исторически опирался на модуль ядра, но сейчас активно переходит на eBPF. Его сила в языке описания правил, который стал де-факто стандартом. Однако Falco работает преимущественно как система обнаружения: он сообщает о проблеме, когда она уже случилась.
Tetragon, будучи частью проекта Cilium, использует более современный подход. Благодаря использованию LSM (Linux Security Modules) через eBPF, он может блокировать (Enforce) нежелательные действия в реальном времени прямо в ядре, не дожидаясь реакции агента в User Space.
Tracee выделяется своей способностью обнаруживать сложные атаки, такие как загрузка вредоносных модулей ядра или выполнение кода в памяти, предоставляя детальный лог для последующего расследования (forensics).
Проектирование с учетом производительности
При разработке собственного решения на базе eBPF критически важно учитывать влияние на производительность (Performance Overhead). Основные потребители ресурсов здесь:
Для минимизации оверхеда применяется стратегия «Раннего отказа»: 99% событий должны отсеиваться внутри eBPF-кода. Например, если мы следим за записью в /etc, мы передаем в User Space только те вызовы write(), где дескриптор файла указывает на целевую директорию.
Резюмируя архитектурный подход
Разработка системы Runtime Security в Kubernetes — это баланс между глубиной видимости и ценой этой видимости. Мы начинаем с уровня ядра, где eBPF дает нам сырые данные о системных вызовах. Затем мы поднимаемся на уровень оркестрации, чтобы придать этим данным смысл. И, наконец, мы применяем логику (будь то статические правила или модели машинного обучения), чтобы отделить нормальное поведение микросервиса от действий злоумышленника.
В следующих главах мы перейдем от теории к практике: разберем конкретные типы eBPF-программ, научимся работать с мапами и строить эффективные фильтры, которые станут фундаментом нашего собственного решения для защиты Kubernetes.