From 34fbdd14124eb5ed68a58ad25b5b894fb4d75678 Mon Sep 17 00:00:00 2001 From: Vassiliy Yegorov Date: Wed, 8 Apr 2026 20:22:14 +0700 Subject: [PATCH] init --- .gitignore | 1 + 01-introduction/01-what-is-kyverno/README.md | 66 ++++++ 01-introduction/02-installation/README.md | 123 +++++++++++ .../02-installation/test-policy.yaml | 28 +++ .../02-installation/values-development.yaml | 31 +++ .../02-installation/values-production.yaml | 73 +++++++ 01-introduction/03-policy-structure/README.md | 56 +++++ .../03-policy-structure/annotated-policy.yaml | 68 ++++++ .../test-resources/pod-bad.yaml | 10 + .../test-resources/pod-good.yaml | 11 + .../01-resource-validation/README.md | 81 +++++++ .../allow-only-trusted-registries.yaml | 51 +++++ .../disallow-latest-tag.yaml | 44 ++++ .../require-labels.yaml | 46 ++++ .../require-min-replicas-production.yaml | 31 +++ .../require-resource-limits.yaml | 49 +++++ .../test-resources/pod-latest-image.yaml | 19 ++ .../test-resources/pod-no-limits.yaml | 10 + .../test-resources/pod-with-limits.yaml | 21 ++ .../tests/kyverno-test.yaml | 30 +++ 02-validation/02-security/README.md | 75 +++++++ .../disallow-dangerous-capabilities.yaml | 59 +++++ .../02-security/disallow-host-namespaces.yaml | 57 +++++ .../disallow-privileged-containers.yaml | 43 ++++ .../require-drop-all-capabilities.yaml | 41 ++++ .../02-security/require-run-as-non-root.yaml | 58 +++++ .../02-security/require-seccomp-profile.yaml | 52 +++++ .../restrict-automount-sa-token.yaml | 37 ++++ .../test-resources/pod-insecure.yaml | 21 ++ .../test-resources/pod-secure.yaml | 28 +++ 02-validation/03-reporting/README.md | 126 +++++++++++ .../03-reporting/prometheus-alert-rules.yaml | 99 +++++++++ .../03-reporting/service-monitor.yaml | 17 ++ 03-mutation/01-basics/README.md | 82 +++++++ .../add-default-resource-requests.yaml | 36 ++++ .../add-default-security-context.yaml | 55 +++++ .../01-basics/add-standard-labels.yaml | 33 +++ .../test-resources/pod-before-mutation.yaml | 16 ++ 03-mutation/02-sidecar/README.md | 82 +++++++ .../02-sidecar/fluent-bit-configmap.yaml | 41 ++++ 03-mutation/02-sidecar/inject-fluent-bit.yaml | 57 +++++ .../inject-prometheus-exporter.yaml | 58 +++++ .../pod-already-has-sidecar.yaml | 21 ++ .../test-resources/pod-no-logging.yaml | 14 ++ .../test-resources/pod-with-logging.yaml | 15 ++ .../test-resources/pod-with-monitoring.yaml | 16 ++ 03-mutation/03-advanced/README.md | 131 +++++++++++ .../add-creator-audit-annotation.yaml | 34 +++ .../03-advanced/kyverno-global-config.yaml | 22 ++ .../set-dynamic-resource-limits.yaml | 44 ++++ 04-generation/01-configmaps-secrets/README.md | 96 +++++++++ .../clone-registry-secret.yaml | 39 ++++ .../developer-clusterrole.yaml | 36 ++++ .../generate-default-networkpolicy.yaml | 51 +++++ .../generate-developer-rolebinding.yaml | 52 +++++ .../generate-namespace-config.yaml | 45 ++++ .../generate-resource-quota.yaml | 59 +++++ .../quota-defaults-configmap.yaml | 25 +++ 04-generation/02-lifecycle/README.md | 106 +++++++++ .../02-lifecycle/cleanup-debug-pods.yaml | 29 +++ 05-variables/01-configmaps/README.md | 134 ++++++++++++ .../inherit-namespace-labels.yaml | 36 ++++ .../01-configmaps/kyverno-global-config.yaml | 27 +++ .../set-resource-limits-from-config.yaml | 44 ++++ 05-variables/02-context/README.md | 127 +++++++++++ .../02-context/deployment-freeze-config.yaml | 13 ++ .../restrict-deploys-during-freeze.yaml | 52 +++++ .../restrict-privileged-non-admins.yaml | 45 ++++ 05-variables/03-templates/README.md | 128 +++++++++++ .../03-templates/kyverno-policies/Chart.yaml | 16 ++ .../templates/disallow-privileged.yaml | 42 ++++ .../templates/generate-networkpolicy.yaml | 44 ++++ .../templates/require-labels.yaml | 40 ++++ .../templates/require-resource-limits.yaml | 41 ++++ .../kyverno-policies/tests/kyverno-test.yaml | 31 +++ .../tests/resources/deployment-no-labels.yaml | 23 ++ .../tests/resources/pod-compliant.yaml | 16 ++ .../tests/resources/pod-no-limits.yaml | 9 + .../tests/resources/pod-privileged.yaml | 15 ++ .../kyverno-policies/values-production.yaml | 21 ++ .../kyverno-policies/values-staging.yaml | 10 + .../03-templates/kyverno-policies/values.yaml | 55 +++++ 06-monitoring/01-logging/README.md | 134 ++++++++++++ .../01-logging/grafana-dashboard.json | 130 +++++++++++ 06-monitoring/02-debugging/README.md | 203 ++++++++++++++++++ .../02-debugging/debug-context-inspector.yaml | 42 ++++ .../policy-exception-example.yaml | 24 +++ 06-monitoring/03-performance/README.md | 185 ++++++++++++++++ .../01-cicd/.github/workflows/policy-ci.yaml | 154 +++++++++++++ 07-advanced/01-cicd/README.md | 153 +++++++++++++ 07-advanced/01-cicd/argocd-application.yaml | 39 ++++ 07-advanced/02-external-data/README.md | 172 +++++++++++++++ .../check-image-vulnerabilities.yaml | 54 +++++ .../02-external-data/external-data-cache.yaml | 105 +++++++++ .../validate-registry-from-cache.yaml | 54 +++++ README.md | 46 ++++ 96 files changed, 5321 insertions(+) create mode 100644 .gitignore create mode 100644 01-introduction/01-what-is-kyverno/README.md create mode 100644 01-introduction/02-installation/README.md create mode 100644 01-introduction/02-installation/test-policy.yaml create mode 100644 01-introduction/02-installation/values-development.yaml create mode 100644 01-introduction/02-installation/values-production.yaml create mode 100644 01-introduction/03-policy-structure/README.md create mode 100644 01-introduction/03-policy-structure/annotated-policy.yaml create mode 100644 01-introduction/03-policy-structure/test-resources/pod-bad.yaml create mode 100644 01-introduction/03-policy-structure/test-resources/pod-good.yaml create mode 100644 02-validation/01-resource-validation/README.md create mode 100644 02-validation/01-resource-validation/allow-only-trusted-registries.yaml create mode 100644 02-validation/01-resource-validation/disallow-latest-tag.yaml create mode 100644 02-validation/01-resource-validation/require-labels.yaml create mode 100644 02-validation/01-resource-validation/require-min-replicas-production.yaml create mode 100644 02-validation/01-resource-validation/require-resource-limits.yaml create mode 100644 02-validation/01-resource-validation/test-resources/pod-latest-image.yaml create mode 100644 02-validation/01-resource-validation/test-resources/pod-no-limits.yaml create mode 100644 02-validation/01-resource-validation/test-resources/pod-with-limits.yaml create mode 100644 02-validation/01-resource-validation/tests/kyverno-test.yaml create mode 100644 02-validation/02-security/README.md create mode 100644 02-validation/02-security/disallow-dangerous-capabilities.yaml create mode 100644 02-validation/02-security/disallow-host-namespaces.yaml create mode 100644 02-validation/02-security/disallow-privileged-containers.yaml create mode 100644 02-validation/02-security/require-drop-all-capabilities.yaml create mode 100644 02-validation/02-security/require-run-as-non-root.yaml create mode 100644 02-validation/02-security/require-seccomp-profile.yaml create mode 100644 02-validation/02-security/restrict-automount-sa-token.yaml create mode 100644 02-validation/02-security/test-resources/pod-insecure.yaml create mode 100644 02-validation/02-security/test-resources/pod-secure.yaml create mode 100644 02-validation/03-reporting/README.md create mode 100644 02-validation/03-reporting/prometheus-alert-rules.yaml create mode 100644 02-validation/03-reporting/service-monitor.yaml create mode 100644 03-mutation/01-basics/README.md create mode 100644 03-mutation/01-basics/add-default-resource-requests.yaml create mode 100644 03-mutation/01-basics/add-default-security-context.yaml create mode 100644 03-mutation/01-basics/add-standard-labels.yaml create mode 100644 03-mutation/01-basics/test-resources/pod-before-mutation.yaml create mode 100644 03-mutation/02-sidecar/README.md create mode 100644 03-mutation/02-sidecar/fluent-bit-configmap.yaml create mode 100644 03-mutation/02-sidecar/inject-fluent-bit.yaml create mode 100644 03-mutation/02-sidecar/inject-prometheus-exporter.yaml create mode 100644 03-mutation/02-sidecar/test-resources/pod-already-has-sidecar.yaml create mode 100644 03-mutation/02-sidecar/test-resources/pod-no-logging.yaml create mode 100644 03-mutation/02-sidecar/test-resources/pod-with-logging.yaml create mode 100644 03-mutation/02-sidecar/test-resources/pod-with-monitoring.yaml create mode 100644 03-mutation/03-advanced/README.md create mode 100644 03-mutation/03-advanced/add-creator-audit-annotation.yaml create mode 100644 03-mutation/03-advanced/kyverno-global-config.yaml create mode 100644 03-mutation/03-advanced/set-dynamic-resource-limits.yaml create mode 100644 04-generation/01-configmaps-secrets/README.md create mode 100644 04-generation/01-configmaps-secrets/clone-registry-secret.yaml create mode 100644 04-generation/01-configmaps-secrets/developer-clusterrole.yaml create mode 100644 04-generation/01-configmaps-secrets/generate-default-networkpolicy.yaml create mode 100644 04-generation/01-configmaps-secrets/generate-developer-rolebinding.yaml create mode 100644 04-generation/01-configmaps-secrets/generate-namespace-config.yaml create mode 100644 04-generation/01-configmaps-secrets/generate-resource-quota.yaml create mode 100644 04-generation/01-configmaps-secrets/quota-defaults-configmap.yaml create mode 100644 04-generation/02-lifecycle/README.md create mode 100644 04-generation/02-lifecycle/cleanup-debug-pods.yaml create mode 100644 05-variables/01-configmaps/README.md create mode 100644 05-variables/01-configmaps/inherit-namespace-labels.yaml create mode 100644 05-variables/01-configmaps/kyverno-global-config.yaml create mode 100644 05-variables/01-configmaps/set-resource-limits-from-config.yaml create mode 100644 05-variables/02-context/README.md create mode 100644 05-variables/02-context/deployment-freeze-config.yaml create mode 100644 05-variables/02-context/restrict-deploys-during-freeze.yaml create mode 100644 05-variables/02-context/restrict-privileged-non-admins.yaml create mode 100644 05-variables/03-templates/README.md create mode 100644 05-variables/03-templates/kyverno-policies/Chart.yaml create mode 100644 05-variables/03-templates/kyverno-policies/templates/disallow-privileged.yaml create mode 100644 05-variables/03-templates/kyverno-policies/templates/generate-networkpolicy.yaml create mode 100644 05-variables/03-templates/kyverno-policies/templates/require-labels.yaml create mode 100644 05-variables/03-templates/kyverno-policies/templates/require-resource-limits.yaml create mode 100644 05-variables/03-templates/kyverno-policies/tests/kyverno-test.yaml create mode 100644 05-variables/03-templates/kyverno-policies/tests/resources/deployment-no-labels.yaml create mode 100644 05-variables/03-templates/kyverno-policies/tests/resources/pod-compliant.yaml create mode 100644 05-variables/03-templates/kyverno-policies/tests/resources/pod-no-limits.yaml create mode 100644 05-variables/03-templates/kyverno-policies/tests/resources/pod-privileged.yaml create mode 100644 05-variables/03-templates/kyverno-policies/values-production.yaml create mode 100644 05-variables/03-templates/kyverno-policies/values-staging.yaml create mode 100644 05-variables/03-templates/kyverno-policies/values.yaml create mode 100644 06-monitoring/01-logging/README.md create mode 100644 06-monitoring/01-logging/grafana-dashboard.json create mode 100644 06-monitoring/02-debugging/README.md create mode 100644 06-monitoring/02-debugging/debug-context-inspector.yaml create mode 100644 06-monitoring/02-debugging/policy-exception-example.yaml create mode 100644 06-monitoring/03-performance/README.md create mode 100644 07-advanced/01-cicd/.github/workflows/policy-ci.yaml create mode 100644 07-advanced/01-cicd/README.md create mode 100644 07-advanced/01-cicd/argocd-application.yaml create mode 100644 07-advanced/02-external-data/README.md create mode 100644 07-advanced/02-external-data/check-image-vulnerabilities.yaml create mode 100644 07-advanced/02-external-data/external-data-cache.yaml create mode 100644 07-advanced/02-external-data/validate-registry-from-cache.yaml create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..496ee2c --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/01-introduction/01-what-is-kyverno/README.md b/01-introduction/01-what-is-kyverno/README.md new file mode 100644 index 0000000..951d18b --- /dev/null +++ b/01-introduction/01-what-is-kyverno/README.md @@ -0,0 +1,66 @@ +# Урок 1.1 — Что такое Kyverno и зачем он нужен + +Этот урок — теоретический. Ниже — сравнительная шпаргалка и ссылки для подготовки к следующим урокам. + +## Сравнение: Kyverno vs OPA Gatekeeper + +| Критерий | Kyverno | OPA Gatekeeper | +|----------|---------|----------------| +| Язык политик | YAML + JMESPath | Rego | +| Порог входа | Низкий | Высокий | +| Kubernetes-native | Да | Частично | +| Готовых политик | 300+ (kyverno.io/policies) | Много (github.com/open-policy-agent/gatekeeper-library) | +| Mutation | Да | Ограниченно | +| Generation | Да | Нет | +| Image verification | Да (Cosign) | Нет | + +## Типы политик + +``` +ClusterPolicy — кластерный ресурс (все namespace) +Policy — namespace-scoped ресурс + +Операции: + validate → проверить ресурс, отклонить при нарушении + mutate → изменить ресурс при создании/обновлении + generate → создать вспомогательные ресурсы + verifyImages → проверить подпись образа +``` + +## Архитектура Kyverno + +``` +kubectl apply -f pod.yaml + │ + ▼ + API Server + │ + ├──► Mutating Webhook ──► Kyverno (mutate rules) + │ │ + │ изменённый ресурс + │ │ + ├──► Validating Webhook ──► Kyverno (validate rules) + │ │ + │ pass / deny + ▼ + etcd + │ + ▼ + Background Controller ──► сканирует существующие ресурсы + Reports Controller ──► создаёт PolicyReport + Generate Controller ──► создаёт вспомогательные ресурсы + Cleanup Controller ──► удаляет по расписанию +``` + +## Задание + +1. Изучите [официальную документацию](https://kyverno.io/docs/) +2. Просмотрите готовые политики на [kyverno.io/policies](https://kyverno.io/policies/) +3. Подготовьте кластер для следующего урока: + ```bash + # Локально через kind + kind create cluster --name kyverno-lab + + # Или minikube + minikube start --memory=4096 --cpus=2 + ``` diff --git a/01-introduction/02-installation/README.md b/01-introduction/02-installation/README.md new file mode 100644 index 0000000..0f615d8 --- /dev/null +++ b/01-introduction/02-installation/README.md @@ -0,0 +1,123 @@ +# Урок 1.2 — Установка и настройка Kyverno + +## 1. Добавить Helm репозиторий + +```bash +helm repo add kyverno https://kyverno.github.io/kyverno/ +helm repo update + +# Посмотреть доступные версии +helm search repo kyverno/kyverno --versions | head -10 +``` + +## 2. Установка + +### Development / Lab + +```bash +helm install kyverno kyverno/kyverno \ + --namespace kyverno \ + --create-namespace \ + --values values-development.yaml +``` + +### Production + +```bash +helm install kyverno kyverno/kyverno \ + --namespace kyverno \ + --create-namespace \ + --values values-production.yaml +``` + +## 3. Проверка установки + +```bash +# Статус подов +kubectl get pods -n kyverno + +# Все поды должны быть Running: +# NAME READY STATUS RESTARTS +# kyverno-admission-controller-xxx 1/1 Running 0 +# kyverno-background-controller-xxx 1/1 Running 0 +# kyverno-cleanup-controller-xxx 1/1 Running 0 +# kyverno-reports-controller-xxx 1/1 Running 0 + +# Проверить webhook конфигурации +kubectl get validatingwebhookconfigurations | grep kyverno +kubectl get mutatingwebhookconfigurations | grep kyverno + +# Проверить что caBundle не пустой (признак корректной регистрации webhook) +kubectl get validatingwebhookconfigurations \ + kyverno-resource-validating-webhook-cfg \ + -o jsonpath='{.webhooks[0].clientConfig.caBundle}' | wc -c + +# Посмотреть события +kubectl get events -n kyverno --sort-by='.lastTimestamp' | tail -20 + +# Логи admission controller +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=admission-controller \ + --tail=50 +``` + +## 4. Тестовая политика + +```bash +# Создать тестовую политику +kubectl apply -f test-policy.yaml + +# Попробовать создать ConfigMap без обязательного лейбла (должна быть ошибка) +kubectl create configmap test-bad --from-literal=key=value -n default + +# Создать правильный ConfigMap +kubectl apply -f - </dev/null || true + +kubectl delete mutatingwebhookconfigurations \ + $(kubectl get mutatingwebhookconfigurations | grep kyverno | awk '{print $1}') \ + 2>/dev/null || true +``` diff --git a/01-introduction/02-installation/test-policy.yaml b/01-introduction/02-installation/test-policy.yaml new file mode 100644 index 0000000..e68e7af --- /dev/null +++ b/01-introduction/02-installation/test-policy.yaml @@ -0,0 +1,28 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: test-installation-policy + annotations: + policies.kyverno.io/title: "Тестовая политика установки" + policies.kyverno.io/description: >- + Временная политика для проверки корректности установки Kyverno. + Удалите после проверки. +spec: + validationFailureAction: Enforce + background: false + rules: + - name: require-test-label + match: + resources: + kinds: + - ConfigMap + namespaces: + - default + validate: + message: >- + ConfigMap в namespace default должен иметь лейбл test=true. + Это тестовая политика для проверки установки Kyverno. + pattern: + metadata: + labels: + test: "?*" diff --git a/01-introduction/02-installation/values-development.yaml b/01-introduction/02-installation/values-development.yaml new file mode 100644 index 0000000..b501180 --- /dev/null +++ b/01-introduction/02-installation/values-development.yaml @@ -0,0 +1,31 @@ +# values-development.yaml +# Минимальная конфигурация для dev/lab окружения + +replicaCount: 1 + +admissionController: + replicas: 1 + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + +backgroundController: + replicas: 1 + resources: + limits: + cpu: 200m + memory: 128Mi + +reportsController: + replicas: 1 + +cleanupController: + replicas: 1 + +config: + webhooks: + timeoutSeconds: 10 diff --git a/01-introduction/02-installation/values-production.yaml b/01-introduction/02-installation/values-production.yaml new file mode 100644 index 0000000..4237ec9 --- /dev/null +++ b/01-introduction/02-installation/values-production.yaml @@ -0,0 +1,73 @@ +# values-production.yaml +# Production-ready конфигурация с HA и мониторингом + +replicaCount: 3 + +admissionController: + replicas: 3 + + podDisruptionBudget: + enabled: true + minAvailable: 2 + + resources: + limits: + cpu: 1000m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + + # Распределение по разным нодам + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: DoNotSchedule + labelSelector: + matchLabels: + app.kubernetes.io/component: admission-controller + + # Метрики для Prometheus Operator + serviceMonitor: + enabled: true + metricsService: + create: true + port: 8000 + + extraArgs: + - --v=2 + - --loggingFormat=json + +backgroundController: + replicas: 2 + resources: + limits: + cpu: 500m + memory: 256Mi + requests: + cpu: 100m + memory: 128Mi + extraArgs: + - --v=2 + - --loggingFormat=json + +reportsController: + replicas: 2 + resources: + limits: + cpu: 200m + memory: 128Mi + extraArgs: + - --v=2 + +cleanupController: + replicas: 2 + +config: + webhooks: + # Если Kyverno не ответил за 10 секунд — API Server применяет failurePolicy + timeoutSeconds: 10 + +# Автоматическая очистка webhook-конфигураций при удалении Kyverno +webhooksCleanup: + enabled: true diff --git a/01-introduction/03-policy-structure/README.md b/01-introduction/03-policy-structure/README.md new file mode 100644 index 0000000..739cf4b --- /dev/null +++ b/01-introduction/03-policy-structure/README.md @@ -0,0 +1,56 @@ +# Урок 1.3 — Структура и синтаксис политик Kyverno + +## Анатомия политики + +Откройте `annotated-policy.yaml` — в нём каждое поле прокомментировано. + +## Операторы в паттернах + +| Оператор | Значение | Пример | +|----------|----------|--------| +| `?*` | поле существует и не пустое | `app: "?*"` | +| `*` | wildcard, любое значение | `image: "*.azurecr.io/*"` | +| `^(a\|b)$` | регулярное выражение | `env: "^(dev\|staging\|production)$"` | +| `>=2` | числовое сравнение | `replicas: ">=2"` | +| `=(field)` | опциональное поле | `=(privileged): false` | +| `+(field)` | добавить только если нет (в mutate) | `+(label): value` | + +## Структура match + +```yaml +match: + any: # ИЛИ — любое из условий + - resources: + kinds: [Pod] + namespaces: [production] + all: # И — все условия одновременно + - resources: + kinds: [Deployment] + - subjects: + - kind: ServiceAccount + name: my-sa +``` + +## Практические задания + +```bash +# 1. Изучить структуру политики +cat annotated-policy.yaml + +# 2. Применить политику +kubectl apply -f annotated-policy.yaml + +# 3. Протестировать через kyverno CLI без кластера +kyverno apply annotated-policy.yaml --resource test-resources/pod-good.yaml +kyverno apply annotated-policy.yaml --resource test-resources/pod-bad.yaml + +# 4. Применить тестовые ресурсы в кластер +kubectl apply -f test-resources/ + +# 5. Посмотреть результаты в PolicyReport +kubectl get policyreports -n default -o yaml + +# 6. Удалить ресурсы +kubectl delete -f test-resources/ +kubectl delete -f annotated-policy.yaml +``` diff --git a/01-introduction/03-policy-structure/annotated-policy.yaml b/01-introduction/03-policy-structure/annotated-policy.yaml new file mode 100644 index 0000000..bc73554 --- /dev/null +++ b/01-introduction/03-policy-structure/annotated-policy.yaml @@ -0,0 +1,68 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy # ClusterPolicy = все namespace, Policy = один namespace +metadata: + name: annotated-example-policy + annotations: + # Мета-информация для Kyverno UI и PolicyReport + policies.kyverno.io/title: "Пример структуры политики" + policies.kyverno.io/category: "Best Practices" + policies.kyverno.io/severity: medium # critical | high | medium | low + policies.kyverno.io/subject: "Pod" + policies.kyverno.io/description: >- + Пример политики с подробными комментариями. + Требует наличия лейбла app у всех Podов. + policies.kyverno.io/version: "1.0.0" +spec: + # Enforce = отклонять запросы при нарушении + # Audit = пропускать, но фиксировать в PolicyReport + validationFailureAction: Audit + + # true = проверять также уже существующие ресурсы (через background controller) + background: true + + rules: + - name: require-app-label # уникальное имя правила внутри политики + + # match: к каким ресурсам применяется правило + match: + any: # ИЛИ: ресурс попадает хотя бы под одно условие + - resources: + kinds: + - Pod + namespaces: + - default + - production + - staging + + # exclude: ресурсы-исключения (не будут проверяться) + exclude: + resources: + namespaces: + - kube-system + - kyverno + selector: + matchLabels: + kyverno.io/skip-validation: "true" + + # preconditions: дополнительные условия применения правила + # (match фильтрует по типу, preconditions — по содержимому) + preconditions: + any: + - key: "{{ request.operation }}" + operator: In + value: ["CREATE", "UPDATE"] # не проверять DELETE + + # validate: правило проверки + validate: + # Сообщение об ошибке (поддерживает переменные) + message: >- + Pod '{{ request.object.metadata.name }}' + в namespace '{{ request.object.metadata.namespace }}' + должен иметь лейбл 'app'. + Добавьте: metadata.labels.app: <имя-приложения> + + # pattern: описание того, как должен выглядеть ресурс + pattern: + metadata: + labels: + app: "?*" # поле должно существовать и быть не пустым diff --git a/01-introduction/03-policy-structure/test-resources/pod-bad.yaml b/01-introduction/03-policy-structure/test-resources/pod-bad.yaml new file mode 100644 index 0000000..4c1eb2b --- /dev/null +++ b/01-introduction/03-policy-structure/test-resources/pod-bad.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-bad + namespace: default + # нет лейбла app — политика зафиксирует нарушение +spec: + containers: + - name: nginx + image: nginx:1.25 diff --git a/01-introduction/03-policy-structure/test-resources/pod-good.yaml b/01-introduction/03-policy-structure/test-resources/pod-good.yaml new file mode 100644 index 0000000..c4d04b4 --- /dev/null +++ b/01-introduction/03-policy-structure/test-resources/pod-good.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-good + namespace: default + labels: + app: my-application # лейбл присутствует — политика пропустит +spec: + containers: + - name: nginx + image: nginx:1.25 diff --git a/02-validation/01-resource-validation/README.md b/02-validation/01-resource-validation/README.md new file mode 100644 index 0000000..da1f245 --- /dev/null +++ b/02-validation/01-resource-validation/README.md @@ -0,0 +1,81 @@ +# Урок 2.1 — Создание политик валидации ресурсов + +## Файлы + +| Файл | Описание | +|------|----------| +| `require-resource-limits.yaml` | Обязательные CPU и memory limits | +| `disallow-latest-tag.yaml` | Запрет тега :latest | +| `allow-only-trusted-registries.yaml` | Только внутренние реестры | +| `require-labels.yaml` | Обязательные стандартные лейблы | +| `require-min-replicas-production.yaml` | Минимум 2 реплики в production | + +## Применение политик + +```bash +# Применить все политики из папки +kubectl apply -f . + +# Проверить статус +kubectl get clusterpolicies + +# Подробный статус +kubectl get clusterpolicies -o wide +``` + +## Локальное тестирование через Kyverno CLI + +```bash +# Протестировать одну политику +kyverno apply require-resource-limits.yaml \ + --resource test-resources/pod-no-limits.yaml + +# Протестировать все политики против всех ресурсов +kyverno apply . --resource test-resources/ --table + +# Запустить встроенные тесты +kyverno test tests/ +``` + +## Тестирование в кластере + +```bash +# Попытка создать под без limits (должна быть ошибка) +kubectl apply -f test-resources/pod-no-limits.yaml + +# Создать корректный под +kubectl apply -f test-resources/pod-with-limits.yaml + +# Попытка создать под с образом :latest +kubectl apply -f test-resources/pod-latest-image.yaml + +# Посмотреть PolicyReport после создания +kubectl get policyreports -n default +kubectl describe policyreport -n default + +# Найти все нарушения +kubectl get policyreports -A -o json | \ + jq -r '.items[] | .metadata.namespace as $ns | + .results[] | select(.result == "fail") | + "\($ns)/\(.resources[0].name): \(.policy)/\(.rule): \(.message)"' +``` + +## Режим Audit — миграция без риска + +```bash +# Переключить политику в Audit режим для оценки нарушений +kubectl patch clusterpolicy require-resource-limits \ + --type merge \ + -p '{"spec":{"validationFailureAction":"Audit"}}' + +# Посмотреть нарушения без блокировки +kubectl get policyreports -A -o json | \ + jq '[.items[].results[] | select(.result == "fail") | .policy] | + group_by(.) | map({policy: .[0], violations: length}) | + sort_by(-.violations)[]' + +# После исправления — переключить обратно +kubectl patch clusterpolicy require-resource-limits \ + --type merge \ + -p '{"spec":{"validationFailureAction":"Enforce"}}' +``` diff --git a/02-validation/01-resource-validation/allow-only-trusted-registries.yaml b/02-validation/01-resource-validation/allow-only-trusted-registries.yaml new file mode 100644 index 0000000..78909be --- /dev/null +++ b/02-validation/01-resource-validation/allow-only-trusted-registries.yaml @@ -0,0 +1,51 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: allow-only-trusted-registries + annotations: + policies.kyverno.io/title: "Только доверенные реестры" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Разрешает образы только из доверенных реестров компании. + Предотвращает использование образов из публичных реестров + без проверки безопасности. + НАСТРОЙТЕ список разрешённых реестров под вашу инфраструктуру. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-trusted-registries + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + validate: + message: >- + Образ '{{ element.image }}' из недоверенного реестра. + Разрешены только: + - registry.company.com/ + - gcr.io/company-project/ + Загрузите образ в внутренний реестр и обновите манифест. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) + deny: + conditions: + all: + # Образ НЕ из первого доверенного реестра + - key: "{{ element.image }}" + operator: NotStartsWith + value: "registry.company.com/" + # И НЕ из второго доверенного реестра + - key: "{{ element.image }}" + operator: NotStartsWith + value: "gcr.io/company-project/" + # Добавьте дополнительные условия по аналогии diff --git a/02-validation/01-resource-validation/disallow-latest-tag.yaml b/02-validation/01-resource-validation/disallow-latest-tag.yaml new file mode 100644 index 0000000..d04ba4b --- /dev/null +++ b/02-validation/01-resource-validation/disallow-latest-tag.yaml @@ -0,0 +1,44 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-latest-tag + annotations: + policies.kyverno.io/title: "Запрет тега latest" + policies.kyverno.io/category: Best Practices + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Запрещает использование тега :latest и образов без тега. + Оба варианта резолвятся в "последний доступный образ", + что делает деплойменты невоспроизводимыми. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: disallow-latest-tag + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Образ '{{ element.image }}' использует тег :latest или не имеет тега. + Используйте конкретный тег (например, nginx:1.25.3) или digest + (nginx@sha256:abc123...) для воспроизводимых деплойментов. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) + deny: + conditions: + any: + - key: "{{ element.image }}" + operator: Contains + value: ":latest" + - key: "{{ element.image }}" + operator: NotContains + value: ":" diff --git a/02-validation/01-resource-validation/require-labels.yaml b/02-validation/01-resource-validation/require-labels.yaml new file mode 100644 index 0000000..e6f115e --- /dev/null +++ b/02-validation/01-resource-validation/require-labels.yaml @@ -0,0 +1,46 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-standard-labels + annotations: + policies.kyverno.io/title: "Обязательные стандартные лейблы" + policies.kyverno.io/category: Governance + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Deployment,StatefulSet,DaemonSet + policies.kyverno.io/description: >- + Требует наличия стандартных лейблов у workload ресурсов. + Лейблы используются для мониторинга, алертинга и распределения затрат. + Допустимые значения environment: dev | staging | production +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-required-labels + match: + resources: + kinds: + - Deployment + - StatefulSet + - DaemonSet + exclude: + resources: + namespaces: + - kube-system + - kyverno + validate: + message: >- + Ресурс '{{ request.object.metadata.name }}' должен иметь лейблы: + app, version, team, environment (dev|staging|production) + Пример: + labels: + app: my-service + version: "1.0.0" + team: payments + environment: production + pattern: + metadata: + labels: + app: "?*" + version: "?*" + team: "?*" + environment: "^(dev|staging|production)$" diff --git a/02-validation/01-resource-validation/require-min-replicas-production.yaml b/02-validation/01-resource-validation/require-min-replicas-production.yaml new file mode 100644 index 0000000..803828f --- /dev/null +++ b/02-validation/01-resource-validation/require-min-replicas-production.yaml @@ -0,0 +1,31 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-min-replicas-production + annotations: + policies.kyverno.io/title: "Минимальное количество реплик в production" + policies.kyverno.io/category: Availability + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Deployment + policies.kyverno.io/description: >- + В namespace production требуется минимум 2 реплики для Deployment. + Одна реплика = single point of failure при обновлении ноды или пода. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-min-replicas + match: + resources: + kinds: + - Deployment + namespaces: + - production + validate: + message: >- + Deployment '{{ request.object.metadata.name }}' в production + имеет {{ request.object.spec.replicas }} реплику(и). + Минимально требуется 2 реплики для обеспечения доступности. + pattern: + spec: + replicas: ">=2" diff --git a/02-validation/01-resource-validation/require-resource-limits.yaml b/02-validation/01-resource-validation/require-resource-limits.yaml new file mode 100644 index 0000000..f4cccbc --- /dev/null +++ b/02-validation/01-resource-validation/require-resource-limits.yaml @@ -0,0 +1,49 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-resource-limits + annotations: + policies.kyverno.io/title: "Обязательные resource limits" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Каждый контейнер (включая init и ephemeral) обязан иметь + limits.memory и limits.cpu. Без лимитов контейнер может + потребить все ресурсы ноды и убить соседей (noisy neighbor). +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-container-limits + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + validate: + message: >- + Контейнер '{{ element.name }}' в поде '{{ request.object.metadata.name }}' + (namespace: {{ request.object.metadata.namespace }}) не имеет resource limits. + + Добавьте в манифест: + resources: + limits: + memory: "256Mi" + cpu: "500m" + + Документация: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) | + merge(request.object.spec.ephemeralContainers[] || `[]`, @) + pattern: + resources: + limits: + memory: "?*" + cpu: "?*" diff --git a/02-validation/01-resource-validation/test-resources/pod-latest-image.yaml b/02-validation/01-resource-validation/test-resources/pod-latest-image.yaml new file mode 100644 index 0000000..f6aa8fd --- /dev/null +++ b/02-validation/01-resource-validation/test-resources/pod-latest-image.yaml @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-latest-image + namespace: default +spec: + containers: + - name: nginx + image: nginx:latest # тег :latest — политика отклонит + resources: + limits: + memory: "128Mi" + cpu: "100m" + - name: redis + image: redis # нет тега вообще — тоже отклонит + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/02-validation/01-resource-validation/test-resources/pod-no-limits.yaml b/02-validation/01-resource-validation/test-resources/pod-no-limits.yaml new file mode 100644 index 0000000..e6d6cee --- /dev/null +++ b/02-validation/01-resource-validation/test-resources/pod-no-limits.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-no-limits + namespace: default +spec: + containers: + - name: nginx + image: nginx:1.25.3 + # нет блока resources — политика require-resource-limits отклонит diff --git a/02-validation/01-resource-validation/test-resources/pod-with-limits.yaml b/02-validation/01-resource-validation/test-resources/pod-with-limits.yaml new file mode 100644 index 0000000..155b72e --- /dev/null +++ b/02-validation/01-resource-validation/test-resources/pod-with-limits.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-limits + namespace: default + labels: + app: my-app + version: "1.0.0" + team: platform + environment: staging +spec: + containers: + - name: nginx + image: nginx:1.25.3 # конкретный тег — хорошо + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" diff --git a/02-validation/01-resource-validation/tests/kyverno-test.yaml b/02-validation/01-resource-validation/tests/kyverno-test.yaml new file mode 100644 index 0000000..a92ad6e --- /dev/null +++ b/02-validation/01-resource-validation/tests/kyverno-test.yaml @@ -0,0 +1,30 @@ +name: resource-validation-tests +policies: + - ../require-resource-limits.yaml + - ../disallow-latest-tag.yaml + - ../require-labels.yaml +resources: + - pod-with-limits.yaml + - pod-no-limits.yaml + - pod-latest-image.yaml +results: + - policy: require-resource-limits + rule: check-container-limits + resource: pod-with-limits + namespace: default + result: pass + - policy: require-resource-limits + rule: check-container-limits + resource: pod-no-limits + namespace: default + result: fail + - policy: disallow-latest-tag + rule: disallow-latest-tag + resource: pod-with-limits + namespace: default + result: pass + - policy: disallow-latest-tag + rule: disallow-latest-tag + resource: pod-latest-image + namespace: default + result: fail diff --git a/02-validation/02-security/README.md b/02-validation/02-security/README.md new file mode 100644 index 0000000..4f47c22 --- /dev/null +++ b/02-validation/02-security/README.md @@ -0,0 +1,75 @@ +# Урок 2.2 — Политики безопасности и соответствия стандартам + +## Файлы + +| Файл | PSS профиль | Описание | +|------|-------------|----------| +| `disallow-privileged-containers.yaml` | Baseline | Запрет `privileged: true` | +| `disallow-dangerous-capabilities.yaml` | Baseline | Запрет опасных capabilities | +| `require-drop-all-capabilities.yaml` | Restricted | Обязательный `drop: [ALL]` | +| `require-run-as-non-root.yaml` | Restricted | Запрет запуска от root | +| `disallow-host-namespaces.yaml` | Baseline | Запрет hostNetwork/PID/IPC/Path | +| `require-seccomp-profile.yaml` | Restricted | Обязательный seccomp | +| `restrict-automount-sa-token.yaml` | CIS | Отключение автомонтирования токена | + +## Стратегия внедрения (поэтапно) + +### Этап 1 — Аудит (неделя 1–2) + +```bash +# Применить все политики в режиме Audit +for f in *.yaml; do + kubectl apply -f "$f" +done + +# Подождать 5 минут для background scan, затем: +kubectl get policyreports -A -o json | \ + jq -r '[.items[].results[] | select(.result == "fail") | .policy] | + group_by(.) | map({policy: .[0], count: length}) | + sort_by(-.count)[] | "\(.count)\t\(.policy)"' +``` + +### Этап 2 — Оценка нарушений + +```bash +# Детали нарушений по конкретной политике +POLICY="disallow-privileged-containers" +kubectl get policyreports -A -o json | \ + jq --arg p "$POLICY" \ + -r '.items[] | .metadata.namespace as $ns | + .results[] | select(.policy == $p and .result == "fail") | + "\($ns)/\(.resources[0].name): \(.message)"' +``` + +### Этап 3 — Тестирование через Kyverno CLI + +```bash +# Проверить под с нарушениями +kyverno apply . \ + --resource test-resources/pod-insecure.yaml \ + --table + +# Проверить корректный под +kyverno apply . \ + --resource test-resources/pod-secure.yaml \ + --table +``` + +### Этап 4 — Перевод в Enforce + +```bash +# По одной политике +kubectl patch clusterpolicy disallow-privileged-containers \ + --type merge \ + -p '{"spec":{"validationFailureAction":"Enforce"}}' +``` + +## Тестовые ресурсы + +```bash +# Под с нарушениями (должен быть отклонён) +kubectl apply -f test-resources/pod-insecure.yaml + +# Безопасный под (должен пройти) +kubectl apply -f test-resources/pod-secure.yaml +``` diff --git a/02-validation/02-security/disallow-dangerous-capabilities.yaml b/02-validation/02-security/disallow-dangerous-capabilities.yaml new file mode 100644 index 0000000..1fcd548 --- /dev/null +++ b/02-validation/02-security/disallow-dangerous-capabilities.yaml @@ -0,0 +1,59 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-dangerous-capabilities + annotations: + policies.kyverno.io/title: "Запрет опасных Linux Capabilities" + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Запрещает добавление опасных Linux capabilities. + SYS_ADMIN, NET_ADMIN, SYS_PTRACE и другие дают контейнеру + привилегированный доступ к ядру и сети хоста. + Допустима только NET_BIND_SERVICE (порты < 1024). +spec: + validationFailureAction: Enforce + background: true + rules: + - name: disallow-dangerous-capabilities + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Контейнер '{{ element.name }}' добавляет запрещённые capabilities: + {{ element.securityContext.capabilities.add }}. + Разрешена только NET_BIND_SERVICE. + Пересмотрите необходимость этих привилегий. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) + deny: + conditions: + any: + - key: "{{ element.securityContext.capabilities.add[] }}" + operator: AnyIn + value: + - SYS_ADMIN + - NET_ADMIN + - SYS_PTRACE + - SYS_MODULE + - SYS_RAWIO + - SYS_BOOT + - SYS_NICE + - SYS_RESOURCE + - SYS_TIME + - AUDIT_CONTROL + - MAC_ADMIN + - MAC_OVERRIDE + - SETUID + - SETGID + - KILL + - MKNOD diff --git a/02-validation/02-security/disallow-host-namespaces.yaml b/02-validation/02-security/disallow-host-namespaces.yaml new file mode 100644 index 0000000..0e76dbd --- /dev/null +++ b/02-validation/02-security/disallow-host-namespaces.yaml @@ -0,0 +1,57 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-host-namespaces + annotations: + policies.kyverno.io/title: "Запрет host namespaces и HostPath" + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: critical + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + hostNetwork, hostPID, hostIPC и hostPath дают контейнеру прямой доступ + к соответствующим ресурсам ноды. Это нарушает изоляцию контейнеров + и является вектором для escape-атак. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: disallow-host-namespaces + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Под '{{ request.object.metadata.name }}' использует host namespace. + Запрещены: hostNetwork, hostIPC, hostPID. + Эти настройки дают контейнеру доступ к сети/процессам/IPC ноды. + pattern: + spec: + =(hostNetwork): false + =(hostIPC): false + =(hostPID): false + + - name: disallow-hostpath-volumes + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Под '{{ request.object.metadata.name }}' использует HostPath volume. + HostPath даёт контейнеру прямой доступ к файловой системе ноды. + Используйте emptyDir, configMap, secret или PersistentVolumeClaim. + deny: + conditions: + any: + - key: "{{ request.object.spec.volumes[].hostPath | length(@) }}" + operator: GreaterThan + value: "0" diff --git a/02-validation/02-security/disallow-privileged-containers.yaml b/02-validation/02-security/disallow-privileged-containers.yaml new file mode 100644 index 0000000..1095251 --- /dev/null +++ b/02-validation/02-security/disallow-privileged-containers.yaml @@ -0,0 +1,43 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-privileged-containers + annotations: + policies.kyverno.io/title: "Запрет привилегированных контейнеров" + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: critical + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Привилегированный контейнер имеет полный доступ к хост-системе — + эквивалент root на самой ноде. Компрометация такого контейнера + означает компрометацию всей ноды. + Проверяются: containers, initContainers, ephemeralContainers. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: privileged-containers + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Контейнер '{{ element.name }}' имеет securityContext.privileged: true. + Привилегированные контейнеры запрещены — они получают полный доступ к хосту. + Удалите поле securityContext.privileged или установите значение false. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) | + merge(request.object.spec.ephemeralContainers[] || `[]`, @) + deny: + conditions: + any: + - key: "{{ element.securityContext.privileged }}" + operator: Equals + value: true diff --git a/02-validation/02-security/require-drop-all-capabilities.yaml b/02-validation/02-security/require-drop-all-capabilities.yaml new file mode 100644 index 0000000..3dd9299 --- /dev/null +++ b/02-validation/02-security/require-drop-all-capabilities.yaml @@ -0,0 +1,41 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-drop-all-capabilities + annotations: + policies.kyverno.io/title: "Обязательный drop ALL capabilities" + policies.kyverno.io/category: Pod Security Standards (Restricted) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Каждый контейнер должен явно сбросить все capabilities через + securityContext.capabilities.drop: [ALL]. + Это часть профиля Restricted согласно Pod Security Standards. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: require-drop-all + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Контейнер '{{ element.name }}' не сбрасывает все capabilities. + Добавьте в securityContext: + capabilities: + drop: + - ALL + foreach: + - list: "request.object.spec.containers" + deny: + conditions: + all: + - key: "ALL" + operator: NotIn + value: "{{ element.securityContext.capabilities.drop[] || `[]` }}" diff --git a/02-validation/02-security/require-run-as-non-root.yaml b/02-validation/02-security/require-run-as-non-root.yaml new file mode 100644 index 0000000..fe5c5d3 --- /dev/null +++ b/02-validation/02-security/require-run-as-non-root.yaml @@ -0,0 +1,58 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-run-as-non-root + annotations: + policies.kyverno.io/title: "Запрет запуска от root" + policies.kyverno.io/category: Pod Security Standards (Restricted) + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Контейнеры не должны запускаться от пользователя root (UID 0). + Процесс от root может читать/писать файлы хоста через volume mounts + даже без привилегированного режима. + Установите runAsNonRoot: true или runAsUser >= 1000. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-runasnonroot-pod-level + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Под '{{ request.object.metadata.name }}' должен иметь + spec.securityContext.runAsNonRoot: true. + Это гарантирует, что ни один контейнер не запустится от root. + pattern: + spec: + securityContext: + runAsNonRoot: true + + - name: check-runasuser-not-root + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Контейнер '{{ element.name }}' использует runAsUser: 0 (root). + Установите runAsUser >= 1000. + foreach: + - list: "request.object.spec.containers" + deny: + conditions: + any: + - key: "{{ element.securityContext.runAsUser }}" + operator: Equals + value: 0 diff --git a/02-validation/02-security/require-seccomp-profile.yaml b/02-validation/02-security/require-seccomp-profile.yaml new file mode 100644 index 0000000..d1b1483 --- /dev/null +++ b/02-validation/02-security/require-seccomp-profile.yaml @@ -0,0 +1,52 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-seccomp-profile + annotations: + policies.kyverno.io/title: "Обязательный Seccomp профиль" + policies.kyverno.io/category: Pod Security Standards (Restricted) + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Seccomp ограничивает системные вызовы контейнера, уменьшая + поверхность атаки на ядро Linux. + Используйте RuntimeDefault (профиль рантайма) или Localhost (кастомный). +spec: + validationFailureAction: Enforce + background: true + rules: + - name: require-seccomp-profile + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Под '{{ request.object.metadata.name }}' не имеет seccomp профиля. + Добавьте в spec.securityContext: + seccompProfile: + type: RuntimeDefault + pattern: + spec: + securityContext: + seccompProfile: + type: "RuntimeDefault | Localhost" + + - name: disallow-unconfined-seccomp + match: + resources: + kinds: + - Pod + validate: + message: >- + Тип Unconfined отключает seccomp защиту. Используйте RuntimeDefault. + deny: + conditions: + any: + - key: "{{ request.object.spec.securityContext.seccompProfile.type }}" + operator: Equals + value: Unconfined diff --git a/02-validation/02-security/restrict-automount-sa-token.yaml b/02-validation/02-security/restrict-automount-sa-token.yaml new file mode 100644 index 0000000..a1c6714 --- /dev/null +++ b/02-validation/02-security/restrict-automount-sa-token.yaml @@ -0,0 +1,37 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-automount-service-account-token + annotations: + policies.kyverno.io/title: "Ограничение автомонтирования ServiceAccount токена" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + По умолчанию Kubernetes монтирует токен ServiceAccount в каждый под. + Если приложение не использует Kubernetes API, этот токен — лишняя + поверхность атаки. CIS Benchmark рекомендует отключать автомонтирование + там, где токен не нужен. +spec: + validationFailureAction: Audit + background: true + rules: + - name: check-automount-service-account + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + validate: + message: >- + Под '{{ request.object.metadata.name }}' автоматически монтирует + ServiceAccount токен (automountServiceAccountToken: true по умолчанию). + Если под не обращается к Kubernetes API, добавьте: + spec: + automountServiceAccountToken: false + pattern: + spec: + automountServiceAccountToken: false diff --git a/02-validation/02-security/test-resources/pod-insecure.yaml b/02-validation/02-security/test-resources/pod-insecure.yaml new file mode 100644 index 0000000..fbac3c5 --- /dev/null +++ b/02-validation/02-security/test-resources/pod-insecure.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-insecure + namespace: default +spec: + hostNetwork: true # нарушение: host namespace + hostPID: true # нарушение: host namespace + containers: + - name: app + image: nginx:1.25.3 + securityContext: + privileged: true # нарушение: привилегированный контейнер + runAsUser: 0 # нарушение: запуск от root + capabilities: + add: + - SYS_ADMIN # нарушение: опасная capability + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/02-validation/02-security/test-resources/pod-secure.yaml b/02-validation/02-security/test-resources/pod-secure.yaml new file mode 100644 index 0000000..aee2cec --- /dev/null +++ b/02-validation/02-security/test-resources/pod-secure.yaml @@ -0,0 +1,28 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-secure + namespace: default +spec: + securityContext: + runAsNonRoot: true + runAsUser: 1000 + seccompProfile: + type: RuntimeDefault + automountServiceAccountToken: false + containers: + - name: app + image: nginx:1.25.3 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" diff --git a/02-validation/03-reporting/README.md b/02-validation/03-reporting/README.md new file mode 100644 index 0000000..0cbe4ad --- /dev/null +++ b/02-validation/03-reporting/README.md @@ -0,0 +1,126 @@ +# Урок 2.3 — Обработка ошибок и отчётность + +## Файлы + +| Файл | Описание | +|------|----------| +| `prometheus-alert-rules.yaml` | PrometheusRule с алертами | +| `service-monitor.yaml` | ServiceMonitor для Prometheus Operator | + +## Работа с PolicyReport + +```bash +# Посмотреть все отчёты +kubectl get policyreports -A +kubectl get clusterpolicyreports + +# Детальный отчёт по namespace +kubectl describe policyreport -n production + +# Все нарушения в кластере (форматированный вывод) +kubectl get policyreports -A -o json | \ + jq -r '.items[] | .metadata.namespace as $ns | + .results[] | select(.result == "fail") | + "\($ns)/\(.resources[0].name)\t\(.policy)/\(.rule)\t\(.message)"' | \ + column -t -s $'\t' + +# Топ-10 политик по нарушениям +kubectl get policyreports -A -o json | \ + jq -r '[.items[].results[] | select(.result == "fail") | .policy] | + group_by(.) | map({policy: .[0], count: length}) | + sort_by(-.count)[:10][] | "\(.count)\t\(.policy)"' + +# Нарушения по конкретной политике +POLICY="require-resource-limits" +kubectl get policyreports -A -o json | \ + jq --arg p "$POLICY" \ + -r '.items[] | .metadata.namespace as $ns | + .results[] | select(.policy == $p and .result == "fail") | + "\($ns)/\(.resources[0].name): \(.message)"' + +# Compliance rate по namespace +kubectl get policyreports -A -o json | \ + jq -r '.items[] | .metadata.namespace as $ns | + {ns: $ns, pass: [.results[] | select(.result=="pass")] | length, + fail: [.results[] | select(.result=="fail")] | length} | + "\(.ns): pass=\(.pass) fail=\(.fail)"' +``` + +## Prometheus метрики + +```bash +# Проверить доступность метрик (port-forward если нет Ingress) +kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 & + +# Посмотреть все метрики +curl -s http://localhost:8000/metrics | grep kyverno_ + +# Ключевые метрики +curl -s http://localhost:8000/metrics | grep kyverno_policy_results_total +curl -s http://localhost:8000/metrics | grep kyverno_admission_review_duration +``` + +## Полезные PromQL запросы (для Grafana) + +```promql +# Compliance rate (цель: 1.0 = 100%) +sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) / +sum(rate(kyverno_policy_results_total[5m])) + +# Нарушений в час по политикам +topk(10, sum by(policy_name)( + increase(kyverno_policy_results_total{rule_result="fail"}[1h]) +)) + +# p95 латентность admission +histogram_quantile(0.95, + sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le) +) + +# Нарушения по namespace за сутки +sum by(resource_namespace)( + increase(kyverno_policy_results_total{rule_result="fail"}[24h]) +) +``` + +## Применение мониторинга + +```bash +# Применить ServiceMonitor (требует Prometheus Operator) +kubectl apply -f service-monitor.yaml + +# Применить правила алертов +kubectl apply -f prometheus-alert-rules.yaml + +# Проверить что правила подхватились +kubectl get prometheusrule -n kyverno +``` + +## Режим Audit для существующего кластера + +```bash +# Применить политику в Audit режиме +kubectl apply -f - <- + Admission controller Kyverno не отвечает более 1 минуты. + Проверьте поды: kubectl get pods -n kyverno + + - alert: KyvernoAdmissionLatencyHigh + expr: > + histogram_quantile(0.95, + sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le) + ) > 0.5 + for: 5m + labels: + severity: warning + annotations: + summary: "Высокая латентность Kyverno admission (p95 > 500ms)" + description: >- + p95 латентность: {{ $value | humanizeDuration }}. + Это замедляет деплойменты. Проверьте политики с apiCall в context. + + - alert: KyvernoAdmissionErrors + expr: > + rate(kyverno_admission_requests_total{ + admission_request_type="error" + }[5m]) > 0 + for: 2m + labels: + severity: critical + annotations: + summary: "Ошибки обработки запросов в Kyverno" + description: "Kyverno возвращает ошибки. Проверьте логи: kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller" + + - name: kyverno.policy + rules: + - alert: KyvernoCriticalPolicyViolation + expr: > + increase(kyverno_policy_results_total{ + rule_result="fail", + policy_name=~"disallow-privileged.*|disallow-host.*|disallow-dangerous.*" + }[5m]) > 0 + for: 0m + labels: + severity: critical + annotations: + summary: "Нарушение критической политики безопасности: {{ $labels.policy_name }}" + description: >- + Политика {{ $labels.policy_name }} была нарушена в namespace {{ $labels.resource_namespace }}. + Немедленно проверьте: kubectl get policyreports -n {{ $labels.resource_namespace }} + + - alert: KyvernoHighViolationRate + expr: > + sum(increase(kyverno_policy_results_total{rule_result="fail"}[1h])) > 50 + for: 5m + labels: + severity: warning + annotations: + summary: "Высокое количество нарушений политик (> 50 за час)" + description: >- + За последний час: {{ $value }} нарушений. + Проверьте отчёты: kubectl get policyreports -A + + - name: kyverno.performance + rules: + - alert: KyvernoCPUThrottling + expr: > + rate(container_cpu_cfs_throttled_seconds_total{ + namespace="kyverno", + container=~"kyverno.*" + }[5m]) > 0.1 + for: 10m + labels: + severity: warning + annotations: + summary: "CPU throttling Kyverno — возможна деградация производительности" + description: "Увеличьте CPU limit для Kyverno admission controller." + + - name: kyverno.recording + rules: + - record: kyverno:compliance_rate:5m + expr: > + sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) / + sum(rate(kyverno_policy_results_total[5m])) diff --git a/02-validation/03-reporting/service-monitor.yaml b/02-validation/03-reporting/service-monitor.yaml new file mode 100644 index 0000000..cbe8bce --- /dev/null +++ b/02-validation/03-reporting/service-monitor.yaml @@ -0,0 +1,17 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: kyverno-metrics + namespace: kyverno + labels: + prometheus: kube-prometheus +spec: + selector: + matchLabels: + app.kubernetes.io/name: kyverno + app.kubernetes.io/component: admission-controller + endpoints: + - port: metrics-port + interval: 30s + path: /metrics + honorLabels: true diff --git a/03-mutation/01-basics/README.md b/03-mutation/01-basics/README.md new file mode 100644 index 0000000..7fac4cf --- /dev/null +++ b/03-mutation/01-basics/README.md @@ -0,0 +1,82 @@ +# Урок 3.1 — Основы мутации ресурсов + +## Файлы + +| Файл | Описание | +|------|----------| +| `add-standard-labels.yaml` | Автодобавление лейблов managed-by, monitored, cost-center | +| `add-default-resource-requests.yaml` | Дефолтные requests для контейнеров без них | +| `add-default-security-context.yaml` | Безопасный SecurityContext по умолчанию | + +## Как проверить результат мутации + +```bash +# Применить политики +kubectl apply -f . + +# Создать под без лейблов и requests +kubectl apply -f test-resources/pod-before-mutation.yaml + +# Посмотреть что изменилось — сравнить с оригиналом +kubectl get pod pod-before-mutation -o yaml | \ + grep -A 20 "labels:" + +kubectl get pod pod-before-mutation -o yaml | \ + grep -A 10 "resources:" + +# Dry-run — увидеть результат без реального создания +kubectl apply -f test-resources/pod-before-mutation.yaml \ + --dry-run=server -o yaml | \ + diff test-resources/pod-before-mutation.yaml - || true +``` + +## Отладка мутаций через Kyverno CLI + +```bash +# Показывает изменённый ресурс в stdout +kyverno apply add-standard-labels.yaml \ + --resource test-resources/pod-before-mutation.yaml \ + --detailed-results + +# Сохранить мутированный ресурс +kyverno apply . \ + --resource test-resources/pod-before-mutation.yaml \ + > test-resources/pod-after-mutation.yaml + +cat test-resources/pod-after-mutation.yaml +``` + +## Типы patch операций + +```yaml +# Strategic Merge — наиболее читаемый, для добавления полей +mutate: + patchStrategicMerge: + metadata: + labels: + +(new-label): value # + = добавить только если нет + +# JSON Patch RFC 6902 — для точечных изменений и удаления полей +mutate: + patchesJson6902: |- + - op: add + path: /metadata/annotations/company.com~1reviewed # ~1 = / в имени + value: "true" + - op: replace + path: /spec/replicas + value: 3 + - op: remove + path: /spec/template/metadata/annotations/debug +``` + +## Порядок применения политик + +Если несколько политик мутируют один ресурс — они применяются +в **алфавитном порядке** имён политик. + +```bash +# Управляйте порядком через числовые префиксы в именах: +# 01-add-labels → 02-add-annotations → 03-add-sidecar +kubectl get mutatingwebhookconfigurations -o yaml | \ + grep "name:" | grep kyverno +``` diff --git a/03-mutation/01-basics/add-default-resource-requests.yaml b/03-mutation/01-basics/add-default-resource-requests.yaml new file mode 100644 index 0000000..1b9287d --- /dev/null +++ b/03-mutation/01-basics/add-default-resource-requests.yaml @@ -0,0 +1,36 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-default-resource-requests + annotations: + policies.kyverno.io/title: "Дефолтные resource requests" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Автоматически добавляет дефолтные resource requests контейнерам, + у которых они не указаны. Requests нужны для корректного планирования. + Символ + добавляет только отсутствующие поля. +spec: + rules: + - name: add-default-requests + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + mutate: + foreach: + - list: "request.object.spec.containers" + patchStrategicMerge: + spec: + containers: + - name: "{{ element.name }}" + resources: + requests: + +(memory): "128Mi" + +(cpu): "100m" diff --git a/03-mutation/01-basics/add-default-security-context.yaml b/03-mutation/01-basics/add-default-security-context.yaml new file mode 100644 index 0000000..2d8344b --- /dev/null +++ b/03-mutation/01-basics/add-default-security-context.yaml @@ -0,0 +1,55 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-default-security-context + annotations: + policies.kyverno.io/title: "Дефолтный SecurityContext" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Автоматически применяет безопасный SecurityContext к подам и контейнерам, + если поля не заданы явно. Работает в связке с validation политиками + (сначала mutate, потом validate). +spec: + rules: + - name: add-pod-security-context + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + mutate: + patchStrategicMerge: + spec: + +(securityContext): + +(runAsNonRoot): true + +(runAsUser): 1000 + +(seccompProfile): + +(type): RuntimeDefault + + - name: add-container-security-context + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + mutate: + foreach: + - list: "request.object.spec.containers" + patchStrategicMerge: + spec: + containers: + - name: "{{ element.name }}" + +(securityContext): + +(allowPrivilegeEscalation): false + +(readOnlyRootFilesystem): true + +(capabilities): + +(drop): + - ALL diff --git a/03-mutation/01-basics/add-standard-labels.yaml b/03-mutation/01-basics/add-standard-labels.yaml new file mode 100644 index 0000000..c0209c3 --- /dev/null +++ b/03-mutation/01-basics/add-standard-labels.yaml @@ -0,0 +1,33 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: add-standard-labels + annotations: + policies.kyverno.io/title: "Добавление стандартных лейблов" + policies.kyverno.io/category: Governance + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Deployment,StatefulSet,DaemonSet + policies.kyverno.io/description: >- + Автоматически добавляет стандартные лейблы к workload ресурсам, + если они отсутствуют. Символ + означает "добавить только если нет". +spec: + rules: + - name: add-managed-by-label + match: + resources: + kinds: + - Deployment + - StatefulSet + - DaemonSet + exclude: + resources: + namespaces: + - kube-system + mutate: + patchStrategicMerge: + metadata: + labels: + +(managed-by): "kyverno" + +(monitored): "true" + # Динамически берём namespace как cost-center + +(cost-center): "{{ request.object.metadata.namespace }}" diff --git a/03-mutation/01-basics/test-resources/pod-before-mutation.yaml b/03-mutation/01-basics/test-resources/pod-before-mutation.yaml new file mode 100644 index 0000000..8ce1aff --- /dev/null +++ b/03-mutation/01-basics/test-resources/pod-before-mutation.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-before-mutation + namespace: default + # нет лейблов managed-by, monitored, cost-center +spec: + containers: + - name: app + image: nginx:1.25.3 + # нет resources.requests — будут добавлены мутацией + # нет securityContext — будет добавлен мутацией + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/03-mutation/02-sidecar/README.md b/03-mutation/02-sidecar/README.md new file mode 100644 index 0000000..548872b --- /dev/null +++ b/03-mutation/02-sidecar/README.md @@ -0,0 +1,82 @@ +# Урок 3.2 — Автоматическое добавление sidecar контейнеров + +## Файлы + +| Файл | Описание | +|------|----------| +| `inject-fluent-bit.yaml` | Opt-in injection Fluent Bit (по аннотации) | +| `fluent-bit-configmap.yaml` | ConfigMap с конфигурацией Fluent Bit | +| `inject-prometheus-exporter.yaml` | Opt-in injection Prometheus exporter | + +## Подготовка + +```bash +# Применить ConfigMap (нужен до создания подов с Fluent Bit) +kubectl apply -f fluent-bit-configmap.yaml + +# Применить политики injection +kubectl apply -f inject-fluent-bit.yaml +kubectl apply -f inject-prometheus-exporter.yaml +``` + +## Тестирование Fluent Bit injection + +```bash +# Под БЕЗ аннотации — sidecar не добавится +kubectl apply -f test-resources/pod-no-logging.yaml +kubectl get pod pod-no-logging -o jsonpath='{.spec.containers[*].name}' +# Вывод: app + +# Под С аннотацией — sidecar добавится автоматически +kubectl apply -f test-resources/pod-with-logging.yaml +kubectl get pod pod-with-logging -o jsonpath='{.spec.containers[*].name}' +# Вывод: app fluent-bit + +# Проверить конфигурацию sidecar +kubectl describe pod pod-with-logging | grep -A 30 "fluent-bit:" + +# Удалить тесты +kubectl delete pod pod-no-logging pod-with-logging +``` + +## Тестирование Prometheus exporter injection + +```bash +kubectl apply -f test-resources/pod-with-monitoring.yaml + +kubectl get pod pod-with-monitoring \ + -o jsonpath='{.spec.containers[*].name}' +# Вывод: app prometheus-exporter + +# Проверить аннотации +kubectl get pod pod-with-monitoring \ + -o jsonpath='{.metadata.annotations}' | jq . + +kubectl delete pod pod-with-monitoring +``` + +## Opt-in vs Opt-out + +```yaml +# Opt-in: срабатывает только при наличии аннотации +preconditions: + - key: "{{ request.object.metadata.annotations.\"logging.company.com/enabled\" }}" + operator: Equals + value: "true" + +# Opt-out: срабатывает везде, кроме явно исключённых +preconditions: + - key: "{{ request.object.metadata.annotations.\"logging.company.com/disabled\" || 'false' }}" + operator: NotEquals + value: "true" +``` + +## Защита от дублирования + +```bash +# Попробовать создать под, который уже содержит fluent-bit +kubectl apply -f test-resources/pod-already-has-sidecar.yaml +# Sidecar НЕ будет дублирован (precondition NotIn защищает) +kubectl get pod pod-already-has-sidecar \ + -o jsonpath='{.spec.containers[*].name}' +``` diff --git a/03-mutation/02-sidecar/fluent-bit-configmap.yaml b/03-mutation/02-sidecar/fluent-bit-configmap.yaml new file mode 100644 index 0000000..845f5f6 --- /dev/null +++ b/03-mutation/02-sidecar/fluent-bit-configmap.yaml @@ -0,0 +1,41 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: fluent-bit-config + namespace: default +data: + fluent-bit.conf: | + [SERVICE] + Flush 5 + Log_Level info + Daemon off + + [INPUT] + Name tail + Path /var/log/containers/*.log + Parser docker + Tag kube.* + Refresh_Interval 5 + Mem_Buf_Limit 5MB + Skip_Long_Lines On + + [FILTER] + Name kubernetes + Match kube.* + Kube_URL https://kubernetes.default.svc:443 + Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt + Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token + Merge_Log On + Keep_Log Off + + [OUTPUT] + Name stdout + Match * + # Замените на ваш реальный output (Elasticsearch, Loki, etc.) + + parsers.conf: | + [PARSER] + Name docker + Format json + Time_Key time + Time_Format %Y-%m-%dT%H:%M:%S.%L diff --git a/03-mutation/02-sidecar/inject-fluent-bit.yaml b/03-mutation/02-sidecar/inject-fluent-bit.yaml new file mode 100644 index 0000000..29f3908 --- /dev/null +++ b/03-mutation/02-sidecar/inject-fluent-bit.yaml @@ -0,0 +1,57 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: inject-fluent-bit-sidecar + annotations: + policies.kyverno.io/title: "Автовнедрение Fluent Bit sidecar" + policies.kyverno.io/category: Logging + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Автоматически добавляет Fluent Bit sidecar контейнер ко всем подам + с аннотацией logging.company.com/enabled: "true". + Подход Opt-in: разработчик явно запрашивает injection. + ЗАМЕНИТЕ образ registry.company.com на ваш внутренний реестр. +spec: + rules: + - name: inject-fluent-bit + match: + resources: + kinds: + - Pod + preconditions: + all: + # Opt-in: только поды с явной аннотацией + - key: "{{ request.object.metadata.annotations.\"logging.company.com/enabled\" }}" + operator: Equals + value: "true" + # Не добавлять если sidecar уже есть (защита от дублирования) + - key: "fluent-bit" + operator: NotIn + value: "{{ request.object.spec.containers[].name }}" + mutate: + patchStrategicMerge: + spec: + containers: + - name: fluent-bit + image: fluent/fluent-bit:2.1 # замените на внутренний реестр + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + volumeMounts: + - name: varlog + mountPath: /var/log + readOnly: true + - name: fluent-bit-config + mountPath: /fluent-bit/etc/ + volumes: + - name: varlog + hostPath: + path: /var/log + - name: fluent-bit-config + configMap: + name: fluent-bit-config diff --git a/03-mutation/02-sidecar/inject-prometheus-exporter.yaml b/03-mutation/02-sidecar/inject-prometheus-exporter.yaml new file mode 100644 index 0000000..db6bb82 --- /dev/null +++ b/03-mutation/02-sidecar/inject-prometheus-exporter.yaml @@ -0,0 +1,58 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: inject-prometheus-exporter + annotations: + policies.kyverno.io/title: "Автовнедрение Prometheus exporter" + policies.kyverno.io/category: Monitoring + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Добавляет node-exporter sidecar ко всем подам с аннотацией + monitoring.company.com/scrape: "true". + Порт scraping берётся из аннотации monitoring.company.com/port + или дефолт 8080. +spec: + rules: + - name: inject-exporter + match: + resources: + kinds: + - Pod + preconditions: + all: + - key: "{{ request.object.metadata.annotations.\"monitoring.company.com/scrape\" }}" + operator: Equals + value: "true" + - key: "prometheus-exporter" + operator: NotIn + value: "{{ request.object.spec.containers[].name }}" + mutate: + patchStrategicMerge: + metadata: + annotations: + # Аннотация для Prometheus autodiscovery + +(prometheus.io/scrape): "true" + +(prometheus.io/port): >- + {{ request.object.metadata.annotations.\"monitoring.company.com/port\" || '9100' }} + +(prometheus.io/path): "/metrics" + spec: + containers: + - name: prometheus-exporter + image: prom/node-exporter:v1.7.0 + ports: + - name: metrics + containerPort: 9100 + protocol: TCP + resources: + limits: + cpu: 100m + memory: 64Mi + requests: + cpu: 50m + memory: 32Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65534 diff --git a/03-mutation/02-sidecar/test-resources/pod-already-has-sidecar.yaml b/03-mutation/02-sidecar/test-resources/pod-already-has-sidecar.yaml new file mode 100644 index 0000000..2ebe003 --- /dev/null +++ b/03-mutation/02-sidecar/test-resources/pod-already-has-sidecar.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-already-has-sidecar + namespace: default + annotations: + logging.company.com/enabled: "true" +spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + limits: + memory: "128Mi" + cpu: "100m" + - name: fluent-bit # sidecar уже есть — injection не сработает + image: fluent/fluent-bit:2.1 + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/03-mutation/02-sidecar/test-resources/pod-no-logging.yaml b/03-mutation/02-sidecar/test-resources/pod-no-logging.yaml new file mode 100644 index 0000000..0a523ba --- /dev/null +++ b/03-mutation/02-sidecar/test-resources/pod-no-logging.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-no-logging + namespace: default + # нет аннотации logging.company.com/enabled — sidecar не добавится +spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/03-mutation/02-sidecar/test-resources/pod-with-logging.yaml b/03-mutation/02-sidecar/test-resources/pod-with-logging.yaml new file mode 100644 index 0000000..fd98c82 --- /dev/null +++ b/03-mutation/02-sidecar/test-resources/pod-with-logging.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-logging + namespace: default + annotations: + logging.company.com/enabled: "true" # триггер для injection +spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/03-mutation/02-sidecar/test-resources/pod-with-monitoring.yaml b/03-mutation/02-sidecar/test-resources/pod-with-monitoring.yaml new file mode 100644 index 0000000..c9d63f0 --- /dev/null +++ b/03-mutation/02-sidecar/test-resources/pod-with-monitoring.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-with-monitoring + namespace: default + annotations: + monitoring.company.com/scrape: "true" + monitoring.company.com/port: "8080" # кастомный порт scraping +spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/03-mutation/03-advanced/README.md b/03-mutation/03-advanced/README.md new file mode 100644 index 0000000..859cbb1 --- /dev/null +++ b/03-mutation/03-advanced/README.md @@ -0,0 +1,131 @@ +# Урок 3.3 — Продвинутые техники мутации и переменные + +## Файлы + +| Файл | Описание | +|------|----------| +| `kyverno-global-config.yaml` | ConfigMap с централизованными настройками | +| `set-dynamic-resource-limits.yaml` | Лимиты из ConfigMap по типу сервиса | +| `add-creator-audit-annotation.yaml` | Автоматический audit trail | + +## Подготовка + +```bash +# Сначала создать ConfigMap с конфигурацией +kubectl apply -f kyverno-global-config.yaml + +# Затем политики +kubectl apply -f set-dynamic-resource-limits.yaml +kubectl apply -f add-creator-audit-annotation.yaml +``` + +## Тест динамических лимитов + +```bash +# Pod типа "api" — получит 512Mi/500m +kubectl apply -f - <- + При создании Deployment или StatefulSet автоматически добавляет + аннотации: кто создал, когда, из каких групп. + Создаёт автоматический audit trail без дополнительных инструментов. +spec: + rules: + - name: add-creator-annotation + match: + resources: + kinds: + - Deployment + - StatefulSet + preconditions: + any: + - key: "{{ request.operation }}" + operator: Equals + value: CREATE + mutate: + patchStrategicMerge: + metadata: + annotations: + audit.company.com/created-by: "{{ request.userInfo.username }}" + audit.company.com/created-at: "{{ time_now_utc() }}" + audit.company.com/user-groups: >- + {{ request.userInfo.groups | join(', ', @) }} diff --git a/03-mutation/03-advanced/kyverno-global-config.yaml b/03-mutation/03-advanced/kyverno-global-config.yaml new file mode 100644 index 0000000..9dc0b25 --- /dev/null +++ b/03-mutation/03-advanced/kyverno-global-config.yaml @@ -0,0 +1,22 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kyverno-global-config + namespace: kyverno + labels: + app: kyverno-config +data: + # Дефолтные resource limits по типу сервиса + api_memory: "512Mi" + api_cpu: "500m" + worker_memory: "1Gi" + worker_cpu: "250m" + default_memory: "256Mi" + default_cpu: "250m" + + # Node selector defaults + default_node_type: "standard" + region: "eu-west-1" + + # Allowed registries (через запятую) + allowed_registries: "registry.company.com,gcr.io/company-project" diff --git a/03-mutation/03-advanced/set-dynamic-resource-limits.yaml b/03-mutation/03-advanced/set-dynamic-resource-limits.yaml new file mode 100644 index 0000000..b7ea406 --- /dev/null +++ b/03-mutation/03-advanced/set-dynamic-resource-limits.yaml @@ -0,0 +1,44 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: set-dynamic-resource-limits + annotations: + policies.kyverno.io/title: "Динамические resource limits из ConfigMap" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Устанавливает resource limits на основе лейбла service-type пода. + Значения берутся из ConfigMap kyverno-global-config в namespace kyverno. + Изменение лимитов — это kubectl edit configmap, не изменение политики. + Лейблы: service-type: api | worker | (default) +spec: + rules: + - name: set-limits-from-config + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + context: + - name: globalConfig + configMap: + name: kyverno-global-config + namespace: kyverno + mutate: + foreach: + - list: "request.object.spec.containers" + patchStrategicMerge: + spec: + containers: + - name: "{{ element.name }}" + resources: + limits: + +(memory): >- + {{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_memory\" || '256Mi' }} + +(cpu): >- + {{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_cpu\" || '250m' }} diff --git a/04-generation/01-configmaps-secrets/README.md b/04-generation/01-configmaps-secrets/README.md new file mode 100644 index 0000000..2224d88 --- /dev/null +++ b/04-generation/01-configmaps-secrets/README.md @@ -0,0 +1,96 @@ +# Урок 4.1 — Автоматическое создание ConfigMaps и Secrets + +## Файлы + +| Файл | Триггер | Что создаёт | +|------|---------|-------------| +| `generate-namespace-config.yaml` | Namespace | ConfigMap с мета-данными namespace | +| `generate-default-networkpolicy.yaml` | Namespace | NetworkPolicy deny-all | +| `generate-resource-quota.yaml` | Namespace | ResourceQuota по tier | +| `quota-defaults-configmap.yaml` | — | ConfigMap с квотами (нужен заранее) | +| `clone-registry-secret.yaml` | Namespace | Копия registry Secret | +| `generate-developer-rolebinding.yaml` | Namespace+label | RoleBinding для команды | +| `developer-clusterrole.yaml` | — | ClusterRole developer (нужна заранее) | + +## Подготовка (применить до политик) + +```bash +# 1. Создать ConfigMap с квотами +kubectl apply -f quota-defaults-configmap.yaml + +# 2. Создать ClusterRole developer +kubectl apply -f developer-clusterrole.yaml + +# 3. Создать мастер-секрет для registry (замените данные на реальные) +kubectl create secret docker-registry registry-credentials-master \ + --docker-server=registry.company.com \ + --docker-username=robot-account \ + --docker-password=your-token \ + --namespace=kyverno + +# 4. Применить все политики +kubectl apply -f generate-namespace-config.yaml +kubectl apply -f generate-default-networkpolicy.yaml +kubectl apply -f generate-resource-quota.yaml +kubectl apply -f clone-registry-secret.yaml +kubectl apply -f generate-developer-rolebinding.yaml +``` + +## Тест: создать namespace и проверить генерацию + +```bash +# Создать тестовый namespace +kubectl create namespace test-team \ + --dry-run=client -o yaml | \ + kubectl apply -f - + +kubectl label namespace test-team \ + team=platform \ + tier=standard \ + environment=staging + +# Подождать ~5 секунд для генерации, затем проверить +kubectl get configmap,networkpolicy,resourcequota,rolebinding \ + -n test-team + +# Детали сгенерированных ресурсов +kubectl get configmap namespace-config -n test-team -o yaml +kubectl get networkpolicy default-deny-all -n test-team -o yaml +kubectl get resourcequota default-quota -n test-team -o yaml +``` + +## Тест synchronize — защита от случайного удаления + +```bash +# Удалить NetworkPolicy вручную +kubectl delete networkpolicy default-deny-all -n test-team + +# Kyverno восстановит её через несколько секунд +sleep 10 +kubectl get networkpolicy -n test-team +# default-deny-all снова присутствует +``` + +## Тест cascade delete + +```bash +# Удалить namespace — все сгенерированные ресурсы удалятся вместе с ним +kubectl delete namespace test-team + +# Проверить что ресурсы не зависли +kubectl get generaterequests -n kyverno +``` + +## Мониторинг GenerateRequests + +```bash +# Просмотр очереди генерации +kubectl get generaterequests -n kyverno -o wide + +# Детали при ошибке +kubectl describe generaterequests -n kyverno | grep -A 20 "Status:" + +# Метрики генерации +kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 & +curl -s http://localhost:8000/metrics | grep kyverno_generate +``` diff --git a/04-generation/01-configmaps-secrets/clone-registry-secret.yaml b/04-generation/01-configmaps-secrets/clone-registry-secret.yaml new file mode 100644 index 0000000..3f0a140 --- /dev/null +++ b/04-generation/01-configmaps-secrets/clone-registry-secret.yaml @@ -0,0 +1,39 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: clone-registry-secret + annotations: + policies.kyverno.io/title: "Копирование Registry Secret во все Namespace" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + При создании Namespace копирует Secret с кредентиалами реестра + из namespace kyverno (мастер-копия) в новый namespace. + synchronize: true — при обновлении мастер-секрета все копии обновятся. + Создайте мастер-секрет: kubectl create secret docker-registry + registry-credentials-master -n kyverno ... +spec: + rules: + - name: clone-registry-credentials + match: + resources: + kinds: + - Namespace + exclude: + resources: + names: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + apiVersion: v1 + kind: Secret + name: registry-credentials + namespace: "{{ request.object.metadata.name }}" + synchronize: true + # clone копирует существующий ресурс вместо создания из шаблона + clone: + namespace: kyverno + name: registry-credentials-master diff --git a/04-generation/01-configmaps-secrets/developer-clusterrole.yaml b/04-generation/01-configmaps-secrets/developer-clusterrole.yaml new file mode 100644 index 0000000..164ae42 --- /dev/null +++ b/04-generation/01-configmaps-secrets/developer-clusterrole.yaml @@ -0,0 +1,36 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: developer + labels: + app: kyverno-config +rules: +- apiGroups: [""] + resources: + - pods + - pods/log + - pods/exec + - services + - configmaps + - persistentvolumeclaims + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["apps"] + resources: + - deployments + - statefulsets + - daemonsets + - replicasets + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["batch"] + resources: + - jobs + - cronjobs + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +- apiGroups: ["networking.k8s.io"] + resources: + - ingresses + verbs: ["get", "list", "watch", "create", "update", "patch"] +- apiGroups: [""] + resources: + - secrets + verbs: ["get", "list", "watch"] # только чтение секретов diff --git a/04-generation/01-configmaps-secrets/generate-default-networkpolicy.yaml b/04-generation/01-configmaps-secrets/generate-default-networkpolicy.yaml new file mode 100644 index 0000000..760e51f --- /dev/null +++ b/04-generation/01-configmaps-secrets/generate-default-networkpolicy.yaml @@ -0,0 +1,51 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: generate-default-networkpolicy + annotations: + policies.kyverno.io/title: "Генерация NetworkPolicy по умолчанию" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + При создании Namespace автоматически создаёт NetworkPolicy + "запрети весь трафик по умолчанию". Zero Trust сетевая модель: + команды явно разрешают нужный трафик поверх этой базовой политики. +spec: + rules: + - name: generate-deny-all-networkpolicy + match: + resources: + kinds: + - Namespace + exclude: + resources: + names: + - kube-system + - kube-public + - kube-node-lease + - kyverno + - monitoring + - logging + generate: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + name: default-deny-all + namespace: "{{ request.object.metadata.name }}" + synchronize: true + data: + kind: NetworkPolicy + apiVersion: networking.k8s.io/v1 + metadata: + name: default-deny-all + labels: + generated-by: kyverno + annotations: + description: >- + Запрещает весь входящий и исходящий трафик по умолчанию. + Добавьте явные NetworkPolicy для разрешения нужного трафика. + spec: + podSelector: {} # применяется ко всем подам + policyTypes: + - Ingress + - Egress diff --git a/04-generation/01-configmaps-secrets/generate-developer-rolebinding.yaml b/04-generation/01-configmaps-secrets/generate-developer-rolebinding.yaml new file mode 100644 index 0000000..38b6ade --- /dev/null +++ b/04-generation/01-configmaps-secrets/generate-developer-rolebinding.yaml @@ -0,0 +1,52 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: generate-developer-rolebinding + annotations: + policies.kyverno.io/title: "Генерация RoleBinding для команды" + policies.kyverno.io/category: RBAC + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + При создании Namespace с лейблом team: автоматически создаёт + RoleBinding, дающий группе -developers права ClusterRole developer. + Namespace с лейблом team=payments → группа payments-developers получает доступ. +spec: + rules: + - name: generate-team-rolebinding + match: + resources: + kinds: + - Namespace + selector: + matchExpressions: + - key: team + operator: Exists + exclude: + resources: + names: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + name: developer-access + namespace: "{{ request.object.metadata.name }}" + synchronize: true + data: + kind: RoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: developer-access + labels: + generated-by: kyverno + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: developer # ClusterRole должна существовать + subjects: + - kind: Group + name: "{{ request.object.metadata.labels.team }}-developers" + apiGroup: rbac.authorization.k8s.io diff --git a/04-generation/01-configmaps-secrets/generate-namespace-config.yaml b/04-generation/01-configmaps-secrets/generate-namespace-config.yaml new file mode 100644 index 0000000..d0d28cb --- /dev/null +++ b/04-generation/01-configmaps-secrets/generate-namespace-config.yaml @@ -0,0 +1,45 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: generate-namespace-config + annotations: + policies.kyverno.io/title: "Генерация ConfigMap при создании Namespace" + policies.kyverno.io/category: Governance + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + При создании нового Namespace автоматически создаёт ConfigMap + с базовой конфигурацией. synchronize: true — ConfigMap обновляется + при изменении namespace и удаляется вместе с ним. +spec: + rules: + - name: generate-config + match: + resources: + kinds: + - Namespace + exclude: + resources: + names: + - kube-system + - kube-public + - kube-node-lease + - kyverno + generate: + apiVersion: v1 + kind: ConfigMap + name: namespace-config + namespace: "{{ request.object.metadata.name }}" + synchronize: true + data: + kind: ConfigMap + metadata: + labels: + generated-by: kyverno + policy: generate-namespace-config + data: + namespace: "{{ request.object.metadata.name }}" + environment: "{{ request.object.metadata.labels.environment || 'unknown' }}" + team: "{{ request.object.metadata.labels.team || 'unknown' }}" + created-at: "{{ time_now_utc() }}" + documentation: "https://wiki.company.com/kubernetes/namespaces" diff --git a/04-generation/01-configmaps-secrets/generate-resource-quota.yaml b/04-generation/01-configmaps-secrets/generate-resource-quota.yaml new file mode 100644 index 0000000..9d57037 --- /dev/null +++ b/04-generation/01-configmaps-secrets/generate-resource-quota.yaml @@ -0,0 +1,59 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: generate-resource-quota + annotations: + policies.kyverno.io/title: "Генерация ResourceQuota для Namespace" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Namespace + policies.kyverno.io/description: >- + При создании Namespace генерирует ResourceQuota. + Квота зависит от лейбла tier: standard | premium. + Значения квот берутся из ConfigMap quota-defaults. +spec: + rules: + - name: generate-quota + match: + resources: + kinds: + - Namespace + exclude: + resources: + names: + - kube-system + - kube-public + - kube-node-lease + - kyverno + context: + - name: quotaConfig + configMap: + name: quota-defaults + namespace: kyverno + generate: + apiVersion: v1 + kind: ResourceQuota + name: default-quota + namespace: "{{ request.object.metadata.name }}" + synchronize: true + data: + kind: ResourceQuota + apiVersion: v1 + metadata: + name: default-quota + labels: + generated-by: kyverno + spec: + hard: + # Квота CPU зависит от tier namespace + requests.cpu: >- + {{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_cpu_request\" || '4' }} + requests.memory: >- + {{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_memory_request\" || '8Gi' }} + limits.cpu: >- + {{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_cpu_limit\" || '8' }} + limits.memory: >- + {{ quotaConfig.data.\"{{ request.object.metadata.labels.tier || 'standard' }}_memory_limit\" || '16Gi' }} + pods: "50" + services: "20" + persistentvolumeclaims: "10" diff --git a/04-generation/01-configmaps-secrets/quota-defaults-configmap.yaml b/04-generation/01-configmaps-secrets/quota-defaults-configmap.yaml new file mode 100644 index 0000000..18ad99f --- /dev/null +++ b/04-generation/01-configmaps-secrets/quota-defaults-configmap.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: quota-defaults + namespace: kyverno + labels: + app: kyverno-config +data: + # Standard tier (по умолчанию) + standard_cpu_request: "4" + standard_memory_request: "8Gi" + standard_cpu_limit: "8" + standard_memory_limit: "16Gi" + + # Premium tier (для нагруженных сервисов) + premium_cpu_request: "16" + premium_memory_request: "32Gi" + premium_cpu_limit: "32" + premium_memory_limit: "64Gi" + + # Dev tier (для разработки) + dev_cpu_request: "2" + dev_memory_request: "4Gi" + dev_cpu_limit: "4" + dev_memory_limit: "8Gi" diff --git a/04-generation/02-lifecycle/README.md b/04-generation/02-lifecycle/README.md new file mode 100644 index 0000000..4fe853a --- /dev/null +++ b/04-generation/02-lifecycle/README.md @@ -0,0 +1,106 @@ +# Урок 4.2 — Управление жизненным циклом генерируемых ресурсов + +## Файлы + +| Файл | Описание | +|------|----------| +| `cleanup-debug-pods.yaml` | CleanupPolicy — автоудаление debug подов старше 4ч | + +## Параметр synchronize — детально + +```yaml +generate: + synchronize: true # живая ссылка: обновляется, восстанавливается, удаляется вместе + synchronize: false # создать один раз, дальше независимо +``` + +| Событие | synchronize: true | synchronize: false | +|---------|-------------------|-------------------| +| Источник изменился | Генерируемый обновляется | Без изменений | +| Генерируемый удалён | Восстанавливается | Не восстанавливается | +| Источник удалён | Генерируемый удаляется | Остаётся | + +## Применение CleanupPolicy + +```bash +# CleanupPolicy требует Kyverno >= 1.9 +kubectl apply -f cleanup-debug-pods.yaml + +# Проверить статус +kubectl get clustercleanuppolicies +``` + +## Тест CleanupPolicy + +```bash +# Создать debug-под +kubectl run debug-test \ + --image=busybox:1.36 \ + --labels="purpose=debug" \ + --restart=Never \ + -- sleep 3600 + +# Принудительно запустить cleanup (изменив schedule на ближайшую минуту) +# Или подождать час и проверить: +kubectl get pod debug-test + +# Проверить логи cleanup controller +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=cleanup-controller \ + --tail=30 +``` + +## Диагностика GenerateRequests + +```bash +# Все GenerateRequests +kubectl get generaterequests -n kyverno + +# Зависшие в Processing статусе +kubectl get generaterequests -n kyverno \ + -o jsonpath='{range .items[?(@.status.state=="Failed")]}{.metadata.name}{"\n"}{end}' + +# Детали конкретного +kubectl describe generaterequest -n kyverno + +# Типичные причины ошибок: +# 1. Нет прав у Kyverno ServiceAccount +kubectl get clusterrolebinding | grep kyverno +# 2. Ресурс уже существует (не управляется Kyverno) +# 3. Синтаксическая ошибка в шаблоне generate +``` + +## Взять под управление существующий ресурс + +```bash +# Если ресурс уже существует и нужно передать его Kyverno: +kubectl label configmap namespace-config \ + -n my-namespace \ + kyverno.io/managed=true + +# Или удалить ресурс, Kyverno пересоздаст его +kubectl delete configmap namespace-config -n my-namespace +# Kyverno создаст новый через background controller +``` + +## Bootstrap Namespace — полный сценарий + +```bash +# Одна команда создаёт полностью настроенный namespace: +kubectl create namespace payments-service +kubectl label namespace payments-service \ + team=payments \ + tier=premium \ + environment=production + +# Через ~10 секунд: +kubectl get all,configmap,secret,networkpolicy,resourcequota,rolebinding \ + -n payments-service + +# Ожидаемый результат: +# configmap/namespace-config +# networkpolicy/default-deny-all +# resourcequota/default-quota (с premium квотами) +# secret/registry-credentials +# rolebinding/developer-access (для группы payments-developers) +``` diff --git a/04-generation/02-lifecycle/cleanup-debug-pods.yaml b/04-generation/02-lifecycle/cleanup-debug-pods.yaml new file mode 100644 index 0000000..c4e36a9 --- /dev/null +++ b/04-generation/02-lifecycle/cleanup-debug-pods.yaml @@ -0,0 +1,29 @@ +apiVersion: kyverno.io/v2alpha1 +kind: ClusterCleanupPolicy +metadata: + name: cleanup-debug-pods + annotations: + policies.kyverno.io/title: "Автоудаление debug подов" + policies.kyverno.io/category: Governance + policies.kyverno.io/severity: low + policies.kyverno.io/description: >- + Удаляет поды с лейблом purpose=debug старше 4 часов. + Запускается каждый час по расписанию. + Предотвращает накопление забытых отладочных подов в кластере. +spec: + schedule: "0 * * * *" # каждый час в :00 + match: + resources: + kinds: + - Pod + namespaces: + - default + - staging + selector: + matchLabels: + purpose: debug + conditions: + any: + - key: "{{ time_since('', request.object.metadata.creationTimestamp, '') }}" + operator: GreaterThan + value: "4h" diff --git a/05-variables/01-configmaps/README.md b/05-variables/01-configmaps/README.md new file mode 100644 index 0000000..657e91e --- /dev/null +++ b/05-variables/01-configmaps/README.md @@ -0,0 +1,134 @@ +# Урок 5.1 — Переменные окружения и ConfigMaps + +## Файлы + +| Файл | Описание | +|------|----------| +| `kyverno-global-config.yaml` | Централизованный ConfigMap с настройками | +| `set-resource-limits-from-config.yaml` | Мутация: лимиты из ConfigMap | +| `inherit-namespace-labels.yaml` | Мутация: лейблы из Namespace (через apiCall) | + +## Применение + +```bash +# Сначала ConfigMap +kubectl apply -f kyverno-global-config.yaml + +# Затем политики +kubectl apply -f set-resource-limits-from-config.yaml +kubectl apply -f inherit-namespace-labels.yaml +``` + +## Типы источников переменных в Kyverno + +```yaml +# 1. Данные запроса (всегда доступны) +{{ request.object.metadata.name }} +{{ request.object.metadata.namespace }} +{{ request.operation }} # CREATE | UPDATE | DELETE +{{ request.userInfo.username }} +{{ request.userInfo.groups }} + +# 2. ConfigMap +context: +- name: myConfig + configMap: + name: my-config + namespace: kyverno +# Использование: +{{ myConfig.data.my-key }} + +# 3. Прямой API вызов +context: +- name: nsInfo + apiCall: + urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}" + jmesPath: "metadata.labels" +# Использование: +{{ nsInfo.environment }} + +# 4. Встроенная переменная +context: +- name: defaults + variable: + value: + memory: "256Mi" + cpu: "250m" +# Использование: +{{ defaults.memory }} + +# 5. Генерируемые значения +{{ random('[0-9]{6}') }} # случайные 6 цифр +{{ time_now_utc() }} # текущее UTC время +``` + +## Тест переменных из ConfigMap + +```bash +# Создать под — должны примениться лимиты из ConfigMap +kubectl apply -f - <- + Копирует лейблы environment и team из Namespace в Pod. + Позволяет автоматически тегировать поды данными их namespace + без ручного дублирования в каждом манифесте. +spec: + rules: + - name: copy-namespace-labels + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + context: + - name: nsLabels + apiCall: + urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}" + jmesPath: "metadata.labels" + mutate: + patchStrategicMerge: + metadata: + labels: + +(environment): "{{ nsLabels.environment || 'unknown' }}" + +(team): "{{ nsLabels.team || 'unknown' }}" diff --git a/05-variables/01-configmaps/kyverno-global-config.yaml b/05-variables/01-configmaps/kyverno-global-config.yaml new file mode 100644 index 0000000..7441a84 --- /dev/null +++ b/05-variables/01-configmaps/kyverno-global-config.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: kyverno-global-config + namespace: kyverno + labels: + app: kyverno-config +data: + # --- Resource defaults --- + default.memory.request: "128Mi" + default.memory.limit: "512Mi" + default.cpu.request: "100m" + default.cpu.limit: "500m" + + # --- Registry --- + allowed.registries: "registry.company.com,gcr.io/company-project" + + # --- Environment --- + production.replicas.minimum: "2" + staging.replicas.minimum: "1" + + # --- Required labels (через запятую) --- + required.labels: "app,team,environment" + + # --- Node settings --- + default.node.type: "standard" + default.region: "eu-west-1" diff --git a/05-variables/01-configmaps/set-resource-limits-from-config.yaml b/05-variables/01-configmaps/set-resource-limits-from-config.yaml new file mode 100644 index 0000000..a4fac02 --- /dev/null +++ b/05-variables/01-configmaps/set-resource-limits-from-config.yaml @@ -0,0 +1,44 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: set-resource-limits-from-config + annotations: + policies.kyverno.io/title: "Resource limits из централизованного ConfigMap" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: low + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Устанавливает дефолтные resource limits из ConfigMap kyverno-global-config. + Изменение лимитов для всего кластера — это kubectl edit configmap, + а не изменение и деплой политики. +spec: + rules: + - name: set-limits-from-configmap + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + context: + - name: globalConfig + configMap: + name: kyverno-global-config + namespace: kyverno + mutate: + foreach: + - list: "request.object.spec.containers" + patchStrategicMerge: + spec: + containers: + - name: "{{ element.name }}" + resources: + requests: + +(memory): "{{ globalConfig.data.\"default.memory.request\" }}" + +(cpu): "{{ globalConfig.data.\"default.cpu.request\" }}" + limits: + +(memory): "{{ globalConfig.data.\"default.memory.limit\" }}" + +(cpu): "{{ globalConfig.data.\"default.cpu.limit\" }}" diff --git a/05-variables/02-context/README.md b/05-variables/02-context/README.md new file mode 100644 index 0000000..fab9e92 --- /dev/null +++ b/05-variables/02-context/README.md @@ -0,0 +1,127 @@ +# Урок 5.2 — Контекстные данные и информация о кластере + +## Файлы + +| Файл | Описание | +|------|----------| +| `deployment-freeze-config.yaml` | ConfigMap для управления freeze | +| `restrict-deploys-during-freeze.yaml` | Блокировка деплоев во время freeze | +| `restrict-privileged-non-admins.yaml` | Привилегии только для admin-групп | + +## Применение + +```bash +kubectl apply -f deployment-freeze-config.yaml +kubectl apply -f restrict-deploys-during-freeze.yaml +kubectl apply -f restrict-privileged-non-admins.yaml +``` + +## Тест deployment freeze + +```bash +# Убедиться что freeze выключен +kubectl get configmap deployment-freeze-config -n kyverno \ + -o jsonpath='{.data.freeze_active}' +# Должно быть: false + +# Создать тестовый deployment — должен пройти +kubectl create deployment test-deploy \ + --image=nginx:1.25.3 --replicas=1 + +# Активировать freeze +kubectl patch configmap deployment-freeze-config -n kyverno \ + --type merge \ + -p '{"data":{"freeze_active":"true"}}' + +# Подождать обновления кэша (~60 сек), затем попробовать деплой +kubectl create deployment blocked-deploy \ + --image=nginx:1.25.3 --replicas=1 +# Ожидаем ошибку: Деплойменты заморожены... + +# Деактивировать freeze +kubectl patch configmap deployment-freeze-config -n kyverno \ + --type merge \ + -p '{"data":{"freeze_active":"false"}}' + +# Уборка +kubectl delete deployment test-deploy 2>/dev/null || true +``` + +## Доступные контекстные данные запроса + +```yaml +# Информация о ресурсе +request.object.metadata.name +request.object.metadata.namespace +request.object.metadata.labels +request.object.metadata.annotations + +# Информация об операции +request.operation # CREATE | UPDATE | DELETE | CONNECT +request.dryRun # true при --dry-run=server + +# Старый объект (только при UPDATE) +request.oldObject.spec.replicas + +# Информация о пользователе +request.userInfo.username +request.userInfo.uid +request.userInfo.groups # список групп +request.userInfo.extra # дополнительные атрибуты + +# Пример: разные правила для CREATE vs UPDATE +preconditions: + any: + - key: "{{ request.operation }}" + operator: Equals + value: CREATE +``` + +## Тест userInfo — проверка групп + +```bash +# Посмотреть свои группы в кластере +kubectl auth whoami + +# Попробовать создать привилегированный под (должен быть отклонён) +kubectl apply -f - <- + Блокирует создание новых Deployments когда в ConfigMap + deployment-freeze-config поле freeze_active=true. + Пользователи группы emergency-deployers и system:masters + могут деплоить даже во время freeze. + Активируйте freeze: kubectl patch configmap deployment-freeze-config + -n kyverno --type merge -p '{"data":{"freeze_active":"true"}}' +spec: + rules: + - name: check-freeze + match: + resources: + kinds: + - Deployment + preconditions: + any: + - key: "{{ request.operation }}" + operator: Equals + value: CREATE + context: + - name: freezeConfig + configMap: + name: deployment-freeze-config + namespace: kyverno + validate: + message: >- + Деплойменты заморожены с {{ freezeConfig.data.freeze_start }} + до {{ freezeConfig.data.freeze_end }}. + Причина: {{ freezeConfig.data.freeze_reason }} + Для экстренного деплоя обратитесь к oncall инженеру. + deny: + conditions: + all: + # Freeze активен + - key: "{{ freezeConfig.data.freeze_active }}" + operator: Equals + value: "true" + # Пользователь НЕ в группе экстренного деплоя + - key: "{{ request.userInfo.groups }}" + operator: AllNotIn + value: + - emergency-deployers + - system:masters diff --git a/05-variables/02-context/restrict-privileged-non-admins.yaml b/05-variables/02-context/restrict-privileged-non-admins.yaml new file mode 100644 index 0000000..974029e --- /dev/null +++ b/05-variables/02-context/restrict-privileged-non-admins.yaml @@ -0,0 +1,45 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: restrict-privileged-for-non-admins + annotations: + policies.kyverno.io/title: "Привилегированные контейнеры — только для администраторов" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Запрещает создание привилегированных контейнеров для обычных пользователей. + Администраторы (system:masters, cluster-admins) могут создавать + привилегированные поды для системных нужд. + Использует request.userInfo для проверки прав запрашивающего. +spec: + rules: + - name: restrict-privileged-non-admin + match: + resources: + kinds: + - Pod + # Precondition: применять только если пользователь НЕ администратор + preconditions: + all: + - key: "system:masters" + operator: NotIn + value: "{{ request.userInfo.groups }}" + - key: "cluster-admins" + operator: NotIn + value: "{{ request.userInfo.groups }}" + validate: + message: >- + Пользователь '{{ request.userInfo.username }}' не имеет права + создавать привилегированные контейнеры. + Обратитесь к cluster-admin для выполнения системных задач. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) + deny: + conditions: + any: + - key: "{{ element.securityContext.privileged }}" + operator: Equals + value: true diff --git a/05-variables/03-templates/README.md b/05-variables/03-templates/README.md new file mode 100644 index 0000000..54cffed --- /dev/null +++ b/05-variables/03-templates/README.md @@ -0,0 +1,128 @@ +# Урок 5.3 — Переиспользуемые шаблоны политик (Helm Chart) + +## Структура + +``` +kyverno-policies/ +├── Chart.yaml +├── values.yaml # дефолтные значения +├── values-staging.yaml # overrides для staging +├── values-production.yaml # overrides для production +├── templates/ +│ ├── require-resource-limits.yaml +│ ├── disallow-privileged.yaml +│ ├── require-labels.yaml +│ └── generate-networkpolicy.yaml +└── tests/ + ├── kyverno-test.yaml + └── resources/ + ├── pod-compliant.yaml + ├── pod-no-limits.yaml + ├── pod-privileged.yaml + └── deployment-no-labels.yaml +``` + +## Запуск тестов (без кластера) + +```bash +cd kyverno-policies + +# Сгенерировать манифесты из шаблонов и протестировать +helm template . -f values.yaml | \ + kyverno apply - --resource tests/resources/ --table + +# Запустить встроенные kyverno тесты +# (сначала нужно отрендерить шаблоны в обычные YAML) +helm template . -f values.yaml --output-dir /tmp/rendered-policies + +kyverno test tests/ \ + --policy-report \ + --detailed-results +``` + +## Деплой в кластер + +```bash +# Staging — всё в Audit +helm upgrade --install kyverno-policies . \ + --namespace kyverno \ + --create-namespace \ + -f values-staging.yaml + +# Production — Enforce +helm upgrade --install kyverno-policies . \ + --namespace kyverno \ + --create-namespace \ + -f values-production.yaml + +# Проверить задеплоенные политики +kubectl get clusterpolicies -l helm.sh/chart=kyverno-policies-1.0.0 + +# Rollback если что-то пошло не так +helm rollback kyverno-policies 1 -n kyverno +``` + +## PolicyException — управляемые исключения + +```bash +# Применить временное исключение для legacy-сервиса +kubectl apply -f - <- + Корпоративные политики безопасности Kyverno. + Содержит базовый набор validation, mutation и generation политик. +type: application +version: 1.0.0 +appVersion: "1.11" +keywords: + - kyverno + - policy + - security + - kubernetes +maintainers: + - name: Platform Team + email: platform@company.com diff --git a/05-variables/03-templates/kyverno-policies/templates/disallow-privileged.yaml b/05-variables/03-templates/kyverno-policies/templates/disallow-privileged.yaml new file mode 100644 index 0000000..2514a38 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/templates/disallow-privileged.yaml @@ -0,0 +1,42 @@ +{{- if .Values.disallowPrivileged.enabled }} +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: disallow-privileged-containers + annotations: + policies.kyverno.io/title: "Запрет привилегированных контейнеров" + policies.kyverno.io/category: Pod Security Standards (Baseline) + policies.kyverno.io/severity: critical + policies.kyverno.io/version: {{ .Chart.Version }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +spec: + validationFailureAction: {{ .Values.disallowPrivileged.failureAction | default .Values.global.failureAction }} + background: true + rules: + - name: privileged-containers + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + {{- range .Values.global.excludedNamespaces }} + - {{ . }} + {{- end }} + validate: + message: >- + Контейнер '{{ "{{" }} element.name {{ "}}" }}' имеет privileged: true. + Привилегированные контейнеры запрещены политикой безопасности. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) | + merge(request.object.spec.ephemeralContainers[] || `[]`, @) + deny: + conditions: + any: + - key: "{{ "{{" }} element.securityContext.privileged {{ "}}" }}" + operator: Equals + value: true +{{- end }} diff --git a/05-variables/03-templates/kyverno-policies/templates/generate-networkpolicy.yaml b/05-variables/03-templates/kyverno-policies/templates/generate-networkpolicy.yaml new file mode 100644 index 0000000..f119888 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/templates/generate-networkpolicy.yaml @@ -0,0 +1,44 @@ +{{- if .Values.generateNetworkPolicy.enabled }} +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: generate-default-networkpolicy + annotations: + policies.kyverno.io/title: "Генерация NetworkPolicy по умолчанию" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: high + policies.kyverno.io/version: {{ .Chart.Version }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +spec: + rules: + - name: generate-deny-all + match: + resources: + kinds: + - Namespace + exclude: + resources: + names: + {{- range (concat .Values.global.excludedNamespaces .Values.generateNetworkPolicy.excludedNamespaces) | uniq }} + - {{ . }} + {{- end }} + generate: + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + name: default-deny-all + namespace: "{{ "{{" }} request.object.metadata.name {{ "}}" }}" + synchronize: true + data: + kind: NetworkPolicy + apiVersion: networking.k8s.io/v1 + metadata: + name: default-deny-all + labels: + generated-by: kyverno + helm-release: {{ .Release.Name }} + spec: + podSelector: {} + policyTypes: + - Ingress + - Egress +{{- end }} diff --git a/05-variables/03-templates/kyverno-policies/templates/require-labels.yaml b/05-variables/03-templates/kyverno-policies/templates/require-labels.yaml new file mode 100644 index 0000000..7ef1d2f --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/templates/require-labels.yaml @@ -0,0 +1,40 @@ +{{- if .Values.requiredLabels.enabled }} +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-standard-labels + annotations: + policies.kyverno.io/title: "Обязательные стандартные лейблы" + policies.kyverno.io/category: Governance + policies.kyverno.io/severity: medium + policies.kyverno.io/version: {{ .Chart.Version }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +spec: + validationFailureAction: {{ .Values.requiredLabels.failureAction | default .Values.global.failureAction }} + background: true + rules: + - name: check-required-labels + match: + resources: + kinds: + - Deployment + - StatefulSet + - DaemonSet + exclude: + resources: + namespaces: + {{- range .Values.global.excludedNamespaces }} + - {{ . }} + {{- end }} + validate: + message: >- + Ресурс '{{ "{{" }} request.object.metadata.name {{ "}}" }}' + должен иметь все обязательные лейблы: + {{ .Values.requiredLabels.labels | join ", " }} + pattern: + metadata: + labels: + {{- range .Values.requiredLabels.labels }} + {{ . }}: "?*" + {{- end }} +{{- end }} diff --git a/05-variables/03-templates/kyverno-policies/templates/require-resource-limits.yaml b/05-variables/03-templates/kyverno-policies/templates/require-resource-limits.yaml new file mode 100644 index 0000000..4068495 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/templates/require-resource-limits.yaml @@ -0,0 +1,41 @@ +{{- if .Values.resourceLimits.enabled }} +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: require-resource-limits + annotations: + policies.kyverno.io/title: "Обязательные resource limits" + policies.kyverno.io/category: Resources + policies.kyverno.io/severity: high + policies.kyverno.io/version: {{ .Chart.Version }} + helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }} +spec: + validationFailureAction: {{ .Values.resourceLimits.failureAction | default .Values.global.failureAction }} + background: true + rules: + - name: check-container-limits + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + {{- range .Values.global.excludedNamespaces }} + - {{ . }} + {{- end }} + validate: + message: >- + Контейнер '{{ "{{" }} element.name {{ "}}" }}' не имеет resource limits. + Добавьте resources.limits.memory и resources.limits.cpu. + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) | + merge(request.object.spec.ephemeralContainers[] || `[]`, @) + pattern: + resources: + limits: + memory: "?*" + cpu: "?*" +{{- end }} diff --git a/05-variables/03-templates/kyverno-policies/tests/kyverno-test.yaml b/05-variables/03-templates/kyverno-policies/tests/kyverno-test.yaml new file mode 100644 index 0000000..461e3ed --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/tests/kyverno-test.yaml @@ -0,0 +1,31 @@ +name: kyverno-policies-chart-tests +policies: + - ../templates/require-resource-limits.yaml + - ../templates/disallow-privileged.yaml + - ../templates/require-labels.yaml +resources: + - resources/pod-compliant.yaml + - resources/pod-no-limits.yaml + - resources/pod-privileged.yaml + - resources/deployment-no-labels.yaml +results: + - policy: require-resource-limits + rule: check-container-limits + resource: pod-compliant + namespace: default + result: pass + - policy: require-resource-limits + rule: check-container-limits + resource: pod-no-limits + namespace: default + result: fail + - policy: disallow-privileged-containers + rule: privileged-containers + resource: pod-privileged + namespace: default + result: fail + - policy: require-standard-labels + rule: check-required-labels + resource: deployment-no-labels + namespace: default + result: fail diff --git a/05-variables/03-templates/kyverno-policies/tests/resources/deployment-no-labels.yaml b/05-variables/03-templates/kyverno-policies/tests/resources/deployment-no-labels.yaml new file mode 100644 index 0000000..de9023d --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/tests/resources/deployment-no-labels.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: deployment-no-labels + namespace: default + # нет лейблов app, team, environment +spec: + replicas: 1 + selector: + matchLabels: + run: app + template: + metadata: + labels: + run: app + spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/05-variables/03-templates/kyverno-policies/tests/resources/pod-compliant.yaml b/05-variables/03-templates/kyverno-policies/tests/resources/pod-compliant.yaml new file mode 100644 index 0000000..4bc093b --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/tests/resources/pod-compliant.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-compliant + namespace: default +spec: + containers: + - name: app + image: nginx:1.25.3 + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" diff --git a/05-variables/03-templates/kyverno-policies/tests/resources/pod-no-limits.yaml b/05-variables/03-templates/kyverno-policies/tests/resources/pod-no-limits.yaml new file mode 100644 index 0000000..e436745 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/tests/resources/pod-no-limits.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-no-limits + namespace: default +spec: + containers: + - name: app + image: nginx:1.25.3 diff --git a/05-variables/03-templates/kyverno-policies/tests/resources/pod-privileged.yaml b/05-variables/03-templates/kyverno-policies/tests/resources/pod-privileged.yaml new file mode 100644 index 0000000..7dbada7 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/tests/resources/pod-privileged.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: pod-privileged + namespace: default +spec: + containers: + - name: app + image: nginx:1.25.3 + securityContext: + privileged: true + resources: + limits: + memory: "128Mi" + cpu: "100m" diff --git a/05-variables/03-templates/kyverno-policies/values-production.yaml b/05-variables/03-templates/kyverno-policies/values-production.yaml new file mode 100644 index 0000000..ec6ed2e --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/values-production.yaml @@ -0,0 +1,21 @@ +# Production overrides +global: + failureAction: Enforce + +resourceLimits: + enabled: true + maxMemory: "8Gi" + maxCpu: "8" + +disallowLatestTag: + enabled: true + failureAction: Enforce + +requiredLabels: + enabled: true + failureAction: Enforce + labels: + - app + - team + - environment + - version diff --git a/05-variables/03-templates/kyverno-policies/values-staging.yaml b/05-variables/03-templates/kyverno-policies/values-staging.yaml new file mode 100644 index 0000000..d1b7cff --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/values-staging.yaml @@ -0,0 +1,10 @@ +# Staging overrides — всё в Audit для наблюдения +global: + failureAction: Audit + +disallowPrivileged: + enabled: true + failureAction: Audit # в staging даже security в Audit + +generateNetworkPolicy: + enabled: false # в staging не генерируем NetworkPolicy diff --git a/05-variables/03-templates/kyverno-policies/values.yaml b/05-variables/03-templates/kyverno-policies/values.yaml new file mode 100644 index 0000000..a7fb650 --- /dev/null +++ b/05-variables/03-templates/kyverno-policies/values.yaml @@ -0,0 +1,55 @@ +# Дефолтные значения для kyverno-policies Helm chart. +# Переопределите в values-staging.yaml или values-production.yaml. + +global: + # Audit = фиксировать нарушения, Enforce = блокировать + failureAction: Audit + excludedNamespaces: + - kube-system + - kube-public + - kube-node-lease + - kyverno + +# --- Политика: resource limits --- +resourceLimits: + enabled: true + failureAction: "" # пусто = использовать global.failureAction + maxMemory: "4Gi" + maxCpu: "4" + +# --- Политика: запрет latest тега --- +disallowLatestTag: + enabled: true + failureAction: "" + +# --- Политика: обязательные лейблы --- +requiredLabels: + enabled: true + failureAction: "" + labels: + - app + - team + - environment + +# --- Политика: привилегированные контейнеры --- +disallowPrivileged: + enabled: true + # security политики всегда Enforce + failureAction: Enforce + +# --- Политика: запрет host namespaces --- +disallowHostNamespaces: + enabled: true + failureAction: Enforce + +# --- Политика: автодобавление лейблов (mutation) --- +addStandardLabels: + enabled: true + +# --- Политика: генерация NetworkPolicy --- +generateNetworkPolicy: + enabled: true + excludedNamespaces: + - monitoring + - logging + - istio-system diff --git a/06-monitoring/01-logging/README.md b/06-monitoring/01-logging/README.md new file mode 100644 index 0000000..169a878 --- /dev/null +++ b/06-monitoring/01-logging/README.md @@ -0,0 +1,134 @@ +# Урок 6.1 — Логирование и мониторинг Kyverno + +## Файлы + +| Файл | Описание | +|------|----------| +| `grafana-dashboard.json` | Готовый Grafana dashboard | +| `../03-reporting/prometheus-alert-rules.yaml` | Alerting правила | +| `../03-reporting/service-monitor.yaml` | ServiceMonitor | + +## Настройка уровня логирования + +```bash +# Изменить уровень логирования без перезапуска (через Helm upgrade) +helm upgrade kyverno kyverno/kyverno \ + --namespace kyverno \ + --reuse-values \ + --set admissionController.extraArgs="{--v=4\,--loggingFormat=json}" + +# Уровни: +# --v=1 production (warnings + important events) +# --v=2 informational (policy apply/deny events) +# --v=4 debug (full AdmissionReview) +# --v=6 trace (очень детально, только для отладки) +``` + +## Просмотр логов + +```bash +# Логи admission controller +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=admission-controller \ + --tail=100 -f + +# Логи background controller (для generate политик) +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=background-controller \ + --tail=50 + +# Фильтрация — только нарушения +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=admission-controller \ + --tail=200 | grep '"result":"fail"' + +# Фильтрация — конкретная политика +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=admission-controller \ + --tail=200 | grep '"policy":"require-resource-limits"' +``` + +## Метрики через port-forward + +```bash +# Открыть порт metrics +kubectl port-forward -n kyverno \ + svc/kyverno-svc-metrics 8000:8000 & + +# Все метрики +curl -s http://localhost:8000/metrics | grep kyverno_ | head -40 + +# Конкретная метрика +curl -s http://localhost:8000/metrics | \ + grep kyverno_policy_results_total + +# Топ метрик по нарушениям +curl -s http://localhost:8000/metrics | \ + grep 'kyverno_policy_results_total.*rule_result="fail"' + +# Остановить port-forward +kill %1 +``` + +## Импорт Grafana Dashboard + +```bash +# Способ 1: через UI +# Grafana → Dashboards → Import → Upload JSON file → grafana-dashboard.json + +# Способ 2: через ConfigMap (если используете Grafana Operator) +kubectl create configmap kyverno-dashboard \ + --from-file=grafana-dashboard.json \ + --namespace=monitoring + +kubectl label configmap kyverno-dashboard \ + -n monitoring \ + grafana_dashboard=1 +``` + +## Ключевые PromQL запросы + +```promql +# Compliance rate (цель: 100%) +sum(rate(kyverno_policy_results_total{rule_result="pass"}[5m])) / +sum(rate(kyverno_policy_results_total[5m])) * 100 + +# p95 латентность в миллисекундах +histogram_quantile(0.95, + sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le) +) * 1000 + +# Нарушений за сутки по политикам +topk(10, sum by(policy_name)( + increase(kyverno_policy_results_total{rule_result="fail"}[24h]) +)) + +# Нарушений за сутки по namespace +sum by(resource_namespace)( + increase(kyverno_policy_results_total{rule_result="fail"}[24h]) +) + +# Процент ошибок обработки +rate(kyverno_admission_requests_total{admission_request_type="error"}[5m]) / +rate(kyverno_admission_requests_total[5m]) * 100 + +# CPU throttling Kyverno +rate(container_cpu_cfs_throttled_seconds_total{ + namespace="kyverno", + container=~"kyverno.*" +}[5m]) +``` + +## События Kubernetes + +```bash +# PolicyViolation события +kubectl get events -A \ + --field-selector reason=PolicyViolation \ + --sort-by='.lastTimestamp' + +# События от Kyverno +kubectl get events -A \ + --field-selector source.component=kyverno-admission \ + --sort-by='.lastTimestamp' | tail -20 +``` diff --git a/06-monitoring/01-logging/grafana-dashboard.json b/06-monitoring/01-logging/grafana-dashboard.json new file mode 100644 index 0000000..3d9b42d --- /dev/null +++ b/06-monitoring/01-logging/grafana-dashboard.json @@ -0,0 +1,130 @@ +{ + "title": "Kyverno Policy Dashboard", + "uid": "kyverno-main", + "tags": ["kyverno", "policy", "security"], + "timezone": "browser", + "refresh": "30s", + "panels": [ + { + "id": 1, + "title": "Policy Compliance Rate", + "type": "stat", + "gridPos": {"h": 4, "w": 6, "x": 0, "y": 0}, + "targets": [ + { + "expr": "sum(rate(kyverno_policy_results_total{rule_result=\"pass\"}[5m])) / sum(rate(kyverno_policy_results_total[5m])) * 100", + "legendFormat": "Compliance %" + } + ], + "options": { + "reduceOptions": {"calcs": ["lastNotNull"]}, + "thresholds": { + "steps": [ + {"color": "red", "value": 0}, + {"color": "yellow", "value": 90}, + {"color": "green", "value": 99} + ] + } + } + }, + { + "id": 2, + "title": "Admission Requests/s", + "type": "stat", + "gridPos": {"h": 4, "w": 6, "x": 6, "y": 0}, + "targets": [ + { + "expr": "sum(rate(kyverno_admission_requests_total[5m]))", + "legendFormat": "Requests/s" + } + ] + }, + { + "id": 3, + "title": "Admission Latency p95 (ms)", + "type": "stat", + "gridPos": {"h": 4, "w": 6, "x": 12, "y": 0}, + "targets": [ + { + "expr": "histogram_quantile(0.95, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000", + "legendFormat": "p95 latency" + } + ], + "options": { + "thresholds": { + "steps": [ + {"color": "green", "value": 0}, + {"color": "yellow", "value": 100}, + {"color": "red", "value": 500} + ] + } + } + }, + { + "id": 4, + "title": "Total Violations (24h)", + "type": "stat", + "gridPos": {"h": 4, "w": 6, "x": 18, "y": 0}, + "targets": [ + { + "expr": "sum(increase(kyverno_policy_results_total{rule_result=\"fail\"}[24h]))", + "legendFormat": "Violations" + } + ], + "options": { + "thresholds": { + "steps": [ + {"color": "green", "value": 0}, + {"color": "yellow", "value": 10}, + {"color": "red", "value": 100} + ] + } + } + }, + { + "id": 5, + "title": "Top Violated Policies (1h)", + "type": "bargauge", + "gridPos": {"h": 8, "w": 12, "x": 0, "y": 4}, + "targets": [ + { + "expr": "topk(10, sum by(policy_name)(increase(kyverno_policy_results_total{rule_result=\"fail\"}[1h])))", + "legendFormat": "{{policy_name}}" + } + ] + }, + { + "id": 6, + "title": "Violations by Namespace (24h)", + "type": "table", + "gridPos": {"h": 8, "w": 12, "x": 12, "y": 4}, + "targets": [ + { + "expr": "sort_desc(sum by(resource_namespace)(increase(kyverno_policy_results_total{rule_result=\"fail\"}[24h])))", + "legendFormat": "{{resource_namespace}}", + "instant": true + } + ] + }, + { + "id": 7, + "title": "Admission Latency over time", + "type": "timeseries", + "gridPos": {"h": 8, "w": 24, "x": 0, "y": 12}, + "targets": [ + { + "expr": "histogram_quantile(0.50, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000", + "legendFormat": "p50" + }, + { + "expr": "histogram_quantile(0.95, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000", + "legendFormat": "p95" + }, + { + "expr": "histogram_quantile(0.99, sum(rate(kyverno_admission_review_duration_seconds_bucket[5m])) by (le)) * 1000", + "legendFormat": "p99" + } + ] + } + ] +} diff --git a/06-monitoring/02-debugging/README.md b/06-monitoring/02-debugging/README.md new file mode 100644 index 0000000..cb0c130 --- /dev/null +++ b/06-monitoring/02-debugging/README.md @@ -0,0 +1,203 @@ +# Урок 6.2 — Отладка проблем с политиками + +## Файлы + +| Файл | Описание | +|------|----------| +| `policy-exception-example.yaml` | Пример PolicyException | +| `debug-context-inspector.yaml` | DEBUG политика для инспекции переменных | + +## Диагностический чеклист + +Когда политика не работает — проверяйте по порядку: + +### Шаг 1: Политика применена и Ready? + +```bash +kubectl get clusterpolicies +# Колонка READY должна быть True + +kubectl get clusterpolicy my-policy -o yaml | grep -A 10 "status:" +# conditions[0].status: "True" +# conditions[0].type: Ready +``` + +### Шаг 2: Match срабатывает? + +```bash +# Kyverno CLI — самый быстрый способ проверить +kyverno apply my-policy.yaml \ + --resource my-resource.yaml \ + --detailed-results + +# Вывод: +# Rule my-rule -> resource: PASS/FAIL/SKIP +# SKIP = match не сработал +``` + +### Шаг 3: Ресурс не попадает в exclude? + +```bash +# Проверить лейблы namespace +kubectl get namespace my-namespace --show-labels + +# Проверить лейблы ресурса +kubectl get pod my-pod --show-labels +``` + +### Шаг 4: Логи admission controller + +```bash +# Включить более детальные логи временно +kubectl set env deployment/kyverno-admission-controller \ + -n kyverno \ + KYVERNO_LOG_LEVEL=4 + +# Затем смотреть логи +kubectl logs -n kyverno \ + -l app.kubernetes.io/component=admission-controller \ + --tail=50 -f | grep "my-policy\|my-resource" + +# Вернуть обратно +kubectl set env deployment/kyverno-admission-controller \ + -n kyverno \ + KYVERNO_LOG_LEVEL=2 +``` + +### Шаг 5: Dry-run для проверки мутаций + +```bash +# Видеть итоговый ресурс после мутации без создания +kubectl apply -f my-pod.yaml --dry-run=server -o yaml + +# Diff оригинала и мутированной версии +diff <(cat my-pod.yaml) \ + <(kubectl apply -f my-pod.yaml --dry-run=server -o yaml 2>/dev/null) +``` + +## Типичные ошибки и решения + +### Ошибка 1: JMESPath — экранирование аннотаций + +```yaml +# НЕПРАВИЛЬНО — парсер не поймёт / +key: "{{ request.object.metadata.annotations.company.com/env }}" + +# ПРАВИЛЬНО — экранировать / через \" +key: "{{ request.object.metadata.annotations.\"company.com/env\" }}" +``` + +### Ошибка 2: Опциональные поля + +```yaml +# НЕПРАВИЛЬНО — упадёт если поле не задано +pattern: + spec: + securityContext: + runAsNonRoot: true + +# ПРАВИЛЬНО — = для опциональных полей +pattern: + spec: + =(securityContext): + =(runAsNonRoot): true +``` + +### Ошибка 3: foreach по несуществующему массиву + +```yaml +# НЕПРАВИЛЬНО — упадёт если initContainers не задан +foreach: +- list: "request.object.spec.initContainers" + +# ПРАВИЛЬНО — fallback на пустой массив +foreach: +- list: "request.object.spec.initContainers || `[]`" +``` + +### Ошибка 4: Match по kind без учёта controller + +```yaml +# Если хотите проверять Deployment — матчите Deployment +match: + resources: + kinds: + - Deployment # проверяет сам Deployment + +# Если хотите проверять Pod через Deployment — матчите Pod +# (Deployment controller создаёт Pod — Kyverno поймает его) +match: + resources: + kinds: + - Pod +``` + +## Инспекция переменных через debug-политику + +```bash +# Создать namespace для отладки +kubectl create namespace debug-namespace + +# Применить debug политику (она всегда выдаёт fail с данными) +kubectl apply -f debug-context-inspector.yaml + +# Создать тестовый под — получить ошибку с содержимым переменных +kubectl run debug-pod --image=nginx:1.25.3 \ + --restart=Never -n debug-namespace \ + --dry-run=server 2>&1 + +# Вывод покажет все переменные в сообщении ошибки: +# [DEBUG] Pod: debug-pod +# Namespace: debug-namespace +# Namespace labels: map[kubernetes.io/metadata.name:debug-namespace] +# ... + +# Удалить debug политику после использования +kubectl delete -f debug-context-inspector.yaml +kubectl delete namespace debug-namespace +``` + +## Работа с PolicyException + +```bash +# Применить исключение +kubectl apply -f policy-exception-example.yaml + +# Проверить что исключение работает +kubectl run legacy-app-test \ + --image=nginx:1.25.3 \ + --restart=Never \ + -n production +# Под без limits должен создаться без ошибок + +# Посмотреть все активные исключения +kubectl get policyexceptions -A + +# Найти истекающие исключения (скрипт) +kubectl get policyexceptions -A -o json | \ + jq -r '.items[] | + .metadata.name + " expires: " + + .metadata.annotations["exception.company.com/expires"]' | \ + sort +``` + +## Анализ PolicyReport для диагностики + +```bash +# Найти ресурс который нарушает политику +POLICY="require-resource-limits" +kubectl get policyreports -A -o json | \ + jq --arg p "$POLICY" \ + -r '.items[] | .metadata.namespace as $ns | + .results[] | + select(.policy == $p and .result == "fail") | + "\($ns)/\(.resources[0].name): \(.message)"' + +# Сводная статистика +kubectl get policyreports -A -o json | \ + jq '{ + total: [.items[].results[]] | length, + pass: [.items[].results[] | select(.result=="pass")] | length, + fail: [.items[].results[] | select(.result=="fail")] | length + }' +``` diff --git a/06-monitoring/02-debugging/debug-context-inspector.yaml b/06-monitoring/02-debugging/debug-context-inspector.yaml new file mode 100644 index 0000000..9aceaf3 --- /dev/null +++ b/06-monitoring/02-debugging/debug-context-inspector.yaml @@ -0,0 +1,42 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: debug-context-inspector + annotations: + policies.kyverno.io/title: "DEBUG: Инспектор контекстных переменных" + policies.kyverno.io/description: >- + ТОЛЬКО ДЛЯ ОТЛАДКИ. Всегда отклоняет запрос, выводя содержимое + переменных в сообщении ошибки. Помогает понять, что видит политика. + УДАЛИТЕ после отладки — не держите в production. +spec: + validationFailureAction: Enforce + background: false # не запускать в background — только для живых запросов + rules: + - name: print-context + match: + resources: + kinds: + - Pod + namespaces: + - debug-namespace # применяем только в отдельном namespace + context: + - name: nsLabels + apiCall: + urlPath: "/api/v1/namespaces/{{ request.object.metadata.namespace }}" + jmesPath: "metadata.labels" + validate: + # Всегда fail — выводим данные в сообщении + message: >- + [DEBUG] Pod: {{ request.object.metadata.name }} + Namespace: {{ request.object.metadata.namespace }} + Namespace labels: {{ nsLabels }} + Pod labels: {{ request.object.metadata.labels }} + User: {{ request.userInfo.username }} + Groups: {{ request.userInfo.groups }} + Operation: {{ request.operation }} + Containers: {{ request.object.spec.containers[].name }} + deny: + conditions: + - key: "true" + operator: Equals + value: "true" diff --git a/06-monitoring/02-debugging/policy-exception-example.yaml b/06-monitoring/02-debugging/policy-exception-example.yaml new file mode 100644 index 0000000..e1bb918 --- /dev/null +++ b/06-monitoring/02-debugging/policy-exception-example.yaml @@ -0,0 +1,24 @@ +apiVersion: kyverno.io/v2beta1 +kind: PolicyException +metadata: + name: legacy-app-resource-limits-exception + namespace: production + annotations: + # Обязательные поля для аудита — заполняйте всегда + exception.company.com/expires: "2025-06-01" + exception.company.com/reason: "Legacy приложение в процессе миграции. JIRA-4321" + exception.company.com/approved-by: "platform-team" + exception.company.com/created-by: "john.doe" +spec: + exceptions: + - policyName: require-resource-limits + ruleNames: + - check-container-limits + match: + resources: + kinds: + - Pod + namespaces: + - production + names: + - legacy-app-* # только legacy поды diff --git a/06-monitoring/03-performance/README.md b/06-monitoring/03-performance/README.md new file mode 100644 index 0000000..2712096 --- /dev/null +++ b/06-monitoring/03-performance/README.md @@ -0,0 +1,185 @@ +# Урок 6.3 — Производительность и оптимизация + +## Измерение текущей производительности + +```bash +# Открыть метрики +kubectl port-forward -n kyverno svc/kyverno-svc-metrics 8000:8000 & + +# p95 и p99 латентность +curl -s http://localhost:8000/metrics | \ + grep 'kyverno_admission_review_duration_seconds_bucket' | \ + tail -20 + +# Найти самые медленные политики (через Prometheus) +# topk(10, +# histogram_quantile(0.99, +# sum by(policy_name, rule_name, le)( +# rate(kyverno_admission_review_duration_seconds_bucket[5m]) +# ) +# ) +# ) + +# Измерить время admission вручную +time kubectl run perf-test \ + --image=nginx:1.25.3 \ + --restart=Never \ + --dry-run=server + +kubectl delete pod perf-test 2>/dev/null || true +kill %1 +``` + +## Пороговые значения + +| Метрика | Хорошо | Приемлемо | Проблема | +|---------|--------|-----------|---------| +| p95 латентность | < 100ms | < 500ms | > 500ms | +| p99 латентность | < 200ms | < 1000ms | > 1000ms | +| CPU throttling | 0% | < 5% | > 10% | +| Error rate | 0% | < 0.1% | > 0.1% | + +## Оптимизация 1: точные selectors + +```yaml +# МЕДЛЕННО — матчит всё подряд +match: + resources: + kinds: + - "*" + +# БЫСТРО — только нужные ресурсы и namespace +match: + resources: + kinds: + - Pod + namespaces: + - production + - staging +``` + +## Оптимизация 2: preconditions для быстрого выхода + +```bash +# Добавить precondition перед дорогим context.apiCall +# Правило запустит apiCall только если precondition прошёл +``` + +```yaml +rules: +- name: expensive-check + match: + resources: + kinds: [Pod] + preconditions: + any: + # Быстрая проверка лейбла ДО дорогого apiCall + - key: "{{ request.object.metadata.labels.\"needs-check\" }}" + operator: Equals + value: "true" + context: + - name: externalData # вызывается только если precondition прошёл + apiCall: + urlPath: "..." +``` + +## Оптимизация 3: разбивать мегаполитики + +```bash +# Посмотреть сколько правил в каждой политике +kubectl get clusterpolicies -o json | \ + jq -r '.items[] | "\(.metadata.name): \(.spec.rules | length) rules"' | \ + sort -t: -k2 -rn | head -10 + +# Политики с >5 правилами — кандидаты на разбивку +# Каждая политика применяется параллельно с другими +# Правила внутри политики — последовательно +``` + +## Оптимизация 4: ресурсы для Kyverno + +```bash +# Проверить текущее потребление ресурсов +kubectl top pods -n kyverno + +# Проверить CPU throttling +kubectl get --raw /apis/metrics.k8s.io/v1beta1/namespaces/kyverno/pods | \ + jq '.items[] | {name: .metadata.name, cpu: .containers[].usage.cpu}' + +# Увеличить ресурсы если нужно +helm upgrade kyverno kyverno/kyverno \ + --namespace kyverno \ + --reuse-values \ + --set admissionController.resources.limits.cpu=2000m \ + --set admissionController.resources.limits.memory=1Gi +``` + +## Оптимизация 5: замена apiCall на ConfigMap + +```yaml +# МЕДЛЕННО — apiCall при каждом запросе +context: +- name: registries + apiCall: + urlPath: "/api/v1/namespaces/kyverno/configmaps/allowed-registries" + jmesPath: "data.list" + +# БЫСТРО — ConfigMap кэшируется Kyverno +context: +- name: registries + configMap: + name: allowed-registries + namespace: kyverno +``` + +## Оптимизация 6: HPA для Kyverno + +```bash +# Включить HPA для admission controller +kubectl apply -f - < /dev/null 2>&1 +done + +# Запустить параллельно +seq 1 20 | xargs -P 10 -I {} \ + kubectl run perf-pod-{} \ + --image=nginx:1.25.3 \ + --restart=Never \ + --dry-run=server + +# Во время нагрузки мониторить latency +watch -n 2 'curl -s http://localhost:8000/metrics | \ + grep admission_review_duration_seconds_sum' +``` diff --git a/07-advanced/01-cicd/.github/workflows/policy-ci.yaml b/07-advanced/01-cicd/.github/workflows/policy-ci.yaml new file mode 100644 index 0000000..dc10000 --- /dev/null +++ b/07-advanced/01-cicd/.github/workflows/policy-ci.yaml @@ -0,0 +1,154 @@ +name: Kyverno Policy CI + +on: + pull_request: + branches: + - main + paths: + - 'policies/**' + - 'k8s/**' + - 'helm/**' + push: + branches: + - main + paths: + - 'policies/**' + +jobs: + # ------------------------------------------------------- + # JOB 1: Линтинг и тестирование политик + # ------------------------------------------------------- + test-policies: + name: Test Kyverno Policies + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Kyverno CLI + uses: kyverno/action-install-cli@v0.2.0 + with: + release: 'v1.11.4' + + - name: Verify Kyverno CLI + run: kyverno version + + - name: Lint policies (validate YAML structure) + run: | + find policies/ -name '*.yaml' -exec kubectl apply \ + --dry-run=client -f {} \; 2>&1 | \ + grep -v "^$" | \ + tee lint-results.txt + # Завершиться с ошибкой если есть failures + grep -q "error\|Error" lint-results.txt && exit 1 || exit 0 + + - name: Run Kyverno tests + run: | + kyverno test policies/ \ + --detailed-results \ + 2>&1 | tee test-results.txt + # Проверить что все тесты прошли + grep -q "Tests Summary" test-results.txt || exit 1 + grep "Passed" test-results.txt + # Завершиться с ошибкой если есть Failed тесты + grep -q "^Failed: [^0]" test-results.txt && exit 1 || exit 0 + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: kyverno-test-results + path: test-results.txt + + # ------------------------------------------------------- + # JOB 2: Проверка Kubernetes манифестов против политик + # ------------------------------------------------------- + validate-manifests: + name: Validate K8s Manifests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: 'v3.14.0' + + - name: Install Kyverno CLI + uses: kyverno/action-install-cli@v0.2.0 + with: + release: 'v1.11.4' + + - name: Generate manifests from Helm + run: | + helm template my-app ./helm/my-app \ + -f helm/my-app/values-production.yaml \ + --namespace production \ + > /tmp/rendered-manifests.yaml + + - name: Validate manifests against policies + run: | + kyverno apply policies/ \ + --resource /tmp/rendered-manifests.yaml \ + --detailed-results \ + --table \ + 2>&1 | tee kyverno-apply-results.txt + + # Завершиться с ошибкой если есть FAIL + grep -q "^| FAIL" kyverno-apply-results.txt && exit 1 || exit 0 + + - name: Comment PR with violations + if: failure() && github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const results = fs.readFileSync('kyverno-apply-results.txt', 'utf8'); + const body = `## ❌ Kyverno Policy Violations\n\n` + + `Следующие манифесты нарушают политики безопасности:\n\n` + + `\`\`\`\n${results}\`\`\`\n\n` + + `Исправьте нарушения перед merge.`; + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: body + }); + + # ------------------------------------------------------- + # JOB 3: Деплой политик в staging (только main ветка) + # ------------------------------------------------------- + deploy-staging: + name: Deploy Policies to Staging + runs-on: ubuntu-latest + needs: [test-policies, validate-manifests] + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + environment: staging + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Helm + uses: azure/setup-helm@v4 + + - name: Configure kubectl + run: | + echo "${{ secrets.STAGING_KUBECONFIG }}" | \ + base64 -d > /tmp/kubeconfig + echo "KUBECONFIG=/tmp/kubeconfig" >> $GITHUB_ENV + + - name: Deploy policies via Helm + run: | + helm upgrade --install kyverno-policies \ + ./05-variables/03-templates/kyverno-policies \ + --namespace kyverno \ + -f ./05-variables/03-templates/kyverno-policies/values-staging.yaml \ + --wait \ + --timeout 5m + + - name: Verify deployment + run: | + kubectl get clusterpolicies \ + -l helm.sh/chart=kyverno-policies-1.0.0 \ + -o wide diff --git a/07-advanced/01-cicd/README.md b/07-advanced/01-cicd/README.md new file mode 100644 index 0000000..00d2f6a --- /dev/null +++ b/07-advanced/01-cicd/README.md @@ -0,0 +1,153 @@ +# Урок 7.1 — Интеграция с CI/CD пайплайнами + +## Файлы + +| Файл | Описание | +|------|----------| +| `.github/workflows/policy-ci.yaml` | GitHub Actions: тест + валидация + деплой | +| `argocd-application.yaml` | ArgoCD Application для GitOps деплоя | + +## Kyverno CLI — локальная проверка + +```bash +# Установка +brew install kyverno # macOS +# или +curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz +tar -xzf kyverno-cli_*.tar.gz && sudo mv kyverno /usr/local/bin/ + +kyverno version + +# Проверить манифест против политики (без кластера) +kyverno apply \ + ../../02-validation/01-resource-validation/require-resource-limits.yaml \ + --resource ../../02-validation/01-resource-validation/test-resources/pod-no-limits.yaml + +# Проверить папку политик против папки манифестов +kyverno apply ../../02-validation/ \ + --resource ./test-k8s-manifests/ \ + --table \ + --detailed-results + +# Запустить тесты +kyverno test ../../02-validation/01-resource-validation/tests/ +``` + +## Локальная симуляция CI pipeline + +```bash +# 1. Сгенерировать манифесты из Helm +helm template my-app ./helm-chart/ \ + -f ./helm-chart/values-production.yaml \ + > /tmp/manifests.yaml + +# 2. Запустить Kyverno проверку +kyverno apply ../../02-validation/ \ + --resource /tmp/manifests.yaml \ + --table 2>&1 | tee results.txt + +# 3. Проверить наличие нарушений +if grep -q "^| FAIL" results.txt; then + echo "❌ Policy violations found!" + cat results.txt + exit 1 +else + echo "✅ All policies passed" +fi +``` + +## Тестовые манифесты для CI + +```bash +mkdir -p test-k8s-manifests + +# Манифест с нарушениями — pipeline должен упасть +cat > test-k8s-manifests/bad-deployment.yaml < .github/CODEOWNERS <- + Проверяет образ контейнера через внешний vulnerability scanning API + перед деплоем в production. Блокирует деплой если найдены + критические уязвимости. + НАСТРОЙТЕ URL вашего сканера (Trivy, Snyk, Grype и т.д.) + Требует: admissionController.extraArgs: [--enableExternalDataCall=true] +spec: + validationFailureAction: Enforce + background: false # только для живых запросов, не background scan + rules: + - name: check-critical-vulnerabilities + match: + resources: + kinds: + - Pod + namespaces: + - production + preconditions: + any: + - key: "{{ request.operation }}" + operator: In + value: [CREATE, UPDATE] + context: + - name: scanResult + apiCall: + # Замените на URL вашего vulnerability API + urlPath: "https://vuln-api.company.com/v1/scan" + method: POST + data: + - key: image + value: "{{ request.object.spec.containers[0].image }}" + - key: severity + value: CRITICAL + jmesPath: "critical_count || `0`" + validate: + message: >- + Образ '{{ request.object.spec.containers[0].image }}' + содержит {{ scanResult }} критических уязвимостей. + Деплой в production запрещён. + Обновите базовый образ и пересоберите: https://vuln-api.company.com/report + deny: + conditions: + - key: "{{ scanResult }}" + operator: GreaterThan + value: "0" diff --git a/07-advanced/02-external-data/external-data-cache.yaml b/07-advanced/02-external-data/external-data-cache.yaml new file mode 100644 index 0000000..66a7e3f --- /dev/null +++ b/07-advanced/02-external-data/external-data-cache.yaml @@ -0,0 +1,105 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: external-data-cache + namespace: kyverno + labels: + app: kyverno-config +data: + # Список разрешённых реестров (обновляется CronJob) + allowed-registries: | + registry.company.com + gcr.io/company-project + public.ecr.aws/company + # Список одобренных StorageClass + approved-storage-classes: | + standard-ssd + premium-ssd + backup-hdd + # Последнее обновление (проставляется CronJob) + last-updated: "2024-01-01T00:00:00Z" +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: update-policy-cache + namespace: kyverno + annotations: + description: >- + Обновляет ConfigMap external-data-cache данными из внешних API. + Позволяет политикам использовать актуальные данные без прямых apiCall + к внешним сервисам на каждый запрос. +spec: + schedule: "*/10 * * * *" # каждые 10 минут + concurrencyPolicy: Forbid + jobTemplate: + spec: + template: + spec: + serviceAccountName: policy-cache-updater + restartPolicy: OnFailure + containers: + - name: cache-updater + image: bitnami/kubectl:1.28 + env: + - name: EXTERNAL_API_URL + value: "https://api.company.com/v1" + - name: CONFIGMAP_NAME + value: "external-data-cache" + - name: NAMESPACE + value: "kyverno" + command: + - /bin/bash + - -c + - | + set -e + + echo "Fetching allowed registries from external API..." + # В реальности заменить на curl к вашему API + REGISTRIES=$(echo -e "registry.company.com\ngcr.io/company-project") + + echo "Updating ConfigMap..." + kubectl patch configmap ${CONFIGMAP_NAME} \ + -n ${NAMESPACE} \ + --type merge \ + -p "{\"data\":{ + \"allowed-registries\": \"${REGISTRIES}\", + \"last-updated\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\" + }}" + + echo "Cache updated successfully" + resources: + limits: + cpu: 100m + memory: 64Mi +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: policy-cache-updater + namespace: kyverno +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: configmap-updater + namespace: kyverno +rules: +- apiGroups: [""] + resources: ["configmaps"] + resourceNames: ["external-data-cache"] + verbs: ["get", "patch", "update"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: policy-cache-updater + namespace: kyverno +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: configmap-updater +subjects: +- kind: ServiceAccount + name: policy-cache-updater + namespace: kyverno diff --git a/07-advanced/02-external-data/validate-registry-from-cache.yaml b/07-advanced/02-external-data/validate-registry-from-cache.yaml new file mode 100644 index 0000000..058b10a --- /dev/null +++ b/07-advanced/02-external-data/validate-registry-from-cache.yaml @@ -0,0 +1,54 @@ +apiVersion: kyverno.io/v1 +kind: ClusterPolicy +metadata: + name: validate-registry-from-cache + annotations: + policies.kyverno.io/title: "Проверка реестра через кэшированные данные" + policies.kyverno.io/category: Security + policies.kyverno.io/severity: high + policies.kyverno.io/subject: Pod + policies.kyverno.io/description: >- + Проверяет образ контейнера против списка разрешённых реестров, + хранящегося в ConfigMap external-data-cache. + Список обновляется каждые 10 минут CronJob из внешнего API. + Паттерн "кэш в ConfigMap" — быстрее чем прямые apiCall к внешним API. +spec: + validationFailureAction: Enforce + background: true + rules: + - name: check-registry-from-cache + match: + resources: + kinds: + - Pod + exclude: + resources: + namespaces: + - kube-system + - kyverno + context: + - name: allowedRegistries + configMap: + name: external-data-cache + namespace: kyverno + validate: + message: >- + Образ '{{ element.image }}' из недоверенного реестра. + Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}): + {{ allowedRegistries.data.\"allowed-registries\" }} + foreach: + - list: >- + request.object.spec.containers[] | + merge(request.object.spec.initContainers[] || `[]`, @) + deny: + conditions: + all: + - key: "{{ element.image }}" + operator: NotStartsWith + value: "registry.company.com/" + - key: "{{ element.image }}" + operator: NotStartsWith + value: "gcr.io/company-project/" + - key: "{{ element.image }}" + operator: NotStartsWith + value: "public.ecr.aws/company/" diff --git a/README.md b/README.md new file mode 100644 index 0000000..f45bf46 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# Kyverno: Полный контроль Kubernetes + +Репозиторий с практическими материалами курса. Каждая папка соответствует уроку и содержит готовые к запуску манифесты и инструкции. + +## Структура курса + +| Раздел | Тема | +|--------|------| +| [01-introduction](./01-introduction/) | Введение, установка, структура политик | +| [02-validation](./02-validation/) | Validation политики | +| [03-mutation](./03-mutation/) | Mutation политики | +| [04-generation](./04-generation/) | Generation политики | +| [05-variables](./05-variables/) | Переменные и контекст | +| [06-monitoring](./06-monitoring/) | Мониторинг и отладка | +| [07-advanced](./07-advanced/) | CI/CD и внешние интеграции | + +## Требования + +- Kubernetes >= 1.26 +- Helm >= 3.10 +- kubectl +- [Kyverno CLI](https://kyverno.io/docs/kyverno-cli/) (для локального тестирования) + +```bash +# Установка Kyverno CLI (macOS) +brew install kyverno + +# Linux +curl -LO https://github.com/kyverno/kyverno/releases/latest/download/kyverno-cli_linux_amd64.tar.gz +tar -xzf kyverno-cli_linux_amd64.tar.gz && sudo mv kyverno /usr/local/bin/ +``` + +## Быстрый старт + +```bash +# 1. Установить Kyverno +helm repo add kyverno https://kyverno.github.io/kyverno/ +helm repo update +helm install kyverno kyverno/kyverno -n kyverno --create-namespace + +# 2. Проверить установку +kubectl get pods -n kyverno + +# 3. Применить первую политику +kubectl apply -f 02-validation/01-resource-validation/require-resource-limits.yaml +```