Après avoir scanné les vulnérabilités de vos images Docker avec Trivy, l'étape suivante consiste à vérifier que vos Dockerfiles respectent les meilleures pratiques de sécurité. C'est là qu'intervient Dockle.
Dockle est un outil de linting développé par Aqua Security qui analyse vos images Docker pour détecter les problèmes de configuration et vérifier la conformité avec les standards CIS (Center for Internet Security).
Qu'est-ce que CIS ? Le CIS Docker Benchmark est un ensemble de 100+ règles de sécurité reconnues mondialement pour sécuriser vos conteneurs Docker. Dockle automatise leur vérification.
Contrairement à Trivy qui cherche des vulnérabilités dans les packages, Dockle vérifie comment votre image est construite : utilisateur root, secrets exposés, permissions incorrectes, etc.
Une image Docker peut être exempte de vulnérabilités CVE mais présenter des failles de sécurité majeures :
latest : Non reproductibilité des buildsbrew install goodwithtech/r/dockleVERSION
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
goodwithtech/dockle:latest [IMAGE_NAME]dockle --versiondockle myapp:1.0FATAL - CIS-DI-0001: Create a user for the container
* Last user should not be root
WARN - CIS-DI-0005: Enable Content trust for Docker
* export DOCKER_CONTENT_TRUST=1 before docker pull/build
WARN - CIS-DI-0006: Add HEALTHCHECK instruction to the container image
* not found HEALTHCHECK statement
INFO - CIS-DI-0008: Confirm safety of setuid/setgid files
* Found setuid file: usr/bin/su urwxr-xr-x
PASS - CIS-DI-0009: Use COPY instead of ADD in DockerfileDockle classe les problèmes en 4 niveaux :
❌ Problème :
FROM node:18-alpine
WORKDIR /app
COPY . .
CMD ["node", "server.js"]
# Pas de USER défini = exécution en root✅ Solution :
FROM node:18-alpine
# Créer un utilisateur non-root
RUN addgroup -g 1000 nodeapp && \
adduser -u 1000 -G nodeapp -s /bin/sh -D nodeapp
WORKDIR /app
COPY --chown=nodeapp:nodeapp . .
# Basculer vers l'utilisateur non-root
USER nodeapp
CMD ["node", "server.js"]❌ Problème : Images non signées.
✅ Solution :
# Activer Content Trust
export DOCKER_CONTENT_TRUST=1
# Maintenant docker pull vérifiera les signatures
docker pull node:18-alpineOu dans le Dockerfile :
# Utiliser des digests SHA256 au lieu de tags
FROM node@sha256:a1b2c3d4e5f6...❌ Problème : Pas de healthcheck.
✅ Solution :
FROM node:18-alpine
# ... reste du Dockerfile
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD node healthcheck.js || exit 1
CMD ["node", "server.js"]Fichier healthcheck.js simplifié :
// Vérifie que le serveur répond sur /health
const http = require('http');
http.get('http://localhost:3000/health', (res) => {
process.exit(res.statusCode === 200 ? 0 : 1);
}).on('error', () => process.exit(1));Qu'est-ce que setuid/setgid ? Ce sont des permissions spéciales qui permettent à un fichier de s'exécuter avec les droits de son propriétaire (souvent root). Si un attaquant compromet votre conteneur, ces fichiers deviennent des portes dérobées pour obtenir les privilèges root.
❌ Problème : Fichiers avec permissions setuid/setgid dangereuses.
✅ Solution :
FROM alpine:3.19
# Supprimer les bits setuid/setgid inutiles
RUN find / -perm /6000 -type f -exec chmod a-s {} \; || true
# Ou plus sélectif
RUN chmod u-s /usr/bin/su /usr/bin/sudolatest❌ Problème :
FROM node:latest # Version non fixée✅ Solution :
FROM node:18.19.0-alpine3.19 # Version spécifiqueMaintenant que nous avons vu les principales vérifications, voici un Dockerfile Node.js qui passe tous les checks Dockle :
Astuce 💡 : Ce Dockerfile combine toutes les bonnes pratiques vues précédemment. Vous pouvez l'utiliser comme template pour vos projets.
# Version spécifique (pas latest)
FROM node:18.19.0-alpine3.19
# Metadata labels
LABEL maintainer="tavernetech@gmail.com" \
version="1.0" \
description="Application Node.js sécurisée"
# Installer dumb-init pour gérer proprement les signaux système (SIGTERM, etc.)
RUN apk add --no-cache dumb-init=1.2.5-r3
# Créer un utilisateur non-root
RUN addgroup -g 1000 nodeapp && \
adduser -u 1000 -G nodeapp -s /bin/sh -D nodeapp
WORKDIR /app
# Copier les fichiers de dépendances
COPY --chown=nodeapp:nodeapp package*.json ./
# Installer les dépendances en production
RUN npm ci --only=production && \
npm cache clean --force
# Copier le code source
COPY --chown=nodeapp:nodeapp . .
# Supprimer les permissions setuid/setgid dangereuses
RUN find /app -perm /6000 -type f -exec chmod a-s {} \; || true
# Exposer uniquement les ports nécessaires
Vérification :
docker build -t secure-app:1.0 .
dockle secure-app:1.0
# Résultat attendu : Aucune erreur FATAL ou WARNDans certains cas, vous devez ignorer certaines règles Dockle (par exemple, pas de healthcheck pour une image de build CI/CD). Voici comment faire :
dockle --ignore CIS-DI-0006 myapp:1.0.dockleignoreCréez un fichier .dockleignore :
# Ignorer healthcheck pour images de développement
CIS-DI-0006
# Ignorer Content Trust en dev
CIS-DI-0005Puis :
dockle myapp:1.0Pour garantir que vos images Docker restent conformes, intégrez Dockle dans votre pipeline CI/CD :
name: Dockle Security Lint
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
dockle:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Dockle
uses: goodwithtech/dockle-action@main
with:
image: myapp:${{ github.sha }}
format: 'json'
output
Pour une sécurité complète, combinez Trivy (vulnérabilités) et Dockle (configuration) :
name: Docker Security Scan
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t myapp:${{ github.sha }} .
- name: Scan vulnerabilities with Trivy
uses: aquasecurity/trivy-action@master
with:
image-ref: myapp:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'
- name: Lint configuration with Dockle
uses: goodwithtech/dockle-action@main
with:
Pour les applications Go, l'utilisation d'images distroless (sans shell, sans package manager) maximise la sécurité tout en passant tous les checks Dockle.
Qu'est-ce que distroless ? Images ultra-minimales de Google contenant uniquement votre application et ses dépendances runtime. Pas de shell = pas de vecteur d'attaque pour un hacker.
# Build stage
FROM golang:1.21-alpine as builder
WORKDIR /build
COPY . .
# Build statique pour distroless
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
# Runtime stage avec distroless (ultra-minimal)
FROM gcr.io/distroless/static-debian11:nonroot
LABEL maintainer="tavernetech@gmail.com" \
version="1.0" \
description="Application Go sécurisée"
# L'utilisateur nonroot (UID 65532) est déjà défini dans distroless
COPY --from=builder --chown=nonroot:nonroot /build/app /app
EXPOSE 8080
# Note: distroless ne supporte pas HEALTHCHECK avec CMD shell
# Utilisez un healthcheck externe (Kubernetes liveness probe, etc.)
ENTRYPOINT ["/app"]Avantages de distroless :
USERlatest.dockleignorelatest pour les images de baseAdaptez la politique de sécurité selon vos besoins :
# Mode strict : échouer sur tout (même INFO)
dockle --exit-code 1 --exit-level info myapp:latest
# Mode production : échouer sur WARN ou plus grave (recommandé)
dockle --exit-code 1 --exit-level warn myapp:latest
# Mode permissif : échouer uniquement sur FATAL
dockle --exit-code 1 --exit-level fatal myapp:latestSi vous avez beaucoup d'erreurs sur un projet legacy, corrigez progressivement :
# Semaine 1 : Corriger les FATAL uniquement
dockle --exit-level fatal myapp:latest
# Semaine 2 : Corriger les WARN
dockle --exit-level warn myapp:latest
# Semaine 3 : Corriger les INFO
dockle --exit-level info myapp:latest# Générer un rapport JSON pour vos outils de monitoring
dockle --format json --output report.json myapp:latest
# Filtrer uniquement les problèmes critiques avec jq
dockle --format json myapp:latest | jq '.details[] | select(.level=="FATAL")'Dockle est un outil essentiel pour garantir que vos images Docker respectent les meilleures pratiques de sécurité. En quelques secondes, il identifie les problèmes de configuration qui pourraient compromettre vos déploiements.
latest en productionexit-code: 1Merci de me suivre dans cette aventure ! 🚀
Cet article a été écrit avec ❤️ pour la communauté DevOps.