OPA Gatekeeper es un controlador de admisión para Kubernetes basado en Open Policy Agent. Intercepta cada petición a la API del clúster y rechaza los recursos que incumplen las políticas que definas en Rego, además de auditar periódicamente lo que ya está desplegado. En este tutorial instalarás Gatekeeper con Helm, crearás dos políticas (etiquetas obligatorias en namespaces y prohibición de contenedores privilegiados), las probarás primero en modo dryrun y después las pondrás a bloquear.
Requisitos previos
Para seguir esta guía necesitas:
- Un clúster Kubernetes en una versión mantenida (1.30 o superior), por ejemplo un clúster Kubernetes gestionado de CubePath o uno propio con kubeadm o k3s.
kubectlconfigurado con permisos decluster-adminsobre ese clúster.- Helm 3 instalado en tu equipo.
- Conocimientos básicos de Pods, Deployments y namespaces.
Comprueba el acceso antes de empezar:
kubectl version
helm version --short
Paso 1: Instalar Gatekeeper con Helm
Añade el repositorio oficial del chart y actualiza el índice:
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
Instala Gatekeeper en su propio namespace. Los valores por defecto (3 réplicas del webhook y una auditoría cada 60 segundos) sirven para la mayoría de clústeres:
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace
Espera a que los Deployments estén listos:
kubectl -n gatekeeper-system rollout status deployment/gatekeeper-controller-manager
kubectl -n gatekeeper-system rollout status deployment/gatekeeper-audit
Comprueba los pods:
kubectl get pods -n gatekeeper-system
NAME READY STATUS RESTARTS AGE
gatekeeper-audit-6c9d7b8f5d-8kq2x 1/1 Running 0 60s
gatekeeper-controller-manager-7f8b6d9c4b-2xk7p 1/1 Running 0 60s
gatekeeper-controller-manager-7f8b6d9c4b-9hz4m 1/1 Running 0 60s
gatekeeper-controller-manager-7f8b6d9c4b-rt5vn 1/1 Running 0 60s
El controller-manager atiende el webhook de admisión y gatekeeper-audit revisa periódicamente los recursos existentes.
Paso 2: Entender ConstraintTemplate y Constraint
Gatekeeper separa la lógica de la política de su aplicación:
| Objeto | Qué contiene | Ejemplo |
|---|---|---|
ConstraintTemplate | El código Rego y el esquema de parámetros. Crea un nuevo tipo de recurso (CRD). | K8sRequiredLabels |
| Constraint | Una instancia de ese tipo: a qué recursos se aplica, con qué parámetros y qué acción tomar. | "Todo namespace debe llevar la etiqueta owner" |
La acción de cada Constraint se define en enforcementAction:
deny: rechaza la petición (valor por defecto).dryrun: no bloquea nada; las infracciones solo aparecen en la auditoría.warn: acepta la petición pero devuelve un aviso akubectl.
Paso 3: Crear la plantilla de etiquetas obligatorias
Crea un directorio de trabajo para los manifiestos:
mkdir -p ~/gatekeeper && cd ~/gatekeeper
Crea la plantilla:
nano k8srequiredlabels-template.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("faltan las etiquetas obligatorias: %v", [missing])
}
La regla violation compara el conjunto de etiquetas presentes en el objeto (input.review.object) con las que exige la Constraint (input.parameters.labels) y genera un mensaje si falta alguna.
Aplica la plantilla:
kubectl apply -f k8srequiredlabels-template.yaml
Comprueba que Gatekeeper ha compilado el Rego y ha creado el nuevo tipo:
kubectl get constrainttemplate k8srequiredlabels -o jsonpath='{.status.created}{"\n"}'
kubectl get crd k8srequiredlabels.constraints.gatekeeper.sh
true
NAME CREATED AT
k8srequiredlabels.constraints.gatekeeper.sh 2026-09-25T10:12:31Z
Si status.created no es true, revisa kubectl describe constrainttemplate k8srequiredlabels: los errores de sintaxis de Rego aparecen en la sección Status.
Paso 4: Aplicar la política en modo dryrun
Antes de bloquear nada, conviene ver cuántos recursos existentes incumplirían la política. Crea una Constraint que exija la etiqueta owner en todos los namespaces, excepto los del sistema:
nano ns-must-have-owner.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-owner
spec:
enforcementAction: dryrun
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
excludedNamespaces:
- kube-system
- kube-public
- kube-node-lease
- default
- gatekeeper-system
parameters:
labels: ["owner"]
kubectl apply -f ns-must-have-owner.yaml
Espera un minuto a que se ejecute la auditoría y consulta el resultado:
kubectl get k8srequiredlabels
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
ns-must-have-owner dryrun 3
Para ver qué recursos concretos incumplen la política:
kubectl get k8srequiredlabels ns-must-have-owner \
-o jsonpath='{range .status.violations[*]}{.kind}/{.name}: {.message}{"\n"}{end}'
Namespace/monitoring: faltan las etiquetas obligatorias: {"owner"}
Namespace/web: faltan las etiquetas obligatorias: {"owner"}
Namespace/staging: faltan las etiquetas obligatorias: {"owner"}
Corrige los recursos existentes antes de activar el bloqueo, por ejemplo:
kubectl label namespace web owner=equipo-web
Notala auditoría guarda como máximo 20 infracciones por Constraint en
status.violations(valorconstraintViolationsLimitdel chart).TOTAL-VIOLATIONSsí muestra el número real.
Paso 5: Pasar la política a modo deny
Cuando la auditoría esté limpia o las excepciones estén justificadas, cambia la acción a deny:
kubectl patch k8srequiredlabels ns-must-have-owner \
--type merge -p '{"spec":{"enforcementAction":"deny"}}'
Prueba a crear un namespace sin la etiqueta:
kubectl create namespace prueba
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-owner] faltan las etiquetas obligatorias: {"owner"}
Ahora crea el mismo namespace con la etiqueta en un manifiesto:
nano ns-prueba.yaml
apiVersion: v1
kind: Namespace
metadata:
name: prueba
labels:
owner: equipo-devops
kubectl apply -f ns-prueba.yaml
namespace/prueba created
Guarda los manifiestos en Git y cambia enforcementAction en el YAML para que el repositorio refleje el estado real del clúster.
Paso 6: Prohibir contenedores privilegiados
Un contenedor con privileged: true tiene acceso prácticamente total al nodo. Esta segunda plantilla revisa contenedores normales e initContainers:
nano k8spspprivileged.yaml
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8spspprivileged
spec:
crd:
spec:
names:
kind: K8sPSPPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8spspprivileged
violation[{"msg": msg}] {
c := input_containers[_]
c.securityContext.privileged
msg := sprintf("contenedor privilegiado no permitido: %v", [c.name])
}
input_containers[c] {
c := input.review.object.spec.containers[_]
}
input_containers[c] {
c := input.review.object.spec.initContainers[_]
}
Crea la Constraint en otro archivo, porque la plantilla debe existir antes de que Kubernetes acepte el nuevo tipo:
nano no-privileged.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivileged
metadata:
name: no-privileged-containers
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
excludedNamespaces:
- kube-system
- gatekeeper-system
kubectl apply -f k8spspprivileged.yaml
kubectl get constrainttemplate k8spspprivileged -o jsonpath='{.status.created}{"\n"}'
kubectl apply -f no-privileged.yaml
Prueba la política con un pod privilegiado en el namespace que creaste antes:
nano pod-privilegiado.yaml
apiVersion: v1
kind: Pod
metadata:
name: privilegiado
namespace: prueba
spec:
containers:
- name: app
image: nginx:1.27
securityContext:
privileged: true
kubectl apply -f pod-privilegiado.yaml
Error from server (Forbidden): error when creating "pod-privilegiado.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [no-privileged-containers] contenedor privilegiado no permitido: app
Importantela Constraint se aplica a objetos
Pod. Si creas un Deployment con un contenedor privilegiado, el Deployment se acepta pero su ReplicaSet no podrá crear los pods. Lo verás conkubectl describe replicaset -n <namespace>. Para rechazarlo antes, añadeapps/Deploymentamatch.kindsy adapta el Rego para leerspec.template.spec.
Elimina los recursos de prueba:
kubectl delete namespace prueba
Paso 7: Reutilizar la biblioteca oficial de políticas
No hace falta escribir cada política desde cero. El proyecto mantiene Gatekeeper Library, con plantillas probadas para los casos más comunes: registros de imágenes permitidos (K8sAllowedRepos), límites de recursos obligatorios (K8sContainerLimits), prohibición de la etiqueta latest, equivalentes de Pod Security Standards, etc. Cada política incluye su template.yaml, ejemplos de Constraint y casos de prueba.
El flujo recomendado es el mismo que has seguido: aplica la plantilla, crea la Constraint en dryrun, revisa la auditoría y solo entonces cambia a deny.
Solución de problemas
La Constraint no aparece o da no matches for kind. La plantilla aún no se ha procesado o su Rego tiene un error. Revisa:
kubectl describe constrainttemplate k8srequiredlabels
Las peticiones no se bloquean. Comprueba que el webhook está registrado y que el namespace no está excluido:
kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration
kubectl get namespace <namespace> --show-labels
Los namespaces con la etiqueta admission.gatekeeper.sh/ignore quedan fuera del webhook.
Errores en los logs.
kubectl logs -n gatekeeper-system deployment/gatekeeper-controller-manager
kubectl logs -n gatekeeper-system deployment/gatekeeper-audit
El clúster se queda sin poder crear recursos si Gatekeeper falla. Por defecto el webhook de validación usa failurePolicy: Ignore, así que una caída de Gatekeeper no bloquea el clúster, pero durante ese tiempo tampoco se aplican las políticas. La auditoría detectará después lo que haya entrado.
Conclusión
Has instalado OPA Gatekeeper con Helm, has escrito dos ConstraintTemplates en Rego y has seguido el ciclo seguro para activarlas: dryrun, revisión de la auditoría y paso a deny. Como siguientes pasos, guarda plantillas y Constraints en Git y despliégalas con Argo CD o Flux, incorpora políticas de Gatekeeper Library como K8sAllowedRepos, y compara este enfoque con Kyverno si tu equipo prefiere escribir políticas en YAML en lugar de Rego.
