Motor de Políticas Kyverno para Kubernetes

Kyverno es un motor de políticas nativo de Kubernetes que permite definir, validar, mutar y generar recursos del clúster usando YAML estándar sin necesidad de aprender un lenguaje de programación adicional como Rego. Su integración nativa como controlador de admisión lo convierte en la solución más accesible para equipos que necesitan gobernanza de Kubernetes sin la curva de aprendizaje de OPA Gatekeeper. Esta guía cubre la instalación de Kyverno, tipos de políticas y casos de uso comunes.

Requisitos Previos

  • Clúster Kubernetes 1.26+
  • kubectl configurado con acceso de administrador
  • Helm 3 instalado
  • Mínimo 2 GB de RAM disponible para Kyverno

Instalación de Kyverno

# Instalar Kyverno con Helm (recomendado)
helm repo add kyverno https://kyverno.github.io/kyverno/
helm repo update

# Instalación en modo de alta disponibilidad (3 réplicas)
helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace \
  --set replicaCount=3 \
  --set admissionController.replicas=3 \
  --set backgroundController.replicas=2 \
  --set cleanupController.replicas=1 \
  --set reportsController.replicas=1

# Instalación simple para desarrollo/pruebas (1 réplica)
helm install kyverno kyverno/kyverno \
  --namespace kyverno \
  --create-namespace \
  --set replicaCount=1

# Verificar que los pods están en ejecución
kubectl get pods -n kyverno
kubectl rollout status deployment kyverno-admission-controller -n kyverno

# Instalar también las políticas predefinidas de la comunidad
helm install kyverno-policies kyverno/kyverno-policies \
  --namespace kyverno \
  --set podSecurityStandard=restricted  # baseline, restricted o privileged

# Verificar las políticas instaladas
kubectl get clusterpolicies

Políticas de Validación

Las políticas de validación rechazan recursos que no cumplen las reglas:

# Política: requerir límites de recursos en todos los contenedores
cat > /tmp/require-limits.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-resource-limits
  annotations:
    policies.kyverno.io/title: Límites de recursos obligatorios
    policies.kyverno.io/category: Best Practices
    policies.kyverno.io/description: >-
      Requiere que todos los contenedores definan limits de CPU y memoria
      para prevenir el consumo descontrolado de recursos del nodo.
spec:
  # Deny: bloquear recursos que no cumplen / Audit: solo reportar
  validationFailureAction: Enforce
  
  # Evaluar también los recursos existentes (no solo nuevos)
  background: true
  
  rules:
    - name: validar-limites-cpu-memoria
      match:
        any:
          - resources:
              kinds: [Pod]
              namespaceSelector:
                matchLabels:
                  enforce-limits: "true"
      
      # Excluir namespaces del sistema
      exclude:
        any:
          - resources:
              namespaces:
                - kube-system
                - kyverno
                - monitoring
      
      validate:
        message: >-
          El contenedor '{{ request.object.spec.containers[0].name }}'
          debe definir resources.limits.cpu y resources.limits.memory
        
        # El patrón define la estructura que DEBE tener el recurso
        foreach:
          - list: "request.object.spec.containers"
            deny:
              conditions:
                any:
                  - key: "{{ element.resources.limits | length(@) }}"
                    operator: Equals
                    value: 0
                  - key: "{{ element.resources.limits.memory }}"
                    operator: Equals
                    value: ""
                  - key: "{{ element.resources.limits.cpu }}"
                    operator: Equals
                    value: ""
EOF

kubectl apply -f /tmp/require-limits.yaml

# Política: prohibir el uso de la imagen 'latest'
cat > /tmp/no-latest-tag.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: no-latest-image-tag
  annotations:
    policies.kyverno.io/title: Prohibir tag 'latest'
    policies.kyverno.io/description: >-
      Prohibe el uso del tag 'latest' en imágenes de contenedor para
      garantizar versiones reproducibles y trazabilidad en el historial.
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: no-imagen-latest
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, kyverno]
      validate:
        message: "El tag ':latest' está prohibido. Usar un tag de versión específico como 'v1.2.3'"
        foreach:
          - list: "request.object.spec.containers"
            pattern:
              image: "!*:latest"
          - list: "request.object.spec.initContainers"
            pattern:
              image: "!*:latest"
EOF

kubectl apply -f /tmp/no-latest-tag.yaml

Políticas de Mutación

Las políticas de mutación modifican los recursos automáticamente antes de aceptarlos:

# Política: añadir automáticamente etiquetas de equipo a todos los pods
cat > /tmp/add-default-labels.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-labels
  annotations:
    policies.kyverno.io/title: Añadir etiquetas por defecto
    policies.kyverno.io/description: >-
      Añade automáticamente etiquetas de gestión a todos los pods
      para facilitar el inventario, monitorización y facturación.
spec:
  rules:
    - name: add-labels
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, kyverno]
      mutate:
        patchStrategicMerge:
          metadata:
            labels:
              # Mantener etiquetas existentes y añadir las que falten
              +(managed-by): kyverno
              +(environment): "{{ request.object.metadata.namespace }}"
EOF

kubectl apply -f /tmp/add-default-labels.yaml

# Política: establecer securityContext por defecto en todos los pods
cat > /tmp/default-security-context.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: add-default-security-context
  annotations:
    policies.kyverno.io/title: Security context por defecto
spec:
  rules:
    - name: add-security-context
      match:
        any:
          - resources:
              kinds: [Pod]
      exclude:
        any:
          - resources:
              namespaces: [kube-system, kyverno]
      mutate:
        patchStrategicMerge:
          spec:
            securityContext:
              # Solo añadir si no está definido (operador +)
              +(runAsNonRoot): true
              +(runAsUser): 1000
              +(fsGroup): 1000
              +(seccompProfile):
                type: RuntimeDefault
            containers:
              - (name): "*"
                securityContext:
                  +(allowPrivilegeEscalation): false
                  +(readOnlyRootFilesystem): true
                  +(capabilities):
                    drop: ["ALL"]
EOF

kubectl apply -f /tmp/default-security-context.yaml

Políticas de Generación

Las políticas de generación crean recursos automáticamente cuando se crean otros recursos:

# Política: crear automáticamente un NetworkPolicy por defecto
# en cada namespace nuevo que deniegue todo el tráfico
cat > /tmp/generate-networkpolicy.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: generate-default-network-policy
  annotations:
    policies.kyverno.io/title: Generar NetworkPolicy por defecto
    policies.kyverno.io/description: >-
      Crea automáticamente una NetworkPolicy de denegación por defecto
      en cada nuevo namespace de aplicación, requiriendo políticas explícitas
      para permitir el tráfico.
spec:
  rules:
    - name: crear-networkpolicy-default-deny
      match:
        any:
          - resources:
              kinds: [Namespace]
      exclude:
        any:
          - resources:
              selector:
                matchLabels:
                  skip-network-policy: "true"
      
      generate:
        # Tipo de recurso a generar
        apiVersion: networking.k8s.io/v1
        kind: NetworkPolicy
        name: default-deny-all
        # Namespace donde crear el recurso (el recién creado)
        namespace: "{{ request.object.metadata.name }}"
        
        # Si el namespace se elimina, eliminar también la política
        synchronize: true
        
        data:
          spec:
            # Seleccionar todos los pods del namespace
            podSelector: {}
            policyTypes:
              - Ingress
              - Egress
            # Sin reglas = denegar todo el tráfico entrante y saliente
EOF

kubectl apply -f /tmp/generate-networkpolicy.yaml

# Crear un namespace de prueba y verificar que se generó la NetworkPolicy
kubectl create namespace prueba-kyverno
kubectl get networkpolicies -n prueba-kyverno
# Debe mostrar: default-deny-all

Verificación de Imágenes

# Política: verificar que las imágenes están firmadas con Cosign
cat > /tmp/verify-signatures.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-signed-images
  annotations:
    policies.kyverno.io/title: Imágenes firmadas obligatorias
    policies.kyverno.io/description: >-
      Verifica que las imágenes de los registros internos están firmadas
      con la clave Cosign de la empresa antes de permitir su ejecución.
spec:
  validationFailureAction: Enforce
  background: false
  rules:
    - name: verificar-firma-cosign
      match:
        any:
          - resources:
              kinds: [Pod]
              namespaces: ["produccion", "staging"]
      verifyImages:
        - imageReferences:
            - "registry.empresa.com/*"
          required: true
          attestors:
            - count: 1
              entries:
                - keys:
                    publicKeys: |
                      -----BEGIN PUBLIC KEY-----
                      MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEXXXXXXXXXXXXXXXXXXXXXXXXXXXX
                      xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx==
                      -----END PUBLIC KEY-----
EOF

kubectl apply -f /tmp/verify-signatures.yaml

Auditoría e Informes

# Ver el estado de cumplimiento de todas las políticas
kubectl get policyreport -A

# Ver el informe detallado de un namespace
kubectl get policyreport -n produccion -o yaml | \
  python3 -c "
import sys, yaml
for doc in yaml.safe_load_all(sys.stdin.read()):
    if doc:
        results = doc.get('results', [])
        fails = [r for r in results if r['result'] == 'fail']
        for f in fails[:10]:
            print(f'FAIL: {f[\"policy\"]}/{f[\"rule\"]} - {f[\"resources\"][0][\"kind\"]}/{f[\"resources\"][0][\"name\"]}: {f[\"message\"]}')
"

# Ver el informe a nivel de clúster
kubectl get clusterpolicyreport

# Número de violaciones por política
kubectl get policyreport -A -o json | \
  python3 -c "
import sys, json
data = json.load(sys.stdin)
counts = {}
for item in data['items']:
    for result in item.get('results', []):
        if result['result'] == 'fail':
            key = result['policy']
            counts[key] = counts.get(key, 0) + 1
for policy, count in sorted(counts.items(), key=lambda x: -x[1]):
    print(f'{count:4d} violaciones: {policy}')
"

Mejores Prácticas de Seguridad

# Aplicar las políticas del Pod Security Standard 'restricted'
# (equivalente al antiguo PodSecurityPolicy más estricto)
cat > /tmp/pss-restricted.yaml << 'EOF'
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: pod-security-standards-restricted
spec:
  validationFailureAction: Enforce
  background: true
  rules:
    - name: no-privileged
      match:
        any: [{resources: {kinds: [Pod]}}]
      exclude:
        any: [{resources: {namespaces: [kube-system, kyverno]}}]
      validate:
        message: "Los contenedores privilegiados no están permitidos"
        pattern:
          spec:
            =(initContainers):
              - =(securityContext):
                  =(privileged): "false"
            containers:
              - =(securityContext):
                  =(privileged): "false"
    
    - name: no-host-namespaces
      match:
        any: [{resources: {kinds: [Pod]}}]
      exclude:
        any: [{resources: {namespaces: [kube-system, kyverno]}}]
      validate:
        message: "El uso de host namespaces no está permitido"
        pattern:
          spec:
            =(hostPID): "false"
            =(hostIPC): "false"
            =(hostNetwork): "false"
EOF

kubectl apply -f /tmp/pss-restricted.yaml

Solución de Problemas

# Ver los logs de Kyverno
kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller -f

# Estado de las políticas
kubectl get clusterpolicies
kubectl describe clusterpolicy require-resource-limits

# Probar una política sin aplicarla (modo seco)
# Cambiar validationFailureAction a "Audit" temporalmente
kubectl patch clusterpolicy require-resource-limits \
  --type merge \
  -p '{"spec":{"validationFailureAction":"Audit"}}'

# Ver por qué un recurso fue rechazado
kubectl get events --field-selector reason=PolicyViolation

# Ver todas las decisiones de admisión de Kyverno
kubectl logs -n kyverno -l app.kubernetes.io/component=admission-controller \
  --since=10m | grep "admission.review"

# Kyverno no está interceptando recursos
# Verificar que el webhook está configurado
kubectl get validatingwebhookconfigurations | grep kyverno
kubectl get mutatingwebhookconfigurations | grep kyverno

# Regenerar los webhooks si están corruptos
kubectl delete validatingwebhookconfiguration kyverno-resource-validating-webhook-cfg
# Kyverno los regenera automáticamente

Conclusión

Kyverno simplifica radicalmente la gobernanza de Kubernetes al usar YAML nativo para definir políticas de validación, mutación y generación, eliminando la necesidad de aprender Rego u otros lenguajes de dominio específico. Su capacidad para generar recursos automáticamente, combinar con verificación de imágenes Cosign y producir informes de auditoría detallados lo convierte en una solución integral de policy-as-code que los equipos de DevOps pueden adoptar progresivamente sin interrumpir los flujos de trabajo existentes.