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 - kube-system
- kyverno - kyverno
validate: 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: foreach:
- list: >- - list: >-
request.object.spec.containers[] | request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) | merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @) merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' в поде '{{ request.object.metadata.name }}'
не имеет resource limits. Добавьте resources.limits.memory и resources.limits.cpu.
pattern: pattern:
resources: resources:
limits: limits:
@@ -25,20 +25,20 @@ spec:
namespaces: namespaces:
- kube-system - kube-system
validate: validate:
message: >-
Образ '{{ element.image }}' использует тег :latest или не имеет тега.
Используйте конкретный тег (например, nginx:1.25.3) или digest
(nginx@sha256:abc123...) для воспроизводимых деплойментов.
foreach: foreach:
- list: >- - list: >-
request.object.spec.containers[] | 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: deny:
conditions: conditions:
any: any:
- key: "{{ element.image }}" - key: "{{ regex_match(':latest', element.image) }}"
operator: Contains operator: Equals
value: ":latest" value: true
- key: "{{ element.image }}" - key: "{{ regex_match('^(([^/]+/)*[^/:]+)$', element.image) }}"
operator: NotContains operator: Equals
value: ":" value: true
@@ -27,25 +27,23 @@ spec:
- kube-system - kube-system
- kyverno - kyverno
validate: validate:
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Разрешены только:
- registry.company.com/
- gcr.io/company-project/
Загрузите образ в внутренний реестр и обновите манифест.
foreach: foreach:
- list: >- - list: >-
request.object.spec.containers[] | 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: deny:
conditions: conditions:
all: all:
# Образ НЕ из первого доверенного реестра # regex_match: false = образ НЕ начинается с доверенного реестра
- key: "{{ element.image }}" - key: "{{ regex_match('^registry\\.company\\.com/', element.image) }}"
operator: NotStartsWith operator: Equals
value: "registry.company.com/" value: false
# И НЕ из второго доверенного реестра - key: "{{ regex_match('^gcr\\.io/company-project/', element.image) }}"
- key: "{{ element.image }}" operator: Equals
operator: NotStartsWith value: false
value: "gcr.io/company-project/"
# Добавьте дополнительные условия по аналогии # Добавьте дополнительные условия по аналогии
@@ -26,15 +26,14 @@ spec:
namespaces: namespaces:
- kube-system - kube-system
validate: validate:
message: >-
Контейнер '{{ element.name }}' добавляет запрещённые capabilities:
{{ element.securityContext.capabilities.add }}.
Разрешена только NET_BIND_SERVICE.
Пересмотрите необходимость этих привилегий.
foreach: foreach:
- list: >- - list: >-
request.object.spec.containers[] | 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: deny:
conditions: conditions:
any: any:
@@ -52,6 +52,6 @@ spec:
deny: deny:
conditions: conditions:
any: any:
- key: "{{ request.object.spec.volumes[].hostPath | length(@) }}" - key: "{{ request.object.spec.volumes[?hostPath] | length(@) }}"
operator: GreaterThan operator: GreaterThan
value: "0" value: "0"
@@ -26,15 +26,14 @@ spec:
namespaces: namespaces:
- kube-system - kube-system
validate: validate:
message: >-
Контейнер '{{ element.name }}' имеет securityContext.privileged: true.
Привилегированные контейнеры запрещены — они получают полный доступ к хосту.
Удалите поле securityContext.privileged или установите значение false.
foreach: foreach:
- list: >- - list: >-
request.object.spec.containers[] | request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) | merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @) merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >-
Контейнер '{{ element.name }}' имеет securityContext.privileged: true.
Привилегированные контейнеры запрещены. Удалите поле или установите false.
deny: deny:
conditions: conditions:
any: any:
@@ -25,14 +25,14 @@ spec:
namespaces: namespaces:
- kube-system - kube-system
validate: validate:
foreach:
- list: >-
request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) |
merge(request.object.spec.ephemeralContainers[] || `[]`, @)
message: >- message: >-
Контейнер '{{ element.name }}' не сбрасывает все capabilities. Контейнер '{{ element.name }}' не сбрасывает все capabilities.
Добавьте в securityContext: Добавьте securityContext.capabilities.drop: [ALL].
capabilities:
drop:
- ALL
foreach:
- list: "request.object.spec.containers"
deny: deny:
conditions: conditions:
all: all:
@@ -45,11 +45,11 @@ spec:
namespaces: namespaces:
- kube-system - kube-system
validate: validate:
message: >-
Контейнер '{{ element.name }}' использует runAsUser: 0 (root).
Установите runAsUser >= 1000.
foreach: 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: deny:
conditions: conditions:
any: any:
@@ -30,11 +30,15 @@ spec:
Добавьте в spec.securityContext: Добавьте в spec.securityContext:
seccompProfile: seccompProfile:
type: RuntimeDefault type: RuntimeDefault
pattern: anyPattern:
spec: - spec:
securityContext: securityContext:
seccompProfile: seccompProfile:
type: "RuntimeDefault | Localhost" type: RuntimeDefault
- spec:
securityContext:
seccompProfile:
type: Localhost
- name: disallow-unconfined-seccomp - name: disallow-unconfined-seccomp
match: match:
+1 -2
View File
@@ -28,5 +28,4 @@ spec:
spec: spec:
containers: containers:
- name: "{{ element.name }}" - name: "{{ element.name }}"
image: >- image: "{{ replace_all(element.image, ':latest', ':stable') }}"
{{ replace_all('{{ element.image }}', ':latest', ':stable') }}
+6 -6
View File
@@ -41,12 +41,12 @@ spec:
- list: "request.object.spec.containers" - list: "request.object.spec.containers"
preconditions: preconditions:
any: any:
- key: "{{ element.image }}" - key: "{{ regex_match('openjdk-v1', element.image) }}"
operator: Contains operator: Equals
value: "openjdk-v1" value: true
- key: "{{ element.image }}" - key: "{{ regex_match('eclipse-v1', element.image) }}"
operator: Contains operator: Equals
value: "eclipse-v1" value: true
patchStrategicMerge: patchStrategicMerge:
spec: spec:
containers: containers:
@@ -31,7 +31,7 @@ spec:
audit.company.com/created-by: "{{ request.userInfo.username }}" audit.company.com/created-by: "{{ request.userInfo.username }}"
audit.company.com/created-at: "{{ time_now_utc() }}" audit.company.com/created-at: "{{ time_now_utc() }}"
audit.company.com/user-groups: >- audit.company.com/user-groups: >-
{{ request.userInfo.groups | join(', ', @) }} {{ join(', ', request.userInfo.groups) }}
- name: set-environment-labels - name: set-environment-labels
match: match:
@@ -29,6 +29,9 @@ spec:
configMap: configMap:
name: kyverno-global-config name: kyverno-global-config
namespace: kyverno namespace: kyverno
- name: serviceType
variable:
value: "{{ request.object.metadata.labels.\"service-type\" || 'default' }}"
mutate: mutate:
foreach: foreach:
- list: "request.object.spec.containers" - list: "request.object.spec.containers"
@@ -38,7 +41,5 @@ spec:
- name: "{{ element.name }}" - name: "{{ element.name }}"
resources: resources:
limits: limits:
+(memory): >- +(memory): "{{ globalConfig.data.\"{{ serviceType }}_memory\" || '256Mi' }}"
{{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_memory\" || '256Mi' }} +(cpu): "{{ globalConfig.data.\"{{ serviceType }}_cpu\" || '250m' }}"
+(cpu): >-
{{ globalConfig.data.\"{{ request.object.metadata.labels.\"service-type\" || 'default' }}_cpu\" || '250m' }}
@@ -32,7 +32,6 @@ spec:
namespace: "{{ request.object.metadata.name }}" namespace: "{{ request.object.metadata.name }}"
synchronize: true synchronize: true
data: data:
kind: ConfigMap
metadata: metadata:
labels: labels:
generated-by: kyverno generated-by: kyverno
@@ -24,6 +24,6 @@ spec:
purpose: debug purpose: debug
conditions: conditions:
any: any:
- key: "{{ time_since('', request.object.metadata.creationTimestamp, '') }}" - key: "{{ time_since('', target.metadata.creationTimestamp, '') }}"
operator: GreaterThan operator: GreaterThan
value: "4h" value: "4h0s"
+79 -22
View File
@@ -1,9 +1,20 @@
# Другие варианты проверок apiVersion: kyverno.io/v1
# kind: ClusterPolicy
# проверить, что PVC использует StorageClass из одобренного списка: metadata:
# name: check-storage-class-approval
rules: annotations:
- name: check-storage-class 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: match:
resources: resources:
kinds: kinds:
@@ -16,42 +27,88 @@ rules:
validate: validate:
message: >- message: >-
StorageClass '{{ request.object.spec.storageClassName }}' не одобрена для production. StorageClass '{{ request.object.spec.storageClassName }}' не одобрена для production.
Используйте StorageClass с лейблом approved-for-production: "true" Используйте StorageClass с лейблом approved-for-production: "true".
deny: deny:
conditions: conditions:
any:
- key: "{{ storageClassInfo }}" - key: "{{ storageClassInfo }}"
operator: NotEquals operator: NotEquals
value: "true" value: "true"
---
# проверить, что количество реплик не превышает кворум с учётом текущей нагрузки: apiVersion: kyverno.io/v1
# kind: ClusterPolicy
context: metadata:
- name: existingDeployments 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: apiCall:
urlPath: >- urlPath: >-
/apis/apps/v1/namespaces/{{ request.object.metadata.namespace }}/deployments /apis/apps/v1/namespaces/{{ request.object.metadata.namespace }}/deployments
jmesPath: "items[?metadata.name != '{{ request.object.metadata.name }}'] | length(@)" jmesPath: "items[?metadata.name != '{{ request.object.metadata.name }}'] | length(@)"
validate: validate:
message: >- message: >-
В namespace уже {{ existingDeployments }} деплойментов. В namespace '{{ request.object.metadata.namespace }}' уже {{ existingDeployments }} деплойментов.
Максимум разрешено 20. Максимум разрешено 20.
deny: deny:
conditions: conditions:
any:
- key: "{{ existingDeployments }}" - key: "{{ existingDeployments }}"
operator: GreaterThanOrEquals operator: GreaterThanOrEquals
value: "20" value: "20"
---
# принимать решения на основе состояния нод apiVersion: kyverno.io/v1
# kind: ClusterPolicy
context: metadata:
- name: nodesInfo 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: apiCall:
urlPath: "/api/v1/nodes" urlPath: "/api/v1/nodes"
jmesPath: "items[?metadata.labels.\"node-type\" == 'gpu'].metadata.name" jmesPath: "items[?metadata.labels.\"node-type\" == 'gpu'].metadata.name"
validate: validate:
message: "GPU workloads требуют минимум 2 GPU-ноды в кластере" message: >-
GPU workloads требуют минимум 2 GPU-ноды в кластере.
Текущее количество GPU-нод: {{ length(gpuNodes) }}.
deny: deny:
conditions: conditions:
- key: "{{ length(nodesInfo) }}" any:
- key: "{{ length(gpuNodes) }}"
operator: LessThan operator: LessThan
value: "2" value: "2"
@@ -1,7 +1,21 @@
# Полезный паттерн — разные правила для CREATE и UPDATE apiVersion: kyverno.io/v1
kind: ClusterPolicy
rules: metadata:
- name: validate-on-create-only 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: match:
resources: resources:
kinds: kinds:
@@ -12,9 +26,15 @@ rules:
operator: Equals operator: Equals
value: CREATE value: CREATE
validate: 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: match:
resources: resources:
kinds: kinds:
@@ -24,10 +44,17 @@ rules:
- key: "{{ request.operation }}" - key: "{{ request.operation }}"
operator: Equals operator: Equals
value: UPDATE value: UPDATE
- key: >- - key: "{{ request.object.spec.template.spec.containers[0].image }}"
{{ request.object.spec.template.spec.containers[0].image }}
operator: NotEquals operator: NotEquals
value: >- value: "{{ request.oldObject.spec.template.spec.containers[0].image }}"
{{ request.oldObject.spec.template.spec.containers[0].image }}
validate: 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: configMap:
name: external-data-cache name: external-data-cache
namespace: kyverno namespace: kyverno
- name: allowedPattern
variable:
value: >-
{{ join('', ['^(', join('|', split(allowedRegistries.data.\"allowed-registries\", '\n')[?@ != '']), ')']) }}
validate: validate:
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}):
{{ allowedRegistries.data.\"allowed-registries\" }}
foreach: foreach:
- list: >- - list: >-
request.object.spec.containers[] | request.object.spec.containers[] |
merge(request.object.spec.initContainers[] || `[]`, @) merge(request.object.spec.initContainers[] || `[]`, @)
message: >-
Образ '{{ element.image }}' из недоверенного реестра.
Список разрешённых реестров (обновлён {{ allowedRegistries.data.\"last-updated\" }}):
{{ allowedRegistries.data.\"allowed-registries\" }}
deny: deny:
conditions: conditions:
all: all:
- key: "{{ element.image }}" - key: "{{ regex_match(allowedPattern, element.image) }}"
operator: NotStartsWith operator: Equals
value: "registry.company.com/" value: false
- key: "{{ element.image }}"
operator: NotStartsWith
value: "gcr.io/company-project/"
- key: "{{ element.image }}"
operator: NotStartsWith
value: "public.ecr.aws/company/"