Aprende › Cloud, APIs, forense y más

OPA y Gatekeeper: policy as code para Kubernetes

· 11 min de lectura

Aprende a usar Open Policy Agent (OPA) y Gatekeeper como admission controller en Kubernetes para imponer políticas de seguridad antes de que los recursos lleguen al clúster.

En Kubernetes, cualquier persona con acceso al API Server puede crear pods, deployments y services. Sin un mecanismo que valide lo que se despliega, un contenedor privilegiado, una imagen sin tag fijo o un volumen hostPath pueden convertirse en la puerta de entrada de un atacante hacia el nodo subyacente. Open Policy Agent (OPA) y su componente para Kubernetes, Gatekeeper, resuelven ese problema: interceptan cada petición al API Server, la evalúan contra políticas escritas en código y la rechazan si no cumple los requisitos de seguridad.

Es la segunda lección de la Tanda 9. Aquí verás cómo funciona el flujo de admisión de Kubernetes, qué es OPA y su lenguaje Rego, cómo Gatekeeper lo integra como validating admission webhook, las políticas más comunes para endurecer un clúster y cómo testear y desplegar esas políticas en un pipeline CI/CD.

Practica solo en clústeres propios o en entornos de laboratorio autorizados. Modificar políticas de admisión en clústeres ajenos sin permiso es acceso no autorizado y puede interrumpir servicios en producción.

Qué es OPA#

Open Policy Agent es un motor de políticas de propósito general creado por Styra y graduado en la CNCF en 2021. Su idea central es sencilla: desacoplar la lógica de decisión (permitir o denegar) de la aplicación que la consume. OPA recibe datos estructurados (JSON), los evalúa contra políticas escritas en un lenguaje declarativo llamado Rego y devuelve una decisión.

Gatekeeper admission flow: API request → webhook → OPA evaluate → allow/deny
Gatekeeper admission flow: API request → webhook → OPA evaluate → allow/deny

OPA no es exclusivo de Kubernetes: se usa en API gateways (Envoy, Kong), en pipelines CI/CD (conftest), en Terraform (conftest sobre el plan JSON) y en microservicios para autorización fine-grained. Pero su caso de uso más conocido y maduro es como admission controller en Kubernetes, donde se despliega a través de Gatekeeper.

Rego en dos minutos#

Rego es un lenguaje declarativo inspirado en Datalog. No tiene bucles explícitos ni variables mutables: defines reglas que son verdaderas cuando se cumplen sus condiciones. Un ejemplo mínimo:

package kubernetes.admission

deny[msg] {
  input.request.object.spec.containers[_].securityContext.privileged == true
  msg := "No se permiten contenedores privilegiados"
}

La regla deny genera un mensaje cada vez que encuentra un contenedor con privileged: true en la petición de admisión. Si deny no produce ningún mensaje, la petición se permite. Si produce uno o más, se rechaza y los mensajes se devuelven al usuario.

Conceptos clave de Rego:

  • Unificación: x := input.request.object.metadata.name asigna y compara a la vez.
  • Iteración implícita: input.request.object.spec.containers[_] itera sobre todos los contenedores sin un for.
  • Conjunción: cada línea dentro de una regla es un AND. Si cualquiera falla, la regla no se activa.
  • Parciales: una regla puede generar múltiples valores (un set o un objeto).

Por qué ocurre: la causa raíz#

Kubernetes, por diseño, no impone restricciones de seguridad estrictas por defecto. Un manifiesto como este es perfectamente válido:

apiVersion: v1
kind: Pod
metadata:
  name: dangerous-pod
spec:
  hostNetwork: true
  hostPID: true
  containers:
    - name: pwn
      image: ubuntu:latest
      securityContext:
        privileged: true
      volumeMounts:
        - name: host-root
          mountPath: /host
  volumes:
    - name: host-root
      hostPath:
        path: /

Este pod monta el sistema de archivos raíz del nodo, tiene acceso a la red y PIDs del host, corre como privilegiado y usa una imagen mutable (latest). Si un atacante compromete la aplicación dentro de este pod, tiene acceso completo al nodo, lo que corresponde a la técnica MITRE ATT&CK T1611 — Escape to Host.

Sin un admission controller que lo bloquee, Kubernetes lo acepta sin protestar. Las Pod Security Standards (PSS) y los Pod Security Admission (PSA) controllers nativos de Kubernetes cubren casos básicos, pero carecen de la flexibilidad para políticas personalizadas como exigir labels específicos, restringir registries de imágenes o imponer convenciones de naming.

Tipos de admission controllers#

Kubernetes procesa cada petición al API Server a través de una cadena de admission controllers. Los dos tipos relevantes aquí:

TipoFunciónEjemplo
Mutating admission webhookModifica el recurso antes de persistirlo (inyectar sidecars, añadir labels)Istio sidecar injector
Validating admission webhookEvalúa el recurso y lo acepta o rechaza sin modificarloGatekeeper, Kyverno

Gatekeeper actúa como validating admission webhook: recibe la petición, la pasa a OPA, y si OPA devuelve una violación, el API Server rechaza la petición con el mensaje de error correspondiente. El usuario ve un error claro al hacer kubectl apply.

Dónde aparece Gatekeeper#

Gatekeeper se instala en el clúster como un Deployment en el namespace gatekeeper-system. Sus componentes principales:

  • gatekeeper-controller-manager: el webhook server que intercepta peticiones.
  • gatekeeper-audit: un proceso que escanea recursos ya existentes y reporta violaciones (no los borra, solo informa).
  • CRDs: ConstraintTemplate (define la política en Rego) y Constraint (aplica la política con parámetros).

El flujo completo cuando haces kubectl apply -f pod.yaml:

  1. kubectl envía la petición al API Server.
  2. El API Server pasa por autenticación y autorización (RBAC).
  3. Mutating webhooks modifican el recurso si aplica.
  4. Validating webhooks evalúan el recurso. Gatekeeper lo recibe aquí.
  5. Gatekeeper evalúa todas las Constraints activas contra el recurso.
  6. Si alguna falla, devuelve error y el API Server rechaza la petición.
  7. Si todas pasan, el recurso se persiste en etcd.

Cómo se detecta la falta de políticas#

Detectar que un clúster carece de políticas de admisión adecuadas es el primer paso de una auditoría:

PruebaComando o herramientaQué indica
Listar admission webhookskubectl get validatingwebhookconfigurationsSi no hay ninguno, no hay validación externa
Comprobar Gatekeeperkubectl get pods -n gatekeeper-systemSi no existe el namespace, no está instalado
Buscar constraints activaskubectl get constraintsSi no hay constraints, Gatekeeper no bloquea nada
Auditar violacioneskubectl get constraints -o json y revisar .status.violationsMuestra recursos existentes que violan políticas
Crear pod privilegiado de pruebakubectl run test --image=alpine --overrides='...' con privileged:trueSi se crea, la política no existe o está en audit mode
Escaneo externokubeaudit, kube-bench, PolarisDetectan misconfigs comunes incluyendo falta de policies

Explotación paso a paso en un laboratorio#

El laboratorio cloudship de whoami-labs simula un entorno cloud con contenedores mal configurados. Uno de los vectores es el escape de contenedor a través de configuraciones privilegiadas, exactamente lo que Gatekeeper previene.

Paso 1: reconocimiento del clúster#

Desde dentro de un pod comprometido, verifica el acceso al API Server:

# Comprobar si hay un service account token montado
ls -la /var/run/secrets/kubernetes.io/serviceaccount/
cat /var/run/secrets/kubernetes.io/serviceaccount/token

Paso 2: verificar admission controllers#

# Desde fuera, con kubectl configurado
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations

Si la salida está vacía o no incluye Gatekeeper, el clúster no tiene policy enforcement.

Paso 3: intentar desplegar un pod privilegiado#

kubectl run escape-test --image=alpine \
  --overrides='{
    "spec": {
      "containers": [{
        "name": "escape-test",
        "image": "alpine",
        "securityContext": {"privileged": true},
        "command": ["sleep", "3600"]
      }]
    }
  }'

Si el pod se crea sin error, el clúster es vulnerable.

Paso 4: escape al host (si privileged)#

kubectl exec -it escape-test -- sh
# Dentro del pod privilegiado
nsenter --target 1 --mount --uts --ipc --net --pid -- bash
# Ahora estás en el namespace del host
whoami  # root del nodo

El comando nsenter cambia al namespace del PID 1 (el init del host), dando acceso completo al nodo.

Paso 5: comprobar qué más falta#

# Listar pods sin resource limits
kubectl get pods -A -o json | jq '.items[] | select(.spec.containers[].resources.limits == null) | .metadata.name'

# Listar pods con hostPath
kubectl get pods -A -o json | jq '.items[] | select(.spec.volumes[]?.hostPath != null) | .metadata.name'

Qué puede lograr un atacante#

Sin políticas de admisión, un atacante con acceso al API Server (vía RBAC débil, service account token filtrado o credential leak) puede:

AcciónImpactoTécnica MITRE
Desplegar pod privilegiadoEscape al nodo host, acceso a todos los pods del nodoT1611 Escape to Host
Montar hostPath /Leer/escribir cualquier archivo del nodo (keys SSH, kubeconfig, secrets)T1006 Direct Volume Access
Usar hostNetworkInterceptar tráfico de red del nodo, acceder a servicios internosT1040 Network Sniffing
Imagen sin pin (latest)Supply chain attack: la imagen puede cambiar sin que nadie lo noteT1610 Deploy Container
Sin resource limitsCryptominer consume todos los recursos del nodo (DoS)T1496 Resource Hijacking
Sin labels obligatoriosRecursos huérfanos sin dueño, imposible auditar quién desplegó quéT1036 Masquerading

Cómo se corrige#

La corrección es instalar Gatekeeper y definir las políticas mínimas de seguridad.

Políticas comunes: privileged, latest tag, resources, hostPath
Políticas comunes: privileged, latest tag, resources, hostPath

Instalar Gatekeeper#

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.16.0/deploy/gatekeeper.yaml

Crear un ConstraintTemplate#

El ConstraintTemplate define la lógica en Rego. Ejemplo para bloquear contenedores privilegiados:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8spspprivilegedcontainer
spec:
  crd:
    spec:
      names:
        kind: K8sPSPPrivilegedContainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8spspprivilegedcontainer

        violation[{"msg": msg}] {
          c := input.review.object.spec.containers[_]
          c.securityContext.privileged == true
          msg := sprintf("Contenedor '%v' no puede ser privilegiado", [c.name])
        }

Crear la Constraint#

La Constraint aplica el template con parámetros y scope:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: deny-privileged-containers
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]

Ahora, cualquier kubectl apply con un pod privilegiado fuera de kube-system recibirá un error:

Error from server: admission webhook "validation.gatekeeper.sh" denied the request:
[deny-privileged-containers] Contenedor 'pwn' no puede ser privilegiado

Audit mode vs enforce#

Gatekeeper permite dos modos de enforcement por constraint:

  • enforcementAction: deny — rechaza la petición (producción).
  • enforcementAction: dryrun — registra la violación pero permite el recurso (ideal para rollout gradual).

La estrategia recomendada: desplegar todas las policies nuevas en dryrun, revisar .status.violations durante una semana, corregir los recursos que violen, y luego pasar a deny.

Políticas esenciales del Gatekeeper Library#

La Gatekeeper Policy Library (repositorio oficial en GitHub) incluye templates listos para usar:

# Clonar la biblioteca
git clone https://github.com/open-policy-agent/gatekeeper-library.git

# Aplicar templates comunes
kubectl apply -f gatekeeper-library/library/pod-security-policy/privileged-containers/
kubectl apply -f gatekeeper-library/library/general/disallowedtags/
kubectl apply -f gatekeeper-library/library/general/containerresources/

Conftest para CI/CD#

Conftest usa OPA para validar manifiestos antes del deploy, en el pipeline:

# Instalar conftest
brew install conftest

# Crear política en policy/deny-privileged.rego
# Validar manifiestos
conftest test deployment.yaml --policy policy/

Esto permite detectar violaciones en el PR, antes de que lleguen al clúster.

Errores comunes al defenderse#

  • Instalar Gatekeeper pero no crear ninguna Constraint (el webhook existe pero no bloquea nada).
  • Excluir demasiados namespaces de las policies (excludedNamespaces demasiado amplio).
  • No usar dryrun antes de deny y romper deployments legítimos en producción.
  • Escribir Rego que solo valida containers pero olvida initContainers y ephemeralContainers.
  • No sincronizar datos con Gatekeeper (Config resource) cuando las policies necesitan consultar otros recursos del clúster (como verificar que un namespace existe).
  • Olvidar que Gatekeeper no modifica recursos: si necesitas inyectar defaults (como resource limits), necesitas un mutating webhook adicional o Kyverno.
  • Confundir OPA standalone con Gatekeeper: OPA es el motor, Gatekeeper es la integración con Kubernetes. Instalar OPA solo no crea webhooks.

Preguntas frecuentes#

Gatekeeper vs Kyverno: cuál elegir#

Gatekeeper usa Rego, un lenguaje propio que requiere aprendizaje. Es muy potente para políticas complejas. Kyverno define políticas directamente en YAML, más accesible para equipos que ya conocen Kubernetes. Kyverno además puede mutar recursos (añadir labels, inyectar defaults), algo que Gatekeeper no hace nativamente. Para equipos pequeños sin experiencia en Rego, Kyverno es más rápido de adoptar. Para organizaciones grandes con políticas complejas cross-resource, Gatekeeper ofrece más flexibilidad.

Las Pod Security Standards nativas no son suficientes#

Kubernetes incluye Pod Security Admission (PSA) desde la v1.25 como GA. PSA aplica tres perfiles (Privileged, Baseline, Restricted) a nivel de namespace. Es útil como baseline mínimo, pero no permite políticas personalizadas: no puedes exigir labels específicos, restringir registries o validar annotations. Gatekeeper y Kyverno complementan PSA para organizaciones con requisitos propios.

Cómo testear políticas Rego antes de desplegar#

OPA incluye un framework de testing integrado:

# Archivo: policy_test.rego
package kubernetes.admission

test_deny_privileged {
  deny["No se permiten contenedores privilegiados"] with input as {
    "request": {
      "object": {
        "spec": {
          "containers": [{"securityContext": {"privileged": true}}]
        }
      }
    }
  }
}

test_allow_non_privileged {
  count(deny) == 0 with input as {
    "request": {
      "object": {
        "spec": {
          "containers": [{"securityContext": {"privileged": false}}]
        }
      }
    }
  }
}
opa test . -v
# PASS: 2/2

Qué pasa con los recursos que ya existen cuando activo una policy#

Gatekeeper no elimina ni modifica recursos existentes. Su componente audit escanea periódicamente (por defecto cada 60 segundos) y reporta violaciones en el campo .status.violations de cada Constraint. Puedes consultar las violaciones con kubectl get constraints -o yaml y corregir manualmente los recursos antes de pasar de dryrun a deny.

OPA se usa fuera de Kubernetes#

Sí. OPA es un motor de políticas de propósito general. Casos de uso comunes fuera de Kubernetes: autorización en API gateways (Envoy External Authorization), validación de planes Terraform con conftest, autorización fine-grained en microservicios (reemplazando RBAC hardcoded) y validación de configuraciones de CI/CD.

Resumen#

  • OPA es un motor de políticas de propósito general que usa Rego como lenguaje declarativo.
  • Gatekeeper integra OPA en Kubernetes como validating admission webhook.
  • Sin policies de admisión, un clúster acepta pods privilegiados, imágenes mutables y volúmenes hostPath que facilitan el escape al nodo host.
  • Las políticas se definen en dos CRDs: ConstraintTemplate (lógica Rego) y Constraint (parámetros y scope).
  • Usar dryrun antes de deny evita romper deployments legítimos.
  • Conftest permite validar manifiestos en el CI/CD antes de que lleguen al clúster.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría