« Sauvegarder la configuration des commutateurs c'est très important, parce qu'un jour vous allez faire de la merde et ça ne va plus marcher. » — Captain Obvious 2018/07/07
Et oui, je vais utiliser le mot « commutateur » tout le long de cet article.
Il y a très longtemps il semble qu'on utilisait un script qui analysait les logs des commutateurs sur la machine serveurlog1. Plus de détails sur la page Sauvegarde des Confs. Je n'ai jamais vu le contenu du script mais je suppose que ça a du marcher pendant un moment si quelqu'un a pris la peine d'écrire un article dessus.
Ensuite, on a la solution actuelle (qui date aussi) et qui marche… plus ou moins… C'est la tache nommée « Sauvegarde switchs »2 qu'on peut trouver sur jobs et qui envoie les configurations sur GitLab sauvegarde-des-switchs-svgs. On peut constater que c'est un peu le bordel: certaines sauvegardes datent d'il y a 8 mois, d'autres d'il y a 2 ans et d'autres encore d'il y a 15 heures.
1 serveurlog c'était un serveur syslog, qui a été remplacé depuis par logstash et elasticsearch.
2 C'est en fait un script ruby (c'était encore à la mode à l'époque) qui en fait récupère la configuration des commutateurs en s'y connectant via SSH (avec bien sûr le mot de passe SSH3 et Enable en clair dans le script).
3 En plus maintenant que les commutateurs utilisent les identifiants LDAP pour SSH, utiliser le mot de passe du compte SSH local risque de moins bien marcher.
Evidemment il existe tout un tas de logiciel pour faire de la sauvegarde de conf (avec différents supports de matériel, de fonctionnalités), comme par exemple Oxidized, mais ils demanderont probablement la création d'un user pour pouvoir récupérer la conf notamment
Vu que la solution de la tâche « Sauvegarde switchs » ne marchait plus très très bien, on a décidé (entre septembre et décembre 2017) avec varens de trouver une solution alternative. Cette solution c'était un peu un mélange des 2 solutions précédentes à savoir: utiliser logstash avec le plugin exec pour lancer un script ou playbook ansible pour aller récupérer la configuration du commutateur via SSH lorsque celui-ci produit une certaine ligne de log.
Bref, ayant surement des choses plus urgentes (ou pas) à gérer on a mis ce projet de côté.
Et puis on en a reparlé il y a pas longtemps et je me suis chauffé pour lire un peu la doc Cisco pour savoir comment s'y prendre pour implémenter tout le bordel. Et là c'est le drame je découvre qu'il y a déjà quelque chose prévu par Cisco pour faire des sauvegarde de configuration de manière très simple et pire encore c'était presque déjà configuré sur le commutateur du local (il manquait la partie la plus importante de la configuration).
En fait il est possible de définir des « archives de configuration » sur les commutateurs. Ainsi pour définir une archive on doit donner au commutateur un chemin où stocker l'archive (ça peut être un chemin local ou sur le réseau) ainsi qu'une condition pour lancer le processus de création d'archive (un intervalle de temps ou une action).
Ce qu'on veut faire c'est sauvegarder sur un serveur la configuration de notre commutateur lorsque quelqu'un fait une modification. On a plusieurs protocole à notre disposition pour faire parler notre commutateur avec notre serveur: FTP, TFTP, HTTP(S), RCP, SCP.
Ici j'ai choisi d'utiliser SCP pour 2 raisons principales: 1) Éviter d'envoyer des configurations de commutateur en clair sur le réseau. (Alors oui, on va me dire « c'est sur un réseau privé » mais c'est pas une raison).
2) Vu que SCP est basé sur SSH, on a pas grand chose à installer sur notre serveur: openssh-server et openssh-client(cf infra).
sw> enable sw# configure terminal sw(config)# archive
sw(archive-config)# path scp://utilisateur:motdepassesuperduratrouver@192.168.102.148/var/archives/$h_$t
C.f. : Note sur le nom du fichier 1
sw(archive-config)# write-memory
sw(archive-config)# time-period 20160
On va lancer le processus d'archivage lorsqu'on fait un « write mem » ou toutes les 20160 minutes (toutes les 2 semaines).
1 Note sur le nom du fichier: « $h_$t »
Le « $h » correspond au nom d'hote du commutateur, « $t » correspond à l'horodatage et un numéro de révision est ajouté automatiquement à la fin. Dans notre exemple « $h_$t » correspond à <hostname>_<timestamp>-<revision>, soit quelque chose comme « switch-local_Jul–8-2018-09-13-39.196-2 »
Ici le timestamp est sous la forme «mois–numéro du jour-année-heure-minute-seconde-milliseconde», pour cela il faut s'assurer que notre commutateur génère bien son timestamp de cette façon:
sw(config)# service timestamp log datetime msec year
Il est important de garder des noms de fichier uniformes, ça nous sera utile ensuite pour la partie versioning.
La configuration minimale du serveur est très simple: openssh-server, openssh-client1 et un utilisateur pour le commutateur.
On commence par créer un utilisateur et définir son mot de passe:
root@confsw:/# useradd -d /var/confsw toto && passwd toto
Il nous faudra faire aussi 2 petites modifications de la configuration de notre serveur SSH:
Nos commutateurs utilisent une veille version de SSH qui utilise des algorithmes d'échange de clé et de chiffrement qui ne sont plus activé par défaut dans la configuration du démon SSH sur Debian Stretch. Pour déterminer les algos à ajouter à notre sshd on va essayer de se connecter en SSH depuis le commutateur sur notre serveur, normalement ça foire. Il ne nous reste plus qu'a retrouver les algos proposés par le commutateur dans /var/log/auth.log.
Easy:
root@confsw:/# grep -E 'no matching key exchange method found|no matching cipher found' /var/log/auth.log Jul 7 13:11:49 confsw sshd[1537]: Unable to negotiate with 192.168.103.219 port 25650: no matching key exchange method found. Their offer: diffie-hellman-group1-sha1 [preauth] Jul 7 13:13:53 confsw sshd[1212]: Unable to negotiate with 192.168.103.219 port 50277: no matching cipher found. Their offer: aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc [preauth]
On ajoute donc ces algos à notre /etc/ssh/sshd_config:
KexAlgorithms +diffie-hellman-group1-sha1 Ciphers +aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc
1 Le binaire « scp » se trouve dans le paquet openssh-client et on a besoin de ce binaire. superuser.com
Pour éviter que le commutateur ait un accès shell à notre machine on va utiliser un petit programme qui s'appelle rssh qui va nous permettre de limiter ce qu'il est possible de faire via SSH.
apt install rssh
On change le login shell de notre utilisateur par rssh:
usermod -s /usr/bin/rssh toto
Dans le fichier /etc/rssh.conf on ajoute:
user=toto:022:000010:"/var/confsw/chroot"
Ce qui veut dire: « Pour l'utilisateur toto, on utilise 022 comme umask (celui par défaut normalement), on autorise la commande scp, on chroot l'utilisateur dans /var/confsw »
Extrait de man 5 rssh.conf:
The user keyword's argument consists of a group of fields separated by a colon (':'), as shown below. The fields are, in order: username The username of the user for whom the entry provides options umask The umask for this user, in octal, just as it would be specified to the shell access bits Six binary digits, which indicate whether the user is allowed to use rsync, rdist, cvs, sftp, scp and svnserve, in that order. One means the command is allowed, zero means it is not. path The directory to which this user should be chrooted (this is not a command, it is a directory name). See chroot_path above for complete details.
Il nous reste plus qu'a créer le chroot:
root@confsw:/# chmod u+s /usr/lib/rssh/rssh_chroot_helper root@confsw:/# mkdir -p /var/confsw/chroot/archives root@confsw:/# chown toto:toto /var/confsw/chroot/archives root@confsw:/# /usr/share/doc/rssh/examples/mkchroot.sh /var/confsw/chroot/
TODO: Comment tester rapidement la configuration de rssh
On commence par créer un utilisateur qui va « gitter » nos configurations. On crée également le dossier qui va être le dossier de travail de notre utilisateur, par ex:/var/confsw/versions/.
Il faut bien faire en sorte que notre nouvel utilisateur ait les droits de lecture au moins dans /var/confsw/chroot/archives/ et les droits d'écriture dans /var/confsw/versions/
On utilise inotify via incron pour monitorer les fichiers dans /var/confsw/chroot/archives/.
root@confsw:/# apt install incron root@confsw:/# echo "jhondoe" >> /etc/incron.allow #on autorise notre nouvel utilisateur à utiliser incron
Il faudra ensuite éditer la incrontab de jhondoe:
root@confsw:/# su johndoe -c 'incrontab -e'
Pour y ajouter notre tâche:
/var/confsw/chroot/archives/ IN_CREATE /opt/git_swconfig/git_swconfig.sh $@$#
Qui peut se traduire par: « Lorsqu'un fichier est crée dans /var/confsw/chroot/archives/ lance le script /opt/git_swconfig/git_swconfig.sh avec pour argument le chemin absolu du nouveau fichier ».
Le script « git_swconfig.sh » va alors traiter notre fichier de configuration fraîchement uploadé par le commutateur afin de le copier dans un dépôt git (tout en générant des logs dans syslog):
#!/bin/bash INPUT_FILE=$1 LOGGER="/usr/bin/logger id=${$} ${0} ' '" GIT_BIN="/usr/bin/git" GIT_USER="-c user.name='Confsw' -c user.email='confsw@minet.net'" OUTPUT_DIR="/var/confsw/versions/" GIT_WORK_TREE="--git-dir=${OUTPUT_DIR}/.git/ --work-tree=${OUTPUT_DIR}" #Expected filename format: <hostname>_<MMM>--<d>-<yyyy>-<hh>-<mm>-<ss>.<ms>-<revision> FILENAME=$(basename $INPUT_FILE) HOSTNAME=$(echo $FILENAME | awk -F'_' '{print $1}') REVISION=$(echo $FILENAME | awk -F'-' '{print $NF}') TIMESTAMP_RAW=$(echo $FILENAME | awk -F'_' '{print $2}' | awk -F'-' '{$NF=""; print $0}') MONTH=$(echo $TIMESTAMP_RAW | awk '{print $1}') DAY=$(echo $TIMESTAMP_RAW | awk '{print $2}') YEAR=$(echo $TIMESTAMP_RAW | awk '{print $3}') HOUR=$(echo $TIMESTAMP_RAW | awk '{print $4}') MIN=$(echo $TIMESTAMP_RAW | awk '{print $5}') SEC=$(echo $TIMESTAMP_RAW | awk '{print $6}' | awk -F'.' '{print $1}') MSEC=$(echo $TIMESTAMP_RAW | awk '{print $6}' | awk -F'.' '{print $2}') OUTPUT_FILE="${OUTPUT_DIR}/${HOSTNAME}.conf" DATE=$(date -d"${MONTH} ${DAY} ${YEAR} ${HOUR}:${MIN}:${SEC}.${MSEC}" --rfc-3339='ns') function versioning { ACTION=$1 $GIT_BIN $GIT_WORK_TREE init $OUTPUT_DIR cp $INPUT_FILE $OUTPUT_FILE if [ $ACTION = "added" ]; then $GIT_BIN $GIT_WORK_TREE add ${HOSTNAME}.conf ADD_STATUS=$? $LOGGER "$GIT_BIN $GIT_WORK_TREE add $OUTPUT_FILE: exit code $ADD_STATUS." fi COMMIT_MSG="${HOSTNAME} configuration ${ACTION} on ${DATE} (rev ${REVISION})." $GIT_BIN $GIT_WORK_TREE $GIT_USER commit -am "$COMMIT_MSG" COMMIT_STATUS=$? $LOGGER "$GIT_BIN $GIT_WORK_TREE $GIT_USER commit -am ${COMMIT_MSG}: exit code $COMMIT_STATUS." #$GIT_BIN $GIT_WORK_TREE push -u origin master #PUSH_STATUS=$? #$LOGGER "$GIT_BIN $GIT_WORK_TREE -u origin master: exit code $PUSH_STATUS." } if [ -e $OUTPUT_FILE ]; then diff -q $INPUT_FILE $OUTPUT_FILE DIFF=$? if [ $DIFF -eq 1 ]; then $LOGGER "DEST: ${OUTPUT_FILE} differs from SRC: ${INPUT_FILE}. Updating DEST." versioning "modified" else $LOGGER "No change detected between ${INPUT_FILE} and ${OUTPUT_FILE}. Nothing to do." fi else $LOGGER "DEST: ${OUTPUT_FILE} does not exist. Adding DEST." versioning "added" fi
Notre dossier /var/confsw/versions/ contiendra alors un joli dépôt git avec un commit à chaque « write mem ».
Vous pouvez retrouver le dépôt ici : Confsw.
Pour restaurer une ancienne configuration il suffit en théorie de faire:
sw> enable sw# copy scp://utilisateur:motdepassesuperduratrouver@192.168.102.148/archives/nom-du-fichier-de-conf startup-config:
Il est possible de voir les dernières archives via
sw# show archive
Pour déployer la configuration rapidement sur les switchs, on utilise Ansible.
Voici le script (très basique) utilisé.
- hosts: [switchs] gather_facts: false tasks: - name : stop archiving ios_config: lines: - archive - no archive - name: save conf ios_config: lines: - service timestamp log datetime msec year - archive - path scp://utilisateur:motdepassesuperduratrouver@192.168.102.148//var/confsw/archives/$h_$t - time-period 20160 - write-memory
Et voici le fichier hosts utilisé:
[switch:vars] ansible_ssh_common_args='-o Ciphers=aes256-cbc -o KexAlgorithms=diffie-hellman-group1-sha1' ansible_connection=network_cli ansible_user=tonidldap ansible_network_os=ios ansible_become=no ansible_become_method=enable ansible_ssh_pass=tonmdpldap [switch_u2] 192.168.102.206 [switch_wifi] 192.168.102.221 192.168.102.222 192.168.102.224 192.168.102.224 192.168.102.225 192.168.102.216 [switch] 192.168.102.201 192.168.102.202 192.168.102.203 192.168.102.204 192.168.102.205 192.168.102.206 192.168.102.207 192.168.102.208 192.168.102.209 192.168.102.210 192.168.102.211 192.168.102.212 192.168.102.213 192.168.102.214 192.168.102.215 192.168.102.217 192.168.102.218 192.168.102.230 192.168.102.240 192.168.102.241 192.168.102.242 192.168.102.243 192.168.102.244 192.168.102.245 192.168.102.246
Ajoutez également
host_key_checking = False
dans le fichier ansible.cfg. Ca vous permettra d'éviter la vérification de la clé ssh des switchs ou autres équipements.
Il est possible de journaliser toutes les commandes entrées sur le commutateur par un utilisateur. Combiné avec l'authentification via le service Radius-Switch ça peut être utile pour savoir « qui a fait de la merde ? ». Tout est expliqué dans ce pdf.
— Kevin Cazal 2018/07/07 16:45