scripts/gestion/gen_confs/switchs.py
Nicolas Dandrimont 1c5970d3e5 [switchs.py] Utilisation de nouvelles features des switches
Ignore-this: cc9c1e80009975a99f12f7101a6d0b5f

Nommément, les features sont :

 - Le DHCP Snooping (filtrage des requêtes/réponses DHCP par port)
 - La loop-detection (désactivation de prises lors de détection de
   boucle, plus simple que le STP)
 - Nettoyage pour générer des config compatibles avec les séries 2600
   et 2610

Pour que la configuration fonctionne correctement sur les switches de
la série 2600, l'OS H10.74 ou supérieur est préconisé.

darcs-hash:20090830170259-ffbb2-5d6e0f26e23e246f2e915f82fbae480d3d98bf85.gz
2009-08-30 19:02:59 +02:00

522 lines
18 KiB
Python

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
""" met à jour les propriétés des prises des switchs du bat :
mac autorisée(s), état (activé ou non) et nom de la prise
argument : nom du switch
procédure de configuration initiale :
* mot de passe admin (password manager user-name <username>)
* activation du ssh (crypto key generate ssh)
* copie fichier de conf
pour les reconfiguration copier le fichier de conf
Dans tous les cas FAIRE LE SNMP A LA MAIN (script hpttols)
"""
import string, sys, os, commands, smtplib, tempfile, getopt
sys.path.append('/usr/scripts/gestion')
from hptools import hpswitch, sw_chbre
from ldap_crans import crans_ldap, BorneWifi
from annuaires import chbre_prises, uplink_prises, reverse, bat_manuels, all_switchs
from random import shuffle
from gen_confs import *
from time import localtime
import config
from annuaires import bat_switchs
class switch(gen_config) :
# Répertoire ou écire les fichiers de conf
CONF_REP='/tmp/' # avec un / derrière
config = """%(switch_config_header)s
hostname "%(switch)s"
;-------------------------------------------------------- Snmp
snmp-server contact "root@crans.org"
snmp-server location "Batiment %(bat)s"
;A faire à la main
snmpv3 enable
snmpv3 restricted-access
;snmpv3 user "initial"
snmpv3 user "crans"
snmpv3 group ManagerPriv user "crans" sec-model ver3
snmp-server community "public" Operator
;-------------------------------------------------------- Heure/date
time timezone 60
time daylight-time-rule Western-Europe
sntp server 10.231.136.3
timesync sntp
sntp unicast
;-------------------------------------------------------- Misc
console inactivity-timer 30
;-------------------------------------------------------- Logs
logging 10.231.136.12
;-------------------------------------------------------- IP du switch
ip default-gateway 10.231.136.4
vlan %(vlan_adherent)s
name "DEFAULT_VLAN"
untagged %(prises_default)s
no ip address
ip igmp
no ip igmp querier
exit
vlan %(vlan_adm)s
name "Adm"
%(prises_adm)s
ip address %(ip)s 255.255.255.0
exit
vlan %(vlan_wifi)s
name "Wifi"
%(prises_wifi)s
no ip address
exit
vlan %(vlan_hotspot)s
name "Hotspot"
%(prises_hotspot)s
no ip address
exit
vlan %(vlan_gratuit)s
name "Gratuit"
tagged %(prises_default)s
no ip address
exit
vlan %(vlan_accueil)s
name "Accueil"
tagged %(prises_default)s
no ip address
exit
vlan %(vlan_isolement)s
name "Isolement"
tagged %(prises_default)s
no ip address
exit
vlan %(vlan_appts)s
name "Appt ENS"
%(prises_appts)s
no ip address
exit
;-------------------------------------------------------- Logs
%(INTERFACES_CONF)s
;------------------------------------------------------- Accès d'administration
no telnet-server
no web-management
aaa authentication ssh login public-key none
aaa authentication ssh enable public-key none
ip ssh
ip authorized-managers 10.231.136.0 255.255.255.0
ip ssh filetransfer
;------------------------------------------------------- Spanning-tree
spanning-tree force-version rstp-operation
; Config des uplinks
no spanning-tree %(uplinks)s admin-edge-port
spanning-tree %(uplinks)s point-to-point-mac true
spanning-tree %(uplinks)s priority 6
; Config des prises adhérent
spanning-tree %(non_uplinks)s admin-edge-port
spanning-tree %(non_uplinks)s point-to-point-mac auto
spanning-tree %(non_uplinks)s priority 8
; On active
spanning-tree
;------------------------------------------------------- DHCP Snooping
dhcp-snooping vlan %(vlan_adherent)s
dhcp-snooping trust %(uplinks)s
no dhcp-snooping trust %(non_uplinks)s
dhcp-snooping authorized-server %(dhcp)s
; Activation
dhcp-snooping
;------------------------------------------------------- Protection contre les boucles
loop-protect disable-timer 30
loop-protect transmit-interval 3
loop-protect %(non_uplinks)s
;------------------------------------------------------- Serveurs radius
radius-server dead-time 2
radius-server key %(radius_key)s
%(radius-serveurs)s
;------------------------------------------------------- Filtrage mac
aaa port-access mac-based addr-format multi-colon
;------------------------------------------------------- Bricoles
no cdp run
no stack
"""
interface_template = """interface %(prise)i
enable
name "%(nom)s"
flow-control%(speed)s
no lacp
exit
"""
# Serveurs radius
rad_servs = [ '10.231.136.72', '10.231.136.9' ]
rad_template = "radius-server host %s\n"
# Serveur DHCP du vlan par défaut
dhcp_server = "138.231.136.9"
def __init__(self,truc):
""" truc est soit :
* une _liste_ de chambres => reconfig de ces chambres
* un _tulpe_ de noms de switch => reconfig de ces swiths"""
self.db = crans_ldap() # connexion LDAP
if type(truc) == list :
# On enlève les chambres "CRA", "????" et EXT qui n'ont pas besion de config
self.chbres = [ch for ch in truc if (ch not in [ "CRA", "????", "EXT" ]) ]
self.switch = None
else :
self.chbres = None
self.switch = truc
def __str__(self) :
return 'switchs'
def restart(self) :
if self.chbre :
# Tout est déja fait
return
####### Vu qu'il n'y a pas de serveur tftp ici
# on excécute pas le truc en dessous
#for switch in self.switch :
# self.aff = anim('\treboot de %s' % switch)
# sw = hptools.switch(switch)
# sw.update()
def gen_conf(self) :
if self.chbres :
self.chbres.sort()
for chbre in self.chbres :
self.configure_chbre(chbre)
elif self.switch :
for switch in self.switch :
self.configure_switch(switch)
def configure_chbre(self,chbre) :
""" Recontigure la chambre fournie chambre """
try :
bat = chbre[0].lower()
if bat in bat_switchs :
prise = sw_chbre(chbre)
prise.reconfigure() # Vitesse et nom (juste au cas ou ca aurait changé)
elif bat in bat_manuels :
class prise_non_manageable :
def __init__(self,chbre) :
self.chbre = chbre
def __mail(self,sujet) :
To = "clef%s@crans.org" % self.chbre[0].lower()
From = To
conn=smtplib.SMTP('localhost')
txt_mail = "From: Crans scripts <%(From)s>\n"
txt_mail+= "To: %(To)s\n"
txt_mail+= "Subject: (CRANS) %s\n\nMerci." % sujet
conn.sendmail(From, To , txt_mail % { 'From' : From, 'To' : To })
conn.quit()
def disable(self) :
self.__mail("Chambre %s à débrancher." % self.chbre)
def enable(self) :
self.__mail("Chambre %s à brancher." % self.chbre)
prise=prise_non_manageable(chbre)
else :
# Rien a faire
print OK
return True
a = self.db.search('chbre=%s&paiement=ok' % chbre)
a = a['adherent'] + a['club']
if a and 'bloq' not in a[0].blacklist_actif() :
# Il faut activer la prise
anim('\tactivation chbre %s' % chbre)
prise.enable()
else :
# Il faut désactiver la prise
anim('\tdésactivation chbre %s' % chbre)
prise.disable()
print OK
except :
print ERREUR
if self.debug :
import traceback
traceback.print_exc()
return False
return True
def configure_switch(self,switch) :
self.aff = anim('\tconfiguration de %s' % switch)
try:
warn = self.__configure_switch(switch)
self.aff.reinit()
if warn :
print WARNING
if self.debug :
sys.stderr.write(warn)
else :
print OK
except :
self.aff.reinit()
print ERREUR
self._restore()
return 1
def __configure_switch(self,switch) :
""" Génère le fichier de conf du switch donné """
### Récupération données du switch
# Batiment et numéro du switch
bat = switch[3].lower()
sw_num = int(switch[5])
# Conf radius
sys.path.append('/usr/scripts/gestion/secrets')
from secrets import radius_key
self.aff.cycle()
## On veut par défaut tout confier au serveur radius principal
#shuffle(self.rad_servs)
rad = self.rad_template * len(self.rad_servs)
params = { 'switch' : switch, 'bat' : bat.upper() ,
'radius_key' : radius_key ,
'radius-serveurs' : rad[:-1] % tuple(self.rad_servs),
'dhcp': self.dhcp_server}
self.aff.cycle()
options = [ opt for opt,arg in opts]
if '-g' in options or '--get-conf' in options:
old_config = NamedTemporaryFile()
res, msg = commands.getstatusoutput("scp bat%s-%i:cfg/startup-config %s" % (bat, sw_num, old_config.name))
if not res:
raise RuntimeError(u"Erreur : impossible de récupérer l'ancienne configuration du switch")
params['switch_config_header'] = old_config.readline()
old_config.close()
self.aff.cycle()
else:
params['switch_config_header']= '; J4899A Configuration Editor; Created on release #H.10.50'
# IP
machine = self.db.search(switch)['machine'][0]
params['ip'] = str(machine.ip())
self.aff.cycle()
# Nombre de prises et modèle
nb_prises = machine.nombrePrises()
if nb_prises < 0 :
raise RuntimeError("Erreur : impossible de déterminer les caractéristiques du switch.")
### Configuration prises
params['INTERFACES_CONF'] = ''
# Dictionnaire prise -> chambre
prise_chbres = reverse(bat)
# Prises occupées par des machines du Cr@ns
crans_prises={}
for m in self.db.search('prise=%s%i*' % (bat.upper(), sw_num))['machine'] :
try: crans_prises[m.prise()].append(m)
except: crans_prises[m.prise()] = [ m ]
self.aff.iter = nb_prises+1
# Paramètres à affecter
for key in ( 'uplinks', 'non_uplinks' ) :
params[key] = []
vlans = { 'wifi_tagged' : [] , 'wifi_untagged' : [] ,
'hotspot_tagged' : [], 'hotspot_untagged' : [],
'adm_tagged' : [] , 'adm_untagged' : [] ,
'appts_tagged' : [], 'appts_untagged' : [],
# VLans pour le reste: le vlan des adhérents, des
# inconnus et de ceux qui ne paie pas
'default' : [] }
personnels_loges = self.db.search('etudes=Personnel ENS')['adherent']
prises_appartements= [ p.chbre() for p in personnels_loges ]
# Génération de la conf de chaque prise
for prise in range(1,nb_prises+1):
self.aff.cycle()
# Conf par défaut : activée, autonégociation
prise_params = { 'prise' : prise , 'speed' : '',
'etat' : '' }
annu_prise = '%i%02i' % (sw_num, prise) # prise telle que notée dans l'annuaire
if uplink_prises[bat].has_key(int(annu_prise)) :
### Prise d'uplink
prise_params['nom'] = uplink_prises[bat][int(annu_prise)]
params['uplinks'].append(prise)
vlans['default'].append(prise)
vlans['adm_tagged'].append(prise)
vlans['wifi_tagged'].append(prise)
vlans['hotspot_tagged'].append(prise)
vlans['appts_tagged'].append(prise)
params['INTERFACES_CONF'] += self.interface_template % prise_params
continue
params['non_uplinks'].append(prise)
if crans_prises.has_key("%s%s" % (bat.upper(), annu_prise)) :
### Prise réservée à l'association
wifi=0
adm=0
autres=0
for m in crans_prises["%s%s" % (bat.upper(), annu_prise)] :
if isinstance(m, BorneWifi): wifi += 1
elif m.Nom().find('.adm.crans.org')!=-1 : adm+=1
else : autres+=1
if autres==0 and adm==0 :
# Vlan wifi uniquement
if wifi == 1 :
prise_params['nom'] = "Wifi_%s" % m.nom().split(".")[0]
# Certaines bornes sont dans des chambres, est-ce le cas ?
if prise_chbres.has_key(annu_prise):
chbres = prise_chbres[annu_prise]
prise_params['nom'] += '+Chambre'
if len(chbres) > 1 : prise_params['nom'] += 's'
for chbre in chbres :
prise_params['nom'] += '_%s%s' % (bat.upper(), chbre)
vlans['default'].append(prise)
else :
prise_params['nom'] = "Wifi"
vlans['hotspot_tagged'].append(prise)
vlans['wifi_tagged'].append(prise)
elif wifi==0 and autres==0 :
# Vlan adm uniquement
if adm == 1 :
prise_params['nom'] = m.nom().split(".")[0]
else :
prise_params['nom'] = "Uplink_adm"
vlans['adm_untagged'].append(prise)
else :
# Tous les vlans
prise_params['nom'] = "Prise_crans"
vlans['default'].append(prise)
vlans['adm_tagged'].append(prise)
vlans['wifi_tagged'].append(prise)
vlans['hotspot_tagged'].append(prise)
params['INTERFACES_CONF'] += self.interface_template % prise_params
continue
# Quelle(s) chambre(s) est/sont sur la prise. A cause du
# cas PDJ il peux y avoir des prises avec plusieurs
# chambres.
chbres = prise_chbres.get(annu_prise, [])
# "unauth-vid" est le vlan sur lequel sont envoyés les machines
# quand l'authentification RADIUS échoue. On met le VLAN 1 pour
# éviter les problèmes quand LDAP se ch@#! dessus sur pegase.
params['INTERFACES_CONF'] += """aaa port-access mac-based %(prise)s
aaa port-access mac-based %(prise)s addr-limit %(nbmac)s
aaa port-access mac-based %(prise)s logoff-period 3600
aaa port-access mac-based %(prise)s unauth-vid 1
""" % { 'nbmac': 1+2*len(chbres), 'prise': prise }
# On donne à la prise un nom qui dépend des chambres
# connectés dessus
if chbres :
prise_params['nom'] = 'Chambre'
if len(chbres) > 1 : prise_params['nom'] += 's'
for chbre in chbres :
prise_params['nom'] += '_%s%s' % (bat.upper(), chbre)
else :
prise_params['nom'] = 'Inconnu'
# Si c'est une chambre d'un personnel de l'ENS, on lui donne
# le VLAN 21 en untagged
if chbres:
if chbres[0] in prises_appartements: # il faudrait faire
# un truc moins sale
vlans['appts_untagged'].append(prise)
prise_params['nom'] += "(appartement ENS)"
params['INTERFACES_CONF'] += self.interface_template % prise_params
# Petite verif
if not params['uplinks'] or not params['non_uplinks'] :
raise RuntimeError('Switch sans uplink ou sans prise adhérent.')
def mk_list(liste_prise) :
"""
transforme une liste de prises en une chaine pour le switch
exemple : 1, 3, 4, 5, 6, 7, 9, 10, 11, 12 => 1,3-7,9-12
"""
if not liste_prise : return ''
liste_prise.sort()
# initialisation
i = liste_prise.pop(0)
groupe = [ i, i ]
result = []
liste_prise.append(9999) # apparaitra jamais dans la liste
while liste_prise :
nouveau = liste_prise.pop(0)
if nouveau == groupe[1] + 1 :
groupe[1] += 1
else :
# Ajout du groupe au résultat
if groupe[0] == groupe[1] :
result.append(str(groupe[0]))
else :
result.append('-'.join(map(str,groupe)))
# Réinit de groupe
groupe = [ nouveau, nouveau ]
return ','.join(result)
# Saut de ligne parasite
params['INTERFACES_CONF'] = params['INTERFACES_CONF'][:-1].encode('iso-8859-15')
# Conversion des listes
for key in [ 'uplinks', 'non_uplinks' ] :
params[key] = mk_list(params[key])
for key, prises in vlans.items() :
vlans[key]=mk_list(prises)
# Config des vlans spéciaux (adm, wifi et appartements)
for v in ('adm', 'wifi', 'hotspot', 'appts') :
params['prises_%s' % v] = ''
for t in ('tagged' , 'untagged') :
if vlans['%s_%s' % (v,t)] :
params['prises_%s' % v] += '\n %s %s' % (t, vlans['%s_%s' % (v,t)])
# Saut de ligne parasite
params['prises_%s' % v] = params['prises_%s' % v][4:]
params['prises_default'] = vlans['default']
for name, number in config.vlans.items():
params["vlan_%s" % name] = number
# Ecriture
fd = self._open_conf(self.CONF_REP + switch + '.conf')
fd.write(self.config % params)
fd.close()
if __name__ == '__main__' :
opts, args = getopt.getopt(sys.argv[1:], 'hga', ['get-conf', 'help', 'all'])
if '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) == 1 :
print "%s [-g|--get-conf] <switch>" % sys.argv[0].split('/')[-1].split('.')[0]
print "Génération du fichier de configuration des switchs donnés."
sys.exit(255)
if args[0] == 'all' or 'a' in opts or '--all' in opts :
switchs = tuple(all_switchs())
else :
switchs = tuple(args)
sw = switch(switchs)
sw.debug = 1
sw.reconfigure()