В четвёртой части Wazuh научился не только видеть атаку, но и сам закрывать атакующему IP. Но всё это время взгляд SIEM ограничивался отдельными хостами: агент, логи, файловая система. Пятая часть меняет масштаб — подключаем Kubernetes. У кластера есть свойство, за которое его любят и атакующие, и те, кто защищается: любое действие в кластере — это запрос к API-серверу. kubectl exec, чтение секрета, создание privileged-пода, смена RBAC — всё это HTTP-запросы, которые API-сервер умеет записывать в audit-лог. Умеет — но по умолчанию либо не пишет вообще, либо пишет в файл на самом API-сервере, где логи лежат мёртвым грузом, и первым их читает как раз тот, кто скомпрометировал кластер.
Содержание
Открыть содержание
Что логирует API-сервер
Kubernetes audit logging — встроенный механизм kube-apiserver: флаг --audit-policy-file включает политику, которая решает, какие запросы писать и с какой детализацией. Каждое событие — одна JSON-строка формата audit.k8s.io/v1:
{"kind":"Event","apiVersion":"audit.k8s.io/v1","level":"Request",
"auditID":"8f6b...","stage":"ResponseComplete","verb":"create",
"user":{"username":"dev@company","groups":["system:authenticated"]},
"objectRef":{"resource":"pods","subresource":"exec","namespace":"prod","name":"api-7d9f"},
"responseStatus":{"code":101}}
Из этого куска уже видно, почему аудит — золотая жила для детектов: в одном событии есть кто (user.username), что (verb + objectRef), где (namespace, имя объекта) и чем закончилось (responseStatus.code — 101 у exec это переключение протокола, то есть сессия открылась). Рядом в полной строке лежат sourceIPs — цепочка адресов от клиента до прокси — и userAgent, по которому видно, зашёл человек через kubectl или что-то стучится скриптом. Именно эти техники — exec в под, чтение secrets, создание privileged-подов — прямо описаны в матрице MITRE ATT&CK for Containers, и именно их возьмём под детекты.
Проблема не в том, что данных нет, а в том, что они умирают на диске API-сервера: на трёх-пяти мастер-нодах по файлу, без центрального хранения, без правил, без алертов. Задача этой части — построить конвейер: API-сервер → Fluent Bit → Wazuh → алерт.
Уровни аудита: Metadata, Request, RequestResponse
Главное решение при написании audit policy — уровень детализации на каждый тип ресурсов. Их три, и отличаются они тем, сколько тела запроса и ответа попадает в лог:
- Metadata — только обложка: кто, что, когда, от какого источника. Тел нет. Одна строка на событие, объём предсказуем,
- Request — обложка плюс
requestObject: тело запроса. Дляpods/execздесь лежит команда, которую запускают в поде, для создания пода — его спецификация, - RequestResponse — плюс
responseObject: что вернул API. Самый подробный и самый дорогой: объём логов на бойком кластере легко вырастает в разы.
Сведём выбор в таблицу — она же ответ на вопрос «какой уровень на что вешать»:
| Уровень | Тела запроса и ответа | Объём относительно Metadata | Когда выбирать |
|---|---|---|---|
| Metadata | нет | 1× | всё по умолчанию; secrets и configmaps — всегда |
| Request | requestObject | ~2–3× | exec (нужна команда), создание подов (нужна спецификация) |
| RequestResponse | + responseObject | ~5–10×, зависит от ресурсов | точечно: RBAC, отдельные ресурсы под конкретные детекты |
Два решения, которые определяют объём и безопасность самого лога:
- omitStages с “RequestReceived” — API-сервер по умолчанию пишет событие дважды: на входе и после обработки. Девяносто процентов второй копии вам не нужны: убрав стадию RequestReceived, вы почти вдвое режете объём, не потеряв ни одного результата,
- secrets — только Metadata, никогда RequestResponse. Классическая ошибка из лучших побуждений: включить RequestResponse на всё, чтобы «видеть больше» — и утащить тела всех секретов кластера в логи, а с ними и в SIEM, бэкапы и всё, куда логи утекают дальше. Атакующему достаточно права на чтение логов — это куда тише, чем чтение самих секретов.
Политика — упорядоченный список правил, первое совпадение выигрывает, поэтому частные случаи идут раньше общих:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
rules:
# 1. секреты — только Metadata: тела секретов не должны
# утекать в логи и SIEM
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
# 2. exec/attach/portforward — Request: команда в requestObject,
# в ответе бинарный поток, которого всё равно нет
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward"]
# 3. поды — RequestResponse: спецификация нужна детекту
# privileged-подов, ответ подтверждает, что под создан
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
# 4. RBAC — всегда максимум: кто раздал себе права,
# интереснее всего с телами запросов
- level: RequestResponse
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# 5. всё остальное — Metadata
- level: Metadata
В self-hosted кластере (kubeadm) политика включается двумя флагами в /etc/kubernetes/manifests/kube-apiserver.yaml, файл политики и каталог логов пробрасываются в под через hostPath:
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit.log
- --audit-log-maxage=7
В managed-кластерах аудит пишется в облачный лог (CloudWatch в EKS, Cloud Logging в GKE) — конвейер тот же, меняется только вход Fluent Bit.
Декодер: учим Wazuh читать audit-события
Дальше — знакомая по третьей части механика: prematch выбирает наши строки, JSON-движок разворачивает вложенности в пути через точку. В /var/ossec/etc/decoders/local_decoder.xml:
<decoder name="k8s-audit">
<prematch>^.+"kind":"Event".+"apiVersion":"audit.k8s.io</prematch>
<plugin_decoder>JSON_Decoder</plugin_decoder>
</decoder>
После декодера поля события доступны правилам как динамические: verb, user.username, objectRef.resource, objectRef.namespace, requestObject.*. Вложенные массивы JSON-движок разворачивает с числовыми индексами — requestObject.spec.containers.0.securityContext.privileged — это ещё пригодится для privileged-подов.
Правила: exec, secrets, privileged-поды
Три детекта из заготовки, в /var/ossec/etc/rules/local_rules.xml. Базовое правило распознаёт все аудит-события, дети навешивают условия — ровно та же иерархия if_sid, что мы строили для SSH и биллинга:
<group name="kubernetes,audit,">
<rule id="100210" level="3">
<decoded_as>k8s-audit</decoded_as>
<description>kubernetes: audit event $(objectRef.resource) $(verb)</description>
</rule>
<rule id="100211" level="12">
<if_sid>100210</if_sid>
<field name="verb">^create$</field>
<field name="objectRef.resource">^pods/exec$</field>
<field name="user.username">!^system:serviceaccount:kube-system:</field>
<description>kubernetes: exec into pod by $(user.username) — outside allowlist</description>
<mitre><id>T1609</id></mitre>
</rule>
<rule id="100212" level="9">
<if_sid>100210</if_sid>
<field name="verb">^get|list|watch$</field>
<field name="objectRef.resource">^secrets$</field>
<field name="user.username">!^system:serviceaccount:</field>
<description>kubernetes: secrets read via API by human user $(user.username)</description>
<mitre><id>T1552.007</id></mitre>
</rule>
<rule id="100213" level="12">
<if_sid>100210</if_sid>
<field name="verb">^create$</field>
<field name="objectRef.resource">^pods$</field>
<field name="requestObject.spec.containers.0.securityContext.privileged">^true$</field>
<description>kubernetes: privileged pod created by $(user.username)</description>
<mitre><id>T1610</id></mitre>
</rule>
</group>
Что здесь важно:
- базовое правило уровня 3 — чтобы аудит-события вообще попадали в алерты и по ним можно было строить дашборды; если объём давит, снизьте до 0 — дети продолжат срабатывать,
- отрицание с
!в поле — весь allow-лист сервис-аккаунтов kube-system умещается в одно условие: exec отkube-system— это работа операторов и CSI, от всех остальных — событие для разбора, - чтение secrets разрешаем только сервис-аккаунтам — людям в кластере брать секреты напрямую нечего; контроллеры делают это постоянно, поэтому отсекаем весь префикс
system:serviceaccount:, - privileged-под ловится по полю из тела запроса — RequestResponse-уровень на подах нужен именно ради этого: флаг лежит в
requestObject, и JSON-движок достаёт его через числовой индекс контейнера, - блоки
mitre— Wazuh знает матрицу ATT&CK и показывает техники прямо в карточке алерта; для отчётности и триажа это дешевле любого объяснения, зачем ваш SIEM нужен.
Доставка: Fluent Bit до менеджера
Осталось довести лог до Wazuh. Канонический путь из доков — Fluent Bit DaemonSet: под на каждой ноде читает audit-лог с hostPath и отправляет его менеджеру по syslog over TCP. ConfigMap:
[SERVICE]
Flush 5
Daemon Off
Log_Level warning
[INPUT]
Name tail
Path /var/log/kubernetes/audit.log
Refresh_Interval 10
Skip_Long_Lines On
[OUTPUT]
Name syslog
Host WAZUH_MANAGER_IP
Port 514
Mode tcp
Syslog_Hostname k8s-audit
Syslog_Appname kube-apiserver
Syslog_Tag audit
DaemonSet монтирует /var/log/kubernetes с нод мастеров (подробный манифест есть в доках Wazuh — там обычные tolerations для control-plane). На менеджере в ossec.conf открываем syslog-приёмник только для pod-сети кластера:
<remote>
<connection>syslog</connection>
<port>514</port>
<protocol>tcp</protocol>
<allowed-ips>10.42.0.0/16</allowed-ips>
</remote>
allowed-ips здесь — не косметика: открытый syslog-порт на 514 — это приглашение всему интернета кормить ваш SIEM мусором, поэтому приёмник смотрит только в сеть подов.
Как проверить, что всё работает
В отличие от Active Response из четвёртой части, декодеры и правила спокойно проверяются без живого кластера — /var/ossec/bin/wazuh-logtest прогоняет строку через все фазы. Возьмите реальную строку из /var/log/kubernetes/audit.log на мастере, вставьте в logtest и убедитесь, что Phase 2 нашла k8s-audit, а Phase 3 — правило 100211.
Живая проверка end-to-end — exec от человеческой учётки в любой под прод-неймспейса:
kubectl -n prod exec -it deploy/api -- sh
На менеджере:
tail -f /var/ossec/logs/alerts/alerts.json | grep -E '100211|100212|100213'
Алерт с T1609, вашим user.username и именем пода должен появиться в течение секунд (Flush 5). Если logtest срабатывает, а живой алерт нет — разрыв в транспорте: смотрите логи Fluent Bit (kubectl logs -n logging -l app=fluent-bit) и /var/ossec/logs/ossec.log на ошибки syslog-приёмника.
Итог
Аудит-лог Kubernetes — редкий случай, когда лучший источник сигналов о компрометации кластера уже пишется бесплатно, его нужно только довести до SIEM: политика на сто строк, ConfigMap Fluent Bit, декодер на три строки и три правила — и в Wazuh видно всё, что происходит на control-plane API: кто залогинился, кто сделал exec, кто выдал себе RBAC. Но у этой картины есть граница: API-сервер видит запросы, а не процессы. Что бинарник запустил внутри контейнера, какие файлы он открыл, куда установил соединение — на уровне API этого нет, и аудит это не покажет никогда. Этот пробел закрывает runtime-детект на syscalls — Falco, герой следующей, заключительной части цикла.