Sécurité du moteur Docker
Il y a quatre domaines majeurs à considérer lors de l'examen de la sécurité Docker :
- La sécurité intrinsèque du noyau et son support pour les espaces de noms et les cgroups
- La surface d'attaque du démon Docker lui-même
- Les failles dans le profil de configuration du conteneur, soit par défaut, soit lorsqu'il est personnalisé par les utilisateurs.
- Les fonctionnalités de sécurité "durcies" du noyau et comment elles interagissent avec les conteneurs.
Espaces de noms du noyau
Les conteneurs Docker sont très similaires aux conteneurs LXC, et ils ont
des fonctionnalités de sécurité similaires. Lorsque vous démarrez un conteneur avec
docker run
, en arrière-plan Docker crée un ensemble d'espaces de noms et de groupes de contrôle
pour le conteneur.
Les espaces de noms fournissent la première et plus directe forme d' isolation. Les processus s'exécutant dans un conteneur ne peuvent pas voir, et encore moins affecter, les processus s'exécutant dans un autre conteneur, ou dans le système hôte.
Chaque conteneur obtient également sa propre pile réseau, ce qui signifie qu'un conteneur n'obtient pas d'accès privilégié aux sockets ou interfaces d'un autre conteneur. Bien sûr, si le système hôte est configuré en conséquence, les conteneurs peuvent interagir entre eux par le biais de leurs interfaces réseau respectives — tout comme ils peuvent interagir avec des hôtes externes. Lorsque vous spécifiez des ports publics pour vos conteneurs ou utilisez des liens alors le trafic IP est autorisé entre les conteneurs. Ils peuvent se ping mutuellement, envoyer/recevoir des paquets UDP, et établir des connexions TCP, mais cela peut être restreint si nécessaire. D'un point de vue de l'architecture réseau, tous les conteneurs sur un hôte Docker donné sont assis sur des interfaces de pont. Cela signifie qu'ils sont comme des machines physiques connectées par un commutateur Ethernet commun ; ni plus, ni moins.
Quelle est la maturité du code fournissant les espaces de noms du noyau et le réseau privé ? Les espaces de noms du noyau ont été introduits entre la version du noyau 2.6.15 et 2.6.26. Cela signifie que depuis juillet 2008 (date de la version 2.6.26 ), le code des espaces de noms a été exercé et scruté sur un grand nombre de systèmes de production. Et il y a plus : la conception et l'inspiration pour le code des espaces de noms sont encore plus anciennes. Les espaces de noms sont en fait un effort pour réimplémenter les fonctionnalités d'OpenVZ de manière à ce qu'elles puissent être fusionnées dans le noyau principal. Et OpenVZ a été initialement publié en 2005, donc la conception et l'implémentation sont assez matures.
Groupes de contrôle
Les Groupes de Contrôle sont un autre composant clé des conteneurs Linux. Ils implémentent la comptabilité et la limitation des ressources. Ils fournissent de nombreuses métriques utiles, mais ils aident également à s'assurer que chaque conteneur obtient sa juste part de mémoire, CPU, E/S disque ; et, plus important encore, qu'un seul conteneur ne peut pas faire tomber le système en épuisant l'une de ces ressources.
Donc bien qu'ils ne jouent pas un rôle dans la prévention qu'un conteneur accède ou affecte les données et processus d'un autre conteneur, ils sont essentiels pour repousser certaines attaques de déni de service. Ils sont particulièrement importants sur les plateformes multi-locataires, comme les PaaS publics et privés, pour garantir un temps de fonctionnement (et performance) cohérent même lorsque certaines applications commencent à mal se comporter.
Les Groupes de Contrôle existent depuis un moment aussi : le code a été commencé en 2006, et initialement fusionné dans le noyau 2.6.24.
Surface d'attaque du démon Docker
Exécuter des conteneurs (et applications) avec Docker implique d'exécuter le
démon Docker. Ce démon nécessite des privilèges root
à moins que vous n'optiez pour
le mode Rootless, et vous devriez donc être conscient de
certains détails importants.
Tout d'abord, seuls les utilisateurs de confiance devraient être autorisés à contrôler votre
démon Docker. Ceci est une conséquence directe de certaines fonctionnalités puissantes de Docker.
Spécifiquement, Docker vous permet de partager un répertoire entre
l'hôte Docker et un conteneur invité ; et il vous permet de le faire
sans limiter les droits d'accès du conteneur. Cela signifie que vous
pouvez démarrer un conteneur où le répertoire /host
est le répertoire /
sur votre hôte ; et le conteneur peut altérer votre système de fichiers hôte
sans aucune restriction. Ceci est similaire à la façon dont les systèmes de virtualisation
permettent le partage de ressources de système de fichiers. Rien ne vous empêche de partager votre
système de fichiers racine (ou même votre périphérique bloc racine) avec une machine virtuelle.
Cela a une forte implication de sécurité : par exemple, si vous instrumentez Docker depuis un serveur web pour provisionner des conteneurs par le biais d'une API, vous devriez être encore plus prudent que d'habitude avec la vérification des paramètres, pour vous assurer qu'un utilisateur malveillant ne peut pas passer des paramètres conçus causant Docker à créer des conteneurs arbitraires.
Pour cette raison, le point de terminaison de l'API REST (utilisé par la CLI Docker pour communiquer avec le démon Docker) a changé dans Docker 0.5.2, et utilise maintenant un socket Unix au lieu d'un socket TCP lié sur 127.0.0.1 (ce dernier étant sujet aux attaques de falsification de requête inter-sites si vous exécutez Docker directement sur votre machine locale, en dehors d'une VM). Vous pouvez alors utiliser les vérifications de permissions Unix traditionnelles pour limiter l'accès au socket de contrôle.
Vous pouvez également exposer l'API REST sur HTTP si vous décidez explicitement de le faire. Cependant, si vous faites cela, soyez conscient des implications de sécurité mentionnées ci-dessus. Notez que même si vous avez un pare-feu pour limiter les accès au point de terminaison de l'API REST depuis d'autres hôtes du réseau, le point de terminaison peut toujours être accessible depuis les conteneurs, et cela peut facilement résulter en une escalade de privilèges. Par conséquent, il est obligatoire de sécuriser les points de terminaison API avec HTTPS et certificats. Exposer l'API démon sur HTTP sans TLS n'est pas permis, et une telle configuration cause l'échec précoce du démon au démarrage, voir Connexions TCP non authentifiées. Il est également recommandé de s'assurer qu'il n'est accessible que depuis un réseau de confiance ou VPN.
Vous pouvez également utiliser DOCKER_HOST=ssh://USER@HOST
ou ssh -L /path/to/docker.sock:/var/run/docker.sock
à la place si vous préférez SSH à TLS.
Le démon est également potentiellement vulnérable à d'autres entrées, telles que le
chargement d'images soit depuis le disque avec docker load
, soit depuis le réseau avec
docker pull
. À partir de Docker 1.3.2, les images sont maintenant extraites dans un
sous-processus chrooté sur les plateformes Linux/Unix, étant la première étape dans un effort plus large
vers la séparation des privilèges. À partir de Docker 1.10.0, toutes les images sont stockées et
accédées par les sommes de contrôle cryptographiques de leur contenu, limitant la
possibilité pour un attaquant de causer une collision avec une image existante.
Enfin, si vous exécutez Docker sur un serveur, il est recommandé d'exécuter exclusivement Docker sur le serveur, et de déplacer tous les autres services dans des conteneurs contrôlés par Docker. Bien sûr, il est bien de garder vos outils d'administration favoris (probablement au moins un serveur SSH), ainsi que les processus de surveillance/supervision existants, tels que NRPE et collectd.
Capacités du noyau Linux
Par défaut, Docker démarre les conteneurs avec un ensemble restreint de capacités. Qu'est-ce que cela signifie ?
Les capacités transforment la dichotomie binaire "root/non-root" en un
système de contrôle d'accès à grain fin. Les processus (comme les serveurs web) qui
ont juste besoin de se lier sur un port en dessous de 1024 n'ont pas besoin de s'exécuter comme root : ils
peuvent juste se voir accorder la capacité net_bind_service
à la place. Et il y a
de nombreuses autres capacités, pour presque tous les domaines spécifiques où les privilèges root
sont habituellement nécessaires. Cela signifie beaucoup pour la sécurité des conteneurs.
Les serveurs typiques exécutent plusieurs processus comme root
, y compris le démon SSH,
le démon cron
, les démons de journalisation, les modules du noyau, les outils de configuration réseau,
et plus. Un conteneur est différent, car presque toutes ces tâches sont
gérées par l'infrastructure autour du conteneur :
- L'accès SSH est typiquement géré par un seul serveur s'exécutant sur l'hôte Docker
cron
, quand nécessaire, devrait s'exécuter comme un processus utilisateur, dédié et adapté pour l'app qui a besoin de son service de planification, plutôt que comme une facilité à l'échelle de la plateforme- La gestion des journaux est également typiquement confiée à Docker, ou à des services tiers comme Loggly ou Splunk
- La gestion du matériel est non pertinente, ce qui signifie que vous n'avez jamais besoin d'
exécuter
udevd
ou des démons équivalents dans les conteneurs - La gestion réseau se passe en dehors des conteneurs, appliquant
la séparation des préoccupations autant que possible, ce qui signifie qu'un conteneur
ne devrait jamais avoir besoin d'effectuer des commandes
ifconfig
,route
, ou ip (sauf quand un conteneur est spécifiquement conçu pour se comporter comme un routeur ou pare-feu, bien sûr)
Cela signifie que dans la plupart des cas, les conteneurs n'ont pas besoin de privilèges root "réels" du tout. Et par conséquent, les conteneurs peuvent s'exécuter avec un ensemble de capacités réduit ; ce qui signifie que "root" dans un conteneur a beaucoup moins de privilèges que le "root" réel. Par exemple, il est possible de :
- Refuser toutes les opérations "mount"
- Refuser l'accès aux sockets raw (pour prévenir l'usurpation de paquets)
- Refuser l'accès à certaines opérations de système de fichiers, comme créer de nouveaux nœuds de périphérique, changer le propriétaire des fichiers, ou altérer des attributs (y compris le flag immutable)
- Refuser le chargement de modules
Cela signifie que même si un intrus réussit à escalader vers root dans un conteneur, il est beaucoup plus difficile de faire des dégâts sérieux, ou d'escalader vers l'hôte.
Cela n'affecte pas les applications web régulières, mais réduit les vecteurs d'attaque par des utilisateurs malveillants considérablement. Par défaut Docker supprime toutes les capacités sauf celles nécessaires, une approche de liste blanche au lieu d'une liste noire. Vous pouvez voir une liste complète des capacités disponibles dans les pages de manuel Linux.
Un risque principal avec l'exécution de conteneurs Docker est que l'ensemble par défaut de capacités et montages donnés à un conteneur peut fournir une isolation incomplète, soit indépendamment, soit lorsqu'utilisé en combinaison avec des vulnérabilités du noyau.
Docker supporte l'ajout et la suppression de capacités, permettant l'utilisation d'un profil non-par défaut. Cela peut rendre Docker plus sécurisé par la suppression de capacités, ou moins sécurisé par l'ajout de capacités. La meilleure pratique pour les utilisateurs serait de supprimer toutes les capacités sauf celles explicitement requises pour leurs processus.
Vérification de signature Docker Content Trust
Le moteur Docker peut être configuré pour exécuter seulement des images signées. La fonctionnalité de vérification de signature Docker Content
Trust est intégrée directement dans le binaire dockerd
.
Ceci est configuré dans le fichier de configuration Dockerd.
Pour activer cette fonctionnalité, l'épinglage de confiance peut être configuré dans daemon.json
, par lequel
seuls les dépôts signés avec une clé racine spécifiée par l'utilisateur peuvent être tirés et exécutés.
Cette fonctionnalité fournit plus d'informations aux administrateurs que précédemment disponible avec la CLI pour appliquer et effectuer la vérification de signature d'image.
Pour plus d'informations sur la configuration de la Vérification de Signature Docker Content Trust, allez à Confiance de contenu dans Docker.
Autres fonctionnalités de sécurité du noyau
Les capacités ne sont qu'une des nombreuses fonctionnalités de sécurité fournies par les noyaux Linux modernes. Il est également possible de tirer parti des systèmes existants, bien connus comme TOMOYO, AppArmor, SELinux, GRSEC, etc. avec Docker.
Bien que Docker n'active actuellement que les capacités, il n'interfère pas avec les autres systèmes. Cela signifie qu'il y a de nombreuses façons différentes de durcir un hôte Docker. Voici quelques exemples.
- Vous pouvez exécuter un noyau avec GRSEC et PAX. Cela ajoute de nombreuses vérifications de sécurité, à la fois au moment de la compilation et de l'exécution ; cela défait également de nombreux exploits, grâce à des techniques comme la randomisation d'adresse. Cela ne nécessite pas de configuration spécifique à Docker, puisque ces fonctionnalités de sécurité s'appliquent à l'échelle du système, indépendamment des conteneurs.
- Si votre distribution vient avec des modèles de modèle de sécurité pour les conteneurs Docker, vous pouvez les utiliser prêts à l'emploi. Par exemple, nous livrons un modèle qui fonctionne avec AppArmor et Red Hat vient avec des politiques SELinux pour Docker. Ces modèles fournissent un filet de sécurité supplémentaire (même s'il se chevauche largement avec les capacités).
- Vous pouvez définir vos propres politiques en utilisant votre mécanisme de contrôle d'accès favori.
Tout comme vous pouvez utiliser des outils tiers pour augmenter les conteneurs Docker, y compris des topologies réseau spéciales ou des systèmes de fichiers partagés, des outils existent pour durcir les conteneurs Docker sans avoir besoin de modifier Docker lui-même.
À partir de Docker 1.10, les Espaces de Noms Utilisateur sont supportés directement par le démon docker. Cette fonctionnalité permet à l'utilisateur root dans un conteneur d'être mappé à un utilisateur non uid-0 en dehors du conteneur, ce qui peut aider à atténuer les risques d'évasion de conteneur. Cette facilité est disponible mais pas activée par défaut.
Référez-vous à la commande démon dans la référence de ligne de commande pour plus d'informations sur cette fonctionnalité. Des informations supplémentaires sur l'implémentation des Espaces de Noms Utilisateur dans Docker peuvent être trouvées dans ce billet de blog.
Conclusions
Les conteneurs Docker sont, par défaut, assez sécurisés ; surtout si vous exécutez vos processus comme des utilisateurs non-privilégiés à l'intérieur du conteneur.
Vous pouvez ajouter une couche supplémentaire de sécurité en activant AppArmor, SELinux, GRSEC, ou un autre système de durcissement approprié.
Si vous pensez à des façons de rendre docker plus sécurisé, nous accueillons les demandes de fonctionnalités, les pull requests, ou les commentaires sur les forums de la communauté Docker.