← 19: kube-apiserver | 21: Scheduler →
Три независимых checkpoint
Между TLS handshake и записью в etcd запрос проходит три отдельных checkpoint. Их легко перепутать в одну «проверку доступа», но это три разных вопроса с тремя разными failure modes — и на собеседовании ценят именно умение их разделять.
Ментальная модель: три двери в клуб
- Authentication — вышибала на входе. Вопрос: «кто ты?» Проверяет документ (сертификат, токен). Нет документа или он поддельный — 401, дальше не пускают. Вышибале всё равно, что вы собираетесь делать внутри; его дело — установить личность.
- Authorization — список гостей. Вопрос: «тебе-то сюда можно?» Личность уже известна; проверяют, есть ли у неё право на это конкретное действие (verb + resource + namespace). Нет в списке — 403.
- Admission — фейс/дрес-контроль у самой двери зала. Вопрос: «а этот конкретный объект мы вообще примем?» Личность подтверждена, право есть — но смотрят уже на содержимое заявки: правильный ли securityContext, не превышена ли квота, есть ли обязательные labels. И, в отличие от первых двух, admission может поправить заявку (дорисовать defaults, внедрить sidecar), а не только развернуть.
Request
│
├─ Authentication: кто ты? → 401 Unauthorized
│
├─ Authorization: можно ли тебе? → 403 Forbidden
│
└─ Admission: можно ли этот объект? → reject (обычно 403 с деталями)
Порядок важен и логичен: сначала выясняем личность, потом права, и только потом тратим ресурсы на разбор содержимого. Проверять securityContext у анонима, которого всё равно развернёт вышибала, — бессмысленно.
Ключевое отличие admission: он вызывается только для mutating операций (CREATE, UPDATE, DELETE, CONNECT) — там, где есть объект, который можно менять или отклонять. GET, LIST, WATCH проходят только authn + authz: читать нечего проверять на содержимое, объект уже существует.
source: kubernetes.io/docs/reference/access-authn-authz/controlling-access/
Authentication: кто ты?
Authentication определяет identity запроса. API server проверяет credentials и извлекает username, groups и extra fields. Механизмы проверяются по цепочке — первый успешный побеждает.
source: kubernetes.io/docs/reference/access-authn-authz/authentication/
Механизмы аутентификации
| Механизм | Как работает | Типичный use case |
|---|---|---|
| X.509 client cert | cert в TLS handshake, CN = username, O = group | kubelet → API, admin kubeconfig |
| ServiceAccount JWT | projected volume token, подписан API server | Pod → API server |
| OIDC | JWT от внешнего IdP (Google, Keycloak, Dex) | пользователи через kubectl + oidc-login |
| Webhook token review | API server вызывает внешний webhook | кастомная identity integration |
| Bootstrap token | короткоживущий token для начальной настройки | kubeadm join |
Результат аутентификации
Успех:
username: "system:serviceaccount:default:my-sa"
groups: ["system:serviceaccounts", "system:authenticated"]
extra: {}
Провал:
→ 401 Unauthorized (запрос дальше не идёт)
Эта тройка (username, groups, extra) передаётся как userInfo в authorization и admission.
Failure modes
| Сценарий | Результат |
|---|---|
| Expired token / cert | 401 |
| OIDC provider недоступен | 401 для новых токенов (кэшированные могут работать) |
| Webhook token review timeout | 401 |
| Нет credentials в запросе | запрос как system:anonymous (если anonymous auth включён) |
Authorization: можно ли тебе это действие?
Authorization проверяет, имеет ли authenticated user право выполнить конкретное действие над конкретным ресурсом. Модули проверяются по цепочке — первый однозначный ответ побеждает.
source: kubernetes.io/docs/reference/access-authn-authz/authorization/
Модули авторизации
| Модуль | Как работает | Где используется |
|---|---|---|
| RBAC | Role/ClusterRole + RoleBinding/ClusterRoleBinding | стандарт для всех кластеров |
| Node | специальный авторизатор для kubelet | ограничивает kubelet доступом к своим pod-ам и secrets |
| Webhook | внешний сервис принимает решение | managed Kubernetes (EKS, GKE) |
| ABAC | файл-based правила | устаревший, не для production |
RBAC: основной механизм
RBAC оперирует четырьмя объектами, и они разбиваются на две пары: что можно (Role / ClusterRole — набор разрешений) и кому это дано (RoleBinding / ClusterRoleBinding — привязка набора к субъекту). Разрешение само по себе ничего не делает, пока его не привяжешь binding-ом.
ЧТО МОЖНО (правила) КОМУ ДАНО (привязка)
Role (namespace-scoped) ──► RoleBinding
ClusterRole (cluster-wide) ──► ClusterRoleBinding
Разделение «правила отдельно, привязка отдельно» — не бюрократия: одну ClusterRole (например, «читать Pod-ы») можно переиспользовать в десятках namespace-ов через отдельные RoleBinding, не копируя правила.
Здесь любимый подвох собеседований — четыре комбинации, а не две. Binding может ссылаться на роль другого «масштаба»:
| Role kind | Binding kind | Что получается |
|---|---|---|
| Role | RoleBinding | разрешения в одном namespace (самый частый случай) |
| ClusterRole | ClusterRoleBinding | разрешения во всём кластере (например, cluster-admin) |
| ClusterRole | RoleBinding | переиспользуем общий набор правил, но ограничиваем его одним namespace — правила из ClusterRole действуют только там, где висит этот RoleBinding |
| Role | ClusterRoleBinding | невозможно — namespaced Role нельзя привязать кластерно |
Третья строка — самая полезная на практике: определяешь ClusterRole один раз (скажем, pod-reader), а даёшь её команде только в их namespace через RoleBinding. Правила общие, scope узкий.
Пример Role и RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: app-ns
name: pod-manager
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: app-ns
name: developer-pod-manager
subjects:
- kind: User
name: developer
roleRef:
kind: Role
name: pod-manager
apiGroup: rbac.authorization.k8s.io
RBAC decision flow
Запрос: user=developer, verb=create, resource=pods, namespace=app-ns
1. Найти все RoleBindings в app-ns, где subject = developer или его group
2. Для каждого binding: проверить linked Role
→ есть rule с verb=create + resource=pods?
3. Найти все ClusterRoleBindings, где subject = developer или его group
4. Для каждого binding: проверить linked ClusterRole
Хотя бы одно совпадение → Allow
Ни одного совпадения → Deny (403 Forbidden)
RBAC — additive only
RBAC работает только на разрешение. Нет explicit deny. Если одна Role разрешает delete pods, другая Role не может это запретить. Единственный способ ограничить — не давать разрешение.
kubectl auth can-i — что проверяет и чего НЕ проверяет
# Проверка authorization layer
kubectl auth can-i create pods -n app-ns
# yes
# Эта команда проверяет authorization layer.
# Она НЕ проверяет:
# - Admission webhooks
# - ResourceQuota
# - LimitRange
# - PodSecurity
# - Custom webhook policies
# "yes" НЕ гарантирует, что create pod пройдёт!
# Проверить все permissions пользователя в namespace
kubectl auth can-i --list -n app-ns
# Проверить от имени другого пользователя (impersonation)
kubectl auth can-i create pods -n app-ns --as=developer
# Проверить от имени ServiceAccount
kubectl auth can-i get secrets -n app-ns \
--as=system:serviceaccount:app-ns:my-sa
source: kubernetes.io/docs/reference/access-authn-authz/rbac/
Admission: можно ли принять этот конкретный объект?
Admission — третий и самый сложный checkpoint. В отличие от authn и authz, admission вызывается только для mutating операций и может изменить объект.
Две фазы: mutating → validating
Admission control проходит в двух фазах: сначала все mutating контроллеры, затем все validating. Если любой контроллер в любой из фаз отклоняет запрос — весь запрос отклоняется немедленно, дальнейшая обработка не выполняется.
Mutating Admission (фаза 1)
│ встроенные controllers (LimitRanger, ServiceAccount, ...)
│ + MutatingAdmissionPolicy (CEL, in-process)
│ + внешние mutating webhooks
│ → могут изменить объект
│
▼
Object Schema Validation
│ проверка структуры объекта после mutations
│
▼
Validating Admission (фаза 2)
│ встроенные controllers (ResourceQuota, PodSecurity, ...)
│ + ValidatingAdmissionPolicy (CEL, in-process)
│ + внешние validating webhooks
│ → только accept/reject, без изменений
│
▼
Persist to etcd
Mutating идёт первым, чтобы validating webhooks видели объект после всех mutations.
Reinvocation — и зачем он. Mutating webhooks вызываются по очереди, и каждый видит объект в том виде, в каком его оставил предыдущий. Проблема: webhook, отработавший раньше, не видел изменений того, кто отработал позже. Классический пример: webhook A навешивает политику «на каждый контейнер — sidecar с ресурсными лимитами». Webhook B (Istio) после него внедряет свой sidecar-контейнер. Теперь у нового контейнера нет лимитов — A его уже не видел.
Решение — reinvocation: после того как какой-то webhook что-то поменял, все ранее вызванные mutating webhooks могут быть вызваны повторно, чтобы каждый увидел финальную картину. Webhook включает это через reinvocationPolicy (Never — дефолт | IfNeeded). Прямое следствие: mutating webhook обязан быть идемпотентным — повторный вызов на уже обработанном объекте не должен добавлять второй sidecar или ломать результат. Практическое правило: «прежде чем добавить — проверь, не добавлено ли уже».
source: kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
Встроенные admission controllers
| Controller | Что делает | Фаза |
|---|---|---|
| NamespaceLifecycle | блокирует создание объектов в terminating namespace, защищает system namespaces от удаления | Validating |
| LimitRanger | применяет default limits/requests из LimitRange | Mutating |
| ResourceQuota | проверяет, что namespace quota не превышена | Validating |
| ServiceAccount | auto-mount SA token, создаёт default SA | Mutating |
| DefaultStorageClass | ставит default StorageClass для PVC без указанного class | Mutating |
| DefaultIngressClass | ставит default IngressClass для Ingress без указанного class | Mutating |
| PodSecurity | enforce Pod Security Standards (restricted/baseline/privileged) | Validating |
| CertificateApproval / CertificateSigning | авторизуют approve/sign для CSR | Validating |
| RuntimeClass | подставляет overhead pod из RuntimeClass | Mutating + Validating |
| MutatingAdmissionWebhook / ValidatingAdmissionWebhook | вызывают внешние webhook (dynamic admission) | Mutating / Validating |
Встроенные controllers скомпилированы в бинарь kube-apiserver, работают in-process — без сетевых вызовов, без external dependency. Их порядок определяется реализацией API server, а не порядком в флаге --enable-admission-plugins; сам флаг лишь включает/отключает плагины. Один контроллер может работать в обеих фазах (например, RuntimeClass, AlwaysPullImages).
Дефолтный набор в Kubernetes 1.36: CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, LimitRanger, MutatingAdmissionWebhook, NamespaceLifecycle, PersistentVolumeClaimResize, PodSecurity, Priority, ResourceQuota, RuntimeClass, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook.
source: kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
Webhook admission: внешние HTTP-сервисы
Dynamic admission control описывается двумя объектами: MutatingWebhookConfiguration (фаза 1) и ValidatingWebhookConfiguration (фаза 2). API server вызывает внешний сервис по HTTPS с телом AdmissionReview для каждого matching запроса:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: policy-check
webhooks:
- name: validate.policy.example.com # должно быть FQDN
rules:
- apiGroups: [""]
resources: ["pods"]
operations: ["CREATE", "UPDATE"]
scope: Namespaced
clientConfig:
service:
name: policy-webhook
namespace: policy-system
path: /validate
caBundle: <base64 CA> # для проверки TLS webhook-сервера
failurePolicy: Fail # ← ключевое решение
matchPolicy: Equivalent # ловить и другие версии API той же группы
namespaceSelector: # НЕ трогать system namespaces
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "policy-system"]
objectSelector: {} # доп. фильтр по labels объекта
timeoutSeconds: 5 # 1..30, дефолт 10
sideEffects: None # None | NoneOnDryRun — иначе dry-run сломан
admissionReviewVersions: ["v1"]
Ключевые поля:
| Поле | Смысл |
|---|---|
failurePolicy | Fail (дефолт) или Ignore — что делать при ошибке/таймауте webhook |
matchPolicy | Equivalent (дефолт) ловит запросы через любые версии API группы; Exact — только точное совпадение rules |
namespaceSelector / objectSelector | сузить scope; критично для исключения system namespaces |
sideEffects | есть ли у webhook побочные эффекты вне AdmissionReview; None/NoneOnDryRun нужны для dry-run |
timeoutSeconds | 1..30, дефолт 10; держать низким против каскадных отказов |
reinvocationPolicy | только для mutating: Never (дефолт) или IfNeeded — повторный вызов после мутаций других webhook |
Для mutating webhook объект тот же (kind: MutatingWebhookConfiguration), плюс поле reinvocationPolicy. Mutating webhook обязан быть идемпотентным.
failurePolicy: Fail vs Ignore
| Значение | При ошибке webhook | Availability risk | Security risk |
|---|---|---|---|
| Fail | запрос отклоняется | высокий: webhook outage = write outage | низкий |
| Ignore | запрос проходит без проверки | низкий | высокий: policy bypass |
Каскадный отказ: deadlock через webhook
Webhook pod на node-1
│
node-1 уходит в drain
│
▼
Webhook pod нужно переместить на другой node
│
▼
Scheduler пытается создать новый Pod → API write
│
▼
API server вызывает webhook → webhook недоступен
│
failurePolicy: Fail → write rejected
│
▼
Webhook не может быть создан, потому что webhook блокирует writes
│
▼
DEADLOCK: кластер read-only
Защита:
- Webhook pods на dedicated nodes с tolerations
- Минимум 2 реплики с podAntiAffinity
namespaceSelector— исключить kube-system из scopetimeoutSecondsне больше 5s- Break-glass:
kubectl delete validatingwebhookconfiguration <name>
source: kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
ValidatingAdmissionPolicy (CEL) — GA в Kubernetes 1.30
Альтернатива validating webhooks без внешних зависимостей. API-группа admissionregistration.k8s.io/v1. Политика описывается тремя объектами: сама ValidatingAdmissionPolicy (абстрактная логика на CEL), опциональный параметр-ресурс (ConfigMap или CRD с конфигурацией) и ValidatingAdmissionPolicyBinding (привязка политики к scope и параметрам):
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-team-label
spec:
failurePolicy: Fail
paramKind: # опционально: параметризация политики
apiVersion: v1
kind: ConfigMap
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
resources: ["deployments"]
operations: ["CREATE", "UPDATE"]
validations:
- expression: "has(object.metadata.labels) && 'team' in object.metadata.labels"
message: "Deployment must have 'team' label"
reason: Invalid
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-team-label-binding
spec:
policyName: require-team-label
validationActions: [Deny] # Deny | Warn | Audit (можно комбинировать)
matchResources:
namespaceSelector:
matchLabels:
environment: prod
validationActions определяет реакцию на провал: Deny (отклонить), Warn (warning клиенту), Audit (запись в audit log). Можно комбинировать ([Warn, Audit]), но Deny и Warn вместе нельзя. CEL expressions выполняются in-process в API server: нет network call, нет external dependency, нет availability risk.
Доступные CEL-переменные: object, oldObject, request, params, namespaceObject, authorizer. Ограничение: только validation (нет mutation).
source: kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/
MutatingAdmissionPolicy (CEL) — стабильна с Kubernetes 1.36
In-process декларативная альтернатива mutating webhooks. Та же API-группа admissionregistration.k8s.io/v1, объекты MutatingAdmissionPolicy + MutatingAdmissionPolicyBinding. Мутация описывается на CEL двумя способами:
patchType: ApplyConfiguration— CEL возвращает объект черезObject{...}, применяется по стратегии server-side apply merge (нельзя менять atomic-структуры).patchType: JSONPatch— CEL возвращает массивJSONPatch{...}(RFC 6902).
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingAdmissionPolicy
metadata:
name: default-runasnonroot
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
operations: ["CREATE"]
reinvocationPolicy: IfNeeded
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{ spec: Object.spec{
securityContext: Object.spec.securityContext{ runAsNonRoot: true }
}}
Закрывает главный исторический пробел VAP — теперь и мутацию, и валидацию можно делать in-process на CEL, без webhook-инфраструктуры и без availability risk от внешнего сервиса.
source: kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/
Policy-as-code: Gatekeeper vs Kyverno vs встроенные policy
Для сложных политик по содержимому манифестов (запрет hostNetwork, registry allowlist, обязательные labels, инъекция sidecar) исторически используют внешние policy-движки поверх admission webhooks. Два основных — OPA Gatekeeper и Kyverno.
OPA Gatekeeper
Работает как validating + mutating webhook, исполняющий политики через Open Policy Agent (язык Rego). Политики описываются двумя CRD:
ConstraintTemplate— шаблон политики: Rego-логика + схема параметров. Определяет новый вид Constraint.Constraint— инстанс политики: параметры +match(какие ресурсы/namespace) +enforcementAction(deny|warn|dryrun).
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names: {kind: K8sRequiredLabels}
validation:
openAPIV3Schema:
properties:
labels: {type: array, items: {type: string}}
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg}] {
required := input.parameters.labels
provided := {k | input.review.object.metadata.labels[k]}
missing := required[_]
not provided[missing]
msg := sprintf("missing label: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels # kind из ConstraintTemplate
metadata:
name: deployments-must-have-team
spec:
enforcementAction: deny
match:
kinds:
- {apiGroups: ["apps"], kinds: ["Deployment"]}
parameters:
labels: ["team"]
Gatekeeper поддерживает audit (найти уже существующие в кластере ресурсы, нарушающие политику) и mutation через отдельные CRD: AssignMetadata, Assign, ModifySet, AssignImage.
source: open-policy-agent.github.io/gatekeeper/website/docs/
Kyverno
Kubernetes-native policy-движок: политики пишутся на YAML (+ опционально CEL), отдельного языка учить не нужно. Работает как admission controller (validating + mutating webhooks), CLI-сканер и в runtime. Правила бывают типов:
- validate — проверка (accept/reject или audit);
- mutate — изменение объекта (patch/strategic merge);
- generate — создание сопутствующих ресурсов (например, дефолтная NetworkPolicy / ResourceQuota при создании namespace);
- verifyImages — проверка подписей образов (cosign/sigstore);
- cleanup — удаление ресурсов по расписанию.
Объекты: ClusterPolicy (cluster-scoped) и Policy (namespaced) — это исходный API (теперь legacy, поле validationFailureAction переименовано в failureAction); в новых версиях предпочтительны специализированные CEL-типы ValidatingPolicy / MutatingPolicy / GeneratingPolicy / ImageValidatingPolicy.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-team-label
spec:
validationFailureAction: Enforce # Enforce | Audit
rules:
- name: check-team-label
match:
any:
- resources:
kinds: ["Deployment"]
validate:
message: "label 'team' is required"
pattern:
metadata:
labels:
team: "?*"
source: kyverno.io/docs/introduction/
Сравнение
| Критерий | ValidatingAdmissionPolicy / MutatingAdmissionPolicy | OPA Gatekeeper | Kyverno |
|---|---|---|---|
| Где выполняется | in-process в API server | внешний webhook | внешний webhook |
| Язык | CEL | Rego | YAML + CEL |
| Availability risk | нет (нет внешнего сервиса) | есть (webhook) | есть (webhook) |
| Mutation | да (MAP, 1.36+) | да (Assign*) | да (mutate) |
| Generate новых ресурсов | нет | нет | да (generate) |
| Verify image signatures | нет | нет (через сторонние) | да (verifyImages) |
| Audit существующих ресурсов | через validationAction Audit | да (audit) | да (policy reports) |
| Кривая входа | низкая для простых правил | высокая (Rego) | средняя (YAML) |
Практика: простые правила (labels, resource limits, запрет привилегий) — предпочтительно VAP/MAP на CEL, без внешних зависимостей. Сложная бизнес-логика, generate, verify images, готовые библиотеки политик — Kyverno (проще) или Gatekeeper (мощнее Rego).
Сигналы мониторинга
Authentication
| Метрика | Что показывает | Alert |
|---|---|---|
authentication_attempts | попытки authn по результату | rate(failures) sustained → investigate |
authentication_duration_seconds | latency authn | P99 > 1s → OIDC/webhook problem |
Authorization
| Метрика | Что показывает | Alert |
|---|---|---|
authorization_attempts_total | решения authz по результату | rate(denied) spike → RBAC misconfiguration |
authorization_duration_seconds | latency authz | P99 spike → webhook authorizer problem |
Admission
| Метрика | Что показывает | Alert |
|---|---|---|
apiserver_admission_controller_admission_duration_seconds | latency admission | P99 > 1s → slow webhook |
apiserver_admission_webhook_rejection_count | отклонения по webhook | spike → policy change или misconfiguration |
apiserver_admission_webhook_fail_open_count | bypass при failurePolicy: Ignore | 0 → webhook degraded, policy не enforce-ится |
Когда использовать
RBAC:
- Всегда. RBAC API стабилизировался (GA
v1) в Kubernetes 1.8. Важный нюанс: сам kube-apiserver по умолчанию использует--authorization-mode=AlwaysAllow— RBAC включает дистрибутив/провижининг (kubeadm, EKS/GKE/AKS ставят--authorization-mode=Node,RBAC). На managed-кластерах он включён из коробки; отключать нет причин. - Для сервисных аккаунтов: минимально необходимые permissions, не cluster-admin по умолчанию.
- ClusterRole используйте только когда реально нужен cluster-wide доступ.
Webhook admission:
- Когда нужна логика, которую нельзя выразить в CEL (внешние вызовы, сложные условия, generate ресурсов, verify image signatures).
- OPA/Gatekeeper, Kyverno — типичные use cases.
ValidatingAdmissionPolicy / MutatingAdmissionPolicy (CEL):
- Предпочтительно для новых policy: VAP GA с 1.30, MAP стабильна с 1.36. Нет availability risk от внешнего webhook.
- Простые правила: проверка/установка labels, annotations, resource limits, дефолтов securityContext.
- Не подходит для: внешних lookups, generate новых ресурсов, сложной бизнес-логики.
OIDC:
- Когда нужен централизованный IdP для пользователей (Keycloak, Dex, Google Workspace).
- Позволяет делать kubectl через SSO без распространения сертификатов.
Типичные ошибки
1. Путать kubectl auth can-i с реальной проверкой
can-i проверяет authorization layer через SelfSubjectAccessReview, но не admission. ResourceQuota, LimitRange, webhook policies и PodSecurity не проверяются. Можно получить yes и всё равно получить rejection при apply.
2. Широкие ClusterRoleBindings
cluster-admin для ServiceAccount оператора — типичная ошибка. Оператор нужен доступ к конкретным ресурсам, не ко всему кластеру. Используйте минимальные Roles.
3. Один инстанс webhook с failurePolicy: Fail
Гарантированный путь к deadlock при рестарте webhook. Минимум 2 реплики + podAntiAffinity + namespaceSelector, исключающий system namespaces.
4. Не контролировать apiserver_admission_webhook_fail_open_count
Если failurePolicy: Ignore и webhook деградировал — он молча пропускает все запросы без проверки. Метрика fail_open_count > 0 означает, что политика не работает.
5. ServiceAccount tokens без expiry
Здесь часто путают две разные вехи. С 1.22 (GA BoundServiceAccountTokenVolume) Pod по умолчанию получает projected token: короткоживущий (по умолчанию 1 час), привязанный к Pod и автоматически ротируемый kubelet-ом. С 1.24 (GA в 1.26) Kubernetes перестал автоматически создавать старые вечные Secret-based токены для каждого ServiceAccount. Итог: новые workloads давно живут на ротируемых токенах; сломаться могут старые, которые полагаются на вечный токен из Secret или читают /var/run/secrets/kubernetes.io/serviceaccount/token в обход projected-механизма.
Альтернативы
Вместо webhook-based authorization: ValidatingAdmissionPolicy (CEL) для stateless validation без external dependency. OPA Gatekeeper и Kyverno используют webhooks, но дают policy-as-code абстракцию (Kyverno проще, Gatekeeper мощнее).
Вместо X.509 cert для пользователей: OIDC + oidc-login plugin для kubectl — централизованный revocation, MFA, группы из IdP, без ручной ротации сертификатов.
Вместо ручного RBAC: GitOps (ArgoCD + RBAC manifests в git) или rbac-manager (operator) для управления RoleBindings.
source: kubernetes.io/docs/concepts/security/rbac-good-practices/