
certains services vers le vlan adm. Pour l'instant, hardcodéedans la classe parente, on matche sur les uid pour les services de vert. ce sera plutot mettre dans chaque classe, en fonction de l'install. A terme, d'ailleurs, vert (zamok) ne sera meme plus accessible aux adhérents. darcs-hash:20050521224302-f163d-7adc92b18773077552adfa89c8032e99d9054230.gz
426 lines
15 KiB
Python
Executable file
426 lines
15 KiB
Python
Executable file
#!/usr/bin/env python
|
|
# -*- coding: iso-8859-1 -*-
|
|
# The authors of this code are
|
|
# Manuel Sabban <manu@feyd-rautha.org>
|
|
# Frédéric Pauget <pauget@crans.ens-cachan.fr>
|
|
#
|
|
# 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"
|
|
|
|
adm_uids = [ 0, 1, 38, 103, 105, 106, 111, 112 ]
|
|
|
|
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', 'SERV_OUT_ADM' ] :
|
|
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 -A OUTPUT -d %s -j SERV_OUT_ADM" % self.vlan_adm)
|
|
iptables("-t nat -P PREROUTING DROP")
|
|
iptables("-t nat -P OUTPUT ACCEPT")
|
|
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) :
|
|
self.exception_catcher(self.reseaux_non_routables)
|
|
self.exception_catcher(self.test_mac_ip)
|
|
self.exception_catcher(self.serv_out_adm)
|
|
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 serv_out_adm(self) :
|
|
self.anim = anim(' Output vers VLAN adm', len(self.adm_uids))
|
|
for uid in self.adm_uids :
|
|
self.anim.cycle()
|
|
iptables("-t nat -A SERV_OUT_ADM -m owner --uid-owner %d -j ACCEPT" % uid)
|
|
iptables("-t nat -A SERV_OUT_ADM -j DROP")
|
|
self.anim.reinit()
|
|
print OK
|
|
|
|
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"
|