Introducción: Por qué la seguridad de contenedores importa (más que nunca) en 2026
Si trabajas con contenedores en producción, probablemente ya lo sabes: la contenedorización se ha vuelto el estándar para desplegar aplicaciones modernas. Pero con esa adopción masiva también han llegado problemas serios. Los números no mienten — más del 85% de las imágenes de contenedores en producción contienen vulnerabilidades clasificadas como de alta o crítica severidad. Sí, leíste bien.
El 2025 y lo que llevamos de 2026 han sido especialmente intensos en lo que respecta a vulnerabilidades en el runtime de contenedores runc, ese componente fundamental que tanto Docker como Podman usan para ejecutar contenedores. Vulnerabilidades como CVE-2025-31133, CVE-2025-52565 y CVE-2025-52881 han permitido escapes de contenedores — es decir, un atacante dentro de un contenedor comprometido logra acceso privilegiado al sistema host. Esto nos recuerda algo incómodo: los contenedores no son máquinas virtuales, y la superficie de ataque compartida con el kernel del host requiere múltiples capas de defensa.
Así que, vamos al grano. En este artículo vamos a recorrer las técnicas más avanzadas de hardening para contenedores en Linux usando Docker y Podman. Desde la arquitectura de seguridad hasta la implementación práctica de controles en tiempo de ejecución, pasando por integración con pipelines DevSecOps y cumplimiento normativo. Si administras infraestructura basada en contenedores, esto te va a servir.
Comprendiendo la superficie de ataque de contenedores
Antes de lanzarnos a implementar controles de seguridad, vale la pena entender las capas que componen el stack de contenedores y dónde se esconden las vulnerabilidades potenciales.
Capa 1: El kernel del host
A diferencia de la virtualización completa, los contenedores comparten el kernel del sistema operativo host. Esto implica que una vulnerabilidad en el kernel puede ser explotada desde dentro de un contenedor para comprometer todo el sistema. Tecnologías como namespaces (PID, NET, MNT, UTS, IPC, USER) y cgroups proporcionan aislamiento, pero — y esto es importante — no son barreras de seguridad absolutas.
Capa 2: El runtime de contenedores (runc)
runc es el runtime de bajo nivel que ejecuta contenedores según la especificación OCI (Open Container Initiative). Las vulnerabilidades recientes han dejado claro que errores en este componente pueden permitir escapes de contenedores completos. Mantenerlo actualizado no es opcional, es crítico.
Capa 3: El motor de contenedores (Docker/Podman)
Docker y Podman son los motores de alto nivel que gestionan el ciclo de vida de los contenedores. Es aquí donde configuramos políticas de seguridad, capabilities, perfiles seccomp y más. La arquitectura de cada uno tiene implicaciones de seguridad significativas, y las vamos a explorar en detalle.
Capa 4: La imagen de contenedor
Las imágenes son el artefacto que contiene la aplicación y todas sus dependencias. Una imagen mal construida puede incluir vulnerabilidades conocidas, credenciales hardcodeadas o binarios innecesarios que amplían la superficie de ataque. He visto imágenes de producción con compiladores de C++ incluidos — créeme, pasa más de lo que pensarías.
Capa 5: La aplicación
Finalmente, la aplicación misma puede tener vulnerabilidades. Los contenedores añaden capas de aislamiento, pero no te protegen contra bugs en tu propio código.
La seguridad efectiva requiere hardening en cada una de estas capas. Un enfoque de defensa en profundidad asume que alguna capa será comprometida y establece controles redundantes. No hay atajos aquí.
Podman vs Docker: Comparación de arquitecturas de seguridad
La elección entre Docker y Podman tiene implicaciones profundas para la postura de seguridad de tu infraestructura. Ambos pueden ejecutar contenedores OCI, pero sus arquitecturas son fundamentalmente diferentes.
Arquitectura basada en daemon vs fork-exec
Docker utiliza una arquitectura cliente-servidor con un daemon persistente (dockerd) que se ejecuta con privilegios de root. Todos los contenedores son hijos de este daemon. Esto crea un punto único de fallo: si el daemon se compromete, el atacante obtiene acceso root al host. No es un escenario teórico, ha pasado.
Podman, en cambio, usa un modelo daemonless. Cuando ejecutas podman run, el proceso hace fork/exec directamente del proceso del contenedor. No hay daemon de larga duración. Esto elimina una superficie de ataque significativa y significa que cada contenedor es un proceso hijo del usuario que lo lanzó.
Contenedores rootless: capabilities del kernel
Una de las diferencias más relevantes está en los contenedores rootless. Con Podman rootless, el proceso del contenedor obtiene solo 11 capabilities del kernel por defecto. Docker rootless, aunque ha mejorado bastante, otorga 14 capabilities. Menos capabilities equivale a menor capacidad de un atacante para realizar operaciones privilegiadas, incluso si logra escapar del contenedor.
Las capabilities que Podman rootless NO otorga incluyen:
CAP_SYS_ADMIN— Operaciones administrativas del sistemaCAP_SYS_MODULE— Carga de módulos del kernelCAP_SYS_RAWIO— Operaciones I/O directas
Mapeo de user namespaces
Tanto Docker como Podman soportan user namespaces, pero Podman los habilita por defecto en modo rootless. El usuario root (UID 0) dentro del contenedor queda mapeado a un usuario sin privilegios en el host (típicamente tu UID + 1 en el rango subordinado).
Por ejemplo, si tu UID es 1000, el root del contenedor (UID 0) podría mapearse al UID 100000 en el host. Esto se configura en /etc/subuid y /etc/subgid:
usuario:100000:65536
¿Qué significa esto en la práctica? Que incluso si un atacante escapa del contenedor con privilegios de root, en el host solo tiene los permisos de un usuario sin privilegios. Bastante elegante como mecanismo de defensa.
Integración nativa con SELinux
Podman fue diseñado con SELinux en mente desde el principio (no es casualidad, viene de Red Hat). La integración es transparente y automática en distribuciones como RHEL, Fedora y CentOS Stream. Cada contenedor obtiene automáticamente un contexto SELinux único del tipo container_t, y los volúmenes se etiquetan con container_file_t.
Docker también soporta SELinux, pero la integración requiere configuración adicional y, honestamente, no es tan fluida.
Recomendación de seguridad
Para entornos de alta seguridad, Podman en modo rootless con SELinux habilitado ofrece la mejor postura de seguridad por defecto. Dicho esto, Docker ha cerrado mucha de la brecha y sigue siendo viable con la configuración apropiada. Lo más importante es aplicar hardening consistente independientemente del motor que elijas.
Hardening de imágenes: Construyendo contenedores seguros desde la base
La seguridad empieza en la imagen. Una imagen bien construida minimiza la superficie de ataque y reduce drásticamente las vulnerabilidades que llegan a producción.
Multi-stage builds para minimizar superficie de ataque
Los multi-stage builds te permiten compilar tu aplicación en una imagen con todas las herramientas de desarrollo, y luego copiar solo el binario final a una imagen mínima de producción. Adiós compiladores, herramientas de desarrollo y dependencias innecesarias.
Ejemplo de Dockerfile multi-stage para una aplicación Go:
# Stage 1: Build
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o myapp .
# Stage 2: Production
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/myapp /myapp
USER nonroot:nonroot
ENTRYPOINT ["/myapp"]
Este Dockerfile tiene dos stages. El primero usa una imagen completa de Go para compilar. El segundo usa una imagen distroless que contiene solo tu aplicación y las bibliotecas runtime estrictamente necesarias. La diferencia en tamaño (y superficie de ataque) es enorme.
Imágenes base distroless y mínimas
Las imágenes distroless de Google no contienen gestores de paquetes, shells, ni utilidades del sistema. Esto significa que incluso si un atacante compromete la aplicación, no tiene herramientas disponibles para escalar privilegios o pivotar a otros sistemas.
Alternativas mínimas que vale la pena considerar:
gcr.io/distroless/static-debian12— Para binarios estáticos (Go, Rust)gcr.io/distroless/base-debian12— Para aplicaciones con dependencias glibcalpine:3.19— Imagen mínima (5MB) con musl libc y apkchainguard/static:latest— Imágenes minimalistas con SBOM incluido
Pinning de versiones explícito
Nunca uses tags como latest o stable. En serio, nunca. Siempre especifica versiones exactas y digests SHA256 para reproducibilidad y seguridad:
FROM alpine:3.19.1@sha256:6457d53fb065d6f250e1504b9bc42d5b6c65941d57532c072d929dd0628977d0
Esto garantiza que builds futuros usen exactamente la misma imagen base, previniendo ataques de supply chain donde una imagen podría ser reemplazada con una versión maliciosa.
Ejecutar como usuario no-root dentro del contenedor
Por defecto, los procesos en contenedores se ejecutan como root (UID 0) dentro del namespace del contenedor. Aunque esté mapeado a un usuario sin privilegios en el host (en modo rootless), es mucho mejor ejecutar como usuario no-root también dentro del contenedor:
FROM alpine:3.19
RUN addgroup -g 1000 appgroup && \
adduser -D -u 1000 -G appgroup appuser
COPY --chown=appuser:appgroup ./app /app
USER appuser
ENTRYPOINT ["/app"]
O usando imágenes distroless con el usuario nonroot predefinido:
FROM gcr.io/distroless/static-debian12:nonroot
COPY --chown=nonroot:nonroot ./app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
Escaneo de vulnerabilidades en tiempo de build
Integra escaneo de vulnerabilidades en tu proceso de build. La idea es simple: si se detectan vulnerabilidades críticas, el build falla. Mejor enterarte ahí que en producción.
# Build la imagen
docker build -t myapp:latest .
# Escanear con Trivy
trivy image --severity HIGH,CRITICAL --exit-code 1 myapp:latest
Hardening de runtime: Asegurando contenedores en ejecución
Incluso con imágenes perfectamente construidas, la configuración de runtime es crítica. Aquí es donde realmente aplicamos el principio de mínimo privilegio.
Eliminación de capabilities innecesarias
Por defecto, Docker y Podman otorgan un conjunto de capabilities del kernel que la mayoría de aplicaciones simplemente no necesitan. La mejor práctica es eliminar TODAS las capabilities y añadir solo las estrictamente necesarias:
# Docker
docker run -d \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--cap-add CHOWN \
--read-only \
--tmpfs /tmp \
nginx:alpine
# Podman
podman run -d \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--read-only \
--tmpfs /tmp \
nginx:alpine
En este ejemplo, una aplicación web necesita solo NET_BIND_SERVICE para enlazarse a puertos menores a 1024, y CHOWN si necesita cambiar ownership de archivos.
Para aplicaciones que no requieren puertos privilegiados, no necesitas ninguna capability en absoluto:
podman run -d \
--cap-drop ALL \
--read-only \
--tmpfs /tmp \
-p 8080:8080 \
myapp:latest
Sistema de archivos de solo lectura
Montar el filesystem del contenedor como solo lectura previene que un atacante modifique binarios o instale malware. Usa --read-only y proporciona volúmenes tmpfs para los directorios que necesitan escritura:
docker run -d \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--tmpfs /var/run:rw,noexec,nosuid,size=50m \
--tmpfs /var/cache/nginx:rw,noexec,nosuid,size=200m \
nginx:alpine
Las opciones noexec y nosuid previenen la ejecución de binarios y setuid en estos filesystems temporales. Es un detalle pequeño que marca una gran diferencia.
Límites de recursos
Sin límites, un contenedor comprometido puede consumir todos los recursos del host en un ataque de denegación de servicio. Siempre configura límites — no hay excusa para no hacerlo:
podman run -d \
--memory=512m \
--memory-swap=512m \
--cpus=1.5 \
--pids-limit=200 \
--ulimit nofile=1024:2048 \
--ulimit nproc=100:200 \
myapp:latest
Esto limita el contenedor a:
- 512MB de memoria RAM (sin swap adicional)
- 1.5 núcleos de CPU
- Máximo 200 PIDs (procesos)
- 1024 archivos abiertos (soft) / 2048 (hard)
- 100 procesos de usuario (soft) / 200 (hard)
Aislamiento de red
Para contenedores que no necesitan comunicarse con otros contenedores, usa el modo de red none o crea redes bridge aisladas:
# Sin red (ideal para procesamiento batch)
podman run -d --network=none myapp:latest
# Red bridge personalizada (Docker)
docker network create --driver bridge \
--subnet=172.20.0.0/16 \
--opt com.docker.network.bridge.name=br-secure \
secure-net
docker run -d --network=secure-net myapp:latest
Prevención de escalada de privilegios
La flag --security-opt=no-new-privileges previene que procesos dentro del contenedor ganen nuevos privilegios a través de setuid, setgid o file capabilities. Es una línea que añade mucha seguridad:
docker run -d \
--security-opt=no-new-privileges:true \
--cap-drop ALL \
myapp:latest
Ejemplo completo de hardening runtime
Vamos a combinar todas las técnicas anteriores en un solo comando. Esto es lo que debería verse un contenedor bien asegurado:
podman run -d \
--name myapp-secure \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid,size=100m \
--security-opt=no-new-privileges:true \
--memory=512m \
--memory-swap=512m \
--cpus=1.5 \
--pids-limit=200 \
--ulimit nofile=1024:2048 \
--health-cmd="curl -f http://localhost:8080/health || exit 1" \
--health-interval=30s \
--health-timeout=10s \
--health-retries=3 \
-p 8080:8080 \
myapp:1.2.3
Perfiles Seccomp: Restricción de syscalls del kernel
Seccomp (Secure Computing Mode) es un mecanismo del kernel Linux que permite filtrar las system calls que un proceso puede realizar. Honestamente, es una de las defensas más efectivas contra exploits de kernel, y no recibe la atención que merece.
¿Qué hace seccomp exactamente?
El kernel Linux expone más de 300 syscalls. La mayoría de aplicaciones necesitan solo un pequeño subconjunto. El perfil seccomp por defecto de Docker y Podman bloquea aproximadamente 44 syscalls consideradas peligrosas, incluyendo:
keyctl,add_key— Manipulación de claves del kernelbpf— Programas BPF que podrían usarse para evasiónclock_settime— Modificar el reloj del sistemamount,umount— Operaciones de montajereboot— Reiniciar el sistemaswapon,swapoff— Gestión de swapptrace— Debugging de procesos (puede usarse para evasión)
Verificar que seccomp está activo
Algo que muchos administradores pasan por alto: verificar que seccomp realmente esté funcionando. Es fácil:
# Verificar modo seccomp (2 = filtrado activo)
docker run --rm alpine grep Seccomp /proc/1/status
# Salida: Seccomp: 2
Creación de perfiles seccomp personalizados
Para aplicaciones críticas, deberías crear un perfil que permita solo las syscalls que tu aplicación realmente usa. Primero, audita las syscalls con strace:
# Ejecutar la aplicación con strace para identificar syscalls
strace -c -f -S name ./myapp 2>&1 | tail -40
# Alternativamente, dentro de un contenedor
podman run --rm --cap-add SYS_PTRACE myapp:latest \
strace -c -f -o /dev/stderr ./myapp
Con esa información, crea un perfil seccomp JSON personalizado. Aquí tienes un ejemplo de perfil restrictivo para un servidor web simple:
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_AARCH64"
],
"syscalls": [
{
"names": [
"read", "write", "open", "openat", "close",
"stat", "fstat", "lstat", "poll", "lseek",
"mmap", "mprotect", "munmap", "brk",
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
"ioctl", "access", "pipe", "select",
"socket", "connect", "accept", "sendto",
"recvfrom", "bind", "listen",
"getsockname", "getpeername", "setsockopt",
"clone", "execve", "exit", "exit_group",
"wait4", "uname", "fcntl", "getcwd",
"gettimeofday", "getuid", "getgid",
"geteuid", "getegid", "epoll_create",
"epoll_ctl", "epoll_wait", "epoll_create1",
"futex", "set_tid_address",
"clock_gettime", "clock_getres",
"getrandom", "prlimit64",
"newfstatat", "set_robust_list"
],
"action": "SCMP_ACT_ALLOW"
}
]
}
Guarda este perfil como custom-seccomp.json y úsalo al ejecutar el contenedor:
# Docker
docker run --rm \
--security-opt seccomp=/path/to/custom-seccomp.json \
myapp:latest
# Podman
podman run --rm \
--security-opt seccomp=/path/to/custom-seccomp.json \
myapp:latest
AppArmor y SELinux para contenedores
Mientras que seccomp restringe syscalls, AppArmor y SELinux proporcionan Mandatory Access Control (MAC) — restringen el acceso a archivos, capabilities y recursos de red basándose en políticas del sistema. Son capas de defensa complementarias.
Perfiles AppArmor para contenedores Docker
Docker incluye un perfil AppArmor por defecto llamado docker-default. Para aplicaciones específicas, puedes (y deberías) crear perfiles personalizados:
# /etc/apparmor.d/docker-nginx-custom
#include <tunables/global>
profile docker-nginx-custom flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Permitir acceso de red
network inet tcp,
network inet udp,
# Denegar acceso a archivos sensibles del host
deny /proc/sys/kernel/** wklx,
deny /sys/kernel/security/** wklx,
deny /proc/kcore rwklx,
# Permitir lectura de configuración de nginx
/etc/nginx/** r,
/var/log/nginx/** w,
/var/cache/nginx/** rw,
/usr/share/nginx/** r,
# Permitir ejecución del binario nginx
/usr/sbin/nginx ix,
# Capabilities mínimas
capability net_bind_service,
capability setuid,
capability setgid,
# Denegar capabilities peligrosas
deny capability sys_admin,
deny capability sys_module,
deny capability sys_rawio,
}
Cargar y aplicar el perfil:
# Cargar el perfil
sudo apparmor_parser -r /etc/apparmor.d/docker-nginx-custom
# Usar el perfil con Docker
docker run -d \
--security-opt apparmor=docker-nginx-custom \
nginx:alpine
# Verificar que el perfil está activo
docker exec container_id cat /proc/1/attr/current
SELinux para contenedores Podman
SELinux proporciona contextos de seguridad que controlan el acceso a recursos. Podman asigna automáticamente el tipo container_t a los contenedores:
# Verificar el contexto SELinux de un contenedor
podman run --rm alpine cat /proc/self/attr/current
# Salida: system_u:system_r:container_t:s0:c123,c456
Para volúmenes, necesitas etiquetar correctamente los archivos del host. Podman hace esto automáticamente con la opción :Z:
# Etiquetado privado (recomendado)
podman run -v /host/data:/container/data:Z myapp:latest
# Etiquetado compartido entre contenedores
podman run -v /host/shared:/container/shared:z myapp:latest
Creación de políticas SELinux personalizadas
Para casos avanzados, puedes crear módulos SELinux personalizados. Primero identifica las denegaciones con audit2allow:
# Ejecutar el contenedor y observar denegaciones
sudo ausearch -m avc -ts recent | grep container_t
# Generar un módulo de política
sudo ausearch -m avc -ts recent | grep container_t | audit2allow -M myapp_container
# Instalar el módulo
sudo semodule -i myapp_container.pp
Eso sí, ten cuidado: permitir todo indiscriminadamente puede debilitar la seguridad más de lo que imaginas. Revisa cada regla antes de aplicarla.
Escaneo de vulnerabilidades con Trivy y Grype
El escaneo de vulnerabilidades debe ser parte integral de tu pipeline. No es algo "nice to have", es una necesidad. Las herramientas modernas detectan vulnerabilidades en dependencias de SO, bibliotecas de lenguajes, y generan SBOMs (Software Bill of Materials).
Trivy: Escaneo completo de imágenes
Trivy es una herramienta open-source de Aqua Security que escanea imágenes, filesystems y repositorios Git. Es rápida, fácil de usar y se integra bien con casi todo:
# Instalación
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
# Escaneo básico de imagen
trivy image nginx:alpine
# Escaneo solo de vulnerabilidades HIGH y CRITICAL
trivy image --severity HIGH,CRITICAL nginx:alpine
# Fallar si se encuentran vulnerabilidades críticas (para CI/CD)
trivy image --severity CRITICAL --exit-code 1 myapp:latest
# Escaneo con formato JSON para procesamiento automático
trivy image --format json --output results.json myapp:latest
# Escaneo de filesystem local
trivy fs /path/to/project
# Escaneo de Dockerfile para misconfiguraciones
trivy config Dockerfile
Grype: Alternativa de Anchore
Grype, desarrollado por Anchore, es otra excelente opción con un enfoque particular en la integración con SBOMs:
# Instalación
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
# Escaneo de imagen
grype myapp:latest
# Fallar si se encuentran vulnerabilidades altas
grype myapp:latest --fail-on high
# Salida en formato JSON
grype myapp:latest -o json > vulnerabilities.json
# Escaneo de imagen en registro remoto
grype registry:docker.io/library/nginx:alpine
Generación de SBOM con Syft
Syft genera Software Bill of Materials en formatos estándar como SPDX y CycloneDX. Los SBOMs son esenciales para saber exactamente qué componentes contienen tus imágenes y poder reaccionar rápido cuando se publica una nueva CVE:
# Instalación
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generar SBOM en formato SPDX JSON
syft myapp:latest -o spdx-json > sbom.spdx.json
# Generar SBOM en formato CycloneDX
syft myapp:latest -o cyclonedx-json > sbom.cyclonedx.json
# Usar SBOM con Grype para escaneo rápido
syft myapp:latest -o json | grype
Integración en pipelines DevSecOps
La seguridad de contenedores debe estar integrada en cada etapa del pipeline de desarrollo, no añadida como una ocurrencia tardía. El concepto de «shift-left» significa mover la seguridad más temprano en el ciclo de desarrollo. Y funciona.
Pipeline CI/CD con GitHub Actions
Aquí tienes un ejemplo completo de un pipeline que integra escaneo de seguridad en GitHub Actions:
name: Container Security Pipeline
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Lint Dockerfile
uses: hadolint/[email protected]
with:
dockerfile: Dockerfile
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Upload Trivy results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myapp:${{ github.sha }}
format: spdx-json
output-file: sbom.spdx.json
Policy-as-Code con Open Policy Agent (OPA)
OPA permite definir políticas de seguridad como código usando el lenguaje Rego. Este es un enfoque que personalmente me parece muy potente, porque puedes versionar tus políticas de seguridad igual que tu código. Ejemplo de política que rechaza contenedores ejecutándose como root:
package container.admission
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.runAsNonRoot
msg := sprintf("El contenedor '%s' debe ejecutarse como usuario no-root", [container.name])
}
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
container.securityContext.privileged
msg := sprintf("El contenedor '%s' no puede ser privilegiado", [container.name])
}
deny[msg] {
input.request.kind.kind == "Pod"
container := input.request.object.spec.containers[_]
not container.securityContext.readOnlyRootFilesystem
msg := sprintf("El contenedor '%s' debe tener filesystem de solo lectura", [container.name])
}
Valida tus manifiestos de Kubernetes localmente con conftest:
# Validar un deployment
conftest test deployment.yaml -p policy/
# Integrar en CI con fallo en warnings
conftest test deployment.yaml -p policy/ --fail-on-warn
Monitoreo y detección de amenazas en runtime con Falco
Falco es un proyecto graduado de la CNCF que proporciona detección de amenazas en runtime para contenedores. Funciona interceptando llamadas al kernel para detectar comportamientos anómalos en tiempo real. Piénsalo como un sistema de alarma para tus contenedores.
Instalación y configuración de Falco
# Instalación en sistemas Debian/Ubuntu
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | \
sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | \
sudo tee /etc/apt/sources.list.d/falcosecurity.list
sudo apt-get update && sudo apt-get install -y falco
# Instalación en sistemas Red Hat/Fedora
sudo rpm --import https://falco.org/repo/falcosecurity-packages.asc
sudo dnf install -y falco
# Habilitar e iniciar
sudo systemctl enable --now falco
Reglas personalizadas de detección para contenedores
La magia de Falco está en sus reglas. Personalízalas en /etc/falco/falco_rules.local.yaml para detectar amenazas específicas de tu entorno:
- rule: Ejecución de binario no original en contenedor
desc: Detecta ejecución de binarios que no estaban en la imagen original
condition: >
spawned_process and
container and
proc.is_exe_upper_layer=true
output: >
Binario no original ejecutado en contenedor
(user=%user.name container=%container.name
image=%container.image.repository exe=%proc.exe cmd=%proc.cmdline)
priority: WARNING
tags: [container, drift]
- rule: Shell interactivo en contenedor de producción
desc: Detecta apertura de shells interactivos en contenedores
condition: >
spawned_process and
container and
proc.name in (bash, sh, zsh, ash) and
proc.tty != 0
output: >
Shell interactivo detectado en contenedor
(user=%user.name container=%container.name
image=%container.image.repository shell=%proc.name)
priority: HIGH
tags: [container, shell]
- rule: Acceso a credenciales en contenedor
desc: Detecta lectura de archivos sensibles
condition: >
open_read and
container and
fd.name pmatch (/etc/shadow, /etc/sudoers, /root/.ssh/*)
output: >
Acceso a archivo de credenciales desde contenedor
(user=%user.name container=%container.name
file=%fd.name image=%container.image.repository)
priority: CRITICAL
tags: [container, credential_access]
Integración de alertas
Configura Falco para enviar alertas a tu sistema de monitoreo en /etc/falco/falco.yaml:
json_output: true
json_include_output_property: true
file_output:
enabled: true
filename: /var/log/falco/events.json
syslog_output:
enabled: true
http_output:
enabled: true
url: "https://tu-siem.ejemplo.com/api/falco-alerts"
Cumplimiento normativo y auditoría
Para organizaciones que necesitan cumplimiento normativo (y cada vez son más), existen frameworks y herramientas específicas para contenedores.
CIS Benchmarks y Docker Bench Security
Docker Bench Security es una herramienta automatizada que verifica el cumplimiento con el CIS Docker Benchmark. Cubre más de 100 controles en áreas como configuración del host, del daemon, de archivos, imágenes, runtime y operaciones:
# Ejecutar Docker Bench Security
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /var/lib:/var/lib \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /usr/lib/systemd:/usr/lib/systemd \
-v /etc:/etc --label docker_bench_security \
docker/docker-bench-security
La salida categoriza hallazgos en PASS, WARN, INFO y NOTE, lo que te permite priorizar las remediaciones fácilmente.
NIST SP 800-190: Guía de seguridad para contenedores
La guía NIST SP 800-190 identifica cinco áreas principales de riesgo: imagen, registro, orquestador, contenedor y host. Para cumplir con esta guía, necesitas implementar:
- Escaneo de imágenes antes de cada despliegue
- Content Trust para verificar la autenticidad de las imágenes
- Seguridad runtime con seccomp, AppArmor/SELinux y capabilities mínimas
- Segmentación de red con políticas de red estrictas
- Logging centralizado de todos los eventos de contenedores
- Actualizaciones regulares de host, runtime y orquestador
Script de auditoría para Podman
Para auditar contenedores Podman en ejecución, este script verifica las configuraciones de seguridad más importantes. Es un buen punto de partida para tus auditorías:
#!/bin/bash
# audit-containers.sh - Auditoría de seguridad de contenedores Podman
echo "=== Auditoría de Seguridad de Contenedores ==="
echo "Fecha: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
for id in $(podman ps -q); do
name=$(podman inspect $id --format '{{.Name}}')
image=$(podman inspect $id --format '{{.Config.Image}}')
echo "--- Contenedor: $name ---"
echo "Imagen: $image"
echo "Capabilities: $(podman inspect $id --format '{{.EffectiveCaps}}')"
echo "Usuario: $(podman inspect $id --format '{{.Config.User}}')"
echo "Read-only FS: $(podman inspect $id --format '{{.HostConfig.ReadonlyRootfs}}')"
echo "Privilegiado: $(podman inspect $id --format '{{.HostConfig.Privileged}}')"
echo "No New Privs: $(podman inspect $id --format '{{.HostConfig.SecurityOpt}}')"
echo "Límite memoria: $(podman inspect $id --format '{{.HostConfig.Memory}}')"
echo "Límite PIDs: $(podman inspect $id --format '{{.HostConfig.PidsLimit}}')"
echo ""
done
Conclusión y checklist de hardening
La seguridad de contenedores es un proceso continuo que requiere atención en múltiples capas. En 2026, con las amenazas evolucionando constantemente y vulnerabilidades críticas como las recientes en runc, no podemos darnos el lujo de mantener configuraciones por defecto inseguras.
Aquí tienes tu checklist definitiva de hardening para contenedores Docker y Podman. Guárdala, imprímela, ponla en la pared — lo que sea necesario para no olvidar nada.
Imagen y desarrollo
- Usar multi-stage builds para minimizar el tamaño final de la imagen
- Basar imágenes en distroless, Alpine o imágenes mínimas verificadas
- Especificar versiones exactas y digests SHA256 de imágenes base
- Ejecutar aplicaciones como usuario no-root dentro del contenedor
- No incluir secretos, credenciales o claves en imágenes
- Escanear imágenes con Trivy o Grype antes de push al registro
- Generar y mantener SBOMs con Syft
Runtime
- Ejecutar contenedores en modo rootless siempre que sea posible
- Usar
--cap-drop ALLy añadir solo capabilities necesarias - Habilitar filesystem de solo lectura con
--read-only - Configurar límites de recursos (CPU, memoria, PIDs)
- Usar
--security-opt=no-new-privileges - Aplicar perfiles seccomp personalizados
- Habilitar AppArmor o SELinux con perfiles apropiados
Infraestructura y monitoreo
- Mantener kernel, runc y motor de contenedores actualizados
- Implementar detección de amenazas runtime con Falco
- Configurar logging centralizado de eventos de contenedores
- Ejecutar Docker Bench Security regularmente
- Auditar cumplimiento con NIST SP 800-190 y CIS Benchmarks
Pipeline DevSecOps
- Integrar escaneo de vulnerabilidades en CI/CD
- Implementar policy-as-code con OPA o Kyverno
- Automatizar generación de SBOMs en cada build
- Establecer gates de calidad que bloqueen deployments con CVEs críticos
Recuerda: un contenedor no es una frontera de seguridad absoluta. Es una herramienta de aislamiento que debe combinarse con defensa en profundidad — kernel hardening, MAC (AppArmor/SELinux), capabilities mínimas, seccomp, network policies y monitoreo runtime. Solo con este enfoque holístico puedes tener confianza real en la seguridad de tu infraestructura containerizada.