Article publié dans Linux Magazine 117, juin 2009.
Copyright © 2008 - Dominique Dumont
Config::Model
Config::Model
appliqué à OpenSSH Sshd
AllowTcpForwarding
AcceptEnv
Banner
Ciphers
GatewayPorts
GSSApiAuthentication
et GSSAPIKeyExchange
ServerKeyBits
ClientAliveInterval
et ClientAliveCountMax
ClientAlive
Match
Sshd::MatchBlock
Sshd::MatchElement
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.
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é (e.g. /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 sera prise 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.
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 ou 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'au 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.
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 (e.g. 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 au 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. Cet 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'application. 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.
Config::Model
Un des principes fondateur 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'autre é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é.
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ément 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.
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és aux modèles compliqués comme celui d'Xorg)
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ée 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 :
Config::Model
appliqué à OpenSSHComme j'en vois qui baillent au fond, voici ce que ça donne appliqué à
la configuration du daemon sshd
de 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 sera beaucoup plus facile 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étails comment spécifier les différentes parties du modèle de configuration.
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 fournit 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èle 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
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 à coté 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.
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 AllowTcpForwarding
dans le champ à coté 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'autre 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érent 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
AllowTcpForwarding
dans 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é.
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 terme
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 (i.e. la structure de donnée Perl) devront être
reportée 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ée correspondent à des paramètres ou à des informations à rentrer.
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', }
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
d'encryption acceptée 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' ], }
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'ai pas à lire la page de
manuel. (clic 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 :
clic droit sur help
dans l'arbre de configuration à gauche
faites Add item
avec yes
comme valeur.
clic 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'] }
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.
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 [...]', }
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
coté, s'éloigner de la spécification de sshd_config en ajoutant un
paramètre « artificiel » peut perturber les administrateurs chevronnés. Et
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.
Dans certains cas, le coté 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.
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 terme 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 ...
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ère et motif (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
ajouter les 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 plusieur 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 Match
est 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.
Sshd::MatchBlock
La nouvelle classe Sshd::MatchBlock
va contenir quatre éléments de type
leaf
: User
, Group
, Host
and 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'élement
Elements
.
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és avec built_in
dans le modèle). Mais cette valeur par
défaut ne s'applique dans les bloc 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
.
yes
dans l'instance de Sshd::Elements
si X11Forwarding
est
yes
dans l'instance de Sshd
.
Et 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.
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 (e.g. Banner
, ForceCommand
...)
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 terme 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
(AllowTcpForwarding
dans 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 AllowTcpForwarding
contenu 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
.
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. Et 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.
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
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.
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
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.