#!/usr/bin/env python # -*- coding: utf-8 -*- """ 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 ) * 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 hptools) """ import sys, os, commands, smtplib, tempfile, getopt os.umask(0117) sys.path.append('/usr/scripts/') sys.path.append('/usr/scripts/gestion') from hptools import hpswitch, sw_chbre from ldap_crans import crans_ldap, BorneWifi from annuaires_pg import uplink_prises, reverse, bat_manuels, all_switchs, bat_switchs from random import shuffle from gen_confs import * import datetime import config import re from cranslib.deprecated import deprecated capture_model = re.compile(r'\((.*)\)') headers_by_model = { 'HP 2626': '; J4900C Configuration Editor; Created on release #H.10.83', 'HP 2650': '; J4899B Configuration Editor; Created on release #H.10.83', 'HP 2810-24G':'; J9021A Configuration Editor; Created on release #N.11.25', 'HP 2610-24':'; J9085A Configuration Editor; Created on release #R.11.54', 'HP 2610-48':'; J9088A Configuration Editor; Created on release #R.11.54', 'HP 2910al-24G':'; J9145A Configuration Editor; Created on release #W.14.38'} gigabit_models = ['J9021A', 'J9145A'] class switch(gen_config) : """Classe de configuration d'un switch""" # Répertoire ou écire les fichiers de conf CONF_REP='/tmp/' # avec un / derrière config = """%(switch_config_header)s hostname "%(switch)s" ; Generated %(date_gen)s by switchs.py %(module-type)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.98 timesync sntp sntp unicast ;-------------------------------------------------------- Misc console inactivity-timer 30 ;-------------------------------------------------------- Logs logging 10.231.136.38 ;-------------------------------------------------------- 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 ;------------------------------------------------------- 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 """ # Serveur DHCP des différent vlans dhcp_servers = { '1':'138.231.136.34', '3':'138.231.148.34', '6':'10.42.0.34', '7':'10.51.0.34', '9':'10.52.0.34', '21':'10.2.9.34' } dhcp_snooping_template = """;------------------------------------------------------- DHCP Snooping dhcp-snooping vlan""" for vlan in dhcp_servers.keys(): dhcp_snooping_template +=""" %s""" % vlan dhcp_snooping_template +=""" dhcp-snooping trust %(uplinks)s no dhcp-snooping trust %(non_uplinks)s """ for vlan in dhcp_servers.keys(): dhcp_snooping_template +="""dhcp-snooping authorized-server %%(dhcp-%s)s """ % vlan dhcp_snooping_template +="""; Activation dhcp-snooping""" interface_template = """interface %(prise)i enable name "%(nom)s" %(no_flowcontrol)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" 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) @deprecated("Tous les switchs possèdent une authentification radius.") def configure_chbre(self,chbre) : """ Recontigure la chambre fournie chambre. Déprécié. Tous les switchs possèdent une authentification radius.""" return False 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]) dhcp_servers = self.dhcp_servers # 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() , 'date_gen': str(datetime.datetime.now()), 'radius_key' : radius_key , 'radius-serveurs' : rad[:-1] % tuple(self.rad_servs), } for i in dhcp_servers.keys(): params['dhcp-%s' % i]=dhcp_servers[i] self.aff.cycle() options = [ opt for opt,arg in opts] # IP machine = self.db.search(switch)['machine'][0] params['ip'] = str(machine.ip()) 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 res != 0: 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() elif '--header' in options: for opt, arg in opts: if opt == '--header': params['switch_config_header'] = arg break else: try: match = capture_model.search(machine.info()[0]) model = match.group(1) sys.stderr.write(model) params['switch_config_header']=headers_by_model[model] except: sys.stderr.write('Impossible de déterminer le header à utiliser (switch %s)' % switch) params['switch_config_header']= '; J4899A Configuration Editor; Created on release #H.10.50' model = params['switch_config_header'].split(' ', 2)[1] if model == "J9145A": params['module-type'] = 'module 1 type J9145A' else: params['module-type'] = '' 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.") has_dhcp_snooping = "2810" not in " ".join(machine.info()) ### 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' : '', 'no_flowcontrol': '' } 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)] prise_params['no_flowcontrol'] = 'no ' 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 required vlans['hotspot_tagged'].append(prise) vlans['wifi_tagged'].append(prise) if wifi == 1 : prise_params['nom'] = "Wifi_%s" % m.nom().split(".")[0] else : prise_params['nom'] = "Wifi" # Certaines bornes sont dans des chambres, est-ce le cas ? if prise_chbres.has_key(annu_prise): vlans['default'].append(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) 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, []) # Pour les switchs gigabit, on bloque le gigabit par défaut, sauf # pour les membres actifs et les clubs (cf plus bas) if model in gigabit_models: prise_params['speed'] = 'speed-duplex auto-10-100' # Combien de machines sont succeptibles d'etre sur la prise nombre_de_machines = 0 # Pour chaque chambre sur cette prise for chb in chbres: # On selectionne les eventuels adherents y residant residents = self.db.search("chbre=%s%s" % (bat, chb)) for adherent in residents['adherent']: if adherent.droits(): #Seuls les membres actifs ont le droit à plus prise_params['speed'] = '' # On selectionne les machines fixes de l'adherent, et on ajoute le nombre au quota nombre_de_machines += len(adherent.machines_fixes()) if residents['club']: prise_params['speed'] = '' # Authentification RADIUS, pas pour les clubs... if not any("cl" in chbre.lower() for chbre in chbres): # "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. 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': 2 + nombre_de_machines, 'prise': prise } # On regle le nombre de machines connectables a la prise au nombre de machines # sur cette prise dans l'annuaire plus 2 else: # ... et pour les clubs, vlans par défaut vlans['default'].append(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('utf-8') # 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') my_config = self.config if has_dhcp_snooping: my_config += self.dhcp_snooping_template fd.write(my_config % params) fd.close() os.chown(self.CONF_REP + switch + '.conf',-1,4) if __name__ == '__main__' : opts, args = getopt.getopt(sys.argv[1:], 'hga', ['get-conf', 'help', 'all', 'header=' ]) if '-h' in sys.argv or '--help' in sys.argv or len(sys.argv) == 1 : print "%s [-g|--get-conf] " % 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()