====== Sauvegarder la configuration des commutateurs Cisco ======
« 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. »
--- //[[no_pseudo@minet.net|Captain Obvious]] 2018/07/07//
Et oui, je vais utiliser le mot « commutateur » tout le long de cet article.
===== Ce qui a été fait par le passé =====
Il y a très longtemps il semble qu'on utilisait un script qui analysait les logs des commutateurs sur la machine //serveurlog//1. Plus de détails sur la page [[wiki:services:confsvg|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 [[https://gitlab.minet.net/divers/sauvegarde-des-switchs-svgs|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 [[https://github.com/ytti/oxidized|Oxidized]], mais ils demanderont probablement la création d'un user pour pouvoir récupérer la conf notamment
===== Ce qu'on voulait faire =====
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).
===== Ce qui est en place =====
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).
==== Côté commutateur ====
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 à _-, 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.
==== Côté serveur ====
La configuration __minimale__ du serveur est très simple: **openssh-server**, **openssh-client**1 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. [[https://superuser.com/questions/577911/scp-lost-connection-but-ssh-works-fine|superuser.com]]
=== SCP oui, SSH non ! ===
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
=== Gestion des versions ===
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: _------.-
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 : [[https://gitlab.minet.net/zastava/switchsaves|Confsw]].
===== Restaurer une configuration =====
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
===== Déployer rapidement sur les switchs et autres équipements =====
Pour déployer la configuration rapidement sur les switchs, on utilise [[https://wiki.minet.net/wiki/services/ansible/|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.
===== Autre fonctionnalité des archives Cisco =====
Il est possible de journaliser toutes les commandes entrées sur le commutateur par un utilisateur. Combiné avec l'authentification via le service [[wiki:services:radius-switch|Radius-Switch]] ça peut être utile pour savoir //« qui a fait de la merde ? »//.
Tout est expliqué dans [[https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/config-mgmt/configuration/15-sy/config-mgmt-15-sy-book/cm-config-logger.pdf|ce pdf]].
Exemple de log:
{{:wiki:services:logcmdcisco.png?600|}}
===== FIN =====
--- //[[no_pseudo@minet.net|Kevin Cazal]] 2018/07/07 16:45//