Seguridad en Docker y Kubernetes: cómo reforzar tus contenedores
Los contenedores cambiaron la forma en que desplegamos software, pero muchos equipos los publican con acceso root, imágenes obsoletas y secretos expuestos. Aquí tienes la guía completa de refuerzo.
Tabla de contenidos
- El error conceptual sobre la seguridad de los contenedores
- Refuerzo de las imágenes de Docker
- Usa imágenes base mínimas
- Compilaciones multietapa
- Nunca ejecutes como root
- Analiza las imágenes en busca de vulnerabilidades
- Seguridad del entorno de ejecución de Docker
- Descarta capacidades
- Seccomp y AppArmor
- Nunca uses --privileged
- Refuerzo de la seguridad de Kubernetes
- Estándares de seguridad de pods
- RBAC: mínimo privilegio para las cuentas de servicio
- Gestión de secretos
- Políticas de red
- Postura de seguridad continua
El error conceptual sobre la seguridad de los contenedores
Un error conceptual común es creer que los contenedores proporcionan un aislamiento fuerte, análogo al de las máquinas virtuales. No es así. Los contenedores comparten el kernel del host: una vulnerabilidad de escape de contenedor que logre ejecutar código en el kernel del host concede acceso a todos los contenedores de ese host, además del propio host. La seguridad de los contenedores consiste en reducir el radio de impacto de un contenedor comprometido, no en asumir que la frontera es impenetrable.
La superficie de ataque abarca la imagen del contenedor, la configuración del entorno de ejecución, la plataforma de orquestación, la red y la capa de gestión de secretos. El refuerzo debe abordarlas todas.
Refuerzo de las imágenes de Docker
Usa imágenes base mínimas
Cada paquete de una imagen base es una vulnerabilidad potencial. Prefiere las imágenes distroless (imágenes de Google sin shell, gestor de paquetes ni herramientas del sistema operativo) o Alpine Linux (musl libc, apk, ~5 MB):
# Instead of ubuntu:22.04
FROM gcr.io/distroless/java21-debian12
A las imágenes distroless no se puede acceder de forma interactiva mediante un shell, lo que limita significativamente las opciones del atacante si el contenedor se ve comprometido.
Compilaciones multietapa
Las dependencias de compilación (compiladores, herramientas de compilación, frameworks de pruebas) nunca deberían estar en la imagen de producción:
FROM golang:1.22 AS builder
WORKDIR /app
COPY . .
RUN go build -o server .
FROM gcr.io/distroless/static-debian12
COPY --from=builder /app/server /server
ENTRYPOINT ["/server"]
La imagen final contiene únicamente el binario compilado y sus dependencias de ejecución.
Nunca ejecutes como root
De forma predeterminada, los procesos de los contenedores se ejecutan como root (UID 0). Si se produce un escape de contenedor, el atacante tiene root en el host. Crea siempre un usuario sin privilegios de root:
RUN useradd -u 1001 -r appuser
USER appuser
Combínalo con un sistema de archivos --read-only y --tmpfs /tmp para las ubicaciones que necesiten escritura.
Analiza las imágenes en busca de vulnerabilidades
Integra el análisis de imágenes en tu pipeline de CI/CD:
# Trivy — fast, comprehensive vulnerability scanner
trivy image myapp:latest
# Grype — alternative with SBOM support
grype myapp:latest
Haz que la compilación falle si se encuentran CVE altos o críticos. Recompila y actualiza las imágenes base de forma periódica, no solo cuando cambies el código de la aplicación.
Seguridad del entorno de ejecución de Docker
Descarta capacidades
Las capacidades de Linux dividen el modelo de privilegios de todo o nada de root en unidades discretas. Descarta todas las capacidades y vuelve a añadir solo las que la aplicación necesite:
docker run --cap-drop ALL --cap-add NET_BIND_SERVICE myapp
Seccomp y AppArmor
El perfil de seccomp predeterminado de Docker bloquea unas 44 llamadas al sistema peligrosas. Úsalo de forma explícita y considera un perfil personalizado para cargas de trabajo sensibles. Los perfiles de AppArmor o SELinux añaden control de acceso obligatorio sobre el kernel:
docker run --security-opt seccomp=profile.json myapp
docker run --security-opt apparmor=docker-default myapp
Nunca uses --privileged
El indicador --privileged otorga al contenedor un acceso casi completo al host, desactivando todo el aislamiento de seguridad. En ocasiones es necesario para casos de uso concretos (como ejecutar Docker dentro de Docker), pero debe evitarse por completo en producción y tratarse como una señal de alarma inmediata durante las revisiones de seguridad.
Refuerzo de la seguridad de Kubernetes
Estándares de seguridad de pods
Kubernetes aplica políticas de seguridad mediante los Pod Security Standards (que sustituyen a la obsoleta PodSecurityPolicy):
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
El perfil restricted impone: sin contenedores privilegiados, sin usuario root, sin escalada de privilegios, perfil de seccomp obligatorio y todas las capacidades descartadas.
RBAC: mínimo privilegio para las cuentas de servicio
Cada pod se ejecuta con una cuenta de servicio. De forma predeterminada, esa cuenta de servicio puede acceder a la API de Kubernetes. Aplica el mínimo privilegio:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-role
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
Audita con kubectl auth can-i --list --as=system:serviceaccount:default:myapp. Desactiva el montaje automático de los tokens de cuenta de servicio cuando no sea necesario: automountServiceAccountToken: false.
Gestión de secretos
Los Secrets de Kubernetes están codificados en Base64, no cifrados, de forma predeterminada. Usa:
- Cifrado en reposo: activa
EncryptionConfigurationen el servidor de la API para cifrar los secretos en etcd - Gestores de secretos externos: AWS Secrets Manager, HashiCorp Vault o GCP Secret Manager a través del External Secrets Operator
- Nunca pongas secretos en ConfigMaps, en variables de entorno de las especificaciones de Deployment (visibles con
kubectl describe) ni en las imágenes de los contenedores
Políticas de red
De forma predeterminada, todos los pods de un clúster de Kubernetes pueden comunicarse entre sí. Implementa NetworkPolicy para aplicar microsegmentación:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {}
policyTypes: [Ingress]
Después, añade reglas de permiso explícitas solo para las rutas de comunicación necesarias. Usa un plugin de CNI que aplique NetworkPolicy (Calico, Cilium, Weave).
Postura de seguridad continua
- Usa Falco (herramienta de seguridad en tiempo de ejecución de la CNCF) para detectar comportamientos sospechosos durante la ejecución: aperturas inesperadas de shells, conexiones salientes a nuevas IP, intentos de escalada de privilegios
- Analiza las configuraciones del clúster con kube-bench (CIS Kubernetes Benchmark) y el operador de Trivy
- Activa el registro de auditoría en el servidor de la API de Kubernetes y envía los logs a un SIEM
- Rota con regularidad los digests de las imágenes y automatiza las actualizaciones de dependencias con Renovate o Dependabot