GNU/Linux >> Tutoriels Linux >  >> Linux

Comment déboguer les problèmes avec les volumes montés sur des conteneurs sans racine

L'une des questions les plus fréquentes que l'on me pose sur Podman sans racine est de savoir comment déboguer les problèmes avec les volumes montés dans le conteneur. Cette question est trompeusement difficile. À bien des égards, exécuter Podman sans root est presque identique à l'exécuter en tant que root . Malheureusement, ce n'est pas toujours vrai, et les volumes sont l'un des domaines où les différences sont les plus importantes. Ici, j'expliquerai en détail quelles sont ces différences, quels types d'erreurs elles peuvent causer et comment vous pouvez les contourner. Pour commencer, nous avons besoin d'informations générales sur le fonctionnement des conteneurs sans racine, en commençant par l'une des fonctionnalités les plus fondamentales de Podman sans racine : Espaces de noms d'utilisateurs .

L'espace de noms d'utilisateur

L'une des fonctionnalités de sécurité fondamentales des conteneurs est les espaces de noms du noyau Linux. Un espace de noms est un moyen d'isoler un processus (ou un groupe de processus) du reste du système en limitant ce qu'il peut voir. Il existe de nombreux espaces de noms différents, chacun avec un effet différent. Par exemple, l'espace de noms PID limite les PID qu'un processus peut afficher. Seuls les PID de l'espace de noms sont visibles et numérotés indépendamment de l'hôte. Le processus a toujours un PID sur l'hôte également, donc le PID 2000 sur l'hôte peut être le PID 1 dans l'espace de noms. Au moment d'écrire ces lignes, les espaces de noms fournis par le noyau sont Mount, PID, Network, IPC, UTS, User, cgroup et time, chacun isolant un aspect différent du système; celui qui nous intéresse le plus pour ce blog est l'espace de noms d'utilisateur.

Les espaces de noms d'utilisateurs isolent les utilisateurs et les groupes disponibles dans le conteneur de ceux disponibles sur le système hôte. Un espace de noms d'utilisateur fonctionne en mappant les utilisateurs du conteneur aux utilisateurs de l'hôte. Par exemple, nous pourrions mapper les utilisateurs 0 à 1000 dans le conteneur aux utilisateurs 100000 à 101000 sur l'hôte (les groupes sont également mappés de manière identique, mais nous nous concentrerons sur les utilisateurs pour plus de simplicité). Ce mappage agit de manière très similaire à l'espace de noms PID que nous avons décrit ci-dessus, mais avec les utilisateurs.

Depuis l'hôte, tous les accès depuis root dans le conteneur (UID 0) semblera provenir de l'UID 100000. Dans le conteneur, tout fichier appartenant à l'utilisateur 100000 sur l'hôte apparaîtra comme appartenant à l'UID 0 (root ). Une question intéressante est ce qui arrive aux utilisateurs non mappés dans le conteneur - et si je monte un volume appartenant à l'utilisateur 1001 sur l'hôte dans un conteneur en utilisant l'espace de noms d'utilisateur que j'ai décrit ? Le noyau, dans ce cas, mappera tout UID ou GID non valide dans l'espace de noms à l'UID/GID 65534, un utilisateur et un groupe appelé nobody , qui n'est pas un groupe normal.

Il est toujours possible d'interagir avec des fichiers appartenant à personne si les autorisations le permettent (par exemple, vous pouvez lire un fichier appartenant à personne qui est lisible par tout le monde et écrire dans ce fichier s'il est accessible en écriture par tout le monde), mais vous ne pouvez pas en changer le propriétaire. Les espaces de noms d'utilisateurs accordent également des versions limitées de fonctionnalités spécifiques qui ne sont normalement disponibles que pour root - l'exemple typique est que les espaces de noms d'utilisateurs peuvent monter certains types de systèmes de fichiers, comme tmpfs.

Les espaces de noms d'utilisateurs sont extrêmement utiles car ils nous permettent d'agir en tant que root dans le conteneur sans être réellement root sur le système. Nous pouvons utiliser des espaces de noms d'utilisateurs pour séparer les conteneurs de différents utilisateurs sur des systèmes multi-locataires :les conteneurs d'un utilisateur seraient exécutés avec les UID 10000 à 10999, ceux d'un autre entre 11000 et 11999, etc. Dans chaque conteneur, il semble que l'application soit root , et grâce aux capacités limitées accordées, il peut effectuer la plupart des opérations courantes (installation de packages, par exemple).

Cependant, supposons qu'une application réussisse à sortir du conteneur. Dans ce cas, il ne s'exécute pas en tant que root sur le système et ne s'exécutant pas avec les mêmes UID et GID que les conteneurs de tout autre utilisateur, la capacité d'attaquer différentes parties du système est extrêmement limitée.

Cependant, leur adoption était limitée par des limitations techniques, notamment le fait qu'il n'y avait jusqu'à très récemment aucun moyen de remapper les UID et les GID au niveau du système de fichiers. Pour avoir un conteneur utilisant les UID 10000 à 10999, il fallait faire une copie de son image puis chown chaque UID dans ladite image en ajoutant 10000 à l'UID existant. Ce chown peut être très lent et (dans de nombreux systèmes de fichiers) augmente considérablement la quantité d'espace requis.

[ Vous débutez avec les conteneurs ? Découvrez ce cours gratuit. Déploiement d'applications conteneurisées :présentation technique. ]

Conteneurs sans racine

Là où les espaces de noms d'utilisateurs sont devenus extrêmement utiles et populaires, ce sont les conteneurs sans racine. Un utilisateur non root sous Linux n'a accès qu'à un seul UID et GID (le sien). Cependant, les conteneurs s'attendent à accéder à plusieurs utilisateurs et groupes :de nombreux fichiers dans les images de conteneur n'appartiennent pas à root , et les applications seront souvent exécutées en tant qu'utilisateur non root dans le conteneur pour appliquer la séparation des privilèges dans le conteneur.

Pour certains environnements (calcul haute performance, HPC, étant un exemple notable), n'avoir qu'un seul utilisateur et groupe dans le conteneur est acceptable (voire souhaitable). Pour la plupart des autres environnements, un utilisateur et un groupe constituent une limite importante à l'utilité du conteneur. Nous pouvons utiliser des espaces de noms d'utilisateurs pour gagner ces utilisateurs et groupes supplémentaires dont nous avons besoin pour agir comme un conteneur typique.

Cependant, nous avons besoin de privilèges élevés pour atteindre ces utilisateurs et groupes supplémentaires. C'est ce que le newuidmap et newgidmap exécutables (et le /etc/subuid et /etc/subgid les fichiers de configuration qu'ils lisent) le font :ils nous accordent l'accès à un bloc d'utilisateurs et de groupes, qui sont ensuite mappés dans un espace de noms d'utilisateurs pour les conteneurs sans racine à utiliser. Les limitations des espaces de noms d'utilisateurs concernant la prise en charge du système de fichiers s'appliquent toujours dans une certaine mesure, mais sont atténuées en n'ayant qu'un seul ensemble d'UID et de GID à utiliser par chaque utilisateur.

A noter également le fait que le noyau gérera automatiquement le chown opération pour nous si nous déballons l'image à l'intérieur d'un espace de noms d'utilisateur. Le déplacement de l'utilisateur de l'espace de noms garantit que l'UID correct est attribué lors de la création au lieu d'exiger que l'environnement d'exécution du conteneur le définisse manuellement.

Les capacités supplémentaires d'un espace de noms d'utilisateur sont également essentielles pour certaines des choses dont les conteneurs sans racine ont besoin :sans la possibilité de monter les systèmes de fichiers FUSE et tmpfs, les conteneurs sans racine seraient beaucoup plus limités (au point d'être presque inutilisables).

Espaces de noms d'utilisateurs dans Podman

Maintenant que nous comprenons le fonctionnement général des espaces de noms d'utilisateurs, voyons comment ils sont implémentés dans Podman sans racine.

Tous les conteneurs Podman sans racine sont exécutés dans un espace de noms d'utilisateur, même si l'utilisateur n'a pas plus d'un UID et GID disponibles. Tous les conteneurs d'un utilisateur partagent un espace de noms d'utilisateur unique, maintenu ouvert par le processus de pause sans racine. Les espaces de noms sont généralement élagués par le noyau lorsqu'il n'y a plus de processus dedans, donc garder un processus qui ne fait que dormir et ne se termine jamais gardera l'espace de noms de l'utilisateur en vie.

La première chose qu'un processus Podman sans racine fait est de rejoindre l'espace de noms d'utilisateur sans racine (ou de créer un nouvel espace de noms et de suspendre le processus s'il n'existe pas encore). Dans le cadre de la création de l'espace de noms d'utilisateur, Podman exécutera le newuidmap et newgidmap exécutables pour accorder tous les UID et GID supplémentaires attribués à l'utilisateur dans /etc/subuid et /etc/subgid (le montant par défaut accordé à la création de l'utilisateur étant de 65536 de chaque). Vous pouvez voir les mappages d'utilisateurs disponibles dans les podman info commande, dans les idMappings champ :

mheon@podman-rhel8-test $ podman info
  idMappings:
    gidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 100000
      size: 65536
    uidmap:
    - container_id: 0
      host_id: 1000
      size: 1
    - container_id: 1
      host_id: 100000
      size: 65536

Veuillez noter que l'espace de noms d'utilisateur sans racine n'est pas recréé par défaut si le /etc/subuid et /etc/subgid les fichiers sont modifiés ; cela se fait en exécutant le podman system migrate commande. Si vous avez modifié ces fichiers et que Podman ne semble pas reconnaître vos modifications, exécutez cette commande.

Toutes les commandes Podman sans racine sont exécutées dans l'espace de noms d'utilisateur sans racine créé pour garantir que nous disposons des mappages et des privilèges d'utilisateur corrects. Même des commandes d'information simples, comme podman info , nécessitent l'espace de noms d'utilisateur sans racine. En tant que tel, un espace de noms d'utilisateur non fonctionnel est un obstacle pour Podman sans racine. Heureusement, cela n'arrive pas souvent. Lorsque c'est le cas, nous constatons généralement que cela est dû à des autorisations de fichier insuffisantes sur le newuidmap et/ou newgidmap binaires sur le système (il manque généralement une capacité de fichier). Réinstaller le package les contenant (appelé shadow-utils sur RHEL, CentOS et Fedora) résoudra généralement ce problème.

Une fois l'espace de noms d'utilisateur sans racine créé, nous pouvons commencer à exécuter des conteneurs. L'utilisation de volumes avec ces conteneurs est le premier point auquel la plupart des utilisateurs rencontreront les différences pratiques entre Podman racine et sans racine. L'utilisateur exécutera un conteneur avec un volume monté et découvrira rapidement que le conteneur ne peut pas accéder aux fichiers du volume, même si tout semble correctement configuré.

Par exemple, examinons une simple commande Podman :

podman run --user 1000:1000 -v /home/mheon/data:/data:Z ubi8 sh

Cette commande est exécutée par mon utilisateur, mheon , avec UID et GID définis sur 1000 (le même utilisateur que le conteneur a été chargé d'utiliser). Mon utilisateur s'est vu attribuer 65 536 UID et GID à partir de l'UID/GID 100 000 via /etc/subuid et /etc/subgid . Le contexte SELinux a été défini pour que le conteneur puisse accéder au répertoire via le :Z option sur le montage du volume.

Cependant, l'accès aux /data dossier dans le conteneur sera refusé, et le seul message d'erreur que le système renverra est une autorisation refusée générique du noyau. Tout utilisateur qui ne sait pas ce qu'est un espace de noms d'utilisateur n'aurait aucune idée de ce qui ne va pas.

Cependant, maintenant que nous le savons, la cause devrait être évidente :le mappage utilisateur signifie que l'UID 1000 dans le conteneur n'est pas réellement l'UID 1000 sur l'hôte. Vous pouvez voir l'utilisateur dans le conteneur et l'utilisateur réel sur l'hôte via le podman top commande :

mheon@podman-rhel8-test $ podman top -l user,huser
USER   HUSER
1000   100999

Ici, UTILISATEUR est l'utilisateur dans le conteneur, tandis que HUSER est l'utilisateur sur l'hôte.

Pourtant, savoir pourquoi quelque chose se passe ne signifie pas que nous savons comment y remédier. Nous voulons toujours exécuter notre conteneur Podman sans racine avec un volume spécifique monté dedans. Comment fait-on cela? Heureusement, il existe de nombreuses façons de résoudre ce problème, que je vais aborder ci-dessous.

La première solution

Le premier est simple :le --user l'option peut être omise du conteneur, en exécutant la commande du conteneur en tant que root . Comme indiqué ci-dessus, par défaut, Podman mappe l'utilisateur exécutant le conteneur sur root dans le conteneur - nous allons donc maintenant accéder au volume en tant qu'UID/GID 1000 sur l'hôte, bien que nous soyons root dans le conteneur. Exécuté en tant que root dans un conteneur racine est un problème de sécurité potentiel car vous exécutez en tant que racine du système utilisateur :si un attaquant s'échappait du conteneur, il pourrait agir en tant que root sur le système. Tout l'intérêt d'un conteneur sans racine est que cela n'est jamais vrai :les problèmes de sécurité sont principalement un non-facteur.

Malheureusement, la solution consistant à exécuter le conteneur en tant que root tombe à plat lorsque l'image est spécifiquement écrite pour utiliser un utilisateur non root. Certaines images de conteneur comportent des scripts de point d'entrée complexes pour supprimer des autorisations qui ne peuvent pas être facilement modifiées. Ceux-ci nécessiteront une solution alternative.

La deuxième solution

La deuxième option consiste à accorder à l'utilisateur qui s'exécute dans le conteneur l'autorisation de lire et d'écrire dans le dossier monté à partir de l'hôte. Depuis Podman v3.1.0, cela peut être fait automatiquement via le :U option de volume au -v drapeau (par exemple -v /home/mheon/data:/data:Z,U ).

Saisissez ensuite podman unshare chown 1000:1000 /home/mheon/data . Cette option de volume ajustera automatiquement la propriété du répertoire, de sorte que l'utilisateur s'exécutant dans le conteneur (quel que soit l'UID utilisateur 1000 dans le conteneur est mappé sur l'hôte) sera propriétaire du répertoire. Dans les versions sans ce drapeau, le podman unshare La commande peut être utilisée pour entrer l'espace de noms de l'utilisateur sans racine, puis chown le répertoire qui appartiendra à l'utilisateur exécutant le conteneur.

Dans ce cas, podman unshare chown 1000:1000 /home/mheon/data changerait la propriété du répertoire sur l'hôte à l'utilisateur et au groupe qui correspondent à l'UID/GID 1000 dans l'espace de noms d'utilisateur. Veuillez noter que, si la propriété est modifiée, tous les répertoires parents sur l'hôte nécessiteront également l'autorisation d'exécution pour tous les utilisateurs (chmod a+x… ) l'autorisation de s'assurer que le répertoire en question est accessible.

Malheureusement, le chown approche vient avec son propre ensemble d'inconvénients. Le /home/mheon/data se trouve dans le répertoire personnel de mon utilisateur, mais il n'appartient plus à mon utilisateur (dans ce cas, il appartient à l'utilisateur et au groupe 100999 ). Dans l'espace de noms d'utilisateur sans racine, le mheon l'utilisateur peut agir en tant que root et lire, écrire et modifier des fichiers appartenant à cet utilisateur ; mais il ne peut faire aucune de ces choses en dehors de lui. Podman fournit une commande pour entrer un shell dans l'espace de noms de l'utilisateur sans racine (podman unshare ) qui peuvent être utilisés pour modifier ou supprimer ces fichiers, mais l'impossibilité de gérer ces fichiers autrement est gênante.

La troisième solution

La troisième option consiste à utiliser le --userns=keep-id option pour podman run . Cet indicateur indique à Podman de faire deux choses :premièrement, pour définir l'utilisateur que le conteneur exécute sur l'UID et le GID de l'utilisateur qui a exécuté Podman (sauf s'il est explicitement remplacé par le --user flag), et deuxièmement, pour réorganiser les utilisateurs mappés dans le conteneur de sorte que l'utilisateur qui a exécuté Podman soit mappé sur ses propres UID et GID, au lieu de root (cela se fait via un deuxième espace de noms d'utilisateur, imbriqué dans l'espace de noms d'utilisateur sans racine, créé uniquement pour ce conteneur). L'utilisateur dans l'espace de noms d'utilisateur sous lequel le conteneur s'exécute n'est pas root mais correspond toujours à l'utilisateur exécutant Podman (mheon ) sur l'hôte et peut ainsi accéder au répertoire monté dans l'exemple /home/mheon/data .

Cependant, cela ne résoudra pas toutes les erreurs d'accès. Un autre problème courant consiste à tenter de monter dans un fichier ou un périphérique auquel l'utilisateur exécutant Podman a accès, mais uniquement par un groupe supplémentaire. Par exemple, disons que mon utilisateur, mheon , fait partie du kvm groupe qui possède /dev/kvm , et je choisis de monter /dev/kvm dans un conteneur en utilisant podman run -t -i -v /dev/kvm:/dev/kvm fedora bash . Le conteneur ne pourra pas accéder à /dev/kvm , malgré le fait qu'il s'exécute en tant que mon utilisateur sur l'hôte (qui devrait y avoir accès).

La raison en est que, pour des raisons de sécurité, les conteneurs supprimeront (par défaut) tous les groupes supplémentaires lors de leur création. Ce comportement peut être désactivé, mais uniquement en utilisant le crun Exécution OCI (cela devrait être par défaut à partir de Podman 3.0 sur toutes les distributions sauf RHEL) en transmettant une annotation spéciale (--annotation run.oci.keep_original_groups=1 ).

Dans le prochain Podman v3.2.0, cela sera disponible via un argument spécial pour le group-add flag (--group-add keep-groups ). Veuillez noter que, même si nous pouvons conserver l'accès à ces groupes, nous ne sommes pas autorisés à les ajouter à l'espace de noms d'utilisateurs sans racine. Nous ne pouvons le faire qu'aux utilisateurs et groupes qui nous sont attribués dans /etc/subuid et /etc/subgid . Un ls -al sur /dev/kvm dans le conteneur trouvera qu'il appartient à nobody:nobody (en tant que propriétaire et groupe réels, root:kvm , ne sont pas mappés dans le conteneur).

Cependant, il est accessible car, malgré le fait que le kvm groupe ne fait pas partie de l'espace de noms de l'utilisateur, le processus conteneur fait toujours partie du groupe aux yeux du noyau. Ceci est quelque peu limitatif dans la mesure où il n'est pas possible de créer explicitement des fichiers comme l'un de ces groupes supplémentaires (comme personne n'est pas un vrai groupe avec lequel nous pouvons interagir), mais il suffit de donner au conteneur l'accès au contenu sur l'hôte qu'il serait autrement incapable d'atteindre, et les répertoires avec le bit SUID appartenant à un groupe supplémentaire définiront toujours le propriétaire correct .

Un autre type d'erreur courante est rencontré lors de l'extraction d'images avec des fichiers ou des dossiers appartenant à des utilisateurs UID élevés. Tout fichier ou dossier appartenant à un UID ou GID trop volumineux pour être inclus dans l'espace de noms d'utilisateur produira une erreur. J'ai déjà écrit un blog à ce sujet et sur les solutions potentielles, que vous pouvez trouver ici.

Conclusion

L'une des caractéristiques les plus importantes de Podman est notre solide prise en charge des conteneurs sans racine, et il n'est pas difficile de comprendre pourquoi les gens sont excités. Les conteneurs sans racine sont faciles à configurer, plus sécurisés que root conteneurs, et peut faire presque tout ce qu'un conteneur exécute en tant que root peut faire. Bien sûr, le mot clé est presque - parce que l'expérience globale avec root et rootless est si similaire, les différences peuvent être déroutantes et souvent difficiles à expliquer. Après avoir lu ce blog, vous devriez avoir une bonne compréhension de l'une des plus grandes de ces différences et de la façon de travailler avec Podman pour que vos conteneurs fonctionnent comme vous le souhaitez.


Linux
  1. Comment installer Nextcloud avec ISPConfig 3.1

  2. Ajouter un utilisateur au groupe sous Linux, comment le faire (avec exemples)

  3. Exécuter Podman sans racine en tant qu'utilisateur non root

  4. Comment créer un nouvel utilisateur avec un accès Ssh ?

  5. Comment répertorier les conteneurs Docker

Comment exécuter des conteneurs en tant que service Systemd avec Podman

Comment Cirrus CLI utilise Podman pour réaliser des versions sans racine

Comment modifier le code dans les conteneurs Docker avec Visual Studio Code

Comment exécuter des conteneurs Docker

Comment :démarrer avec les conteneurs Windows et Docker

Comment gérer les conteneurs Docker