Description
Symptôme
J'ai deux pods distincts avec le même symptôme — les deux restent en CrashLoopBackOff dans le namespace dso-gitlab :
gitlab-gitlab-shell-* — logs du conteneur principal gitlab-shell :
Begin parsing .tpl templates from /etc/gitlab-shell
Writing /srv/gitlab-shell/config.yml
Copying other config files found in /etc/gitlab-shell to /srv/gitlab-shell
Using existing Host Keys
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_ecdsa_key' for reading: Permission denied
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_ecdsa_key.pub' for reading: Permission denied
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_ed25519_key' for reading: Permission denied
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_ed25519_key.pub' for reading: Permission denied
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_rsa_key' for reading: Permission denied
cp: cannot open '/etc/gitlab-secrets/ssh/ssh_host_rsa_key.pub' for reading: Permission denied
gitlab-minio-* — logs du conteneur principal minio (12 redémarrages au moment où j'ai fait le diagnostic) :
time="2026-08-07T14:27:06Z" level=fatal msg="Config migration failed." cause="Failed to migrate
config from '20' to '21'. rename /tmp/.minio/$tmpfile.config.json.157491455 /tmp/.minio/config.json:
operation not permitted" source="[common-main.go:45:initConfig()]"
J'ai identifié que les deux erreurs viennent du même défaut sous-jacent (détaillé plus bas) — un Permission denied explicite pour gitlab-shell, un operation not permitted sur un rename pour minio (rejeté par le noyau Linux car le fichier cible appartient à un autre utilisateur que celui qui tente de le remplacer).
Ce bug me bloque toute la wave 30 de mon déploiement.
Root cause
Dans les deux cas, un pod contient deux conteneurs qui doivent se passer un fichier : un premier conteneur (init) l'écrit, un second (main) le lit ensuite. Le souci : les deux ne tournent pas sous le même utilisateur Linux (UID). Celui qui écrit est UID 65534, celui qui lit est UID 1000 — deux utilisateurs différents, ni l'un ni l'autre root, donc les permissions Unix classiques s'appliquent et bloquent l'accès croisé.
Sur un cluster sans profil de sécurité strict, ce problème n'existe pas : sans contrainte, les conteneurs tournent en root par défaut, et root peut lire/écrire n'importe quel fichier peu importe qui l'a créé. C'est uniquement parce que j'ai activé profile: cis (qui force runAsNonRoot sur tout) que cette différence d'UID, jusque-là invisible, devient bloquante.
gitlab-shell
- Un init container (
configure) copie les clés SSH host depuis un Secret vers un emptyDir partagé (shell-secrets), monté ensuite en lecture seule par le conteneur principal.
- Le conteneur principal (
gitlab-shell) lit ces fichiers copiés.
UID effectifs constatés (kubectl get pod -o json, .status.initContainerStatuses[].user / .status.containerStatuses[].user) :
| Conteneur |
Type |
UID effectif |
certificates |
initContainer |
65534 |
configure |
initContainer |
65534 |
gitlab-shell |
container (main) |
1000 |
L'init écrit les clés en tant qu'UID 65534, le main essaie de les lire en tant qu'UID 1000 → Permission denied.
minio
Même schéma : un init container (configure) écrit un fichier config.json dans un emptyDir (minio-server-config, monté sur /tmp/.minio), le conteneur principal minio essaie ensuite de le migrer/remplacer.
| Conteneur |
Type |
UID effectif |
configure |
initContainer |
65534 |
minio |
container (main) |
1000 |
Même mismatch (65534 écrit, 1000 lit), mais l'erreur prend une forme différente ici : le noyau Linux refuse le rename du fichier de config avec operation not permitted, parce que le fichier cible appartient à un autre utilisateur que celui qui tente de le remplacer.
Où se trouve le vrai défaut
J'ai trouvé où se trouve le vrai défaut — socle/roles/kyverno/templates/cis.yml.j2, ClusterPolicy security-context-dso-gitlab (ligne 270), règle enforce-security-context :
containers:
- name: '*'
securityContext:
+(allowPrivilegeEscalation): false
+(capabilities):
+(drop):
- ALL
+(runAsNonRoot): true
+(seccompProfile):
type: RuntimeDefault
initContainers:
- name: '*'
securityContext:
+(allowPrivilegeEscalation): false
+(capabilities):
+(drop):
- ALL
+(runAsNonRoot): true
+(seccompProfile):
type: RuntimeDefault
Cette policy impose runAsNonRoot: true (via l'opérateur Kyverno +(), qui n'ajoute la valeur que si le champ est absent) sur les containers et initContainers, mais ne fixe jamais runAsUser — contrairement à plusieurs policies voisines du même fichier (security-context-dso-*, ex. lignes 41-42, 103-104, 168-169, 234-235, 355-356) qui, elles, imposent bien un runAsUser identique et cohérent sur containers ET initContainers (65534, 1001, ou 999 selon le composant). security-context-dso-gitlab est la seule policy du fichier à ne pas suivre cette logique.
Résultat : chaque conteneur du pod gitlab-shell garde l'UID que le chart lui a assigné par défaut (65534 pour les init containers, 1000 pour le main), sans qu'aucune règle ne les force à être identiques.
Résolution
J'ai corrigé socle/roles/kyverno/templates/cis.yml.j2, policy security-context-dso-gitlab — remplacé l'ajout conditionnel par un remplacement forcé (sans le +, pour écraser la valeur déjà posée par le chart) sur les trois blocs securityContext (pod, containers, initContainers) :
securityContext:
+(runAsNonRoot): true
runAsUser: 1000
containers:
- name: '*'
securityContext:
+(allowPrivilegeEscalation): false
+(capabilities):
+(drop):
- ALL
+(runAsNonRoot): true
+(seccompProfile):
type: RuntimeDefault
runAsUser: 1000
initContainers:
- name: '*'
securityContext:
+(allowPrivilegeEscalation): false
+(capabilities):
+(drop):
- ALL
+(runAsNonRoot): true
+(seccompProfile):
type: RuntimeDefault
runAsUser: 1000
Correctif limité à security-context-dso-gitlab uniquement — aucune autre policy du fichier (-harbor, -keycloak, -vault...) touchée.
Pour l'appliquer, la tâche Ansible Create security-context ClusterPolicy for cis profile est create-only : elle vérifie l'existence de la policy security-context-dso (pas -gitlab elle-même) pour décider de régénérer tout le lot. Il a fallu supprimer cette policy pour forcer la recréation avec le contenu corrigé :
kubectl delete clusterpolicy security-context-dso
ansible-playbook install-gitops.yaml -e dsc_cr=dso-xxx -t kyverno
Puis forcer la recréation des pods déjà en échec :
kubectl delete pod -n dso-gitlab -l app=minio
kubectl rollout restart deployment gitlab-gitlab-shell -n dso-gitlab
Confirmé résolu : gitlab-shell et minio tournent tous les deux en 1/1 Running, zéro redémarrage depuis, plusieurs minutes après application.
Breaking change — vérification
Ce correctif force runAsUser: 1000 sur tous les Deployment/StatefulSet/Job du namespace dso-gitlab, pas seulement gitlab-shell/minio — j'ai donc vérifié l'impact sur le reste du namespace en supprimant tous les pods (kubectl delete pod --all -n dso-gitlab) pour forcer une recréation complète sous la nouvelle policy, y compris les composants avec stockage persistant (gitaly, PostgreSQL via CNPG) où un changement d'UID pourrait bloquer l'accès aux données déjà écrites par un autre utilisateur.
Résultat : gitaly, redis, pg-cluster (3/3 nœuds), pooler-pg-cluster, kas, gitlab-exporter — tous Running sans erreur après recréation. Aucune régression observée liée au changement d'UID.
Il est possible que ce changement puisse apporter d'autres soucis.
Etapes de reproduction
1. Aller à '...'
2. Cliquer sur '....'
3. Scroller jusqu'à '....'
4. Voir l'erreur
Captures d'écran
Logs
No response
Navigateurs
No response
OS
No response
Version de la console impactée
No response
Définition du fini
Description
Symptôme
J'ai deux pods distincts avec le même symptôme — les deux restent en
CrashLoopBackOffdans le namespacedso-gitlab:gitlab-gitlab-shell-*— logs du conteneur principalgitlab-shell:gitlab-minio-*— logs du conteneur principalminio(12 redémarrages au moment où j'ai fait le diagnostic) :J'ai identifié que les deux erreurs viennent du même défaut sous-jacent (détaillé plus bas) — un
Permission deniedexplicite pourgitlab-shell, unoperation not permittedsur unrenamepourminio(rejeté par le noyau Linux car le fichier cible appartient à un autre utilisateur que celui qui tente de le remplacer).Ce bug me bloque toute la wave 30 de mon déploiement.
Root cause
Dans les deux cas, un pod contient deux conteneurs qui doivent se passer un fichier : un premier conteneur (init) l'écrit, un second (main) le lit ensuite. Le souci : les deux ne tournent pas sous le même utilisateur Linux (UID). Celui qui écrit est UID 65534, celui qui lit est UID 1000 — deux utilisateurs différents, ni l'un ni l'autre root, donc les permissions Unix classiques s'appliquent et bloquent l'accès croisé.
Sur un cluster sans profil de sécurité strict, ce problème n'existe pas : sans contrainte, les conteneurs tournent en root par défaut, et root peut lire/écrire n'importe quel fichier peu importe qui l'a créé. C'est uniquement parce que j'ai activé
profile: cis(qui forcerunAsNonRootsur tout) que cette différence d'UID, jusque-là invisible, devient bloquante.gitlab-shellconfigure) copie les clés SSH host depuis unSecretvers unemptyDirpartagé (shell-secrets), monté ensuite en lecture seule par le conteneur principal.gitlab-shell) lit ces fichiers copiés.UID effectifs constatés (
kubectl get pod -o json,.status.initContainerStatuses[].user/.status.containerStatuses[].user) :certificatesconfiguregitlab-shellL'init écrit les clés en tant qu'UID 65534, le main essaie de les lire en tant qu'UID 1000 →
Permission denied.minioMême schéma : un init container (
configure) écrit un fichierconfig.jsondans unemptyDir(minio-server-config, monté sur/tmp/.minio), le conteneur principalminioessaie ensuite de le migrer/remplacer.configureminioMême mismatch (65534 écrit, 1000 lit), mais l'erreur prend une forme différente ici : le noyau Linux refuse le
renamedu fichier de config avecoperation not permitted, parce que le fichier cible appartient à un autre utilisateur que celui qui tente de le remplacer.Où se trouve le vrai défaut
J'ai trouvé où se trouve le vrai défaut —
socle/roles/kyverno/templates/cis.yml.j2,ClusterPolicysecurity-context-dso-gitlab(ligne 270), règleenforce-security-context:Cette policy impose
runAsNonRoot: true(via l'opérateur Kyverno+(), qui n'ajoute la valeur que si le champ est absent) sur les containers et initContainers, mais ne fixe jamaisrunAsUser— contrairement à plusieurs policies voisines du même fichier (security-context-dso-*, ex. lignes 41-42, 103-104, 168-169, 234-235, 355-356) qui, elles, imposent bien unrunAsUseridentique et cohérent sur containers ET initContainers (65534, 1001, ou 999 selon le composant).security-context-dso-gitlabest la seule policy du fichier à ne pas suivre cette logique.Résultat : chaque conteneur du pod
gitlab-shellgarde l'UID que le chart lui a assigné par défaut (65534 pour les init containers, 1000 pour le main), sans qu'aucune règle ne les force à être identiques.Résolution
J'ai corrigé
socle/roles/kyverno/templates/cis.yml.j2, policysecurity-context-dso-gitlab— remplacé l'ajout conditionnel par un remplacement forcé (sans le+, pour écraser la valeur déjà posée par le chart) sur les trois blocssecurityContext(pod, containers, initContainers) :Correctif limité à
security-context-dso-gitlabuniquement — aucune autre policy du fichier (-harbor,-keycloak,-vault...) touchée.Pour l'appliquer, la tâche Ansible
Create security-context ClusterPolicy for cis profileest create-only : elle vérifie l'existence de la policysecurity-context-dso(pas-gitlabelle-même) pour décider de régénérer tout le lot. Il a fallu supprimer cette policy pour forcer la recréation avec le contenu corrigé :Puis forcer la recréation des pods déjà en échec :
Confirmé résolu :
gitlab-shelletminiotournent tous les deux en1/1 Running, zéro redémarrage depuis, plusieurs minutes après application.Breaking change — vérification
Ce correctif force
runAsUser: 1000sur tous lesDeployment/StatefulSet/Jobdu namespacedso-gitlab, pas seulementgitlab-shell/minio— j'ai donc vérifié l'impact sur le reste du namespace en supprimant tous les pods (kubectl delete pod --all -n dso-gitlab) pour forcer une recréation complète sous la nouvelle policy, y compris les composants avec stockage persistant (gitaly, PostgreSQL via CNPG) où un changement d'UID pourrait bloquer l'accès aux données déjà écrites par un autre utilisateur.Résultat :
gitaly,redis,pg-cluster(3/3 nœuds),pooler-pg-cluster,kas,gitlab-exporter— tousRunningsans erreur après recréation. Aucune régression observée liée au changement d'UID.Il est possible que ce changement puisse apporter d'autres soucis.
Etapes de reproduction
Captures d'écran
Logs
No response
Navigateurs
No response
OS
No response
Version de la console impactée
No response
Définition du fini