Debian 13 : Permission refusée sur les bind mounts — corriger proprement le mappage UID/GID (cas n°41)

Cet article vous a aidé ?

Vous mettez à niveau un hôte vers Debian 13, redéployez un conteneur, et soudain votre montage bind « parfaitement normal » se transforme en brique :
Permission denied lors de la lecture, de l’écriture, de mkdir, parfois même de ls. Les logs n’aident pas. Vos collègues suggèrent
chmod -R 777 comme si c’était une panacée.

Ce n’est généralement pas un problème de « permissions ». C’est un problème d’identité. Les bind mounts ne traduisent pas la propriété ; ils exposent fidèlement
le système de fichiers hôte au processus qui se trouve dans l’espace de noms cible. Si le mappage UID/GID ne correspond pas, vous obtenez un refus — à raison.
La correction propre consiste à aligner le mappage UID/GID (ou à le traduire délibérément avec des idmapped mounts), pas à lancer une fête chmod en production.

Playbook de diagnostic rapide

Quand la page vous réveille à 02:13, vous n’avez pas besoin de philosophie. Vous avez besoin d’une séquence courte et fiable qui trouve rapidement le goulot d’étranglement.
Voici le chemin le plus rapide que j’ai trouvé pour atteindre la vérité sur des hôtes Debian 13 exécutant des conteneurs et/ou des mounts systemd.

1) Confirmer ce qui échoue : permissions du noyau, mappage d’espace de noms, ou MAC ?

  • Première étape : reproduisez sur l’hôte et à l’intérieur du conteneur. Si ça marche sur l’hôte mais échoue dans le conteneur, c’est presque toujours du mappage ou un LSM.
  • Deuxième étape : vérifiez les IDs numériques de propriété sur le chemin monté (stat), pas les noms. Les noms peuvent tromper ; les numéros non.
  • Troisième étape : vérifiez si vous êtes en mode rootless, userns-remappé, ou si vous utilisez des idmapped mounts. Le mappage détermine si le UID 1000 signifie « vous » ou « quelqu’un d’autre ».
  • Quatrième étape : éliminez rapidement AppArmor/SELinux comme facteur confondant (même si vous « ne l’utilisez pas »).

2) Décidez quelle classe de correctif il vous faut

  • Même hôte, même conteneur : alignez le UID/GID à l’intérieur du conteneur avec les fichiers de l’hôte, ou mappez les IDs hôtes explicitement avec des idmapped mounts.
  • Multi-hôtes / NFS : standardisez les UIDs/GIDs numériques via un annuaire/SSSD ou attribution explicite d’UID ; ne « corrigez » pas chaque nœud à la main.
  • Kubernetes hostPath : considérez-le comme « le système de fichiers du nœud ». Utilisez des IDs cohérents, fsGroup, ou un init container contrôlé, pas des chmod aléatoires.
  • Rootless : vérifiez /etc/subuid et /etc/subgid, et assurez-vous que le mappage couvre les IDs dont vous avez besoin.

3) Validez avec un test définitif

Créez un fichier depuis l’intérieur du conteneur et vérifiez son UID/GID sur l’hôte. Puis inversez : créez sur l’hôte et écrivez depuis le conteneur.
Si les IDs ne correspondent pas aux attentes, arrêtez de toucher aux bits de mode. Vous déboguez l’identité, pas les droits d’accès.

Ce que font vraiment les bind mounts (et ce qu’ils ne feront jamais)

Un bind mount est brutalement littéral. Il prend un répertoire (ou un fichier) d’un endroit de l’arborescence et le fait apparaître ailleurs.
C’est tout. Pas de traduction de propriété. Pas d’intermédiation de permission au-delà de ce que le noyau applique déjà. Pas de renommage sympathique des utilisateurs et groupes
simplement parce que vous avez déplacé le chemin sous /var/lib/containers.

Cette littéralité explique pourquoi les bind mounts sont rapides et prévisibles. C’est aussi la raison pour laquelle ils deviennent une source récurrente d’incidents quand on mélange :
conteneurs rootless, espaces de noms utilisateur, stockage multi-hôtes, et des valeurs par défaut « utiles » des orchestrateurs.

Le modèle mental qui évite les mauvaises corrections

Le noyau décide de l’accès en utilisant les IDs numériques (UID/GID), les bits de mode, les ACL et la politique LSM. Un processus dans un conteneur reste un processus.
Si ce processus a le UID 1000 dans son espace de noms, le noyau peut le traiter comme UID 100000 à l’extérieur, selon le mappage. Si le répertoire
sur l’hôte est possédé par UID 1000, et que le processus est effectivement UID 100000, il n’est pas le propriétaire. L’écriture échoue. À raison.

Si vous « corrigez » cela par chmod -R 777, vous n’avez pas résolu le décalage d’identité. Vous avez juste abandonné la sécurité et rendu le débogage futur plus compliqué.
En plus, vos auditeurs commenceront à employer des mots comme « systémique ».

Blague n°1 : « chmod 777, c’est comme désactiver le détecteur de fumée parce qu’il est bruyant. Vous dormirez mieux… jusqu’à ce que non. »

Faits intéressants & historique qui expliquent la douleur actuelle

  1. Les UIDs et GIDs sont d’abord des nombres. Les noms d’utilisateur ne sont qu’un sucre de présentation (via /etc/passwd ou NSS).
  2. Les bind mounts sont arrivés bien avant les conteneurs. C’était une astuce pour chroots et réorganiser l’arborescence ; les conteneurs les ont hérités comme primitive de volume.
  3. Les espaces de noms utilisateur sont relativement « récents » sur Linux. Ils existaient depuis des années avant de devenir grand public, et certaines distributions les ont activés prudemment pour des raisons de sécurité.
  4. Les conteneurs rootless ont rendu le décalage visible. En mode rootful, le UID 0 passe souvent outre les problèmes (jusqu’à rencontrer le root-squash NFS ou des règles LSM).
  5. NFS a toujours puni la gestion d’identité négligente. Pour le NFS classique, les UID/GID numériques doivent correspondre entre clients ou vous obtenez un chaos « ça marche sur mon nœud ».
  6. Les idmapped mounts sont une fonctionnalité du noyau, pas du conteneur. Ils permettent au noyau de traduire la propriété par mount sans changer la propriété sur disque.
  7. Les ACL peuvent contourner des bits de mode apparemment « corrects ». Un répertoire peut être drwxrwx--- et toujours refuser l’accès à cause d’une entrée ACL oubliée.
  8. Les filesystems en overlay compliquent la propriété perçue. Avec des fichiers empilés, vous pouvez voir une propriété dans la couche conteneur et une autre sur le stockage hôte sous-jacent.

Cause principale : mismatch UID/GID entre espaces de noms

Le schéma récurrent derrière « Permission denied sur un bind mount » est simple :
l’UID/GID effectif du processus tel que vu par le noyau n’est pas le propriétaire des fichiers auxquels il tente d’accéder.
Et les bind mounts ne changent pas cette relation — ils l’exposent.

Où Debian 13 se fait blâmer (souvent à tort)

Debian 13 ne « casse » pas les bind mounts. Ce qui change sur les systèmes modernes, c’est la prévalence de :
Podman/Docker rootless, des unités systemd de montage, des valeurs par défaut plus strictes, et des configurations d’espace de noms utilisateur. Le moindre écart dans la coordination UID/GID
devient visible lorsque vous mettez à jour des composants et redéployez.

Scénarios typiques de mismatch

  • Conteneur rootless + répertoire hôte possédé par votre UID réel. Le « UID 0 » du conteneur est mappé vers une plage d’UID hôte élevée (depuis /etc/subuid).
  • Allocation d’UID différente entre hôtes. L’utilisateur app est UID 1001 sur le nœud A et UID 1002 sur le nœud B. NFS stocke seulement les nombres.
  • L’utilisateur dans l’image du conteneur ne correspond pas à l’utilisateur du service sur l’hôte. L’image s’exécute en UID 1000, le répertoire hôte est possédé par UID 2000, et vous avez supposé que les noms suffisaient.
  • Conteneur rootful + NFS root-squash. « Mais c’est root dans le conteneur » rencontre « root devient nobody sur le serveur ».
  • ACLs ou politique LSM qui refusent malgré des bits de mode corrects. Vous pouvez regarder ls -l toute la journée sans voir un rejet AppArmor.

Citation d’ingénierie fiabilité (idée paraphrasée) : Werner Vogels a dit qu’on construit des systèmes en attendant des pannes, pas en prétendant qu’elles n’arriveront pas.
Les décalages d’identité sont une forme de « panne attendue » dans un Linux multi-tenant ; concevez pour cela.

Tâches pratiques : commandes, sorties et décisions (12+)

Ce sont les commandes que j’exécute réellement. Chaque tâche inclut : la commande, ce que la sortie vous dit, et la décision que vous en tirez.
Supposez que le chemin monté en question est /srv/app/data sur l’hôte et apparaît comme /data dans le conteneur.

Task 1: Confirm it’s a bind mount (not a different filesystem)

cr0x@server:~$ findmnt -T /srv/app/data -o TARGET,SOURCE,FSTYPE,OPTIONS
TARGET        SOURCE         FSTYPE OPTIONS
/srv/app/data /dev/sdb1[/data] ext4   rw,relatime

Signification : Vous êtes sur ext4 et vous n’êtes pas en présence d’un piège NFS/overlay.
Décision : Si cela affiche nfs, passez aux vérifications d’identité NFS. Si cela affiche overlay, traitez-le comme un comportement de couche conteneur.

Task 2: Inspect mount propagation and bind-ness explicitly

cr0x@server:~$ findmnt -o TARGET,SOURCE,FSTYPE,OPTIONS,PROPAGATION | grep -E '(/srv/app/data|/data)'
/srv/app/data  /dev/sdb1[/data] ext4  rw,relatime shared

Signification : La propagation peut affecter si des mounts apparaissent dans les conteneurs.
Décision : Si la propagation est private et que vous comptez sur des montages imbriqués, vous aurez des problèmes de « données manquantes », pas des permission denied. C’est un autre type d’incident.

Task 3: Check numeric ownership and mode bits (host)

cr0x@server:~$ stat -c 'path=%n uid=%u gid=%g mode=%a owner=%U group=%G' /srv/app/data
path=/srv/app/data uid=2000 gid=2000 mode=750 owner=app group=app

Signification : Le noyau exigera UID 2000 ou GID 2000 (ou une ACL) pour les écritures.
Décision : Si votre conteneur s’exécute en UID 1000, ça va échouer. Corrigez le mappage, ne faites pas un chmod.

Task 4: Identify the process identity inside the container

cr0x@server:~$ podman exec -it appctr sh -lc 'id && umask'
uid=1000(app) gid=1000(app) groups=1000(app)
0022

Signification : À l’intérieur du conteneur, le processus est UID/GID 1000.
Décision : Vous devez aligner cela avec la propriété hôte (UID 2000) ou traduire avec des idmapped mounts.

Task 5: Prove the failure with a controlled write (container)

cr0x@server:~$ podman exec -it appctr sh -lc 'touch /data/.perm_test && echo ok'
touch: cannot touch '/data/.perm_test': Permission denied

Signification : Ce n’est pas votre application ; c’est le système de fichiers. Bien — maintenant vous pouvez arrêter d’accuser le code.
Décision : Continuez le diagnostic sur le mappage plutôt que dans les logs applicatifs.

Task 6: Compare with a host-side write as the expected user

cr0x@server:~$ sudo -u app touch /srv/app/data/.host_test && ls -ln /srv/app/data/.host_test
-rw-r--r-- 1 2000 2000 0 Dec 30 12:01 /srv/app/data/.host_test

Signification : UID 2000 fonctionne, et les fichiers sont possédés comme prévu.
Décision : Exécutez le conteneur en UID 2000, ou mappez son UID vers 2000.

Task 7: If rootless, inspect subordinate ID ranges

cr0x@server:~$ grep -E '^cr0x:' /etc/subuid /etc/subgid
/etc/subuid:cr0x:100000:65536
/etc/subgid:cr0x:100000:65536

Signification : Les espaces de noms rootless mappent les IDs du conteneur dans 100000+ sur l’hôte.
Décision : Si le répertoire hôte est possédé par UID 2000, un conteneur rootless mappé vers 100000 ne correspondra pas. Vous avez besoin d’un idmapped mount ou d’un changement de propriété côté hôte.

Task 8: Inspect the user namespace mapping of the container process

cr0x@server:~$ pid=$(podman inspect -f '{{.State.Pid}}' appctr); echo "$pid"; sudo cat /proc/$pid/uid_map
24188
         0     100000      65536

Signification : Le UID 0 du conteneur est l’UID 100000 de l’hôte ; le UID 1000 du conteneur est l’UID 101000 de l’hôte.
Décision : Arrêtez d’essayer de chown le mount depuis l’intérieur du conteneur ; cela n’affectera pas les IDs sur l’hôte comme vous le pensez.

Task 9: Check for ACLs that override mode bits

cr0x@server:~$ getfacl -p /srv/app/data | sed -n '1,20p'
# file: /srv/app/data
# owner: app
# group: app
user::rwx
group::r-x
other::---

Signification : Pas de surprise ACL ici.
Décision : Si vous voyez une ACL refusant les écritures, corrigez les ACL ; ne touchez pas encore aux UID/GID.

Task 10: Check AppArmor denials (common on Debian)

cr0x@server:~$ sudo journalctl -k -g DENIED --no-pager | tail -n 3
Dec 30 12:03:14 server kernel: audit: type=1400 apparmor="DENIED" operation="open" class="file" profile="containers-default" name="/srv/app/data/" pid=24188 comm="myapp"

Signification : Ce n’est pas un problème UID/GID. AppArmor bloque.
Décision : Corrigez le profil AppArmor ou le labeling ; ne modifiez pas la propriété. S’il n’y a pas de refus, revenez au diagnostic de mappage.

Task 11: Confirm the mount options (nosuid/nodev/noexec are not “permission denied”, but can look like it)

cr0x@server:~$ findmnt -T /srv/app/data -o OPTIONS
OPTIONS
rw,relatime

Signification : Pas de noexec étrange.
Décision : Si votre appli échoue à exécuter des binaires depuis le mount et que vous voyez noexec, c’est la cause.

Task 12: Verify the container’s effective user matches expectations (rootful vs rootless)

cr0x@server:~$ podman info --format '{{.Host.Security.Rootless}}'
true

Signification : Vous êtes en rootless. Super pour la sécurité, et terrible pour les suppositions.
Décision : Prévoyez des correctifs compatibles avec le mappage userns, pas contre lui.

Task 13: Reproduce ownership mismatch with numeric listing (container)

cr0x@server:~$ podman exec -it appctr sh -lc 'ls -ldn /data; ls -ln /data | head'
drwxr-x--- 2 2000 2000 4096 Dec 30 12:01 /data
-rw-r--r-- 1 2000 2000 0 Dec 30 12:01 .host_test

Signification : Le conteneur voit UID 2000 sur les fichiers, mais il s’exécute en UID 1000. Cet écart est le problème.
Décision : Choisir entre : (a) changer l’utilisateur du conteneur en 2000, (b) changer la propriété hôte vers l’ID mappé, (c) idmapped mount.

Task 14: Confirm NFS root-squash when applicable

cr0x@server:~$ findmnt -T /srv/app/data -o FSTYPE,SOURCE
FSTYPE SOURCE
nfs4   nfs01:/exports/appdata
cr0x@server:~$ sudo -u root touch /srv/app/data/.root_touch 2>&1 | tail -n 1
touch: cannot touch '/srv/app/data/.root_touch': Permission denied

Signification : Probablement root-squash ou politique d’export côté serveur. Root sur le client n’est pas root sur le serveur.
Décision : Corrigez l’identité sur le serveur NFS (alignement UID/GID), ou ajustez les options d’export délibérément. Ne lancez pas tout en root pour « réparer ».

Corrections propres que vous pouvez défendre en revue

Il existe trois familles de solutions « correctes ». Choisissez selon votre réalité opérationnelle : hôte unique vs flotte, rootless vs rootful,
exigences de conformité, et stockage local ou réseau.

Fix family A: Align the container runtime UID/GID with the host data

C’est la correction la plus simple et la plus ennuyeuse : si /srv/app/data est possédé par UID 2000, exécutez le processus du conteneur en UID 2000.
Ça marche avec les bind mounts, sur la plupart des systèmes de fichiers, et ne nécessite pas de fonctionnalités noyau sophistiquées.

Avec des images qui le supportent, définissez l’utilisateur d’exécution explicitement. Dans des workflows de type Compose, vous mettriez user: "2000:2000".
Pour Podman :

cr0x@server:~$ podman run -d --name appctr --user 2000:2000 -v /srv/app/data:/data:Z docker.io/library/alpine sleep 1d
...output...

Signification : Le processus s’exécute avec le même UID/GID qui possède le répertoire hôte.
Décision : Utilisez ceci lorsque vous contrôlez l’UID de l’application et que vous n’avez pas besoin des sémantiques « UID 0 » dans le conteneur.

Le piège : si vous opérez sur plusieurs hôtes, vous devez conserver l’UID 2000 identique partout. Ce n’est pas optionnel. C’est l’essence du problème.

Fix family B: Standardize UID/GID across the fleet (directory-backed identity)

C’est ce que font les environnements matures : attribuer aux comptes de service des IDs numériques fixes, les gérer centralement, et rendre le stockage cohérent.
Des utilisateurs locaux créés ad hoc sur chaque hôte vont dériver. La dérive devient des pannes.

Si vous ne pouvez pas centraliser, réservez au moins une plage UID/GID pour les identités de service et imposez-la dans la provisioning.
« On se souviendra de créer user app avec le même UID sur chaque serveur » n’est pas un processus ; c’est une prière.

Fix family C: Use idmapped mounts to translate ownership cleanly

Les idmapped mounts sont la chose la plus proche que Linux ait d’un « bind mount mais avec traduction d’IDs ». Ils permettent de monter un répertoire de sorte que la propriété
soit mappée pour ce mount seulement — pas de changement de propriété sur disque, pas de renumérotation globale des UIDs.

C’est particulièrement attractif lorsque :

  • Vous avez des données existantes possédées par un UID/GID, mais l’image du conteneur en attend un autre.
  • Vous ne pouvez pas chown (gros datasets, contraintes de conformité, mounts partagés).
  • Vous exécutez en rootless et voulez que le conteneur voie une propriété « raisonnable ».

La plomberie exacte varie selon le runtime et le support noyau. Le principe reste : définissez un mappage et appliquez-le au mount.
Sur des noyaux Debian modernes, les idmapped mounts sont viables, mais n’assumez pas que chaque couche de la pile (systemd, runtime, orchestration) le fera automatiquement.

What not to do (unless you like recurring incidents)

  • Ne faites pas chmod 777 sur un répertoire de données partagé « pour que ça marche ». Vous casserez le principe du moindre privilège et vous aurez quand même la dérive d’identité.
  • Ne faites pas un chown de téraoctets pendant un incident à moins d’avoir mesuré et de pouvoir revenir en arrière. C’est lent, perturbateur et facile à rater.
  • Ne faites pas la « correction » consistant à tout exécuter en root. NFS rira, les LSM se plaindront, et vous multiplierez les problèmes.

Blague n°2 : Chaque fois que quelqu’un suggère « exécutez en privilégié », un ingénieur sécurité gagne un cheveu gris de plus et le nomme en votre honneur.

Trois micro-histoires du monde d’entreprise

1) Incident causé par une mauvaise supposition : « les noms correspondent, donc les IDs doivent correspondre »

Une entreprise de taille moyenne exécutait un service stateful en conteneurs sur plusieurs hôtes Debian. Le compte de service s’appelait app
partout. L’équipe a supposé que cela signifiait la même identité partout. Ce n’était pas le cas.

Un nœud avait été installé plus tôt et contenait quelques utilisateurs locaux supplémentaires. Sur cette machine, app était devenu UID 1002. Sur les nœuds plus récents,
c’était UID 1001. Le stockage était une export NFS monté sur /srv/app/data, et NFS stockait la propriété numérique, pas « l’ambiance du nom d’utilisateur ».

Le mode de défaillance était subtil : le service fonctionnait sur deux nœuds et échouait sur un avec Permission denied lors de la création de nouveaux fichiers,
mais pouvait lire les anciens. Les ingénieurs ont passé des heures à fouiller la configuration du conteneur, puis les options de montage NFS, puis à se demander « peut-être que Debian a changé quelque chose ».

La percée est venue d’une personne faisant la chose peu glamour : ls -ln sur le répertoire de données depuis chaque nœud, plus id app.
Ils ont vu l’écart instantanément. La correction fut tout aussi banale : standardiser UID/GID pour app via leur pipeline de provisioning et
corriger la propriété sur le serveur NFS pour l’UID prévu.

La leçon : les noms d’utilisateur sont une interface utilisateur. Les IDs numériques sont le contrat.

2) Optimisation qui s’est retournée contre eux : « éviter chown en passant au rootless »

Une autre organisation a voulu réduire le blast radius des conteneurs. Ils ont converti une partie de leurs workloads en conteneurs rootless.
Bonne décision globalement. Mais ils l’ont traitée comme un changement uniquement sécurité et n’ont pas vérifié la compatibilité stockage.

Leurs bind mounts pointaient vers des répertoires hôtes possédés par des utilisateurs de service avec des UIDs faibles. Le mappage rootless a décalé les IDs conteneur
dans la plage 100000+. Soudain, des workloads qui écrivaient auparavant dans /srv ont commencé à échouer. La première réaction de l’équipe fut « optimiser » :
élargir les permissions de répertoires pour supprimer les contrôles de groupe. Cela fit marcher certains workloads et empirer les constats d’audit.

Ils ont ensuite tenté un « correctif rapide » : chown récursif des répertoires hôtes vers la plage d’UID mappée. Ça a marché pour les conteneurs mais a cassé
des outils de maintenance côté hôte qui supposaient que l’utilisateur de service possédait ses propres données. Ils se sont retrouvés avec deux univers de propriété, aucun satisfaisant.

La solution stable finale fut d’arrêter de traiter l’identité comme accessoire. Ils ont défini une stratégie explicite d’UID/GID pour les services, documenté quels
workloads sont rootless, et utilisé une combinaison d’exécution des conteneurs avec UIDs alignés et d’utilisation sélective d’idmapped mounts lorsque des données legacy
ne pouvaient pas être déplacées. L’« optimisation » n’était pas le rootless ; c’était la gouvernance autour.

3) Pratique ennuyeuse mais correcte qui a sauvé la mise : checks preflight CI pour l’identité de stockage

Une grande équipe plateforme avait une habitude qui semblait ridicule jusqu’à ce qu’elle ne le soit plus : chaque pipeline de déploiement avait un job preflight qui exécutait un conteneur
avec l’utilisateur d’exécution prévu, montait le chemin hôte prévu, et effectuait des opérations fichier basiques (mkdir, touch, fsync, rename).

Le job produisait un petit artefact : l’UID/GID vu par le conteneur, l’UID/GID sur l’hôte pour le fichier créé, et tout refus noyau/LSM.
Ce n’était pas sophistiqué. C’était cohérent. Les gens se plaignaient parfois que cela ajoutait quelques minutes et « ne trouve jamais rien ».

Puis une vague de nœuds a été reconstruite avec un ordre de provisioning légèrement différent. Quelques comptes de service ont changé d’UID. Le preflight l’a détecté
avant le déploiement en production. La correction s’est faite dans le provisioning, pas pendant une panne. Pas d’appels à minuit, pas de chmod d’urgence, pas de chown massif.

Voilà à quoi ressemble la fiabilité : pas de l’héroïsme, juste un processus qui rend la panne ennuyeuse et précoce.

Erreurs courantes : symptôme → cause racine → correctif

1) Symptom: Works on host, fails in container with “Permission denied”

Cause racine : L’UID/GID du conteneur ne correspond pas à la propriété hôte, souvent dû au mappage userns rootless.
Correctif : Exécuter le conteneur en UID/GID du répertoire hôte, ou utiliser des idmapped mounts, ou ajuster la propriété hôte vers l’ID mappé intentionnellement.

2) Symptom: Rootful container still can’t write to NFS bind mount

Cause racine : root-squash NFS ou permissions côté serveur. Root dans le conteneur n’est pas spécial côté serveur.
Correctif : Aligner les IDs numériques entre clients et serveur ; ajuster les exports NFS délibérément ; éviter de « tout exécuter en root ».

3) Symptom: ls -l shows write permission, but writes fail

Cause racine : ACL ou politique LSM qui refusent (AppArmor/SELinux).
Correctif : Inspecter les ACL avec getfacl ; inspecter les refus dans les logs noyau ; corriger la politique/profil plutôt que chmod/chown.

4) Symptom: Files created in container appear on host owned by weird high UIDs (100000+)

Cause racine : Mappage user namespace rootless via /etc/subuid//etc/subgid.
Correctif : Prévoyez-le ; ne vous battez pas contre. Utilisez des IDs alignés via idmapped mount ou définissez correctement la stratégie d’UID d’exécution de l’application.

5) Symptom: Some nodes work, others fail, same deployment

Cause racine : Dérive UID/GID entre hôtes ; NFS ou stockage partagé rend cela visible.
Correctif : Centralisez l’identité ou imposez des IDs numériques fixes dans le provisioning ; vérifiez avec id et ls -ln sur tous les nœuds.

6) Symptom: Changing permissions “fixes” it but breaks later

Cause racine : Vous avez masqué le mismatch d’identité en élargissant les permissions ; plus tard, un autre service ou contrôle sécurité s’attend à des modes plus stricts.
Correctif : Rétablissez le moindre privilège ; corrigez le mappage d’identité ; ajoutez un test preflight qui valide l’écriture sous le bon UID/GID.

Checklists / plan étape par étape

Step-by-step plan for a single Debian 13 host (most common)

  1. Confirmez le chemin en échec et le type de mount.
    Utilisez findmnt -T. Si c’est du NFS, passez au plan NFS.
  2. Enregistrez la propriété hôte numériquement.
    Utilisez stat -c 'uid=%u gid=%g mode=%a' sur le chemin hôte.
  3. Enregistrez l’identité d’exécution du conteneur.
    Dans le conteneur : id. Capturez aussi si vous êtes en rootless.
  4. Vérifiez le mappage userns (particulièrement en rootless).
    Inspectez /proc/$pid/uid_map. Si les UIDs sont décalés, supposez un mismatch jusqu’à preuve du contraire.
  5. Éliminez les ACL et refus LSM.
    Exécutez getfacl et vérifiez journalctl -k -g DENIED.
  6. Choisissez un correctif :

    • Si vous contrôlez l’utilisateur du conteneur : exécutez le conteneur avec l’UID/GID hôte.
    • Si vous ne pouvez pas changer l’utilisateur du conteneur : idmapped mount (préféré) ou changement de propriété contrôlé.
    • Si environnement partagé : standardisez les IDs plutôt que de patcher un hôte.
  7. Validez avec un test d’écriture bidirectionnel.
    Créez un fichier depuis le conteneur et vérifiez son UID/GID sur l’hôte ; puis créez depuis l’hôte et écrivez depuis le conteneur.
  8. Figez la décision.
    Mettez les décisions UID/GID dans la gestion de configuration, pas dans la connaissance tribale.

Step-by-step plan for NFS-backed bind mounts

  1. Identifiez le serveur NFS et la politique d’export. Confirmez les attentes de root-squash.
  2. Vérifiez la cohérence des UID/GID numériques entre tous les clients. Le même compte de service doit avoir le même UID partout.
  3. Corrigez l’identité à la source. Identity backed by directory ou provisioning cohérent bat les rustines locales.
  4. Évitez les chown massifs. Si une correction de propriété est nécessaire, faites-la côté serveur dans une fenêtre contrôlée.
  5. Retestez avec l’UID réel de l’application. « Ça marche en root » n’est pas un critère de réussite sur NFS.

Step-by-step plan for rootless containers on Debian 13

  1. Confirmez le mode rootless. podman info ou équivalent runtime.
  2. Vérifiez /etc/subuid et /etc/subgid. Assurez-vous que les plages existent et sont assez larges.
  3. Inspectez uid_map/gid_map. Sachez quels IDs conteneur deviennent sur l’hôte.
  4. Choisissez une stratégie de propriété. Soit les données sont possédées par les IDs hôte mappés, soit vous utilisez des idmapped mounts pour présenter les IDs attendus.
  5. Documentez-le. Le rootless sans documentation d’identité est un abonnement aux incidents.

FAQ

Pourquoi cela est apparu après la migration vers Debian 13 ?

Généralement parce que vous avez changé une couche : runtime de conteneur, valeurs par défaut rootless, jeu de fonctionnalités noyau, comportement systemd, ou ordre de provisioning d’identité.
La règle sous-jacente n’a pas changé : l’accès est appliqué par les IDs numériques et la politique.

Pourquoi ls -l affiche un nom d’utilisateur familier, mais le conteneur ne peut pas écrire ?

Parce que le UID du processus dans le conteneur ne correspond pas à l’UID numérique qui possède le répertoire. Les noms sont résolus via NSS et peuvent différer selon l’environnement.
Vérifiez toujours les IDs numériques avec ls -ln ou stat.

Puis-je simplement chown -R le répertoire pour correspondre au conteneur ?

Vous pouvez, mais c’est souvent la pire option : lent sur de grands arbres, risqué sur des données partagées, et cela entérine des mappages accidentels.
Préférez aligner le runtime UID/GID ou utiliser des idmapped mounts lorsque vous avez besoin de traduction sans réécrire la propriété.

Quel est le correctif le plus propre quand plusieurs services partagent le même chemin hôte ?

Standardisez les UIDs/GIDs des comptes de service sur la flotte et attribuez la propriété de groupe de manière réfléchie.
Si des services nécessitent vraiment des vues de propriété différentes, les idmapped mounts sont un meilleur outil que le chaos des chmod.

Pourquoi rootless crée des fichiers avec des UIDs comme 101000 sur l’hôte ?

Parce que les espaces de noms utilisateur mappent les IDs du conteneur dans une plage subordonnée définie dans /etc/subuid//etc/subgid.
Le UID 1000 du conteneur devient l’UID hôte (base subuid + 1000).

AppArmor peut-il vraiment causer des « Permission denied » qui ressemblent à un problème de système de fichiers ?

Oui. Il peut refuser des opérations open/create même quand les bits de mode semblent permissifs. Vérifiez les logs noyau pour des refus.
Si vous voyez des refus AppArmor, corrigez le profil ; ne modifiez pas la propriété.

Et les volumes hostPath Kubernetes ?

Même principe : l’identité du processus du pod doit pouvoir accéder au chemin du nœud. Utilisez runAsUser/runAsGroup explicites,
ou une étape d’initialisation contrôlée, ou une classe de stockage qui évite hostPath pour les données stateful.

Est-ce que fsGroup suffit ?

Parfois. Si les permissions de groupe sont correctement définies (g+rwx) et que le processus est dans ce groupe, ça marche.
Mais cela ne corrigera pas les répertoires qui exigent le propriétaire et n’aidera pas si le mappage userns empêche la correspondance du GID.

Comment prouver que c’est le mappage UID/GID et non « les permissions Debian bizarres » ?

Faites un test bidirectionnel : créez un fichier dans le conteneur et inspectez son propriétaire numérique sur l’hôte. Puis inversez.
Si les IDs ne s’alignent pas, le verdict est un mismatch de mappage.

Quand dois-je utiliser des idmapped mounts au lieu de changer l’utilisateur du conteneur ?

Utilisez des idmapped mounts quand vous ne pouvez pas changer l’UID de l’application (image fournisseur), ne pouvez pas changer la propriété sur disque (partagé ou volumineux),
ou avez besoin de vues multiples des mêmes données avec des sémantiques de propriété différentes.

Prochaines étapes qui ne vous hanteront pas

« Permission denied » sur un bind mount est rarement un mystère et presque jamais résolu en rendant les permissions plus permissives.
Traitez-le comme un problème de mappage d’identité jusqu’à preuve du contraire.

Faites ceci ensuite, dans l’ordre :

  1. Capturez l’UID/GID numérique du chemin hôte et du processus conteneur.
  2. Confirmez si les espaces de noms utilisateur décalent les IDs (rootless est le coupable habituel).
  3. Éliminez les ACL et les refus AppArmor avec des vérifications ciblées.
  4. Choisissez une correction propre : aligner UID/GID d’exécution, standardiser les IDs sur les hôtes, ou utiliser des idmapped mounts pour la traduction.
  5. Codifiez cela dans le provisioning et la CI avec un test preflight d’écriture.

Si vous ne faites qu’une chose : cessez de faire confiance aux noms d’utilisateur lorsque vous déboguez les permissions de stockage. Les IDs numériques sont la vérité, et les bind mounts sont d’une honnêteté implacable.

← Précédent
DLSS expliqué : la plus grande astuce FPS de la décennie
Suivant →
Jeux de données ZFS par charge de travail : l’astuce de gestion qui prévient le chaos

Laisser un commentaire