I. Présentation

Je ne sais pas s’il vous arrive de vous demander comment changer les mots de passe de votre compte utilisateur principal, sur l’ensemble de vos machines. Mais, jusqu’à il n’y a pas si longtemps je me disais que j’allais écrire un module Puppet pour ce genre de tâches. Bien sûr, Puppet est une des alternatives. Il existe aussi Chef ou Ansible. Le problème avec ces outils c’est justement qu’il faut imaginer et développer de nombreuses lignes de code dans des langages pas toujours maitrisés. Et puis, il y a eu l’illumination lorsque j’ai parcouru les lignes de cette page web : https://blog.zwindler.fr/2016/08/11/reinventons-roue-jautomatise-actions-via-sshmdp-script-expect/. Je vous propose dans cet article de détailler le fonctionnement de ce petit outil qu’est expect.

II. Principe de fonctionnement

La commande expect s’appuie principalement sur un script écrit en langage relativement lisible permettant de proposer une solution simple créant une boucle d’analyse syntaxique d’une commande ssh ou scp. La boucle, quant à elle est alimentée par un fichier de type .csv contenant les enregistrements des différentes machines, sur lesquelles exécuter les traitements. Une fois ce socle mis en place, il est alors possible de générer un ou plusieurs scripts shell contenant les actions à mener. On peut schématiser cela de la façon suivante :

REMARQUE : outre l’analyse syntaxique d’une commande scp ou ssh, on peut imaginer également introduire ce genre de manipulation pour des commandes de transfert de fichiers sftp ou ftp.

Pour ceux qui comme moi n’ont pas eu encore à manipuler cet outil, il faut se rappeler qu’expect est un binaire automatisant les actions demandées en fonction du retour renvoyé par le terminal. Cela est utile afin de composer un ensemble de tâches au travers des protocoles FTP, TFTP, SSH, etc. Là où l’outil se distingue, c’est que généralement dans ce genre d’interaction il est nécessaire d’insérer un mot de passe au sein d’un véritable terminal. Ici, c’est le script shell qui permettra de proposer un mot de passe (le plus souvent chiffré), qui sera analysé en tant que paramètre par le script expect.

Par ailleurs, l’outil possède un langage de script permettant d’automatiser relativement facilement les différentes actions, à l’aide de structures conditionnelles standard. Si l’on se réfère aux scripts, proposés par l’auteur de l’article mentionné ci-dessus, sur le site GitHub à cette adresse https://github.com/zwindler/expect.us, on constate alors que l’on peut copier des fichiers sur un liste de triplet de type <Host>;>User>;<Password>; et ainsi modifier le fichier de dépôt yum sur un ou plusieurs serveur(s) distant(s) tout en nettoyant le cache.

III. Mise en place d’un script expect

Je vous propose maintenant de mettre en place un système de changement de mot de passe du compte principal sur l’ensemble des machines du parc. Imaginons que l’on ait un compte usrsys, permettant de se connecter à tout serveur de l’infrastructure (en dehors du compte root que l’on supposera bloqué en accès SSH) et que l’on souhaite en modifier le mot de passe à intervalle régulier : tous les 90 jours par exemple.

L’objectif de ce programme consiste à changer, à la volée, le mot de passe de ce même compte, sur l’ensemble des machines du parc, grâce à une seule commande. En s’appuyant sur l’exemple proposé sur le site précédent, on va donc commencer par générer un script expect permettant d’analyser la syntaxe des commandes ssh ou scp manipulées.

REMARQUE : dans notre contexte, on ne s’intéressera qu’à des commandes passées au travers du protocole SSH. Mais, cela n’empêche pas de fournir aussi un moyen de s’intéresser aux copies de fichiers via scp (voire via rsync).

Mais, dans la mesure où le compte usrsys doit passer une commande (en l’occurrence chpasswd), réservée au compte root, il va falloir mettre à jour le fichier /etc/sudoers. Dans ce but, on devra alors créer les lignes suivantes (pour ma part, je me suis servi de Puppet pour diffuser la nouvelle version du fichier /etc/sudoers):

# visudo … User_Alias USRSYS = usrsys Cmnd_Alias CHGTPWD = /usr/sbin/chpasswd … USRSYS ALL=(ALL) NOPASSWD: CHGTPWD

IMPORTANT : dans la mesure où les machines sur lesquelles on souhaite intervenir sont connues, on peut créer un alias spécifique listant ces serveurs grâce à l’instruction suivante et en remplaçant le premier ‘ALL’ par le nom de l’alias CHGTPWD ainsi créé, comme le montre l’exemple suivant:

Host_Alias CHGTPWD=srv1, srv2, srv3 … USRSYS CHGTPWD=(ALL) NOPASSWD: CHGTPWD

Ensuite, on peut alors créer le script expect en générant le fichier expect.us selon l’exemple précèdent:

#!/usr/bin/expect -f set action [lindex $argv 0] set hostname [lindex $argv 1] set username [lindex $argv 2] set password [lindex $argv 3] set argumen1 [lindex $argv 4] set argumen2 [lindex $argv 5] if { $action == "ssh" } { puts "Execution commnande : ssh $username@$hostname // $argumen1" spawn ssh -o StrictHostKeyChecking=no -o LogLevel=ERROR $username@$hostname expect "?*assword:" send "$password\r" expect "login" expect "#" send -- "$argumen1\r" expect "~]#" } elseif { $action == "scp" } { puts "Execution commande : scp $argumen1 $username@$hostname:$argumen2" spawn scp -rp -o StrictHostKeyChecking=no -o LogLevel=ERROR $argumen1 $usernam e@$hostname:$argumen2 expect "?*assword:" send "$password\r" expect "~]#" } puts "Fin Script"

D’après ce script on en déduit le type d’action : ssh ou scp, le nom du serveur courant (via le paramètre hostname), ainsi que le nom du compte utilisé et de son mot de passe. Les deux arguments supplémentaires se substituent respectivement à la commande complète et au répertoire passé en paramètre.

Il ne reste plus qu’à générer un script shell, appelé ChgmtPwd.sh, créant une boucle logique permettant, pour chaque ligne d’un fichier de données, d’exécuter les actions demandées : ici, le changement du mot de passe (que l’on fournit en paramètre), du compte courant :

#!/bin/bash [[ $# -ne 1 ]] && { echo "ERREUR: le programme $0 doit connaître le paramètre \$Password" echo "=> $0 <Password>" exit 1 } Pwd=$1 for line in `cat expect_file.csv` do hostname=`echo $line | cut -d";" -f1` username=`echo $line | cut -d";" -f2` password=`openssl passwd -1 $Pwd` cd /etc/expect ./expect.us ssh $hostname $username $password "echo '$username:$password' | sudo chpasswd -e" done

Le lien entre ces deux briques, c’est le fichier CSV, dans lequel on stocke les enregistrements des différentes machines selon le format : <Host>;<User> :

srv1;usrsys srv2;usrsys …

REMARQUE : nul besoin de placer le mot de passe dans le fichier CSV, car on le passe en paramètre. Afin de le fournir sous sa forme chiffrée, on utilise la commande ci-dessous, intégrée au script et renseignant alors le paramètre password :

# openssl passwd -1 <New Pwd>

Exemple : Pour créer un nouveau mot de passe UsrSysP@sswd2018.1 au compte usrsys :

# openssl passwd -1 UsrSysP@sswd2018.1 $1$.T5H.yhQ$C1yH4zBDVJ2vVeNrQPDCQ1

Ainsi, il suffit de passer le nouveau mot de passe au script shell, qui effectuera de lui-même les modifications, sur l’ensemble des serveurs mentionnés dans le fichier CSV. On remarquera l’exécution de la commande expect.us (reprise de l’exemple mentionné en introduction), dans laquelle, seule le second membre du pipe utilisant chpasswd, nécessite sudo.

Afin de s’assurer du bon fonctionnement de cet ensemble, je vous propose également d’écrire un script listant le nom de l’hôte (via la commande hostname) sur l’ensemble des machines listées dans le fichier CSV. Ce script s’appellera Hostname.sh et permettra surtout de vérifier la bonne connectivité des machines et le bon déroulement de l’exécution expect, sans risquer de casser votre parc de production :

#!/bin/bash [[ $# -ne 1 ]] && { echo "ERREUR: le programme $0 doit connaitre le paramètre \$Password" echo "=> $0 <Password>" exit 1 } Pwd=$1 for line in `cat expect_file.csv` do hostname=`echo $line | cut -d";" -f1` username=`echo $line | cut -d";" -f2` password=$Pwd

L’exécution de ce petit test s’effectue en exécutant la commande suivante :

# ./hostname.sh <Passwd>

cd /etc/expect ./expect.us ssh $hostname $username $password "hostname" done

ATTENTION : certains pourraient imaginer supprimer le mot de passe en paramètre. Mais, ce serait une erreur, car pour exécuter les commandes ssh, il faut bien (à moins d’avoir activé un agent SSH), fourni le mot de passe du compte sur lequel on effectue la commande distante.

IV. Conclusion

Au travers de ce tutoriel on dispose non seulement d’un exemple pour les dépôts yum, fournit sur le site GitHub mentionné précédemment, mais également d’un exemple de mise à jour de mot de passe.

L’outil expect peut également s’utiliser à des fins de création de compte SFTP ou FTP, sur de multiples serveurs en simultané, ou encore, au travers de PHP pour pouvoir gérer différentes pages web sur de multiples serveurs. On le devine aisément, il s’agit là d’un outil puissant, qui non seulement, n’est pas incompatible avec les outils d’automatisation que sont Puppet, Chef ou Ansible, mais qui en plus, permet de nombreuses modifications simultanément, sans trop de mal et surtout sans aucune complexité ajoutée.

Cela ouvre alors pas mal de perspectives d’automatisation et de déploiement, y compris sur l’automatisation de commandes distantes, effectuées sur des commutateurs (généralement installée avec une distribution Linux), que l’on peut alors implémenter en batterie. Il existe sur Internet de nombreux exemples, que je vous invite à consulter pour vous en inspirer.

REMARQUE : ceci dit, si vous n’avez pas besoin de simultanéité et que les modifications n’ont pas d’urgence précise, il vaut mieux mettre en place un nouveau module Puppet (ou autres). Car, vous y gagnerez en sécurité et en souplesse.