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.
  • kubectl configurado con permisos de cluster-admin sobre 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:

ObjetoQué contieneEjemplo
ConstraintTemplateEl código Rego y el esquema de parámetros. Crea un nuevo tipo de recurso (CRD).K8sRequiredLabels
ConstraintUna 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 a kubectl.

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

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

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.