Tema 15
Los microservicios suelen ejecutarse en contenedores, y eso cambia la forma en que se modelan privilegios, aislamiento y superficie de ataque. Un contenedor no es una frontera mágica: si se construye con imágenes débiles o se ejecuta con privilegios excesivos, el riesgo se traslada directamente al runtime.
Los contenedores simplifican empaquetado, despliegue y portabilidad, pero no eliminan el problema de seguridad. En realidad, trasladan parte del riesgo al proceso de construcción de imágenes y al entorno de ejecución. Una imagen demasiado grande, un contenedor con privilegios excesivos o una mala exposición del filesystem pueden convertir un microservicio en una puerta de entrada útil para un atacante.
Por eso la seguridad de contenedores no debe limitarse a "funciona en Docker". Hay que revisar qué se empaqueta, con qué usuario corre, qué capacidades del kernel conserva, qué recursos monta y qué aislamiento real existe frente al host y frente a otros contenedores.
Este tema aborda el hardening del contenedor como unidad de runtime. En los temas siguientes eso se extenderá a Kubernetes y a las políticas del clúster.
Un contenedor aísla procesos, filesystem, red y otros recursos mediante mecanismos del kernel, pero ese aislamiento no es absoluto. No debe pensarse como si fuera una máquina virtual completamente independiente.
Un contenedor protege mejor cuando:
La seguridad del contenedor empieza mucho antes de arrancarlo. Empieza en la imagen. Cada paquete extra, intérprete innecesario, herramienta de depuración o dependencia sin revisar amplía superficie de ataque y complejidad de parcheo.
Una imagen demasiado amplia puede incluir:
Una buena práctica es construir imágenes lo más pequeñas y específicas posible. Esto reduce superficie de ataque, acelera distribución y simplifica auditoría. También conviene que el proceso de build sea reproducible y que la procedencia de la imagen base sea confiable.
| Práctica | Qué aporta | Riesgo que reduce |
|---|---|---|
| Base mínima | Menos componentes en runtime | Menor superficie vulnerable |
| Multi-stage build | Evita dejar herramientas de compilación en producción | Menos ruido y menor post-explotación |
| Origen confiable | Mejor trazabilidad de dependencias | Menor riesgo de imagen contaminada |
Uno de los principios más importantes del runtime es evitar que el proceso principal del contenedor corra como root. Aunque el contenedor tenga cierto aislamiento, ejecutar como root incrementa el daño potencial si el proceso es comprometido.
Cuando el servicio corre con un usuario no privilegiado:
Un contenedor privilegiado recibe acceso amplio al host y al kernel, perdiendo gran parte del aislamiento esperado. En microservicios de aplicación general esto casi nunca debería ser necesario.
Permitir privilegios amplios por comodidad operativa es una mala práctica, porque transforma un compromiso de aplicación en un riesgo mucho más cercano al host o al nodo donde corre.
Las capabilities permiten granular privilegios que antes se asociaban enteramente al usuario root. Esto es útil porque permite entregar menos permisos al proceso del contenedor.
Desde el punto de vista de hardening, lo importante es:
Los namespaces son parte del mecanismo que aísla recursos entre procesos y contenedores: PID, red, mount, usuarios y otros ámbitos. Son fundamentales, pero no deberían interpretarse como garantía suficiente por sí solos.
Un buen hardening asume que el aislamiento del namespace necesita apoyo de otras capas: privilegios mínimos, restricciones de syscalls, políticas de plataforma y buena segmentación de red.
Si un proceso no necesita escribir en el filesystem raíz del contenedor, conviene ejecutarlo como read-only. Esto reduce la capacidad de modificar binarios, dejar herramientas persistentes o alterar archivos de configuración en runtime.
Cuando sí se necesitan escrituras, conviene acotar explícitamente dónde:
Los mecanismos como seccomp y AppArmor permiten restringir llamadas al sistema y comportamientos del proceso en runtime. No siempre se configuran manualmente desde cero, pero es importante entender su propósito: reducir qué puede hacer el proceso aun si es comprometido.
Esto ayuda a limitar:
Los contenedores consumen secretos, pero no deberían cargarlos dentro de la imagen. Si un secreto viaja en el Dockerfile, en layers, en archivos copiados o en variables mal controladas, la exposición puede quedar persistida mucho más allá del runtime.
Las buenas prácticas aquí se alinean con el tema anterior:
Definir límites de CPU, memoria y otros recursos no es solo una cuestión de costo o performance. También ayuda a contener daño. Un contenedor sin límites claros puede consumir recursos excesivos y afectar estabilidad de otros workloads en el mismo nodo.
Esto es relevante tanto para errores de software como para abuso intencional o comportamientos inesperados bajo carga.
Hardening no termina en la configuración inicial. También hay que observar comportamiento del contenedor en ejecución. Procesos inesperados, escrituras fuera de patrón, conexiones anómalas o cambios en el sistema de archivos pueden ser señales valiosas de compromiso o mala configuración.
Conviene monitorear al menos:
El contenedor es la unidad práctica donde termina corriendo gran parte del sistema, y por eso su seguridad impacta directamente en la postura general de la arquitectura. Cuando se construyen imágenes mínimas, se acotan privilegios y se endurece el runtime, el daño potencial de un compromiso disminuye y la operación gana previsibilidad.
En el próximo tema estudiaremos seguridad en Kubernetes: pods, RBAC, network policies y admission control para ampliar este hardening desde el contenedor individual hacia el clúster completo.