Aller au contenu principal

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

  1. Installer Trivy :
sudo apt install trivy
  1. Scanner une image Docker :
trivy image nginx:latest
  1. Analyser les résultats et identifier les vulnérabilités détectées.

Étape 2 : Exécuter un conteneur avec un utilisateur non root

  1. Créer un fichier Dockerfile sécurisé :
FROM nginx:latest  
RUN useradd -m user
USER user
CMD ["nginx", "-g", "daemon off;"]
  1. Construire et exécuter le conteneur :
docker build -t secure-nginx .  
docker run -d --name secure-nginx -p 8080:80 secure-nginx
  1. 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

  1. Créer un réseau Docker isolé :
docker network create secure-net
  1. Exécuter le conteneur dans ce réseau :
docker run -d --name secure-nginx --network secure-net secure-nginx
  1. Vérifier que le conteneur n'est pas accessible depuis d'autres réseaux.

Test de mémorisation/compréhension


Pourquoi est-il dangereux d'exécuter un conteneur en tant que root ?


Quel outil permet de scanner une image Docker pour détecter des vulnérabilités ?


Quelle est une bonne pratique pour sécuriser les accès réseau d’un conteneur ?


Pourquoi faut-il mettre à jour régulièrement les images Docker ?


Quel fichier permet de définir un utilisateur non root dans un conteneur ?


Quelle commande permet de vérifier l'utilisateur exécutant un conteneur ?


Quel est un risque lié à l'utilisation d'images non vérifiées ?


Quel outil permet de surveiller l’activité des conteneurs en temps réel ?


Comment stocker des secrets en toute sécurité dans Docker ?


Pourquoi est-il important de limiter les permissions des conteneurs ?



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.

astuce

L'application cible est une simple API Node.js, qui sera conteneurisée et sécurisée progressivement.


Étape 1 : Préparer l'environnement

  1. Installer Docker et Node.js si ce n'est pas déjà fait.
  2. Créer un dossier de travail :
mkdir secure-api && cd secure-api
  1. Initialiser un projet Node.js :
npm init -y
  1. Installer Express.js pour créer une API simple :
npm install express
  1. 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");
});
  1. Tester l'application en local :
node server.js
  1. Vérifier que l'API fonctionne en ouvrant un navigateur et en accédant à http://localhost:3000.
info

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 fichier package.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

  1. Créer un fichier Dockerfile dans le dossier secure-api :
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
EXPOSE 3000
  1. Construire l'image Docker :
docker build -t secure-api .
  1. Exécuter le conteneur :
docker run -d --name secure-api -p 3000:3000 secure-api
  1. Vérifier que l'API est accessible via http://localhost:3000.
info

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

  1. 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
  1. Reconstruire et exécuter l’image :
docker build -t secure-api .
docker run -d --name secure-api -p 3000:3000 secure-api
  1. Vérifier que le conteneur tourne avec un utilisateur non root :
docker exec -it secure-api whoami
info

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

  1. Installer Trivy si ce n’est pas déjà fait :
sudo apt install trivy
  1. Scanner l’image Docker :
trivy image secure-api
  1. Identifier les vulnérabilités et noter les correctifs possibles.
info

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

  1. Créer un réseau Docker isolé :
docker network create secure-net
  1. Exécuter le conteneur dans ce réseau :
docker run -d --name secure-api --network secure-net secure-api
  1. Vérifier que l’application n’est plus accessible depuis http://localhost:3000.
info

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

  1. 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");
});
  1. 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
  1. Vérifier que le message affiché change en fonction de la variable.
info

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.