Creds Postgres dynamiques : les tables restent la propriété du rôle expiré → « permission denied » au pod suivant (kadans-api en CrashLoopBackOff) #17

Open
opened 2026-07-24 21:31:19 +02:00 by arcodange · 1 comment
Owner

Incident latent, constaté sur kadans-api le 2026-07-24 — l'API répond encore (/readyz 200) mais uniquement grâce au pod qui a créé les tables ; le pod suivant crashe en boucle :

migrations (base joignable ?) : ERROR: permission denied for table schema_migrations (SQLSTATE 42501)

Deux ReplicaSets vivants : kadans-api-7dc5ff66d7 (1/1, celui qui a migré la base) et kadans-api-d4745fbff (CrashLoopBackOff, 12 restarts). Même image dans les deux — ce n'est pas une régression applicative.

Diagnostic

Le module hashicorp-vault/iac/modules/app_roles fait bien GRANT <owner>_role TO "{{name}}" à la création du bail. Mais l'appartenance au rôle propriétaire ne donne pas accès aux objets créés par un autre rôle dynamique : les tables (schema_migrations, etc.) ont été créées par le rôle éphémère du premier pod, et lui appartiennent. Le rôle du pod suivant est un rôle frère, pas l'héritier.

Le revocation_statements prévoit bien REASSIGN OWNED BY "{{name}}" TO <owner>_role, mais il porte déjà son propre TODO en commentaire : « reassign must be executed in the database where the reassigned objects are » — il ne s'exécute pas dans la bonne base, donc le transfert de propriété n'a jamais lieu.

Conséquence générale (pas propre à Kadans) : toute app du homelab qui crée ses objets sous creds dynamiques est une bombe à retardement — elle survit tant que le pod fondateur vit, et meurt au premier redémarrage après expiration du bail.

Correctif proposé (à arbitrer — module partagé)

Faire en sorte que les objets appartiennent d'emblée au rôle commun, en ajoutant aux creation_statements :

"ALTER ROLE \"{{name}}\" SET ROLE ${local.owner_role};",

Toute session du rôle dynamique agit alors comme <owner>_role : les objets créés lui appartiennent, et tous les rôles dynamiques (déjà membres) y accèdent — le problème disparaît structurellement, sans dépendre du REASSIGN à la révocation.

Pour les bases déjà dans cet état (dont kadans), un rattrapage ponctuel est nécessaire :

REASSIGN OWNED BY <role_dynamique_actuel> TO kadans_role;
-- ou, si le rôle a expiré :
ALTER TABLE schema_migrations OWNER TO kadans_role;  -- + les autres tables

⚠ Le module est partagé (erp, webapp, …) : l'apply Vault passe par le workflow OIDC interactif, donc l'arbitrage et l'exécution te reviennent. Je n'ai rien modifié.

En attendant : ne pas redémarrer le pod kadans-api-7dc5ff66d7 — c'est lui qui tient l'API debout.

**Incident latent, constaté sur `kadans-api` le 2026-07-24** — l'API répond encore (`/readyz` 200) mais **uniquement grâce au pod qui a créé les tables** ; le pod suivant crashe en boucle : ``` migrations (base joignable ?) : ERROR: permission denied for table schema_migrations (SQLSTATE 42501) ``` Deux ReplicaSets vivants : `kadans-api-7dc5ff66d7` (1/1, celui qui a migré la base) et `kadans-api-d4745fbff` (CrashLoopBackOff, 12 restarts). **Même image** dans les deux — ce n'est pas une régression applicative. ## Diagnostic Le module `hashicorp-vault/iac/modules/app_roles` fait bien `GRANT <owner>_role TO "{{name}}"` à la création du bail. Mais **l'appartenance au rôle propriétaire ne donne pas accès aux objets créés par un *autre* rôle dynamique** : les tables (`schema_migrations`, etc.) ont été créées par le rôle éphémère du premier pod, et lui appartiennent. Le rôle du pod suivant est un rôle *frère*, pas l'héritier. Le `revocation_statements` prévoit bien `REASSIGN OWNED BY "{{name}}" TO <owner>_role`, mais il porte déjà son propre TODO en commentaire : *« reassign must be executed in the database where the reassigned objects are »* — il ne s'exécute pas dans la bonne base, donc le transfert de propriété n'a jamais lieu. Conséquence générale (pas propre à Kadans) : **toute app du homelab qui crée ses objets sous creds dynamiques est une bombe à retardement** — elle survit tant que le pod fondateur vit, et meurt au premier redémarrage après expiration du bail. ## Correctif proposé (à arbitrer — module partagé) Faire en sorte que les objets appartiennent d'emblée au rôle commun, en ajoutant aux `creation_statements` : ```hcl "ALTER ROLE \"{{name}}\" SET ROLE ${local.owner_role};", ``` Toute session du rôle dynamique agit alors comme `<owner>_role` : les objets créés lui appartiennent, et tous les rôles dynamiques (déjà membres) y accèdent — le problème disparaît structurellement, sans dépendre du `REASSIGN` à la révocation. Pour les bases **déjà** dans cet état (dont `kadans`), un rattrapage ponctuel est nécessaire : ```sql REASSIGN OWNED BY <role_dynamique_actuel> TO kadans_role; -- ou, si le rôle a expiré : ALTER TABLE schema_migrations OWNER TO kadans_role; -- + les autres tables ``` ⚠ Le module est partagé (erp, webapp, …) : l'`apply` Vault passe par le workflow OIDC interactif, donc l'arbitrage et l'exécution te reviennent. Je n'ai rien modifié. En attendant : **ne pas redémarrer le pod `kadans-api-7dc5ff66d7`** — c'est lui qui tient l'API debout.
Author
Owner

Incident résolu — le homelab avait déjà l'outil. Un CronJob pg-fix-table-ownership existe dans kube-system (déployé par factory/ansible/arcodange/factory/playbooks/setup/postgres.yml, planifié 0 3 * * *) : il boucle sur les rôles %_role, en dérive la base, et fait ALTER TABLE … OWNER TO <app>_role sur tout le schéma public.

Il ne pouvait rien pour kadans : son dernier passage (3 h du matin) précédait la création des tables (déploiement de l'API en fin d'après-midi). Run one-shot déclenché :

kubectl -n kube-system create job --from=cronjob/pg-fix-table-ownership pg-fix-kadans-now
→ Database for kadans_role: kadans
→ Changing owner to kadans_role for all tables in kadans...
→ Owner changed for kadans_role in kadans

Le pod qui bouclait depuis 48 min démarre désormais (1/1 Running, /readyz 200, rollout terminé). kadans-api n'est plus suspendue au pod fondateur.

Reste le fond, qui justifie cette issue : chaque nouvelle table naît propriété d'un rôle éphémère et n'est réparée qu'au prochain passage nocturne — toute app qui migre son schéma entre deux redémarrages rejoue le même crash. La cause racine est le TODO déjà écrit dans hashicorp-vault/iac/modules/app_roles/main.tf : le REASSIGN OWNED BY "{{name}}" TO <owner>_role de la révocation s'exécute sur la connexion …/postgres, jamais dans la base de l'app — il faudrait une vault_database_secret_backend_connection par base (connection_url …/${local.database}). L'alternative plus simple reste ALTER ROLE "{{name}}" SET ROLE <owner>_role; dans les creation_statements : les objets naissent alors propriété du rôle commun, et le CronJob nocturne devient un filet, plus un pansement.

Deux détails relevés au passage dans le CronJob (postgres.yml) : il dérive le nom de base par ${role%_role}, donc erp_sandbox_roleerp_sandbox alors que la base s'appelle erp-sandbox (cassé silencieusement — une résolution via pg_database.datdba corrigerait) ; et il ne traite que les tables (ni vues, ni fonctions, ni séquences hors dépendance).

**Incident résolu — le homelab avait déjà l'outil.** Un CronJob `pg-fix-table-ownership` existe dans `kube-system` (déployé par `factory/ansible/arcodange/factory/playbooks/setup/postgres.yml`, planifié `0 3 * * *`) : il boucle sur les rôles `%_role`, en dérive la base, et fait `ALTER TABLE … OWNER TO <app>_role` sur tout le schéma `public`. Il ne pouvait rien pour `kadans` : son dernier passage (3 h du matin) précédait la création des tables (déploiement de l'API en fin d'après-midi). Run one-shot déclenché : ``` kubectl -n kube-system create job --from=cronjob/pg-fix-table-ownership pg-fix-kadans-now → Database for kadans_role: kadans → Changing owner to kadans_role for all tables in kadans... → Owner changed for kadans_role in kadans ``` Le pod qui bouclait depuis 48 min démarre désormais (`1/1 Running`, `/readyz` 200, rollout terminé). **`kadans-api` n'est plus suspendue au pod fondateur.** Reste le **fond**, qui justifie cette issue : chaque nouvelle table naît propriété d'un rôle éphémère et n'est réparée qu'au prochain passage nocturne — toute app qui migre son schéma entre deux redémarrages rejoue le même crash. La cause racine est le TODO déjà écrit dans `hashicorp-vault/iac/modules/app_roles/main.tf` : le `REASSIGN OWNED BY "{{name}}" TO <owner>_role` de la révocation s'exécute sur la connexion `…/postgres`, jamais dans la base de l'app — il faudrait **une `vault_database_secret_backend_connection` par base** (`connection_url …/${local.database}`). L'alternative plus simple reste `ALTER ROLE "{{name}}" SET ROLE <owner>_role;` dans les `creation_statements` : les objets naissent alors propriété du rôle commun, et le CronJob nocturne devient un filet, plus un pansement. Deux détails relevés au passage dans le CronJob (`postgres.yml`) : il dérive le nom de base par `${role%_role}`, donc `erp_sandbox_role` → `erp_sandbox` alors que la base s'appelle `erp-sandbox` (**cassé silencieusement** — une résolution via `pg_database.datdba` corrigerait) ; et il ne traite que les tables (ni vues, ni fonctions, ni séquences hors dépendance).
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: arcodange-org/tools#17