Sécurisation de Docker
Comprendre comment protéger les conteneurs Docker contre les vulnérabilités et les attaques.
Notions théoriques
Docker est une technologie de conteneurisation qui permet d'exécuter des applications de manière isolée. Cependant, une mauvaise configuration peut exposer un système à des risques de sécurité.
Principales menaces
- Images non sécurisées : L'utilisation d'images non vérifiées peut introduire des vulnérabilités.
- Permissions excessives : Exécuter un conteneur en tant que root augmente les risques d'attaques.
- Réseaux mal configurés : Une exposition excessive des ports peut permettre des accès non autorisés.
- Données non protégées : Un stockage mal sécurisé peut entraîner des fuites d'informations sensibles.
Bonnes pratiques de sécurisation
Utiliser des images officielles et vérifiées
- Télécharger uniquement des images depuis des sources de confiance comme Docker Hub ou des registres privés.
- Scanner les images avec des outils comme Trivy ou Docker Scout pour détecter les vulnérabilités.
Exécuter les conteneurs avec des permissions limitées
- Ne pas exécuter les conteneurs en tant que root.
- Utiliser un utilisateur dédié dans le Dockerfile :
RUN useradd -m user
USER user
Restreindre les accès réseau
- Éviter d’exposer des ports inutiles.
- Utiliser des réseaux Docker isolés pour limiter les communications entre conteneurs.
Gérer les secrets et les configurations de manière sécurisée
- Ne pas stocker les mots de passe en dur dans les Dockerfiles.
- Utiliser des variables d’environnement ou des gestionnaires de secrets comme Docker Secrets.
Mettre à jour et surveiller les conteneurs
- Mettre à jour régulièrement les images pour corriger les failles de sécurité.
- Utiliser des outils comme Falco ou Sysdig pour surveiller l’activité des conteneurs.
Exemple pratique
Il est possible d'améliorer la sécurité d'un conteneur Docker en appliquant plusieurs bonnes pratiques.
Étape 1 : Scanner une image Docker avec Trivy
- Installer Trivy :
sudo apt install trivy
- Scanner une image Docker :
trivy image nginx:latest
- Analyser les résultats et identifier les vulnérabilités détectées.
Étape 2 : Exécuter un conteneur avec un utilisateur non root
- Créer un fichier
Dockerfile
sécurisé :
FROM nginx:latest
RUN useradd -m user
USER user
CMD ["nginx", "-g", "daemon off;"]
- Construire et exécuter le conteneur :
docker build -t secure-nginx .
docker run -d --name secure-nginx -p 8080:80 secure-nginx
- Vérifier que le conteneur ne tourne pas en tant que root :
docker exec -it secure-nginx whoami
Étape 3 : Restreindre les accès réseau
- Créer un réseau Docker isolé :
docker network create secure-net
- Exécuter le conteneur dans ce réseau :
docker run -d --name secure-nginx --network secure-net secure-nginx
- Vérifier que le conteneur n'est pas accessible depuis d'autres réseaux.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
L'objectif de ce TP est d'analyser et de sécuriser une application Web exécutée dans un conteneur Docker en appliquant les bonnes pratiques.
L'application cible est une simple API Node.js, qui sera conteneurisée et sécurisée progressivement.
Étape 1 : Préparer l'environnement
- Installer Docker et Node.js si ce n'est pas déjà fait.
- Créer un dossier de travail :
mkdir secure-api && cd secure-api
- Initialiser un projet Node.js :
npm init -y
- Installer Express.js pour créer une API simple :
npm install express
- Créer un fichier
server.js
avec le contenu suivant :
const express = require("express");
const app = express();
app.get("/", (req, res) => {
res.send("API sécurisée !");
});
app.listen(3000, () => {
console.log("Serveur en écoute sur le port 3000");
});
- Tester l'application en local :
node server.js
- Vérifier que l'API fonctionne en ouvrant un navigateur et en accédant à
http://localhost:3000
.
Cette étape permet de créer une API simple avec Node.js et Express.js.
mkdir secure-api && cd secure-api
crée un dossier et y accède.npm init -y
initialise un projet Node.js avec un fichierpackage.json
.npm install express
installe Express.js pour gérer les requêtes HTTP.server.js
définit une API qui répond avec "API sécurisée !" sur la route/
.node server.js
lance l'API en local sur le port 3000.
Si tout fonctionne, un message "API sécurisée !" s'affiche dans le navigateur.
Étape 2 : Conteneuriser l'application
- Créer un fichier
Dockerfile
dans le dossiersecure-api
:
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
EXPOSE 3000
- Construire l'image Docker :
docker build -t secure-api .
- Exécuter le conteneur :
docker run -d --name secure-api -p 3000:3000 secure-api
- Vérifier que l'API est accessible via
http://localhost:3000
.
Cette étape permet de transformer l'application en un conteneur Docker.
FROM node:18-alpine
utilise une image légère de Node.js.WORKDIR /app
définit le répertoire de travail.COPY package.json package-lock.json ./
copie les fichiers nécessaires à l’installation des dépendances.RUN npm install
installe les dépendances.COPY . .
copie le reste du code source.CMD ["node", "server.js"]
exécute l’application au démarrage du conteneur.EXPOSE 3000
indique que le conteneur utilise le port 3000.
La commande docker build -t secure-api .
construit l’image, et docker run -d --name secure-api -p 3000:3000 secure-api
exécute le conteneur en arrière-plan.
Si tout fonctionne, l’API est accessible sur http://localhost:3000
.
Étape 3 : Sécuriser l’image Docker
- Modifier le
Dockerfile
pour éviter d’exécuter l’application en tant que root :
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN adduser -D appuser
USER appuser
CMD ["node", "server.js"]
EXPOSE 3000
- Reconstruire et exécuter l’image :
docker build -t secure-api .
docker run -d --name secure-api -p 3000:3000 secure-api
- Vérifier que le conteneur tourne avec un utilisateur non root :
docker exec -it secure-api whoami
Cette étape améliore la sécurité en exécutant l’application avec un utilisateur non root.
RUN adduser -D appuser
crée un utilisateur sans privilèges.USER appuser
exécute l’application avec cet utilisateur.
La commande docker exec -it secure-api whoami
doit afficher appuser
, confirmant que l’application ne tourne pas en tant que root.
Étape 4 : Scanner l’image Docker avec Trivy
- Installer Trivy si ce n’est pas déjà fait :
sudo apt install trivy
- Scanner l’image Docker :
trivy image secure-api
- Identifier les vulnérabilités et noter les correctifs possibles.
Cette étape permet d’analyser l’image Docker pour détecter des failles de sécurité.
trivy image secure-api
scanne l’image et affiche les vulnérabilités détectées.
Si des vulnérabilités sont identifiées, il est recommandé de mettre à jour les dépendances et d’utiliser une image de base plus récente.
Étape 5 : Restreindre les accès réseau
- Créer un réseau Docker isolé :
docker network create secure-net
- Exécuter le conteneur dans ce réseau :
docker run -d --name secure-api --network secure-net secure-api
- Vérifier que l’application n’est plus accessible depuis
http://localhost:3000
.
Cette étape limite l’exposition du conteneur sur le réseau.
docker network create secure-net
crée un réseau privé.docker run -d --name secure-api --network secure-net secure-api
exécute le conteneur sans l’exposer publiquement.
L’application ne doit plus être accessible sur http://localhost:3000
, ce qui renforce la sécurité.
Étape 6 : Stocker les secrets de manière sécurisée
- Modifier
server.js
pour utiliser une variable d’environnement :
const express = require("express");
const app = express();
const SECRET_MESSAGE = process.env.SECRET_MESSAGE || "Message par défaut";
app.get("/", (req, res) => {
res.send(`Message secret : ${SECRET_MESSAGE}`);
});
app.listen(3000, () => {
console.log("Serveur en écoute sur le port 3000");
});
- Exécuter le conteneur avec une variable d’environnement :
docker run -d --name secure-api -p 3000:3000 -e SECRET_MESSAGE="Ceci est un secret" secure-api
- Vérifier que le message affiché change en fonction de la variable.
Cette étape empêche de stocker des informations sensibles directement dans le code.
process.env.SECRET_MESSAGE || "Message par défaut"
utilise une variable d’environnement.docker run -d --name secure-api -p 3000:3000 -e SECRET_MESSAGE="Ceci est un secret" secure-api
passe le secret au conteneur.
L’API doit maintenant afficher "Message secret : Ceci est un secret" au lieu du message par défaut.
Conclusion
Ce TP a permis de conteneuriser une application Node.js et d’appliquer plusieurs bonnes pratiques de sécurité :
- Exécution avec un utilisateur non root
- Analyse des vulnérabilités avec Trivy
- Isolation réseau
- Gestion sécurisée des secrets
Ces techniques permettent de réduire les risques de sécurité et d’améliorer la protection des conteneurs Docker.