272 lines
10 KiB
Python
272 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import sys
|
|
import netaddr
|
|
|
|
if '/usr/scripts' not in sys.path:
|
|
sys.path.append('/usr/scripts')
|
|
|
|
import syslog
|
|
import subprocess
|
|
|
|
from gestion.affich_tools import anim, OK, cprint
|
|
from gestion.iptools import AddrInNet, NetSubnets, IpSubnet, NetInNets
|
|
|
|
import lc_ldap.shortcuts
|
|
import lc_ldap.objets
|
|
import lc_ldap.attributs
|
|
|
|
squeeze = os.uname()[2] < '3'
|
|
|
|
#: Chaines par défaut d'iptables
|
|
default_chains = ['INPUT', 'OUTPUT', 'FORWARD', 'PREROUTING', 'POSTROUTING']
|
|
#: Tables d'iptables
|
|
tables = ['raw', 'mangle', 'filter', 'nat']
|
|
|
|
|
|
def pretty_print(table, chain):
|
|
"""Affiche quelle chaine est en train d'être construite dans quelle table de NetFilter"""
|
|
anim('\t%s dans %s' % (chain, table))
|
|
|
|
|
|
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 tc(cmd, block=True):
|
|
""" Interface de tc """
|
|
params = ['/sbin/tc']
|
|
params.extend(cmd.split())
|
|
p = subprocess.Popen(params , stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
if block:
|
|
status = p.wait()
|
|
stdoutdata, stderrdata = p.communicate()
|
|
if status:
|
|
raise TcError(' '.join(params), status, stdoutdata + stderrdata)
|
|
return stdoutdata + stderrdata
|
|
|
|
class firewall_tools(object) :
|
|
"""Classe de base du pare-feu implémentant l'association mac-ip (pour les machines filaires) et les blacklists hard"""
|
|
|
|
def machines(self):
|
|
"""Renvois la liste de toutes les machines"""
|
|
if self._machines:
|
|
return self._machines
|
|
# On utilise allMachinesAdherents car on a besoin que
|
|
# les machine.proprio() soit déjà peuplés. En effet, on regarde
|
|
# les blacklistes d'un proprio lorsque l'on regarde les blacklistes
|
|
# d'une machine
|
|
anim('\tChargement des machines')
|
|
self._machines, self._adherents = self.conn.allMachinesAdherents()
|
|
self._adherents = [ adh for adh in self._adherents if adh.paiement_ok() ]
|
|
print OK
|
|
return self._machines
|
|
|
|
def adherents(self):
|
|
"""
|
|
Renvois la liste de tous les adhérents à jour de paiement
|
|
(car on suppose que la blackliste paiement est hard)
|
|
"""
|
|
if self._adherents:
|
|
return self._adherents
|
|
self._machines, self._adherents = self.conn.allMachinesAdherents()
|
|
self._adherents = [ adh for adh in self._adherents if adh.paiement_ok() ]
|
|
return self._adherents
|
|
|
|
def blacklisted_machines(self):
|
|
"""Renvois la liste de toutes les machines ayant une blackliste actives"""
|
|
if self._blacklisted_machines:
|
|
return self._blacklisted_machines
|
|
self._blacklisted_machines = [ machine for machine in self.machines() if machine.blacklist_actif() ]
|
|
return self._blacklisted_machines
|
|
|
|
def blacklisted_ips(self, blacklist_sanctions=None, nets=None):
|
|
"""Renvois l'ensemble des ips des machines ayant une blacklist dans blacklist_sanctions et étant dans nets si spécifié"""
|
|
bl_ips = set()
|
|
for machine in self.blacklisted_machines():
|
|
if blacklist_sanctions is None or set(bl['type'] for bl in machine.blacklist_actif()).intersection(blacklist_sanctions):
|
|
for ip in machine['ipHostNumber']:
|
|
if nets is None:
|
|
bl_ips.add(str(ip))
|
|
else:
|
|
for net in nets:
|
|
if ip.value in netaddr.IPNetwork(net):
|
|
bl_ips.add(str(ip))
|
|
return bl_ips
|
|
|
|
def blacklisted_adherents(self, excepts=[]):
|
|
"""Renvois la liste de tous les adhérents ayant une blackliste active en ignorant les blacklist de excepts"""
|
|
if self._blacklisted_adherents and self._blacklisted_adherents_type == set(excepts):
|
|
return self._blacklisted_adherents
|
|
self._blacklisted_adherents = filter(lambda adh: adh.blacklist_actif(excepts), self.adherents())
|
|
self._blacklisted_adherents_type = set(excepts)
|
|
return self._blacklisted_adherents
|
|
|
|
def add(self, table, chain, rule):
|
|
"""Ajoute la règle ``rule`` à la chaine ``chain`` dans la table ``table``"""
|
|
if not chain in self.chain_list[table]:
|
|
self.chain_list[table].append(chain)
|
|
self.rules_list[table][chain]=[]
|
|
self.rules_list[table][chain].append(rule)
|
|
|
|
def delete(self, table=None, chain=None):
|
|
"""Supprime ``chain`` de ``table`` si fournis, sinon supprime ``table``, sinon toutes les tables"""
|
|
if not table:
|
|
for table in tables:
|
|
self.delete(table, chain)
|
|
if not chain:
|
|
for chain in self.chain_list[table]:
|
|
self.delete(table, chain)
|
|
|
|
if not chain in self.chain_list[table]:
|
|
return
|
|
if not chain in default_chains:
|
|
self.chain_list[table].remove(chain)
|
|
del(self.rules_list[table][chain])
|
|
else:
|
|
self.rules_list[table][chain]=[]
|
|
|
|
def flush(self, table=None, chain=None):
|
|
"""Vide ``chain`` dans ``table`` si fournis, sinon vide toutes
|
|
les chaines de ``table``, sinon vide toutes les chaines de toutes les tables"""
|
|
if not table:
|
|
for table in tables:
|
|
self.flush(table, chain)
|
|
if not chain:
|
|
for chain in self.chain_list[table]:
|
|
self.flush(table, chain)
|
|
if not chain in self.chain_list[table]:
|
|
self.chain_list[table].append(chain)
|
|
self.rules_list[table][chain]=[]
|
|
|
|
def restore(self, table=None, chains=[], noflush=False):
|
|
"""Restores les règles du pare-feu dans le noyau en appelant ``iptables-restore``.
|
|
Si ``table`` est fournis, on ne restore que table.
|
|
Si ``noflush`` n'est pas à ``True`` tout le contenu précédent est supprimé"""
|
|
str=self.format(chains)
|
|
f=open('/tmp/ipt_rules', 'w')
|
|
f.write(str)
|
|
f.close()
|
|
params = ['/sbin/iptables-restore']
|
|
if noflush:
|
|
params.append('--noflush')
|
|
if table and table in ['raw', 'mangle', 'filter', 'nat']:
|
|
params.append('--table')
|
|
params.append(table)
|
|
p = subprocess.Popen(params , stdin=subprocess.PIPE)
|
|
p.communicate(input=str)
|
|
|
|
def apply(self, table, chain):
|
|
"""Applique les règles de ``chain`` dans ``table``"""
|
|
if not chain in self.chain_list[table]:
|
|
return
|
|
self.restore(table, [chain], noflush=True)
|
|
self.delete(table, chain)
|
|
|
|
def format(self, chains=[]):
|
|
"""Transforme la structure interne des règles du pare-feu en celle comprise par ``iptables-restore``"""
|
|
str = ''
|
|
for table in self.chain_list.keys():
|
|
str += '*%s\n' % table
|
|
for chain in self.chain_list[table]:
|
|
if not chains or chain in chains or chain in default_chains:
|
|
str += ':%s %s [0:0]\n' % (chain, chain in default_chains and 'ACCEPT' or '-')
|
|
for chain in self.chain_list[table]:
|
|
if not chains or chain in chains :
|
|
for rule in self.rules_list[table][chain]:
|
|
str += '-A %s %s\n' % (chain, rule)
|
|
str += 'COMMIT\n'
|
|
return str
|
|
|
|
def reload(self, func_name):
|
|
if squeeze and self.reloadable[func_name] in self.use_ipset:
|
|
anim('\tVidage de %s' % self.reloadable[func_name]())
|
|
for table in ['raw', 'mangle', 'filter', 'nat']:
|
|
self.flush(table, self.reloadable[func_name]())
|
|
self.restore(noflush=True)
|
|
print OK
|
|
|
|
for table in ['raw', 'mangle', 'filter', 'nat']:
|
|
self.reloadable[func_name](table)
|
|
if self.reloadable[func_name] in self.use_ipset:
|
|
self.reloadable[func_name](fill_ipset=True)
|
|
if self.reloadable[func_name] in self.use_tc:
|
|
self.reloadable[func_name](run_tc=True)
|
|
|
|
anim('\tRestauration d\'iptables')
|
|
self.restore(noflush=True)
|
|
print OK
|
|
|
|
def __init__(self):
|
|
#initialisation des structures communes : récupération des ipset
|
|
if os.getuid() != 0:
|
|
from affich_tools import coul
|
|
sys.stderr.write(coul("Il faut être root pour utiliser le firewall\n", 'gras'))
|
|
sys.exit(1)
|
|
|
|
# Connection à la base ldap
|
|
self.conn = lc_ldap.shortcuts.lc_ldap_admin(user=u'firewall')
|
|
|
|
self.reloadable = {}
|
|
|
|
self.use_ipset = []
|
|
self.use_tc = []
|
|
|
|
self._machines = None
|
|
self._adherents = None
|
|
self._blacklisted_machines = None
|
|
self._blacklisted_adherents = None
|
|
|
|
self.chain_list={
|
|
'raw':['OUTPUT', 'PREROUTING'],
|
|
'mangle':['INPUT', 'OUTPUT', 'FORWARD', 'PREROUTING', 'POSTROUTING'],
|
|
'filter':['INPUT', 'OUTPUT', 'FORWARD'],
|
|
'nat':['OUTPUT', 'PREROUTING', 'POSTROUTING']
|
|
}
|
|
self.rules_list = {
|
|
'raw': { 'OUTPUT':[], 'PREROUTING':[] },
|
|
'mangle':{'INPUT':[], 'OUTPUT':[], 'FORWARD':[], 'PREROUTING':[], 'POSTROUTING':[]},
|
|
'filter':{'INPUT':[], 'OUTPUT':[], 'FORWARD':[]},
|
|
'nat':{'OUTPUT':[], 'PREROUTING':[], 'POSTROUTING':[]}
|
|
}
|
|
|
|
self.ipset={}
|
|
|
|
|
|
|
|
def start(self):
|
|
"""Démarre le pare-feu : génère les règles, puis les restore"""
|
|
self.machines()
|
|
|
|
if squeeze:
|
|
anim('\tVidage du pare-feu')
|
|
self.restore()
|
|
print OK
|
|
|
|
self.raw_table()
|
|
self.mangle_table()
|
|
self.filter_table()
|
|
self.nat_table()
|
|
|
|
anim('\tRestauration d\'iptables')
|
|
self.restore()
|
|
print OK
|
|
return
|
|
|
|
def stop(self):
|
|
"""Vide les règles du pare-feu"""
|
|
self.delete()
|
|
self.restore()
|
|
return
|
|
|
|
def restart(self):
|
|
"""Alias de :py:func:`start`"""
|
|
self.start()
|
|
return
|
|
|