scripts/gestion/gen_confs/firewall.py
salles eabc381699 Comme le dbit par adhrent n'est plus une inconnue mais une variable
relativement fixe dans nos scripts, on peut faire appel  'tc' avec 'del'
Le add tait en suspens car impossible d'avoir une valeur identique pour
tous les adhrents quand on faisait max/nb_adherents pour la dterminer.

darcs-hash:20060613155106-72cb0-06e83c6c65b0da4d9a60b4e52172287712caf882.gz
2006-06-13 17:51:06 +02:00

1141 lines
45 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>
# Mathieu Segaud <matt@minas-morgul.org>
# Nicolas Salles <salles@crans.org>
#
# Rewritten as inherited classes from firewall_crans
# by Mathieu Segaud <matt@minas-morgul.org>
#
# Copyright (c) 2004 Manuel Sabban, Frédéric Pauget
# Copyright (c) 2005 Mathieu Segaud
# Copyright (c) 2006 Nicolas Salles
#
# 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 EXPRSS 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.
import sys
sys.path.append('/usr/scripts/gestion')
import syslog
import pwd
from lock import *
from ldap_crans import crans_ldap, ann_scol, hostname
from ldap_crans import AssociationCrans, Machine, MachineWifi
from affich_tools import *
from commands import getstatusoutput
from iptools import AddrInNet, NetSubnets, IpSubnet
from config import NETs, mac_komaz, mac_wifi, conf_fw, p2p
syslog.openlog('firewall')
debug = 1
db = crans_ldap()
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)
class TcError(Exception):
""" Gestion des erreurs de tc """
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 d'iptables """
syslog.syslog(syslog.LOG_INFO,cmd)
status,output=getstatusoutput("/sbin/iptables "+cmd)
if status:
raise IptablesError(cmd,status,output)
return output
def tc(cmd):
""" Interface de tc """
syslog.syslog(syslog.LOG_INFO,cmd)
status,output=getstatusoutput("/sbin/tc "+cmd)
if status:
raise TcError(cmd,status,output)
return output
def redirect_chain(table, chain_in, chain_out, ip) :
try :
iptables("-t %s -N %s" % (table, chain_out))
except IptablesError :
iptables("-t %s -F %s" % (table, chain_out))
# On redirige les paquets de la chaîne in dans la chaîne out
iptables("-t %s -A %s -o ens -s %s -j %s" % (table, chain_in, ip, chain_out))
iptables("-t %s -A %s -o crans -d %s -j %s" % (table, chain_in, ip, chain_out))
class firewall_crans :
"""
Classe parente pour les firewalls du crans
Implémentée directement à partir du firewall de komaz, initialement
écrit par Manuel Sabban et Frédéric Pauget.
* les méthodes à surcharger pour l'implémentation eme des firewall
sont mangle_table, nat_table, filter_table, pour la préparation
du fw, start_fw_funcs pour la mise en place du filtrage.
* 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 factorisées pour la simple et bonne raison que les sources
de la liste to_do originale ne seront pas forcément identiques
(c'est un peu sale...)
* la classe parente contient à peu de choses prés tout ce qu'il
faut pour mettre en place un fw basique n'effectuant que la
verif MAC-IP.
"""
zone_serveur = NETs['serveurs'][0]
vlan_adm = NETs['vlan-adm'][0]
adm_users = [ "root", "identd", "daemon", "postfix", "freerad", "amavis", "nut", "respbats", "list", "sqlgrey", "ntpd" ]
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 --hashlimit-name flood'
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 """
return db.all_machines(graphic=True)
def __init__(self) :
""" Pose un lock """
make_lock('firewall')
def __del__(self) :
""" Destruction du lock """
# Comprend pas pourquoi il faut réimporter ici -- Fred
from lock import remove_lock
remove_lock('firewall')
def mangle_table(self) :
""" Remplit la table mangle """
return
def nat_table(self) :
""" Remplit la table nat """
return
def filter_table(self) :
""" Remplit la table filter """
self.anim = anim('\tStructure de la table filter')
print OK
def filter_table_tweaks(self) :
""" Complete la table filter """
return
def start_fw_funcs(self) :
""" Ordonnance la construction du firewall """
self.exception_catcher(self.test_mac_ip)
def post_start_hook(self) :
""" Hook de fin de demarrage """
return
def pre_stop_hook(self) :
""" Hook de debut d'arret """
return
def restart(self):
""" Rédémarrage du firewall """
cprint(u'Redémarrage firewall', 'gras')
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(u'Démarrage firewall', 'gras')
# Préliminaires
if not self.__machines() or self.exception_catcher(self.__stop) :
cprint(u"Abandon", 'rouge')
return
# Initialisation
self.exception_catcher(self.mangle_table)
self.exception_catcher(self.nat_table)
self.exception_catcher(self.filter_table)
self.exception_catcher(self.filter_table_tweaks)
# Remplissage
self.start_fw_funcs()
# On peux router
self.post_start_hook()
cprint(u"\t -> fin de la procédure de démarrage",'vert')
def stop(self):
""" Arrête le firewall """
cprint(u"Arrêt du firewall", 'gras')
self.pre_stop_hook()
self.exception_catcher(self.__stop)
cprint(u"\t -> fin de la procédure d'arrêt",'vert')
def __stop(self) :
self.anim = anim("\tSuppression des règles")
iptables("-t nat -P PREROUTING ACCEPT")
iptables("-F")
iptables("-t nat -F")
iptables("-t mangle -F")
iptables("-X")
iptables("-t nat -X")
iptables("-t mangle -X")
print OK
def test_mac_ip(self) :
""" Reconstruit la correspondance MAC-IP des machines des adhérents """
self.anim = anim('\tChaî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 -A TEST_MAC-IP -j DROP")
iptables("-t nat -P PREROUTING ACCEPT")
self.anim.reinit()
print OK
self.exception_catcher(procedure)
def __test_mac_ip(self,machine):
ip=machine.ip()
mac=machine.mac()
insert = '-I'
if isinstance(machine, MachineWifi):
# Machine wifi, c'est la mac de Nectaris
iptables("-t nat %s TEST_MAC-IP -s "%(insert)+\
"%s -m mac --mac-source %s -j RETURN"%(ip, mac_wifi))
else:
# Machine fixe
iptables("-t nat %s TEST_MAC-IP -s "%(insert)+\
"%s -m mac --mac-source %s -j RETURN"%(ip,mac))
def serveurs_maj(self):
pass
def serveurs_maj_list_to_do(self) :
pass
def adh_maj_list_to_do(self) :
pass
def port_maj(self,ip_list) :
""" Mise à jour des ports pour les ip fournies """
# Note : système bourrin (on efface les chaînes 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)
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('\tAnalyse du travail à effectuer')
if ip_list == [''] :
print OK + ' (rien à faire)'
return
mac_ip_maj = {}
serveur_maj = False
for ip in ip_list :
machine = db.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
# Il faut avoir payé ou être une machine du crans
if db.search('paiement=ok&ip=%s'% ip)['machine'] or \
machine[0].proprietaire().__class__ == AssociationCrans:
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\n" % ip)
print OK
## Traitement
# Serveurs
if serveur_maj :
self.serveurs_maj()
# Correspondance MAC-IP
if mac_ip_maj or serveur_maj :
def procedure() :
warn = ''
self.anim = anim('\tActualisation TEST_MAC-IP')
for regle in iptables("-t nat -L TEST_MAC-IP -n").split('\n')[2:] :
regle = regle.split()
if regle[0] == 'DROP':
# On est arrivé à la fin de la liste
break
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 RETURN" % (ip, mac))
else :
if (isinstance(machine, MachineWifi) and mac != mac_wifi) \
or (not isinstance(machine, MachineWifi) 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 RETURN" % (ip, mac))
# Toutes les autres occurences devront être détruites
mac_ip_maj[ip]=None
except IptablesError, c:
warn += str(c) + '\n'
# 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ères
if not self.build_chaine(chaine, methode) :
# Puis si pas de problèmes les autorisations 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('\tChaî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)
"""
Komaz
"""
class firewall_komaz(firewall_crans) :
"""
Structure du firewall :
table mangle :
PREROUTING (policy par défaut : ACCEPT)
1) proxy transparent
2) marquage des paquets bittorrent
POSTROUTING (policy par défaut : ACCEPT)
1) passage dans un sous-réseau de l'ip crans : SUBNET
SUBNET classe pour chaque ip de son réseau dans la classe htb correspondante
table nat :
PREROUTING (policy par défaut : ACCEPT)
1) passage par TEST_VIRUS_FLOOD pour tout ce qui n'est pas dans zone_serveur
2) passage dans RESEAUX_NON_ROUTABLES_DST
3) passage est paquets venant de l'extérieur dans RESEAUX_NON_ROUTABLES_SRC
4) on laisse passer vers filter les paquets suivants :
source ou destination les serveurs de serveurs_crans
ce qui vient de l'extérieur
5) passage par TEST_MAC-IP
TEST_VIRUS_FLOOD droppe les paquets contenant des virus ou les paquets de flood
RESEAUX_NON_ROUTABLES_DST droppe les paquets dont la destination est non routable
RESEAUX_NON_ROUTABLES_SRC droppe les paquets dont la source est non routable
TEST_MAC-IP envoi les bon paquets vers CRANS_VERS_EXT
table filter :
FORWARD (policy par défaut : ACCEPT)
1) passage par BLACKLIST
2) passage pas FILTRE_P2P (ACCEPT sur le trafic p2p, sanction gérées par déconnexion.py)
3) ce qui a pour source les serveurs de serveurs_crans est dirigé vers SERVEURS_VERS_EXT
4) ce qui a pour destination les serveurs de serveurs_crans est dirigé EXT_VERS_SERVEURS
5) tout ce qui vient de l'interface externe est dirigé vers EXT_VERS_CRANS
6) ce qui a pour source les serveurs de serveurs_crans est dirigé vers EXT_VERS_CRANS
BLACKLIST fitre des ip blacklistées (REJECT)
FILTRE_P2P filtre le traffic de p2p
EXT_VERS_CRANS et CRANS_VERS_EXT
ACCEPT pour les paquets vers les machines du crans (test port-ip)
REJECT pour le reste
EXT_VERS_SERVEURS et SERVEURS_VERS_EXT
ACCEPT pour bon mac-ip-port
REJECT pour le reste
"""
# interfaces physiques
eth_ext = "ens"
eth_int = "crans"
# Ports ouverts
ports_default = { 'tcp_EXT_VERS_CRANS' : [ '22' ],
'tcp_CRANS_VERS_EXT': [ ':79', '81:134', '136', '140:444', '446:'],
'udp_EXT_VERS_CRANS' : [ ],
'udp_CRANS_VERS_EXT': [ ':136','140:'] }
ports_virus = { 'tcp' : [ 135, 445 ] , 'udp' : [] }
# Filtrage du peer to peer
filtres_p2p = [ ('bit', 'Bittorrent'),
('apple', 'AppleJuice'),
('soul', 'SoulSeek'),
('winmx', 'WinMX'),
('edk', 'eDonkey'),
('dc', 'DirectConnect'),
('kazaa', 'KaZaa'),
('ares', 'Ares'),
('gnu', 'GNUtella') ]
ports_p2p = [ '412', '1214', '4662:4665' , '6346:6347', '6699', '6881:6889' ]
liste_reseaux_non_routables = [ '10.0.0.0/8', '172.16.0.0/12',
'169.254.0.0/16', '192.168.0.0/16', '224.0.0.0/4']
def reseaux_non_routables(self) :
""" Construction de RESEAUX_NON_ROUTABLES_{DST,SRC} """
self.anim = anim('\tFiltrage 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 mangle_table(self) :
self.anim = anim('\tStructure de la table mangle')
# On vide complètement la table
iptables("-t mangle -F")
iptables("-t mangle -X")
# Proxy transparent
iptables("-t mangle -A PREROUTING -s %s -j RETURN" % self.zone_serveur)
iptables("-t mangle -A PREROUTING -p tcp --destination-port 80 " +
"-s %s -d ! %s -j MARK " % (NETs['fil'][0], NETs['wifi'][0]) +
"--set-mark %s" % conf_fw.mark['proxy'])
iptables("-t mangle -A PREROUTING -m mark --mark %s -j ACCEPT" % conf_fw.mark['proxy'])
# On ne va pas plus loin si il ne s'agit pas de bittorrent
iptables("-t mangle -A POSTROUTING -m mark ! --mark %s -j ACCEPT" % conf_fw.mark['bittorrent'])
# On marque les paquets bittorrent uniquement
iptables("-t mangle -A PREROUTING -p tcp -j CONNMARK --restore-mark")
iptables("-t mangle -A PREROUTING -p tcp -m mark ! --mark 0x0 " +
"-j ACCEPT")
iptables("-t mangle -A PREROUTING -p tcp -m ipp2p --bit -j MARK " +
"--set-mark %s" % conf_fw.mark['bittorrent'])
iptables("-t mangle -A PREROUTING -p tcp -m mark " +
"--mark %s -j CONNMARK --save-mark" % conf_fw.mark['bittorrent'])
warn = ''
# Par défaut on envoit les paquets dans la classe 9998
for net in NETs['all'] :
iptables("-t mangle -A POSTROUTING -o crans -d %s " % net +
"-j CLASSIFY --set-class 1:9998")
iptables("-t mangle -A POSTROUTING -o ens -s %s " % net +
"-j CLASSIFY --set-class 1:9998")
# On crée les chaînes de sous-réseaux
for net in NETs['all'] :
for mask in conf_fw.mask :
for subnet in NetSubnets(net, mask) :
index = conf_fw.mask.index(mask)
if index == 0 :
prev_chain = "POSTROUTING"
else :
ip = subnet.split('/')[0]
prev_subnet = IpSubnet(ip, conf_fw.mask[index-1])
prev_chain = "SUBNET-%s" % prev_subnet
next_chain = "SUBNET-%s" % subnet
redirect_chain('mangle', prev_chain, next_chain, subnet)
print OK
# Création des classes et qdisc
for interface in [self.eth_ext, self.eth_int] :
# On vide les classes et qdisc
try :
tc("qdisc del dev %s root" % interface)
except TcError, c:
warn += str(c) + '\n'
# On construit les classes et qdisc de base
# La partie principale qui définit le comportement par défaut
tc("qdisc add dev %s root handle 1: htb r2q 1" % interface)
tc("class add dev %s parent 1: classid 1:1 htb rate %s ceil %s" % (interface, p2p.debit_max, p2p.debit_max))
tc("class add dev %s parent 1:1 classid 1:9998 htb rate %s ceil %s" % (interface, p2p.debit_adh, p2p.debit_adh))
tc("qdisc add dev %s parent 1:9998 handle 9999: sfq perturb 10" % interface)
adherents = db.search('paiement=ok')['adherent']
self.anim = anim('\tGénération des classes de filtrage p2p', len(adherents))
# On construit ensuite les classes et qdisc pour chaque adhérent
for adherent in adherents :
self.anim.cycle()
class_id = int(adherent.id())+1 # On ne peut pas reprendre le numéro 1
qdisc_id = class_id+5000 # Il nous faut un n° inférieur à 9998 unique
for interface in [self.eth_ext, self.eth_int] :
tc("class add dev %s parent 1:1 classid 1:%d htb rate %s ceil %s" % (interface, class_id, p2p.debit_adh, p2p.debit_max))
tc("qdisc add dev %s parent 1:%d handle %d: sfq perturb 10" % (interface, class_id, qdisc_id))
# Classification des adhérents dans leur classe respective
for machine in adherent.machines() :
ip = machine.ip()
subnet = IpSubnet(machine.ip(), conf_fw.mask[len(conf_fw.mask)-1])
iptables("-t mangle -A SUBNET-%s -o crans -d %s " % (subnet, ip) +
"-j CLASSIFY --set-class 1:%s" % class_id)
iptables("-t mangle -A SUBNET-%s -o ens -s %s " % (subnet, ip) +
"-j CLASSIFY --set-class 1:%s" % class_id)
self.anim.reinit()
print OK
def nat_table(self) :
self.anim = anim('\tStructure de la table nat')
for chaine in [ 'TEST_MAC-IP', 'RESEAUX_NON_ROUTABLES_SRC', 'RESEAUX_NON_ROUTABLES_DST', 'TEST_VIRUS_FLOOD', 'LOG_VIRUS', 'LOG_FLOOD' ] :
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 -s ! %s -j TEST_VIRUS_FLOOD" % self.zone_serveur)
iptables("-t nat -A PREROUTING -j RESEAUX_NON_ROUTABLES_DST")
iptables("-t nat -A PREROUTING -i %s -j RESEAUX_NON_ROUTABLES_SRC" % self.eth_ext )
iptables("-t nat -A PREROUTING -i %s -p udp --destination 138.231.136.1 --destination-port 53 -j DNAT --to-destination 138.231.136.3" % self.eth_ext)
iptables("-t nat -A PREROUTING -i %s -j ACCEPT" % self.eth_ext )
iptables("-t nat -A PREROUTING -s %s -j ACCEPT" % self.zone_serveur )
iptables("-t nat -A PREROUTING -d %s -j ACCEPT" % self.zone_serveur )
# iptables("-t nat -A PREROUTING -i %s -p tcp --dport 80 -s ! %s -j DNAT --to-destination 138.231.136.3:81" % (self.eth_int, self.zone_serveur) )
# iptables("-t nat -A POSTROUTING -o %s -p tcp --dport 81 -s 138.231.136.0/21 -d 138.231.136.3 -j SNAT --to-source 138.231.136.4" % self.eth_int )
iptables("-t nat -A PREROUTING -j TEST_MAC-IP")
iptables("-t nat -P PREROUTING ACCEPT")
iptables("-t nat -P OUTPUT ACCEPT")
# Proxy transparent
iptables("-t nat -A PREROUTING -p tcp -m mark --mark %s " % conf_fw.mark['proxy'] +
"-j DNAT --to-destination 138.231.144.10:3128")
print OK
def filter_table_tweaks(self) :
self.anim = anim('\tRègles spécifiques à komaz')
for chaine in [ 'ADMIN_VLAN', 'EXT_VERS_SERVEURS', 'SERVEURS_VERS_EXT' , 'EXT_VERS_CRANS', 'CRANS_VERS_EXT', 'BLACKLIST_SRC', 'BLACKLIST_DST' , 'FILTRE_P2P' ] :
iptables('-N %s' % chaine)
iptables("-A FORWARD -i lo -j ACCEPT")
iptables("-A FORWARD -p icmp -j ACCEPT")
iptables("-A FORWARD -i %s -d %s -j REJECT" % (self.eth_ext, self.vlan_adm) )
iptables("-A FORWARD -i %s -j BLACKLIST_DST" % self.eth_ext )
iptables("-A FORWARD -o %s -j BLACKLIST_SRC" % self.eth_ext )
iptables("-A FORWARD -s ! %s -d ! %s -j FILTRE_P2P" % (self.zone_serveur, self.zone_serveur) )
iptables("-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT")
iptables("-A FORWARD -i %s -d %s -j ADMIN_VLAN" % (self.eth_int, self.vlan_adm) )
iptables("-A FORWARD -i %s -d %s -j EXT_VERS_SERVEURS" % (self.eth_ext, self.zone_serveur) )
iptables("-A FORWARD -o %s -s %s -j SERVEURS_VERS_EXT" % (self.eth_ext, self.zone_serveur) )
iptables("-A FORWARD -i %s -j EXT_VERS_CRANS" % self.eth_ext )
iptables("-A FORWARD -o %s -j CRANS_VERS_EXT" % self.eth_ext )
# Proxy transparent
iptables("-I FORWARD -m mark --mark %s -j ACCEPT" % conf_fw.mark['proxy'])
print OK
def classes_p2p_maj(self, ip_list) :
""" Mise à jour de la classification pour les ip fournies
On ne crée que les règles iptables pour classer les paquets, les
classes correspondantes ne sont à créer que toutes à la fois """
## Que faut-il faire ?
self.anim = anim('\tAnalyse du travail à effectuer')
if ip_list == [''] :
print OK + ' (rien à faire)'
return
print OK
## Traitement
# MAJ des règles de classification de l'IP
def procedure() :
self.anim = anim('\tMise à jour des classes p2p')
warn = ''
try :
for ip in ip_list :
recherche = db.search('ip=%s&paiement=ok'% ip)
# Si l'ip n'appartient pas à un adhérent,
# on cherche pas plus loin
if not recherche['adherent'] :
continue
machines = recherche['machine']
if not machines :
# Il faut supprimer cette entrée
iptables_option = '-D'
tc_option = 'del'
subnet = IpSubnet(ip, conf_fw.mask[len(conf_fw.mask)-1])
regles = iptables("-t mangle -L SUBNET-%s -n | grep %s" % (subnet, ip)).split('\n')
# On sélectionne la première qui doit contenir ce que l'on veut
regle = regles[0].split()
mark = regle[7]
class_id = int(mark.split(':')[1])
elif len(machines) == 1 :
# Il faut ajouter cette entrée
iptables_option = '-A'
tc_option = 'add'
machine = machines[0]
adherent = machine.proprietaire()
ip = machine.ip()
subnet = IpSubnet(ip, conf_fw.mask[len(conf_fw.mask)-1])
class_id = int(adherent.id())+1 # On ne peut pas reprendre le numéro 1
else :
warn += "Plusieurs machines avec l'IP %s\n" % ip
qdisc_id = class_id+5000 # Il nous faut un n° inférieur à 9998 unique
for interface in [self.eth_ext, self.eth_int] :
if tc_option :
tc("class %s dev %s parent 1:1 classid 1:%d htb rate %s ceil %s" % (tc_option, interface, class_id, p2p.debit_adh, p2p.debit_max))
tc("qdisc %s dev %s parent 1:%d handle %d: sfq perturb 10" % (tc_option, interface, class_id, qdisc_id))
iptables("-t mangle %s SUBNET-%s -o crans -d %s -m mark " % (iptables_option, subnet, ip) +
"--mark %s -j CLASSIFY --set-class 1:%s" % (conf_fw.mark['bittorrent'], class_id))
iptables("-t mangle %s SUBNET-%s -o ens -s %s -m mark " % (iptables_option, subnet, ip) +
"--mark %s -j CLASSIFY --set-class 1:%s" % (conf_fw.mark['bittorrent'], class_id))
except IptablesError, c:
warn += str(c) + '\n'
except KeyError :
print OK
if warn :
print WARNING
sys.stdout.write(warn)
else :
print OK
self.exception_catcher(procedure)
def post_start_hook(self) :
self.anim = anim("\tMise en place du routage")
warn = ''
for cmd in [ 'echo 1 > /proc/sys/net/ipv4/ip_forward' ,
'echo 65536 > /proc/sys/net/ipv4/ip_conntrack_max' ,
'modprobe ip_conntrack_ftp' ,
'modprobe ip_conntrack_irc' ] :
status,output=getstatusoutput(cmd)
if status :
warn += output + '\n'
if warn :
print WARNING
if self.debug :
print warn
else :
print OK
def pre_stop_hook(self) :
self.anim = anim("\tArrêt du routage")
status,output=getstatusoutput('echo 0 > /proc/sys/net/ipv4/ip_forward')
if status :
print ERREUR
else :
print OK
def start_fw_funcs(self) :
self.exception_catcher(self.log_chaines)
self.exception_catcher(self.test_virus_flood)
self.exception_catcher(self.reseaux_non_routables)
self.exception_catcher(self.blacklist)
self.exception_catcher(self.admin_vlan)
self.exception_catcher(self.serveurs_vers_ext)
self.exception_catcher(self.ext_vers_serveurs)
self.exception_catcher(self.crans_vers_ext)
self.exception_catcher(self.ext_vers_crans)
self.exception_catcher(self.test_mac_ip)
self.exception_catcher(self.filtre_p2p)
def serveurs_maj_list_to_do(self) :
self.exception_catcher(self.serveurs_vers_ext)
self.exception_catcher(self.ext_vers_serveurs)
def adh_maj_list_to_do(self) :
self.exception_catcher(self.crans_vers_ext)
self.exception_catcher(self.ext_vers_crans)
def log_chaines(self) :
""" Construction des chaînes de log (LOG_VIRUS et LOG_FLOOD) """
self.anim = anim('\tCréation des chaînes de log')
for filtre in [ 'VIRUS', 'FLOOD' ] :
# Vidage de la chaîne
iptables('-t nat -F LOG_%s' % filtre)
iptables('-t nat -A LOG_%s %s %s:' % (filtre, self.log_template, filtre.capitalize()) )
iptables('-t nat -A LOG_%s -j DROP' % filtre )
self.anim.cycle()
self.anim.reinit()
print OK
def test_virus_flood(self) :
""" Construction de la chaîne TEST_VIRUS """
iptables('-t nat -F TEST_VIRUS_FLOOD')
self.anim = anim('\tFiltrage virus et floods')
for proto, ports in self.ports_virus.items() :
for port in ports :
iptables('-t nat -A TEST_VIRUS_FLOOD -p %s --dport %s -j LOG_VIRUS' % (proto, port) )
self.anim.cycle()
iptables('-t nat -A TEST_VIRUS_FLOOD %s -j RETURN' % self.filtre_flood) # Les limites en négatif ca ne marche pas.
self.anim.cycle()
iptables('-t nat -A TEST_VIRUS_FLOOD -j LOG_FLOOD')
self.anim.reinit()
print OK
def serveurs_vers_ext(self) :
""" Reconstruit la chaîne SERVEURS_VERS_EXT """
if not self.build_chaine('SERVEURS_VERS_EXT', self.__serveurs_vers_ext) :
self.anim.reinit()
print OK
def ext_vers_serveurs(self) :
""" Reconstruit la chaîne EXT_VERS_SERVEURS """
if not self.build_chaine('EXT_VERS_SERVEURS', self.__ext_vers_serveurs) :
self.anim.reinit()
print OK
def crans_vers_ext(self) :
""" Reconstruit la chaîne CRANS_VERS_EXT """
self.build_chaine_adherent('CRANS_VERS_EXT',self.__crans_vers_ext)
def ext_vers_crans(self) :
""" Reconstruit la chaîne EXT_VERS_CRANS """
self.build_chaine_adherent('EXT_VERS_CRANS',self.__ext_vers_crans)
def admin_vlan(self) :
""" Reconstruit la chaîne ADMIN_VLAN """
iptables("-F ADMIN_VLAN")
nounou_machines = []
for adherent in db.search('droits=Nounou')['adherent'] :
for machine in adherent.machines() :
nounou_machines.append(machine.ip())
iptables("-A ADMIN_VLAN -j REJECT")
self.anim = anim('\tChaîne ADMIN_VLAN', len(nounou_machines))
for machine in nounou_machines :
self.anim.cycle()
iptables("-I ADMIN_VLAN -p tcp -s %s --dport ssh -j ACCEPT" % machine)
iptables("-I ADMIN_VLAN -p tcp -s %s --dport https -j ACCEPT" % machine)
self.anim.reinit()
print OK
def __serveurs_vers_ext(self,machine):
ip=machine.ip()
if not AddrInNet(ip,self.zone_serveur):
# C'est une machine adhérent, rien à faire ici
return
mac = machine.mac()
ports = { 'tcp' : machine.portTCPout(),
'udp' : machine.portUDPout() }
for proto in [ 'tcp', 'udp' ] :
for port in ports[proto]:
iptables("-I SERVEURS_VERS_EXT -s %s -p %s --dport %s -m mac --mac-source %s -j ACCEPT" \
%(ip,proto,port,mac))
def __ext_vers_serveurs(self,machine):
ip=machine.ip()
if not AddrInNet(ip,self.zone_serveur):
# C'est une machine adhérent, rien à faire ici
return
ports = { 'tcp' : machine.portTCPin(),
'udp' : machine.portUDPin() }
for proto in [ 'tcp', 'udp' ] :
for port in ports[proto]:
iptables("-I EXT_VERS_SERVEURS -d %s -p %s --dport %s -j ACCEPT"\
%(ip,proto,port))
def __crans_vers_ext(self,machine):
ip=machine.ip()
if AddrInNet(ip,self.zone_serveur):
# C'est un serveur, rien à faire ici
return
ports = { 'tcp' : machine.portTCPout(),
'udp' : machine.portUDPout() }
for proto in [ 'tcp', 'udp' ] :
for port in ports[proto]:
iptables("-I CRANS_VERS_EXT -s %s -p %s --dport %s -j ACCEPT" \
%(ip,proto,port))
def __ext_vers_crans(self,machine):
ip=machine.ip()
if AddrInNet(ip,self.zone_serveur):
# C'est un serveur, rien à faire ici
return
ports = { 'tcp' : machine.portTCPin(),
'udp' : machine.portUDPin() }
for proto in [ 'tcp', 'udp' ] :
for port in ports[proto]:
iptables("-I EXT_VERS_CRANS -d %s -p %s --dport %s -j ACCEPT" \
%(ip,proto,port))
def blacklist(self):
""" Construit les chaînes de blackliste (BLACKLIST_{DST,SRC}) """
iptables('-F BLACKLIST_DST')
iptables('-F BLACKLIST_SRC')
# Peut-être à mettre dans config.py ?
blacklist_sanctions = ('upload', 'warez', 'p2p', 'autodisc_p2p', 'autodisc_upload', 'bloq')
blacklist = []
# Recherche sur le champ ablacklist (clubs compris)
search = db.search('ablacklist=*&paiement=%s' % ann_scol)
self.anim = anim("\tBlackliste adhérents+clubs", len(search['adherent']+search['club']))
for entite in search['adherent'] + search['club']:
self.anim.cycle()
sanctions = entite.blacklist_actif()
for s in blacklist_sanctions:
if s in sanctions:
blacklist.extend(entite.machines())
break
self.anim.reinit()
print OK
# Recherche sur le champ mblacklist
search = db.search('mblacklist=*&paiement=%s' % ann_scol)
self.anim = anim("\tBlackliste machines", len(search['machine']))
for entite in search['machine']:
self.anim.cycle()
sanctions = entite.blacklist_actif()
for s in blacklist_sanctions:
if s in sanctions:
blacklist.append(entite)
break
self.anim.reinit()
print OK
self.anim = anim("\tChaînes BLACKLISTE", len(blacklist))
for machine in blacklist:
self.anim.cycle()
iptables("-A BLACKLIST_DST -d %s -j REJECT --reject-with icmp-host-prohibited" % machine.ip())
iptables("-A BLACKLIST_SRC -s %s -j REJECT --reject-with icmp-host-prohibited" % machine.ip())
self.anim.reinit()
print OK
def filtre_p2p(self):
""" Construit la chaînes de filtrage du p2p (FILTRE_P2P) """
self.anim = anim("\tFiltrage p2p")
iptables('-F FILTRE_P2P')
# On ne filtre que ce qui passe sur l'interface externe
iptables('-A FILTRE_P2P -i %s -o %s -j RETURN' % (self.eth_int, self.eth_int) )
for filtre in self.filtres_p2p :
iptables('-A FILTRE_P2P -m ipp2p --%s -j LOG --log-prefix "IPP2P=%s "' % (filtre[0],
filtre[1]))
#iptables('-A FILTRE_P2P -m ipp2p --%s -j REJECT --reject-with icmp-admin-prohibited' % filtre[0])
iptables('-A FILTRE_P2P -m ipp2p --%s -j RETURN' % filtre[0])
self.anim.cycle()
self.anim.reinit()
print OK
def serveurs_maj(self) :
self.exception_catcher(self.serveurs_vers_ext)
"""
Zamok
"""
class firewall_zamok(firewall_crans) :
"""
Structure du firewall :
table nat :
SERV_OUT_ADM
TEST_MAC-IP
table filter :
FORWARD (policy par défaut : DROP)
rien ne passe pas la chaîne FORWARD
INPUT (policy par défaut : ACCEPT)
"""
# interfaces physiques
eth_pub = "crans"
eth_adm = "crans.2"
def serv_out_adm(self) :
self.anim = anim('\tOutput vers VLAN adm', len(self.adm_users))
# Supression des éventuelles règles
iptables("-t filter -F SERV_OUT_ADM")
for user in self.adm_users :
self.anim.cycle()
try:
iptables("-A SERV_OUT_ADM -m owner --uid-owner %d -j ACCEPT" % pwd.getpwnam(user)[2])
except KeyError:
continue
# LDAP et DNS toujours joignable
iptables("-A SERV_OUT_ADM -p tcp --dport ldap -j ACCEPT")
iptables("-A SERV_OUT_ADM -p tcp --dport domain -j ACCEPT")
iptables("-A SERV_OUT_ADM -p udp --dport domain -j ACCEPT")
# Pour le nfs (le paquet à laisser passer n'a pas d'owner)
iptables("-A SERV_OUT_ADM -d nfs.adm.crans.org -m owner ! --uid-owner 0 -j REJECT --reject-with icmp-net-prohibited")
iptables("-A SERV_OUT_ADM -d nfs.adm.crans.org -j ACCEPT")
# Rien d'autre ne passe
iptables("-A SERV_OUT_ADM -j REJECT --reject-with icmp-net-prohibited")
self.anim.reinit()
print OK
def nat_table(self) :
self.anim = anim('\tStructure de la table nat')
iptables('-t nat -N TEST_MAC-IP')
iptables('-t filter -N SERV_OUT_ADM')
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")
for net in NETs['fil'] + NETs['vlan-adm'] + NETs['wifi'] :
iptables("-t nat -A PREROUTING -s %s -j TEST_MAC-IP" % net)
iptables("-t filter -A OUTPUT -o lo -j ACCEPT")
# pour une connection entrante venant du VLAN adm, il faut que le ACK
# puisse passer (interaction avec pam, uid de l'utilisateur en OUTPUT)
# on accepte donc les paquets si la connection est
# en RELATED et ESTABLISHED
iptables("-t filter -A OUTPUT -m state --state RELATED,ESTABLISHED -j ACCEPT")
iptables("-t filter -A OUTPUT -o %s -j SERV_OUT_ADM" % self.eth_adm)
iptables("-t nat -P PREROUTING ACCEPT")
iptables("-t filter -P OUTPUT ACCEPT")
print OK
def start_fw_funcs(self) :
self.exception_catcher(self.test_mac_ip)
self.serv_out_adm()
def filter_table_tweaks(self) :
self.anim = anim('\tRègles spécifiques à zamok')
iptables("-P INPUT ACCEPT")
iptables("-P FORWARD DROP")
print OK
"""
Rouge
"""
class firewall_rouge(firewall_crans) :
"""
Structure du firewall :
table nat :
SERV_OUT_ADM
TEST_MAC-IP
table filter :
FORWARD (policy par défaut : DROP)
rien ne passe pas la chaîne FORWARD
INPUT (policy par défaut : ACCEPT)
"""
# interfaces physiques
eth_pub = "eth0"
eth_adm = "eth0.2"
def nat_table(self) :
self.anim = anim('\tStructure de la table nat')
iptables('-t nat -N TEST_MAC-IP')
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")
for net in NETs['fil'] + NETs['vlan-adm'] + NETs['wifi'] :
iptables("-t nat -A PREROUTING -s %s -j TEST_MAC-IP" % net)
iptables("-t nat -P PREROUTING ACCEPT")
iptables("-t nat -P OUTPUT ACCEPT")
print OK
def filter_table_tweaks(self) :
self.anim = anim('\tRègles spécifiques à rouge')
iptables("-P INPUT ACCEPT")
iptables("-P FORWARD DROP")
print OK
"""
Vert
"""
class firewall_vert(firewall_crans) :
"""
Structure du firewall :
table nat :
MAC-IP
table filter :
FORWARD (policy par défaut : DROP)
rien ne passe pas la chaîne FORWARD
INPUT (policy par défaut : ACCEPT)
"""
# interfaces physiques
eth_crans = "crans"
def nat_table(self) :
self.anim = anim('\tStructure de la table nat')
iptables('-t nat -N TEST_MAC-IP')
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")
for net in NETs['fil'] + NETs['vlan-adm'] + NETs['wifi'] :
iptables("-t nat -A PREROUTING -s %s -j TEST_MAC-IP" % net)
iptables("-t nat -P PREROUTING ACCEPT")
iptables("-t nat -P OUTPUT ACCEPT")
print OK
def filter_table_tweaks(self) :
self.anim = anim('\tRègles spécifiques à vert')
iptables("-P INPUT ACCEPT")
iptables("-P FORWARD DROP")
print OK
class firewall_sila(firewall_rouge):
"""Comme pour rouge, avec le proxy transparent en plus"""
def mangle_table(self):
# Pour le proxy transparent
iptables("-t mangle -F PREROUTING")
iptables("-t mangle -i crans.2 -A PREROUTING -p tcp --destination-port 3128 " +
"--destination 138.231.144.10 " +
"-m mac --mac-source %s " % mac_komaz +
"-j MARK --set-mark %s" % conf_fw.mark['proxy'])
iptables("-t mangle -A PREROUTING -m mark --mark %s -j ACCEPT" % conf_fw.mark['proxy'])
def nat_table(self):
firewall_rouge.nat_table(self)
# Pour le proxy transparent
iptables("-t nat -I PREROUTING -i crans.2 -m mark --mark %s -j ACCEPT" % conf_fw.mark['proxy'])
firewall_bleu = firewall_zamok
if __name__ == '__main__' :
# Chaînes pouvant être recontruites
fw = eval('firewall_%s()' % hostname)
chaines = []
for nom in dir(fw) :
if nom in [ 'log_chaines' , 'test_virus_flood', 'reseaux_non_routables', 'test_mac_ip' , 'blacklist' , 'ext_vers_serveurs' , 'serveurs_vers_ext', 'ext_vers_crans', 'crans_vers_ext' , 'filtre_p2p', 'admin_vlan' , 'serv_out_adm', 'mangle_table', 'classes_p2p_maj' ] :
chaines.append(nom)
def __usage(txt=None) :
if txt!=None : cprint(txt,'gras')
print """Usage:
%(p)s start : Construction du firewall.
%(p)s restart : Reconstruction du firewall.
%(p)s stop : Arrêt du firewall.
%(p)s <noms de chaînes> : reconstruit les chaînes
Les chaînes pouvant être reconstruites sont :
%(chaines)s
Pour reconfiguration d'IPs particulières, utiliser generate. """ % \
{ 'p' : sys.argv[0].split('/')[-1] , 'chaines' : '\n '.join(chaines) }
sys.exit(-1)
# Bons arguments ?
if len(sys.argv) == 1 :
__usage()
for arg in sys.argv[1:] :
if arg in [ 'stop', 'restart', 'start' ] and len(sys.argv) != 2 :
__usage("L'argument %s ne peut être employé que seul." % arg)
if arg not in [ 'stop', 'restart', 'start' ] + chaines :
__usage("L'argument %s est inconnu." % arg)
for arg in sys.argv[1:] :
eval('fw.%s()' % arg )