init
This commit is contained in:
81
02-validation/01-resource-validation/README.md
Normal file
81
02-validation/01-resource-validation/README.md
Normal file
@@ -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"}}'
|
||||
```
|
||||
@@ -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/"
|
||||
# Добавьте дополнительные условия по аналогии
|
||||
@@ -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: ":"
|
||||
46
02-validation/01-resource-validation/require-labels.yaml
Normal file
46
02-validation/01-resource-validation/require-labels.yaml
Normal file
@@ -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)$"
|
||||
@@ -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"
|
||||
@@ -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: "?*"
|
||||
@@ -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"
|
||||
@@ -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 отклонит
|
||||
@@ -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"
|
||||
30
02-validation/01-resource-validation/tests/kyverno-test.yaml
Normal file
30
02-validation/01-resource-validation/tests/kyverno-test.yaml
Normal file
@@ -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
|
||||
75
02-validation/02-security/README.md
Normal file
75
02-validation/02-security/README.md
Normal file
@@ -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
|
||||
```
|
||||
@@ -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
|
||||
57
02-validation/02-security/disallow-host-namespaces.yaml
Normal file
57
02-validation/02-security/disallow-host-namespaces.yaml
Normal file
@@ -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"
|
||||
@@ -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
|
||||
41
02-validation/02-security/require-drop-all-capabilities.yaml
Normal file
41
02-validation/02-security/require-drop-all-capabilities.yaml
Normal file
@@ -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[] || `[]` }}"
|
||||
58
02-validation/02-security/require-run-as-non-root.yaml
Normal file
58
02-validation/02-security/require-run-as-non-root.yaml
Normal file
@@ -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
|
||||
52
02-validation/02-security/require-seccomp-profile.yaml
Normal file
52
02-validation/02-security/require-seccomp-profile.yaml
Normal file
@@ -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
|
||||
37
02-validation/02-security/restrict-automount-sa-token.yaml
Normal file
37
02-validation/02-security/restrict-automount-sa-token.yaml
Normal file
@@ -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
|
||||
21
02-validation/02-security/test-resources/pod-insecure.yaml
Normal file
21
02-validation/02-security/test-resources/pod-insecure.yaml
Normal file
@@ -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"
|
||||
28
02-validation/02-security/test-resources/pod-secure.yaml
Normal file
28
02-validation/02-security/test-resources/pod-secure.yaml
Normal file
@@ -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"
|
||||
126
02-validation/03-reporting/README.md
Normal file
126
02-validation/03-reporting/README.md
Normal file
@@ -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 - <<EOF
|
||||
apiVersion: kyverno.io/v1
|
||||
kind: ClusterPolicy
|
||||
metadata:
|
||||
name: audit-all-pods
|
||||
spec:
|
||||
validationFailureAction: Audit
|
||||
background: true
|
||||
rules:
|
||||
- name: check-labels
|
||||
match:
|
||||
resources:
|
||||
kinds: [Pod]
|
||||
validate:
|
||||
message: "Под должен иметь лейбл app"
|
||||
pattern:
|
||||
metadata:
|
||||
labels:
|
||||
app: "?*"
|
||||
EOF
|
||||
|
||||
# Подождать background scan (30-60 секунд), потом:
|
||||
kubectl get policyreports -A | grep -v 0/0
|
||||
```
|
||||
99
02-validation/03-reporting/prometheus-alert-rules.yaml
Normal file
99
02-validation/03-reporting/prometheus-alert-rules.yaml
Normal file
@@ -0,0 +1,99 @@
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: kyverno-alerts
|
||||
namespace: kyverno
|
||||
labels:
|
||||
prometheus: kube-prometheus
|
||||
role: alert-rules
|
||||
spec:
|
||||
groups:
|
||||
- name: kyverno.availability
|
||||
rules:
|
||||
- alert: KyvernoDown
|
||||
expr: up{job="kyverno-svc-metrics"} == 0
|
||||
for: 1m
|
||||
labels:
|
||||
severity: critical
|
||||
annotations:
|
||||
summary: "Kyverno недоступен"
|
||||
description: >-
|
||||
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]))
|
||||
17
02-validation/03-reporting/service-monitor.yaml
Normal file
17
02-validation/03-reporting/service-monitor.yaml
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user