Аудит манифеста

Статья про Kyverno держится на одной мысли: RBAC решает, кому можно создать Pod, и внутрь него не заглядывает, поэтому содержимое манифеста судят позже, на admission. Здесь те же проверки выполняются прямо на странице, так что вердикт видно без кластера.

Ничего не уходит с этой страницы. Cmd или Ctrl с Enter тоже запускает.

Как этим пользоваться

Вставь один документ или целый файл из нескольких, разделённых ---. Каждый документ проверяется отдельно и получает свою карточку с видом и именем, поэтому набор из двадцати ресурсов читается как двадцать вердиктов, а не как одна куча. Кнопка рядом загружает намеренно плохой пример, если хочется сначала посмотреть на вывод, а уже потом доверять ему своё. Cmd или Ctrl с Enter запускают проверку с клавиатуры.

Группы читаются сверху вниз, потому что упорядочены по жёсткости отказа. Первая это прямой отказ API server. Средние это политика: вторая и третья то, что применяет PodSecurity, четвёртая это реальные права, которые ни один профиль не остановит. Последняя группа моё мнение, и она так и подписана, так что её можно пропустить, ничего важного для кластера не потеряв.

Никуда ничего не отправляется. Разбор, схема и все правила работают внутри этой страницы, поэтому манифест не покидает браузер, и здесь просто нет сервера, который мог бы его записать. При этом Secret остаётся секретом: эта страница его не видит, а вот твой буфер обмена и экран видят.

Что он понимает

Поды и шесть видов, которые несут в себе шаблон пода, то есть Deployment, StatefulSet, DaemonSet, ReplicaSet, Job и CronJob, проходят через все слои. Role, ClusterRole, RoleBinding, ClusterRoleBinding, Service, Ingress, NetworkPolicy, ServiceAccount и PersistentVolume проходят схему и проверки доступа. Любой другой встроенный вид всё равно сверяется со схемой. Ресурс из CRD, политика Kyverno или приложение Argo, пропускается, а не угадывается, потому что его форма живёт в кластере, а не в том, что доступно этой странице.

Что именно проверяется

Сначала то, примет ли API server такую форму вообще. Полная схема OpenAPI из Kubernetes 1.37 зашита прямо в страницу, поэтому опечатка в имени поля, значение не того типа, пропущенное обязательное поле или вид, которого нет в этой apiVersion, ловятся здесь ровно так же, как их поймал бы kubectl apply. Это тот слой, который отклоняет манифест ещё до того, как на него посмотрит Pod Security.

Поверх схемы идут перекрёстные правила, которые API server проверяет кодом и которые никакая схема выразить не может: селектор, не совпадающий с собственным шаблоном пода, nodePort у сервиса типа ClusterIP, расписание cron не из пяти полей, privileged вместе с allowPrivilegeEscalation: false, незамаскированный /proc без hostUsers: false и порт узла, не равный порту контейнера при hostNetwork. Каждое из них подтверждено настоящим API server, а не взято из документации.

Дальше правила из двух профилей Pod Security Standards, которые идут в комплекте с Kubernetes и которые применяет PodSecurity, когда ты вешаешь лейбл на неймспейс. Baseline закрывает известные побеги: привилегированные контейнеры, хостовые пространства имён, тома hostPath, хостовые порты, добавленные возможности сверх небольшого набора, небезопасные sysctl, отключённые seccomp и AppArmor, подмену SELinux, незамаскированный /proc, пробы в сторону хоста и Windows HostProcess. Restricted добавляет сверху закалку: сбросить все возможности, запретить эскалацию привилегий, работать не от root и не с нулевым UID, явно задать профиль seccomp и уложиться в короткий список разрешённых типов томов.

Pod Security покрывает только поды, поэтому всё остальное в манифесте проходит через второй набор проверок, собранный в группу опасно по умолчанию. Role и ClusterRole читаются на подстановочные знаки, на действия escalate, bind и impersonate, на чтение секретов и на pods/exec с nodes/proxy. Привязки проверяются на cluster-admin и на субъектов уровня system:authenticated. Service отмечается, когда публикует себя наружу или присваивает произвольные внешние адреса. Ingress отмечается, когда идёт без TLS, NetworkPolicy когда выбирает всё и разрешает всё, PersistentVolume когда лежит на пути узла.

Последняя группа это советы, и она намеренно субъективная: непринкреплённый тег, отсутствие requests и limits, отсутствие проб, доступная на запись корневая файловая система, сервисный аккаунт по умолчанию, примонтированный токен, которым никто не пользуется, единственная реплика. Ничто из этого не является правилом Pod Security, чинить стоит всё, а выдавать это за часть стандарта было бы ровно той мелкой ложью, которой этот сайт старается избегать.

Откуда взяты правила

Схема сгенерирована прямо из спецификации OpenAPI, которая идёт в комплекте с Kubernetes, закреплена на релизе 1.37, все 862 определения. Правила Pod Security написаны по девятнадцати проверкам в исходниках pod-security-admission, а не по их пересказу, и именно так сюда не попало распространённое заблуждение, будто readOnlyRootFilesystem входит в профиль restricted. Перекрёстные правила вообще нигде не вычитаны: каждое найдено тем, что манифест скармливался работающему API server, а ответ читался.

Дальше всё это сверяется с настоящим кластером, причём не на случаях, которые я придумал сам: такие покрывают лишь то, о чём я уже догадывался. Генератор берёт десять исправных манифестов и ломает их систематически: удаляет по очереди каждое поле, подменяет тип каждого поля, дописывает несуществующие и отдельно применяет двадцать два нарушения безопасности. Все 464 получившихся манифеста по двадцати видам ресурсов идут и через эту страницу, и через API server Kubernetes 1.37 в режиме проверки без применения, и любое расхождение это ошибка здесь. Сейчас расхождений нет ни одного.

При первом же запуске он нашёл немало: шесть входных данных, на которых страница падала, тридцать девять правил, которые API server применяет кодом и которых здесь не было, и три проверки, срабатывавшие на манифестах, которые кластер принимает. Отдельно триста одиннадцать живых объектов, вынутых из работающего кластера, не дают ни одной претензии по схеме, и это самое важное число: инструмент, который поднимает ложную тревогу на рабочих манифестах, хуже отсутствующего.

Как документы связаны между собой

Файл обычно это набор, поэтому документы в нём сверяются и друг с другом. Сервис, чей селектор не совпадает ни с одним шаблоном пода в файле, Ingress, ведущий на отсутствующий сервис, под, который просит сервисный аккаунт или монтирует ConfigMap, которых никто не описал, два объекта одного вида с одинаковым именем в одном неймспейсе: обычные ошибки, которых не видно ни в одном документе по отдельности. Всё это идёт как совет и сформулировано честно, «нет в этом файле», а не «не существует», потому что файл часто лишь часть большего набора.

Чего он не делает

Он читает манифест, а не кластер. Он не видит лейбл неймспейса, который решает, какой профиль применяется, не видит мутирующие вебхуки, которые перепишут объект до валидации, и не знает про политики, написанные тобой в Kyverno или Gatekeeper. Чистый результат здесь означает, что у двух встроенных профилей нет возражений, а не что твой кластер это примет.

Если нужен настоящий вердикт, в статье показано, как получить его минут за десять, а готовый кластер убирает даже эту подготовку.