#!/usr/bin/env 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 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('\tRestoration 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('\tRestoration 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