Parte: 11 — DevSecOps y seguridad del SDLC · Fuente: Securing DevOps (Julien Vehent) y NIST SP 800-190 (Application Container Security Guide) ⏱️ Duración estimada: 120 min · Nivel: Avanzado
Construir imágenes de contenedor seguras dentro del pipeline: mínimas (distroless/multi-stage), sin ejecutar como root, escaneadas por vulnerabilidades y firmadas para garantizar su integridad. Una imagen es un artefacto que llega a producción; si está inflada, vulnerable o no verificada, es un vector directo de ataque. Usaremos Trivy para escanear, hadolint para el Dockerfile, y cosign para firmar.
Al finalizar, el alumno podrá:
| # | Tema | Por qué importa |
|---|---|---|
| 1 | Superficie de ataque de una imagen | Menos paquetes = menos CVE y menos exploits |
| 2 | Multi-stage builds y distroless | Imágenes mínimas de producción |
| 3 | Usuario no-root y capabilities | Limitar el daño de un contenedor comprometido |
| 4 | Escaneo con Trivy | Detectar CVE de OS y librerías |
| 5 | hadolint | Buenas prácticas del Dockerfile |
| 6 | Firma con cosign | Integridad y procedencia del artefacto |
| 7 | Gate de admisión | Solo imágenes firmadas y limpias a producción |
Una imagen es un sistema de archivos por capas más metadatos de ejecución. Reducirla elimina paquetes y superficie, pero «mínima» no significa automáticamente segura: una aplicación vulnerable sigue siéndolo en distroless, y una imagen sin shell puede ejecutarse como root o recibir secretos inseguros. El pipeline debe controlar el origen de la base, el contenido construido, la identidad del artefacto y la configuración con que se ejecutará.
El diagrama enseña una cadena de identidad. La etiqueta app:1.0 es una referencia humana que puede apuntar a otro contenido; el digest identifica bytes concretos. La firma debe cubrir ese digest y la admisión debe verificar firma, emisor y política antes de ejecutar. Firmar no afirma que no existan vulnerabilidades: afirma quién autorizó un artefacto y permite vincularlo con procedencia.
Multi-stage separa herramientas de compilación del runtime, pero no borra automáticamente secretos copiados en una etapa o incluidos en el contexto. Se usan montajes de secretos del builder, .dockerignore y contextos mínimos; después se inspecciona el historial. La base se selecciona por necesidad, mantenimiento y compatibilidad, se fija por digest para reproducibilidad y se actualiza mediante un proceso controlado. Fijarla eternamente también conserva vulnerabilidades, por lo que bots o tareas de actualización proponen nuevos digests con pruebas.
El escaneo debe observar la imagen final y su SBOM. Un CVE en el stage de build importa de manera distinta a uno distribuido, aunque el constructor también puede ser atacado durante el build. Los resultados se triagean por alcance, exposición y política, como en la clase 240.
El usuario no-root, sistema de archivos de solo lectura, capacidades Linux mínimas, seccomp, límites de recursos y ausencia de sockets privilegiados reducen el impacto. No constituyen una frontera equivalente a una VM en todos los escenarios: los contenedores comparten kernel. Las excepciones —escritura temporal, puerto o capacidad— se conceden en un punto específico, no desactivando todo el control.
Una imagen distroless de 30 MB pasa el escáner de paquetes, pero ejecuta como UID 0 y monta el socket de Docker para lanzar tareas. Un compromiso de la aplicación puede controlar el daemon del host. El equipo elimina el socket mediante una API de trabajos con permisos limitados, declara usuario no-root, fija la base, genera SBOM y verifica firma en admisión. El tamaño era una señal de mantenimiento; nunca fue una garantía de aislamiento.
| Término | Definición útil |
|---|---|
| Digest | Identificador criptográfico del contenido exacto de una imagen. |
| Multi-stage build | Construcción con etapas separadas para no distribuir todas las herramientas. |
| Distroless | Imagen de runtime sin distribución de propósito general; no implica invulnerabilidad. |
| Admisión | Decisión previa a ejecutar basada en identidad y política del artefacto. |
| Capacidad Linux | Privilegio granular que puede retirarse del proceso. |
El alumno domina la clase cuando puede seguir una imagen desde su base hasta runtime, demostrar su identidad por digest, explicar qué cubren escaneo y firma, evitar secretos en capas y justificar controles de ejecución sin confundir tamaño con seguridad.
# Escanear una imagen:
trivy image --severity HIGH,CRITICAL miapp:1.0
# Lintar un Dockerfile:
docker run --rm -i hadolint/hadolint < Dockerfile
# Firmar (keyless con OIDC):
cosign sign miregistry/miapp@sha256:<digest>
🧪 Laboratorio ejecutable del programa:
devsecops-pipeline— son las capas 4 y 5 del lab:hadolintsobre el Dockerfile ytrivysobre el sistema operativo base.
docker run --rm -i hadolint/hadolint < Dockerfile
# build stage
FROM golang:1.22 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app .
# runtime stage
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
trivy image --severity HIGH,CRITICAL miapp:seguro
Observa la caída de CVE al usar distroless. 4. Firma la imagen con cosign (keyless):
cosign sign --yes miregistry/miapp@sha256:<digest>
cosign verify miregistry/miapp@sha256:<digest> \
--certificate-identity-regexp '.*' \
--certificate-oidc-issuer-regexp '.*'
trivy image --format cyclonedx -o sbom.json miapp:seguro
Nota ética: escanear y endurecer imágenes propias es defensivo. No publiques imágenes con malware ni las uses para atacar; el laboratorio se hace con imágenes tuyas.
ubuntu por distroless y compara los CVE con Trivy.Construye un pipeline que produzca una imagen mínima, escaneada y firmada.
Criterio de aceptación: (a) la imagen final usa multi-stage y base mínima/distroless y corre como no-root; (b) hadolint no reporta errores; (c) Trivy no reporta CVE CRITICAL y el gate falla si aparecen; (d) la imagen se firma con cosign y la firma se verifica; y (e) se genera y publica un SBOM de la imagen.
| Síntoma / mensaje | Causa y cómo arreglar |
|---|---|
| Imagen de 1.2 GB con cientos de CVE | Base pesada y build en una etapa. Usa multi-stage + distroless/alpine. |
| El contenedor corre como root | No definiste USER. Añade un usuario no-root o usa base :nonroot. |
cosign verify falla |
Identidad/issuer no coinciden. Ajusta --certificate-identity al del firmante real. |
| Trivy reporta CVE sin fix disponible | No hay parche aún. Documenta como riesgo aceptado/VEX y monitoriza. |
| La imagen firmada se puede sustituir por otra | Firmaste el tag, no el digest. Firma y despliega por digest inmutable. |
❓ ¿Distroless o Alpine? Distroless minimiza al máximo (sin shell, ideal para producción y para reducir superficie). Alpine es más pequeña que Debian y conserva shell/apk, útil para depurar. Elige según necesidad de troubleshooting vs mínima superficie.
❓ ¿Firmar la imagen impide que sea vulnerable? No. La firma garantiza integridad y procedencia, no ausencia de CVE. Necesitas escanear además de firmar.
❓ ¿Debo escanear en build o en el registry? Ambos. En build para bloquear temprano; en el registry de forma continua porque aparecen CVE nuevos sobre imágenes ya publicadas.
❓ ¿Keyless de cosign es seguro sin gestionar claves? Sí: usa identidad OIDC efímera y un transparency log público (Rekor). Elimina la gestión de claves de larga vida, aunque puedes usar claves propias si lo prefieres.
Clase 242 — Seguridad en pipelines CI/CD