scripts/gestion/gen_confs/firewall_crans.py
segaud c78754d3e4 Les machines accessibles aux adhérentsne devront laisser passer que
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
2005-05-22 00:43:02 +02:00

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"