Vous montez par bind un répertoire hôte dans un conteneur LXC et tout semble correct — jusqu’à ce que le conteneur essaie d’écrire, de chmod, de chown, ou même de lister le répertoire.
Alors les logs vous informent poliment : permission denied. Les systèmes en production aiment les échecs polis parce qu’ils gaspillent votre temps avec dignité.
Ce problème est rarement « un problème de permissions Linux » au sens générique. Dans Proxmox, il s’agit généralement d’un décalage spécifique et prévisible entre
le mappage d’IDs des conteneurs non privilégiés et les propriétaires/ACLs côté hôte, souvent agrémenté des particularités du stockage réseau.
La solution n’est pas un chmod aléatoire. La solution est d’aligner les identités.
Ce qui se passe réellement (et pourquoi vos yeux vous trompent)
Les conteneurs LXC non privilégiés de Proxmox sont une fonctionnalité de sécurité : root à l’intérieur du conteneur n’est pas root sur l’hôte.
Il est mappé sur un UID non privilégié élevé sur l’hôte, typiquement à partir de 100000.
C’est tout l’intérêt : si le conteneur est compromis, « root » ne pourra pas écrire dans /etc sur l’hôte, charger des modules noyau, ou effacer vos données sans conséquence.
Les bind mounts (Proxmox mp0, mp1, etc.) créent un trou contrôlé dans cette isolation. Mais ils ne traduisent pas magiquement la propriété des fichiers.
Si le répertoire hôte est possédé par root:root (0:0) et que root du conteneur est mappé sur 100000:100000, alors à l’intérieur du conteneur le montage peut sembler
appartenir à root, mais les contrôles de permission se font contre les IDs mappés de l’hôte.
Voilà pourquoi « mais il appartient à root dans le conteneur » est un piège. Votre root du conteneur est un utilisateur ordinaire sur l’hôte. Un utilisateur ordinaire avec un numéro très élevé.
Traduction jargon : l’erreur ne porte pas sur le « montage ». Elle concerne l’identité et l’autorisation à la frontière VFS.
Une petite blague courte, parce que nous allons parler d’offsets UID pendant 30 minutes : les bind mounts sont comme des badges de bureau — votre carte fonctionne bien jusqu’à ce que vous essayiez la salle serveurs.
Playbook de diagnostic rapide
Si vous êtes en plein incident, ne vous dispersez pas. Vérifiez ceci dans l’ordre. Cette séquence trouve le goulot rapidement et minimise le syndrome « j’ai changé trois choses et c’est pire ».
1) Confirmez que le conteneur est non privilégié et notez le mappage d’IDs
- Si c’est non privilégié, supposez que le mappage UID/GID est la cause jusqu’à preuve du contraire.
- Si c’est privilégié, les problèmes de permissions existent toujours, mais les modes d’échec diffèrent.
2) Identifiez le chemin hôte derrière mp0 et inspectez la propriété/les ACL côté hôte
- La propriété côté hôte compte plus que la propriété côté conteneur pour les bind mounts.
- Les ACL peuvent outrepasser les attentes de chmod, et les systèmes de fichiers réseau ajoutent leurs propres règles.
3) Testez le comportement d’écriture et de chown depuis l’intérieur du conteneur
- Si les écritures échouent mais que les lectures fonctionnent : décalage propriétaire/ACL.
- Si chown échoue mais que les écritures fonctionnent : vous n’avez probablement pas besoin de chown ; corrigez l’application ou définissez la bonne propriété une fois.
4) Vérifiez la sémantique du backend de stockage (NFS root_squash, SMB, datasets ZFS)
- NFS avec root_squash transforme souvent « root » en « nobody », surtout dans les conteneurs.
- Les datasets ZFS peuvent imposer leur propre mode ACL et héritage de permissions.
5) Ensuite seulement, envisagez les « solutions de contournement »
- Passer en conteneur privilégié est l’option nucléaire. Ça résout les symptômes en retirant une fonctionnalité de sécurité.
- Un
chmod -R 777aléatoire n’est pas une solution ; c’est une confession.
Faits et contexte intéressants (ce qu’on apprend après l’incident)
- Les namespaces d’UID sont apparus dans le noyau Linux des années avant que la plupart des distributions ne les activent par défaut ; la tech conteneur attendait des outils et du durcissement.
- LXC précède Docker en usage grand public ; c’est plus proche des « conteneurs système » que des conteneurs applicatifs mono-processus. Ergonomie différente, douleur différente.
- Proxmox a choisi les conteneurs non privilégiés comme recommandation par défaut car ils réduisent énormément le blast radius de l’hôte pour le scénario courant « application web compromise ».
- Le mappage Proxmox par défaut commence souvent à
100000et couvre65536IDs. Ce nombre n’est pas magique ; c’est une taille typique pour une plage d’IDs subordonnée. - Les permissions POSIX sont évaluées sur les IDs hôte après mappage. Ce que vous voyez dans le conteneur est une couche de traduction, pas la vérité côté hôte.
- NFS root_squash a été inventé pour empêcher des root distants de devenir root sur le serveur NFS. Les conteneurs n’ont pas d’exemption spéciale.
- Shiftfs a existé comme solution hors arbre d’Ubuntu au problème du décalage UID. Ce n’est pas la réponse mainstream aujourd’hui, et s’y fier sur le long terme est un pari.
- Les montages idmapped sont une fonctionnalité noyau plus récente qui peut résoudre proprement ce problème, mais votre version Proxmox/noyau et vos outils déterminent si c’est pratique.
- Les ACL existent depuis plus longtemps que beaucoup d’ingénieurs ne l’imaginent dans les environnements Linux d’entreprise ; elles ne sont pas exotiques et expliquent souvent « chmod dit oui mais ça dit non quand même ».
Modèle mental : mappage UID/GID, plages idmap et bind mounts
LXC non privilégié fonctionne via un namespace utilisateur. À l’intérieur du conteneur, les processus tournent avec des « UIDs conteneur ».
Le noyau les mappe sur des « UIDs hôte » avant d’effectuer les vérifications de permission sur les systèmes de fichiers hôtes.
Un mappage typique ressemble à ceci :
- Container UID 0 (root) → Host UID 100000
- Container UID 1 → Host UID 100001
- …
- Container UID 65535 → Host UID 165535
Donc si vous bind-montez /tank/shared de l’hôte vers /mnt/shared dans le conteneur, et que le répertoire hôte est possédé par root:root,
alors root du conteneur n’est pas le propriétaire en termes d’hôte. Root du conteneur est l’UID hôte 100000. Root hôte est UID 0.
Ce décalage produit les erreurs classiques :
- Écriture échoue : le répertoire n’est pas inscriptible par l’UID hôte
100000. - chown échoue : un conteneur non privilégié ne peut pas changer arbitrairement la propriété sur les systèmes de fichiers hôtes.
- chmod semble fonctionner ou pas : cela dépend du mappage, du système de fichiers et de si les ACL l’empêchent néanmoins.
Il y a essentiellement quatre voies robustes en production :
- Faire correspondre la propriété sur l’hôte aux IDs mappés (la réponse habituelle, ennuyeuse et correcte).
- Utiliser des ACL sur l’hôte pour accorder l’accès aux IDs mappés sans changer la propriété de tout.
- Utiliser des montages idmapped (si votre pile le supporte de manière fiable).
- Ne pas bind-monter des chemins hôtes ; utilisez plutôt un stockage conçu pour le partage (NFS/SMB avec mappage d’utilisateur explicite, ou un dataset dédié par conteneur avec la bonne propriété).
Passer à un conteneur privilégié est la « cinquième voie », mais c’est plus comme déclencher l’alarme incendie pour arrêter le micro-ondes. Ça marche, et tout le monde s’en souviendra.
Tâches pratiques : commandes, sorties attendues et décision à prendre
Ce sont les vérifications pratiques que j’exécute réellement. Chaque tâche inclut : une commande, ce que signifie la sortie, et la suite à faire.
Exécutez les commandes côté hôte sur le nœud Proxmox, et côté conteneur dans l’LXC.
Tâche 1 : Vérifier que le conteneur est non privilégié
cr0x@server:~$ pct config 103 | egrep 'unprivileged|lxc.idmap|mp0|mp1'
unprivileged: 1
mp0: /tank/shared,mp=/mnt/shared
Sens : unprivileged: 1 confirme que vous avez un mappage UID/GID en jeu.
Si vous voyez des lignes lxc.idmap, le mappage est personnalisé et vous devez le respecter.
Décision : Continuez en supposant un décalage de mappage jusqu’à preuve du contraire que l’accès est accordé aux IDs hôte mappés.
Tâche 2 : Inspecter le mappage d’IDs du conteneur depuis l’hôte
cr0x@server:~$ cat /etc/pve/lxc/103.conf | egrep 'unprivileged|lxc.idmap'
unprivileged: 1
Sens : L’absence de lignes idmap personnalisées signifie généralement que les plages subordonnées par défaut s’appliquent.
Décision : Vérifiez ensuite les plages subuid/subgid du nœud ; elles déterminent l’offset UID sur l’hôte.
Tâche 3 : Vérifier les plages UID/GID subordonnées sur le nœud Proxmox
cr0x@server:~$ grep -E '^root:' /etc/subuid /etc/subgid
/etc/subuid:root:100000:65536
/etc/subgid:root:100000:65536
Sens : Container UID 0 est mappé sur l’UID hôte 100000, et vous avez une plage de 65536 IDs.
Décision : Tout répertoire hôte que vous bind-montez doit être inscriptible par l’UID/GID hôte 100000 (ou par les IDs hôte mappés pour l’utilisateur d’exécution de votre app).
Tâche 4 : Trouver le chemin hôte pour le point de montage
cr0x@server:~$ pct config 103 | grep '^mp'
mp0: /tank/shared,mp=/mnt/shared
Sens : Le chemin hôte est /tank/shared. C’est ce chemin dont la propriété et les ACL doivent permettre les IDs mappés.
Décision : Inspectez la propriété et les permissions côté hôte sur ce chemin (pas d’abord dans le conteneur).
Tâche 5 : Inspecter la propriété hôte, les bits de mode et la présence d’ACL
cr0x@server:~$ ls -ldn /tank/shared
drwxr-x--- 5 0 0 5 Dec 26 08:41 /tank/shared
Sens : Le propriétaire UID/GID est 0:0 (root hôte). Le mode 750 signifie que seuls propriétaire et groupe peuvent écrire/lire ; les autres ne peuvent pas.
L’UID hôte 100000 est « autres », donc root du conteneur ne pourra pas écrire.
Décision : Soit changez la propriété en 100000:100000, soit ajoutez une ACL accordant l’accès à l’UID 100000, soit restructurez le stockage.
Tâche 6 : Confirmer quel utilisateur exécute la charge à l’intérieur du conteneur
cr0x@server:~$ pct exec 103 -- ps -eo user,uid,group,gid,comm | head
USER UID GROUP GID COMMAND
root 0 root 0 ps
www-data 33 www-data 33 nginx
Sens : Votre application peut ne pas tourner en root. Ici, nginx tourne en UID 33 à l’intérieur du conteneur.
Décision : Mappez l’UID d’exécution du conteneur vers l’hôte : host UID = 100000 + 33 = 100033. C’est l’identité qui doit avoir accès côté hôte.
Tâche 7 : Calculer l’UID/GID hôte mappé pour un utilisateur conteneur
cr0x@server:~$ echo $((100000 + 33))
100033
Sens : L’UID conteneur 33 correspond à l’UID hôte 100033 (en supposant le mappage par défaut).
Décision : Accordez les permissions d’écriture sur l’hôte à l’UID 100033 (et éventuellement au GID 100033), pas à l’UID 33.
Tâche 8 : Prouver l’échec depuis l’intérieur du conteneur (test d’écriture)
cr0x@server:~$ pct exec 103 -- bash -lc 'id && touch /mnt/shared/.permtest'
uid=0(root) gid=0(root) groups=0(root)
touch: cannot touch '/mnt/shared/.permtest': Permission denied
Sens : Classique. Le montage est visible, mais les permissions hôte refusent l’UID mappé.
Décision : Corrigez la propriété/les ACL côté hôte. Ne « corrigez » pas cela en faisant un chmod depuis le conteneur en espérant que ça persiste.
Tâche 9 : Vérifier le type de montage et les options sur l’hôte
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /tank/shared
tank/shared zfs rw,xattr,posixacl
Sens : Dataset ZFS avec support ACL activé. Bien. Cela vous donne des options plus contrôlées que chmod seul.
Décision : Préférez une attribution via ACL ou un dataset par conteneur avec propriété mappée, plutôt que d’élargir les bits de mode globalement.
Tâche 10 : Corriger la propriété sur l’hôte pour correspondre aux IDs mappés (cas simple)
cr0x@server:~$ chown -R 100000:100000 /tank/shared
Sens : Maintenant root du conteneur (hôte 100000) possède l’arbre. À l’intérieur du conteneur, root devrait pouvoir écrire.
Décision : Utilisez ceci quand le répertoire est dédié à ce conteneur. Évitez-le pour des données partagées multi-conteneurs sauf si vous avez conçu une stratégie d’identité partagée.
Tâche 11 : Alternative : accorder l’accès via des ACL à l’utilisateur mappé (moins disruptif)
cr0x@server:~$ setfacl -m u:100033:rwx /tank/shared
cr0x@server:~$ getfacl -p /tank/shared | sed -n '1,12p'
# file: /tank/shared
# owner: root
# group: root
user::rwx
user:100033:rwx
group::r-x
mask::rwx
other::---
Sens : L’utilisateur hôte 100033 (www-data du conteneur) a un accès complet, sans changer la propriété root.
Décision : Choisissez les ACL quand vous devez que root hôte reste propriétaire ou quand plusieurs identités ont besoin d’un accès contrôlé. Pensez à définir des ACL par défaut pour les nouveaux fichiers si nécessaire.
Tâche 12 : Définir des ACL par défaut pour que les nouveaux fichiers héritent des permissions (fix fréquent « ça marchait hier »)
cr0x@server:~$ setfacl -d -m u:100033:rwx /tank/shared
cr0x@server:~$ getfacl -p /tank/shared | grep -E '^default:user:100033'
default:user:100033:rwx
Sens : Les nouveaux fichiers et répertoires créés sous /tank/shared héritent de l’entrée ACL.
Décision : Si votre application crée des sous-répertoires et ne peut soudainement plus y écrire, les ACL par défaut sont souvent la pièce manquante.
Tâche 13 : Valider depuis l’intérieur du conteneur après les modifications
cr0x@server:~$ pct exec 103 -- bash -lc 'touch /mnt/shared/.permtest && ls -ln /mnt/shared/.permtest'
-rw-r--r-- 1 0 0 0 Dec 26 09:02 /mnt/shared/.permtest
Sens : Le conteneur peut maintenant écrire. Ne vous précipitez pas sur l’UID affiché dans le conteneur ; l’hôte applique correctement via le mappage/les ACL.
Décision : Passez aux attentes spécifiques de l’application : nécessite-t-elle un chown ? a-t-elle besoin d’écriture de groupe ? Traitez ces points explicitement.
Tâche 14 : Diagnostiquer correctement « chown: Operation not permitted »
cr0x@server:~$ pct exec 103 -- bash -lc 'chown 33:33 /mnt/shared/.permtest'
chown: changing ownership of '/mnt/shared/.permtest': Operation not permitted
Sens : Les conteneurs non privilégiés ne peuvent généralement pas changer la propriété sur des chemins bind-montés côté hôte. C’est normal.
Décision : Corrigez la propriété sur l’hôte une fois, ou adaptez l’application pour qu’elle n’essaie pas de chown à l’exécution. Si l’app exige un chown, concevez autour de ce besoin (dataset dédié pré-propriétaire, ou méthode de partage différente).
Tâche 15 : Vérifier root_squash NFS (si le chemin hôte est un montage NFS)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS /tank/shared
nas:/export/shared nfs4 rw,relatime,vers=4.2,hard,proto=tcp,timeo=600,retrans=2,sec=sys
Sens : Les permissions « réelles » sont maintenant gouvernées par le serveur NFS, plus les options d’export comme root_squash et le comportement d’idmapping.
Décision : Si vous suspectez root_squash, arrêtez d’essayer de faire « root dans le conteneur » propriétaire de choses. Utilisez un export NFS dédié avec un UID/GID de service explicite correspondant aux IDs hôte mappés, ou évitez de bind-monter NFS dans des conteneurs non privilégiés.
Tâche 16 : Voir quel UID l’hôte utilise quand le conteneur écrit (forensique)
cr0x@server:~$ rm -f /tank/shared/hostview.test
cr0x@server:~$ pct exec 103 -- bash -lc 'echo hi > /mnt/shared/hostview.test'
cr0x@server:~$ ls -ln /tank/shared/hostview.test
-rw-r--r-- 1 100000 100000 3 Dec 26 09:06 /tank/shared/hostview.test
Sens : Le fichier est possédé par l’hôte 100000:100000 car il a été créé par root du conteneur.
Décision : Utilisez ceci pour valider vos hypothèses de mappage. Si vous attendiez 100033 mais voyez 100000, votre processus tourne en root dans le conteneur.
Schémas de correction fiables en production
Schéma A : Répertoire hôte dédié par conteneur, possédé par le root mappé (rapide, propre)
Si un seul conteneur utilise le bind mount, le plan le plus simple est : créez un répertoire dédié, puis chown dessus l’UID root mappé du conteneur.
C’est ennuyeux. L’ennui, c’est bien.
cr0x@server:~$ mkdir -p /tank/ct103-data
cr0x@server:~$ chown -R 100000:100000 /tank/ct103-data
cr0x@server:~$ chmod 0750 /tank/ct103-data
Pourquoi ça marche : vous alignez le propriétaire hôte avec l’identité mappée du root conteneur.
Quand éviter : données partagées entre plusieurs conteneurs avec des plages UID différentes ; vous finirez avec un tiroir fourre-tout de propriétaires incompatibles.
Schéma B : Grants ACL pour des utilisateurs de service conteneur spécifiques (chirurgical, évolutif)
C’est l’approche adulte pour les répertoires partagés : conservez une propriété hôte significative, accordez l’accès aux IDs mappés via ACL, définissez des ACL par défaut pour l’héritage,
et cessez de considérer chmod comme votre seul outil.
cr0x@server:~$ setfacl -m u:100033:rwx /tank/shared
cr0x@server:~$ setfacl -d -m u:100033:rwx /tank/shared
Pourquoi ça marche : cela accorde exactement l’accès requis, et cela survit à la création de nouveaux fichiers.
Compromis : vous devez industrialiser la visibilité des ACL. Si votre équipe ne sait pas exécuter getfacl, vous aurez des mystères récurrents.
Schéma C : Un dataset par conteneur (particulièrement avec ZFS), propriété fixée une fois
Avec ZFS sur Proxmox, des datasets par conteneur sont propres car ils gardent quotas, snapshots et réplication ordonnés.
Ils réduisent aussi la complexité ACL car chaque dataset a une seule « histoire d’identité ».
Créez le dataset, montez-le sur un chemin hôte, chownnez-le aux IDs mappés, puis bind-montez dans le conteneur. Gardez la frontière de données nette.
Schéma D : montages idmapped (moderne, élégant, mais vérifiez votre plateforme)
Les montages idmapped peuvent présenter un arbre de fichiers avec un mappage UID/GID différent au moment du montage.
C’est essentiellement « faire le décalage au niveau VFS » au lieu de pré-chownner toute la donnée.
En pratique, votre décision dépend de la version de Proxmox, du support noyau, et de si vous voulez être le premier de l’équipe à le déboguer à 3h du matin.
Si vous ne pouvez pas répondre « comment valider ce mappage après une mise à jour du noyau ? », restez sur la propriété/les ACL éprouvées.
Schéma E : Arrêter de bind-monter et utiliser un protocole de partage avec identité explicite
Parfois la bonne correction est architecturale : ne bind-montez pas des répertoires hôte dans des conteneurs non privilégiés quand le stockage sous-jacent est lui-même un système distant,
ou quand plusieurs conteneurs ont besoin d’un accès en écriture partagé avec des utilisateurs différents.
Utilisez NFS/SMB dans le conteneur avec un compte de service bien défini et des UID/GID cohérents entre systèmes. Ou exposez le stockage via une couche service.
Les bind mounts sont fantastiques jusqu’à ce qu’ils ne le soient plus.
Trois mini-histoires du monde de l’entreprise (anonymisées, douloureusement plausibles)
Mini-histoire 1 : L’incident causé par une mauvaise hypothèse
Une équipe plateforme a migré une charge legacy VM vers Proxmox LXC pour économiser des ressources. Ils ont fait ce qu’il fallait — laissé le conteneur non privilégié, déplacé les configs, configuré la supervision.
Ils ont aussi bind-monté un répertoire hôte contenant des uploads applicatifs.
En staging, « ça marchait ». En production, les uploads ont commencé à échouer de façon intermittente. L’app renvoyait des erreurs I/O génériques, et l’astreinte a fait ce que font les humains sous stress :
ils ont d’abord accusé l’application, puis le réseau, puis le « stockage ».
La mauvaise hypothèse était simple : « root à l’intérieur du conteneur est suffisamment root. » Ils ont regardé ls -l dans le conteneur,
vu root:root, et supposé que le conteneur pourrait écrire.
Pendant ce temps, sur l’hôte, le répertoire était possédé par 0:0 avec des permissions 750.
Root conteneur était l’hôte 100000 — effectivement « autres ». Les écritures échouaient quand l’app créait de nouveaux sous-répertoires avec des modes plus stricts.
La correction fut une seule séquence de commandes et une modification de politique : chown du répertoire d’uploads dédié à l’UID de service mappé,
ajout d’ACL par défaut pour que les nouveaux dossiers héritent de l’écriture, et documentation de la règle de mappage dans le runbook.
Le postmortem fut court, ce qui est la meilleure catégorie.
Mini-histoire 2 : L’optimisation qui s’est retournée contre eux
Une autre organisation voulait réduire la duplication. Ils ont créé un répertoire hôte partagé pour les « assets communs » et l’ont bind-monté dans une flotte de conteneurs.
Pour « simplifier », ils ont exécuté un large chmod -R 777 sur l’arbre et sont passés à autre chose. Personne n’aime les tickets de permissions.
Deux mois plus tard, un conteneur compromis a écrit un binaire malveillant dans ce répertoire partagé. Un autre conteneur — d’une équipe complètement différente — a exécuté un script d’aide
depuis ce chemin partagé pendant une étape de déploiement. Ce n’était pas une attaque sophistiquée. C’était juste un répertoire partagé inscriptible faisant exactement ce que font les répertoires partagés inscriptibles.
La remédiation fut pénible. Ils ont dû démêler quels conteneurs avaient besoin d’accès en lecture seule, lesquels avaient besoin d’écriture, et lesquels n’auraient jamais dû avoir le montage.
Ils ont fini par remplacer le montage d’écriture partagé par un montage en lecture seule pour la plupart et un emplacement d’écriture séparé par conteneur.
L’ironie : l’« optimisation » pour économiser de l’espace a créé une dette de sécurité et de fiabilité. Elle a aussi augmenté la charge opérationnelle car auditer un répertoire world-writable
en environnement multi-tenant est essentiellement un loisir, pas un contrôle.
Mini-histoire 3 : La pratique ennuyeuse mais correcte qui a sauvé la mise
Une équipe de services financiers (oui, ces gens-là) avait une politique : chaque bind mount doit avoir un ticket avec (1) objectif, (2) responsable des données, (3) mode d’accès, (4) UID/GID mappés,
et (5) étapes de rollback. Les ingénieurs levaient les yeux au ciel. Puis ils suivaient la procédure quand même.
Une mise à jour du noyau est passée sur le cluster, et un conteneur a commencé à échouer à écrire sur son montage. L’astreinte a ouvert le ticket,
a vu l’UID/GID mappés exacts et les permissions attendues du chemin hôte, et a exécuté les commandes de vérification.
L’ACL du répertoire hôte avait été écrasée par un script de « nettoyage » qui normalisait les permissions sur les datasets.
Parce que les attentes étaient documentées, la correction fut déterministe : restaurer les entrées ACL et les valeurs par défaut, valider avec un test d’écriture,
et ajouter une règle au script de nettoyage pour ignorer les points de montage gérés.
Personne n’a débattu de « simplement rendre le conteneur privilégié ». Personne n’a deviné.
C’était ennuyeux. C’était correct. Ça a évité une escalade compliquée et limité le blast radius.
L’hygiène opérationnelle ennuyeuse ne vous fait pas promouvoir sur le moment, mais elle vous permet de dormir.
Erreurs courantes : symptôme → cause racine → correctif
1) Symptom : « Permission denied » sur touch ou écritures dans le montage
Cause racine : répertoire hôte non inscriptible par l’UID/GID hôte mappé (souvent 100000+).
Fix : chown du répertoire hôte vers les IDs mappés ou accorder des ACLs aux IDs mappés ; valider avec ls -ln côté hôte et test d’écriture dans le conteneur.
2) Symptom : « Operation not permitted » sur chown depuis le conteneur
Cause racine : le conteneur non privilégié ne peut pas changer la propriété sur des bind mounts comme le ferait un vrai root.
Fix : définir la bonne propriété sur l’hôte au préalable ; modifier l’application pour qu’elle cesse de chowner à l’exécution ; ou utiliser des datasets par conteneur et des chemins pré-propriétaires.
3) Symptom : Fonctionne sur des fichiers existants, échoue sur des sous-répertoires nouvellement créés
Cause racine : absence d’ACL par défaut ou umask qui crée des répertoires sans permission d’écriture pour l’utilisateur de service mappé.
Fix : définir des ACL par défaut sur le chemin hôte ; assurer la propriété de groupe et le bit setgid si vous utilisez des schémas d’accès basés sur le groupe.
4) Symptom : Chemin hôte monté NFS se comporte étrangement ; root ne peut pas écrire même après chown
Cause racine : options d’export NFS (root_squash), ou mismatch d’UID entre client et serveur, ou problèmes d’idmapping sur NFSv4.
Fix : utiliser un export NFS dédié avec UID/GID de service explicite correspondant aux IDs hôte mappés ; envisager de monter NFS dans le conteneur avec un plan d’identités cohérent.
5) Symptom : chmod/chown ne « tiennent pas » ou se comportent de façon incohérente
Cause racine : ACLs ou modèle de permissions du système de fichiers (mode ACL ZFS, sémantique SMB) qui outrepassent les attentes de chmod.
Fix : inspecter et gérer les ACL intentionnellement ; confirmer les réglages ACL du système de fichiers ; éviter de mélanger attentes POSIX et NFSv4 ACL sans plan.
6) Symptom : Vous « avez corrigé » en rendant le conteneur privilégié, et maintenant la sécurité est inquiète
Cause racine : vous avez retiré le mécanisme de sécurité au lieu d’aligner les identités.
Fix : revenir à non privilégié si possible ; implémenter le mappage de propriété/ACL ou redesigner le stockage ; réserver les conteneurs privilégiés aux cas avec une exception écrite et des contrôles compensatoires.
7) Symptom : Un seul nœud du cluster échoue après migration du conteneur
Cause racine : plages /etc/subuid//etc/subgid inconsistantes entre nœuds, ou ACLs/propriétés inconsistantes sur le stockage partagé.
Fix : standardiser les plages subordonnées sur tous les nœuds ; traitez-les comme une configuration de cluster. Validez sur chaque nœud susceptible d’exécuter le conteneur.
8) Symptom : Le répertoire semble possédé par « nobody » ou par des IDs étranges dans le conteneur
Cause racine : décalage de mappage, mappage d’identité d’un système distant, ou absence de service de nommage dans le conteneur.
Fix : raisonnez en IDs numériques ; utilisez ls -ln ; ne poursuivez pas des noms d’utilisateur tant que le mappage numérique n’est pas compris.
Checklists / plan étape par étape
Étape par étape : corriger un bind mount pour un conteneur unique en toute sécurité
- Sur l’hôte : confirmez
unprivileged: 1pour le conteneur. - Sur l’hôte : lisez
/etc/subuidet récupérez la base (souvent 100000). - Sur l’hôte : identifiez le chemin
mp0et inspectezls -ldn. - Décidez qui a besoin d’écriture dans le conteneur (root vs utilisateur de service).
- Calculez l’UID/GID hôte mappé pour cet utilisateur (base + UID conteneur).
- Pour un répertoire dédié :
chownle chemin hôte vers l’UID/GID mappé ; définissez des bits de mode conservateurs. - Pour un répertoire partagé : ajoutez des ACLs hôte pour l’UID/GID mappé ; définissez aussi des ACLs par défaut.
- Validez depuis le conteneur : test d’écriture et (si nécessaire) test de création de répertoire.
- Validez depuis l’hôte : vérifiez que la propriété des fichiers montre les IDs mappés, pas 0:0.
- Consignez le mappage et les attentes dans une fiche runbook attachée à l’ID du conteneur et au point de montage.
Étape par étape : empêcher que le stockage partagé ne devienne une décharge de sécurité
- Arrêtez d’utiliser des permissions world-writable comme « solution temporaire ». Temporaire est la manière dont on construit des incidents permanents.
- Créez un modèle de groupe partagé si approprié, mais souvenez-vous que les GID mappés diffèrent dans les conteneurs non privilégiés.
- Privilégiez des bind mounts en lecture seule pour les assets partagés. L’accès en écriture devrait être rare et justifié.
- Utilisez des ACLs avec des IDs mappés explicites pour les quelques conteneurs qui ont besoin d’écrire.
- Définissez des ACLs par défaut et testez la création de répertoires imbriqués, pas seulement d’un fichier unique.
- Mettez en place des audits périodiques : lister les ACLs et la propriété pour les racines de montage gérées.
Petite blague courte, puis retour au travail : un chmod 777, c’est comme désactiver votre détecteur de fumée parce qu’il bippe — silencieux, oui ; sage, non.
FAQ
1) Pourquoi « permission denied » alors que le répertoire semble appartenir à root dans le conteneur ?
Parce que la propriété est traduite. Root du conteneur est mappé sur un UID hôte non-root (souvent 100000). Le système de fichiers hôte vérifie les permissions contre cet UID hôte.
2) Quelle est la correction la plus rapide et fiable ?
Si le répertoire est dédié à ce conteneur : chownnez-le sur l’hôte vers l’UID/GID mappé (typiquement 100000:100000 pour root conteneur) et gardez des permissions serrées.
3) Comment mapper un utilisateur conteneur (comme www-data UID 33) vers l’UID hôte ?
Prenez la base subordonnée dans /etc/subuid (par ex. 100000) et ajoutez l’UID conteneur (33). Résultat : 100033. Même calcul pour le GID.
4) Puis-je simplement exécuter chown dans le conteneur ?
Sur les chemins bind-montés hôte dans des conteneurs non privilégiés, généralement non. Vous obtiendrez « Operation not permitted ». Définissez la propriété sur l’hôte à la place, ou utilisez des ACLs.
5) Passer à un conteneur privilégié est-il acceptable ?
Parfois, mais traitez cela comme une exception avec contrôles compensatoires. Les conteneurs privilégiés augmentent considérablement le risque pour l’hôte. Aligner les UID/GID est presque toujours préférable.
6) Pourquoi ça marche sur un nœud Proxmox mais pas après migration du conteneur ?
Raison fréquente : plages /etc/subuid//etc/subgid différentes entre nœuds. Le même UID conteneur se mappe sur des UIDs hôte différents, donc les permissions cassent.
7) Quid de NFS ? Pourquoi c’est plus pénible ?
NFS ajoute une application des permissions côté serveur et des options d’export comme root_squash. L’UID mappé du conteneur peut ne pas correspondre à ce que le serveur NFS attend.
Résolvez cela en concevant des identités de service cohérentes ou en utilisant des exports dédiés avec stratégie UID/GID explicite.
8) Dois-je utiliser des ACLs ou chown ?
Utilisez chown pour des chemins dédiés par conteneur. Utilisez les ACLs quand la propriété doit rester root hôte ou quand plusieurs identités ont besoin d’accès contrôlé.
Si vous avez besoin des deux, vous modélisez probablement du stockage partagé — traitez-le comme tel, pas comme un bind mount bricolé.
9) Quel rôle jouent les montages idmapped ici ?
Ils peuvent résoudre proprement la traduction UID/GID au moment du montage sans chownner les données. Mais le support opérationnel varie selon le noyau/outillage.
Si vous ne pouvez pas tester et surveiller cela en confiance, restez sur la méthode propriété/ACL éprouvée.
10) Comment empêcher que ça ne se reproduise ?
Standardisez les plages subuid/subgid sur le cluster, documentez les attentes de propriété/ACL par conteneur, et ajoutez un test de validation simple (créer un fichier, créer un répertoire).
Conclusion : étapes suivantes que vous pouvez faire aujourd’hui
« Permission denied » sur un bind mount Proxmox LXC n’est presque jamais mystérieux. C’est des maths plus de la politique :
votre utilisateur conteneur est mappé sur un UID/GID hôte différent, et le chemin hôte ne l’autorise pas.
Faites ceci ensuite :
- Choisissez un conteneur avec un bind mount en échec et notez : ID du conteneur, chemin mpX, flag unprivileged, et base subuid.
- Calculez l’UID/GID hôte mappé pour l’utilisateur de service qui a besoin d’accès.
- Corrigez soit par un chown dédié (chemin mono-tenant) soit par des ACLs + ACLs par défaut (chemin partagé).
- Validez par un test d’écriture et un test « création de sous-répertoire ». Le futur vous remerciera pour le test de sous-répertoire.
- Standardisez
/etc/subuid//etc/subgidsur les nœuds avant la prochaine surprise de migration.
Idée paraphrasée de John Allspaw : le vrai travail en opérations consiste à concevoir des systèmes et des processus qui rendent les pannes compréhensibles et récupérables.