применять правила соответствия на каждом запросе к API Kubernetes
Policy as Code в Kubernetes: соответствие требованиям и безопасность через OPA Gatekeeper и Kyverno
14 минут
Страница в вики не отклонит плохой манифест. Policy as Code превращает правила безопасности и соответствия в проверки при admission, которые хранятся в Git и срабатывают при каждом создании и обновлении ресурса — до попадания в продакшен.
Почему записанные правила не работают без admission
В продакшен-кластере накапливаются требования, которые легко сформулировать и трудно применять единообразно: контейнеры не от root, образы только из доверенных реестров, у каждого пода заданы requests, обязательные метки для учёта затрат, сеть по умолчанию «запретить всё», секреты не в ConfigMap. Правила живут в вики и чатах, но документация не отклоняет манифест. Helm-чарт с root, чужим реестром и без лимитов всё равно применится — пока об этом не вспомнят на разборе инцидента. Ручная проверка каждого изменения не масштабируется. Самописные admission-вебхуки без тестов и владельца быстро устаревают. Policy as Code хранит правила в Git, проверяет каждый CREATE и UPDATE на API-сервере через validating и mutating admission, и даёт платформенной команде профилактику вместо разбора «почему так выкатили» после инцидента.
OPA Gatekeeper и Kyverno: два движка, один путь admission
Оба инструмента стоят в цепочке admission: оценивают входящий ресурс, затем принимают, отклоняют или изменяют его до записи в etcd, и умеют проверять уже существующие объекты в режиме только отчёта. OPA Gatekeeper оборачивает Open Policy Agent и язык Rego — удобен для сложной логики, внешних данных и компаний, где OPA уже стандарт вне Kubernetes. Политики оформляются как ConstraintTemplate и экземпляры Constraint с параметрами. Kyverno описывает правила на YAML — знакомые match, validate, mutate, generate и verifyImages без нового языка; мутация «из коробки» богаче, чем у Gatekeeper. У Gatekeeper мутация уже — в основном через Assign и ModifySet; для дефолтов в подах чаще хватает Kyverno. Gatekeeper — graduated-проект CNCF с большой библиотекой; Kyverno — incubating, но растёт быстро. Берите Kyverno, если команда хочет только YAML и встроенную мутацию. Берите Gatekeeper, если логика compliance тянется на несколько систем или нужна выразительность Rego. На один тип правил лучше один движок — иначе вебхуки спорят друг с другом.
helm repo add kyverno https://kyverno.github.io/kyverno/
helm upgrade --install kyverno kyverno/kyverno \
--namespace kyverno --create-namespace
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm upgrade --install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system --create-namespace \
--set enableExternalData=falseKyverno: запрет root и автоматические настройки безопасности
Надёжнее запрещать любой контейнер или initContainer с runAsUser равным нулю через deny, чем жёсткий pattern, который пропускает неполный securityContext. Перед режимом Enforce по всему кластеру проверьте отклонение и принятие тестовых подов через kubectl. Мутация может проставить runAsNonRoot, сброс capabilities и readOnlyRootFilesystem, если разработчик ничего не указал — но рядом держите validate, чтобы нельзя было вернуть привилегированный режим. Исключите kube-system и пространство имён самого Kyverno из жёстких правил.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: disallow-root-containers
spec:
validationFailureAction: Enforce
background: true
rules:
- name: disallow-root-uid
match:
any:
- resources:
kinds: [Pod]
exclude:
any:
- resources:
namespaces:
- kube-system
- kyverno
validate:
message: "Containers must not run as root (UID 0)."
deny:
conditions:
any:
- key: "{{ request.object.spec.containers[?securityContext.runAsUser == `0`] | length(@) }}"
operator: GreaterThan
value: 0
- key: "{{ request.object.spec.initContainers[?securityContext.runAsUser == `0`] | length(@) }}"
operator: GreaterThan
value: 0# Rejected — runs as root
kubectl run nginx-root --image=nginx:1.27 --dry-run=server -o yaml \
--overrides='{"spec":{"containers":[{"name":"nginx-root","image":"nginx:1.27","securityContext":{"runAsUser":0}}]}}' \
| kubectl apply -f -
# Accepted — non-root UID
kubectl run nginx-safe --image=nginx:1.27 --dry-run=server -o yaml \
--overrides='{"spec":{"containers":[{"name":"nginx-safe","image":"nginx:1.27","securityContext":{"runAsNonRoot":true,"runAsUser":1000}}]}}' \
| kubectl apply -f -apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: add-default-security-context
spec:
rules:
- name: add-run-as-non-root
match:
any:
- resources:
kinds: [Pod]
mutate:
patchStrategicMerge:
spec:
securityContext:
+(runAsNonRoot): true
+(runAsUser): 1000
containers:
- (name): "*"
securityContext:
+(allowPrivilegeEscalation): false
+(readOnlyRootFilesystem): true
+(capabilities):
drop:
- ALLOPA Gatekeeper: только доверенные реестры образов
Логику на Rego описываете один раз в ConstraintTemplate, параметры — в Constraint под каждое окружение. Сначала подключите production и staging. Сравнивайте префиксы реестра через startswith и не забывайте initContainers в шаблоне. Режим dryrun даёт аудит без блокировки — исправьте манифесты и Helm в CI, затем смените enforcementAction на deny.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedregistries
spec:
crd:
spec:
names:
kind: K8sAllowedRegistries
validation:
openAPIV3Schema:
type: object
properties:
registries:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedregistries
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not image_allowed(container.image)
msg := sprintf("container '%v' image '%v' not in allowed registries", [container.name, container.image])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not image_allowed(container.image)
msg := sprintf("initContainer '%v' image '%v' not in allowed registries", [container.name, container.image])
}
image_allowed(image) {
prefix := input.parameters.registries[_]
startswith(image, prefix)
}apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
name: allow-only-trusted-registries
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces:
- production
- staging
parameters:
registries:
- "registry.company.com/"
- "gcr.io/my-project/"
- "docker.io/library/"Аудит существующих workload перед режимом блокировки
Не включайте deny в продакшене в первый же день. У Kyverno — validationFailureAction Audit, у Gatekeeper — enforcementAction dryrun: нарушения видны, создание не блокируется. Kyverno пишет PolicyReport и ClusterPolicyReport; Gatekeeper показывает violations в status Constraint и через audit sync. Соберите отчёты за спринт, поправьте манифесты и чарты в CI, потом переводите политики в Enforce. Обязательно исключите kube-system, cert-manager и namespace контроллеров политик.
# Kyverno
spec:
validationFailureAction: Audit
# Gatekeeper Constraint
spec:
enforcementAction: dryrun# Kyverno policy reports
kubectl get policyreport,clusterpolicyreport -A
# Gatekeeper constraint violations
kubectl get k8sallowedregistries.constraints.gatekeeper.sh \
allow-only-trusted-registries \
-o jsonpath='{range .status.violations[*]}{.namespace}/{.name}: {.message}{"\n"}{end}'Практика: GitOps, тесты в CI, уровни критичности и мониторинг
Храните политики в Git рядом с конфигурацией кластера и синхронизируйте через Argo CD или Flux — с тем же ревью, что и код приложений. В CI гоняйте kyverno test и gator verify до merge. Делите правила по критичности: сразу блокируйте root, privileged и чужие реестры; после аудита добавляйте лимиты ресурсов и обязательные метки; соглашения об именах оставьте в режиме отчётов. На старте хватит пяти–десяти политик — иначе разработчики упрются в трение. Исключайте DaemonSet и системные метки вроде kube-dns из широких правил для подов. Рост числа отказов Kyverno или нарушений Gatekeeper в аудите оповещайте так же, как скачок ошибок. Policy as Code смыкает требования на бумаге с тем, что API-сервер реально пропускает: начните с аудита, версионируйте всё, дорабатывайте каждую неделю.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: cluster-policies
namespace: argocd
spec:
project: platform
source:
repoURL: https://github.com/company/k8s-policies.git
targetRevision: main
path: policies/
destination:
server: https://kubernetes.default.svc
namespace: kyverno
syncPolicy:
automated:
prune: true
selfHeal: true# Kyverno policy tests
kyverno test ./policy-tests/ --detailed-results
# Gatekeeper manifest verification
gator verify ./gatekeeper/policies/ --kube-version 1.29groups:
- name: policy-admission
rules:
- alert: KyvernoPolicyFailures
expr: sum(rate(kyverno_policy_results_total{rule_result="fail"}[5m])) > 5
for: 15m
annotations:
summary: "Sustained Kyverno policy failures in admission"Политики admission дополняют базовый hardening из практического гайда по безопасности Kubernetes в продакшене.
Проверка подписи образов логично стыкуется с контролями из гайда по безопасности цепочки поставок ПО.
