Config::Model – Créer un éditeur graphique de configuration avec Perl (1ère partie)

GNU/Linux Magazine n° 117 | juin 2009 | Dominique Dumont
Creative Commons
  • Actuellement 0 sur 5 étoiles
0
Merci d'avoir participé !
Vous avez déjà noté cette page, vous ne pouvez la noter qu'une fois !
Votre note a été changée, merci de votre participation !
La configuration d'une application est très souvent le premier obstacle que doit franchir un utilisateur avant de pouvoir utiliser une application. Le plus souvent, l'utilisateur est dirigé vers un fichier qu'il doit éditer avec « son éditeur favori ». Peu d'applications proposent une interface plus conviviale. Pour combler cette lacune, cet article décrit comment créer un éditeur de configuration d'une manière simple et maintenable. Dans la première partie de cet article, nous allons spécifier le modèle de sshd_config, c'est-à-dire sa structure et ses contraintes. Ce modèle permettra à Config::Model de générer l'interface graphique. Nous verrons dans une seconde partie comment lire et écrire les données de sshd_config pour les charger dans l'interface.

1. Introduction

Quand on lance pour la première fois un logiciel, il est courant de voir un message du genre : « Ce logiciel n'est pas configuré. Veuillez lire la documentation et éditer /etc/machin.conf ». Dans le meilleur des cas, le fichier de configuration en question va contenir des explications sous forme de commentaires. Dans d'autres cas, la documentation est dans un fichier séparé (par exemple /usr/share/doc/machin/README) ou dans une page de manuel.

À charge pour l'utilisateur de lire la documentation, déterminer les informations à ajouter, comprendre la syntaxe du fichier de configuration, ajouter les informations, sauvegarder le fichier et enfin lancer le logiciel.

Chaque étape peut poser un problème :

  • la documentation peut être conséquente, et il est souvent difficile de trouver les points importants.
  • la syntaxe du fichier peut être facile (ex. fichiers INI) ou plus difficile à éditer (XML)
  • pour ajouter les informations, la difficulté est d'entrer des valeurs pertinentes et valides, et d'en estimer les conséquences.

Pour l'utilisateur novice, l'idéal est d'avoir un wizard [WIZARD] pour le guider. Ce wizard va guider l'utilisateur à travers les étapes importantes tout en fournissant les explications nécessaires. La validation des données et l'écriture du fichier seront prises en charge par le wizard.

Le wizard est moins adapté pour un utilisateur sachant à l'avance quelle information il doit modifier. L'idéal pour cet utilisateur est un éditeur interactif qui prendrait en charge aussi la validation des données et l'écriture du fichier.

Malheureusement, peu de projets fournissent de tels outils, car leur écriture est assez longue et rébarbative. De plus, chaque changement dans la structure ou le contenu des fichiers de configuration peut entraîner des modifications importantes de ces outils.

Pour remédier à cette situation, Config::Model propose un environnement où les développeurs de projet peuvent créer un éditeur de configuration qui fournira :

  • la documentation en ligne ;
  • la validation des données de configuration ;
  • la classification en plusieurs niveaux d'expérience des données de configuration pour adapter la quantité de paramètres présentés en fonction du niveau de connaissances de l'utilisateur ;
  • un mode wizard pour les novices.

Bien sûr, le développeur de projet devra fournir certaines informations pour que Config::Model puisse créer un éditeur de configuration :

  • la structure de la configuration et ses paramètres ;
  • pour chaque paramètre, ses contraintes, le niveau d'expérience demandé, l'aide à afficher en ligne...

Toutes ces données fournies par le développeur de projet seront dans une structure de données, ce qui permettra une évolution facile durant la vie du projet. En d'autres termes, cette description est à la configuration du projet, ce que la DTD est à un document XML.

Cet article va :

  • dresser un état des lieux en évoquant les différentes formats en vogue pour stocker les informations de configuration et les outils de configuration de quelques projets populaires ;
  • expliquer l'architecture de Config::Model ;
  • détailler comment créer un modèle en prenant comme exemple la création d'un modèle pour le fichier sshd_config du projet OpenSSH [OPENSSH] ;
  • détailler comment écrire les fonctions pour charger les informations contenues dans sshd_config et les réécrire dans ce fichier ;
  • décrire les différentes interfaces utilisateur proposées par Config::Model ;
  • évoquer le futur du projet.

2. État des lieux

2.1 Configuration et format de stockage

Dans les systèmes UNIX, on trouve dans le répertoire /etc énormément de fichiers de configuration. Ces fichiers ont des syntaxes variées :

  • Très simple comme /etc/ssh/sshd_config où chaque ligne est une clef avec une valeur. Par exemple :

  PermitRootLogin no

  IgnoreRhosts yes

  • Un peu plus élaborée avec la fameuse syntaxe INI, où les paramètres de configuration sont classifiés en sections. Par exemple, voici un extrait de amarokrc :

  [General]

  XMLFile=amarokui.rc

[BrowserBar]

  CurrentPane=ContextBrowser

  • Plus compliquée avec une configuration structurée et une syntaxe adaptée comme la configuration d'Apache. Cette syntaxe permet de spécifier une hiérarchie. Dans cet exemple, les paramètres Order et Deny ne s'appliquent qu'aux fichiers commençant par .ht :

  <Files ~ "^\.ht">

    Order allow,deny

    Deny from all

  </Files>

  • Voire très compliquée avec un langage spécifique, des variables et des instructions conditionnelles comme la configuration d'Exim :

  lowuid_aliases:

    debug_print = "R: lowuid_aliases for $local_part@$domain (UID $local_user_uid)"

    check_local_user

    driver = redirect

    allow_fail

    domains = +local_domains

    condition = COND_SYSTEM_USER_AND_REMOTE_SUBMITTER

    data = ${if exists{/etc/exim4/lowuid-aliases}\

              {${lookup{$local_part}lsearch{/etc/exim4/lowuid-aliases}\

              {$value}{DEFAULT_SYSTEM_ACCOUNT_ALIAS}}}{DEFAULT_SYSTEM_ACCOUNT_ALIAS}}

D'autres syntaxes ont été définies pour permettre de stocker des données structurées. Les plus connues sont XML, JSON et YAML.

Le choix d'une syntaxe pour la configuration d'une application est un compromis entre la facilité d'édition et la complexité des données à traiter.

2.2 Outils de configuration de quelques projets populaires

L'édition de fichier de configuration est souvent considérée par les utilisateurs novices comme un répulsif : trop de documentation à lire et trop de possibilités.

Certains projet ont essayés de s'attaquer à ce problème en proposant des interfaces plus conviviales à leur utilisateurs :

  • Les projets dérivés de Xine (par exemple Kaffeine, gXine, Xine) proposent une interface présentant des vues différentes en fonction du niveau d'expertise de l'utilisateur.
  • Le processus de compilation du noyau Linux propose plusieurs interfaces : une interface ligne, une basée sur curses et une graphique. Toutes ces interfaces sont générées à partir des fichiers Kconfig distribués avec les sources du noyau.
  • KDE fournit une approche globale de la configuration avec le centre de configuration KDE. Ce projet permet aux utilisateurs de KDE de configurer toute leur machine à partir d'une application unique qui fournit à la fois de l'aide en ligne, et de la validation des données entrées. Cette approche a un coût : chaque écran de configuration est implémenté par une classe C++ dédiée (KCModule [KDE]). Ce code va devoir gérer la lecture et l'écriture des fichiers de configuration, la validation et la présentation.

D'autres projets ont une approche plus générale et fournissent une interface cohérente pour configurer un ensemble d'applications. On peut citer Webmin et les system-config-* de Red Hat. Ces projets utilisent une approche similaire à KDE : un framework et beaucoup de code dédié qui mélange le traitement des fichiers de configuration, la logique de validation et la présentation. Je n'ose pas imaginer les efforts requis pour suivre l'évolution de tous les spécifications des projets gérés et la course pour adapter le code à chaque nouvelle version des projets gérés.

3. Architecture de Config::Model

Un des principes fondateurs de Config::Model est de séparer les parties présentation, validation et gestion des données persistantes en trois parties distinctes.

La figure ci-dessous présente :

  • En vert, le fichier de configuration de l'utilisateur (le fil de fer sur la gauche) ;
  • En bleu, les parties fournies par le développeur de l'application :
  • L'application qui va utiliser le fichier de configuration crée par Config::Model (sur la droite),
  • Le modèle de configuration. Celui-ci sera chargé par Config::Model pour créer la « machine de validation » de la configuration,
  • Les routines de lecture et d'écriture qui vont charger les valeurs contenues dans le fichier de configuration dans la partie « validation ».
  • En jaune, les parties fournies par Config::Model :
  • Config::Model et la « machine de validation » créée avec le modèle. La « machine de validation » va contenir en mémoire interne une représentation des données de configuration sous forme d'arbre. On parle de « l'arbre de configuration ».
  • Les interfaces utilisateur générées par Config::Model à partir du modèle.

Pour être plus précis, le modèle de configuration va décrire :

  • La structure des données. C'est-à-dire quelle relation logique ont les différents éléments de la configuration. Par exemple, si on rajoute une imprimante dans /etc/cups/printers.conf, il va falloir paramétrer aussi d'autres éléments de configuration comme Info, Accepting, Shared...
  • La liste des éléments à configurer et leur emplacement dans la structure.
  • Les propriétés de chaque élément : type (nombre, entier, booléen, énumération ...), valeur minimum ou maximum ...
  • Une description de chaque élément pour pouvoir renseigner l'utilisateur quand celui-ci voudra éditer sa configuration. On pourra aussi ajouter des renseignements pour préciser l'effet des différentes valeurs possibles d'un type énuméré.

3.1 Structure d'un modèle

Config::Model part du principe que les données de configuration sont structurées en arbre. Ceci permet une bonne correspondance entre la structure des données écrites dans les fichiers de configuration et la structure du modèle de la configuration.

Chaque nœud de l'arbre est une instance d'une classe de configuration. Chacune de ces classes va avoir des éléments qui peuvent être :

  • un autre nœud de l'arbre. (type => "node").
  • une feuille (une donnée de configuration). Chaque feuille peut être une valeur unique (type => "leaf") ou une liste de choix (type => "checklist").
  • une liste de nœuds ou de feuilles (type => "list").
  • une liste nominative (un hash en vocabulaire perlien) de nœuds ou de feuilles (type => "hash").

Chaque élément d'une classe de configuration va avoir des propriétés :

  • experience qui permet de classifier les éléments selon le niveau de connaissance requis pour les manipuler : beginner (débutant), advanced (avancé) ou master. Cette propriété permet de simplifier les données à configurer pour les débutants.
  • level qui permet de classifier les éléments selon leur importance : important, normal ou hidden(caché). Cette dernière valeur sera expliquée avec les mécanismes de warp.
  • status permet de gérer le cycle de vie des éléments et de préparer le terrain pour les mises à jour (upgrade) des données de configuration : standard, deprecated (périmé) ou obsolete.
  • description est repris par les interfaces utilisateur pour fournir de l'aide en ligne.

Pour plus de détails, voir la documentation de Config::Model::Node.

3.2 Propriétés des feuilles

Chaque feuille de l'arbre représente une donnée de configuration. Les éléments de type leaf doivent être déclarés avec un type (value_type). Ce type peut être :

  • boolean
  • enum – Les valeurs possibles d'un type enum doivent être spécifiées avec le paramètre choice.
  • integer – Type entier non nul
  • number
  • uniline – Une chaîne de caractères d'une ligne
  • string – Une chaîne de caractères quelconque
  • reference – C'est comme le type énuméré sauf que les choix possibles du type énuméré sont définis par les clefs d'un élément de type hash quelque part ailleurs dans l'arbre de configuration. (Rassurez-vous, on n'en aura pas besoin pour la configuration de Sshd. C'est plus adapté aux modèles compliqués comme celui d'Xorg)

4. Comment écrire le modèle

Un modèle de configuration peut être passé à Config::Model de trois manières :

  • Directement en créant un objet Config::Model et en appelant create_config_class dans une application :

    # create new Model object

    my $model = Config::Model->new();

    # create config model

    $model ->create_config_class(

        name    => "UneClasseDeConfig",

        element => [ ... ],

);

Mais, inclure un modèle dans une application peut en rendre la maintenance plus difficile.

  • En fournissant un fichier qui ne contient que le modèle de configuration. Ce fichier spécifie juste une structure de données Perl :

[

   {

     name => "UneClasseDeConfig",

     element => [ ... ]

   },

   {

     name => "UneAutreClasseDeConfig",

     element => [ ... ]

}

];

Mais, écrire des structures de données Perl peut être rébarbatif.

  • En utilisant un éditeur graphique de modèle de configuration. Celui-ci est fourni par Config::Model::TkUI et Config::Model::Itself.

  $ config-model-edit -model UneClasseDeConfig

On obtient cette interface :

5. Config::Model appliqué à OpenSSH

Comme j'en vois qui baillent au fond, voici ce que ça donne appliqué à la configuration du daemon sshd d’OpenSSH. On va avoir (sous Debian) :

  • les données persistantes stockées dans le fichier de configuration /etc/ssh/sshd_config.
  • la gestion de la lecture et l'écriture du fichier sshd_config.
  • le modèle de sshd_config, c'est-à-dire une description formelle (que l'on va détailler dans cet article) des données qui peuvent être stockées dans le fichier sshd_config. Pour construire ce modèle, une lecture attentive de la page de manuel sshd_config est requise.
  • la logique pour exploiter ce modèle. Tout est fourni par Config::Model.
  • la présentation, une interface avec l'utilisateur qui est aussi fournie par le projet Config::Model. Cette interface va être générée en fonction du contenu du modèle de sshd_config.

Si on dresse le bilan, que faut-il écrire pour avoir un outil de gestion de la configuration de sshd ?

  • Le modèle. Ce modèle est une structure de données sans aucune instruction. La maintenance et l'évolution de ce modèle seront beaucoup plus faciles que la maintenance des modules de gestion de la configuration de KDE.
  • Deux routines de lecture et d'écriture du fichier de configuration.

On va maintenant voir plus en détail comment spécifier les différentes parties du modèle de configuration.

5.1 Déclaration du modèle de sshd_config

5.2 Installation de l'éditeur de modèle

Pour se simplifier la vie, la déclaration du modèle de configuration de sshd_config sera faite avec l'éditeur graphique de modèle fourni par Config::Model::Itself et Config::Model::TkUI.

Au moment de la rédaction de cet article, ces modules ne sont disponibles que sur CPAN et sur Debian/Sid.

Sur Debian (en version unstable), vous pouvez installer l'éditeur de modèles et ses dépendances avec aptitude :

# aptitude install libconfig-model-itself-perl

Pour les autres systèmes, le plus simple est d'utiliser la commande cpan pour installer Config::Model::Itself v0.203 ou une version ultérieure. Les autres modules seront installés automatiquement par le jeu des dépendances.

  $ cpan Config::Model::Itself

Pour lancer l'éditeur graphique du modèle Sshd, lancez cette commande :

$ config-model-edit -model Sshd

5.3 Déclaration de la classe Sshd

L'arbre de configuration de sshd_config ressemble à un râteau, car tous les éléments de configuration sont au même niveau. La classe qui va représenter le nœud racine de cette arbre est Sshd.

En utilisant la commande config-model-edit -model Sshd, on lance l'éditeur graphique de modèle. Il faut en premier créer la classe de configuration de la racine de l'arbre :

  • faites un clic droit sur class.
  • dans la fenêtre à côté de Add item, entrez le nom de la classe Sshd.
  • cliquez sur Add item:.
  • sauvez le modèle avec le menu File->save.

config-model-edit aura créé pour vous une arborescence de développement d'un modèle :

  $ tree lib

    lib

    `-- Config

`-- Model

            `-- models

                `-- Sshd.pl

Et Sshd.pl contient le squelette du modèle Sshd :

  [

     {

       'name' => 'Sshd'

     }

  ] ;

La page de manuel de sshd_config fournit la liste des éléments qui devront être spécifiés dans la classe de configuration Sshd. Je vais expliquer la création du modèle en choisissant les éléments de façon à couvrir les possibilités de Config::Model. Les éléments restant ne seront pas détaillés, mais seront rajoutés au modèle réel disponible sur SourceForge et sur le CPAN.

5.4 Les bases avec AllowTcpForwarding

Voici l'extrait de la page de manuel de sshd_config qui spécifie ce paramètre : «Specifies whether TCP forwarding is permitted. The default is "yes". Note that disabling TCP forwarding does not improve security unless users are also denied shell access, as they can always install their own forwarders.»

AllowTcpForwarding est donc une simple valeur de type boolean. Pour la créer avec config-model-edit, il faut ajouter un élément dans la classe Sshd :

  • ouvrez la classe Sshd en cliquant sur le [+] sur la ligne de la classe Sshd.
  • clic droit sur element.
  • entrez AllowTcpForwardingdans le champ à côté du bouton Add item after selection et cliquez dessus.
  • dans la fenêtre de gauche, ouvrez element et AllowTcpForwarding.

Vous noterez sur l'éditeur un petit panneau qui indique une erreur. C'est normal. Il faut impérativement renseigner le type de l'élément AllowTcpForwarding et le mettre à leaf.

  • Cliquez sur le bouton Edit..., sur le champ leaf et, enfin, sur le bouton Store. Vous verrez apparaître d'autres champs à renseigner comme value_type ou default.
  • Renseignez le champ value_type à boolean de la même manière.
  • Définissez le champ built_in à 1. Le paramètre built_in est utilisé pour spécifier une valeur par défaut qui est connue de l'application Sshd. Ceci permettra de signaler à l'utilisateur si la valeur de AllowTcpForwarding est différente de la valeur par défaut (avec la flèche verte visible sur les captures d'écran) et de ne pas surcharger le fichier de configuration /etc/ssh/sshd_config. En effet, config-edit n'écrira pas dans le fichier de configuration une valeur identique à une valeur built_in.

Voilà, c'est tout pour le début. On verra plus tard comment renseigner l'aide en ligne. On peut tout de suite avoir un aperçu de l'interface de configuration de sshd_config en cliquant sur le menu Model->Test. On obtient une nouvelle fenêtre. En cliquant sur AllowTcpForwardingdans cette nouvelle fenêtre, on obtient :

On peut aussi regarder le code généré par l'éditeur du modèle (légèrement réorganisé pour le rendre plus clair) :

[

   {

     'name' => 'Sshd',

     'element' => [

                    'AllowTcpForwarding',

                    {

                      'value_type' => 'boolean',

                      'built_in' => '1',

'type' => 'leaf'

                    }

                  ]

    }

] ;

Par la suite, pour alléger l'article, seuls le nom de l'élément et ses paramètres seront extraits du code généré. C'est-à-dire seul le contenu du array ref (entre [ et ]) sera montré.

5.5 Un élément de type liste avec AcceptEnv

Extrait de sshd_config :

Specifies what environment variables sent by the client will be copied

into the session's environ(7). Variables are specified by name, which

may contain the wildcard characters '*' and '?'. Multiple environment

variables may be separated by whitespace or spread across multiple

AcceptEnv directives.

Cet élément est donc une liste de variables d'environnement. En termes de modèle, on va utiliser un élément de type list. Le contenu de cette liste (cargo) sera de type leaf et uniline.

Sur l'éditeur, il faudra répéter des actions similaires à celles utilisées avec l'élément précédent pour :

  • créer le nouvel élément dans la classe Sshd (clic droit sur Element dans la partie gauche de l'éditeur) ;
  • assigner list au paramètre type ;
  • ouvrir le paramètre cargo ;
  • assigner leaf au paramètre type contenu dans cargo ;
  • assigner uniline au paramètre value_type contenu dans cargo.

Vous devriez obtenir ceci dans l'éditeur du modèle :

Dans l'éditeur, les flèches vertes indiquent des valeurs changées par rapport aux valeurs par défaut. Dans l'image précédente, les flèches vertes correspondent aux informations stockées dans le code du modèle :

  'AcceptEnv' => {

                   'type' => 'list',

                   'cargo' => { 'type' => 'leaf',

                                'value_type' => 'uniline',

}

                 },

Pour alléger la suite de l'article, l'utilisation de l'éditeur sera moins détaillée. Chaque nouvel élément devra être ajouté à la classe Sshd (clic droit sur element). Ensuite les informations données avec le modèle (c’est-à-dire la structure de donnée Perl) devront être reportées dans l'éditeur :

  • Chaque hash ref (les accolades) ou chaque array ref (les [ et ]) correspondent à l'ouverture d'un paramètre dans l'éditeur : il faudra cliquer sur le [+] correspondant à l'élément.
  • Les noms trouvés dans la structure de données correspondent à des paramètres ou à des informations à rentrer.

5.6 Banner

Se transforme en monstre vert quand trop de pirates tentent de se connecter. Euh, non, cet élément spécifie un nom de fichier dont le contenu doit être affiché par sshd lors de la connexion. Pour le modèle, c'est juste une valeur de type uniline.

  'Banner' => {

                'type'       => 'leaf',

                'value_type' => 'uniline',

}

5.7 Une liste à choix multiples avec Ciphers

D'après la page de manuel de sshd_config, cet élément est une liste d'algorithmes de chiffrement choisis parmi des choix possibles. C'est une feuille de l'arbre de configuration avec un type check_list et des choix validés par défaut. Dans l'éditeur, il faudra renseigner les choix disponibles :

  • Dans l'élément Ciphers, éditez choice.
  • Dans le champ à droite de push item, ajoutez le premier choix (3des-cbc).
  • Cliquez sur push item et répétez l'opération pour chaque choix de chiffrement accepté par ssh.

Cette méthode étant vite pénible, vous pouvez aussi faire un copier-coller à partir de la liste fournie par la documentation de sshd_config dans le champ du bouton set all et cliquer sur ce bouton.

Pour finir, on obtient ce modèle :

'Ciphers' =>

{

   'type' => 'check_list',

'choice' => [

                 '3des-cbc', 'aes128-cbc', 'aes192-cbc',

                 'aes256-cbc', 'aes128-ctr', 'aes192-ctr',

                 'aes256-ctr', 'arcfour128', 'arcfour256',

                 'arcfour', 'blowfish-cbc', 'cast128-cbc'

               ],

}

5.8 Fournir l'aide intégrée avec GatewayPorts

Cet élément peut prendre 3 valeurs : yes, no et clientspecified. C'est donc un type énuméré.

Pour faciliter la vie de l'utilisateur final, on va pouvoir aussi :

  • renseigner le champ description pour que le futur utilisateur de votre éditeur de configuration de Sshd n'ait pas à lire la page de manuel. (cliquez droit sur le paramètre description dans l'éditeur du modèle et faites un copier-coller de la man page)
  • spécifier l'effet de chaque valeur avec le champ help du modèle :
  • cliquez droit sur help dans l'arbre de configuration à gauche ;
  • faites Add item avec yes comme valeur ;
  • cliquez droit sur yes (juste en dessous de help) ;
  • faites un copier-coller de l'effet de yes à partir de la page de manuel.
  • répétez l'opération pour clientspecified.

On obtient ceci dans l'éditeur :

Et ce modèle :

  'GatewayPorts' =>

  {

    'type'        => 'leaf',

    'value_type' => 'enum',

    'description' => 'Specifies whether remote hosts [...]',

    'help'        => {

                       'yes' => 'force remote port forwardings [...]',

                       'clientspecified' => 'allow the client to [...]',

                       'no' => 'No port forwarding',

                     },

    'built_in'    => 'no',

    'choice'      => [ 'yes', 'clientspecified', 'no']

}

5.9 Copier/coller avec GSSApiAuthentication et GSSAPIKeyExchange

Ces deux éléments sont des booléens avec des valeurs par défaut à 0. Le plus simple est d'abord de définir le modèle de GSSApiAuthentication :

'GSSApiAuthentication' => {

                             'type' => 'leaf' ,

                             'value_type' => 'boolean',

                             'built_in' => '0',

},

Puis de le copier dans GSSAPIKeyExchange :

  • entrez dans la fenêtre d'édition des éléments de Sshd.
  • sélectionnez GSSApiAuthentication.
  • rentrez GSSAPIKeyExchange dans le champ à droite de Copy selected item into.
  • cliquez sur Copy selected item into.

Et voilà. Il ne reste plus qu'à renseigner les descriptions de chaque élément.

5.10 Un entier et une limite avec ServerKeyBits

Ce paramètre est un entier avec une valeur par défaut à 768 et une valeur minimale de 512 :

  'ServerKeyBits' =>

      {

         'type'        => 'leaf',

         'value_type' => 'integer',

         'min'         => '512',

         'built_in'    => '768',

         'description' => 'Defines the number of [...]',

}

5.11 ClientAliveInterval et ClientAliveCountMax

ClientAliveInterval est documenté comme étant une valeur à double sens. Quand elle est nulle, la fonction de détection des clients inactifs est invalidée et ClientAliveCountMax ne sert à rien.

Pour faciliter la vie l'administrateur de Sshd, on peut choisir de créer un élément ClientAliveCheck et ne présenter les deux autres paramètres à l'administrateur que si ClientAliveCheck est vrai. Ça a pour avantage d'alléger l'interface de l'éditeur. Mais, d'un autre côté, s'éloigner de la spécification de sshd_config en ajoutant un paramètre « artificiel » peut perturber les administrateurs chevronnés. Eh oui, dès qu'il faut tenir compte de l'historique, il n'y a pas de solution idéale, juste des compromis.

On va courir le risque de déplaire aux administrateurs (ne faites surtout pas ça à la maison ou au boulot ! ;-) ), en créant le paramètre ClientAliveCheck. Dans le cadre de cet article, ce paramètre « artificiel » est ajouté dans un but pédagogique pour expliquer le mécanisme de warping (déformation) du modèle. Ce mécanisme, très utile pour des modèles plus compliqués comme Xorg, permet de masquer ou de faire apparaître des paramètres selon les besoins.

5.12 Qu'est ce que le warping ?

Dans certains cas, le côté statique d'un modèle de configuration ne suffit plus. Des valeurs par défaut, des choix ou des éléments de configuration peuvent changer en fonction d'une autre donnée de configuration.

Prenons par exemple la configuration de Xorg. En fonction de votre modèle de carte graphique (Ati ou Nvidia, les pilotes possibles changent :

  • nvidia, nv et bientôt nouveau pour les cartes Nvidia ;
  • ati, radeon, radeonhd et fglrx pour cartes Ati.

Et chaque pilote a son propre jeu d'options disponibles (je vous passe les détails). En fonction du pilote choisi par l'utilisateur, les options et la structure du modèle changent complètement. C'est implémenté dans Config::Model avec le mécanisme de warping.

5.13 Application du warping sur ClientAlive

Comme indiqué au-dessus, on va introduire un nouveau paramètre booléen : ClientAliveCheck. Si celui-ci est vrai, les paramètres ClientAliveInterval et ClientAliveCountMax seront montrés à l'utilisateur. Ces paramètres seront masqués dans le cas contraire.

On va d'abord créer ClientAliveCheck qui est un simple booléen :

'ClientAliveCheck' =>

  {

    'type' => 'leaf',

    'value_type' => 'boolean',

'default' => '0',

  },

Ensuite, on va créer le premier paramètre ClientAliveInterval. Pour que le mécanisme de warping fonctionne, il faut indiquer :

  • Quel paramètre va piloter la déformation. Dans la documentation de Config::Model, on parle du warp master. On utilise une notation assez simple : - ClientAliveCheck qui veut dire : « remonte d'un niveau et utilise la valeur de ClientAliveCheck ».
  • Quel effet a le paramètre en question en fonction de sa valeur. Ici, le warp master va simplement modifier le paramètre pour le cacher ou non en faisant passer level de hidden (caché) à normal.

En termes de modèle, ça se traduit en :

   'ClientAliveInterval',

   {

     'type'       => 'leaf',

     'value_type' => 'integer',

     'level'      => 'hidden',

     'min'        => '1',

'warp'

     => {

         'follow' => {

                       # précise où trouver la variable c_a_check

                       'c_a_check' => '- ClientAliveCheck'

},

         'rules' => [

                     # Cette variable n'est pas interpolée par Perl.

                     # Elle doit être définie dans le hash "follow" au dessus

                     '$c_a_check == 1',

{

                       # ClientAliveInterval redevient visible

                       # quand ClientAliveCheck est vrai

                       'level' => 'normal'

                     }

                    ]

        },

   },

Le mécanisme de warping de Config::Model permet de déformer d'autres attributs des valeurs comme les valeurs par défaut, les limites minimales ou maximales, les choix possibles des types énumérés...

5.14 Une ramification de l'arbre de configuration avec Match

Voici la spécification de ce paramètre de sshd_config :

Introduces a conditional block. If all of the criteria on the Match

line are satisfied, the keywords on the following lines override those

set in the global section of the config file, until either another

Match line or the end of the file. The arguments to Match are one or

more criteria-pattern pairs. The available criteria are User, Group,

Host, and Address. Only a subset of keywords may be used on the lines

following a Match keyword. Available keywords are AllowTcpForwarding,

Banner, ForceCommand, GatewayPorts, GSSApiAuthentication,

KbdInteractiveAuthentication, KerberosAuthentication,

PasswordAuthentication, PermitOpen, RhostsRSAAuthentication,

RSAAuthentication, X11DisplayOffset, X11Forwarding, and

X11UseLocalHost.

Cet élément introduit un bloc conditionnel qui va contenir une série de paramètres. On va devoir modéliser ce bloc conditionnel avec 2 nouvelles classes de configuration :

  • Sshd::MatchBlock, qui va contenir les critères et motifs requis par sshd_config. D'après la documentation, chaque bloc Match spécifie une ou plusieurs paires de critères et motifs (pattern). Chaque critère est User, Group, Host ou Address.
  • Sshd::MatchElement, qui va contenir les éléments autorisés par sshd_config dans un bloc conditionnel.

On obtiendra cette structure dans les classes de configuration :

Pour créer cette structure, il faut rajouter ces deux nouvelles classes dans le modèle en suivant une méthode similaire à celle utilisée pour créer la classe racine Sshd :

  • clic droit sur le paramètre class dans l'éditeur de configuration
  • ajout des 2 nouvelles classes Sshd::MatchBlock et Sshd::MatchElement.

Maintenant, il faut créer l'élément Match dans la classe Sshd pour relier Sshd à Sshd::MatchBlock. Vu que sshd_config peut contenir plusieurs blocs Match, il faut créer un élément de type list. Cette liste va contenir les instances de la classe Sshd::MatchBlock et son cargo doit donc être de type node :

Voici ce que ça donne dans le code généré par l'éditeur :

'Match' =>

{

     'type' => 'list',

     'cargo' => {

                  'type' => 'node',

                  'config_class_name' => 'Sshd::MatchBlock'

},

}

Cet élément Matchest intéressant, car il permet de voir comment relier 2 classes de configuration avec une liaison multiple (1 <--> * ) entre Sdhd et Sshd::MatchBlock. Une liaison de type liste entre 2 classes est assez exceptionnelle. La plupart du temps, cette liaison est faite avec un hash, ce qui est plus facile à exploiter.

5.15 Déclaration de la classe Sshd::MatchBlock

La nouvelle classe Sshd::MatchBlock va contenir quatre éléments de type leaf : User, Group, Host et Address et un élément de type node de classe Sshd::MatchElement>.

Chaque feuille de MatchBlock va contenir un des critères « Match » détaillés par la documentation de sshd_config. Chacun de ces critères est un motif (pattern). D'après la documentation de sshd_config, chaque connexion entrante va devoir satisfaire tous ces motifs pour que les surcharges de configuration contenues dans le bloc Match soient appliquées.

Les données de configuration du bloc Match sont stockées dans une instance de la classe Sshd::MatchElement.

Ça parait compliqué, mais voici le résultat contenu dans le fichier Sshd/MatchBlock.pl généré par l'éditeur du modèle :

[

   {

      'name' => 'Sshd::MatchBlock',

      'element'

       => [

           'User' => {

                       'type' => 'leaf',

                       'value_type' => 'uniline',

                       'description' => 'Define the User criteria [...]',

                      },

           'Group' => {

                       'type' => 'leaf',

                       'value_type' => 'uniline',

                       'description' => 'Define the Group criteria [...]',

                      },

           'Host' => {

                       'type' => 'leaf',

                       'value_type' => 'uniline',

                       'description' => 'Define the Host criteria [...]',

                      },

           'Address' =>

                      {

                       'type' => 'leaf',

                       'value_type' => 'uniline',

                       'description' => 'Define the Address criteria [...]'.

                      },

           'Elements' =>

                      {

                       'type' => 'node',

                       'config_class_name' => 'Sshd::MatchElement',

                       'description' => 'Defines the sshd_config parameters [...]',

}

          ]

  }

] ;

Il reste maintenant à déclarer la classe contenue par l'élément Elements.

5.16 Déclaration de la classe Sshd::MatchElement

On a vu que les blocs Match permettent de spécifier des paramètres sshd pour certaines connexions entrantes. Ces paramètres sont aussi disponibles en dehors de ces blocs. Mais, il va falloir les dupliquer dans la classe Sshd::MatchElement.

Là, j'en vois qui grognent et se disent : « Quoi ? Dupliquer ce modèle ? Ça va pas la tête ?. La duplication complique trop la maintenance ! »

Effectivement, on pourrait utiliser les mécanismes d'inclusion fournis par Config::Model pour éviter cette duplication. Mais, il y a un os.

La plupart des paramètres de sshd_config ont une valeur par défaut (déclarée avec built_in dans le modèle). Mais, cette valeur par défaut ne s'applique dans les blocs Match que si le paramètre correspondant n'est pas utilisé dans la partie principale de sshd_config. Sinon, c'est le paramètre spécifié dans la partie principale qui est en fait la valeur par défaut du même paramètre dans le bloc Match.

Vous êtes perdu ? Voici un exemple : d'après la documentation de sshd_config, la valeur par défaut de X11Forwarding est no.

Dans l'exemple suivant, la spécification de X11Forwarding est inutile, car sa valeur par défaut est no :

Match User toto

X11Forwarding no

Alors que, dans celui-ci, elle est nécessaire, car la valeur par défaut dans le bloc Match est yes.

X11Forwarding yes

Match User toto

X11Forwarding no

Il serait donc intéressant que l'éditeur de sshd_config indique ces valeurs par défaut de X11Forwarding :

  • no dans l'instance de Sshd.
  • no dans l'instance de Sshd::Elements si X11Forwarding est no ou non renseigné dans l'instance de Sshd.
  • no dans l'instance de Sshd::Elements si X11Forwarding est yes dans l'instance de Sshd.

Eh bien, c'est possible avec le paramètre compute de Config::Model::Value. Ce paramètre permet d'aller chercher certaines valeurs (notez le pluriel) dans l'arbre de configuration, de faire quelques calculs (arithmétiques ou substitutions dans une chaîne de caractères) et d'utiliser le résultat comme valeur par défaut.

Dans notre cas, on aura une variable à aller chercher et un calcul très simple. Voici comment.

5.17 Comment copier les éléments dans se fatiguer ?

D'abord, il faut remplir la classe Sshd::MatchElement avec des éléments similaires à celle de Sshd. Le plus rapide est de copier certains éléments de Sshd avec la fonction « copier/coller » :

  • Dans l'arbre de configuration à gauche, sélectionnez class->Sshd->element->AllowTcpForwarding ;
  • Faites Edit->copy ;
  • Sélectionnez class->Sshd::MatchElement->element ;
  • Faites Edit->paste (« coller » en bon français) ;
  • Recommencez pour les autres paramètres acceptés dans les blocs Match de sshd_config (par exemple Banner, ForceCommand...).

5.18 Spécifier une valeur par défaut « adaptable »

Prenons par exemple l'élément AllowTcpForwarding de Sshd::MatchElement. Sa valeur par défaut doit être celle de l'élément AllowTcpForwarding dans la classe Sshd. Ceux qui suivent se rappellent que la structure de l'arbre de configuration est :

  Sshd -> Sshd::MatchBlock -> Sshd::MatchElement

Donc, l'élément AllowTcpForwarding de Sshd::MatchElement doit « utiliser » le AllowTcpForwarding qui est trois niveaux plus haut (ou sous la racine). Pourquoi trois niveaux ? Parce qu'il faut « remonter » trois classes (Sshd::MatchElement, Sshd::MatchBlock et Sshd) avant de trouver AllowTcpForwarding. C'est matérialisé par le parcours rouge dans la figure ci-dessous :

En termes de modèle, on va utiliser le paramètre compute et spécifier :

  • La formule qui contient une ou plusieurs variables. Ici, la formule sera simplement '$main'. ((1) dans la figure ci-dessus).
  • La variable main utilisée dans la formule. Celle-ci va contenir le chemin (path) requis pour trouver l'élément AllowTcpForwarding contenu dans Sshd. ((1) dans la figure ci-dessus). Ce chemin contient :
  • « - - - » qui veut dire « on remonte de 3 niveaux ».
  • &element qui est une fonction que Config::Model va substituer par le nom de l'élément contenant cette fonction (AllowTcpForwardingdans notre cas).
  • L'autorisation pour l'utilisateur de spécifier une valeur, même si celle-ci a été calculée (paramètre allow_override à 1).

Et voilà. Avec tous ces paramètres, Config::Model va :

  • suivre le chemin ((3) à (6) dans la figure ci-dessus) pour extraire la valeur de l'élément AllowTcpForwardingcontenu dans Sshd.
  • utiliser cette valeur comme valeur par défaut de AllowTcpForwarding contenu dans Sshd::MatchElement ((7) dans la figure ci-dessus).

Enfin, voici le modèle généré de AllowTcpForwarding :

   'AllowTcpForwarding' =>

    {

      'type'       => 'leaf',

      'value_type' => 'boolean',

      'compute'    => {

                       'formula'        => '$main',

                       'variables'      => {

                                            'main' => '- - - &element'

                                           },

                       'allow_override' => '1'

                      },

      description => "Specifies whether TCP [...]",

},

Il reste à appliquer ce principe à (presque) tous les autres éléments de Sshd::MatchElement avec la fonction copier/coller. C'est là que la fonction &element utilisée dans la variable main est pratique, car on n'a pas besoin d'ajuster le chemin spécifié dans cette variable pour chaque nouvel élément. Pour plus de détails sur le paramètre compute, vous pouvez consulter la documentation de Config::Model::ValueComputer.

5.19 Premiers essais

Ca y est ! Tous les paramètres de sshd_config sont entrés dans le modèle. Maintenant, on peut avoir un aperçu de l'interface que verra l'administrateur pour configurer Sshd en cliquant sur le menu Model/test :

On peut aussi lancer ce tout nouvel éditeur de configuration avec la commande :

  config-edit -dev -model Sshd

L'option -dev est nécessaire, car nous sommes toujours en phase de développement et le modèle Sshd n'est pas encore installé.

Et là, on se rend compte d'un problème : il n'y a pas une seule flèche verte dans l'éditeur de configuration, aucune valeur spécifique au système hôte. Eh oui, on a créé un beau modèle de configuration, mais l'outil est autiste :il ne peut encore ni lire, ni écrire le fichier /etc/ssh/sshd_config.

6. Fondu au noir, générique de fin

Et là, je vous laisse en plein suspens : notre interface arrivera-t-elle à communiquer avec l'extérieur ?

Vous le saurez dans le prochain épisode où nous verrons comment utiliser Perl, Parse::RecDescent et l'API de Config::Model pour :

  • lire la configuration stockée dans sshd_config avec Parse::RecDescent ;
  • charger cette configuration et ses valeurs dans Config::Model ;
  • ensuite, pour la sauvegarde, parcourir l'arbre de configuration pour extraire les nouvelles valeurs amenées par l'utilisateur ;
  • écrire ces nouvelles valeurs dans sshd_config.

Remerciements

  • Les Mongueurs de Perl pour leur accueil et la relecture de cet article.
  • Mes collègues de Hewlett-Packard pour leur soutien et les relectures.

Liens

Les pages du projet :

[FRESHMEAT]http://freshmeat.net/projects/config_model/

[SOURCEFORGE] http://config-model.wiki.sourceforge.net/

[CPAN] http://search.cpan.org/~ddumont/

Les autres liens :

[WIZARD] http://en.wikipedia.org/wiki/Wizard_%28software%29

[OPENSSH] http://www.openssh.org/

[KDE] http://developer.kde.org/documentation/other/kcm_howto.html

[WEBMIN] http://www.webmin.com/

[CMDEVEL] http://lists.sourceforge.net/mailman/listinfo/config-model-devel

[CMUSERS] http://lists.sourceforge.net/mailman/listinfo/config-model-users