Au cours des précédents articles, j'avais déjà évoqué la sécurisation d'un hôte Docker au travers de la mise en place de certaines règles basiques de sécurité :
- Tout d'abord avec la mise en place de règles iptables :
- Et enfin avec la sécurisation de son hôte Docker avec notamment l'utilisation de l'utilitaire auditd :
Nous allons continuer cette série avec la mise en place d'éléments de sécurité sur le daemon Docker.
Installer Docker Bench Security
Pour rappel, nous allons utiliser l'excellent outil fourni par Docker pour tester la sécurité de son hôte Docker : Docker Bench Security. Vous trouverez une explication sur cet outil dans l'article sur Sécuriser son hôte Docker.
Dans un premier temps, nous allons cloner le dépôt git de l'utilitaire de sécurité fourni par Docker :
cd ~
git clone https://github.com/docker/docker-bench-security.git
Tous les fichiers de l'utilitaire sont donc maintenant dans votre répertoire HOME
sous le dossier docker-bench-security
.
Accédons à ce répertoire : cd docker-bench-security
Vous pouvez lancer l'utilitaire avec la commande suivante :
sudo ./docker-bench-security.sh
Sécuriser le daemon Docker
Dans le cadre de cet article, je vais m'intéresser uniquement au retour lié au daemon Docker. Pour analyser cette partie :
sudo ./docker-bench-security.sh -c docker_daemon_configuration
Sans aucune modification préalable, vous devriez avoir un retour de ce type :
[INFO] 2 - Docker daemon configuration
[WARN] 2.1 - Ensure network traffic is restricted between containers on the default bridge
[WARN] 2.8 - Enable user namespace support
[WARN] 2.11 - Ensure that authorization for Docker client commands is enabled
[WARN] 2.12 - Ensure centralized and remote logging is configured
[WARN] 2.13 - Ensure live restore is Enabled
[WARN] 2.14 - Ensure Userland Proxy is Disabled
[WARN] 2.17 - Ensure containers are restricted from acquiring new privileges
[INFO] Checks: 17
[INFO] Score: 1
J'ai délibérément omis les lignes PASS
afin de nous concentrer sur les modifications à apporter.
1. Ensure network traffic is restricted between containers on the default bridge
Il est possible de restreindre les communications entre les containers sur l'interface bridge par défaut créé par Docker. Cela permet d'isoler nativement les containers au niveau réseau. Il sera alors nécessaire de rendre l'échange entre container explicite avec l'utilisation de l'option --link
pour la commande Docker, ou avec le paramètre links
dans un fichier de configuration Docker Compose.
L' avantage de cette option est que si un attaquant compromet un conteneur, il aura plus de mal à trouver et à attaquer d'autres conteneurs sur le même hôte.
🚩 Je recommande fortement l'utilisation de cette option. Il est vraiment important d'isoler le réseau de vos containers.
Vous pouvez l'activer dans le fichier daemon.json
avec la ligne suivante :
{
"icc": false
}
2. Enable user namespace support
Les namespaces offrent une isolation supplémentaire pour les processus s'exécutant dans vos conteneurs. Le remappage de l'espace de noms des utilisateurs, permet aux processus de s'exécuter en tant que root dans un conteneur, tout en étant remappés vers un utilisateur moins privilégié sur l'hôte.
Vous pouvez activer cette option dans le fichier de configuration avec la ligne suivante : userns-remap": "default"
En définissant le paramètre par default
, Docker va créer un utilisateur dockermap sur lequel les utilisateurs de conteneurs seront remappés. Vous pouvez vérifier que l'utilisateur dockremap a été créé à l'aide de la commande id:
sudo id dockremap
Vous devriez obtenir un retour similaire :
uid=111(dockremap) gid=115(dockremap) groups=115(dockremap)
Si vous le souhaitez, vous pouvez changer l'utilisateur utilisé pour le remap. Il vous suffit alors de spécifier l'utilisateur et le groupe en lieu et place de default
dans le fichier de configuration :
{
userns-remap": "user:group"
}
3. Ensure that authorization for Docker client commands is enabled
Ce retour est très mal documenté sur internet... Beaucoup de tutoriel s'attarde sur l'utilisation de Docker à distance, directement sur la socket TCP ou via SSH. Mais je vous l'assure, il n'en est rien.
La preuve dans le code même du test :
# 2.11
check_2_11() {
id_2_11="2.11"
desc_2_11="Ensure that authorization for Docker client commands is enabled"
check_2_11="$id_2_11 - $desc_2_11"
starttestjson "$id_2_11" "$desc_2_11"
totalChecks=$((totalChecks + 1))
if get_docker_configuration_file_args 'authorization-plugins' | grep -v '\[]'; then
pass "$check_2_11"
resulttestjson "PASS"
currentScore=$((currentScore + 1))
elif get_docker_effective_command_line_args '--authorization-plugin' | grep "authorization-plugin" >/dev/null 2>&1; then
pass "$check_2_11"
resulttestjson "PASS"
currentScore=$((currentScore + 1))
else
warn "$check_2_11"
resulttestjson "WARN"
currentScore=$((currentScore - 1))
fi
}
Globalement on vérifie ici l'utilisation de plugins d’autorisation !
Et oui Docker propose un système de gestion de droit au travers de plugin d’autorisation. Exemple :
- Alice peut éxecuter toutes les commandes Docker :
{"name":"policy_1","users":["alice"],"actions":[""]}
- Bob peut uniquement lire les logs et effectuer un top sur les containers :
{"name":"policy_4","users":["Bob"],"actions":["container_logs","container_top"]}
Il est possible donc de gérer finement les droits d'utilisation de Docker en fonction des utilisateurs.
Le soucis est que cette fonctionnalité est plutôt mal expliquée et qu'il n'existe pas de plugin d’autorisation par défaut conçu par Docker.
Si cette solution vous intéresse fortement, vous pouvez regarder du côté de ce projet de plugin : https://github.com/twistlock/authz
4. Ensure centralized and remote logging is configured
Vous pouvez facilement utiliser un serveur syslog dans la configuration du daemon Docker : "log-driver": "syslog"
ou utiliser un serveur syslog centralisé : "log-opts": { "syslog-address": "udp://198.168.100.100:514" }
.
Cette sécurité doit vous permettre d'externaliser vos journaux afin d'éviter qu'un attaquant puisse venir les altérer. Et ainsi effacer les traces de son passage ou dissimuler ses actions.
Bien évidemment vous pouvez également utiliser d'autres systèmes de récupération des journaux, splunk
, fluentd
ou encore gelf
peuvent vous permettre d'envoyer les logs à un autre service d’agrégation avant de les stocker dans des bases externes comme elasticsearch
par exemple.
5. Ensure live restore is Enabled
Sûrement l'option que vous allez immédiatement mettre en place ! L'option "live-restore": true
permet à vos containers de continuer à fonctionner même lorsque votre daemon Docker est arrêté.
Cela va grandement faciliter vos mises à jour de Docker-CE ! Je vous conseille fortement d'ajouter cette option :
{
"live-restore": true
}
De plus en cas de "crash" du daemon Docker, aucune question. Tout fonctionne.
6. Ensure Userland Proxy is Disabled
Les versions modernes de Docker prennent en charge l'utilisation d'iptables. L'idée est simple : plutôt qu'une application d'espace utilisateur mandatant les connexions au nom de votre conteneur, en l’occurrence docker-proxy
, le noyau est configuré pour les modifier via des règles NAT et les acheminer de manière appropriée directement. Cette fonctionnalité n'est cependant pas activée par défaut.
{
"userland-proxy": false,
"iptables": true
}
🚩 Si elle n'est pas activée par défaut, ce n'est pas forcément pour rien. Il existe des cas de bug, notamment avec des kernels non à jour. Il est préférable de tester cette option avant de l'utiliser en production.
7. Ensure containers are restricted from acquiring new privileges
L'escalation de privilèges au sein d'un environnement Docker est possible. Autant donc le prévenir au travers de la configuration : "no-new-privileges": true
Attention cette option garantit que votre container ne pourra pas recupérer de nouveau privilège, mais si vous le lancez déjà avec l'option --privileged
ça ne changera rien...
Il est important au delà de cette option de vous assurer que vos containers n'ont pas de droits trop importants.
Validation des modifications
Voici la configuration du fichier /etc/docker/daemon.json complète :
{
"icc": false,
"userns-remap": "default",
"userland-proxy": false,
"iptables" : true,
"no-new-privileges": true,
"log-driver" : "syslog",
"live-restore": true
}
Les modifications seront prises en compte après le redémarrage de votre daemon docker : sudo systemctl restart docker
Vous devriez pouvoir valider tout ces points de corrections avec un nouvel audit :
Au travers de ces articles, nous avons pu sécuriser notre environnement Docker. Ces modifications nous permettent d'avoir un environnement plus sécurisé mais est-ce suffisant ?
En terme de sécurité système, je pars du principe que rien n'est jamais suffisant. Un jour ou l'autre, vous commettrez l'erreur qui permettra un piratage. L'essentiel est de mettre en place suffisamment de contre-mesure afin d'en atténuer au maximum les effets.
En somme, il vous reste un dernier recours :
Monitoring, Monitoring, Monitoring !
En tout cas n'hésitez pas à m'apporter des remarques ou des commentaires sur Twitter, c'est toujours un plaisir d'avoir des retours et des échanges !