Seguridad de Contenedores en Linux: Guía de Hardening con Docker y Podman (2026)

Aprende a asegurar contenedores Docker y Podman en Linux con técnicas avanzadas de hardening: modo rootless, capabilities mínimas, seccomp, SELinux, escaneo con Trivy y Grype, detección de amenazas con Falco y cumplimiento normativo CIS/NIST.

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 sistema
  • CAP_SYS_MODULE — Carga de módulos del kernel
  • CAP_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 glibc
  • alpine:3.19 — Imagen mínima (5MB) con musl libc y apk
  • chainguard/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 kernel
  • bpf — Programas BPF que podrían usarse para evasión
  • clock_settime — Modificar el reloj del sistema
  • mount, umount — Operaciones de montaje
  • reboot — Reiniciar el sistema
  • swapon, swapoff — Gestión de swap
  • ptrace — 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:

  1. Escaneo de imágenes antes de cada despliegue
  2. Content Trust para verificar la autenticidad de las imágenes
  3. Seguridad runtime con seccomp, AppArmor/SELinux y capabilities mínimas
  4. Segmentación de red con políticas de red estrictas
  5. Logging centralizado de todos los eventos de contenedores
  6. 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 ALL y 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.

Sobre el Autor Editorial Team

Our team of expert writers and editors.