Pourquoi les processus dans les conteneurs Docker ne doivent pas s'exécuter en tant que root

Publié: 2022-08-19

Graphique montrant le logo Docker

Les processus dans un conteneur Docker ne doivent pas être exécutés en tant que root. Il est plus sûr d'exécuter vos applications en tant qu'utilisateur non root que vous spécifiez dans le cadre de votre Dockerfile ou lors de l'utilisation docker run . Cela minimise les risques en présentant une surface d'attaque réduite à toutes les menaces dans votre conteneur.

Dans cet article, vous découvrirez les dangers liés à l'exécution d'applications conteneurisées en tant que root. Vous verrez également comment créer un utilisateur non root et configurer l'espacement des noms dans les situations où cela n'est pas possible.

Pourquoi courir en tant que root est-il dangereux ?

Les conteneurs sont exécutés en tant que root par défaut. Le démon Docker s'exécute en tant que root sur votre hôte et les conteneurs en cours d'exécution seront également root.

Bien qu'il puisse sembler que root à l'intérieur du conteneur soit un utilisateur indépendant, il s'agit en fait du même compte root sur votre hôte. La séparation est uniquement assurée par les mécanismes d'isolation des conteneurs de Docker. Il n'y a pas de frontière physique forte; votre conteneur est un autre processus exécuté par l'utilisateur root sur le noyau de votre hôte. Cela signifie qu'une vulnérabilité dans votre application, l'environnement d'exécution Docker ou le noyau Linux pourrait permettre à des attaquants de sortir du conteneur et d'effectuer des opérations avec privilèges root sur votre machine.

Certaines protections intégrées réduisent le risque que cela se produise. La racine à l'intérieur du conteneur n'est pas privilégiée et a des capacités restreintes. Cela empêche le conteneur d'utiliser les commandes d'administration système, sauf si vous ajoutez manuellement des fonctionnalités ou utilisez le mode privilégié lorsque vous démarrez vos conteneurs.

Malgré cette atténuation, permettre aux applications de s'exécuter en tant que root reste un danger. Tout comme vous limiteriez l'utilisation de root dans un environnement traditionnel, il n'est pas judicieux de l'utiliser inutilement dans vos conteneurs. Vous fournissez un environnement surprivilégié qui donne aux attaquants une meilleure prise en main en cas de violation.

Exécution d'applications conteneurisées en tant qu'utilisateur non root

Il est recommandé que les applications conteneurisées s'exécutent en tant qu'utilisateur standard. La plupart des logiciels n'ont pas besoin d'un accès root, donc le changement d'utilisateur fournit une couche de défense immédiate contre l'évasion du conteneur.

Vous devez créer un nouveau compte utilisateur comme l'une des dernières étapes de votre Dockerfile. Vous pouvez y parvenir avec l'instruction USER :

 FROM image de base : la plus récente
RUN apt install démo-package
USER demo-user:demo-group
ENTRYPOINT ["démo-binaire"]

Les conteneurs démarrés à partir de cette image s'exécuteront en tant que demo-user . L'utilisateur sera membre du groupe demo-group group. Vous pouvez omettre le nom du groupe si vous n'avez pas besoin que l'utilisateur fasse partie d'un groupe :

 USER démo-utilisateur

Vous pouvez spécifier un ID utilisateur (UID) et un ID de groupe (GID) au lieu de noms :

 UTILISATEUR 950:950

L'attribution d'un UID et d'un GID connus est généralement la manière la plus sûre de procéder. Il empêche l'utilisateur du conteneur d'être mappé sur un compte hôte disposant de trop de privilèges.

USER est souvent spécifié comme l'avant-dernière étape d'un Dockerfile. Cela signifie que vous pouvez toujours exécuter des opérations nécessitant root plus tôt dans la construction de l'image. L'instruction d' apt install dans l'exemple ci-dessus a un besoin légitime de root. Si l'instruction USER était placée au-dessus, apt serait exécuté en tant demo-user qui n'aurait pas les autorisations nécessaires. Comme les instructions Dockerfile ne s'appliquent qu'aux versions d'image, et non aux conteneurs en cours d'exécution, il est prudent de laisser le changement d'utilisateur plus tard dans votre Dockerfile.

La modification de l'utilisateur sous lequel votre conteneur s'exécute peut nécessiter la mise à jour des autorisations sur les fichiers et dossiers auxquels il accède. Définissez la propriété sur tous les chemins qui seront utilisés par votre application :

 COPIER initial-config.yaml /data/config.yaml

USER demo-user:demo-group
RUN chown demo-user:demo-group /data

Dans cet exemple, le répertoire /data doit appartenir à demo-user afin que l'application puisse apporter des modifications à son fichier de configuration. L'instruction COPY précédente aura copié le fichier en tant que root. Un raccourci est disponible en utilisant le drapeau --chown avec copy :

 COPIER --chown=demo-user:demo-group initial-config.yaml /data/config.yaml

Modification de l'utilisateur lors du démarrage d'un conteneur

Bien que vous puissiez facilement changer l'utilisateur dans vos propres Dockerfiles, de nombreuses applications tierces continuent de s'exécuter en tant que root. Vous pouvez réduire le risque associé à leur utilisation en définissant l'indicateur --user chaque fois que vous appelez docker run . Cela remplace l'utilisateur défini dans le Dockerfile de l'image.

 $ docker run -d --user demo-user:demo-group demo-image:latest
$ docker run -d --user demo-user demo-image:latest
$ docker run -d --user 950:950 demo-image:latest

L'indicateur --user exécute le processus du conteneur en tant qu'utilisateur spécifié. C'est moins sûr que l'instruction Dockerfile USER car vous devez l'appliquer individuellement à chaque commande docker run . Une meilleure option pour les images régulièrement utilisées consiste à créer votre propre image dérivée qui peut définir un nouveau compte utilisateur :

 FROM image qui s'exécute en tant que root : dernière
USER démo-utilisateur
 $ docker build . -t image-qui-s'exécute-maintenant-en-tant-que-non-root : la plus récente

Changer l'utilisateur d'une image tierce peut causer des problèmes : si le conteneur s'attend à être exécuté en tant que root ou doit accéder aux chemins du système de fichiers appartenant à root, vous verrez des erreurs lorsque vous utiliserez l'application. Vous pouvez essayer de modifier manuellement les autorisations sur les chemins qui causent des problèmes. Vous pouvez également vérifier si le fournisseur dispose d'une méthode prise en charge pour exécuter l'application avec un compte d'utilisateur non privilégié.

Gestion des applications qui doivent s'exécuter en tant que root

L'espacement des noms d'utilisateurs est une technique permettant de traiter les applications nécessitant certains privilèges root. Il vous permet de mapper root à l'intérieur d'un conteneur vers un utilisateur non root sur votre hôte. La racine simulée à l'intérieur du conteneur dispose des privilèges dont elle a besoin, mais une évasion ne fournira pas d'accès racine à l'hôte.

Le remappage de l'espace de noms est activé en ajoutant un userns-remap à votre fichier /etc/docker/daemon.json :

 {
    "userns-remap": "par défaut"
}

L'utilisation de default comme valeur pour userns-remap indique à Docker de créer automatiquement un nouvel utilisateur sur votre hôte appelé dockremap . La racine dans les conteneurs sera mappée vers dockremap sur votre hôte. Vous pouvez éventuellement spécifier un utilisateur et un groupe existants à la place, en utilisant une combinaison UID/GID ou nom d'utilisateur/nom de groupe :

 {
    "userns-remap": "demo-user"
}

Redémarrez le démon Docker après avoir appliqué votre modification :

 $ redémarrage du menu fixe du service sudo

Si vous utilisez nsuser-remap: default , l'utilisateur dockremap devrait maintenant exister sur votre hôte :

 $ id dockremap

uid=140(dockremap) gid=119(dockremap) groupes=119(dockremap)

L'utilisateur doit également apparaître dans les fichiers d'ID subordonnés /etc/subuid et /etc/subgid :

 $ dockremap:231500:65535

L'utilisateur s'est vu attribuer une plage de 65 535 ID subordonnés à partir de 231500. Dans l'espace de noms d'utilisateur, l'ID 231500 est mappé sur 0 , ce qui en fait l'utilisateur racine dans vos conteneurs. Étant un UID à numéro élevé, 231500 n'a aucun privilège sur l'hôte, de sorte que les attaques par évasion de conteneurs ne pourront pas infliger autant de dégâts.

Tous les conteneurs que vous démarrez s'exécuteront à l'aide de l'espace de noms d'utilisateur remappé, sauf si vous vous désabonnez avec docker run --userns=host . Le mécanisme fonctionne en créant des répertoires avec espace de noms dans /var/lib/docker qui appartiennent à l'UID et au GID subordonnés de l'utilisateur avec espace de noms :

 $ sudo ls -l /var/lib/docker/231500.231500

total 14
drwx------ 5 231500 231500 13 Juil 22 19:00 aufs
drwx------ 3 231500 231500 13 juillet 22 19:00 conteneurs
...

L'espacement des noms d'utilisateurs est un moyen efficace d'augmenter l'isolement des conteneurs, d'éviter les interruptions et de préserver la compatibilité avec les applications nécessitant des privilèges root. Il y a cependant quelques compromis : la fonctionnalité fonctionne mieux sur une nouvelle instance Docker, les volumes montés à partir de l'hôte doivent avoir leurs autorisations ajustées et certains pilotes de stockage externes ne prennent pas du tout en charge le mappage des utilisateurs. Vous devez consulter la documentation avant d'adopter cette option.

Sommaire

L'exécution d'applications conteneurisées en tant que root représente un risque pour la sécurité. Bien qu'il soit facile de l'oublier, l'isolement fourni par les conteneurs n'est pas assez fort pour séparer complètement les utilisateurs du noyau des utilisateurs du conteneur. La racine dans le conteneur est la même que la racine sur votre hôte, donc un compromis réussi pourrait fournir le contrôle de votre machine.

En tant qu'auteur d'image, vous devez inclure l'instruction USER dans votre Dockerfile afin que votre application s'exécute sans root. Les utilisateurs d'images peuvent remplacer cela avec docker run --user pour attribuer un UID et un GID spécifiques. Cela permet d'atténuer les cas où l'image utilise normalement la racine.

Vous pouvez encore renforcer la sécurité en supprimant toutes les fonctionnalités du conteneur à l'aide --cap-drop=ALL , puis en mettant sur liste blanche celles qui sont requises avec --cap-add . La combinaison de ces techniques exécutera votre application en tant qu'utilisateur non root avec l'ensemble minimum de privilèges dont elle a besoin, améliorant ainsi votre posture de sécurité.