Comprendre les risques, réduire les privilèges et relier les protections de l’image jusqu’à l’exploitation.
Sécuriser une application conteneurisée : quel objectif ?
La sécurité d’un conteneur consiste à maîtriser ce qu’un programme peut faire, même lorsque son fonctionnement devient anormal.
Une faille dans une application web peut permettre à un attaquant d’exécuter du code avec les droits de cette application.
La question devient alors : jusqu’où ces droits lui permettent-ils d’agir ?
Le conteneur apporte une isolation, mais les conteneurs Linux partagent le noyau du système Linux qui les héberge.
Une mauvaise configuration ou une vulnérabilité de ce noyau peut réduire la portée de cette isolation.
Les objectifs de sécurité se complètent :
La confidentialité vise à empêcher la lecture de données par un acteur non autorisé.
L’intégrité vise à empêcher une modification non autorisée des données ou des programmes.
La disponibilité vise à maintenir le service accessible dans les conditions attendues.
⚠️ Une protection utile réduit les possibilités d’action d’un attaquant ou les conséquences d’une erreur.
L’ensemble de ces protections forme une défense en profondeur.
Exemple : une faille WordPress ne devrait pas donner automatiquement le contrôle du moteur Docker ni la lecture de tous les fichiers du serveur.
9.1 Identifier les frontières de confiance
9.1.1 Définition d’une frontière
Une frontière est une limite qui sépare deux espaces, deux composants ou deux domaines de responsabilité.
En informatique, elle peut séparer un poste et un serveur, deux services, ou une application et les fichiers auxquels elle accède. Elle n’est pas forcément physique : elle peut être définie par un réseau, un compte utilisateur ou des permissions.
Identifier une frontière permet de repérer ce qui la traverse : des données, des requêtes ou des actions. On peut alors se demander quels échanges sont nécessaires et lesquels doivent être limités.
9.1.2 Frontière de confiance
Source et construction
→
Image distribuée
→
Configuration de déploiement
→
Application en exécution
À chaque frontière, une question différente apparaît.
Qui a fourni le code ?
Quelle image a été reçue ?
Qui a choisi les montages et les droits ?
Qui peut joindre le service ou administrer le moteur ?
⚠️ Attention : une protection située à une frontière ne remplace pas les autres.
Surface
Exemple de risque
Question à poser
Application
Une requête exploite une faille web.
Que pourra lire ou modifier le processus compromis ?
Image et dépendances
Une bibliothèque vulnérable ou une image malveillante est déployée.
L’origine est-elle maîtrisée et les mises à jour suivies ?
Moteur et API
Une console compromise crée un conteneur administrateur.
Qui possède réellement le contrôle de Docker ?
Hôte et noyau
Une faille ou une configuration donne accès à l’hôte.
L’hôte est-il maintenu et les accès limités ?
Données et exploitation
Une suppression.
Une saturation ou une fuite affecte le service.
Que peut-on restaurer et comment détecter l’incident ?
Une frontière de confiance sépare deux zones auxquelles on n’accorde pas les mêmes droits.
Exemple de frontière :
Le serveur web doit recevoir des requêtes du public, mais le public ne doit pas pouvoir modifier sa configuration.
L’application doit lire son mot de passe SQL, mais un autre service sans rapport n’a pas besoin de connaître cette valeur.
Le moteur peut administrer les conteneurs, mais une application métier ne doit pas recevoir ce pouvoir par simple commodité.
Décrire ces frontières permet de relier chaque protection à un besoin précis.
9.1.3 Règle du moindre privilège
Cette règle consiste à n’accorder que les droits nécessaires à la tâche.
Elle s’applique à tous les acteurs qui peuvent agir sur l’environnement :
Les administrateurs.
La chaîne CI/CD.
Les comptes de registre.
Les clients Docker.
Un droit temporaire exceptionnel doit avoir :
Une justification.
Un périmètre défini.
Une fin prévue.
9.2 Protéger le daemon, le socket et les clients
9.2.1 Contrôler les droits d’administration du moteur Docker
Problèmes :
Sur un Engine classique, le daemon fonctionne avec les privilèges root.Un utilisateur autorisé à piloter complètement ce moteur peut lui demander des montages et des exécutions qui donnent un pouvoir très étendu sur l’hôte.
Un membre du groupe docker peut piloter le moteur et potentiellement prendre le contrôle de l’hôte.Ce groupe doit donc être réservé aux utilisateurs autorisés à administrer ce serveur.Ajouter un utilisateur au groupe docker lui transmet les droits d’accès associés à ce groupe.On parle de délégation d’administration : cet utilisateur est autorisé à piloter le moteur sans utiliser directement le compte root.
Utiliser la socket, en montant /var/run/docker.sock dans un conteneur, donne à son processus un chemin vers l’API.Une compromission de ce processus peut alors toucher d’autres conteneurs et l’hôte.Le suffixe :ro du montage ne filtre pas les requêtes de l’API et ne garantit pas une consultation seule.
Réponse au risque : réserver l’accès au moteur aux administrateurs autorisés
Limiter les membres du groupe docker et les comptes pouvant administrer Docker avec sudo.
Éviter de monter le socket dans les applications métier ; ne pas rendre ses permissions accessibles à tous.
Lorsque les besoins le permettent, utiliser le mode rootless pour exécuter le daemon sans les privilèges root de l’hôte (voir 9.3).
Sinon, conserver le daemon en root, limiter strictement l’accès à son socket et réserver son administration à des comptes de confiance, via sudo ou le groupe docker. Ces accès restent des droits d’administration puissants.
usermod : ajouter un compte à un groupe
sudo usermod -aG docker utilisateur
Cette commande ajoute un compte existant au groupe docker.-aG ajoute ce groupe sans remplacer les autres groupes supplémentaires.
Un nouveau login est nécessaire (donc logout + login au besoin) pour prendre en compte l’appartenance.
⚠️ Cette commande délègue l’administration du moteur au compte utilisateur. Elle permet l’usage de Docker sans sudo, mais ne rend pas le daemon rootless et ne réduit pas le pouvoir accordé par le socket.
9.2.2 Accès distant : transport et autorisation
Problèmes :
Une API Docker accessible sur le réseau sans protection permet à un tiers de commander le moteur.L’accès réseau et les clés doivent être protégés pour réserver la connexion aux clients autorisés.
Chiffrer le transport ne réduit pas les droits du client authentifié.SSH peut protéger une connexion distante. Une API HTTPS peut utiliser une authentification mutuelle par certificats. Un client disposant d’un accès complet conserve cependant son pouvoir d’administration.
Réponse au risque : protéger et restreindre la connexion distante
Privilégier une connexion SSH avec un compte d’administration identifié, ou une API protégée par TLS mutuel.
Limiter les accès réseau aux postes ou réseaux d’administration et protéger les clés privées.
Révoquer les accès devenus inutiles. Un accès complet au moteur reste un droit d’administration, même avec un transport chiffré.
9.2.3 Les consoles et la CI/CD font partie du périmètre
Contexte
Plusieurs outils peuvent devenir des chemins d’accès au moteur :
Portainer.
Un runner de build.
Un programme Python.
Ils doivent être traités comme des outils d’administration pour les aspects suivants :
La protection de leurs comptes.
Leurs mises à jour.
Leurs secrets de connexion.
Séparer les environnements réduit les possibilités de propagation :
Le développement.
La production.
Les constructions de code non fiable.
Problèmes :
Les consoles et les programmes peuvent constituer des chemins d’accès au moteur.
Un outil partagé entre plusieurs environnements peut faciliter la propagation d’une compromission.
Une application métier n’a normalement pas besoin de piloter Docker.
Réponse au risque : séparer les outils et leurs accès
Réserver les consoles aux administrateurs et protéger leurs comptes avec une authentification forte.
Donner à chaque outil un accès limité à l’environnement qu’il doit gérer ; protéger et renouveler ses secrets.
Isoler les runners exécutant du code non fiable des moteurs de production.(ex. : une contribution externe, une application en cours de développement)→ En réservant un hôte Docker à la production et un autre au développement et au staging, qui peuvent partager cet autre hôte lorsque leurs besoins d’isolation le permettent.
Maintenir les consoles, runners et bibliothèques clients à jour.
9.2.4 Comprendre un enchaînement de compromission
Imaginons une application web à laquelle on a donné accès au socket Docker.→ Une faille de cette application peut alors permettre à un attaquant de piloter le moteur.
Problèmes :
L’attaquant obtient d’abord les droits du processus applicatif.
Il peut ensuite communiquer avec l’API par le socket si ces droits lui en permettent l’usage.
Le moteur effectue alors les opérations demandées avec ses propres privilèges.
→ Le problème dépasse donc les droits initiaux du processus dans son conteneur.
C’est une délégation de pouvoir : un programme peu privilégié peut commander un service plus puissant lorsque l’accès à ce service n’est pas suffisamment limité.
Réponse au scénario : couper le chemin vers le moteur
Retirer l’accès au socket à l’application web empêche cette voie d’administration directe. L’application conserve uniquement les accès nécessaires à son fonctionnement, par exemple sa connexion à la base de données.
Si une compromission a déjà eu lieu, retirer le montage ne suffit pas : isoler l’environnement affecté, révoquer les secrets exposés et vérifier l’hôte ainsi que les autres conteneurs avant une remise en service depuis une base saine.
9.3 Utilisateur non-root, rootless et remappage : trois notions
9.3.1 Contexte : quel utilisateur exécute quoi ?
Deux comptes sont à distinguer :
Celui qui exécute l’application dans le conteneur.
Celui qui exécute le moteur Docker sur l’hôte.
Sous Linux, root est le compte administrateur, identifié par le numéro 0, appelé UID. Les autres comptes possèdent leurs propres UID.
Dans une installation Docker classique, le moteur fonctionne avec le compte root de l’hôte.
Même exécutée avec root dans un conteneur, une application reste soumise aux protections de Docker.→ Elle n’a donc pas automatiquement tous les pouvoirs sur l’hôte.Mais ces protections ne justifient pas de lui accorder des droits dont elle n’a pas besoin.
Problèmes :
Une application exécutée avec root dans son conteneur dispose de droits élevés à l’intérieur de celui-ci.Si elle est compromise, l’attaquant peut utiliser ces droits et les accès accordés au conteneur, notamment aux dossiers montés.
Changer l’utilisateur de l’application ne réduit pas les privilèges du moteur Docker.Un moteur fonctionnant avec root reste un service puissant sur l’hôte.
Un utilisateur non-root peut rencontrer un refus d’accès aux fichiers dont l’application a besoin.Par exemple, le compte de l’application ne peut pas écrire dans un dossier monté si les permissions de ce dossier ne l’y autorisent pas.
9.3.2 Réponse aux problèmes : choisir la protection au bon niveau
Protection
Ce qu’elle change
Ce qu’il faut retenir
Application non-root
L’application utilise un compte non administrateur dans le conteneur, défini par USER dans le Dockerfile ou par la configuration de déploiement.
Elle réduit les droits de l’application. Le moteur peut continuer à fonctionner avec root sur l’hôte.
Docker rootless
Le moteur et les conteneurs fonctionnent sans les privilèges root de l’hôte.
Il réduit les privilèges du moteur lui-même. Sa compatibilité avec les besoins de l’application doit être vérifiée.
Remappage des utilisateurs
Docker fait correspondre les identifiants des utilisateurs du conteneur à d’autres identifiants sur l’hôte. Par exemple, root dans le conteneur correspond à un identifiant non privilégié sur l’hôte.
Il limite les droits des processus du conteneur sur l’hôte. Le moteur reste exécuté avec root.
→ Ces mécanismes protègent des niveaux différents. Définir un utilisateur non-root pour l’application ne rend pas le moteur rootless.
Si rootless ne convient pas, l’accès au moteur classique doit rester réservé aux administrateurs autorisés, comme expliqué en 9.2.1.
Si l’application non-root ne peut pas écrire dans son dossier de données, adapter les permissions de ce dossier au compte utilisé, plutôt que donner des droits root à l’application.
Pour distinguer namespaces et cgroups, voir 1.4 — Comment Linux isole les conteneurs. Rootless ne signifie pas seulement exécuter Docker sans sudo : le moteur et les conteneurs fonctionnent sans privilèges root, en s’appuyant notamment sur un namespace utilisateur.
9.4 Capabilities et acquisition de privilèges
9.4.1 Comprendre les capabilities : des pouvoirs séparés
Linux décompose une partie des pouvoirs de root en capabilities.Docker retire déjà de nombreux pouvoirs par défaut ; une application peut néanmoins avoir besoin d’un sous-ensemble encore plus petit.Une capability doit répondre à un besoin concret du processus, pas être ajoutée parce qu’un exemple la contient.
9.4.2 Choisir les pouvoirs accordés au conteneur
cap_drop permet de retirer des capabilities.Une stratégie de départ stricte consiste à tout retirer puis à réintroduire seulement un pouvoir justifié.
cap_add ajoute un pouvoir précis.Certaines capabilities, notamment SYS_ADMIN, ont une portée très large.
no-new-privileges empêche un processus et ses descendants d’obtenir des privilèges supplémentaires par les mécanismes d’exécution concernés, tels que setuid ou les file capabilities.Il ne retire pas les droits déjà détenus.
privileged: true donne des pouvoirs et des accès très étendus et relâche plusieurs protections.Ce réglage exige un besoin exceptionnel, pas une utilisation habituelle.
9.4.3 Comprendre un refus avant d’ajouter des privilèges
Le fonctionnement attendu doit être observé avec les droits minimaux.
Une erreur permission denied peut avoir plusieurs causes :
Un fichier est inaccessible selon ses permissions.
Le processus utilise une identité inadaptée.
Une opération noyau est interdite.
Ces causes ne se corrigent pas nécessairement de la même manière.
9.4.4 Réponse au problème : accorder uniquement les capabilities nécessaires
Un outil réseau peut avoir besoin d’une opération que ne réalise jamais une application web ordinaire.
Accorder le même ensemble de pouvoirs aux deux services augmente inutilement les possibilités de l’application web.
La bonne granularité est le besoin du processus, pas la catégorie générale « conteneur ».
Connaître la limite : les permissions des fichiers restent actives
Le retrait des capabilities ne supprime toutefois pas les droits ordinaires déjà accordés sur les fichiers accessibles.
Un processus non-root autorisé à écrire dans un volume peut toujours endommager les fichiers de ce volume.
9.4.5 Exemple : attribuer et lire les capabilities du conteneur web
Cette commande crée le conteneur web sans le démarrer. Elle permet d’illustrer les droits configurés avant leur utilisation :
docker create --name web \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
nginx
--cap-drop ALL retire toutes les capabilities.
--cap-add NET_BIND_SERVICE réintroduit celle qui permet d’écouter sur un port inférieur à 1024, comme le port 80, lorsque le système exige ce privilège.
Pour lire la configuration attribuée au conteneur :
docker inspect web \
--format 'CapDrop={{.HostConfig.CapDrop}} CapAdd={{.HostConfig.CapAdd}}'
CapDrop=[ALL] CapAdd=[NET_BIND_SERVICE]
→ La configuration retire toutes les capabilities puis réintroduit uniquement NET_BIND_SERVICE. Cette lecture décrit la configuration du conteneur, pas les droits effectifs de chacun de ses processus.
ls -l affiche les permissions classiques des fichiers, pas les capabilities du conteneur.
⚠️ Cet exemple illustre les options Docker. Les capabilities nécessaires au démarrage et au fonctionnement de l’image NGINX doivent être vérifiées : cette seule capability ne garantit pas que l’image fonctionne avec tous les autres pouvoirs retirés.
9.5 Seccomp, AppArmor et SELinux : limiter les actions possibles
9.5.1 Que sont Seccomp, AppArmor et SELinux ?
Ce sont des mécanismes de sécurité Linux qui permettent d’imposer des limites supplémentaires aux programmes. Docker peut les utiliser pour encadrer les actions des processus dans les conteneurs.
Seccomp filtre les appels système : les demandes qu’un programme adresse au noyau Linux pour effectuer une opération, par exemple ouvrir un fichier ou créer un processus.Un profil Seccomp définit les appels autorisés ou bloqués. Il ne choisit pas quels fichiers précis un programme peut lire.
AppArmor applique un profil de sécurité : un ensemble de règles décrivant ce qu’un programme peut faire, notamment les chemins de fichiers auxquels il peut accéder et les opérations autorisées.Par exemple, un profil peut permettre à un serveur web de lire le contenu du site tout en lui interdisant certains autres fichiers.
SELinux applique aussi des règles de sécurité, en s’appuyant notamment sur des étiquettes de sécurité associées aux processus et aux ressources.Ces étiquettes permettent de décider si un processus a le droit d’accéder à une ressource, par exemple si le serveur web peut lire un fichier donné.
→ L’idée commune est de limiter ce qu’un programme peut faire, même lorsque les droits de son compte utilisateur lui permettraient davantage.
9.5.2 Quels problèmes ces protections peuvent-elles empêcher ?
Prenons le cas d’un conteneur web compromis : ces protections peuvent bloquer certaines actions de l’attaquant, selon les règles appliquées.
Protection
Exemple d’action tentée
Effet de la règle
Seccomp
Le programme tente un appel système inutile au service web et interdit par le profil.
L’appel est bloqué, même si le programme a été compromis.
AppArmor
Le programme tente de lire un fichier situé hors des chemins autorisés par son profil.
La lecture est refusée, même si les permissions ordinaires du fichier permettraient cet accès.
SELinux
Le programme tente de lire un fichier dont l’étiquette de sécurité n’autorise pas cet accès pour ce processus.
La politique interdit l’accès à ce fichier.
AppArmor est courant sur Ubuntu ; SELinux est courant sur d’autres distributions.Leur activation et leurs politiques effectives dépendent de l’hôte et de la configuration Docker.
Leur simple mention dans un cours ne prouve pas qu’ils protègent une machine donnée.
Désactiver globalement un profil avec une option unconfined pour faire disparaître une erreur supprime une couche de protection.La démarche consiste à identifier le refus, comprendre le besoin et ajuster au plus près, si nécessaire.
9.5.3 Comment ces protections se complètent-elles ?
Rappel :
L’identité répond à la question « qui effectue l’opération ? ».
Les permissions de fichiers déterminent les accès ordinaires associés à cette identité.
Les capabilities répondent à la question des pouvoirs privilégiés accordés au processus.
Seccomp peut empêcher l’utilisation de certains appels système même lorsque le programme souhaite les invoquer.
AppArmor ou SELinux peut interdire à l’application de lire un fichier, même si les permissions Linux de ce fichier autorisent son utilisateur à le lire.
Une demande doit franchir les contrôles applicables pour réussir.
Ajouter une couche de contrôle n’efface pas les autres couches : un fichier accessible selon ses permissions peut rester interdit par une politique obligatoire.
9.5.4 Exemples d’attribution de mécanismes de sécurité
Créer un conteneur avec le profil Seccomp par défaut
docker create --name web-seccomp-default nginx
Sans option spécifique ni configuration contraire du daemon, Docker utilise son profil Seccomp intégré, appelé builtin, lorsque Seccomp est pris en charge.Ce profil autorise une liste d’appels système et refuse les autres. Le profil exact dépend de la version du moteur ; il n’est pas nécessairement installé comme un fichier JSON sur l’hôte.
Ce contenu est enregistré dans le fichier seccomp-web.json. Il refuse ptrace, qui permet d’examiner ou de contrôler un autre processus, et autorise les autres appels système.
Ce profil pédagogique illustre une règle.Il est moins restrictif que le profil par défaut et ne constitue pas une recommandation de durcissement.
La sortie contient une option seccomp avec le contenu JSON du profil. Elle indique la configuration demandée ; elle ne prouve pas à elle seule qu’un appel a été bloqué pendant l’exécution.
Sur un hôte où SELinux et son intégration Docker sont actifs, :Z demande un étiquetage privé du dossier pour ce conteneur ; ro rend le montage accessible en lecture seule.Cette option modifie les étiquettes du dossier sur l’hôte : /srv/site doit être un dossier dédié au contenu du site.
Lire l’étiquette SELinux du dossier
ls -Zd /srv/site
Sur l’hôte, -Z affiche l’étiquette SELinux et -d demande les informations du dossier lui-même.
→ AppArmor et SELinux correspondent à des configurations d’hôte différentes ; il n’est pas nécessaire de réunir tous ces exemples sur une même machine.
9.6 Images et chaîne de construction
9.6.1 Choisir et identifier l’image
Une image, d’un certain point de vue, c’est du code que l’on accepte d’exécuter.
Son nom familier ou sa présence dans un registre ne constitue pas une preuve de confiance.
La confiance repose notamment sur la connaissance de plusieurs éléments :
L’éditeur.
La maintenance.
Les dépendances.
Le processus de production de l’image.
Une image réduite limite les composants embarqués.
Sa petite taille ne prouve pas son innocuité.
Un tag peut pointer vers un autre contenu au fil du temps.Un digest identifie un contenu précis et permet de fixer ce que l’on déploie.Cette précision ne démontre ni que le contenu est sûr ni qu’il restera adapté : une image figée doit être réévaluée et remplacée lorsque des corrections sont nécessaires.
9.6.2 Construire sans embarquer l’inutile
Le contexte de build doit contenir uniquement les fichiers utiles.Un .dockerignore aide à écarter des fichiers locaux ; le Dockerfile doit également éviter les copies trop larges.Le multi-stage sépare les outils de construction de l’image d’exécution, sous réserve de copier seulement les artefacts nécessaires.
Un secret copié dans une couche puis supprimé dans une couche suivante peut rester récupérable dans l’historique de l’image.Les arguments de build et les variables ne sont pas des coffres à secrets.Les montages de secrets du système de build évitent de les enregistrer automatiquement comme fichiers de couche, mais le script de construction peut encore les exposer par une copie ou un journal.
9.6.3 Vérifier et maintenir
Plusieurs informations contribuent à l’évaluation d’une image :
L’analyse de vulnérabilités repère des problèmes connus dans les composants détectés.
La nomenclature logicielle, ou SBOM, décrit les composants.
Une attestation de provenance renseigne sur la production.
Une signature peut aider à vérifier l’origine selon une politique de confiance.
Ces éléments se complètent.
⚠️ Attention : aucun de ces éléments ne prouve à lui seul l’absence de code malveillant ou de faille inconnue.
Corriger une image requiert généralement de reconstruire et de redéployer les conteneurs concernés.Mettre à jour un paquet dans un conteneur en cours d’exécution crée un état temporaire qui sera perdu à sa recréation et ne corrige pas l’image conservée dans le registre.
9.6.4 Lire un scénario de mise à jour
Imaginons une application dont l’image contient une dépendance devenue vulnérable.→ Une image corrigée est disponible, mais le conteneur existant utilise encore l’ancienne version.
Problèmes :
Le conteneur existant continue d’utiliser les fichiers de l’ancienne image et de sa couche modifiable.
Télécharger la nouvelle image ne remplace pas les fichiers de ce conteneur.
Remplacer le conteneur peut faire perdre les données conservées uniquement dans sa couche modifiable.
→ Disposer de l’image corrigée ne suffit pas : il faut recréer le conteneur à partir de celle-ci, tout en préservant les données nécessaires.
Réponse au scénario : renouveler le conteneur et préserver les données
Utiliser une image reconstruite avec la dépendance corrigée, puis recréer le conteneur à partir de cette image. Vérifier ensuite que l’application fonctionne correctement.
Conserver les données durables dans des volumes Docker ou des bind mounts, et prévoir leur sauvegarde. → Elles doivent rester disponibles pour le nouveau conteneur.
Dès la création du conteneur, monter les dossiers contenant les données à conserver dans un volume Docker ou un bind mount.Prévoir leur sauvegarde et réutiliser ce stockage lors de la recréation du conteneur.
9.7 Registres : confidentialité, authentification et droits
9.7.1 Contexte : à quoi sert un registre privé ?
Un registre conserve les images que les machines téléchargent pour créer des conteneurs. Un registre privé permet d’en réserver l’accès aux utilisateurs et outils autorisés.Trois questions sont distinctes : les échanges sont-ils protégés, qui se connecte, et que peut faire ce compte ?
9.7.2 Problèmes : un registre privé ne suffit pas à protéger les images
Le mot « privé » ne garantit pas le chiffrement des échanges.Sur un réseau non protégé, des identifiants ou des données peuvent être exposés.
Un compte capable de télécharger une image n’a pas nécessairement besoin d’en publier ou d’en supprimer.Accorder tous les droits à tous les comptes augmente les conséquences d’un vol d’identifiants.
Supprimer une image du registre ne supprime pas les copies déjà téléchargées ni les conteneurs qui les utilisent.
9.7.3 Réponses : protéger la connexion et séparer les droits
Utiliser HTTPS/TLS pour protéger les échanges et vérifier l’identité du registre.L’authentification identifie le client ; l’autorisation détermine les dépôts et opérations accessibles.
Distinguer les comptes de téléchargement des comptes de publication.Une machine de production qui récupère des images peut se limiter aux droits de lecture.
Limiter les jetons CI/CD aux dépôts nécessaires, protéger leur stockage et pouvoir les révoquer.Protéger également les identifiants enregistrés sur les postes et dans les sauvegardes.
Conserver les traces de publication et les informations permettant d’identifier l’image distribuée.En cas d’incident, rechercher les déploiements concernés et remplacer les conteneurs utilisant l’image compromise.
→ L’accès privé, le transport chiffré et les droits du compte répondent à des besoins différents. Ils se complètent.
9.8 Secrets et configuration sensible
9.8.1 Contexte : reconnaître un secret
Un secret est une information dont la divulgation donne un accès ou révèle une donnée sensible : un mot de passe de base de données, une clé privée ou un jeton d’API.L’application doit pouvoir l’utiliser, mais les autres services n’ont pas nécessairement à le connaître.
9.8.2 Problèmes : les secrets peuvent être copiés et exposés
Un secret enregistré dans le code, une image ou les journaux peut être récupéré par une personne qui accède à ces supports.
Un fichier .env facilite la configuration, mais il n’est pas chiffré par nature.Une variable d’environnement peut apparaître dans l’inspection du conteneur ou dans des diagnostics.
Distribuer le même secret à tous les services multiplie les endroits où il peut être divulgué.Changer sa valeur à un seul endroit ne suffit pas si les applications continuent d’utiliser l’ancienne.
9.8.3 Réponses : distribuer le secret au seul service concerné
Garder les secrets hors du dépôt de code et des couches d’image ; éviter leur affichage dans les journaux.
Fournir le secret au service qui en a besoin, par exemple dans un fichier dont les permissions permettent sa lecture par l’application.Protéger aussi le fichier source sur l’hôte et ses sauvegardes.
Vérifier que l’application sait lire le fichier fourni.Les variables terminées par _FILE sont une convention de certaines images, pas une fonction universelle de Docker.
Lors d’un remplacement du secret, mettre à jour les applications qui le consomment et révoquer l’ancienne valeur.Prendre en compte les copies encore présentes dans les outils et les sauvegardes.
Compose local : les secrets provenant de fichiers sont mis à disposition des services par des montages de fichiers. La déclaration secrets: ne chiffre pas automatiquement le fichier sur l’hôte.Les garanties de Docker Swarm sont différentes de celles de Compose local.
9.8.4 Exemple : limiter l’accès au mot de passe SQL
WordPress a besoin du mot de passe de son compte SQL pour joindre MariaDB. NGINX n’en a pas besoin pour transmettre les requêtes HTTP à WordPress.
Donner le secret à WordPress et prévoir côté MariaDB le compte correspondant.
Ne pas distribuer ce mot de passe à NGINX.
Limiter les droits du compte SQL aux besoins de WordPress.
→ Protéger le mot de passe et limiter les droits du compte SQL sont deux protections complémentaires.
9.9 Réseau : limiter ce qui est joignable
9.9.1 Contexte : distinguer accès public et communication entre services
Un réseau Docker permet aux services qui y sont attachés de communiquer. Sur un réseau bridge utilisateur ordinaire, ils peuvent généralement joindre leurs ports d’écoute sans publication vers l’hôte.Publier un port ouvre un autre chemin : un accès au service via l’hôte Docker.
9.9.2 Problèmes : ne pas publier un port ne suffit pas à isoler un service
Un conteneur peut joindre la base de données s’il partage son réseau, même si le port SQL n’est pas publié.
Un port publié sans adresse explicite peut être accessible sur les interfaces réseau de l’hôte.La portée réelle doit être vérifiée.
La déclaration expose n’est pas un pare-feu.Un réseau commun ne doit donc pas réunir des services sans besoin de communication.
9.9.3 Réponses : ouvrir seulement les communications nécessaires
Publier uniquement l’entrée web destinée aux utilisateurs ; ne pas publier le port SQL pour permettre à WordPress de joindre MariaDB.
Séparer les réseaux et n’y attacher que les services concernés.Par exemple, NGINX rejoint WordPress ; WordPress rejoint MariaDB sur un autre réseau.
Choisir l’adresse de publication selon le besoin.Une publication sur 127.0.0.1 vise un accès local à l’hôte, mais n’ajoute aucune authentification.
Conserver l’authentification des services et vérifier le filtrage réseau de l’hôte.
Paramètre ou choix
Lecture de sécurité
ports
Publie un accès via l’hôte.Une publication sans adresse explicite se lie normalement aux interfaces de l’hôte ; la portée réelle doit être vérifiée.
Publication sur 127.0.0.1
Limite l’accès direct via la boucle locale IPv4 de l’hôte ; ne constitue pas une authentification et ne décrit pas toute la politique réseau.
expose
Documente des ports ; ce n’est pas un pare-feu entre conteneurs du même réseau.
Réseaux distincts
Réduisent les communications possibles si seuls les services nécessaires y sont attachés.
Réseau internal
Crée un réseau extérieurement isolé ; un service attaché aussi à un autre réseau conserve les possibilités de cet autre accès.
Réseau de l’hôte
Réduit la séparation réseau avec l’hôte ; à justifier selon le besoin et la plateforme.
⚠️ Sur Ubuntu, les règles installées par Docker pour les ports publiés peuvent contourner les attentes fondées sur les seules règles UFW.Le filtrage doit tenir compte de la configuration réseau de Docker et être vérifié depuis les points d’accès concernés.
9.9.4 Exemple : suivre une requête vers WordPress
Utilisateur
→
Port web publié
→
NGINX
→
WordPress
WordPress ouvre ensuite une connexion vers MariaDB sur le réseau partagé par ces deux services. Le navigateur n’a pas besoin de joindre directement le port SQL.→ Le port publié, les réseaux partagés et les identifiants applicatifs contrôlent trois aspects différents de l’accès.
9.10 Fichiers, volumes et montages : limiter les dégâts
9.10.1 Contexte : les montages donnent accès à des données
Un volume Docker ou un bind mount rend des fichiers accessibles au conteneur. Le processus de l’application les utilise avec ses droits Linux.Certaines données doivent être modifiées, comme celles de MariaDB ; d’autres peuvent rester en lecture seule, comme les fichiers statiques servis par NGINX.
9.10.2 Problèmes : un accès trop large augmente les dégâts possibles
Un dossier monté en écriture peut être modifié ou supprimé par un processus disposant des droits nécessaires.Si ce processus est compromis, les fichiers montés peuvent être touchés.
Monter une grande arborescence pour quelques fichiers expose davantage de données que nécessaire.
Un volume nommé conserve les données, mais ne les protège pas à lui seul contre la corruption, l’effacement ou le vol.
La lecture seule empêche les modifications par ce montage, mais ne cache pas les fichiers à un programme autorisé à les lire.
9.10.3 Réponses : limiter les chemins, les écritures et les droits
Monter uniquement les dossiers nécessaires à l’application.
Utiliser un montage en lecture seule pour les fichiers qui n’ont pas besoin d’être modifiés.
Pour les données à modifier, accorder les permissions au compte de l’application, sans rendre tous les fichiers accessibles à tous.
Lorsque l’application le permet, rendre sa racine en lecture seule et prévoir séparément les dossiers où elle doit écrire, avec un volume dédié ou un tmpfs.Ces emplacements gardent leurs propres permissions.
Sauvegarder les données et vérifier leur restauration.La persistance lors du remplacement d’un conteneur ne remplace pas une sauvegarde.
tmpfs : ce stockage temporaire utilise la mémoire, sans passer par la couche écrivable du conteneur. Selon la configuration de l’hôte, ses données peuvent être concernées par le swap. Ce mécanisme ne garantit donc pas à lui seul leur confidentialité.
9.10.4 Exemple : NGINX lit, MariaDB écrit
NGINX peut servir le contenu du site depuis un bind mount en lecture seule.Une compromission de NGINX ne permet alors pas de modifier ce contenu par ce montage ; elle peut toutefois exposer les fichiers qu’il est autorisé à lire.
MariaDB doit pouvoir écrire dans son volume de données.Sa protection repose sur les droits du compte, les services autorisés à y accéder et les sauvegardes.
→ Le bon choix dépend de ce que le service doit réellement lire ou modifier.
9.11 Ressources, disponibilité et exploitation
9.11.1 Contexte : les conteneurs partagent les ressources de l’hôte
Les applications utilisent la mémoire, le processeur, des processus et de l’espace disque. Plusieurs conteneurs peuvent se partager le même hôte.La disponibilité d’un service dépend donc aussi de la consommation des autres et de la maintenance de cet hôte.
9.11.2 Problèmes : une application peut épuiser les ressources communes
Une consommation excessive de mémoire, de CPU ou de processus peut perturber les autres services.
Les journaux, images et volumes peuvent remplir le disque.Les limites CPU et mémoire ne fixent pas automatiquement leur taille maximale.
Une politique de redémarrage peut relancer un processus sans corriger sa panne, une faille ou une corruption des données.Un healthcheck mesure un état de santé défini ; ce n’est pas un audit de sécurité.
Après une compromission, redémarrer le même conteneur peut remettre en service un état encore dangereux.
9.11.3 Réponses : limiter, surveiller et maintenir
Dimensionner le CPU et la mémoire selon le rôle du service ; limiter le nombre de processus si nécessaire.Une limite mémoire peut provoquer l’arrêt d’un processus : le service doit pouvoir reprendre correctement.
Prévoir la rotation ou la collecte des journaux et surveiller l’espace utilisé par les images et volumes.
Maintenir l’hôte, son noyau, Docker, les outils d’administration et les images applicatives.
Surveiller les événements Docker, les journaux applicatifs et ceux de l’hôte, sans y enregistrer les secrets.
Préparer des sauvegardes adaptées aux applications et vérifier leur restauration.
9.11.4 Comprendre la reprise après une compromission
La remise en service ne se limite pas à un redémarrage. Elle nécessite plusieurs actions complémentaires :
Évaluer la portée de l’incident et contenir les accès.
Préserver les éléments utiles à l’analyse.
Révoquer les secrets exposés.
Repartir d’une image corrigée et d’un environnement vérifié.
Choisir et contrôler les données restaurées : une sauvegarde peut contenir un état déjà compromis.
9.12 Lire une configuration durcie sans la recopier aveuglément
9.12.1 Contexte : lire l’intention avant les options
Une configuration durcie associe plusieurs protections à une application donnée. Chaque option doit répondre à un besoin ou à un risque identifié.L’exemple ci-dessous décrit une API conçue pour fonctionner sans privilèges et écouter sur le port 8080. L’image exemple.invalid/formation/api:1.0 est fictive : cet extrait sert à comprendre les choix, pas à être déployé tel quel.
9.12.2 Problèmes : une configuration copiée peut être inadaptée
Un utilisateur non-root peut ne pas avoir accès aux fichiers nécessaires à l’application.
La lecture seule ou le retrait des capabilities peut bloquer une application qui a besoin de certaines écritures ou opérations.
Des limites trop basses peuvent provoquer des ralentissements ou des arrêts.
Un port publié localement ne remplace pas l’authentification ; les conteneurs du même réseau peuvent conserver un accès direct.
9.12.3 Réponses : associer chaque réglage à son effet
Les fichiers et l’application doivent être compatibles avec ce compte.
Limiter les écritures
Racine en lecture seule et espace temporaire borné.
L’application peut toujours écrire dans les emplacements explicitement ouverts.
Contenir la consommation
Mémoire.
CPU et nombre de processus bornés.
Ces valeurs sont illustratives ; elles ne limitent pas à elles seules les données et les logs sur disque.
Limiter l’entrée réseau
Port publié sur la boucle locale IPv4.
Les autres services du réseau Docker peuvent avoir un accès direct ; aucune authentification n’est ajoutée.
Les profils Seccomp et AppArmor ne sont pas désactivés dans cet exemple. Leur application dépend de leur prise en charge et de la configuration de l’hôte.→ Les valeurs de CPU, mémoire et nombre de processus sont illustratives. Elles doivent correspondre au fonctionnement attendu de l’application.
9.12.4 Comprendre ce que le fragment ne couvre pas
La fourniture et le renouvellement des secrets.
La persistance des données et leurs sauvegardes.
La protection de l’accès au moteur Docker.
Les mises à jour, les journaux et la surveillance.
Une configuration durcie se comprend avec l’ensemble de son environnement. Les options affichées ne constituent pas à elles seules une garantie de sécurité.
9.13 Ce qu’il faut retenir
9.13.1 Contexte : relier les protections entre elles
La sécurité concerne toute la chaîne : l’hôte et le moteur, les images, la configuration des conteneurs et leur exploitation.Les protections se complètent : elles ne traitent pas toutes le même problème.
Les conteneurs partagent un noyau : l’isolation dépend aussi de l’hôte.
Le socket Docker et les consoles sont des accès d’administration.
Utilisateur non-root, rootless et remappage agissent à des niveaux différents.
Capabilities, Seccomp, AppArmor et SELinux limitent des actions différentes.
9.13.2 Problèmes : éviter les fausses garanties
Un accès complet au moteur reste puissant, même avec une connexion chiffrée.
Une image identifiée par un digest n’est pas nécessairement sûre.
Un port non publié peut rester joignable depuis un autre conteneur du même réseau.
Un montage en lecture seule ne rend pas les données confidentielles.
Un volume persistant n’est pas une sauvegarde.
9.13.3 Réponses : garder une grille de lecture commune
Identifier qui peut accéder au moteur et à ses outils.
Choisir des images de confiance, suivre leurs vulnérabilités et remplacer les conteneurs avec des images corrigées.
Donner à chaque service les secrets, réseaux, montages et pouvoirs dont il a besoin.
Limiter les ressources, maintenir les composants et surveiller leur fonctionnement.
Prévoir la sauvegarde, la restauration et la réponse à un incident.
→ Pour chaque protection, se demander : quel risque réduit-elle, et que laisse-t-elle encore possible ?
9.13.4 Questions de mémorisation
Pourquoi un socket monté avec :ro reste-t-il sensible ?
Un digest prouve-t-il qu’une image est sûre ?
Pourquoi une base sans port publié peut-elle rester accessible à d’autres conteneurs ?
Afficher les réponses
Le montage en lecture seule ne filtre pas les opérations demandées à l’API par le socket.
Le digest identifie un contenu précis ; la confiance et les vulnérabilités restent à évaluer.
Les conteneurs partageant son réseau peuvent généralement joindre directement son port d’écoute.
9.14 Documentation de référence
9.14.1 Contexte : utiliser les références officielles
Les références ci-dessous permettent de vérifier le fonctionnement d’une option et les conditions de son utilisation.Le cours donne les repères ; la documentation précise la syntaxe et les limites selon l’environnement.
9.14.2 Problèmes : un exemple peut concerner un autre environnement
Un exemple peut dépendre de la version de Docker, du système de l’hôte ou de la présence d’un mécanisme Linux.
Compose local et Docker Swarm n’offrent pas nécessairement les mêmes garanties, notamment pour les secrets.
Une option copiée sans son contexte peut supprimer une protection ou empêcher l’application de fonctionner.
9.14.3 Réponses : choisir la référence correspondant au besoin
Avant de reprendre un exemple, vérifier le composant concerné, les prérequis et les limites indiquées. Rechercher ensuite la documentation qui correspond à la question :