fix pols for 1.18
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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') }}
|
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
# Другие варианты проверок
|
apiVersion: kyverno.io/v1
|
||||||
#
|
kind: ClusterPolicy
|
||||||
# проверить, что PVC использует StorageClass из одобренного списка:
|
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:
|
rules:
|
||||||
- name: check-storage-class
|
- name: check-storage-class
|
||||||
match:
|
match:
|
||||||
@@ -16,15 +27,35 @@ 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
|
||||||
|
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:
|
context:
|
||||||
- name: existingDeployments
|
- name: existingDeployments
|
||||||
apiCall:
|
apiCall:
|
||||||
@@ -33,25 +64,51 @@ context:
|
|||||||
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
|
||||||
|
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:
|
context:
|
||||||
- name: nodesInfo
|
- 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,5 +1,19 @@
|
|||||||
# Полезный паттерн — разные правила для CREATE и UPDATE
|
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:
|
rules:
|
||||||
- name: validate-on-create-only
|
- name: validate-on-create-only
|
||||||
match:
|
match:
|
||||||
@@ -12,7 +26,13 @@ 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:
|
||||||
@@ -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/"
|
|
||||||
|
|||||||
Reference in New Issue
Block a user