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 suit
mariadb-comptes.md
(forge_appen DML seul) voitforge db:applyéchouer
(CREATE command denied to 'forge_app'). - Contradiction de doctrine. Pour que
db:applymarche, il faut élargir
forge_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). 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 de
env/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 non
DB_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:apply
utiliseDB_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).