Hardening de contenedores y Kubernetes: checklist de campo
La mayoría de las brechas en Kubernetes no son zero days. Son valores por defecto que nadie cambió. Esta es la checklist de hardening que aplicamos de verdad en los clústeres de nuestros clientes.
Por Innovation T Team
La mayoría de los compromisos en Kubernetes no empiezan con un zero day. Empiezan con un contenedor ejecutándose como root, una service account con cluster-admin o un dashboard que alguien expuso a internet en 2023 y del que nadie volvió a acordarse. El hardening no es un producto que se compra. Es un conjunto de valores por defecto que ustedes se niegan a aceptar, y esta es la checklist que ejecutamos cuando nos hacemos cargo de un clúster.
El modelo de amenazas en un párrafo
Un atacante que aterriza dentro de un contenedor quiere tres cosas: escalar privilegios dentro del contenedor, escapar al nodo, o moverse lateralmente por la red del clúster y la API de Kubernetes. Cada control de esta guía existe para cortar una de esas tres rutas. Si no pueden decir qué ruta bloquea un control, probablemente todavía no lo necesitan. Ese encuadre mantiene honesto el trabajo de hardening y evita que los equipos se ahoguen en líneas del benchmark CIS que no cambian ningún resultado real.
Empiecen por la imagen, no por el clúster
Las vulnerabilidades más baratas de corregir son las que nunca llegan a producción. La higiene de imágenes es donde el hardening rinde primero.
Reduzcan la superficie de ataque
Una imagen node:20 por defecto trae cientos de paquetes del sistema operativo, una shell, un gestor de paquetes y, el día del escaneo, normalmente cientos de CVE conocidas. Cada binario de la imagen es una herramienta para el atacante después del compromiso. Las opciones, en orden aproximado de esfuerzo:
- Variantes slim (
-slim,-alpine): victorias rápidas, aunque la libc musl de Alpine a veces rompe dependencias nativas y el comportamiento de DNS de formas sutiles. Prueben antes de comprometerse. - Distroless (las imágenes
gcr.io/distrolessde Google): sin shell y sin gestor de paquetes. La depuración pasa a hacerse con contenedores efímeros (kubectl debug), un cambio de flujo de trabajo que su equipo debe practicar antes de un incidente, no durante uno. - Imágenes basadas en Chainguard o Wolfi: se reconstruyen a diario, suelen llegar con casi cero CVE conocidas e incluyen SBOM. La contrapartida es depender de la cadencia de builds de un proveedor y, en algunas imágenes, costes de licencia.
Compilen en varias etapas, ejecuten sin root
La cadena de compilación nunca debe viajar en la imagen de runtime. Compiladores, npm, git y curl son exactamente lo que una reverse shell necesita.
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app ./cmd/api
FROM gcr.io/distroless/static:nonroot
COPY --from=build /app /app
USER 65532:65532
ENTRYPOINT ["/app"]
Esa imagen final tiene un solo binario, ninguna shell y un usuario sin privilegios. Un atacante que explote el proceso de la aplicación cae en una habitación sin herramientas.
Escaneen, firmen y fijen versiones
Escanear sin una política es puro teatro. Intégrenlo en el pipeline con una barrera real:
- Trivy o Grype en CI, haciendo fallar la build ante CVE críticas y altas con corrección disponible. Permitan excepciones documentadas y con fecha de caducidad, nunca ignores silenciosos.
- Cosign para firmar imágenes en el momento de la build, con una política de admisión en el clúster que rechace imágenes sin firmar. Esto cierra el agujero de "alguien subió una imagen desde su portátil".
- Fijar por digest las imágenes base (
FROM node@sha256:...) para que las builds sean reproducibles y un tag envenenado no pueda colarse. - Generación de SBOM con Syft, almacenado como artefacto, para que cuando llegue el próximo evento estilo Log4j puedan responder "¿estamos expuestos?" en minutos, no en días.
Esto es un problema de pipeline tanto como de seguridad. Cubrimos el lado de CI en profundidad en nuestra guía sobre cómo construir un pipeline DevSecOps.
Blinden la especificación del pod
Los valores por defecto de Kubernetes están optimizados para "que funcione", no para "que sea seguro". El securityContext del pod es donde se corrige eso, y en runtime cuesta prácticamente nada.
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
seccompProfile:
type: RuntimeDefault
capabilities:
drop: ["ALL"]
Lo que compra cada línea en la práctica:
runAsNonRootmás un UID fijo: la mayoría de las técnicas de escape de contenedor que dependen de root dentro del contenedor dejan de funcionar.allowPrivilegeEscalation: false: impide que binarios setuid eleven privilegios, lo que elimina toda una clase de escaladas locales.readOnlyRootFilesystem: true: el malware no puede escribir payloads en disco. Monten unemptyDiren/tmppara las aplicaciones que necesiten espacio temporal.seccompProfile: RuntimeDefault: filtra alrededor de 60 de las syscalls más exóticas. Casi todos los escapes de contenedor a nivel de kernel de los últimos años necesitaban una syscall que este perfil bloquea.- Capabilities con
drop: ["ALL"]: añadan de vuelta solo lo que puedan justificar, y desconfíen de cualquier cosa que pidaNET_ADMINoSYS_ADMIN. Un pod conSYS_ADMINo conprivileged: truees, a efectos prácticos, root en el nodo.
Hagan cumplir todo esto con Pod Security Admission a nivel de namespace. Etiqueten cada namespace de aplicación con pod-security.kubernetes.io/enforce: restricted y traten las excepciones como tickets con responsable y fecha de caducidad. En nuestra experiencia, la migración destapa un puñado de cargas legacy que de verdad necesitan privilegios (agentes de CNI, colectores de logs, drivers de almacenamiento). Aíslenlas en namespaces dedicados en lugar de relajar la política para todo el mundo.
Contengan el radio de impacto
Asuman que tarde o temprano un pod será comprometido. La pregunta es qué puede alcanzar el atacante después.
RBAC: mínimo privilegio o nada
El hallazgo más frecuente en nuestras revisiones de clústeres: service accounts con permisos que nadie sabe explicar. Las reglas son simples y aburridas:
- Nunca vinculen
cluster-admina una carga de trabajo. Nunca. Incluidos los deployers de CI. - Una service account por carga de trabajo, acotada a su namespace, con los verbos que realmente usa.
- Configuren
automountServiceAccountToken: falsepor defecto. La mayoría de los pods de aplicación jamás llaman a la API de Kubernetes y, aun así, todos llegan con una credencial de API válida montada en una ruta bien conocida. Ese token es lo primero que lee un atacante. - Auditen periódicamente con
kubectl auth can-i --list --as=system:serviceaccount:ns:nameo con herramientas comorbac-tool. Los comodines en verbos o recursos son hallazgos, no comodidades.
Network policies: denegar por defecto, luego permitir
De fábrica, cada pod puede hablar con cualquier otro pod de cualquier namespace. Esa red plana es la razón por la que un frontend comprometido termina en una brecha de base de datos. Arranquen cada namespace con una denegación por defecto:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
spec:
podSelector: {}
policyTypes: ["Ingress", "Egress"]
Después permitan flujos concretos: frontend hacia la API en el 8080, API hacia Postgres en el 5432, todo hacia DNS. La política de egress importa tanto como la de ingress, porque es lo que convierte "atacante en un pod" en "atacante en un pod que no puede alcanzar su servidor de C2 ni el endpoint de metadatos de la nube". Bloquear el acceso de los pods a 169.254.169.254, salvo que la carga lo necesite de verdad, ha frenado rutas reales de robo de credenciales cloud. Esto es zero trust aplicado a nivel de pod, y el razonamiento es el mismo que desarrollamos en la arquitectura zero trust explicada.
La contrapartida: las network policies son implacables de depurar cuando un despliegue se rompe porque alguien olvidó la regla de egress para DNS. Entreguen una biblioteca de políticas probadas con sus plantillas de plataforma en lugar de pedir a cada equipo que escriba las suyas.
Secretos: dejen de fingir que base64 es cifrado
Los Secrets de Kubernetes están codificados en base64, no cifrados, y cualquiera con acceso de lectura a los secretos de un namespace tiene el texto plano. El listón mínimo:
- Habiliten el cifrado en reposo de etcd con un proveedor KMS (en clústeres gestionados: verifíquenlo, no lo den por hecho). Si operan en España, recuerden que el RGPD y la LOPDGDD les exigen medidas técnicas proporcionales al riesgo, y "los secretos estaban en base64" no supera ninguna inspección de la AEPD. Las leyes de protección de datos de América Latina van en la misma dirección.
- Prefieran volúmenes de secretos montados antes que variables de entorno. Las variables de entorno se filtran en volcados de memoria, en
kubectl describey en procesos hijos. - Para cualquier cosa seria, consuman desde un gestor externo (Vault, AWS Secrets Manager, GCP Secret Manager) vía External Secrets Operator o driver CSI, de modo que rotar no exija redesplegar el mundo entero.
- Restrinjan el RBAC sobre el propio recurso Secret. Un
get secretsa nivel de clúster es un administrador de dominio disfrazado.
Endurezcan el plano de control y los nodos
En Kubernetes gestionado (EKS, GKE, AKS), el proveedor es dueño de etcd y de los binarios del API server, pero ustedes siguen siendo dueños de la configuración que importa. Y esto aplica igual si despliegan en eu-west desde Madrid que si su startup corre en las regiones de São Paulo, Santiago o Querétaro para ganar latencia con sus usuarios:
- Endpoint privado para la API o, como mínimo, listas de CIDR estrictas. Un API server expuesto a internet empieza a recibir escaneos a los pocos minutos de crearse.
- Logs de auditoría activados y enviados a algún sitio donde se puedan buscar. Sin ellos, después de un incidente estarán reconstruyendo las acciones del atacante de memoria. Y si el RGPD les obliga a notificar una brecha en 72 horas, ese informe se escribe con logs, no con suposiciones.
- Sistema operativo de los nodos: usen imágenes mínimas optimizadas para contenedores (Bottlerocket, COS, Flatcar), mantengan los node pools en una cadencia de actualización automatizada y nunca permitan SSH a los nodos como práctica habitual.
- Versiones al día: las versiones menores de Kubernetes quedan sin soporte en aproximadamente un año. Los clústeres con más de dos versiones menores de retraso acumulan CVE sin parchear y un precipicio de actualización aterrador. Actualicen poco y a menudo.
Detecten en runtime, porque la prevención falla
Todo lo anterior reduce la probabilidad. La detección en runtime se ocupa del riesgo residual. Herramientas como Falco o Tetragon observan las syscalls vía eBPF y señalan comportamientos que jamás deberían ocurrir en una carga endurecida: una shell arrancando en un contenedor distroless, una conexión saliente inesperada, un proceso leyendo el token de la service account, escrituras en /etc/passwd.
La calidad de la señal aquí es inusualmente buena, precisamente porque el hardening eliminó el ruido. Si su imagen no tiene shell, "se ejecutó una shell" no es una puntuación de anomalía. Es un incidente. Conecten estas alertas a un proceso de respuesta con responsables y runbooks. Si todavía no tienen ese músculo, empiecen por nuestro playbook de respuesta a incidentes.
Fallos que vemos una y otra vez
- Hardening aplicado a los servicios nuevos mientras el namespace legacy conserva
privileged: true"de forma temporal", desde hace dos años. - Un escáner en CI con el umbral de fallo desactivado, generando informes que nadie lee.
- Network policies desplegadas sin la regla de egress para DNS, rompiendo producción, y luego revertidas por completo en lugar de corregidas.
- Políticas de admisión en modo auditoría para siempre, porque nadie agendó el paso a modo de aplicación obligatoria.
- Secretos "migrados a Vault" pero con los Secrets antiguos de Kubernetes sin borrar, todavía legibles, todavía válidos.
El patrón detrás de los cinco: tratar el hardening como un proyecto con fecha de fin en lugar de como un conjunto de valores por defecto que se hacen cumplir. Los motores de políticas (Kyverno, o la ValidatingAdmissionPolicy nativa con CEL) existen para que el camino seguro sea el único camino.
La checklist de campo
Recórranla de arriba abajo. Cada punto es un sí o un no, y "más o menos" cuenta como no.
- Todas las imágenes de runtime son mínimas (distroless, Chainguard o slim) y se construyen en varias etapas.
- CI hace fallar las builds ante CVE críticas y altas con corrección disponible, con excepciones acotadas en el tiempo como único escape.
- Las imágenes se firman con Cosign y el clúster rechaza imágenes sin firmar.
- Las imágenes base están fijadas por digest y se genera un SBOM por build.
- Toda carga de trabajo corre sin root, con
allowPrivilegeEscalation: false, capabilities eliminadas, seccompRuntimeDefaulty sistema de archivos raíz de solo lectura. - Pod Security Admission aplica
restricteden todos los namespaces de aplicación. - Ninguna service account de carga de trabajo tiene cluster-admin, y el automontaje del token está desactivado por defecto.
- Cada namespace tiene network policies de denegación por defecto para ingress y egress, incluido el bloqueo del endpoint de metadatos.
- El cifrado en reposo de etcd usa un KMS, y los secretos viven en un gestor externo con rotación.
- El API server es privado o está restringido por CIDR, y los logs de auditoría se envían a almacenamiento donde se pueden consultar.
- Los nodos corren un sistema operativo optimizado para contenedores con parcheo automatizado, y el clúster está a menos de dos versiones menores de la actual.
- La detección en runtime (Falco o Tetragon) está desplegada, con alertas dirigidas a un proceso de respuesta con responsables.
- Las políticas de admisión hacen cumplir todo lo anterior, de modo que la deriva no pueda ocurrir en silencio.
Con doce o más van por delante de la inmensa mayoría de los clústeres que evaluamos. Con menos de ocho, deberían tratar esto como la prioridad del trimestre, porque los atacantes automatizan el descubrimiento de exactamente estas brechas. Si además son una fintech o una scaleup regulada, con PCI DSS o la AEPD mirando por encima del hombro, la conversación deja de ser opcional.
Cómo puede ayudar Innovation T
Innovation T diseña, construye y endurece plataformas de contenedores para empresas de Europa, el Golfo y el norte de África, incluidos equipos que operan bajo RGPD y compañías con operaciones distribuidas entre España y América Latina. Ejecutamos evaluaciones de seguridad de clústeres contra esta misma checklist y luego hacemos el trabajo sin glamour: migrar cargas a pod security restringida, escribir la biblioteca de políticas, integrar la firma de imágenes en CI y dejar a su equipo con valores por defecto aplicados, no con un informe en PDF.
Si su clúster ha crecido más rápido que sus barreras de seguridad, conozcan nuestros servicios de nube y seguridad o hablen con nuestros ingenieros. Les diremos con honestidad cuáles de estos trece puntos importan más en su entorno y cuáles pueden esperar.
¿Listo para construir con Innovation T?
Ya se trate de seguridad, crecimiento o ingeniería, nuestro equipo puede ayudarte a lograrlo con calidad.