Instalación de Open Policy Agent Gatekeeper para Kubernetes

Open Policy Agent (OPA) Gatekeeper es un controlador de admisión para Kubernetes que permite definir y aplicar políticas de seguridad y gobernanza de forma declarativa usando el lenguaje Rego, garantizando que todos los recursos que se crean en el clúster cumplan con las políticas definidas antes de ser aceptados. A diferencia de PodSecurityPolicy (deprecado), Gatekeeper es extensible y permite crear políticas personalizadas para cualquier tipo de recurso Kubernetes. Esta guía cubre la instalación y configuración de OPA Gatekeeper.

Requisitos Previos

  • Clúster Kubernetes 1.25+
  • kubectl configurado con acceso de administrador al clúster
  • Helm 3 instalado
  • Comprensión básica de recursos Kubernetes (Pods, Deployments, etc.)

Instalación de Gatekeeper

# Método 1: Usando Helm (recomendado para producción)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update

# Instalar Gatekeeper en el namespace dedicado
helm install gatekeeper gatekeeper/gatekeeper \
  --namespace gatekeeper-system \
  --create-namespace \
  --set replicas=2 \
  --set controllerManager.resources.limits.memory=512Mi \
  --set audit.resources.limits.memory=512Mi \
  --set auditInterval=30 \
  --set constraintViolationsLimit=20

# Método 2: Instalación directa con kubectl
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

# Verificar que los pods están en ejecución
kubectl get pods -n gatekeeper-system
kubectl rollout status deployment gatekeeper-controller-manager -n gatekeeper-system

# Verificar los CRDs instalados por Gatekeeper
kubectl get crd | grep gatekeeper

Constraint Templates

Un ConstraintTemplate define una política reutilizable en Rego. Actúa como una plantilla de la que se pueden crear múltiples instancias con diferentes configuraciones:

# Ejemplo: política que requiere límites de recursos en todos los contenedores
cat > /tmp/required-resource-limits.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredresourcelimits
  annotations:
    description: "Requiere que todos los contenedores definan límites de CPU y memoria"
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredResourceLimits
      validation:
        openAPIV3Schema:
          properties:
            containers:
              type: array
              items:
                type: string
              description: "Lista de contenedores que se pueden excluir de la política"

  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredresourcelimits

        violation[{"msg": msg}] {
            # Obtener todos los contenedores del pod (incluye initContainers)
            container := input_containers[_]
            
            # Verificar que no está en la lista de exclusiones
            not container.name in input.parameters.containers
            
            # Comprobar que el contenedor tiene límites definidos
            not container.resources.limits
            
            msg := sprintf(
                "El contenedor '%v' no tiene límites de recursos definidos. Añadir resources.limits.cpu y resources.limits.memory",
                [container.name]
            )
        }

        violation[{"msg": msg}] {
            container := input_containers[_]
            not container.name in input.parameters.containers
            not container.resources.limits.memory
            msg := sprintf(
                "El contenedor '%v' no tiene límite de memoria (resources.limits.memory)",
                [container.name]
            )
        }

        violation[{"msg": msg}] {
            container := input_containers[_]
            not container.name in input.parameters.containers
            not container.resources.limits.cpu
            msg := sprintf(
                "El contenedor '%v' no tiene límite de CPU (resources.limits.cpu)",
                [container.name]
            )
        }

        # Función auxiliar: obtener todos los contenedores del pod
        input_containers[c] {
            c := input.review.object.spec.containers[_]
        }
        input_containers[c] {
            c := input.review.object.spec.initContainers[_]
        }
EOF

kubectl apply -f /tmp/required-resource-limits.yaml

# Verificar que el template se creó correctamente
kubectl get constrainttemplate k8srequiredresourcelimits

Constraints (Instancias de Política)

Una vez creado el template, se crean Constraints para aplicar la política en namespaces específicos:

# Aplicar la política de límites de recursos a todos los namespaces de producción
cat > /tmp/constraint-resource-limits-prod.yaml << 'EOF'
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredResourceLimits
metadata:
  name: require-resource-limits-production
spec:
  # Modo de aplicación: Deny (bloquea) o Warn (solo avisa)
  enforcementAction: deny
  
  # Ámbito: a qué recursos se aplica esta constraint
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    # Aplicar solo en namespaces de producción
    namespaceSelector:
      matchLabels:
        environment: production
    # Excluir namespaces del sistema
    excludedNamespaces:
      - kube-system
      - gatekeeper-system
      - monitoring
  
  # Parámetros de la política (definidos en el ConstraintTemplate)
  parameters:
    containers:
      - "istio-proxy"    # Excluir el sidecar de Istio
      - "vault-agent"    # Excluir el agente de Vault
EOF

kubectl apply -f /tmp/constraint-resource-limits-prod.yaml

# Verificar que la constraint se creó
kubectl get k8srequiredresourcelimits
kubectl describe k8srequiredresourcelimits require-resource-limits-production

Políticas de Seguridad Comunes

# 1. Política: prohibir contenedores privilegiados
cat > /tmp/no-privileged.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoprivilegedcontainer
spec:
  crd:
    spec:
      names:
        kind: K8sNoPrivilegedContainer
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8snoprivilegedcontainer
        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[_] }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoPrivilegedContainer
metadata:
  name: no-privileged-containers
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
EOF
kubectl apply -f /tmp/no-privileged.yaml

# 2. Política: requerir etiquetas obligatorias en todos los recursos
cat > /tmp/required-labels.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels
        violation[{"msg": msg}] {
            required_label := input.parameters.labels[_]
            not input.review.object.metadata.labels[required_label]
            msg := sprintf("Etiqueta obligatoria faltante: %v", [required_label])
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-and-app-labels
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: ["apps"]
        kinds: ["Deployment", "StatefulSet", "DaemonSet"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    labels:
      - "app.kubernetes.io/name"
      - "app.kubernetes.io/team"
      - "app.kubernetes.io/version"
EOF
kubectl apply -f /tmp/required-labels.yaml

# 3. Política: solo imágenes de registros autorizados
cat > /tmp/allowed-registries.yaml << 'EOF'
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8sallowedregistries
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRegistries
      validation:
        openAPIV3Schema:
          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 any_registry_match(container.image)
            msg := sprintf("Imagen '%v' no proviene de un registro autorizado", [container.image])
        }
        any_registry_match(image) {
            registry := input.parameters.registries[_]
            startswith(image, registry)
        }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
  name: only-approved-registries
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    excludedNamespaces: ["kube-system", "gatekeeper-system"]
  parameters:
    registries:
      - "registry.empresa.com/"
      - "docker.io/empresa-oficial/"
      - "ghcr.io/empresa/"
EOF
kubectl apply -f /tmp/allowed-registries.yaml

Modo de Auditoría

El modo de auditoría permite evaluar políticas sin bloquear recursos existentes:

# Cambiar una constraint a modo "warn" para empezar sin bloquear
kubectl patch k8srequiredresourcelimits require-resource-limits-production \
  --type merge \
  -p '{"spec":{"enforcementAction":"warn"}}'

# Ver violaciones de políticas detectadas en el clúster
kubectl get k8srequiredresourcelimits -o json | \
  python3 -m json.tool | grep -A5 "violations"

# Ver todas las violaciones de todas las políticas
kubectl get constraints -o json | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
for item in data.get('items', []):
    name = item['metadata']['name']
    violations = item.get('status', {}).get('violations', [])
    if violations:
        print(f'Política: {name} - {len(violations)} violaciones')
        for v in violations[:3]:
            print(f'  - {v[\"kind\"]}/{v[\"name\"]} en {v[\"namespace\"]}: {v[\"message\"]}')
"

# Activar la aplicación estricta después de revisar las violaciones
kubectl patch k8srequiredresourcelimits require-resource-limits-production \
  --type merge \
  -p '{"spec":{"enforcementAction":"deny"}}'

Verificación de Imágenes

# Probar que una política rechaza recursos incorrectos
cat > /tmp/test-no-limits.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: test-sin-limites
  namespace: produccion
  labels:
    app.kubernetes.io/name: test
    app.kubernetes.io/team: devops
    app.kubernetes.io/version: "1.0"
spec:
  containers:
    - name: app
      image: registry.empresa.com/nginx:latest
      # Sin resources.limits - debe ser rechazado
EOF

kubectl apply -f /tmp/test-no-limits.yaml
# Debe mostrar: Error from server ([require-resource-limits-production] El contenedor 'app' no tiene límites...)

# Recurso correcto que debe ser aceptado
cat > /tmp/test-con-limites.yaml << 'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: test-con-limites
  namespace: produccion
  labels:
    app.kubernetes.io/name: test
    app.kubernetes.io/team: devops
    app.kubernetes.io/version: "1.0"
spec:
  containers:
    - name: app
      image: registry.empresa.com/nginx:latest
      resources:
        requests:
          cpu: "100m"
          memory: "128Mi"
        limits:
          cpu: "500m"
          memory: "256Mi"
EOF

kubectl apply -f /tmp/test-con-limites.yaml  # Debe funcionar correctamente

Solución de Problemas

# Ver los logs de Gatekeeper
kubectl logs -n gatekeeper-system -l control-plane=controller-manager -f

# Estado de un ConstraintTemplate (ver si tuvo errores de compilación Rego)
kubectl describe constrainttemplate k8srequiredresourcelimits | grep -A10 "Status"

# Estado de una Constraint (ver violaciones detectadas)
kubectl describe k8srequiredresourcelimits require-resource-limits-production

# Probar una política Rego sin desplegarla (usando opa eval)
docker run --rm -v $(pwd):/data openpolicyagent/opa:latest eval \
  --data /data/policy.rego \
  --input /data/test-input.json \
  "data.k8srequiredresourcelimits.violation"

# Gatekeeper en modo seco (solo logging, sin bloquear)
# En la instalación de Helm, usar:
# --set validatingWebhookFailurePolicy=Ignore

# Ver webhooks de admission configurados por Gatekeeper
kubectl get validatingwebhookconfigurations | grep gatekeeper
kubectl describe validatingwebhookconfigurations gatekeeper-validating-webhook-configuration

Conclusión

OPA Gatekeeper convierte las políticas de seguridad de Kubernetes en código versionable y auditable, eliminando la dependencia de convenciones manuales y procesos de revisión informales para garantizar el cumplimiento normativo. El uso del modo de auditoría antes de aplicar políticas en modo deny permite una adopción gradual sin interrupciones, y la biblioteca de políticas de la comunidad (Gatekeeper Policy Library) ofrece un punto de partida sólido para las políticas de seguridad más comunes en Kubernetes.