MTES-MCT / asgard-postgresql

ASGARD. Système de gestion des droits pour PostgreSQL.
https://snum.scenari-community.org/Asgard/Documentation/co/SiteReference.html
Other
2 stars 3 forks source link

Echec à la restauration en cas de schéma référencé créé par une extension #17

Closed alhyss closed 1 month ago

alhyss commented 3 months ago

Résumé : La restauration d'une base sur laquelle Asgard est active est très susceptible d'échouer lorsqu'une autre extension active sur la base crée un ou plusieurs schémas qui sont référencés dans la table de gestion d'Asgard.

Cette anomalie, détaillée ci-dessous a été détectée lors de la restauration d'une base PostgreSQL 10 avec l'extension PlumePg sur PostgreSQL 16, néanmoins elle ne semble pas propre à une version spécifique de PostgreSQL et paraît pouvoir concerner d'autres extensions dès lors qu'elles créent des schémas.

La table z_asgard_admin.gestion_schema est marquée comme table de configuration de l'extension, ce qui fait que ses enregistrements sont inclus dans les fichiers de sauvegarde et restaurés avec le reste des données de la base. Ceci permet de préserver des informations qu'il n'est pas possible de déduire de l'état de la base. Notamment quels schémas sont ou non référencés par Asgard, leurs rôles lecteurs et éditeurs s'ils en ont, leur arborescence de classement.

Cette commande de restauration des données est susceptible d'échouer si les enregistrements qu'elle insère dans z_asgard_admin.gestion_schema ne respectent pas les contraintes définies sur la table. Notamment, elle échoue si l'un des enregistrements correspond à un schéma qui apparaitrait déjà dans la table. L'erreur - non respect de l'unicité du champ clé primaire nom_schema - est capturée en amont par le déclencheur asgard_on_modify_gestion_schema_before, qui renvoie un message de la forme suivante :

TB9. Saisie incorrecte (schéma z_plume). Un schéma de même nom est déjà répertorié dans la table de gestion.

Le déclencheur sur évènements asgard_on_create_schema est activé par les commandes CREATE SCHEMA et référence automatiquement les nouveaux schémas créés dans la table de gestion. Ceci n'engendre pas de conflit avec les commandes CREATE SCHEMA exécutées au cours du processus de restauration, car celui-ci restaure les schémas avant de restaurer les extensions. Autrement dit, le déclencheur asgard_on_create_schema n'intervient pas lors de la restauration des schémas, car, tant que la commande CREATE EXTENSION asgard n'a pas été lancée, il n'existe pas encore. Ainsi, les seuls enregistrements ajoutés à la table de gestion relativement à ces schémas le sont lors de la restauration des données des tables de configuration, et - hormis pour les OID qui, comme il se doit, seront automatiquement actualisés - les informations restaurées correspondent exactement à celles qui se trouvaient dans la table lors de la sauvegarde.

Dans le cas général, il n'y a donc pas d'erreur.

Mais les schémas créés par les extensions faussent la donne, car une extension quelconque est très susceptible d'être restaurée après Asgard, et non avant comme les schémas autonomes. Le risque est d'autant plus grand que le nom "Asgard" commence par la lettre "A" et a toutes les chances d'être l'une des premières extensions restaurées.

Au moment où le processus de restauration arrive à la commande CREATE EXTENSION, il exécute les commandes qui se trouvent dans le script de l'extension, incluant les éventuelles commandes CREATE SCHEMA. Si Asgard a alors déjà été restaurée, le déclencheur asgard_on_create_schema est actif et va référencer les schémas dans la table de gestion à mesure de leur création. Si ces mêmes schémas se trouvaient être déjà référencés dans la table de gestion sur la base sauvegardée, alors la commande de restauration des données de la table de configuration inclut des lignes pour ces schémas et va par conséquent échouer, car les enregistrements qu'elle tente de restaurer seront considérés comme des doublons des enregistrements qui viennent d'être insérés par le déclencheur.

Cet échec est lourd de conséquences, car il se traduit par un déréférencement de fait de tous les autres schémas, qui ne sont dès lors plus pris en compte par les mécanismes d'Asgard (mise en cohérence des propriétaires, etc.). Il reste possible de les re-référencer a posteriori avec les fonctions z_asgard_admin.asgard_initialisation_gestion_schema ou z_asgard.asgard_initialise_schema, mais au prix de la perte de toutes les informations qui n'apparaissaient que dans la table de gestion : éditeur, lecteur, valeurs des champs niv1, niv1_abr, etc.

Une anomalie annexe est que la restauration de la base va de fait silencieusement référencer dans la table de gestion d'Asgard tous les schémas créés par les extensions qui sont restaurées après Asgard, même si ces schémas n'étaient initialement pas référencés, et ce potentiellement pour d'excellentes raisons.

alhyss commented 3 months ago

En attendant un correctif, ci-après deux méthodes pour éviter que l'erreur n'apparaisse. Elles compliquent un peu le processus de sauvegarde/restauration mais sont fiables et ne sont pas susceptibles de produire des effets de bord.

Méthode n°1, avec une restauration par étape :

NB : Plusieurs procédés sont possibles pour séparer la restauration de la structure et des données selon le format de sauvegarde. Pour le format plain, il est nécessaire d'utiliser les paramètres -s / --schema-only et -a / --data-only de pg_dump pour obtenir deux scripts de restauration distincts à exécuter ensuite avec psql : la sauvegarde est faite en deux fois, d'abord avec pg_dump -s pour la structure puis pg_dump -a pour les données. Pour les autres formats, il possible d'utiliser la même méthode ou de ne créer qu'une seule archive au moment de la sauvegarde et d'utiliser ensuite les paramètres -s / --schema-only et -a / --data-only de pg_restore pour ne restaurer à chaque étape que la partie de l'archive qui doit l'être.

Méthode n°2, en intervenant avant la sauvegarde :

La méthode n°1 est conseillée dans le cas général, car elle ne nécessite pas de reprise manuelle.

Pour connaître les schémas créés par des extensions sur une base :

SELECT 
    pg_namespace.nspname AS nom_schema, 
    pg_extension.extname AS nom_extension
    FROM pg_depend 
        INNER JOIN pg_extension ON pg_extension.oid = pg_depend.refobjid
        INNER JOIN pg_namespace ON pg_namespace.oid = pg_depend.objid
    WHERE pg_depend.deptype = 'e' 
        AND pg_depend.classid = 'pg_namespace'::regclass 
        AND pg_depend.refclassid = 'pg_extension'::regclass
        AND NOT pg_namespace.nspname IN ('z_asgard', 'z_asgard_admin') ;

Les schémas d'Asgard sont exclus de la commande ci-avant, car leur référencement éventuel ne pose pas de problème. Le script de l'extension assure qu'ils soient créés avant le déclencheur asgard_on_create_schema.

Plutôt que de déréférencer un par un les schémas concernés, il est possible d'inclure la fonction de référencement dans la requête précédente :

SELECT 
    pg_namespace.nspname AS nom_schema, 
    pg_extension.extname AS nom_extension,
    z_asgard_admin.asgard_sortie_gestion_schema(pg_namespace.nspname) AS dereferencement
    FROM pg_depend 
        INNER JOIN pg_extension ON pg_extension.oid = pg_depend.refobjid
        INNER JOIN pg_namespace ON pg_namespace.oid = pg_depend.objid
    WHERE pg_depend.deptype = 'e' 
        AND pg_depend.classid = 'pg_namespace'::regclass 
        AND pg_depend.refclassid = 'pg_extension'::regclass
        AND NOT pg_namespace.nspname IN ('z_asgard', 'z_asgard_admin') ;

La fonction indique __ DEREFERENCEMENT REUSSI. dès lors qu'il n'y a pas eu d'erreur et même si le schéma n'était pas référencé. Dans ce cas elle n'a juste aucun effet.

alhyss commented 3 months ago

Avertissements temporairement ajoutés dans la documentation - à retirer lorsque la version corrective aura été publiée :

J'en ai profité pour ajouter une remarque (pérenne) sur le fait qu'il est fortement déconseillé de désactiver les déclencheurs pendant la restauration.

alhyss commented 1 month ago

Problème résolu par la version 1.4.1. La fonction z_asgard_admin.asgard_on_modify_gestion_schema_before appelée par le déclencheur asgard_on_modify_gestion_schema_before procède désormais à un dé-référencement préalable en cas de tentative d'insertion dans la table de gestion d'un schéma actif déjà référencé.

https://github.com/MTES-MCT/asgard-postgresql/blob/f8ee3a65e64b25c39d45f7eabe0c39c984c27fd4/asgard--1.4.0--1.4.1.sql#L700

Le déréférencement n'affectant pas les droits, cette manipulation est peu susceptible d'avoir des effets de bord gênants. Elle pourra échouer dans certains cas - notamment pour les schémas de la nomenclature nationale dont le dé-référencement n'est pas autorisé - mais devrait au moins permettre de gérer le cas des schémas créés par des extensions.