Admission-контроль и безопасность образов

Последний рубеж обороны — не пустить в кластер то, что не должно туда попасть.

Admission controller — компонент, который перехватывает запросы к API-серверу после аутентификации и авторизации, но до записи объекта в etcd, и может его проверить (validating) или изменить (mutating).

RBAC отвечает на вопрос «кому можно», securityContext — «как запускать под». Admission-контроль отвечает на вопрос «что вообще допустимо создать»: запретить привилегированные поды, потребовать лимиты ресурсов, не пустить образ с тегом latest или из неизвестного реестра. Это политика, применяемая ко всему кластеру централизованно.

Зачем это на практике

Полагаться на то, что все команды напишут безопасные манифесты, нельзя — кто-то забудет, кто-то скопирует пример из интернета с privileged: true. Admission-политики переводят правила безопасности из «договорённости» в «технически невозможно нарушить». Пример: политика «образы только из registry.company.io» физически блокирует деплой левого образа, даже если у разработчика есть права на создание подов.

Это закрывает важный класс угроз цепочки поставок (supply chain). Современное приложение в кластере — это не только ваш код, но и десятки базовых образов, библиотек и слоёв, пришедших из публичных реестров. Любой из них может содержать уязвимость или быть подменён. Admission-контроль вместе с проверкой образов даёт точку, где кластер один раз и для всех решает: какие источники доверенные, какие версии допустимы и подписан ли тот код, который вот-вот начнёт исполняться с правами вашего пода. Без такой точки каждое решение принимается вразнобой в десятках пайплайнов — и одно неверное открывает дверь.

Два вида admission-контроллеров

Запрос проходит две фазы admission. Сначала mutating-контроллеры могут дополнить объект (вписать sidecar, проставить дефолты), затем validating — проверить итог и принять решение «пустить или отклонить»:

ТипЧто делаетПример
Mutatingизменяет объект до записидобавить метку, sidecar, дефолтные ресурсы
Validatingпринимает или отклоняетзапретить privileged, latest, чужой реестр

В кластере уже работают встроенные admission-плагины. Pod Security Admission из урока про безопасность подов — как раз validating-контроллер. А для своих правил подключают policy-движки.

Policy-движки: OPA/Gatekeeper и Kyverno

Писать свой admission-webhook на каждое правило тяжело, поэтому используют готовые движки. Два самых популярных:

Kyverno: политики как обычный YAML

Kyverno описывает правила в виде Kubernetes-ресурсов, без отдельного языка. Запретим тег latest у любого контейнера:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-latest-tag
spec:
  validationFailureAction: Enforce   # Enforce = блокировать, Audit = только лог
  rules:
    - name: require-image-tag
      match:
        any:
          - resources:
              kinds: ["Pod"]
      validate:
        message: "Тег образа обязателен и не должен быть :latest"
        pattern:
          spec:
            containers:
              - image: "!*:latest"   # запрещаем суффикс :latest

Теперь попытка применить под с image: nginx:latest или вовсе без тега будет отклонена с понятным сообщением.

OPA/Gatekeeper: политики на языке Rego

Gatekeeper применяет Open Policy Agent: правила пишутся на языке Rego и оформляются как ConstraintTemplate + Constraint. Тот же запрет latest на Rego выглядит так:

package k8srequiredtag

violation[{"msg": msg}] {
  container := input.review.object.spec.containers[_]
  endswith(container.image, ":latest")
  msg := sprintf("образ %v использует запрещённый тег latest", [container.image])
}

Rego мощнее для сложной логики, но требует отдельного языка; Kyverno проще для типовых правил «запрети/потребуй». Выбор зависит от команды.

Безопасность образов

Образ — это код, который вы запускаете в кластере с правами своего пода. Оборонительные меры:

  • Запрет тега latest. latest непредсказуем: один и тот же манифест завтра запустит другой образ. Требуйте конкретные теги или дайджесты.
  • Образ по дайджесту. image: app@sha256:abc... привязывает под к точному содержимому — подменить его нельзя.
  • Доверенные реестры. Политика, разрешающая только registry.company.io/*, отсекает образы из публичного интернета.
  • Подписи образов. Инструменты вроде Cosign подписывают образы, а admission-политика (например, в Kyverno) проверяет подпись и не пускает неподписанный образ.
  • Сканирование уязвимостей. Сканеры (Trivy, Grype) ищут известные CVE в слоях образа; запускайте их в CI и блокируйте сборку при критических находках.

Проверку подписи в Kyverno описывают отдельным правилом verifyImages:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: check-image-signature
spec:
  validationFailureAction: Enforce
  rules:
    - name: verify-signature
      match:
        any:
          - resources:
              kinds: ["Pod"]
      verifyImages:
        - imageReferences:
            - "registry.company.io/*"
          attestors:
            - entries:
                - keys:
                    publicKeys: |-
                      -----BEGIN PUBLIC KEY-----
                      ...
                      -----END PUBLIC KEY-----

Как это работает под капотом

Policy-движок регистрируется в API-сервере как ValidatingWebhookConfiguration (а Kyverno при мутации — ещё и MutatingWebhookConfiguration). Когда приходит запрос на создание пода, API-сервер после RBAC отправляет объект на webhook движка; тот возвращает allowed: true/false и при отказе — сообщение. В режиме Enforce отказ webhook'а означает, что объект не будет записан в etcd и kubectl apply вернёт ошибку. Важная деталь: для каждого webhook задаётся failurePolicyFail (если движок недоступен, запрос отклоняется) или Ignore (пропускается); для security-политик выбирают Fail, чтобы падение движка не открывало лазейку.

Частые ошибки

  • Сразу Enforce без обкатки. Жёсткая политика на работающий кластер может разом заблокировать все деплои; начинайте с режима Audit/warn.
  • failurePolicy: Ignore для security-правил. Если движок упадёт, проверки молча пропадут — для безопасности нужен Fail.
  • Запрет latest без альтернативы. Команды должны заранее перейти на версионные теги, иначе политика ломает их пайплайны.
  • Сканирование «для галочки». Отчёт сканера без блокировки в CI ничего не предотвращает — настройте порог критичности.
  • Доверие к подписи без проверки реестра. Подпись бессмысленна, если разрешён любой реестр; комбинируйте verifyImages с ограничением источников.

Итоги

  • Admission-контроль — последний рубеж: он решает, что вообще допустимо создать в кластере, после RBAC и до записи в etcd.
  • Mutating-контроллеры дополняют объект, validating — принимают или отклоняют; Pod Security Admission и policy-движки работают на этом этапе.
  • OPA/Gatekeeper (язык Rego) и Kyverno (политики как YAML) задают свои правила: запрет latest, привилегий, чужих реестров.
  • Безопасность образов: конкретные теги или дайджесты, доверенные реестры, подписи (Cosign + verifyImages), сканирование на CVE.
  • Включайте политики через Audit, ставьте failurePolicy: Fail для security-правил и блокируйте сборку при критических уязвимостях.
Проверьте себя
1. В какой момент работает admission controller относительно остального конвейера API-сервера?
AДо аутентификации, самым первым
BПосле аутентификации и авторизации (RBAC), но до записи объекта в etcd
CПосле записи объекта в etcd, при чтении
DТолько при удалении объектов
2. Чем validating admission controller отличается от mutating?
AValidating изменяет объект, mutating только проверяет
BMutating может изменить объект до записи, validating принимает или отклоняет его
CОни идентичны и взаимозаменяемы
DValidating работает только с секретами
3. Почему опасно ставить failurePolicy: Ignore для admission-webhook с политиками безопасности?
AIgnore замедляет работу API-сервера
BЕсли policy-движок станет недоступен, проверки молча пропустят любой объект — лазейка в обход политики
CIgnore запрещает создание любых подов
DIgnore автоматически включает режим Enforce
4. Почему для образов рекомендуют конкретные теги или дайджесты вместо :latest?
Alatest всегда содержит вирусы
Blatest непредсказуем: один и тот же манифест может запустить другой образ, что мешает воспроизводимости и контролю
CKubernetes не умеет загружать образ с тегом latest
Dlatest шифрует образ и замедляет запуск