Política de auditoría de Kubernetes: qué registrar
Cómo escribir una política de auditoría de Kubernetes que capture la evidencia útil (exec, RBAC, workloads, tokens) sin registrar secrets ni ahogarse en ruido.
TL;DR. Una política de auditoría es una lista ordenada de reglas; la primera que coincide fija el nivel de auditoría de cada solicitud. Para forense necesitas Metadata en todo, Request o RequestResponse en las escrituras de objetos RBAC, pods y controladores de workloads, Request en pods/exec, pods/attach, pods/portforward, nodes/proxy y serviceaccounts/token, y estrictamente Metadata en secrets, configmaps y token reviews. Descarta los health checks y la cháchara de kubelets y kube-proxy. Envía el resultado fuera del clúster y consérvalo al menos 90 días.
Un API server autogestionado no escribe nada hasta que le das una política: la documentación de auditoría de Kubernetes es explícita, sin --audit-policy-file no se registra ningún evento. En EKS, GKE y AKS, el proveedor fija la política por ti y tu única decisión es qué logs exportar (consulta la guía de exportación para EKS, GKE y AKS). En ambos casos, saber cómo es una buena política te dice qué evidencia puedes esperar.
Cómo se evalúan las reglas
Cada regla puede filtrar por users, userGroups, verbs, resources (con grupo de API y, opcionalmente, resourceNames), namespaces y nonResourceURLs. Las reglas se procesan en orden y gana la primera que coincide. Una solicitud que no coincide con ninguna regla no se registra. Dos consecuencias prácticas:
- Coloca tus exclusiones (
level: None) y tus reglas de «nunca registrar cuerpos» antes de las reglas amplias. - Termina con un
level: Metadatageneral para que nada se escape en silencio.
omitStages elimina stages de forma global o por regla. Casi todo el mundo omite RequestReceived, que duplica el evento final. omitManagedFields: true quita el voluminoso metadata.managedFields de los cuerpos registrados.
Qué necesita quien investiga, y por qué
| Evidencia | Nivel mínimo | Por qué |
|---|---|---|
| Quién llamó a qué, desde dónde y con qué resultado | Metadata en todo | Identidad, IP de origen, verbo, objeto, código: la columna vertebral de cualquier línea de tiempo |
Specs de pods y workloads (pods, deployments, daemonsets, statefulsets, jobs, cronjobs) en create / update / patch | Request | Sin el cuerpo no puedes ver privileged, hostPID, un hostPath ni la imagen |
Escrituras RBAC (roles, rolebindings, clusterroles, clusterrolebindings) | Request o RequestResponse | El roleRef y los subjects dicen si un binding concede cluster-admin y a quién |
pods/exec, pods/attach, pods/portforward, nodes/proxy | Basta con Metadata, Request también sirve | El comando está en requestURI; la sesión en sí nunca se registra |
serviceaccounts/token | Request, no RequestResponse | La solicitud muestra la caducidad y la audiencia pedidas; la respuesta contiene el propio token |
secrets, configmaps, tokenreviews | Solo Metadata | Los cuerpos contienen credenciales; registrarlos convierte el audit log en un almacén de credenciales |
Borrado de events | Metadata | Borrar Events es una medida antiforense barata |
Una política orientada a forense
Esta política es un punto de partida, no algo para pegar tal cual. Pruébala en un clúster que no sea de producción y vigila el volumen de logs.
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- "RequestReceived"
omitManagedFields: true
rules:
# 1. Noise: health checks and high-frequency system reads
- level: None
nonResourceURLs: ["/healthz*", "/livez*", "/readyz*", "/version"]
- level: None
users: ["system:kube-proxy"]
verbs: ["watch"]
resources:
- group: ""
resources: ["endpoints", "services", "services/status"]
- level: None
userGroups: ["system:nodes"]
verbs: ["get"]
resources:
- group: ""
resources: ["nodes", "nodes/status"]
# 2. Credentials: never log bodies
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- group: "authentication.k8s.io"
resources: ["tokenreviews"]
# 3. Interactive access and token minting (request only: the
# TokenRequest response contains the token)
- level: Request
resources:
- group: ""
resources: ["pods/exec", "pods/attach", "pods/portforward",
"nodes/proxy", "serviceaccounts/token"]
# 4. Events: keep deletions, drop the rest
- level: Metadata
verbs: ["delete", "deletecollection"]
resources:
- group: ""
resources: ["events"]
- group: "events.k8s.io"
resources: ["events"]
- level: None
resources:
- group: ""
resources: ["events"]
- group: "events.k8s.io"
resources: ["events"]
# 5. Writes to workloads, RBAC, service accounts, admission: full bodies
- level: RequestResponse
verbs: ["create", "update", "patch", "delete", "deletecollection"]
resources:
- group: ""
resources: ["pods", "serviceaccounts", "namespaces", "nodes"]
- group: "apps"
- group: "batch"
- group: "rbac.authorization.k8s.io"
- group: "admissionregistration.k8s.io"
# 6. Everything else
- level: Metadata
Algunas decisiones que conviene explicar:
- La regla 2 va antes que la 5. Los secrets deben quedarse en
Metadataincluso encreateyupdate, así que la regla de secrets tiene que coincidir primero. - La regla 3 usa
Request. Los subrecursos de streaming no tienen un cuerpo significativo; lo que necesitas (el comando, el contenedor) está en la URI. Paraserviceaccounts/token,RequestResponseregistraría un bearer token válido. - Las actualizaciones de estado de los kubelets (patches de
nodes/status,pods/status) caen en la regla 6, enMetadata. Es más que suficiente para investigar y mucho más barato que los cuerpos. - Las lecturas se quedan en
Metadata. Registrar los cuerpos de respuesta de loslistes lo que dispara el tamaño de los audit logs; rara vez ayuda en una investigación.
Como comparación, la política que AWS publica en la guía de buenas prácticas de EKS sigue la misma estructura: Metadata para secrets, configmaps y token reviews, Request para serviceaccounts/token, cuerpos completos para la mayoría de grupos de API conocidos. Una diferencia notable: descarta por completo los events, así que los borrados de Events no son visibles en EKS.
Activarla en un API server autogestionado
En un plano de control tipo kubeadm, el API server corre como pod estático. Añade los flags a /etc/kubernetes/manifests/kube-apiserver.yaml y monta la política y el directorio de logs:
- --audit-policy-file=/etc/kubernetes/audit-policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
maxage va en días y maxsize en megabytes. El kubelet reinicia el API server cuando cambia el manifiesto. Distribuciones como k3s y RKE2 exponen los mismos flags a través de su propia configuración; consulta su documentación para las claves exactas.
La documentación también señala que la auditoría aumenta el consumo de memoria del API server, en proporción a lo que registras. Otro motivo para reservar los cuerpos a las escrituras que importan.
Saca los logs de la máquina
Un atacante con cluster-admin y un pod privilegiado puede leer y editar archivos en los nodos del plano de control. Unos logs que solo existen ahí son logs que controla el atacante. Dos opciones:
- El backend webhook (
--audit-webhook-config-file) envía lotes a un endpoint HTTP: un recolector de logs, un SIEM, una cola. - El backend de log más un agente de envío (Fluent Bit, Vector, el agente del proveedor cloud) lee
audit.logy lo manda a un almacenamiento central.
La guía de hardening de Kubernetes de la NSA y la CISA también recomienda activar la auditoría y guardar los logs fuera del clúster. Conserva al menos 90 días: el acceso inicial suele preceder a la detección en semanas.
Comprueba tu política con detecciones reales
Una buena prueba: ejecuta una secuencia maliciosa conocida en un clúster de laboratorio (exec en un pod, creación de un pod privilegiado con un hostPath de /, binding de una cuenta de servicio a cluster-admin), exporta el log y arrástralo al analizador de audit logs. Si no aparecen los hallazgos de pod privilegiado o de cluster-admin, los cuerpos correspondientes no se están registrando. La sección de referencia del analizador enumera las detecciones; la guía paso a paso explica cómo leer el resultado.