ADR-033 : forge db:apply applique les migrations avec DB_ADMIN_*¶
Statut¶
Acceptée (mise en œuvre à suivre, ticket DB-APPLY-ADMIN-CREDS-001).
Renforce la doctrine de séparation des comptes de ADR-008 et la politique des comptes décrite dans docs/install/mariadb-comptes.md.
Corrige une contradiction doc/code et un défaut fonctionnel.
Contexte¶
Forge sépare deux comptes base de données :
| Compte | Rôle voulu |
|---|---|
DB_ADMIN_* (forge_admin) |
provisioning, migrations, structure |
DB_APP_* (forge_app) |
runtime applicatif (DML seulement) |
docs/install/mariadb-comptes.md est explicite : forge_app reçoit uniquement SELECT, INSERT, UPDATE, DELETE sur la base du projet, et ne doit pas recevoir CREATE, ALTER, DROP, INDEX, REFERENCES.
Or forge db:apply (et migration:status, lecture du schéma) se connectent via un unique _connect_db() dans cli/entities/migrations.py, qui utilise les identifiants DB_APP_* :
def _connect_db():
...
return mariadb.connect(
host=config.DB_APP_HOST, port=config.DB_APP_PORT,
user=config.DB_APP_LOGIN, password=config.DB_APP_PWD, ...
)
_apply_one_migration() exécute alors le DDL des migrations (CREATE TABLE, ALTER, DROP…) sur cette connexion forge_app.
Conséquences :
- Défaut fonctionnel.
Un utilisateur qui suitmariadb-comptes.md(forge_appen DML seul) voitforge db:applyéchouer (CREATE command denied to 'forge_app'). - Contradiction de doctrine.
Pour quedb:applymarche, il faut élargirforge_appau DDL (le commentaireDB_APP_PRIVILEGES=...,CREATE,ALTER,DROP,...deenv/example), ce qui contredit «forge_app= runtime à privilèges minimaux » et affaiblit la sécurité (le compte runtime peut modifier la structure et supprimer des tables).
forge db:init se connecte déjà correctement en DB_ADMIN_* (création base, utilisateur applicatif, grants).
Comportement changé depuis
L'ADR-067 a modifié forge db:init :
il affiche désormais le SQL de provisioning et ne se connecte qu'avec --run.
La décision de cet ADR, employer DB_ADMIN_* pour les migrations, reste en vigueur.
L'incohérence est isolée à la chaîne migrations.
Décision¶
forge db:apply, forge migration:status et la lecture de schéma se connectent en DB_ADMIN_*.
Les migrations sont des changements de structure, elles relèvent du compte d'administration du projet.
_connect_db() dans migrations.py lit DB_ADMIN_HOST/PORT/LOGIN/PWD (au lieu de DB_APP_*) et DB_NAME.
forge_admin détient déjà CREATE, ALTER, DROP, INDEX, REFERENCES, SELECT, INSERT, UPDATE, DELETE sur forge_db.* (cf. mariadb-comptes.md), donc il applique les migrations sans élargir forge_app.
Conséquences¶
Positives :
forge db:applyfonctionne avec les comptes tels que documentés (forge_appen DML seul) : la doc et le code redeviennent cohérents.forge_appreste minimal (DML) : il ne peut plus modifier la structure ni supprimer des tables.
Gain de sécurité net.- Le commentaire
DB_APP_PRIVILEGES=...,CREATE,ALTER,DROP,...disparaît deenv/example(il n'a plus de raison d'être). - Doctrine alignée :
forge_adminn'est utilisé que pour le provisioning et les migrations ; l'application runtime tourne enforge_app.
À assumer :
forge db:apply/migration:statusexigent désormaisDB_ADMIN_*dans l'environnement.
En dev c'est déjà le cas (env/dev).
En production, les migrations sont exécutées au déploiement avec les identifiants admin disponibles, conformément à «forge_adminutilisé uniquement pendant les migrations » (variante stricte demariadb-comptes.md).- Les messages d'erreur « Vérifiez
DB_APP_*/DB_NAME» deviennent « VérifiezDB_ADMIN_*/DB_NAME».
Mise en œuvre (ticket DB-APPLY-ADMIN-CREDS-001)¶
- Garde d'abord : test vérifiant que
_connect_db()litDB_ADMIN_*(et nonDB_APP_*). migrations.py:_connect_db()utiliseDB_ADMIN_HOST/PORT/LOGIN/PWD; adapter les messages d'erreur (DB_ADMIN_*).db_init.py:DEFAULT_APP_PRIVILEGESresserré au DML (le provisioning n'accorde plusCREATE/ALTER/DROP/INDEX/REFERENCESàforge_apppar défaut ; un override explicite viaDB_APP_PRIVILEGESreste possible).
env/example(squelette + dogfood) : retirer la ligne commentéeDB_APP_PRIVILEGES=...(le runtime n'a plus besoin de DDL).docs/install/mariadb-comptes.md: préciser queforge_adminapplique les migrations ; confirmerforge_appen DML strict (déjà le cas) ; retirer toute suggestion d'élargirforge_appau DDL.docs/install/mariadb.mdet la doc migrations : indiquer quedb:applyutiliseDB_ADMIN_*.- Tests : mettre à jour ceux qui assertaient les identifiants ou messages
DB_APP_*de la chaîne migrations (la plupart injectent une connexiondb=et ne sont pas affectés).
Validations :
python -m pytest -x -q
python -m compileall -q .
ruff check .
mkdocs build --strict
git diff --check
Suivi (ticket DB-APPLY-ADMIN-CREDS-FIX-001)¶
La première mise en œuvre (DB-APPLY-ADMIN-CREDS-001) a corrigé la chaîne migrations.py (migration:apply, migration:status, lecture de schéma), mais pas la commande forge db:apply elle-même, qui passe par un code distinct (cli/entities/db_apply.py, application du SQL des entités).
Ce chemin continuait de se connecter en DB_APP_* et échouait donc sur le CREATE TABLE des entités dès que forge_app était resserré au DML.
Le ticket DB-APPLY-ADMIN-CREDS-FIX-001 aligne db_apply.py sur la même décision : load_db_apply_config() lit DB_ADMIN_* + DB_NAME, et le message d'erreur renvoie « Vérifiez DB_ADMIN_* / DB_NAME ».
Les deux commandes qui appliquent du DDL (db:apply pour les entités, migration:apply pour les migrations) utilisent désormais le compte d'administration, conformément au titre et à la décision de cet ADR.
Alternatives rejetées¶
Garder db:apply sur DB_APP_* et accorder le DDL à forge_app.
C'est l'état actuel (compromis).
Rejeté : le compte runtime obtient des droits de structure, ce qui contredit la doctrine et la documentation, et augmente la surface en cas de compromission de l'application.
Ajouter un troisième compte « migrations ».
Rejeté : forge_admin couvre déjà ce rôle (il a le DDL sur la base du projet).
Un compte de plus complexifierait sans bénéfice.
Charte appliquée¶
Principe 5 (garder le SQL visible), principe 7 (sécuriser par défaut), principe 10 (API/contrat clair), règle B (révéler avant de corriger : la contradiction est nommée, pas masquée).