Table des matières

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. » — 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 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

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 à <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.

Côté serveur

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

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: <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.

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 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 Radius-Switch ça peut être utile pour savoir « qui a fait de la merde ? ». Tout est expliqué dans ce pdf.

Exemple de log:

FIN

Kevin Cazal 2018/07/07 16:45