
Всем привет! Решил поделиться с вами опытом написания и проведения ревью аудит‑политики Kubernetes.
Аудит‑политика Kubernetes — один из тех пунктов, который пишется один раз при настройке кластера, а потом годами живёт «как есть», обрастая исключениями для новых компонентов и почти никогда не пересматривается целиком. В какой‑то момент это уже не политика безопасности, а слой из комментариев трёхлетней давности.
Недавно я сел пересмотреть собственный конфиг на 500+ строк — собирал его из нескольких источников, в первую очередь опираясь на Kubernetes Threat Matrix. Хочу поделиться не столько конкретными находками (так как они специфичны для конкретного кластера), сколько принципами и типовыми ловушками, которые всплыли в процессе ревью. Если вы пишете или проводите ревью политики аудита для своего кластера — этот чек‑лист вполне сэкономит время.
Как вообще устроена Audit Policy
Если коротко: это список правил, каждое из которых описывает, каким пользователям/группам/verb'ам/ресурсам соответствует, и какой уровень логирования (level) применить — от None (не логировать вообще) до RequestResponse (логировать запрос и ответ целиком, включая тело).
Ключевая деталь, которую легко упустить: правила проверяются сверху вниз, и применяется первое совпадение. Если у вас широкое правило‑исключение стоит выше узкого критичного с точки зрения безопасности правила, второе никогда не сработает для тех же запросов. Большая часть проблем, которые реально стоит искать при ревью, — это именно ошибки порядка, а не ошибки в отдельном правиле.
Принцип 1: разделяй шум и сигнал
В любом мало‑мальски живом кластере 90% API‑трафика — это get/list/watch от системных компонентов: kubelet опрашивает статус нод, Prometheus скрейпит метрики, контроллеры следят за своими ресурсами. Если логировать всё это на приличном уровне, лог утонет в шуме, и искать в нём реальный инцидент станет невозможно.
Правильный паттерн — точечно гасить эти потоки:
- level: None users: ["system:serviceaccount:kube-system:coredns"] verbs: ["get", "list", "watch"]
Важно: гасить нужно максимально узко — по конкретному пользователю, конкретным verb'ам и, желательно, конкретным ресурсам. Вот тут кроется первая типовая ошибка.
Ловушка № 1: исключение без ограничения verb/resource
Встречается регулярно:
- level: None users: ["system:serviceaccount:some-ns:some-controller"]
Без verbs и resources это правило гасит абсолютно любое действие данного пользователя или сервис аккаунта — не только штатный трафик операций чтения, ради которого его писали, но и любые мутации, которые этому сервис аккаунту когда‑либо дадут (случайно через RBAC‑ошибку или намеренно при расширении функционала). Если токен такого сервис аккаунта скомпрометируют, атака пройдёт полностью мимо аудита — не потому что кто‑то планировал дыру, а потому что правило-исключение изначально писалось «на глаз», под текущее поведение компонента, без явной фиксации границ.
Практическое правило: при добавлении нового правила-исключения всегда явно перечисляйте verbs, даже если сейчас компонент делает только get/list/watch. Это фиксация границ, определение, что мы видим и что нам особенно важно.
Ловушка № 2: неактуальные комментарии
# Any actions with secrets and configs (except list/watch) - critical to monitor # for potential data leaks or unauthorized configuration changes - level: None verbs: ["get", "create", "update", "patch", "delete"] resources: - group: "" resources: ["secrets", "configmaps"] users: [...]
Комментарий говорит «critical to monitor», а правило реально исключает логирование для перечисленных в нем сервис аккаунтов.
Это не влияет на работу политики, но такие несостыковки — прямой путь к тому, что через год кто‑то прочитает комментарий, поверит ему на слово и потратит день на дебаг «почему события не логируются, хотя написано, что должны». Ревью комментариев — такая же часть код‑ревью, как и ревью логики.
Ловушка № 3: неактуальные правила от прошлых миграций
CNI мигрировали с kube‑router на Cilium полгода назад, а правило‑исключение для system:kube-router так и осталось в файле. Само по себе оно безобидно — просто никогда не сработает, потому что такого сервис аккаунта уже больше нет. Но:
оно засоряет файл и сбивает с толку при ревью;
если однажды кто‑то снова заведёт сервис аккаунт с таким же именем под другой компонент — унаследует чужие, неактуальные права.
Простой чек: периодически (раз в квартал, например) сверять список пользователей и сервис аккаунтов в правилах исключения политики аудита со списком реально существующих сервис аккаунтов в кластере — kubectl get sa -A — и вычищать несовпадения.
Принцип 2: Для чувствительных данных — максимум Metadata
Отдельный паттерн из разобранного конфига:
# Secrets, ConfigMaps, and TokenReviews can contain sensitive & binary data, # so only log at the Metadata level. - level: Metadata verbs: ["get", "create", "update", "patch", "delete"] resources: - group: "" resources: ["secrets", "configmaps"] - group: "authentication.k8s.io" resources: ["tokenreviews"]
Логика простая: сам факт обращения к секрету нужно фиксировать (кто, когда, какой секрет), а вот содержимое секрета в аудит-лог попадать не должно ни при каких условиях — иначе аудит-лог сам становится хранилищем секретов, только менее защищённым.
Принцип 3: где оправдано, используй RequestResponse
На другой стороне — события, где важно видеть не просто факт, а содержимое запроса целиком:
- level: RequestResponse resources: - group: "" resources: ["pods/exec", "pods/attach", "pods/portforward"] - level: RequestResponse verbs: ["create", "update", "patch", "delete"] resources: - group: "rbac.authorization.k8s.io" resources: ["clusterrolebindings", "clusterroles", "rolebindings", "roles"]
exec/attach/portforward — это прямой доступ внутрь работающего контейнера, классический вектор при разборе инцидентов. Изменения RBAC — это потенциальная эскалация привилегий. Для обоих случаев экономить на уровне логирования не стоит: разница в объёме лога небольшая, а ценность при расследовании — огромная.
Принцип 4: используй порядок правил осознанно, а не случайно
Хороший пример осмысленного использования правильного порядка — обработка events:
# Defense evasion tactic - detect attempts to delete k8s events - level: Metadata verbs: ["delete"] resources: - group: "" resources: ["events"] - group: "events.k8s.io" resources: ["events"] # Don't log events requests. - level: None resources: - group: "" resources: ["events"] - group: "events.k8s.io" resources: ["events"]
Сами события — очень шумный ресурс, логировать все обращения к ним нецелесообразно. Но удаление events — классическая техника defense evasion (атакующий чистит следы своей активности). Поставив узкое правило для delete выше общего «глушим всё», получаем ровно нужное поведение: обычный трафик событий не засоряет лог, а попытка их удалить — фиксируется.
Это стоит держать в голове как общий паттерн: если для одного и того же ресурса нужна разная чувствительность в зависимости от verb'а — специфичное правило всегда должно стоять выше общего.
Мини‑чек‑лист для ревью своей Audit Policy
Есть ли правило для
system:unauthenticated, и стоит ли оно выше всех правил-исключений?Есть ли хоть одно правило-исключение (
level: None) без явныхverbsи/илиresources? Если да — можно ли сузить?Секреты/конфигмапы/токены нигде не поднимаются выше
Metadata?RBAC‑изменения,
exec/attach/portforward, удаление сетевых политик — на достаточном уровне (Request/RequestResponse)?Нет ли в правилах-исключениях пользователей/сервис аккаунтов, которых уже не существует в кластере (последствия миграций)?
Комментарии соответствуют тому, что реально делает правило?
Есть ли catch‑all правило в самом конце файла (обычно
level: Metadata), чтобы ничего не пропадало мимо лога по умолчанию?Нет ли явно временных правил без даты/тикета на их удаление?
Вывод
Аудит‑политика Kubernetes — это не место где «настроил и забыл». Она растёт вместе с кластером, и каждое новое правило-исключение, добавленное на скорую руку, чтобы заглушить очередной шумный компонент, — это потенциальная слепая зона, если её не ограничить явно. Разница между «удобно» и «безопасно» здесь чаще всего решается одной строчкой — явным списком verbs.
Если у вас есть свои находки или паттерны при работе с аудит-политиками — делитесь в комментариях, интересно сравнить и обсудить подходы.
P. S.
В комментариях могу выложить готовый шаблон аудит-политики — образец, по которому можно собрать собственную.