## Introduction Les pilotes de journalisation Docker contrôlent la manière dont les journaux des conteneurs sont capturés, stockés et transmis. Ils constituent un élément essentiel de la sécurité des conteneurs, car les journaux contiennent souvent des données sensibles telles que des erreurs applicatives, des identifiants utilisateurs, des URL internes et des traces de pile. Une mauvaise configuration des pilotes de journalisation peut exposer ces données par le biais de permissions de fichiers trop permissives, de points de terminaison distants non authentifiés ou de transports non chiffrés. Ce guide se concentre sur le renforcement des pilotes de journalisation Docker pour les développeurs, les consultants DevOps et les équipes techniques de startups. Il relie les concepts de sécurité fondamentaux — le durcissement, le contrôle d'accès, la gestion des secrets et les permissions — à des commandes Docker spécifiques, des extraits de configuration, des sorties attendues, des signaux d'échec et des décisions de récupération. Chaque étape est conçue pour être vérifiée en toute sécurité dans un environnement de test avant d'être appliquée en production. L'objectif est la sécurité opérationnelle : observer l'état actuel avant d'apporter des modifications, limiter le rayon d'impact de chaque changement, éviter de mettre de vrais secrets dans les fichiers de configuration ou l'historique du shell, vérifier le résultat avec une sortie concrète et documenter un chemin de récupération si l'état attendu n'est pas atteint. ## Inventaire des versions et de l'environnement Avant de modifier toute configuration de journalisation, documentez l'environnement Docker. Cet inventaire évite d'appliquer des commandes au mauvais hôte, d'utiliser des options non prises en charge ou de manquer des prérequis tels qu'une version Docker ou un plugin spécifique. Exécutez les commandes en lecture seule suivantes pour capturer l'état actuel : ```bash # Version de Docker et version de l'API docker version --format '{{.Server.Version}}' # Exemple de sortie : 24.0.7 # Info Docker : pilote de journalisation, pilote de stockage, répertoire racine docker info --format 'Pilote de journalisation : {{.LoggingDriver}} | Pilote de stockage : {{.Driver}} | Racine Docker : {{.DockerRootDir}}' # Exemple de sortie : Pilote de journalisation : json-file | Pilote de stockage : overlay2 | Racine Docker : /var/lib/docker # Lister les conteneurs en cours d'exécution avec noms, statut et ports docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" # Exemple de sortie : # NOMS STATUT PORTS # web-server Actif 2 h 0.0.0.0:8080->80/tcp # postgres-db Actif 2 h 5432/tcp ``` Pour un conteneur spécifique, inspectez sa configuration de journalisation actuelle : ```bash docker inspect --format '{{ json .HostConfig.LogConfig }}' # Exemple de sortie : {"Type":"json-file","Config":{"max-size":"10m","max-file":"3"}} ``` Si le conteneur fait partie d'un projet Compose, utilisez : ```bash docker compose ps docker compose logs -f --tail 50 docker compose exec sh ``` N'exécutez jamais ces commandes sur un hôte de production sans les tester d'abord sur un environnement de préproduction ou local. Enregistrez la sortie et l'horodatage avant d'effectuer toute modification. Si vous devez inspecter des journaux susceptibles de contenir des secrets, évitez de les afficher directement dans le terminal. Redirigez-les vers un fichier avec des permissions restrictives : ```bash docker logs > /tmp/app-logs.txt 2>&1 chmod 600 /tmp/app-logs.txt ``` Confirmez également où les données persistantes sont stockées. Un volume nommé comme `app_data:/var/lib/app` est géré par Docker et survit aux recréations de conteneurs. Un montage bind comme `./data:/var/lib/app` mappe un répertoire de l'hôte et est utile pour le développement, mais peut introduire des problèmes de permissions, de portabilité et de sauvegarde. Avant de modifier la journalisation, assurez-vous de savoir si les journaux eux-mêmes sont stockés sur un volume, un montage bind ou le système de fichiers du conteneur, car cela affecte la stratégie de rétention et de sauvegarde. Un test de redémarrage rapide vérifie que les données applicatives et la configuration de journalisation survivent à une recréation du conteneur : ```bash docker stop docker rm docker run -d --name [mêmes options] docker logs --tail 20 ``` Si les journaux sont perdus après ce test, le conteneur écrivait probablement dans sa couche inscriptible au lieu d'un emplacement persistant, ce qui est un problème de durcissement distinct mais lié. ## Comprendre les pilotes de journalisation Docker et leurs implications de sécurité Docker prend en charge plusieurs pilotes de journalisation intégrés : `json-file`, `journald`, `syslog`, `gelf`, `fluentd`, `awslogs`, `splunk` et d'autres. Le pilote par défaut est `json-file`, qui écrit les journaux sous forme d'objets JSON dans le système de fichiers de l'hôte. Les considérations de sécurité varient selon le pilote : - **json-file** : les journaux sont stockés sous forme de fichiers dans `/var/lib/docker/containers//`. Les permissions des fichiers sont contrôlées par le paramètre umask du démon Docker, souvent `022`, ce qui rend les fichiers lisibles par tous les utilisateurs locaux. C'est une cible courante de durcissement des permissions. - **syslog** : les journaux sont envoyés à un démon syslog local ou distant. En cas d'utilisation d'UDP, les journaux ne sont pas chiffrés et peuvent être usurpés ou interceptés sur le réseau. En cas d'utilisation de TCP, l'authentification est souvent absente à moins que TLS ne soit configuré. - **gelf (Graylog Extended Log Format)** : envoie les journaux en UDP ou TCP à un serveur Graylog. UDP n'est pas fiable et n'est pas sécurisé sans isolation réseau. TCP peut être sécurisé avec TLS. - **fluentd** : envoie les journaux à un agrégateur Fluentd. Un TCP ou HTTP simple sans TLS expose les données de journalisation en transit. - **splunk** : envoie les journaux à Splunk via HTTP Event Collector (HEC). Il nécessite un jeton, mais le canal doit être chiffré avec HTTPS. - **awslogs** : envoie les journaux à Amazon CloudWatch Logs. Il nécessite des informations d'identification IAM avec les autorisations appropriées ; coder en dur les informations d'identification dans les variables d'environnement du conteneur est une erreur courante. Renforcer un pilote de journalisation signifie choisir le bon pilote pour votre environnement, verrouiller les permissions des fichiers ou le transport réseau, éviter l'exposition des informations d'identification et s'assurer que la rotation et la rétention des journaux ne créent pas de failles de sécurité. ## Chemin de configuration sûr Le moyen le plus sûr de modifier le pilote de journalisation ou ses options est de modifier le fichier de configuration du démon Docker (`/etc/docker/daemon.json`) ou la configuration par conteneur. Avant de modifier, sauvegardez le fichier actuel et validez sa syntaxe. **Étape 1 : Observer la configuration actuelle du démon** ```bash cat /etc/docker/daemon.json # Exemple de sortie : # { # "log-driver": "json-file", # "log-opts": { # "max-size": "10m", # "max-file": "3" # } # } ``` Si le fichier n'existe pas, Docker utilise les paramètres par défaut. **Étape 2 : Sauvegarder et modifier la configuration** ```bash sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak.$(date +%Y%m%d) sudo nano /etc/docker/daemon.json ``` Par exemple, pour définir `json-file` avec des limites de rotation et un umask par défaut restrictif, vous pouvez configurer : ```json { "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "5", "mode": "non-blocking", "max-buffer-size": "4m" } } ``` Remarque : `mode` et `max-buffer-size` sont disponibles pour `json-file` dans Docker 20.10 et versions ultérieures. Définir `mode` sur `non-blocking` empêche le conteneur d'être bloqué si le pilote de journalisation ne peut pas suivre le rythme, mais cela peut entraîner la perte de journaux sous une charge importante. C'est un compromis entre disponibilité et exhaustivité des journaux. Pour modifier les permissions des fichiers, vous ne pouvez pas définir directement l'umask dans `daemon.json`. À la place, définissez l'umask à l'échelle du système pour le service du démon Docker. Pour les systèmes avec systemd, créez un fichier de remplacement : ```bash sudo mkdir -p /etc/systemd/system/docker.service.d sudo tee /etc/systemd/system/docker.service.d/override.conf < /dev/null # Trouver le fichier journal du conteneur terminé (utiliser l'ID du conteneur) container_id=$(docker ps -lq) ls -l /var/lib/docker/containers/$container_id/*-json.log # Permissions attendues : -rw-r----- 1 root root ... ``` Si les permissions sont toujours `644`, le remplacement de l'umask n'a pas été appliqué. Vérifiez `systemctl show docker -p UMask` et redémarrez à nouveau. ## Durcissement du pilote json-file Le pilote `json-file` est le plus couramment utilisé et souvent négligé en matière de sécurité. Voici des étapes de durcissement spécifiques : **Activer la rotation des journaux** Des fichiers journaux non bornés peuvent remplir le disque et provoquer un déni de service. Définissez `max-size` et `max-file` pour chaque conteneur, soit globalement dans `daemon.json`, soit par conteneur dans `docker run` : ```bash docker run -d \ --log-driver json-file \ --log-opt max-size=10m \ --log-opt max-file=3 \ --name web \ nginx:alpine ``` Dans Compose : ```yaml services: web: image: nginx:alpine logging: driver: json-file options: max-size: "10m" max-file: "3" ``` Vérifiez la rotation en remplissant le journal et en vérifiant que les anciens fichiers sont supprimés. Vous pouvez forcer la rotation en envoyant des signaux si l'application journalise beaucoup, ou simplement attendre que la limite de taille soit atteinte. **Définir des permissions restrictives** Comme décrit ci-dessus, utilisez un umask de `0027` ou `0077` pour le démon Docker afin d'empêcher les autres utilisateurs locaux de lire les journaux. C'est particulièrement important sur les hôtes partagés. Après l'application, vérifiez avec : ```bash find /var/lib/docker/containers -name "*-json.log" -ls ``` **Éviter de stocker des données sensibles dans les journaux des conteneurs** Même avec des permissions restrictives, les journaux peuvent être expédiés vers des systèmes centraux. Ne journalisez jamais des numéros de carte de crédit complets, des mots de passe ou des jetons. Utilisez la rédaction des journaux dans le code de l'application ou un sidecar de journalisation qui peut nettoyer les données. Par exemple, si vous utilisez un proxy de journalisation comme Fluentd, vous pouvez appliquer un filtre pour masquer les champs sensibles : ```xml @type record_transformer enable_ruby true message ${record["message"].gsub(/\b\d{4}-\d{4}-\d{4}-\d{4}\b/, '[REDACTED]')} ``` Cela dépasse Docker lui-même, mais fait partie de la posture globale de sécurité de la journalisation. ## Sécurisation des pilotes de journalisation distants Si vous utilisez un pilote de journalisation distant comme `syslog`, `gelf`, `fluentd`, `splunk` ou `awslogs`, considérez les éléments suivants : **Utiliser TLS lorsque cela est possible** Pour `syslog`, utilisez TCP avec TLS : ```bash docker run -d \ --log-driver syslog \ --log-opt syslog-address=tcp+tls://logs.example.com:6514 \ --log-opt syslog-tls-ca-cert=/etc/docker/ca.pem \ --log-opt syslog-tls-cert=/etc/docker/cert.pem \ --log-opt syslog-tls-key=/etc/docker/key.pem \ --name app \ myapp:latest ``` Pour `gelf` en UDP, utilisez un réseau isolé dédié ; pour TCP, utilisez TLS si le serveur le prend en charge. Pour `fluentd`, utilisez TLS : ```bash docker run -d \ --log-driver fluentd \ --log-opt fluentd-address=localhost:24224 \ --log-opt fluentd-async=true \ --log-opt fluentd-sub-second-precision=true \ --name app \ myapp:latest ``` Fluentd ne prend pas en charge nativement TLS dans le pilote Docker, vous devrez donc exécuter un proxy TLS devant Fluentd ou utiliser un sidecar. **Protéger les informations d'identification** Pour `splunk`, le jeton HEC est passé via `--log-opt splunk-token=...`. Ne mettez jamais le jeton dans un script shell ou un fichier Compose qui est versionné. Utilisez les secrets Docker ou des variables d'environnement provenant d'un magasin sécurisé : ```bash echo "valeur-du-jeton-splunk" | docker secret create splunk_token - docker service create \ --name app \ --secret splunk_token \ --log-driver splunk \ --log-opt splunk-token-source=secret \ --log-opt splunk-token-secret=splunk_token \ myapp:latest ``` Pour `awslogs`, stockez les informations d'identification AWS dans des rôles IAM pour EC2 ou ECS plutôt que dans des variables d'environnement. Si vous devez utiliser des clés statiques, utilisez les secrets Docker Compose : ```yaml services: app: image: myapp logging: driver: awslogs options: awslogs-region: us-east-1 awslogs-group: my-log-group awslogs-stream: my-stream secrets: - aws_creds secrets: aws_creds: file: ./aws_credentials ``` À l'intérieur du conteneur, les informations d'identification sont disponibles sous forme de fichier, pas dans l'environnement. **Isolation réseau** Faites toujours transiter le trafic de journalisation distant sur un réseau privé ou un VPN. Pour le développement local, utilisez un réseau Docker dédié et restreignez la sortie avec des règles de pare-feu. ## Contrôle d'accès et permissions pour les fichiers journaux Sur l'hôte, les fichiers journaux sous `/var/lib/docker/containers` ne doivent être accessibles qu'à l'utilisateur root et au groupe Docker. Le groupe `docker` par défaut peut avoir un accès en lecture, mais demandez-vous si tous les utilisateurs de ce groupe en ont besoin. Vous pouvez resserrer l'appartenance au groupe : ```bash grep docker /etc/group # Exemple de sortie : docker:x:999:alice,bob # Retirer un utilisateur du groupe docker sudo gpasswd -d bob docker ``` Si un processus non root a besoin de lire les journaux, utilisez `sudo` avec une commande spécifique plutôt que d'accorder une appartenance large au groupe. Pour les conteneurs qui écrivent des journaux dans un répertoire monté (via un montage bind), assurez-vous que le conteneur s'exécute en tant qu'utilisateur non root et que le répertoire monté a une propriété et des permissions appropriées sur l'hôte : ```bash docker run -d \ --user 1000:1000 \ -v /var/log/myapp:/var/log/app \ --name app \ myapp:latest ``` Sur l'hôte, définissez `/var/log/myapp` pour qu'il appartienne à l'UID 1000 et avec le mode `700` ou `750` selon le cas. ## Vérification et diagnostics Après avoir apporté des modifications, vérifiez soigneusement. Utilisez les contrôles suivants : **1. Confirmer le pilote de journalisation actif pour un conteneur** ```bash docker inspect --format '{{.HostConfig.LogConfig.Type}}' # Sortie attendue : json-file (ou votre pilote configuré) ``` **2. Confirmer les options du pilote** ```bash docker inspect --format '{{json .HostConfig.LogConfig.Config}}' # Exemple de sortie : {"max-file":"3","max-size":"10m"} ``` **3. Tester la génération et la rotation des journaux** Pour `json-file`, vous pouvez forcer la rotation en envoyant un signal `USR1` au conteneur si l'application le gère, ou simplement générer suffisamment de journaux. Par exemple, avec un conteneur busybox : ```bash docker run -d --name logtest --log-opt max-size=1k --log-opt max-file=2 busybox sh -c 'while true; do echo "Ligne de log $(date)"; sleep 0.1; done' ``` Attendez que le fichier journal dépasse 1 Ko, puis vérifiez qu'il ne reste que deux fichiers journaux : ```bash ls -l /var/lib/docker/containers/$(docker inspect logtest --format '{{.Id}}')/*-json.log # Attendu : deux fichiers, par exemple container-json.log et container-json.log.1 ``` **4. Vérifier les permissions des fichiers journaux** ```bash stat -c '%a %U %G %n' /var/lib/docker/containers/$(docker inspect logtest --format '{{.Id}}')/*-json.log # Sortie attendue : 640 root root ... ``` **5. Tester la connectivité du pilote de journalisation distant** Si vous utilisez `syslog` ou `gelf`, envoyez un journal de test et vérifiez qu'il apparaît à la destination : ```bash docker run --rm --log-driver syslog --log-opt syslog-address=udp://syslog-server:514 alpine echo "Journal de test $(date)" ``` Vérifiez ensuite les journaux du serveur syslog pour ce message. **6. Vérifier les journaux perdus** Si vous utilisez le mode `non-blocking` pour `json-file`, surveillez le démon pour les dépassements de tampon : ```bash journalctl -u docker | grep -i "dropping logs" ``` Si vous voyez des messages concernant la perte de journaux, augmentez `max-buffer-size` ou repassez en mode bloquant pour garantir l'exhaustivité. ## Modes d'échec et récupération Des choses peuvent mal tourner lors de la modification de la configuration de journalisation. Voici les modes d'échec courants et comment récupérer : **Un daemon.json malformé empêche Docker de démarrer** Symptômes : `systemctl restart docker` échoue, `docker ps` renvoie « Cannot connect to the Docker daemon ». Récupération : ```bash sudo cp /etc/docker/daemon.json.bak.YYYYMMDD /etc/docker/daemon.json sudo systemctl restart docker ``` Si vous n'avez pas de sauvegarde, supprimez le fichier (après en avoir enregistré une copie ailleurs) et redémarrez Docker. Reconstruisez ensuite la configuration étape par étape, en validant le JSON après chaque modification. **Le conteneur ne démarre pas avec le pilote de journalisation spécifié** Symptômes : `docker run` affiche une erreur comme « failed to initialize logging driver: ... » ou le conteneur se termine immédiatement. Diagnostic : ```bash docker logs # Exemple d'erreur : "Failed to connect to syslog server" ``` Récupération : - Vérifiez les options du pilote de journalisation pour les fautes de frappe. - Vérifiez que le point de terminaison de journalisation distant est joignable et que les informations d'identification sont correctes. - Basculez temporairement le conteneur vers `json-file` pour confirmer que l'application elle-même fonctionne. **Les journaux ne sont pas expédiés au serveur distant** Symptômes : aucun journal n'apparaît à la destination. Diagnostic : - Consultez les journaux du démon Docker : `journalctl -u docker -f` - Testez la connectivité réseau depuis l'hôte Docker : `nc -vz ` - Vérifiez les règles de pare-feu. - Pour TLS, validez les certificats et le nom d'hôte. **La rotation des journaux ne fonctionne pas** Symptômes : le fichier journal dépasse `max-size` et n'est pas pivoté. Diagnostic : - Confirmez que `max-size` est correctement défini dans la configuration de journalisation du conteneur. - Notez que la rotation de `json-file` ne se produit que lorsque Docker écrit une nouvelle entrée de journal ; si le conteneur est inactif, le fichier peut dépasser max-size jusqu'à ce que de nouveaux journaux soient écrits. - Vérifiez que l'option `max-file` n'est pas définie sur 0. **Les changements de permissions empêchent l'application d'écrire des journaux** Si vous avez modifié l'umask et qu'un conteneur s'exécutant en tant que non-root ne peut pas écrire dans son fichier journal (cela est peu probable car Docker crée le fichier journal en tant que root, mais possible avec des journaux montés en bind), ajustez les permissions du montage bind ou exécutez le conteneur avec des identifiants d'utilisateur et de groupe appropriés. ## Liste de contrôle des opérations Utilisez cette liste de contrôle avant et après avoir apporté des modifications au pilote de journalisation : | Étape | Action | Commande / Contrôle | Résultat attendu | |------|--------|---------------------|------------------| | 1 | Sauvegarder la configuration actuelle du démon Docker | `cp /etc/docker/daemon.json /etc/docker/daemon.json.bak` | Le fichier de sauvegarde existe | | 2 | Enregistrer le pilote de journalisation actuel | `docker info --format '{{.LoggingDriver}}'` | La sortie correspond à l'inventaire | | 3 | Enregistrer les configurations de journalisation des conteneurs | `docker inspect --format '{{json .HostConfig.LogConfig}}'` | Configuration documentée | | 4 | Appliquer d'abord le changement à un conteneur | `docker run --log-driver ... --log-opt ...` | Le conteneur démarre | | 5 | Vérifier que le nouveau pilote est actif | `docker inspect --format '{{.HostConfig.LogConfig.Type}}'` | Nom du pilote attendu | | 6 | Vérifier les options | `docker inspect --format '{{json .HostConfig.LogConfig.Config}}'` | Options attendues | | 7 | Générer des journaux de test | `docker exec sh -c 'echo test'` | L'entrée de journal apparaît | | 8 | Vérifier les permissions des fichiers journaux | `stat -c '%a' /var/lib/docker/containers//*-json.log` | 640 ou plus strict | | 9 | Tester la rotation | Écrire des journaux jusqu'à max-size, vérifier le nombre de fichiers | Seul le nombre max-file reste | | 10 | Tester la livraison à distance (le cas échéant) | Envoyer un journal de test, vérifier le récepteur | Le journal apparaît à la destination | | 11 | Vérifier les journaux du démon Docker pour les erreurs | `journalctl -u docker -n 100 --no-pager` | Aucune erreur inattendue | | 12 | Documenter le changement et le plan de retour en arrière | Mettre à jour le runbook | Runbook mis à jour | Pour chaque étape, si le résultat réel diffère du résultat attendu, arrêtez-vous et enquêtez avant de continuer. Revenez à la configuration de sauvegarde si nécessaire. ## Conclusion Le renforcement des pilotes de journalisation Docker est un élément essentiel de la sécurité des conteneurs. En sélectionnant soigneusement le pilote approprié, en appliquant la rotation des journaux, en restreignant les permissions des fichiers, en protégeant les informations d'identification et en utilisant un transport chiffré pour la journalisation à distance, vous pouvez réduire considérablement le risque d'exposition des données et d'attaques par déni de service. Ce guide a fourni une approche systématique : inventorier l'environnement, comprendre les implications de sécurité de chaque pilote, apporter des modifications de configuration contrôlées, vérifier les résultats avec des commandes concrètes et des sorties attendues, et se préparer à l'échec avec des étapes de récupération documentées. Comme prochaine étape, choisissez une mesure de durcissement à faible risque de cet article, comme l'activation de la rotation des journaux avec des permissions restrictives. Implémentez-la dans un environnement de préproduction, exécutez les contrôles de vérification et documentez le résultat. Une fois confiant, appliquez le même changement à la production pendant une fenêtre de maintenance, en suivant la liste de contrôle des opérations. N'oubliez pas que la sécurité est un processus continu. Examinez régulièrement les configurations de journalisation, faites tourner les informations d'identification et tenez-vous informé des nouvelles fonctionnalités et bonnes pratiques de Docker. Un flux de travail technique fiable rend les échecs visibles, protège les valeurs sensibles, limite les modifications à la ressource prévue et définit la vérification de la récupération avant qu'un incident ne force une décision.