← Back to notes

Authentication, Authorization и
Admission в Kubernetes


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 certcert в TLS handshake, CN = username, O = groupkubelet → API, admin kubeconfig
ServiceAccount JWTprojected volume token, подписан API serverPod → API server
OIDCJWT от внешнего IdP (Google, Keycloak, Dex)пользователи через kubectl + oidc-login
Webhook token reviewAPI 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 / cert401
OIDC provider недоступен401 для новых токенов (кэшированные могут работать)
Webhook token review timeout401
Нет credentials в запросезапрос как system:anonymous (если anonymous auth включён)

Authorization: можно ли тебе это действие?

Authorization проверяет, имеет ли authenticated user право выполнить конкретное действие над конкретным ресурсом. Модули проверяются по цепочке — первый однозначный ответ побеждает.

source: kubernetes.io/docs/reference/access-authn-authz/authorization/

Модули авторизации

МодульКак работаетГде используется
RBACRole/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 kindBinding kindЧто получается
RoleRoleBindingразрешения в одном namespace (самый частый случай)
ClusterRoleClusterRoleBindingразрешения во всём кластере (например, cluster-admin)
ClusterRoleRoleBindingпереиспользуем общий набор правил, но ограничиваем его одним namespace — правила из ClusterRole действуют только там, где висит этот RoleBinding
RoleClusterRoleBindingневозможно — 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 из LimitRangeMutating
ResourceQuotaпроверяет, что namespace quota не превышенаValidating
ServiceAccountauto-mount SA token, создаёт default SAMutating
DefaultStorageClassставит default StorageClass для PVC без указанного classMutating
DefaultIngressClassставит default IngressClass для Ingress без указанного classMutating
PodSecurityenforce Pod Security Standards (restricted/baseline/privileged)Validating
CertificateApproval / CertificateSigningавторизуют approve/sign для CSRValidating
RuntimeClassподставляет overhead pod из RuntimeClassMutating + 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"]

Ключевые поля:

ПолеСмысл
failurePolicyFail (дефолт) или Ignore — что делать при ошибке/таймауте webhook
matchPolicyEquivalent (дефолт) ловит запросы через любые версии API группы; Exact — только точное совпадение rules
namespaceSelector / objectSelectorсузить scope; критично для исключения system namespaces
sideEffectsесть ли у webhook побочные эффекты вне AdmissionReview; None/NoneOnDryRun нужны для dry-run
timeoutSeconds1..30, дефолт 10; держать низким против каскадных отказов
reinvocationPolicyтолько для mutating: Never (дефолт) или IfNeeded — повторный вызов после мутаций других webhook

Для mutating webhook объект тот же (kind: MutatingWebhookConfiguration), плюс поле reinvocationPolicy. Mutating webhook обязан быть идемпотентным.

failurePolicy: Fail vs Ignore

ЗначениеПри ошибке webhookAvailability riskSecurity 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

Защита:

  1. Webhook pods на dedicated nodes с tolerations
  2. Минимум 2 реплики с podAntiAffinity
  3. namespaceSelector — исключить kube-system из scope
  4. timeoutSeconds не больше 5s
  5. 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 / MutatingAdmissionPolicyOPA GatekeeperKyverno
Где выполняетсяin-process в API serverвнешний webhookвнешний webhook
ЯзыкCELRegoYAML + 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_secondslatency authnP99 > 1s → OIDC/webhook problem

Authorization

МетрикаЧто показываетAlert
authorization_attempts_totalрешения authz по результатуrate(denied) spike → RBAC misconfiguration
authorization_duration_secondslatency authzP99 spike → webhook authorizer problem

Admission

МетрикаЧто показываетAlert
apiserver_admission_controller_admission_duration_secondslatency admissionP99 > 1s → slow webhook
apiserver_admission_webhook_rejection_countотклонения по webhookspike → policy change или misconfiguration
apiserver_admission_webhook_fail_open_countbypass при 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/


19: kube-apiserver | 21: Scheduler

Authentication, Authorization и Admission в Kubernetes | Aleksandr Suprun