11. Seguridad en Docker

Si bien los contenedores proporcionan un gran nivel de aislamiento, no son una solución mágica de seguridad. Es crucial seguir buenas prácticas para proteger tus aplicaciones y el host subyacente. La seguridad en Docker es una responsabilidad compartida que abarca desde la imagen base hasta la ejecución del contenedor.

Minimizar la superficie de ataque con imágenes pequeñas

Cada programa, librería o dependencia en tu imagen es una potencial puerta de entrada para un atacante. Si una de esas dependencias tiene una vulnerabilidad, tu contenedor es vulnerable. Por lo tanto, una de las reglas más importantes es: incluye solo lo estrictamente necesario en tu imagen.

Una forma efectiva de lograrlo es usar imágenes base minimalistas:

  • Alpine: Basada en Alpine Linux, es increíblemente ligera (alrededor de 5MB). No incluye herramientas comunes como `bash` o `curl` por defecto, forzándote a instalar solo lo que necesitas.
  • Slim: Muchas imágenes oficiales (como la de Python) ofrecen una variante `slim`. No es tan pequeña como Alpine, pero elimina muchos paquetes y herramientas innecesarias que sí vienen en la versión completa.
  • Distroless: Impulsadas por Google, estas imágenes son aún más extremas: contienen únicamente tu aplicación y sus dependencias, sin gestores de paquetes, shells ni ninguna otra herramienta.

Al reducir el número de componentes, reduces la "superficie de ataque" y la probabilidad de que tu imagen contenga software vulnerable.

Principio de menor privilegio: Contenedores sin root

Por defecto, los procesos dentro de un contenedor se ejecutan como usuario `root`. Esto es un riesgo de seguridad significativo. Si un atacante logra explotar una vulnerabilidad en tu aplicación y obtiene acceso al contenedor, tendrá privilegios de `root` dentro de él. Aunque el contenedor está aislado, esto le da un poder considerable y aumenta el riesgo de un "escape" al host.

La solución es aplicar el principio de menor privilegio y ejecutar tus procesos como un usuario sin privilegios. Esto se hace en el Dockerfile:

FROM python:3.9-slim

WORKDIR /app

# Crear un usuario y grupo sin privilegios
RUN groupadd -r appgroup && useradd -r -g appgroup appuser

# Copiar archivos y establecer permisos
COPY --chown=appuser:appgroup . /app

# Cambiar al usuario sin privilegios
USER appuser

# Instalar dependencias
RUN pip install -r requirements.txt

EXPOSE 5000
CMD ["flask", "run"]

En este ejemplo, creamos un usuario `appuser`, le damos la propiedad de los archivos de la aplicación y luego usamos la instrucción `USER` para asegurarnos de que el resto de las instrucciones y el proceso principal se ejecuten como ese usuario no privilegiado.

Manejo de secretos y credenciales

Nunca, bajo ninguna circunstancia, guardes contraseñas, tokens de API u otros secretos directamente en tu Dockerfile o en el código fuente. Hacerlo los expone a cualquiera que tenga acceso a la imagen o al repositorio de código.

Las formas correctas de manejar secretos son:

  • Variables de entorno: Es el método más común. Los secretos se inyectan en el contenedor en tiempo de ejecución a través de variables de entorno. Con Docker Compose, esto se hace en la sección `environment` o, mejor aún, usando un archivo `.env` que no se sube a Git.
  • Docker Secrets: Es la solución nativa y más segura para entornos de orquestación como Docker Swarm o Kubernetes. Los secretos se montan en el contenedor como archivos en una ubicación específica (/run/secrets/), residen en memoria y no se escriben en disco dentro del contenedor.

Ejemplo con Docker Compose y un archivo `.env`:

# docker-compose.yml
services:
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD} # Lee la variable desde el archivo .env
# .env (este archivo NO se sube a Git)
DB_PASSWORD=mi-super-secreto-password

Escaneo de vulnerabilidades en imágenes

Incluso siguiendo todas las buenas prácticas, las dependencias de tu imagen pueden tener vulnerabilidades conocidas (CVEs). Es vital escanear tus imágenes regularmente para detectarlas.

Docker proporciona el comando `docker scan`, que utiliza el motor de Snyk para analizar tu imagen y reportar vulnerabilidades.

docker scan mi-app-flask:1.0

El comando te mostrará una lista de las vulnerabilidades encontradas, su severidad (baja, media, alta, crítica) y a menudo te sugerirá cómo solucionarlas (por ejemplo, actualizando una librería o usando una imagen base más reciente).

Integrar el escaneo de imágenes en tu pipeline de CI/CD es una práctica de seguridad moderna y fundamental (DevSecOps).