применять правила соответствия на каждом запросе к API Kubernetes

Policy as Code в Kubernetes: соответствие требованиям и безопасность через OPA Gatekeeper и Kyverno

14 минут

Страница в вики не отклонит плохой манифест. Policy as Code превращает правила безопасности и соответствия в проверки при admission, которые хранятся в Git и срабатывают при каждом создании и обновлении ресурса — до попадания в продакшен.

Почему записанные правила не работают без admission

В продакшен-кластере накапливаются требования, которые легко сформулировать и трудно применять единообразно: контейнеры не от root, образы только из доверенных реестров, у каждого пода заданы requests, обязательные метки для учёта затрат, сеть по умолчанию «запретить всё», секреты не в ConfigMap. Правила живут в вики и чатах, но документация не отклоняет манифест. Helm-чарт с root, чужим реестром и без лимитов всё равно применится — пока об этом не вспомнят на разборе инцидента. Ручная проверка каждого изменения не масштабируется. Самописные admission-вебхуки без тестов и владельца быстро устаревают. Policy as Code хранит правила в Git, проверяет каждый CREATE и UPDATE на API-сервере через validating и mutating admission, и даёт платформенной команде профилактику вместо разбора «почему так выкатили» после инцидента.

OPA Gatekeeper и Kyverno: два движка, один путь admission

Оба инструмента стоят в цепочке admission: оценивают входящий ресурс, затем принимают, отклоняют или изменяют его до записи в etcd, и умеют проверять уже существующие объекты в режиме только отчёта. OPA Gatekeeper оборачивает Open Policy Agent и язык Rego — удобен для сложной логики, внешних данных и компаний, где OPA уже стандарт вне Kubernetes. Политики оформляются как ConstraintTemplate и экземпляры Constraint с параметрами. Kyverno описывает правила на YAML — знакомые match, validate, mutate, generate и verifyImages без нового языка; мутация «из коробки» богаче, чем у Gatekeeper. У Gatekeeper мутация уже — в основном через Assign и ModifySet; для дефолтов в подах чаще хватает Kyverno. Gatekeeper — graduated-проект CNCF с большой библиотекой; Kyverno — incubating, но растёт быстро. Берите Kyverno, если команда хочет только YAML и встроенную мутацию. Берите Gatekeeper, если логика compliance тянется на несколько систем или нужна выразительность Rego. На один тип правил лучше один движок — иначе вебхуки спорят друг с другом.

Bash · установка Kyverno и Gatekeeper через Helm
helm repo add kyverno https://kyverno.github.io/kyverno/
helm upgrade --install kyverno kyverno/kyverno \
  --namespace kyverno --create-namespace

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm upgrade --install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system --create-namespace \
  --set enableExternalData=false

Kyverno: запрет root и автоматические настройки безопасности

Надёжнее запрещать любой контейнер или initContainer с runAsUser равным нулю через deny, чем жёсткий pattern, который пропускает неполный securityContext. Перед режимом Enforce по всему кластеру проверьте отклонение и принятие тестовых подов через kubectl. Мутация может проставить runAsNonRoot, сброс capabilities и readOnlyRootFilesystem, если разработчик ничего не указал — но рядом держите validate, чтобы нельзя было вернуть привилегированный режим. Исключите kube-system и пространство имён самого Kyverno из жёстких правил.

YAML · Kyverno ClusterPolicy: запрет UID root
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-root-containers
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: disallow-root-uid
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
      validate:
        message: "Containers must not run as root (UID 0)."
        deny:
          conditions:
            any:
              - key: "{{ request.object.spec.containers[?securityContext.runAsUser == `0`] | length(@) }}"
                operator: GreaterThan
                value: 0
              - key: "{{ request.object.spec.initContainers[?securityContext.runAsUser == `0`] | length(@) }}"
                operator: GreaterThan
                value: 0
Bash · проверка отклонения и принятия
# Rejected — runs as root
kubectl run nginx-root --image=nginx:1.27 --dry-run=server -o yaml \
  --overrides='{"spec":{"containers":[{"name":"nginx-root","image":"nginx:1.27","securityContext":{"runAsUser":0}}]}}' \
  | kubectl apply -f -

# Accepted — non-root UID
kubectl run nginx-safe --image=nginx:1.27 --dry-run=server -o yaml \
  --overrides='{"spec":{"containers":[{"name":"nginx-safe","image":"nginx:1.27","securityContext":{"runAsNonRoot":true,"runAsUser":1000}}]}}' \
  | kubectl apply -f -
YAML · мутация Kyverno: securityContext по умолчанию
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-security-context
spec:
  rules:
    - name: add-run-as-non-root
      match:
        any:
          - resources:
              kinds: [Pod]
      mutate:
        patchStrategicMerge:
          spec:
            securityContext:
              +(runAsNonRoot): true
              +(runAsUser): 1000
            containers:
              - (name): "*"
                securityContext:
                  +(allowPrivilegeEscalation): false
                  +(readOnlyRootFilesystem): true
                  +(capabilities):
                    drop:
                      - ALL

OPA Gatekeeper: только доверенные реестры образов

Логику на Rego описываете один раз в ConstraintTemplate, параметры — в Constraint под каждое окружение. Сначала подключите production и staging. Сравнивайте префиксы реестра через startswith и не забывайте initContainers в шаблоне. Режим dryrun даёт аудит без блокировки — исправьте манифесты и Helm в CI, затем смените enforcementAction на deny.

YAML · Gatekeeper ConstraintTemplate для реестров
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sallowedregistries
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRegistries
      validation:
        openAPIV3Schema:
          type: object
          properties:
            registries:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sallowedregistries

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          not image_allowed(container.image)
          msg := sprintf("container '%v' image '%v' not in allowed registries", [container.name, container.image])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          not image_allowed(container.image)
          msg := sprintf("initContainer '%v' image '%v' not in allowed registries", [container.name, container.image])
        }

        image_allowed(image) {
          prefix := input.parameters.registries[_]
          startswith(image, prefix)
        }
YAML · экземпляр Constraint Gatekeeper
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: allow-only-trusted-registries
spec:
  enforcementAction: dryrun
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces:
      - production
      - staging
  parameters:
    registries:
      - "registry.company.com/"
      - "gcr.io/my-project/"
      - "docker.io/library/"

Аудит существующих workload перед режимом блокировки

Не включайте deny в продакшене в первый же день. У Kyverno — validationFailureAction Audit, у Gatekeeper — enforcementAction dryrun: нарушения видны, создание не блокируется. Kyverno пишет PolicyReport и ClusterPolicyReport; Gatekeeper показывает violations в status Constraint и через audit sync. Соберите отчёты за спринт, поправьте манифесты и чарты в CI, потом переводите политики в Enforce. Обязательно исключите kube-system, cert-manager и namespace контроллеров политик.

YAML · настройки только для аудита
# Kyverno
spec:
  validationFailureAction: Audit

# Gatekeeper Constraint
spec:
  enforcementAction: dryrun
Bash · просмотр отчётов о нарушениях
# Kyverno policy reports
kubectl get policyreport,clusterpolicyreport -A

# Gatekeeper constraint violations
kubectl get k8sallowedregistries.constraints.gatekeeper.sh \
  allow-only-trusted-registries \
  -o jsonpath='{range .status.violations[*]}{.namespace}/{.name}: {.message}{"\n"}{end}'

Практика: GitOps, тесты в CI, уровни критичности и мониторинг

Храните политики в Git рядом с конфигурацией кластера и синхронизируйте через Argo CD или Flux — с тем же ревью, что и код приложений. В CI гоняйте kyverno test и gator verify до merge. Делите правила по критичности: сразу блокируйте root, privileged и чужие реестры; после аудита добавляйте лимиты ресурсов и обязательные метки; соглашения об именах оставьте в режиме отчётов. На старте хватит пяти–десяти политик — иначе разработчики упрются в трение. Исключайте DaemonSet и системные метки вроде kube-dns из широких правил для подов. Рост числа отказов Kyverno или нарушений Gatekeeper в аудите оповещайте так же, как скачок ошибок. Policy as Code смыкает требования на бумаге с тем, что API-сервер реально пропускает: начните с аудита, версионируйте всё, дорабатывайте каждую неделю.

YAML · Argo CD Application для политик кластера
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: cluster-policies
  namespace: argocd
spec:
  project: platform
  source:
    repoURL: https://github.com/company/k8s-policies.git
    targetRevision: main
    path: policies/
  destination:
    server: https://kubernetes.default.svc
    namespace: kyverno
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
Bash · тесты политик в CI
# Kyverno policy tests
kyverno test ./policy-tests/ --detailed-results

# Gatekeeper manifest verification
gator verify ./gatekeeper/policies/ --kube-version 1.29
YAML · алерт Prometheus по отказам Kyverno
groups:
  - name: policy-admission
    rules:
      - alert: KyvernoPolicyFailures
        expr: sum(rate(kyverno_policy_results_total{rule_result="fail"}[5m])) > 5
        for: 15m
        annotations:
          summary: "Sustained Kyverno policy failures in admission"

Политики admission дополняют базовый hardening из практического гайда по безопасности Kubernetes в продакшене.

Проверка подписи образов логично стыкуется с контролями из гайда по безопасности цепочки поставок ПО.