fix pols for 1.18

This commit is contained in:
2026-05-14 18:55:39 +07:00
parent 5578400e7f
commit ffa61ab646
19 changed files with 266 additions and 180 deletions
+13
View File
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(kyverno version *)",
"Bash(rtk curl *)",
"Bash(tar -xzf /tmp/kyverno.tar.gz -C /tmp kyverno)",
"Bash(sudo mv /tmp/kyverno /usr/local/bin/kyverno)",
"Bash(mv /tmp/kyverno ~/bin/kyverno)",
"Bash(mv /tmp/kyverno ~/.local/bin/kyverno)",
"Bash(export PATH=\"$HOME/bin:$HOME/.local/bin:$PATH\")"
]
}
}
@@ -26,22 +26,14 @@ spec:
- 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[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' в поде '{{ request.object.metadata.name }}'
не имеет resource limits. Добавьте resources.limits.memory и resources.limits.cpu.
pattern:
resources:
limits:
@@ -25,20 +25,20 @@ spec:
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[] || `[]`, @)
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Образ '{{ element.image }}' использует тег :latest или не имеет тега.
Используйте конкретный тег (nginx:1.25.3) или digest (nginx@sha256:...).
deny:
conditions:
any:
- key: "{{ element.image }}"
operator: Contains
value: ":latest"
- key: "{{ element.image }}"
operator: NotContains
value: ":"
- key: "{{ regex_match(':latest', element.image) }}"
operator: Equals
value: true
- key: "{{ regex_match('^(([^/]+/)*[^/:]+)$', element.image) }}"
operator: Equals
value: true
@@ -27,25 +27,23 @@ spec:
- 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[] || `[]`, @)
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Разрешены: registry.company.com/, gcr.io/company-project/.
Загрузите образ в внутренний реестр и обновите манифест.
deny:
conditions:
all:
# Образ НЕ из первого доверенного реестра
- key: "{{ element.image }}"
operator: NotStartsWith
value: "registry.company.com/"
# И НЕ из второго доверенного реестра
- key: "{{ element.image }}"
operator: NotStartsWith
value: "gcr.io/company-project/"
# regex_match: false = образ НЕ начинается с доверенного реестра
- key: "{{ regex_match('^registry\\.company\\.com/', element.image) }}"
operator: Equals
value: false
- key: "{{ regex_match('^gcr\\.io/company-project/', element.image) }}"
operator: Equals
value: false
# Добавьте дополнительные условия по аналогии
@@ -26,15 +26,14 @@ spec:
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[] || `[]`, @)
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' добавляет запрещённые capabilities.
Разрешена только NET_BIND_SERVICE. Пересмотрите необходимость привилегий.
deny:
conditions:
any:
@@ -52,6 +52,6 @@ spec:
deny:
conditions:
any:
- key: "{{ request.object.spec.volumes[].hostPath | length(@) }}"
- key: "{{ request.object.spec.volumes[?hostPath] | length(@) }}"
operator: GreaterThan
value: "0"
@@ -26,15 +26,14 @@ spec:
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[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' имеет securityContext.privileged: true.
Привилегированные контейнеры запрещены. Удалите поле или установите false.
deny:
conditions:
any:
@@ -25,14 +25,14 @@ spec:
namespaces:
- kube-system
validate:
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' не сбрасывает все capabilities.
Добавьте в securityContext:
capabilities:
drop:
- ALL
foreach:
- list: "request.object.spec.containers"
Добавьте securityContext.capabilities.drop: [ALL].
deny:
conditions:
all:
@@ -45,11 +45,11 @@ spec:
namespaces:
- kube-system
validate:
message: >-
Контейнер '{{ element.name }}' использует runAsUser: 0 (root).
Установите runAsUser >= 1000.
foreach:
- list: "request.object.spec.containers"
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
message: "Контейнер '{{ element.name }}' использует runAsUser: 0 (root). Установите runAsUser >= 1000."
deny:
conditions:
any:
@@ -30,11 +30,15 @@ spec:
Добавьте в spec.securityContext:
seccompProfile:
type: RuntimeDefault
pattern:
spec:
anyPattern:
- spec:
securityContext:
seccompProfile:
type: "RuntimeDefault | Localhost"
type: RuntimeDefault
- spec:
securityContext:
seccompProfile:
type: Localhost
- name: disallow-unconfined-seccomp
match:
+1 -2
View File
@@ -28,5 +28,4 @@ spec:
spec:
containers:
- name: "{{ element.name }}"
image: >-
{{ replace_all('{{ element.image }}', ':latest', ':stable') }}
image: "{{ replace_all(element.image, ':latest', ':stable') }}"
+6 -6
View File
@@ -41,12 +41,12 @@ spec:
- list: "request.object.spec.containers"
preconditions:
any:
- key: "{{ element.image }}"
operator: Contains
value: "openjdk-v1"
- key: "{{ element.image }}"
operator: Contains
value: "eclipse-v1"
- key: "{{ regex_match('openjdk-v1', element.image) }}"
operator: Equals
value: true
- key: "{{ regex_match('eclipse-v1', element.image) }}"
operator: Equals
value: true
patchStrategicMerge:
spec:
containers:
@@ -31,7 +31,7 @@ spec:
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(', ', @) }}
{{ join(', ', request.userInfo.groups) }}
- name: set-environment-labels
match:
@@ -29,6 +29,9 @@ spec:
configMap:
name: kyverno-global-config
namespace: kyverno
- name: serviceType
variable:
value: "{{ request.object.metadata.labels.\"service-type\" || 'default' }}"
mutate:
foreach:
- list: "request.object.spec.containers"
@@ -38,7 +41,5 @@ spec:
- 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' }}
+(memory): "{{ globalConfig.data.\"{{ serviceType }}_memory\" || '256Mi' }}"
+(cpu): "{{ globalConfig.data.\"{{ serviceType }}_cpu\" || '250m' }}"
@@ -32,7 +32,6 @@ spec:
namespace: "{{ request.object.metadata.name }}"
synchronize: true
data:
kind: ConfigMap
metadata:
labels:
generated-by: kyverno
@@ -24,6 +24,6 @@ spec:
purpose: debug
conditions:
any:
- key: "{{ time_since('', request.object.metadata.creationTimestamp, '') }}"
- key: "{{ time_since('', target.metadata.creationTimestamp, '') }}"
operator: GreaterThan
value: "4h"
value: "4h0s"
+79 -22
View File
@@ -1,9 +1,20 @@
# Другие варианты проверок
#
# проверить, что PVC использует StorageClass из одобренного списка:
#
rules:
- name: check-storage-class
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: check-storage-class-approval
annotations:
policies.kyverno.io/title: "Проверка одобрения StorageClass"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: high
policies.kyverno.io/subject: PersistentVolumeClaim
policies.kyverno.io/description: >-
Проверяет, что PVC использует StorageClass из одобренного списка.
StorageClass считается одобренной, если имеет лейбл approved-for-production: "true".
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-storage-class
match:
resources:
kinds:
@@ -16,42 +27,88 @@ rules:
validate:
message: >-
StorageClass '{{ request.object.spec.storageClassName }}' не одобрена для production.
Используйте StorageClass с лейблом approved-for-production: "true"
Используйте StorageClass с лейблом approved-for-production: "true".
deny:
conditions:
any:
- key: "{{ storageClassInfo }}"
operator: NotEquals
value: "true"
# проверить, что количество реплик не превышает кворум с учётом текущей нагрузки:
#
context:
- name: existingDeployments
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: limit-deployments-per-namespace
annotations:
policies.kyverno.io/title: "Лимит Deployment в namespace"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Deployment
policies.kyverno.io/description: >-
Ограничивает количество Deployment в одном namespace до 20.
Используется apiCall для подсчёта существующих деплойментов.
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-deployment-count
match:
resources:
kinds:
- Deployment
context:
- name: existingDeployments
apiCall:
urlPath: >-
/apis/apps/v1/namespaces/{{ request.object.metadata.namespace }}/deployments
jmesPath: "items[?metadata.name != '{{ request.object.metadata.name }}'] | length(@)"
validate:
validate:
message: >-
В namespace уже {{ existingDeployments }} деплойментов.
В namespace '{{ request.object.metadata.namespace }}' уже {{ existingDeployments }} деплойментов.
Максимум разрешено 20.
deny:
conditions:
any:
- key: "{{ existingDeployments }}"
operator: GreaterThanOrEquals
value: "20"
# принимать решения на основе состояния нод
#
context:
- name: nodesInfo
---
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-gpu-nodes-for-gpu-workloads
annotations:
policies.kyverno.io/title: "GPU workloads требуют GPU-нод"
policies.kyverno.io/category: Resources
policies.kyverno.io/severity: high
policies.kyverno.io/subject: Pod
policies.kyverno.io/description: >-
Запрещает запускать GPU-workloads если в кластере меньше 2 GPU-нод.
GPU-workload определяется лейблом workload-type: gpu на поде.
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-gpu-nodes
match:
resources:
kinds:
- Pod
selector:
matchLabels:
workload-type: gpu
context:
- name: gpuNodes
apiCall:
urlPath: "/api/v1/nodes"
jmesPath: "items[?metadata.labels.\"node-type\" == 'gpu'].metadata.name"
validate:
message: "GPU workloads требуют минимум 2 GPU-ноды в кластере"
validate:
message: >-
GPU workloads требуют минимум 2 GPU-ноды в кластере.
Текущее количество GPU-нод: {{ length(gpuNodes) }}.
deny:
conditions:
- key: "{{ length(nodesInfo) }}"
any:
- key: "{{ length(gpuNodes) }}"
operator: LessThan
value: "2"
@@ -1,7 +1,21 @@
# Полезный паттерн — разные правила для CREATE и UPDATE
rules:
- name: validate-on-create-only
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: validate-deployment-operations
annotations:
policies.kyverno.io/title: "Разные проверки для CREATE и UPDATE"
policies.kyverno.io/category: Governance
policies.kyverno.io/severity: medium
policies.kyverno.io/subject: Deployment
policies.kyverno.io/description: >-
Демонстрирует паттерн: разные правила для CREATE и UPDATE операций.
При создании — обязательные лейблы app и team.
При обновлении — запрет смены образа на тег latest.
spec:
validationFailureAction: Enforce
background: false
rules:
- name: validate-on-create-only
match:
resources:
kinds:
@@ -12,9 +26,15 @@ rules:
operator: Equals
value: CREATE
validate:
# применяется только при создании
message: >-
Новый Deployment '{{ request.object.metadata.name }}' должен иметь лейблы app и team.
pattern:
metadata:
labels:
app: "?*"
team: "?*"
- name: validate-image-on-update
- name: validate-image-on-update
match:
resources:
kinds:
@@ -24,10 +44,17 @@ rules:
- key: "{{ request.operation }}"
operator: Equals
value: UPDATE
- key: >-
{{ request.object.spec.template.spec.containers[0].image }}
- key: "{{ request.object.spec.template.spec.containers[0].image }}"
operator: NotEquals
value: >-
{{ request.oldObject.spec.template.spec.containers[0].image }}
value: "{{ request.oldObject.spec.template.spec.containers[0].image }}"
validate:
# применяется только при UPDATE с изменением образа
message: >-
Образ изменён с '{{ request.oldObject.spec.template.spec.containers[0].image }}'
на '{{ request.object.spec.template.spec.containers[0].image }}'.
Запрещено использовать тег latest при обновлении образа.
deny:
conditions:
any:
- key: "{{ request.object.spec.template.spec.containers[0].image }}"
operator: EndsWith
value: ":latest"
@@ -31,24 +31,22 @@ spec:
configMap:
name: external-data-cache
namespace: kyverno
- name: allowedPattern
variable:
value: >-
{{ join('', ['^(', join('|', split(allowedRegistries.data.\"allowed-registries\", '\n')[?@ != '']), ')']) }}
validate:
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}):
{{ allowedRegistries.data.\"allowed-registries\" }}
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @)
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}):
{{ allowedRegistries.data.\"allowed-registries\" }}
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/"
- key: "{{ regex_match(allowedPattern, element.image) }}"
operator: Equals
value: false