Buenas Prácticas de Creación de Imágenes de Contenedores
Introducción
La creación de imágenes de contenedores seguras es fundamental para garantizar la seguridad y estabilidad de las aplicaciones desplegadas en entornos de contenedores. Las imágenes contienen todo lo necesario para ejecutar una aplicación, incluyendo el código, las dependencias y las configuraciones. Sin embargo, una imagen mal construida puede introducir vulnerabilidades que comprometan la seguridad del entorno de producción. Por ello, seguir buenas prácticas en la construcción de imágenes de contenedores es crucial para minimizar riesgos y asegurar aplicaciones seguras y fiables desde el inicio.
Buenas Prácticas de Creación de Imágenes
Uso de Imágenes Oficiales y Verificadas
Utilizar imágenes oficiales y verificadas reduce el riesgo de incluir software malicioso o con vulnerabilidades. Las imágenes oficiales son mantenidas por los desarrolladores de las aplicaciones y se actualizan regularmente para corregir problemas de seguridad. Estas imágenes suelen pasar por procesos rigurosos de validación y control de calidad.
Guía Práctica de Laboratorio
- Buscar Imágenes Oficiales: Visita Docker Hub o un registro de contenedores confiable. Busca imágenes oficiales marcadas con una insignia de verificación.
- Ejemplo: Usar una Imagen Oficial de MySQL
docker pull mysql:latest - Verificar la Integridad de la Imagen:
Habilita Docker Content Trust para asegurar la integridad de la imagen.
export DOCKER_CONTENT_TRUST=1 docker pull mysql:latest - Verificar el resumen de las vulnerabilidades que se presentan en dicha imágen:
docker scout quickview mysql:latest - Visualizar el detalle de los CVES:
Con lo anterior, se puede verificar el estado de la imágen a utilizar como base para nuestros proyectos.
docker scout cves mysql:latest
Minimización de la Superficie de Ataque
Minimizar la superficie de ataque implica eliminar componentes innecesarios de la imagen. Cuantos menos componentes tenga una imagen, menor será la probabilidad de que alguno de ellos tenga una vulnerabilidad explotable. Usar imágenes base minimalistas y solo incluir lo esencial ayuda a reducir los posibles vectores de ataque.
Guía Práctica de Laboratorio
- Crear una Imagen Minimalista:
Usa una imagen base ligera como alpine en lugar de una imagen completa como ubuntu, crea un archivo llamado Dockerfile con el siguiente contenido:
# Dockerfile FROM alpine:latest RUN apk add --no-cache python3 py3-pip COPY . /app WORKDIR /app CMD ["python3", "app.py"] - Construir y Ejecutar la Imagen:
docker build -t myapp:latest .docker run -d --name myapp myapp:latest - Limpia el ambiente
docker stop myappdocker rm myapprm -f Dockerfile
Eliminación de Datos Sensibles y Herramientas Innecesarias
Eliminar datos sensibles y herramientas innecesarias de las imágenes de contenedores es crucial para proteger información confidencial y reducir la cantidad de herramientas que podrían ser utilizadas por atacantes. Durante el proceso de construcción de la imagen, es común incluir herramientas y datos temporales que, si no se eliminan, pueden representar riesgos de seguridad.
Guía Práctica de Laboratorio
- Eliminar Herramientas y Archivos Innecesarios:
Añade una etapa de limpieza al Dockerfile para eliminar herramientas utilizadas solo durante la construcción. Crea un archivo llamado Dockerfile con el siguiente contenido:
# Dockerfile FROM python:3.9-slim as builder WORKDIR /app COPY requirements.txt . RUN pip install --user -r requirements.txt FROM python:3.9-slim WORKDIR /app COPY --from=builder /root/.local /root/.local ENV PATH=/root/.local/bin:$PATH COPY . /app CMD ["python", "app.py"] - Crea un archivo de texto llamado requirements.txt
echo "Flask==2.0.3" > requirements.txt - Construir y Escanear la Imagen:
docker build -t mysecureapp:latest .docker scout quickview mysecureapp:latest - Limpia el ambiente
docker rmi mysecureapp:latestrm -f Dockerfile requirements.txt
Escaneo Regular de Imágenes en Busca de Vulnerabilidades
El escaneo regular de imágenes en busca de vulnerabilidades permite identificar y corregir problemas de seguridad antes de que lleguen a producción. Existen varias herramientas que pueden integrarse en el flujo de trabajo de CI/CD para automatizar este proceso. Mas adelante se realizará una práctica de laboratorio relacionado a este tema
Uso de Multietapas en Dockerfile
El uso de multietapas en Dockerfile permite construir imágenes más pequeñas y seguras al separar el proceso de construcción del runtime. Esto reduce el tamaño de la imagen final y elimina herramientas y dependencias innecesarias utilizadas solo durante la construcción. 1. Crear una imagen Multicapa. Crea un archivo llamado Dockerfile con el siguiente contenido:
# Dockerfile
FROM golang:1.16 as builder
WORKDIR /app
COPY . .
RUN go build -o myapp
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
module myapp
go 1.23
docker build -t mymultistageapp:latest .
docker run -d --name mymultistageapp mymultistageapp:latest
docker logs -f mymultistageapp
docker stop mymultistageapp ; docker rm mymultistageapp
docker rmi mymultistageapp:latest
rm -f Dockerfile go.mod main.go
Implementación de Políticas de Seguridad a Nivel de Contenedor
Implementar políticas de seguridad a nivel de contenedor, como restricciones de usuarios y capacidades, ayuda a reducir el impacto de posibles ataques y mejora la seguridad general del contenedor.
- Restringir el Usuario en Dockerfile:
Ejecutar el contenedor con un usuario no privilegiado. Crea un archivo llamado Dockerfile con el siguiente contenido:
# Dockerfile FROM node:14 WORKDIR /app COPY . . RUN npm install RUN useradd -m appuser USER appuser CMD ["node", "app.js"] - Crea un archivo llamado package.json con el siguiente contenido:
{ "name": "myapp", "version": "1.0.0", "description": "A simple Node.js app", "main": "app.js", "scripts": { "start": "node app.js" }, "author": "", "license": "ISC" } - Crea un archivo llamado app.js con el siguiente contenido:
const os = require('os'); const { execSync } = require('child_process'); const username = os.userInfo().username; const userId = execSync('id -u').toString().trim(); const groupId = execSync('id -g').toString().trim(); console.log('Hello, World!'); console.log(`Running as user: ${username}`); console.log(`User ID: ${userId}`); console.log(`Group ID: ${groupId}`); - Construir y Ejecutar la Imagen:
docker build -t userapp:latest .docker run -d --name userapp userapp:latest - Verifica la salida de los logs del contenedor anterior y deverías ver el mensaje "Hello, World!" y la información del ID del usuario.
docker logs -f userapp - Limpia el ambiente
docker stop userapp ; docker rm userappdocker rmi userapp:latestrm -f Dockerfile app.js package.json
Implementar Capabilities y Seccomp: Limitar las capacidades del contenedor y aplicar un perfil seccomp.
- Descargar el perfil seccomp por defecto
curl -o default.json https://raw.githubusercontent.com/docker/docker/master/profiles/seccomp/default.json - Ejecutar el contenedor con el perfil seccomp
docker run -it --name secureapp --cap-drop=ALL --security-opt seccomp=$(pwd)/default.json alpine sh - Una vez dentro del contenedor, instala el paquete libcap que contiene getpcaps.
apk updateapk add libcap - Usa getpcaps para verificar las capacidades del proceso.
El resultado 1: = indica que no hay capacidades adicionales, lo cual es correcto.
getpcaps 1 - Ejecuta una syscall restringida para verificar que el perfil seccomp está funcionando. Por ejemplo, intenta usar unshare:
Esto debería fallar y devolver un error como Operation not permitted.
unshare --mount - Ejecuta algunos comandos básicos para asegurarte de que el contenedor está funcionando correctamente bajo las restricciones de seguridad. Por ejemplo:
echo "Hello from Alpine!"exit -
Limpia el ambiente
docker stop secureapp ; docker rm secureapprm -f default.json -
--cap-drop=ALL: Esta opción elimina todas las capacidades de Linux del contenedor. Las capacidades son privilegios adicionales que los procesos en un contenedor pueden tener. Al eliminar todas las capacidades, se reduce la superficie de ataque y se limita lo que el contenedor puede hacer en el sistema anfitrión. Esto es una medida de seguridad importante.
-
--security-opt seccomp=default.json: Esta opción aplica un perfil de seccomp (secure computing mode) al contenedor. Seccomp es una herramienta de seguridad que restringe las llamadas al sistema (syscalls) que el contenedor puede realizar. El perfil default.json es un conjunto de reglas que especifica qué llamadas al sistema están permitidas o denegadas, lo que ayuda a limitar el comportamiento del contenedor y proteger el sistema anfitrión de posibles exploits.
Resumen
Implementar estas buenas prácticas en la creación de imágenes de contenedores ayuda a asegurar que las aplicaciones sean seguras desde la base. Usar imágenes oficiales y verificadas, minimizar la superficie de ataque, eliminar datos sensibles y herramientas innecesarias, realizar escaneos regulares de vulnerabilidades, usar multietapas en Dockerfile y aplicar políticas de seguridad a nivel de contenedor son pasos esenciales para construir imágenes de contenedores robustas y seguras. Siguiendo estas prácticas, podemos reducir significativamente los riesgos de seguridad y asegurar la integridad de nuestras aplicaciones en entornos de contenedores.