Parte: 10 — Seguridad en la nube y contenedores · Fuente: Liz Rice, "Container Security" (O'Reilly) y CIS Docker Benchmark ⏱️ Duración estimada: 130 min · Nivel: Intermedio
Entender cómo se aísla realmente un contenedor (namespaces, cgroups, capabilities) y aplicar seguridad en las tres fases del ciclo de vida: construcción de imágenes, almacenamiento/registro y ejecución. Al terminar, el alumno podrá escanear imágenes con Trivy, endurecer un Dockerfile y configurar el runtime según el CIS Docker Benchmark.
Al finalizar, el alumno podrá:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Namespaces y cgroups | Base del aislamiento; no es una VM |
| 2 | Capabilities y user namespaces | Reducir privilegios del proceso |
| 3 | Imágenes y capas | Superficie de vulnerabilidades y secretos |
| 4 | Dockerfile seguro y multi-stage | Imágenes pequeñas y limpias |
| 5 | Escaneo de imágenes (Trivy) | Detectar CVEs antes de desplegar |
| 6 | Runtime seguro (seccomp, AppArmor) | Contener el proceso en ejecución |
| 7 | Registro y firma de imágenes | Cadena de suministro confiable |
Un contenedor es un conjunto de procesos aislados mediante mecanismos del kernel y empaquetados con un filesystem. No posee un kernel independiente como una VM convencional. Por eso el riesgo depende tanto de imagen y configuración como del daemon, host y kernel compartido.
El diagrama recorre dos vidas: cadena de suministro y runtime. Una imagen sin CVE conocida puede ejecutarse con --privileged y mounts peligrosos; una configuración restrictiva no corrige una dependencia vulnerable. La evidencia debe cubrir digest, procedencia, contenido, usuario, capabilities, mounts, red y perfiles efectivos.
Namespaces separan vistas de PID, red, montajes, IPC, hostname y usuarios; cgroups limitan y contabilizan recursos. Capabilities dividen privilegios de root, pero algunas como SYS_ADMIN abarcan operaciones amplias. User namespaces o modo rootless pueden mapear root del contenedor a un usuario no privilegiado del host, con limitaciones funcionales que deben probarse.
--privileged amplía dispositivos y capabilities y relaja controles; no debe describirse simplemente como «root del host», porque el resultado depende de mounts, kernel y runtime, pero rompe buena parte del modelo esperado. También son críticos el socket Docker, hostPath, hostNetwork y dispositivos.
Cada instrucción de build puede crear capas recuperables. Borrar un secreto en una capa posterior no elimina el contenido anterior. BuildKit secrets permiten montar material durante un paso sin copiarlo a la imagen, siempre que el comando tampoco lo persista. Multi-stage reduce herramientas en la salida, pero no demuestra ausencia de vulnerabilidades ni secretos.
El escáner relaciona paquetes con bases de vulnerabilidades y puede producir falsos positivos o carecer de contexto de explotabilidad. Se conserva versión, base, digest y política de excepción. La firma vincula una identidad con un digest y declaración; no garantiza que el contenido sea seguro.
Se ejecuta como usuario no root, filesystem de solo lectura, capabilities eliminadas y recursos limitados. Docker aplica un perfil seccomp predeterminado cuando la plataforma lo soporta; AppArmor o SELinux agregan restricciones. Cada excepción se prueba: si la app necesita escribir, se monta solo la ruta y capacidad necesarias, en lugar de desactivar el perfil completo.
CAP_NET_ADMIN). Clave: recórtalas con --cap-drop=ALL y añade solo las necesarias.La imagen original corre como root, contiene compilador y escribe en /tmp y /var/cache/app. El equipo usa multi-stage, crea un UID no root y monta tmpfs solo en ambas rutas. En kernels modernos puede usar un puerto no privilegiado o ajustar la configuración; si requiere NET_BIND_SERVICE, agrega únicamente esa capability y mantiene las demás eliminadas.
Trivy encuentra una CVE en una biblioteca que la app no carga. La excepción no se cierra como «falso positivo» sin más: documenta digest, paquete, ruta, análisis de alcance, fecha de revisión y versión que la corregirá. El runtime se prueba con filesystem read-only y perfil seccomp activo.
Dominas la clase cuando puedes explicar qué aísla cada mecanismo, reconstruir una imagen sin secretos en capas, fijarla por digest y ejecutar la carga con usuario, mounts, capabilities y perfiles mínimos, documentando cada excepción y su prueba.
docker run aquasec/trivy.# Escanear una imagen en busca de CVEs y secretos
trivy image --severity HIGH,CRITICAL nginx:latest
# Ver capabilities y perfil seccomp de un contenedor en ejecución
docker inspect --format '{{ .HostConfig.CapAdd }} {{ .HostConfig.SecurityOpt }}' mi_contenedor
🧪 Laboratorio ejecutable del programa:
devsecops-pipeline— son las capas 4 y 5 del lab de DevSecOps: un Dockerfile con diez antipatrones y las CVE del sistema base.
docker run -it --rm alpine sh y explora los namespaces con lsns desde el host para ver el aislamiento.capsh --print de un contenedor normal y otro --privileged, sin montar discos ni acceder a datos del host. Documenta qué controles se ampliaron y elimina ambos.ENV) y pásalo por Hadolint; corrige los hallazgos.distroless o scratch, usuario no root (USER 1000) y sin secretos.trivy image y compara el número de CVEs y el tamaño.docker run --read-only --cap-drop=ALL --security-opt=no-new-privileges --user 1000 mi_imagen.userns-remap, logging).ubuntu a una distroless y mide la reducción de CVEs..dockerignore que evite filtrar .env y .git.Toma una imagen de aplicación real y prodúcela endurecida: multi-stage, base mínima, usuario no root, sin secretos, y ejecución con capabilities recortadas y read-only.
Criterio de aceptación: trivy image no reporta CVEs CRÍTICAS ni secretos en la imagen final, la
imagen corre como UID no root, y docker inspect confirma CapDrop: [ALL] y ReadonlyRootfs: true.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
permission denied al escribir en read-only |
El proceso necesita escribir; monta un volumen/tmpfs acotado para esa ruta. |
Secreto visible en docker history |
Se pasó por ENV/ARG o COPY; usa build secrets o inyección en runtime. |
| Contenedor corre como root sin querer | Falta USER en el Dockerfile; añádelo y ajusta permisos de archivos. |
| Trivy reporta cientos de CVEs | Imagen base gorda/antigua; cambia a base mínima y actualiza. |
--privileged "necesario" para que funcione |
No se identificó la operación requerida. Traza el fallo y concede solo capability, dispositivo o mount estrictamente justificado. |
❓ ¿Un contenedor es tan seguro como una máquina virtual? No. Comparten el kernel del host; una fuga o un contenedor privilegiado pueden comprometer el host. Para aislamiento fuerte se usan sandboxes como gVisor o Kata Containers.
❓ ¿Por qué correr como no root si el contenedor ya está aislado? Porque si el atacante escapa del contenedor o explota una capability, ser root dentro facilita el escape hacia el host. El usuario no root reduce el impacto de un compromiso.
❓ ¿Dónde guardo los secretos si no en el Dockerfile? Fuera de las capas de imagen, mediante un gestor y una identidad de carga. La entrega en runtime también debe evitar exposición en variables, comandos, logs, dumps y archivos con permisos amplios.
Clase 226 — Ataques y pentest en entornos cloud