#!/usr/bin/env python # -*- coding: iso-8859-1 -*- # The authors of this code are # Manuel Sabban # Frédéric Pauget # # Copyright (c) 2004 Manuel Sabban, Frédéric Pauget # Copyright (c) 2005 Mathieu Segaud # # Permission to use, copy, and modify this software with or without fee # is hereby granted, provided that this entire notice is included in # all source code copies of any software which is or includes a copy or # modification of this software. # # THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR # IMPLIED WARRANTY. IN PARTICULAR, NONE OF THE AUTHORS MAKES ANY # REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE # MERCHANTABILITY OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR # PURPOSE. """ Firewalls du Cr@ns """ import sys sys.path.append('/usr/scripts/gestion') import syslog from lock import * from ldap_crans import crans_ldap, ann_scol, machine, crans, invite from affich_tools import * from commands import getstatusoutput from iptools import AddrInNet syslog.openlog('firewall') class IptablesError(Exception): """ Gestion des erreurs d'iptables """ def __init__(self,cmd,err_code,output): self.cmd=cmd self.err_code=err_code self.output=output syslog.syslog(syslog.LOG_ERR,"%s : status %s,%s" % (cmd,err_code,output)) def __str__(self): return "%s\n status : %s\n %s" % (self.cmd,self.err_code,self.output) def iptables(cmd): """ Interface à iptables """ syslog.syslog(syslog.LOG_INFO,cmd) status,output=getstatusoutput("/sbin/iptables "+cmd) if status: raise IptablesError(cmd,status,output) return output class firewall_crans : """ Classe parente pour les firewalls du crans Implementee directement a partir du firewall de komaz, initialement ecrit par Manu Sabban et Fred Pauget. * les méthodes à surcharger pour l'implémentation même des firewall sont nat_table, filter_table, pour la preparation du fw, start_fw_funcs pour la mise en place du filtrage. * enable_route et disable_route utilisees seulement par komaz * serveurs_maj, adh_maj_list_to_do et serveurs_maj_list_to_do, pour la mise en place de la MAC-IP. en particulier, adh_maj_list_to_do et serveurs_maj_list_to_do sont factorisees pour la simple et bonne raison que les sources de la liste to_do originale ne seront pas forcement identiques (c'est un peu sale...) * La classe parente contient a peu de choses pres tout ce qu'il faut pour mettre en place un fw basique n'effectuant que la verif MAC-IP. """ zone_serveur="138.231.136.0/28" vlan_adm="138.231.144.0/28" mac_wifi = '00:0c:f1:fa:f1:4b' limit = " -m limit --limit 10/s --limit-burst 10 " log_template = '-m limit --limit 1/s --limit-burst 1 -j LOG --log-level notice --log-prefix ' filtre_flood = '-m hashlimit --hashlimit 20 --hashlimit-mode srcip,dstip --hashlimit-name flood' liste_reseaux_non_routables = [ '1.0.0.0/8','2.0.0.0/8','5.0.0.0/8','7.0.0.0/8',\ '10.0.0.0/8','14.0.0.0/8','23.0.0.0/8','27.0.0.0/8','31.0.0.0/8','36.0.0.0/8',\ '37.0.0.0/8','39.0.0.0/8','41.0.0.0/8','42.0.0.0/8','71.0.0.0/8','72.0.0.0/5',\ '89.0.0.0/8','90.0.0.0/7','92.0.0.0/6','96.0.0.0/3','169.254.0.0/16','172.16.0.0/12',\ '173.0.0.0/8','174.0.0.0/7','176.0.0.0/5','184.0.0.0/6','189.0.0.0/8','190.0.0.0/8',\ '192.168.0.0/16','197.0.0.0/8','223.0.0.0/8','224.0.0.0/4','240.0.0.0/4'] machines = [] debug = 1 def exception_catcher(self,tache) : """ Exécute la tache founie en gérant les diverses exceptions pouvant survenir Retourne 1 en cas d'erreur et 0 sinon """ try : tache() return 0 except IptablesError, c : self.anim.reinit() print ERREUR if self.debug : print c except : self.anim.reinit() print ERREUR import traceback if self.debug : traceback.print_exc() return 1 def __machines(self) : """ Liste des machines du crans """ if not self.machines : self.anim = anim(" Interrogation de la base LDAP") self.machines = crans_ldap().search('ip=*')['machine'] print OK return self.machines def __init__(self) : """ Pose un lock """ make_lock('firewall') def __del__(self) : """ Destruction du lock """ remove_lock('firewall') def nat_table(self) : self.anim = anim(' Structure de la table nat') for chaine in [ 'TEST_MAC-IP', 'RESEAUX_NON_ROUTABLES_SRC', 'RESEAUX_NON_ROUTABLES_DST' ] : iptables('-t nat -N %s' % chaine) iptables("-t nat -P PREROUTING ACCEPT") iptables("-t nat -A PREROUTING -i lo -j ACCEPT") iptables("-t nat -A PREROUTING -d 224.0.0.0/4 -j DROP") iptables("-t nat -A PREROUTING -j RESEAUX_NON_ROUTABLES_DST") iptables("-t nat -A PREROUTING -j RESEAUX_NON_ROUTABLES_SRC") iptables("-t nat -A PREROUTING -j TEST_MAC-IP") iptables("-t nat -P PREROUTING DROP") print OK def nat_table_tweaks(self) : return def filter_table(self) : self.anim = anim(' Structure de la table filter') print OK def filter_table_tweaks(self) : return def start_fw_funcs(self) : return def enable_route(self) : return def disable_route(self) : return def restart(self): """ Rédémarrage du firewall """ cprint('Redémarrage firewall','gras') self.exception_catcher(self.__stop) self.start(False) def start(self,aff_txt_intro=True) : """ Construction du firewall aff_txt_intro s'occupe uniquement de l'esthétisme """ if aff_txt_intro : cprint('Démarrage firewall','gras') # Préliminaires if not self.__machines() or self.exception_catcher(self.__stop) : cprint("Abandon",'rouge') return # Initialisation self.exception_catcher(self.__start) # Remplisage self.start_fw_funcs() # On peux router self.enable_route() cprint(" -> fin de la procédure de démarrage",'vert') def __start(self) : self.nat_table() self.nat_table_tweaks() self.filter_table() self.filter_table_tweaks() def reseaux_non_routables(self) : """ Construction de RESEAUX_NON_ROUTABLES_{DST,SRC} """ self.anim = anim(' Filtrage ip non routables',len(self.liste_reseaux_non_routables)) for reseau in self.liste_reseaux_non_routables : iptables("-t nat -A RESEAUX_NON_ROUTABLES_DST -d %s -j DROP" % reseau) iptables("-t nat -A RESEAUX_NON_ROUTABLES_SRC -s %s -j DROP" % reseau) self.anim.cycle() self.anim.reinit() print OK def stop(self): """ Arrête le firewall """ cprint("Arrêt du firewall",'gras') self.disable_route() """ if self.hostname == 'komaz': self.anim = anim(" Arrêt routage") status,output=getstatusoutput('echo 0 > /proc/sys/net/ipv4/ip_forward') if status : print ERREUR else : print OK """ self.exception_catcher(self.__stop) cprint(" -> fin de la procédure d'arrêt",'vert') def __stop(self) : self.anim = anim(" Suppression règles") iptables("-t nat -P PREROUTING ACCEPT") iptables("-F") iptables("-t nat -F") iptables("-X") iptables("-t nat -X") print OK def test_mac_ip(self) : """ Reconstruit la correspondance MAC-IP des machines des adhérents """ self.anim = anim(' Chaîne TEST_MAC-IP',len(self.__machines())+1) iptables("-t nat -P PREROUTING ACCEPT") iptables("-t nat -F TEST_MAC-IP") self.anim.cycle() def procedure() : for machine in self.__machines() : self.__test_mac_ip(machine) self.anim.cycle() iptables("-t nat -P PREROUTING DROP") self.anim.reinit() print OK self.exception_catcher(procedure) def __test_mac_ip(self,machine): ip=machine.ip() mac=machine.mac() if machine.ipsec(): # Machine wifi, c'est la mac de Nectaris iptables("-t nat -A TEST_MAC-IP -s "+\ "%s -m mac --mac-source %s -j ACCEPT"%(ip,self.mac_wifi)) else: # Machine fixe iptables("-t nat -A TEST_MAC-IP -s "+\ "%s -m mac --mac-source %s -j ACCEPT"%(ip,mac)) def serveur_maj(): return def serveurs_maj_list_to_do(self) : return def adh_maj_list_to_do(self) : return def port_maj(self,ip_list) : """ Mise à jour des ports pour les ip fournies """ # Note : système bourrin (on efface les chaines et on refait) # mais rapide et efficace (si qqn veut se casser le cul à # un système aussi délicat que pour la correspondance MAC-IP...) # -- Fred serveur_maj = False adh_maj = False for ip in ip_list : if AddrInNet(ip,self.zone_serveur) : serveur_maj = True else : adh_maj = True if serveur_maj and adh_maj : break to_do=[] if serveur_maj : self.exception_catcher(self.serveurs_maj_list_to_do) if adh_maj : self.exception_catcher(self.adh_maj_list_to_do) # envoi d'un mail à roots try : from mail import mail_details machines = [] db = crans_ldap() for ip in ip_list : machines += db.search('ipHostNumber=%s' % ip)['machine'] mail_details(machines, Subject = 'Modification du firewall de komaz concernant une machine') #mail_details(machines, To = ['quenelle'], Subject = 'Modification du firewall du CR@NS', no_ascii = True) except : pass def mac_ip_maj(self,ip_list) : """ Mise à jour de la correspondance MAC-IP pour les ip fournies """ ## Que faut-il faire ? self.anim = anim(' Analyse travail à effectuer') if ip_list == [''] : print OK + ' (rien à faire)' return mac_ip_maj = {} serveur_maj = False for ip in ip_list : machine = crans_ldap().search('ip=%s'% ip)['machine'] if not machine : # Destruction des occurences if AddrInNet(ip,self.zone_serveur) : serveur_maj = True else : mac_ip_maj[ip] = None elif len(machine) == 1 : # Mise à jour de la machine if AddrInNet(ip,self.zone_serveur) : serveur_maj = True else : # Il faut avoir payé ou être une machine du crans ou un invite if crans_ldap().search('paiement=ok&ip=%s'% ip)['machine'] or \ machine[0].proprietaire().__class__ == crans or \ machine[0].proprietaire().__class__ == invite : mac_ip_maj[ip] = machine[0] else : mac_ip_maj[ip] = None else : print WARNING if debug : sys.stderr.write("Plusieurs machines avec l'IP %s" % ip) print OK ## Traitement # Serveurs if serveur_maj : self.__serveur_maj() # Correspondance MAC-IP if mac_ip_maj : def procedure() : warn = '' self.anim = anim(' Actualisation TEST_MAC-IP') for regle in iptables("-t nat -L TEST_MAC-IP -n").split('\n')[2:] : regle = regle.split() ip = regle[3] mac = regle[6].lower() if ip in mac_ip_maj.keys() : # La règle correspond à une ip à mettre à jour machine = mac_ip_maj[ip] try : if not machine : # Il faut détruire cette entrée iptables("-t nat -D TEST_MAC-IP -s %s -m mac --mac-source %s -j ACCEPT" % (ip, mac)) else : if ( machine.ipsec() and mac!=self.mac_wifi ) \ or ( not machine.ipsec() and mac != machine.mac() ) : # La correspondance MAC-IP est fausse => on ajoute la bonne règle self.__test_mac_ip(machine) # Supression de l'ancienne ligne iptables("-t nat -D TEST_MAC-IP -s %s -m mac --mac-source %s -j ACCEPT" % (ip, mac)) # Toutes les autres occurences devront être détruites mac_ip_maj[ip]=None except IptablesError, c : warn += c # Ajout des machines qui n'étaient pas dans le firewall for machine in mac_ip_maj.values() : if machine : self.__test_mac_ip(machine) if warn : print WARNING sys.stdout.write(warn) else : print OK self.exception_catcher(procedure) def build_chaine_adherent(self,chaine,methode) : # On construit d'abord les autorisations particuli\uffffres if not self.build_chaine(chaine, methode) : # Puis si pas de problèmes les autorisationq par défaut self.anim.reinit() for proto in [ 'tcp' , 'udp' ] : for port in self.ports_default["%s_%s" % ( proto, chaine) ] : self.anim.cycle() iptables("-I %s -p %s --dport %s -j ACCEPT" % (chaine, proto,port) ) self.anim.reinit() print OK def build_chaine(self,chaine, methode) : self.anim = anim(' Chaîne %s' % chaine,len(self.__machines())+1) iptables("-F %s" % chaine) self.anim.cycle() def procedure() : for machine in self.__machines() : methode(machine) self.anim.cycle() iptables("-A %s -j REJECT" % chaine) return self.exception_catcher(procedure) if __name__ == '__main__' : print "Cette classe n'est pas utilisable telle quelle" print " utilisée pour deriver les firewalls du Cr@ns"