From 2d2cbf2d9f56f88f44ab5e9f4e020eefd21c8e29 Mon Sep 17 00:00:00 2001 From: Valentin Samir Date: Fri, 8 Nov 2013 20:00:10 +0100 Subject: [PATCH] =?UTF-8?q?[firewall4]=20S=C3=A9paration=20en=20plusieurs?= =?UTF-8?q?=20fichiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit En gros, un par pare feu --- gestion/gen_confs/firewall4.py | 1268 ---------------------- gestion/gen_confs/firewall4/__init__.py | 4 + gestion/gen_confs/firewall4/base.py | 233 ++++ gestion/gen_confs/firewall4/firewall4.py | 75 ++ gestion/gen_confs/firewall4/komaz.py | 555 ++++++++++ gestion/gen_confs/firewall4/routeur.py | 108 ++ gestion/gen_confs/firewall4/utils.py | 251 +++++ gestion/gen_confs/firewall4/zamok.py | 109 ++ 8 files changed, 1335 insertions(+), 1268 deletions(-) delete mode 100755 gestion/gen_confs/firewall4.py create mode 100644 gestion/gen_confs/firewall4/__init__.py create mode 100644 gestion/gen_confs/firewall4/base.py create mode 100755 gestion/gen_confs/firewall4/firewall4.py create mode 100644 gestion/gen_confs/firewall4/komaz.py create mode 100644 gestion/gen_confs/firewall4/routeur.py create mode 100644 gestion/gen_confs/firewall4/utils.py create mode 100644 gestion/gen_confs/firewall4/zamok.py diff --git a/gestion/gen_confs/firewall4.py b/gestion/gen_confs/firewall4.py deleted file mode 100755 index 2de94f31..00000000 --- a/gestion/gen_confs/firewall4.py +++ /dev/null @@ -1,1268 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - - -import os -import sys -sys.path.append('/usr/scripts/gestion') -sys.path.append('/usr/scripts/') - -from config import NETs, blacklist_sanctions, blacklist_sanctions_soft, blacklist_bridage_upload, mac_komaz, mac_titanic, adm_users, accueil_route - -import pwd -import config.firewall -import lc_ldap.shortcuts -import lc_ldap.objets -import lc_ldap.attributs -import socket -from ipset import IpsetError, Ipset -from iptools import AddrInNet, NetSubnets, IpSubnet, NetInNets -import netaddr -import subprocess -import syslog -from affich_tools import anim, OK, cprint - -squeeze = os.uname()[2] < '3' - -#: Nom de la machine exécutant le script -hostname = socket.gethostname() - -#: Chaines par défaut d'iptables -default_chains = ['INPUT', 'OUTPUT', 'FORWARD', 'PREROUTING', 'POSTROUTING'] -#: Tables d'iptables -tables = ['raw', 'mangle', 'filter', 'nat'] - -#: Association des interfaces de ``hostname`` -dev = hostname in config.firewall.dev.keys() and config.firewall.dev[hostname] or {} - -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_base(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 - self._machines, self._adherents = self.conn.allMachinesAdherents() - return self._machines - - def adherents(self): - """Renvois la liste de tous les adhérents""" - 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_adherents(self, excepts=[]): - """Renvois la liste de tous les adhérents ayant une blackliste active""" - 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 = { - 'blacklist_hard' : self.blacklist_hard, - 'test_mac_ip' : self.test_mac_ip, - } - - self.use_ipset = [self.blacklist_hard, self.test_mac_ip] - 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={} - - self.ipset['mac_ip']={ - 'adh' : Ipset("MAC-IP-ADH","macipmap","--from 138.231.136.0 --to 138.231.151.255"), - 'adm' : Ipset("MAC-IP-ADM","macipmap","--from 10.231.136.0 --to 10.231.136.255"), - 'app' : Ipset("MAC-IP-APP","macipmap","--from 10.2.9.0 --to 10.2.9.255"), - } - - self.ipset['blacklist']={ - 'hard' : Ipset("BLACKLIST-HARD","ipmap","--from 138.231.136.0 --to 138.231.151.255"), - } - - - - def start(self): - """Démarre le pare-feu : génère les règles, puis les restore""" - anim('\tChargement des machines') - self.machines() - self.blacklisted_machines() - print OK - - 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 - - def blacklist_maj(self, ips): - """Met à jours les blacklists pour les ip présentent dans la liste ``ips``""" - self.blacklist_hard_maj(ips) - - def raw_table(self): - """Génère les règles pour la table ``raw`` et remplis les chaines de la table""" - table = 'raw' - return - - def mangle_table(self): - """Génère les règles pour la table ``mangle`` et remplis les chaines de la table""" - table = 'mangle' - return - - def filter_table(self): - """Génère les règles pour la table ``filter`` et remplis les chaines de la table""" - table = 'filter' - - mac_ip_chain = self.test_mac_ip(table, fill_ipset=True) - blacklist_hard_chain = self.blacklist_hard(table, fill_ipset=True) - - chain = 'INPUT' - self.add(table, chain, '-i lo -j ACCEPT') - self.add(table, chain, '-p icmp -j ACCEPT') - self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') - for net in NETs['all'] + NETs['adm'] + NETs['personnel-ens']: - self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) - self.add(table, chain, '-j %s' % blacklist_hard_chain) - - chain = 'FORWARD' - self.add(table, chain, '-j REJECT') - - return - - def nat_table(self): - """Génère les règles pour la table ``nat`` et remplis les chaines de la table""" - table = 'nat' - return - - - - - def blacklist_hard_maj(self, ip_list): - """Met à jour les blacklists hard, est appelée par :py:func:`blacklist_maj`""" - self.blacklist_hard(fill_ipset=True) -# for ip in ip_list: -# machine = self.conn.search(u"ipHostNumber=%s" % ip) -# # Est-ce qu'il y a des blacklists hard parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_sanctions): -# try: self.ipset['blacklist']['hard'].add(ip) -# except IpsetError: pass -# else: -# try: self.ipset['blacklist']['hard'].delete(ip) -# except IpsetError: pass - - def blacklist_hard(self, table=None, fill_ipset=False, apply=False): - """Génère la chaine ``BLACKLIST_HARD``. - Si ``fill_ipset`` est à ``True``, remplis l'ipset ``BLACKLIST-HARD``. - Si ``apply`` est à True, applique directement les règles""" - chain = 'BLACKLIST_HARD' - - if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard']) - # On récupère la liste de toutes les ips blacklistés hard - bl_hard_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in netaddr.IPNetwork(n) for n in config.NETs['all'] for ip in machine['ipHostNumber'])) - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(blacklist_sanctions) - ] - for ip in ips - ) - - self.ipset['blacklist']['hard'].restore(bl_hard_ips) - print OK - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-m set --match-set %s src -j REJECT' % self.ipset['blacklist']['hard'] ) - self.add(table, chain, '-m set --match-set %s dst -j REJECT' % self.ipset['blacklist']['hard'] ) - print OK - - if apply: - self.apply(table, chain) - return chain - - def test_mac_ip_dispatch(self, func, machine): - """Détermine à quel set de mac-ip appliquer la fonction ``func`` (add, delete, append, ...)""" - ips = machine['ipHostNumber'] - for ip in ips: - # Si la machines est sur le réseau des adhérents - if AddrInNet(str(ip), NETs['wifi']): - # Les machines wifi sont vues à travers komaz - func('adh', "%s,%s" % (ip, mac_komaz)) - elif AddrInNet(str(ip), NETs['fil']): - func('adh', "%s,%s" % (ip, machine['macAddress'][0])) - # Si la machine est sur le réseau admin - elif AddrInNet(str(ip), NETs['adm']): - func('adm', "%s,%s" % (ip, machine['macAddress'][0])) - - def test_mac_ip(self, table=None, fill_ipset=False, apply=False): - """Génère la chaine ``TEST_MAC-IP``. - Si ``fill_ipset`` est à ``True``, remplis les ipsets ``MAC-IP-ADH``, ``MAC-IP-ADM``, ``MAC-IP-ADM``. - Si ``apply`` est à True, applique directement les règles""" - chain = 'TEST_MAC-IP' - - if fill_ipset: - anim('\tRestoration des ipsets %s' % ', '.join(self.ipset['mac_ip'].keys())) - rules={ - 'adh':[], - 'adm':[], - 'app':[], - } - for machine in self.machines(): - self.test_mac_ip_dispatch(lambda set, data: rules[set].append(data), machine) - - for set,rules in rules.items(): - self.ipset['mac_ip'][set].restore(rules) - print OK - - if table == 'filter': - pretty_print(table, chain) - - for key in ['accueil', 'isolement', ]: - for net in NETs[key]: - self.add(table, chain, '-s %s -j RETURN' % net) - for key in self.ipset['mac_ip'].keys(): - self.add(table, chain, '-m set --match-set %s src,src -j RETURN' % self.ipset['mac_ip'][key]) - - # Proxy ARP de Komaz et Titanic pour OVH - ip_ovh = self.conn.search(u"host=ovh.adm.crans.org")[0]['ipHostNumber'][0] - self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, mac_komaz)) - self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, mac_titanic)) - - self.add(table, chain, '-j REJECT') - print OK - - if apply: - self.apply(table, chain) - return chain - - def mac_ip_maj(self, ip_list): - """Met à jour la correspondance mac-ip""" - anim('\tActualisation de la correspondance mac-ipv4') - for ip in ip_list: - machine = self.conn.search(u"ipHostNumber=%s" % ip) - if machine: - try: self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].delete(data.split(',',1)[0]), {'ipHostNumber' : [ip], 'macAddress':[''] }) - except IpsetError: pass - self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].add(data), machine[0]) - else: - try: self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].delete(data.split(',',1)[0]), {'ipHostNumber' : [ip], 'macAddress':[''] }) - except IpsetError: pass - print OK - -class firewall_base_routeur(firewall_base): - """Associe mac-ip pour les machines voyant plusieurs réseaux (wifi, filaire, personnel, ...)""" - def test_mac_ip_dispatch(self, func, machine): - """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" - ips = machine['ipHostNumber'] - for ip in ips: - # Si la machines est sur le réseau des adhérents - if AddrInNet(str(ip), NETs['wifi']): - func('adh', "%s,%s" % (ip, machine['macAddress'][0])) - elif AddrInNet(str(ip), NETs['fil']): - func('adh', "%s,%s" % (ip, machine['macAddress'][0])) - # Si la machine est sur le réseau admin - elif AddrInNet(str(ip), NETs['adm']): - func('adm', "%s,%s" % (ip, machine['macAddress'][0])) - # Si la machine est sur le réseaux des appartements de l'ENS - elif AddrInNet(str(ip), NETs['personnel-ens']): - func('app', "%s,%s" % (ip, machine['macAddress'][0])) - -class firewall_base_wifionly(firewall_base): - """Associe mac-ip pour les machines wifi only : les machines filaires sont vues avec la mac de komaz""" - def test_mac_ip_dispatch(self, func, machine): - """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" - ips = machine['ipHostNumber'] - for ip in ips: - # Si la machines est sur le réseau des adhérents - if AddrInNet(str(ip), NETs['wifi']): - func('adh', "%s,%s" % (ip, machine['macAddress'][0])) - elif AddrInNet(str(ip), NETs['fil']): - func('adh', "%s,%s" % (ip, mac_komaz)) - # Si la machine est sur le réseau admin - elif AddrInNet(str(ip), NETs['adm']): - func('adm', "%s,%s" % (ip, machine['macAddress'][0])) - -class firewall_komaz(firewall_base_routeur): - - def __init__(self): - super(self.__class__, self).__init__() - - self.reloadable.update({ - 'log_all' : self.log_all, - 'admin_vlan' : self.admin_vlan, - 'clamp_mss' : self.clamp_mss, - 'ingress_filtering' : self.ingress_filtering, - 'ssh_on_https' : self.ssh_on_https, - 'connexion_secours' : self.connexion_secours, - 'connexion_appartement' : self.connexion_appartement, - 'blacklist_soft' : self.blacklist_soft, - 'blacklist_upload' : self.blacklist_upload, - 'reseaux_non_routable' : self.reseaux_non_routable, - 'filtrage_ports' : self.filtrage_ports, - 'limitation_debit' : self.limitation_debit, - 'limit_ssh_connexion' : self.limit_ssh_connexion, - 'tunnel_6in4' : self.tunnel_6in4, - }) - - self.use_ipset.extend([self.blacklist_soft, self.blacklist_upload, self.reseaux_non_routable]) - self.use_tc.extend([self.limitation_debit]) - - self.ipset['reseaux_non_routable'] = { - 'deny' : Ipset("RESEAUX-NON-ROUTABLE-DENY","nethash"), - 'allow' : Ipset("RESEAUX-NON-ROUTABLE-ALLOW","nethash"), - } - - self.ipset['blacklist'].update({ - 'soft' : Ipset("BLACKLIST-SOFT","ipmap","--from 138.231.136.0 --to 138.231.151.255"), - 'upload' : Ipset("BLACKLIST-UPLOAD","ipmap","--from 138.231.136.0 --to 138.231.151.255"), - }) - - def blacklist_maj(self, ips): - self.blacklist_hard_maj(ips) - self.blacklist_soft_maj(ips) - self.blacklist_upload_maj(ips) - - def raw_table(self): - """Génère les règles pour la table ``raw`` et remplis les chaines de la table""" - table = 'raw' - - chain = 'PREROUTING' - self.add(table, chain, '-d 225.0.0.50 -j DROP') - return - - - def mangle_table(self): - table = 'mangle' - super(self.__class__, self).mangle_table() - - chain = 'PREROUTING' - self.add(table, chain, '-j %s' % self.log_all(table)) - self.add(table, chain, '-j %s' % self.connexion_secours(table)) - self.add(table, chain, '-p tcp -j CONNMARK --restore-mark') - - chain = 'POSTROUTING' - self.add(table, chain, '-j %s' % self.clamp_mss(table)) - self.add(table,chain, '-j %s' % self.limitation_debit(table, run_tc=True)) - self.add(table, chain, '-j %s' % self.blacklist_upload(table, fill_ipset=True)) - return - - def filter_table(self): - table = 'filter' - super(self.__class__, self).filter_table() - - mac_ip_chain = self.test_mac_ip() - blacklist_hard_chain = self.blacklist_hard() - blacklist_soft_chain = self.blacklist_soft(table) - - chain = 'INPUT' - self.flush(table, chain) - self.add(table, chain, '-i lo -j ACCEPT') - self.add(table, chain, '-p icmp -j ACCEPT') - self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') - self.add(table, chain, '-j %s' % blacklist_soft_chain) - for net in NETs['all'] + NETs['adm'] + NETs['personnel-ens']: - self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) - self.add(table, chain, '-j %s' % blacklist_hard_chain) - - chain = 'FORWARD' - self.flush(table, chain) - self.add(table, chain, '-i lo -j ACCEPT') - self.add(table, chain, '-p icmp -j ACCEPT') - self.add(table, chain, '-j %s' % self.admin_vlan(table)) - self.add(table, chain, '-j %s' % blacklist_soft_chain) - self.add(table, chain, '-i %s -j %s' % (dev['out'], blacklist_hard_chain)) - self.add(table, chain, '-o %s -j %s' % (dev['out'], blacklist_hard_chain)) - self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') - self.add(table, chain, '-j %s' % self.tunnel_6in4(table)) - self.add(table, chain, '-j %s' % self.reseaux_non_routable(table, fill_ipset=True)) - for net in NETs['all'] + NETs['adm'] + NETs['personnel-ens']: - self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) - self.add(table, chain, '-j %s' % self.connexion_secours(table)) - self.add(table, chain, '-j %s' % self.connexion_appartement(table)) - self.add(table, chain, '-j %s' % self.ingress_filtering(table)) - self.add(table, chain, '-j %s' % self.limit_ssh_connexion(table)) - self.add(table, chain, '-i %s -j %s' % (dev['out'], self.filtrage_ports(table))) - self.add(table, chain, '-o %s -j %s' % (dev['out'], self.filtrage_ports(table))) - return - - def nat_table(self): - table = 'nat' - super(self.__class__, self).nat_table() - - chain = 'PREROUTING' - self.add(table, chain, '-j %s' % self.ssh_on_https(table)) - self.add(table, chain, '-j %s' % self.connexion_secours(table)) - self.add(table, chain, '-j %s' % self.blacklist_soft(table)) - - chain = 'POSTROUTING' - self.add(table, chain, '-j %s' % self.connexion_appartement(table)) - return - - def tunnel_6in4(self, table=None, apply=False): - chain = 'TUNNEL_IPV6' - - tunnels_ipv6 = [ ('216.66.84.42', '138.231.136.12'), ('216.66.84.42','138.231.136.164') ] - - if table == 'filter': - pretty_print(table, chain) - for sideA, sideB in tunnels_ipv6: - self.add(table, chain, '--proto 41 -s %s -d %s -j ACCEPT' % (sideA, sideB)) - self.add(table, chain, '--proto 41 -s %s -d %s -j ACCEPT' % (sideB, sideA)) - print OK - - if apply: - self.apply(table, chain) - return chain - - def limit_ssh_connexion(self, table=None, apply=False): - chain = 'LIMIT-SSH-CONNEXION' - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --set' % dev['out']) - self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --update --seconds 30 --hitcount 10 --rttl -j DROP' % dev['out']) - print OK - - if apply: - self.apply(table, chain) - return chain - - def test_mac_ip(self, table=None, fill_ipset=False, apply=False): - chain = super(self.__class__, self).test_mac_ip() - - if table == 'filter': - for key in ['out', 'tun-ovh' ]: - self.add(table, chain, '-i %s -j RETURN' % dev[key]) - - return super(self.__class__, self).test_mac_ip(table, fill_ipset, apply) - - - def log_all(self, table=None, apply=False): - chain = 'LOG_ALL' - - if table == 'mangle': - pretty_print(table, chain) - for device in dev.values(): - self.add(table, chain, '-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % device) - print OK - - if apply: - self.apply(table, chain) - return chain - - def admin_vlan(self, table=None, apply=False): - chain = 'VLAN-ADM' - - if table == 'filter': - pretty_print(table, chain) - for net in NETs['adm']: - self.add(table, chain, '-o %s -s %s -j ACCEPT' % (dev['tun-ovh'], net)) - self.add(table, chain, '-i %s -d %s -j ACCEPT' % (dev['tun-ovh'], net)) - self.add(table, chain, '-d %s -j REJECT' % net) - print OK - - if apply: - self.apply(table, chain) - return chain - - def qos(self, table=None, apply=False): - return - - def clamp_mss(self, table=None, apply=False): - """Force la MSS (Max Segment Size) TCP à rentrer dans la MTU (Max Transfert Unit)""" - chain = 'CLAMP-MSS' - if table == 'mangle': - pretty_print(table, chain) - - self.add(table, chain, '-p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu') - print OK - - if apply: - self.apply(table, chain) - return chain - - def ingress_filtering(self, table=None, apply=False): - """Pour ne pas router les paquêtes n'appartenant pas à notre plage ip voulant sortir de notre réseau - et empêcher certain type de spoof (cf http://travaux.ovh.net/?do=details&id=5183)""" - chain = 'INGRESS_FILTERING' - if table == 'filter': - pretty_print(table, chain) - - for net in NETs['all']: - self.add(table, chain, '-o %s -s %s -j RETURN' % (dev['out'], net)) - self.add(table, chain, '-o %s -j LOG --log-prefix BAD_ROUTE' % dev['out']) - self.add(table, chain, '-o %s -j DROP' % dev['out']) - for net_d in NETs['all']: - for net_s in NETs['all']: - self.add(table, chain,'-i %s ! -s %s -d %s -j RETURN' % (dev['out'], net_s, net_d)) - self.add(table, chain,'-i %s -j LOG --log-prefix BAD_SRC' % dev['out']) - self.add(table, chain,'-i %s -j DROP' % dev['out']) - print OK - - if apply: - self.apply(table, chain) - return chain - - def ssh_on_https(self, table=None, apply=False): - """Pour faire fonctionner ssh2.crans.org""" - chain = 'SSH2' - - if table == 'nat': - pretty_print(table, chain) - self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 22 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok - self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 443 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok (pour passer dans un proxy, avec corkscrew) - print OK - - if apply: - self.apply(table, chain) - return chain - - def connexion_secours(self, table=None, apply=False): - """Redirige les paquets vers un proxy lorsqu'on est en connexion de secours""" - chain = 'CONNEXION-SECOURS' - - if table == 'mangle': - pretty_print(table, chain) - self.add(table, chain, '-p tcp -s 138.231.136.0/16 ! -d 138.231.136.0/16 --destination-port 80 -m condition --condition secours -j MARK --set-mark %s' % (config.firewall.mark['secours'])) - self.add(table, chain, '-m mark --mark %s -j ACCEPT' % config.firewall.mark['secours']) - print OK - - if table == 'nat': - pretty_print(table, chain) - self.add(table, chain, '-p tcp -m mark --mark %s -j DNAT --to-destination 10.231.136.4:3129' % config.firewall.mark['secours'] ) - print OK - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-p tcp -s 138.231.136.0/16 ! -d 138.231.136.0/16 --destination-port 443 -m condition --condition secours -j REJECT') - self.add(table, chain, '-m mark --mark %s -j ACCEPT' % config.firewall.mark['secours']) - print OK - - if apply: - self.apply(table, chain) - return chain - - def connexion_appartement(self, table=None, apply=False): - """PNAT les appartements derrière appartement.crans.org""" - chain = 'CONNEXION-APPARTEMENT' - - if table == 'nat': - pretty_print(table, chain) - for dev_key in ['out', 'fil', 'wifi']: - for net in NETs['personnel-ens']: - self.add(table, chain, '-o %s -s %s -j SNAT --to 138.231.136.44' % (dev[dev_key], net)) - print OK - - if table == 'filter': - pretty_print(table, chain) - for net in NETs['personnel-ens']: - self.add(table, chain, '-s %s -j ACCEPT' % net) - self.add(table, chain, '-d %s -j ACCEPT' % net) - print OK - - if apply: - self.apply(table, chain) - return chain - - def blacklist_soft_maj(self, ip_list): - self.blacklist_soft(fill_ipset=True) -# for ip in ip_list: -# machine = self.conn.search(u"ipHostNumber=%s" % ip) -# # Est-ce qu'il y a des blacklists soft parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_sanctions_soft): -# try: self.ipset['blacklist']['soft'].add(ip) -# except IpsetError: pass -# else: -# try: self.ipset['blacklist']['soft'].delete(ip) -# except IpsetError: pass - - def blacklist_soft(self, table=None, fill_ipset=False, apply=False): - """Redirige les gens blacklisté vers le portail captif""" - chain = 'BLACKLIST_SOFT' - - if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['soft']) - # On récupère la liste de toutes les ips blacklistés soft - bl_soft_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in netaddr.IPNetwork(n) for n in config.NETs['all'] for ip in machine['ipHostNumber'])) - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(blacklist_sanctions_soft) - ] - for ip in ips - ) - - self.ipset['blacklist']['soft'].restore(bl_soft_ips) - print OK - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['soft'] ) - self.add(table, chain, '-p tcp --sport 80 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['soft'] ) - self.add(table, chain, '-p tcp -d 10.231.136.4 --dport 3128 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['soft'] ) - self.add(table, chain, '-p tcp -s 10.231.136.4 --sport 3128 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['soft'] ) - print OK - - if table == 'nat': - pretty_print(table, chain) - for net in NETs['all']: - self.add(table, chain, '-d %s -j RETURN' % net) - self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j DNAT --to-destination 10.231.136.4:3128' % self.ipset['blacklist']['soft'] ) - print OK - - if apply: - self.apply(table, chain) - return chain - - def blacklist_upload_maj(self, ip_list): - self.blacklist_upload(fill_ipset=True) -# for ip in ip_list: -# machine = self.conn.search(u"ipHostNumber=%s" % ip) -# # Est-ce qu'il y a des blacklists pour upload parmis les blacklists de la machine -# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_bridage_upload): -# try: self.ipset['blacklist']['upload'].add(ip) -# except IpsetError: pass -# else: -# try: self.ipset['blacklist']['upload'].delete(ip) -# except IpsetError: pass - - def blacklist_upload(self, table=None, fill_ipset=False, apply=False): - """Les gens blacklistés ne sont plus prioritaires (classe de qos commune)""" - chain = 'BLACKLIST_UPLOAD' - - if fill_ipset: - anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['upload']) - # On récupère la liste de toutes les ips blacklistés pour upload - bl_upload_ips = set( - str(ip) for ips in - [ - machine['ipHostNumber'] for machine in self.blacklisted_machines() - if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(blacklist_bridage_upload) - ] - for ip in ips - ) - - self.ipset['blacklist']['upload'].restore(bl_upload_ips) - print OK - - if table == 'mangle': - pretty_print(table, chain) - # Classification pour les blacklists upload - self.add(table, chain, '-o %s -m set --match-set %s src -j CLASSIFY --set-class 1:11' % (dev['out'], self.ipset['blacklist']['upload'])) - - print OK - - if apply: - self.apply(table, chain) - return chain - - def reseaux_non_routable(self, table=None, fill_ipset=False, apply=False): - """Bloque les réseaux non routables autres que ceux utilisés par le crans""" - chain = 'RESEAUX_NON_ROUTABLES' - - if fill_ipset: - anim('\tRestoration de l\'ipset reseaux_non_routable') - allowed = [ net for nets in NETs.values() for net in nets if NetInNets(net, config.firewall.reseaux_non_routables) ] - self.ipset['reseaux_non_routable']['allow'].restore(allowed) - self.ipset['reseaux_non_routable']['deny'].restore(config.firewall.reseaux_non_routables) - print OK - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-m set --match-set %s src -j RETURN' % self.ipset['reseaux_non_routable']['allow']) - self.add(table, chain, '-m set --match-set %s dst -j RETURN' % self.ipset['reseaux_non_routable']['allow']) - self.add(table, chain, '-m set --match-set %s src -j DROP' % self.ipset['reseaux_non_routable']['deny']) - self.add(table, chain, '-m set --match-set %s dst -j DROP' % self.ipset['reseaux_non_routable']['deny']) - print OK - - if apply: - self.apply(table, chain) - return chain - - def filtrage_ports_maj(self, ip_lists): - self.filtrage_ports('filter', apply=True) - - def filtrage_ports(self, table=None, apply=False): - """Ouvre les ports vers et depuis les machines du réseau crans""" - chain = 'FILTRAGE-PORTS' - - def format_port(port): - port = str(port) - if port.endswith(':'): - port = '%s65535' % port - if port.startswith(':'): - port = '0%s' % port - return port - - def add_ports(ip, machine, proto, sens): - self.add( - table, - chain, - '-p %s -%s %s -m multiport --dports %s -j RETURN' % ( - proto, - (sens=='out' and 's') or (sens == 'in' and 'd'), - ip, - ','.join( format_port(port) for port in machine['port%s%s' % (proto.upper(), sens)]) - ) - ) - - if table == 'filter': - pretty_print(table, chain) - for net in NETs['adherents'] + NETs['wifi-adh'] + NETs['personnel-ens']: - for proto in config.firewall.ports_default.keys(): - if config.firewall.ports_default[proto]['output']: - self.add(table, chain, '-p %s -s %s -m multiport --dports %s -j RETURN' % (proto, net, ','.join( format_port(port) for port in config.firewall.ports_default[proto]['output']))) - if config.firewall.ports_default[proto]['input']: - self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j RETURN' % (proto, net, ','.join( format_port(port) for port in config.firewall.ports_default[proto]['input']))) - - for machine in self.machines(): - for ip in machine['ipHostNumber']: - if 'portTCPout' in machine.attrs.keys(): - add_ports(ip, machine, 'tcp', 'out') - if 'portUDPout' in machine.attrs.keys(): - add_ports(ip, machine, 'udp', 'out') - if 'portTCPin' in machine.attrs.keys(): - add_ports(ip, machine, 'tcp', 'in') - if 'portUDPin' in machine.attrs.keys(): - add_ports(ip, machine, 'udp', 'in') - - self.add(table, chain, '-j REJECT') - print OK - - if apply: - self.apply(table, chain) - return chain - - def limitation_debit(self, table=None, run_tc=False, apply=False): - """Limite le débit de la connexion selon l'agréement avec l'ENS""" - chain = 'LIMITATION-DEBIT' - - debit_max = config.firewall.debit_max - uplink_speed = '1024mbit' - - if table == 'mangle': - pretty_print(table, chain) - # Pas de QoS vers/depuis la zone ENS - self.add(table, chain, '-d 138.231.0.0/16 -s 138.231.0.0/16 -j RETURN') - - # Idem pour le ftp - self.add(table, chain, '-d 138.231.136.98 -j RETURN') - self.add(table, chain, '-s 138.231.136.98 -j RETURN') - - # Idem vers OVH pour le test de la connection de secours - self.add(table, chain, '-d 91.121.84.138 -j RETURN') - self.add(table, chain, '-s 91.121.84.138 -j RETURN') - - # Classification par defaut pour tous les paquets - for net in NETs['all']: - self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:10' % (dev['out'], net)) - self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:10' % (dev['fil'], net)) - self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:10' % (dev['wifi'], net)) - - # Classification pour les appartements - for net in NETs['personnel-ens']: - self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:3' % (dev['app'], net)) - self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:2' % (dev['out'], net)) - - # Classification pour la voip - self.add(table, chain, '-d sip.crans.org -j CLASSIFY --set-class 1:12') - self.add(table, chain, '-s sip.crans.org -j CLASSIFY --set-class 1:12') - print OK - - if run_tc: - anim('\tApplication des commandes tc') - for int_key in ['out', 'fil', 'wifi']: - try: - tc('qdisc del dev %s root' % dev[int_key]) - except TcError: - pass - tc('qdisc add dev %s root handle 1: htb r2q 1' % dev[int_key]) - tc("class add dev %s parent 1: classid 1:1 " - "htb rate %s ceil %s" % (dev[int_key], uplink_speed, uplink_speed)) - tc("class add dev %s parent 1:1 classid 1:2 " - "htb rate %skbps ceil %skbps" % (dev[int_key], debit_max, debit_max)) - - # Classe par defaut - tc('class add dev %s parent 1:2 classid 1:10 ' - 'htb rate %skbps ceil %skbps prio 1' % (dev[int_key], debit_max, debit_max)) - tc('qdisc add dev %s parent 1:10 ' - 'handle 10: sfq perturb 10' % dev[int_key]) - - # Classe par pour la voip - tc('class add dev %s parent 1:2 classid 1:12 ' - 'htb rate %skbps ceil %skbps prio 0' % (dev[int_key], debit_max, debit_max)) - tc('qdisc add dev %s parent 1:12 ' - 'handle 12: sfq perturb 10' % dev[int_key]) - - #Classe des decos upload - tc('class add dev %s parent 1:2 classid 1:11 ' - 'htb rate 60kbps ceil 60kbps prio 1' % dev['out']) - tc('qdisc add dev %s parent 1:11 ' - 'handle 11: sfq perturb 10' % dev['out']) - - for int_key in ['app']: - try: - tc('qdisc del dev %s root' % dev[int_key]) - except TcError: - pass - tc('qdisc add dev %s root handle 1: htb r2q 1' % dev[int_key]) - - tc("class add dev %s parent 1: classid 1:1 " - "htb rate 128kbps ceil 128kbps" % dev[int_key]) - - # Classe pour l'upload des appartements - tc("class add dev %s parent 1:1 classid 1:2 " - "htb rate 128kbps ceil 128kbps" % dev[int_key]) - tc('qdisc add dev %s parent 1:2 ' - 'handle 2: sfq perturb 10' % dev[int_key]) - - # Classe pour le download des apparetments - tc("class add dev %s parent 1: classid 1:3 " - "htb rate %skbps ceil %skbps" % (dev[int_key], debit_max/10, debit_max/2)) - tc('qdisc add dev %s parent 1:3 ' - 'handle 3: sfq perturb 10' % dev[int_key]) - - print OK - - if apply: - self.apply(table, chain) - return chain - - -class firewall_zamok(firewall_base): - - def __init__(self): - super(self.__class__, self).__init__() - - self.reloadable.update({ - 'admin_vlan' : self.admin_vlan, - 'blacklist_output' : self.blacklist_output, - }) - - self.use_ipset.extend([]) - self.use_tc.extend([]) - - - def raw_table(self): - table = 'raw' - - super(self.__class__, self).raw_table() - - return - - def mangle_table(self): - table = 'mangle' - - super(self.__class__, self).mangle_table() - - return - - def filter_table(self): - table = 'filter' - - super(self.__class__, self).filter_table() - - chain = 'OUTPUT' - self.add(table, chain , '-d 224.0.0.0/4 -j DROP') - admin_vlan_chain = self.admin_vlan(table) - self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') - for net in NETs['adm']: - self.add(table, chain, '-d %s -j %s' % (net, admin_vlan_chain)) - self.add(table, chain, '-o lo -j ACCEPT') - self.add(table, chain, '-j %s' % self.blacklist_output(table)) - - return - - def nat_table(self): - table = 'nat' - - super(self.__class__, self).raw_table() - return - - def admin_vlan(self, table=None, apply=False): - chain='ADMIN-VLAN' - - if table == 'filter': - pretty_print(table, chain) - # ldap et dns toujours joinable - self.add(table, chain, '-p tcp --dport ldap -j ACCEPT') - self.add(table, chain, '-p tcp --dport domain -j ACCEPT') - self.add(table, chain, '-p udp --dport domain -j ACCEPT') - - # Pour le nfs (le paquet à laisser passer n'a pas d'owner) - self.add(table, chain, '-d nfs.adm.crans.org -j ACCEPT') - - for user in adm_users: - try: self.add(table, chain, '-m owner --uid-owner %d -j ACCEPT' % pwd.getpwnam(user)[2]) - except KeyError: print "Utilisateur %s inconnu" % user - - for adh in self.conn.search(u"(|(droits=%s)(droits=%s))" % (lc_ldap.attributs.nounou, lc_ldap.attributs.apprenti)): - self.add(table, chain, '-m owner --uid-owner %s -j RETURN' % adh['uidNumber'][0]) - - # Rien d'autre ne passe - self.add(table, chain, '-j REJECT --reject-with icmp-net-prohibited') - print OK - - if apply: - self.apply(table, chain) - return chain - - def blacklist_maj(self, ips): - self.blacklist_output('filter', apply=True) - self.blacklist_hard_maj(ips) - - def blacklist_output(self, table=None, apply=False): - """Empêche les gens blacklisté d'utiliser zamok comme relaie""" - chain='BLACKLIST-OUTPUT' - - if table == 'filter': - pretty_print(table, chain) - self.add(table, chain, '-d 127.0.0.1/8 -j RETURN') - for net in NETs['all']: - self.add(table, chain, '-d %s -j RETURN' % net) - for adh in self.blacklisted_adherents(['paiement']): - if 'uidNumber' in adh.attrs.keys(): - self.add(table, chain, '-m owner --uid-owner %s -j REJECT' % adh['uidNumber'][0]) - print OK - - if apply: - self.apply(table, chain) - return chain - -class firewall_routeur(firewall_base): - - def __init__(self): - super(self.__class__, self).__init__() - - self.reloadable.update({ - 'portail_captif_route' : self.portail_captif_route, - 'portail_captif' : self.portail_captif, - }) - - self.use_ipset.extend([]) - self.use_tc.extend([]) - - def raw_table(self): - table = 'raw' - super(self.__class__, self).raw_table() - return - - def mangle_table(self): - table = 'mangle' - super(self.__class__, self).mangle_table() - return - - def filter_table(self): - table = 'filter' - super(self.__class__, self).filter_table() - - chain = 'FORWARD' - self.flush(table, chain) - self.add(table, chain, '-j %s' % self.portail_captif_route(table)) - return - - def nat_table(self): - table = 'nat' - super(self.__class__, self).raw_table() - - chain = 'PREROUTING' - self.add(table, chain, '-j %s' % self.portail_captif(table)) - - chain = 'POSTROUTING' - self.add(table, chain, '-j %s' % self.portail_captif_route(table)) - return - - def portail_captif_route(self, table=None, apply=False): - """PNAT les (ip,port) à laisser passer à travers le portail captif""" - chain = 'CAPTIF-ROUTE' - - if table == 'filter': - pretty_print(table, chain) - for ip in accueil_route.keys(): - for type in accueil_route[ip].keys(): - if type in ['udp', 'tcp']: - self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j ACCEPT' % (type, ip, ','.join(accueil_route[ip][type]))) - self.add(table, chain, '-p %s -s %s -m multiport --sports %s -j ACCEPT' % (type, ip, ','.join(accueil_route[ip][type]))) - self.add(table, chain, '-j REJECT') - print OK - - if table == 'nat': - pretty_print(table, chain) - #intranet et wiki pour le vlan accueil - for ip in accueil_route.keys(): - for type in accueil_route[ip].keys(): - if type in ['udp', 'tcp']: - for net in NETs['accueil']: - self.add(table, chain, '-s %s -p %s -d %s -m multiport --dports %s -j MASQUERADE' % (net, type, ip, ','.join(accueil_route[ip][type]))) - for net in NETs['isolement']: - self.add(table, chain, '-s %s -p %s -d %s -m multiport --dports %s -j MASQUERADE' % (net, type, ip, ','.join(accueil_route[ip][type]))) - for net in NETs['personnel-ens']: - self.add(table, chain, '-i %s -s %s -j MASQUERADE' % (dev['app'], net)) - print OK - - if apply: - self.apply(table, chain) - return chain - - def portail_captif(self, table=None, apply=False): - """Redirige vers le portail captif""" - chain = 'PORTAIL-CAPTIF' - - if table == 'nat': - pretty_print(table, chain) - for ip in accueil_route.keys(): - for type in accueil_route[ip].keys(): - if type in ['udp', 'tcp']: - self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j RETURN' % (type, ip, ','.join(accueil_route[ip][type]))) - - for net in NETs['isolement']: - self.add(table, chain, '-p tcp -s %s --destination-port 80 -j DNAT --to-destination 10.52.0.10' % net) - - for net in NETs['accueil']: - self.add(table, chain, '-p tcp -s %s --destination-port 80 -j DNAT --to-destination 10.51.0.10' % net) - self.add(table, chain, '-p udp -s %s --dport 53 -j DNAT --to 10.51.0.10' % net) - self.add(table, chain, '-p tcp -s %s --dport 53 -j DNAT --to 10.51.0.10' % net) - print OK - - if apply: - self.apply(table, chain) - return chain - - -#: Associe à un ``hostname`` la classe du pare-feu correspondant -firewall = { - 'komaz' : firewall_komaz, - 'zamok' : firewall_zamok, - 'routeur' : firewall_routeur, - 'gordon' : firewall_base_routeur, - 'eap' : firewall_base_wifionly, -} - -if hostname in firewall.keys(): - #: Classe du pare-feu pour la machine ``hostname`` ou :py:class:`firewall_base` si non trouvé - firewall = firewall[hostname] -else: - #: Classe du pare-feu pour la machine ``hostname`` ou :py:class:`firewall_base` si non trouvé - firewall = firewall_base - -if __name__ == '__main__' : - - fw = firewall() - - # Chaînes pouvant être recontruites - chaines = fw.reloadable.keys() - - def __usage(txt=None) : - if txt!=None : cprint(txt,'gras') - - chaines.sort() - - print """Usage: - %(p)s start : Construction du firewall. - %(p)s restart : Reconstruction du firewall. - %(p)s stop : Arrêt du firewall. - %(p)s : 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:] : - if arg == 'stop': - fw.stop() - elif arg == 'start': - fw.start() - elif arg == 'restart': - fw.restart() - else: - fw.reload(arg) diff --git a/gestion/gen_confs/firewall4/__init__.py b/gestion/gen_confs/firewall4/__init__.py new file mode 100644 index 00000000..4a14464f --- /dev/null +++ b/gestion/gen_confs/firewall4/__init__.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from firewall4 import * diff --git a/gestion/gen_confs/firewall4/base.py b/gestion/gen_confs/firewall4/base.py new file mode 100644 index 00000000..0edd3931 --- /dev/null +++ b/gestion/gen_confs/firewall4/base.py @@ -0,0 +1,233 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import sys +import socket +import netaddr + +import utils +from utils import pretty_print, anim, OK, cprint + +from gestion import config +from gestion.gen_confs.ipset import IpsetError, Ipset + +import config.firewall + +#: Nom de la machine exécutant le script +hostname = socket.gethostname() +hostname='routeur' +#: Association des interfaces de ``hostname`` +dev = hostname in config.firewall.dev.keys() and config.firewall.dev[hostname] or {} + + +class firewall(utils.firewall_tools) : + """Classe de base du pare-feu implémentant l'association mac-ip (pour les machines filaires) et les blacklists hard""" + + def __init__(self): + super(firewall, self).__init__() + + self.reloadable = { + 'blacklist_hard' : self.blacklist_hard, + 'test_mac_ip' : self.test_mac_ip, + } + + self.use_ipset = [self.blacklist_hard, self.test_mac_ip] + + self.ipset['mac_ip']={ + 'adh' : Ipset("MAC-IP-ADH","macipmap","--from 138.231.136.0 --to 138.231.151.255"), + 'adm' : Ipset("MAC-IP-ADM","macipmap","--from 10.231.136.0 --to 10.231.136.255"), + 'app' : Ipset("MAC-IP-APP","macipmap","--from 10.2.9.0 --to 10.2.9.255"), + } + + self.ipset['blacklist']={ + 'hard' : Ipset("BLACKLIST-HARD","ipmap","--from 138.231.136.0 --to 138.231.151.255"), + } + + + def blacklist_maj(self, ips): + """Met à jours les blacklists pour les ip présentent dans la liste ``ips``""" + self.blacklist_hard_maj(ips) + + def raw_table(self): + """Génère les règles pour la table ``raw`` et remplis les chaines de la table""" + table = 'raw' + return + + def mangle_table(self): + """Génère les règles pour la table ``mangle`` et remplis les chaines de la table""" + table = 'mangle' + return + + def filter_table(self): + """Génère les règles pour la table ``filter`` et remplis les chaines de la table""" + table = 'filter' + + mac_ip_chain = self.test_mac_ip(table, fill_ipset=True) + blacklist_hard_chain = self.blacklist_hard(table, fill_ipset=True) + + chain = 'INPUT' + self.add(table, chain, '-i lo -j ACCEPT') + self.add(table, chain, '-p icmp -j ACCEPT') + self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') + for net in config.NETs['all'] + config.NETs['adm'] + config.NETs['personnel-ens']: + self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) + self.add(table, chain, '-j %s' % blacklist_hard_chain) + + chain = 'FORWARD' + self.add(table, chain, '-j REJECT') + + return + + def nat_table(self): + """Génère les règles pour la table ``nat`` et remplis les chaines de la table""" + table = 'nat' + return + + + def blacklist_hard_maj(self, ip_list): + """Met à jour les blacklists hard, est appelée par :py:func:`blacklist_maj`""" + self.blacklist_hard(fill_ipset=True) +# for ip in ip_list: +# machine = self.conn.search(u"ipHostNumber=%s" % ip) +# # Est-ce qu'il y a des blacklists hard parmis les blacklists de la machine +# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(config.blacklist_sanctions): +# try: self.ipset['blacklist']['hard'].add(ip) +# except IpsetError: pass +# else: +# try: self.ipset['blacklist']['hard'].delete(ip) +# except IpsetError: pass + + def blacklist_hard(self, table=None, fill_ipset=False, apply=False): + """Génère la chaine ``BLACKLIST_HARD``. + Si ``fill_ipset`` est à ``True``, remplis l'ipset ``BLACKLIST-HARD``. + Si ``apply`` est à True, applique directement les règles""" + chain = 'BLACKLIST_HARD' + + if fill_ipset: + anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['hard']) + # On récupère la liste de toutes les ips blacklistés hard + bl_hard_ips = set( + str(ip) for ips in + [ + machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in netaddr.IPNetwork(n) for n in config.NETs['all'] for ip in machine['ipHostNumber'])) + if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(config.blacklist_sanctions) + ] + for ip in ips + ) + + self.ipset['blacklist']['hard'].restore(bl_hard_ips) + print OK + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-m set --match-set %s src -j REJECT' % self.ipset['blacklist']['hard'] ) + self.add(table, chain, '-m set --match-set %s dst -j REJECT' % self.ipset['blacklist']['hard'] ) + print OK + + if apply: + self.apply(table, chain) + return chain + + def test_mac_ip_dispatch(self, func, machine): + """Détermine à quel set de mac-ip appliquer la fonction ``func`` (add, delete, append, ...)""" + ips = machine['ipHostNumber'] + for ip in ips: + # Si la machines est sur le réseau des adhérents + if utils.AddrInNet(str(ip), config.NETs['wifi']): + # Les machines wifi sont vues à travers komaz + func('adh', "%s,%s" % (ip, config.mac_komaz)) + elif utils.AddrInNet(str(ip), config.NETs['fil']): + func('adh', "%s,%s" % (ip, machine['macAddress'][0])) + # Si la machine est sur le réseau admin + elif utils.AddrInNet(str(ip), config.NETs['adm']): + func('adm', "%s,%s" % (ip, machine['macAddress'][0])) + + def test_mac_ip(self, table=None, fill_ipset=False, apply=False): + """Génère la chaine ``TEST_MAC-IP``. + Si ``fill_ipset`` est à ``True``, remplis les ipsets ``MAC-IP-ADH``, ``MAC-IP-ADM``, ``MAC-IP-ADM``. + Si ``apply`` est à True, applique directement les règles""" + chain = 'TEST_MAC-IP' + + if fill_ipset: + anim('\tRestoration des ipsets %s' % ', '.join(self.ipset['mac_ip'].keys())) + rules={ + 'adh':[], + 'adm':[], + 'app':[], + } + for machine in self.machines(): + self.test_mac_ip_dispatch(lambda set, data: rules[set].append(data), machine) + + for set,rules in rules.items(): + self.ipset['mac_ip'][set].restore(rules) + print OK + + if table == 'filter': + pretty_print(table, chain) + + for key in ['accueil', 'isolement', ]: + for net in config.NETs[key]: + self.add(table, chain, '-s %s -j RETURN' % net) + for key in self.ipset['mac_ip'].keys(): + self.add(table, chain, '-m set --match-set %s src,src -j RETURN' % self.ipset['mac_ip'][key]) + + # Proxy ARP de Komaz et Titanic pour OVH + ip_ovh = self.conn.search(u"host=ovh.adm.crans.org")[0]['ipHostNumber'][0] + self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, config.mac_komaz)) + self.add(table, chain, '-m mac -s %s --mac-source %s -j RETURN' % (ip_ovh, config.mac_titanic)) + + self.add(table, chain, '-j REJECT') + print OK + + if apply: + self.apply(table, chain) + return chain + + def mac_ip_maj(self, ip_list): + """Met à jour la correspondance mac-ip""" + anim('\tActualisation de la correspondance mac-ipv4') + for ip in ip_list: + machine = self.conn.search(u"ipHostNumber=%s" % ip) + if machine: + try: self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].delete(data.split(',',1)[0]), {'ipHostNumber' : [ip], 'macAddress':[''] }) + except IpsetError: pass + self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].add(data), machine[0]) + else: + try: self.test_mac_ip_dispatch(lambda set, data: self.ipset['mac_ip'][set].delete(data.split(',',1)[0]), {'ipHostNumber' : [ip], 'macAddress':[''] }) + except IpsetError: pass + print OK + + + +class firewall_routeur(firewall): + """Associe mac-ip pour les machines voyant plusieurs réseaux (wifi, filaire, personnel, ...)""" + def test_mac_ip_dispatch(self, func, machine): + """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" + ips = machine['ipHostNumber'] + for ip in ips: + # Si la machines est sur le réseau des adhérents + if utils.AddrInNet(str(ip), config.NETs['wifi']): + func('adh', "%s,%s" % (ip, machine['macAddress'][0])) + elif utils.AddrInNet(str(ip), config.NETs['fil']): + func('adh', "%s,%s" % (ip, machine['macAddress'][0])) + # Si la machine est sur le réseau admin + elif utils.AddrInNet(str(ip), config.NETs['adm']): + func('adm', "%s,%s" % (ip, machine['macAddress'][0])) + # Si la machine est sur le réseaux des appartements de l'ENS + elif utils.AddrInNet(str(ip), config.NETs['personnel-ens']): + func('app', "%s,%s" % (ip, machine['macAddress'][0])) + +class firewall_wifionly(firewall): + """Associe mac-ip pour les machines wifi only : les machines filaires sont vues avec la mac de komaz""" + def test_mac_ip_dispatch(self, func, machine): + """Détermine à quel set de mac-ip appliquer la fonction func (add, delete, append, ...)""" + ips = machine['ipHostNumber'] + for ip in ips: + # Si la machines est sur le réseau des adhérents + if utils.AddrInNet(str(ip), config.NETs['wifi']): + func('adh', "%s,%s" % (ip, machine['macAddress'][0])) + elif utils.AddrInNet(str(ip), config.NETs['fil']): + func('adh', "%s,%s" % (ip, config.mac_komaz)) + # Si la machine est sur le réseau admin + elif utils.AddrInNet(str(ip), config.NETs['adm']): + func('adm', "%s,%s" % (ip, machine['macAddress'][0])) diff --git a/gestion/gen_confs/firewall4/firewall4.py b/gestion/gen_confs/firewall4/firewall4.py new file mode 100755 index 00000000..488c396e --- /dev/null +++ b/gestion/gen_confs/firewall4/firewall4.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys +import socket + +import utils +import base +import komaz +import zamok +import routeur + +#: Nom de la machine exécutant le script +hostname = socket.gethostname() + + + +#: Associe à un ``hostname`` la classe du pare-feu correspondant +firewall = { + 'komaz' : komaz.firewall, + 'zamok' : zamok.firewall, + 'routeur' : routeur.firewall, + 'gordon' : base.firewall_routeur, + 'eap' : base.firewall_wifionly, +} + +if hostname in firewall.keys(): + #: Classe du pare-feu pour la machine ``hostname`` ou :py:class:`firewall_base` si non trouvé + firewall = firewall[hostname] +else: + #: Classe du pare-feu pour la machine ``hostname`` ou :py:class:`firewall_base` si non trouvé + firewall = base.firewall + +if __name__ == '__main__' : + + fw = firewall() + + # Chaînes pouvant être recontruites + chaines = fw.reloadable.keys() + + def __usage(txt=None) : + if txt!=None : cprint(txt,'gras') + + chaines.sort() + + print """Usage: + %(p)s start : Construction du firewall. + %(p)s restart : Reconstruction du firewall. + %(p)s stop : Arrêt du firewall. + %(p)s : 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:] : + if arg == 'stop': + fw.stop() + elif arg == 'start': + fw.start() + elif arg == 'restart': + fw.restart() + else: + fw.reload(arg) diff --git a/gestion/gen_confs/firewall4/komaz.py b/gestion/gen_confs/firewall4/komaz.py new file mode 100644 index 00000000..bfcd3e8d --- /dev/null +++ b/gestion/gen_confs/firewall4/komaz.py @@ -0,0 +1,555 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import utils +import base + +from utils import pretty_print, OK, anim +from base import dev + +class firewall(base.firewall_routeur): + + def __init__(self): + super(self.__class__, self).__init__() + + self.reloadable.update({ + 'log_all' : self.log_all, + 'admin_vlan' : self.admin_vlan, + 'clamp_mss' : self.clamp_mss, + 'ingress_filtering' : self.ingress_filtering, + 'ssh_on_https' : self.ssh_on_https, + 'connexion_secours' : self.connexion_secours, + 'connexion_appartement' : self.connexion_appartement, + 'blacklist_soft' : self.blacklist_soft, + 'blacklist_upload' : self.blacklist_upload, + 'reseaux_non_routable' : self.reseaux_non_routable, + 'filtrage_ports' : self.filtrage_ports, + 'limitation_debit' : self.limitation_debit, + 'limit_ssh_connexion' : self.limit_ssh_connexion, + 'tunnel_6in4' : self.tunnel_6in4, + }) + + self.use_ipset.extend([self.blacklist_soft, self.blacklist_upload, self.reseaux_non_routable]) + self.use_tc.extend([self.limitation_debit]) + + self.ipset['reseaux_non_routable'] = { + 'deny' : base.Ipset("RESEAUX-NON-ROUTABLE-DENY","nethash"), + 'allow' : base.Ipset("RESEAUX-NON-ROUTABLE-ALLOW","nethash"), + } + + self.ipset['blacklist'].update({ + 'soft' : base.Ipset("BLACKLIST-SOFT","ipmap","--from 138.231.136.0 --to 138.231.151.255"), + 'upload' : base.Ipset("BLACKLIST-UPLOAD","ipmap","--from 138.231.136.0 --to 138.231.151.255"), + }) + + def blacklist_maj(self, ips): + self.blacklist_hard_maj(ips) + self.blacklist_soft_maj(ips) + self.blacklist_upload_maj(ips) + + def raw_table(self): + """Génère les règles pour la table ``raw`` et remplis les chaines de la table""" + table = 'raw' + + chain = 'PREROUTING' + self.add(table, chain, '-d 225.0.0.50 -j DROP') + return + + + def mangle_table(self): + table = 'mangle' + super(self.__class__, self).mangle_table() + + chain = 'PREROUTING' + self.add(table, chain, '-j %s' % self.log_all(table)) + self.add(table, chain, '-j %s' % self.connexion_secours(table)) + self.add(table, chain, '-p tcp -j CONNMARK --restore-mark') + + chain = 'POSTROUTING' + self.add(table, chain, '-j %s' % self.clamp_mss(table)) + #self.add(table,chain, '-j %s' % self.limitation_debit(table, run_tc=True)) + self.add(table, chain, '-j %s' % self.blacklist_upload(table, fill_ipset=True)) + return + + def filter_table(self): + table = 'filter' + super(self.__class__, self).filter_table() + + mac_ip_chain = self.test_mac_ip() + blacklist_hard_chain = self.blacklist_hard() + blacklist_soft_chain = self.blacklist_soft(table) + + chain = 'INPUT' + self.flush(table, chain) + self.add(table, chain, '-i lo -j ACCEPT') + self.add(table, chain, '-p icmp -j ACCEPT') + self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') + self.add(table, chain, '-j %s' % blacklist_soft_chain) + for net in base.config.NETs['all'] + base.config.NETs['adm'] + base.config.NETs['personnel-ens']: + self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) + self.add(table, chain, '-j %s' % blacklist_hard_chain) + + chain = 'FORWARD' + self.flush(table, chain) + self.add(table, chain, '-i lo -j ACCEPT') + self.add(table, chain, '-p icmp -j ACCEPT') + self.add(table, chain, '-j %s' % self.admin_vlan(table)) + self.add(table, chain, '-j %s' % blacklist_soft_chain) + self.add(table, chain, '-i %s -j %s' % (dev['out'], blacklist_hard_chain)) + self.add(table, chain, '-o %s -j %s' % (dev['out'], blacklist_hard_chain)) + self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') + self.add(table, chain, '-j %s' % self.tunnel_6in4(table)) + self.add(table, chain, '-j %s' % self.reseaux_non_routable(table, fill_ipset=True)) + for net in base.config.NETs['all'] + base.config.NETs['adm'] + base.config.NETs['personnel-ens']: + self.add(table, chain, '-s %s -j %s' % (net, mac_ip_chain)) + self.add(table, chain, '-j %s' % self.connexion_secours(table)) + self.add(table, chain, '-j %s' % self.connexion_appartement(table)) + self.add(table, chain, '-j %s' % self.ingress_filtering(table)) + self.add(table, chain, '-j %s' % self.limit_ssh_connexion(table)) + self.add(table, chain, '-i %s -j %s' % (dev['out'], self.filtrage_ports(table))) + self.add(table, chain, '-o %s -j %s' % (dev['out'], self.filtrage_ports(table))) + return + + def nat_table(self): + table = 'nat' + super(self.__class__, self).nat_table() + + chain = 'PREROUTING' + self.add(table, chain, '-j %s' % self.ssh_on_https(table)) + self.add(table, chain, '-j %s' % self.connexion_secours(table)) + self.add(table, chain, '-j %s' % self.blacklist_soft(table)) + + chain = 'POSTROUTING' + self.add(table, chain, '-j %s' % self.connexion_appartement(table)) + return + + def tunnel_6in4(self, table=None, apply=False): + chain = 'TUNNEL_IPV6' + + tunnels_ipv6 = [ ('216.66.84.42', '138.231.136.12'), ('216.66.84.42','138.231.136.164') ] + + if table == 'filter': + pretty_print(table, chain) + for sideA, sideB in tunnels_ipv6: + self.add(table, chain, '--proto 41 -s %s -d %s -j ACCEPT' % (sideA, sideB)) + self.add(table, chain, '--proto 41 -s %s -d %s -j ACCEPT' % (sideB, sideA)) + print OK + + if apply: + self.apply(table, chain) + return chain + + def limit_ssh_connexion(self, table=None, apply=False): + chain = 'LIMIT-SSH-CONNEXION' + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --set' % dev['out']) + self.add(table, chain, '-i %s -p tcp --dport ssh -m state --state NEW -m recent --name SSH --update --seconds 30 --hitcount 10 --rttl -j DROP' % dev['out']) + print OK + + if apply: + self.apply(table, chain) + return chain + + def test_mac_ip(self, table=None, fill_ipset=False, apply=False): + chain = super(self.__class__, self).test_mac_ip() + + if table == 'filter': + for key in ['out', 'tun-ovh' ]: + self.add(table, chain, '-i %s -j RETURN' % dev[key]) + + return super(self.__class__, self).test_mac_ip(table, fill_ipset, apply) + + + def log_all(self, table=None, apply=False): + chain = 'LOG_ALL' + + if table == 'mangle': + pretty_print(table, chain) + for device in dev.values(): + self.add(table, chain, '-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % device) + print OK + + if apply: + self.apply(table, chain) + return chain + + def admin_vlan(self, table=None, apply=False): + chain = 'VLAN-ADM' + + if table == 'filter': + pretty_print(table, chain) + for net in base.config.NETs['adm']: + self.add(table, chain, '-o %s -s %s -j ACCEPT' % (dev['tun-ovh'], net)) + self.add(table, chain, '-i %s -d %s -j ACCEPT' % (dev['tun-ovh'], net)) + self.add(table, chain, '-d %s -j REJECT' % net) + print OK + + if apply: + self.apply(table, chain) + return chain + + def qos(self, table=None, apply=False): + return + + def clamp_mss(self, table=None, apply=False): + """Force la MSS (Max Segment Size) TCP à rentrer dans la MTU (Max Transfert Unit)""" + chain = 'CLAMP-MSS' + if table == 'mangle': + pretty_print(table, chain) + + self.add(table, chain, '-p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu') + print OK + + if apply: + self.apply(table, chain) + return chain + + def ingress_filtering(self, table=None, apply=False): + """Pour ne pas router les paquêtes n'appartenant pas à notre plage ip voulant sortir de notre réseau + et empêcher certain type de spoof (cf http://travaux.ovh.net/?do=details&id=5183)""" + chain = 'INGRESS_FILTERING' + if table == 'filter': + pretty_print(table, chain) + + for net in base.config.NETs['all']: + self.add(table, chain, '-o %s -s %s -j RETURN' % (dev['out'], net)) + self.add(table, chain, '-o %s -j LOG --log-prefix BAD_ROUTE' % dev['out']) + self.add(table, chain, '-o %s -j DROP' % dev['out']) + for net_d in base.config.NETs['all']: + for net_s in base.config.NETs['all']: + self.add(table, chain,'-i %s ! -s %s -d %s -j RETURN' % (dev['out'], net_s, net_d)) + self.add(table, chain,'-i %s -j LOG --log-prefix BAD_SRC' % dev['out']) + self.add(table, chain,'-i %s -j DROP' % dev['out']) + print OK + + if apply: + self.apply(table, chain) + return chain + + def ssh_on_https(self, table=None, apply=False): + """Pour faire fonctionner ssh2.crans.org""" + chain = 'SSH2' + + if table == 'nat': + pretty_print(table, chain) + self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 22 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok + self.add(table, chain, '-p tcp -d 138.231.136.2 --dport 443 -j DNAT --to-destination 138.231.136.1:22') # redirection du ssh vers zamok (pour passer dans un proxy, avec corkscrew) + print OK + + if apply: + self.apply(table, chain) + return chain + + def connexion_secours(self, table=None, apply=False): + """Redirige les paquets vers un proxy lorsqu'on est en connexion de secours""" + chain = 'CONNEXION-SECOURS' + + if table == 'mangle': + pretty_print(table, chain) + self.add(table, chain, '-p tcp -s 138.231.136.0/16 ! -d 138.231.136.0/16 --destination-port 80 -m condition --condition secours -j MARK --set-mark %s' % (base.config.firewall.mark['secours'])) + self.add(table, chain, '-m mark --mark %s -j ACCEPT' % base.config.firewall.mark['secours']) + print OK + + if table == 'nat': + pretty_print(table, chain) + self.add(table, chain, '-p tcp -m mark --mark %s -j DNAT --to-destination 10.231.136.4:3129' % base.config.firewall.mark['secours'] ) + print OK + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-p tcp -s 138.231.136.0/16 ! -d 138.231.136.0/16 --destination-port 443 -m condition --condition secours -j REJECT') + self.add(table, chain, '-m mark --mark %s -j ACCEPT' % base.config.firewall.mark['secours']) + print OK + + if apply: + self.apply(table, chain) + return chain + + def connexion_appartement(self, table=None, apply=False): + """PNAT les appartements derrière appartement.crans.org""" + chain = 'CONNEXION-APPARTEMENT' + + if table == 'nat': + pretty_print(table, chain) + for dev_key in ['out', 'fil', 'wifi']: + for net in base.config.NETs['personnel-ens']: + self.add(table, chain, '-o %s -s %s -j SNAT --to 138.231.136.44' % (dev[dev_key], net)) + print OK + + if table == 'filter': + pretty_print(table, chain) + for net in base.config.NETs['personnel-ens']: + self.add(table, chain, '-s %s -j ACCEPT' % net) + self.add(table, chain, '-d %s -j ACCEPT' % net) + print OK + + if apply: + self.apply(table, chain) + return chain + + def blacklist_soft_maj(self, ip_list): + self.blacklist_soft(fill_ipset=True) +# for ip in ip_list: +# machine = self.conn.search(u"ipHostNumber=%s" % ip) +# # Est-ce qu'il y a des blacklists soft parmis les blacklists de la machine +# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(base.config.blacklist_sanctions_soft): +# try: self.ipset['blacklist']['soft'].add(ip) +# except IpsetError: pass +# else: +# try: self.ipset['blacklist']['soft'].delete(ip) +# except IpsetError: pass + + def blacklist_soft(self, table=None, fill_ipset=False, apply=False): + """Redirige les gens blacklisté vers le portail captif""" + chain = 'BLACKLIST_SOFT' + + if fill_ipset: + anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['soft']) + # On récupère la liste de toutes les ips blacklistés soft + bl_soft_ips = set( + str(ip) for ips in + [ + machine['ipHostNumber'] for machine in self.blacklisted_machines() if machine['ipHostNumber'] and reduce(lambda x,y: x or y, ( ip.value in base.netaddr.IPNetwork(n) for n in base.config.NETs['all'] for ip in machine['ipHostNumber'])) + if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(base.config.blacklist_sanctions_soft) + ] + for ip in ips + ) + + self.ipset['blacklist']['soft'].restore(bl_soft_ips) + print OK + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['soft'] ) + self.add(table, chain, '-p tcp --sport 80 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['soft'] ) + self.add(table, chain, '-p tcp -d 10.231.136.4 --dport 3128 -m set --match-set %s src -j ACCEPT' % self.ipset['blacklist']['soft'] ) + self.add(table, chain, '-p tcp -s 10.231.136.4 --sport 3128 -m set --match-set %s dst -j ACCEPT' % self.ipset['blacklist']['soft'] ) + print OK + + if table == 'nat': + pretty_print(table, chain) + for net in base.config.NETs['all']: + self.add(table, chain, '-d %s -j RETURN' % net) + self.add(table, chain, '-p tcp --dport 80 -m set --match-set %s src -j DNAT --to-destination 10.231.136.4:3128' % self.ipset['blacklist']['soft'] ) + print OK + + if apply: + self.apply(table, chain) + return chain + + def blacklist_upload_maj(self, ip_list): + self.blacklist_upload(fill_ipset=True) +# for ip in ip_list: +# machine = self.conn.search(u"ipHostNumber=%s" % ip) +# # Est-ce qu'il y a des blacklists pour upload parmis les blacklists de la machine +# if machine and set([bl.value['type'] for bl in machine[0].blacklist_actif() ]).intersection(blacklist_bridage_upload): +# try: self.ipset['blacklist']['upload'].add(ip) +# except IpsetError: pass +# else: +# try: self.ipset['blacklist']['upload'].delete(ip) +# except IpsetError: pass + + def blacklist_upload(self, table=None, fill_ipset=False, apply=False): + """Les gens blacklistés ne sont plus prioritaires (classe de qos commune)""" + chain = 'BLACKLIST_UPLOAD' + + if fill_ipset: + anim('\tRestoration de l\'ipset %s' % self.ipset['blacklist']['upload']) + # On récupère la liste de toutes les ips blacklistés pour upload + bl_upload_ips = set( + str(ip) for ips in + [ + machine['ipHostNumber'] for machine in self.blacklisted_machines() + if set([bl.value['type'] for bl in machine.blacklist_actif() ]).intersection(base.config.blacklist_bridage_upload) + ] + for ip in ips + ) + + self.ipset['blacklist']['upload'].restore(bl_upload_ips) + print OK + + if table == 'mangle': + pretty_print(table, chain) + # Classification pour les blacklists upload + self.add(table, chain, '-o %s -m set --match-set %s src -j CLASSIFY --set-class 1:11' % (dev['out'], self.ipset['blacklist']['upload'])) + + print OK + + if apply: + self.apply(table, chain) + return chain + + def reseaux_non_routable(self, table=None, fill_ipset=False, apply=False): + """Bloque les réseaux non routables autres que ceux utilisés par le crans""" + chain = 'RESEAUX_NON_ROUTABLES' + + if fill_ipset: + anim('\tRestoration de l\'ipset reseaux_non_routable') + allowed = [ net for nets in base.config.NETs.values() for net in nets if utils.NetInNets(net, base.config.firewall.reseaux_non_routables) ] + self.ipset['reseaux_non_routable']['allow'].restore(allowed) + self.ipset['reseaux_non_routable']['deny'].restore(base.config.firewall.reseaux_non_routables) + print OK + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-m set --match-set %s src -j RETURN' % self.ipset['reseaux_non_routable']['allow']) + self.add(table, chain, '-m set --match-set %s dst -j RETURN' % self.ipset['reseaux_non_routable']['allow']) + self.add(table, chain, '-m set --match-set %s src -j DROP' % self.ipset['reseaux_non_routable']['deny']) + self.add(table, chain, '-m set --match-set %s dst -j DROP' % self.ipset['reseaux_non_routable']['deny']) + print OK + + if apply: + self.apply(table, chain) + return chain + + def filtrage_ports_maj(self, ip_lists): + self.filtrage_ports('filter', apply=True) + + def filtrage_ports(self, table=None, apply=False): + """Ouvre les ports vers et depuis les machines du réseau crans""" + chain = 'FILTRAGE-PORTS' + + def format_port(port): + port = str(port) + if port.endswith(':'): + port = '%s65535' % port + if port.startswith(':'): + port = '0%s' % port + return port + + def add_ports(ip, machine, proto, sens): + self.add( + table, + chain, + '-p %s -%s %s -m multiport --dports %s -j RETURN' % ( + proto, + (sens=='out' and 's') or (sens == 'in' and 'd'), + ip, + ','.join( format_port(port) for port in machine['port%s%s' % (proto.upper(), sens)]) + ) + ) + + if table == 'filter': + pretty_print(table, chain) + for net in base.config.NETs['adherents'] + base.config.NETs['wifi-adh'] + base.config.NETs['personnel-ens']: + for proto in base.config.firewall.ports_default.keys(): + if base.config.firewall.ports_default[proto]['output']: + self.add(table, chain, '-p %s -s %s -m multiport --dports %s -j RETURN' % (proto, net, ','.join( format_port(port) for port in base.config.firewall.ports_default[proto]['output']))) + if base.config.firewall.ports_default[proto]['input']: + self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j RETURN' % (proto, net, ','.join( format_port(port) for port in base.config.firewall.ports_default[proto]['input']))) + + for machine in self.machines(): + for ip in machine['ipHostNumber']: + if 'portTCPout' in machine.attrs.keys(): + add_ports(ip, machine, 'tcp', 'out') + if 'portUDPout' in machine.attrs.keys(): + add_ports(ip, machine, 'udp', 'out') + if 'portTCPin' in machine.attrs.keys(): + add_ports(ip, machine, 'tcp', 'in') + if 'portUDPin' in machine.attrs.keys(): + add_ports(ip, machine, 'udp', 'in') + + self.add(table, chain, '-j REJECT') + print OK + + if apply: + self.apply(table, chain) + return chain + + def limitation_debit(self, table=None, run_tc=False, apply=False): + """Limite le débit de la connexion selon l'agréement avec l'ENS""" + chain = 'LIMITATION-DEBIT' + + debit_max = base.config.firewall.debit_max + uplink_speed = '1024mbit' + + if table == 'mangle': + pretty_print(table, chain) + # Pas de QoS vers/depuis la zone ENS + self.add(table, chain, '-d 138.231.0.0/16 -s 138.231.0.0/16 -j RETURN') + + # Idem pour le ftp + self.add(table, chain, '-d 138.231.136.98 -j RETURN') + self.add(table, chain, '-s 138.231.136.98 -j RETURN') + + # Idem vers OVH pour le test de la connection de secours + self.add(table, chain, '-d 91.121.84.138 -j RETURN') + self.add(table, chain, '-s 91.121.84.138 -j RETURN') + + # Classification par defaut pour tous les paquets + for net in base.config.NETs['all']: + self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:10' % (dev['out'], net)) + self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:10' % (dev['fil'], net)) + self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:10' % (dev['wifi'], net)) + + # Classification pour les appartements + for net in base.config.NETs['personnel-ens']: + self.add(table, chain, '-o %s -d %s -j CLASSIFY --set-class 1:3' % (dev['app'], net)) + self.add(table, chain, '-o %s -s %s -j CLASSIFY --set-class 1:2' % (dev['out'], net)) + + # Classification pour la voip + self.add(table, chain, '-d sip.crans.org -j CLASSIFY --set-class 1:12') + self.add(table, chain, '-s sip.crans.org -j CLASSIFY --set-class 1:12') + print OK + + if run_tc: + anim('\tApplication des commandes tc') + for int_key in ['out', 'fil', 'wifi']: + try: + utils.tc('qdisc del dev %s root' % dev[int_key]) + except utils.TcError: + pass + utils.tc('qdisc add dev %s root handle 1: htb r2q 1' % dev[int_key]) + utils.tc("class add dev %s parent 1: classid 1:1 " + "htb rate %s ceil %s" % (dev[int_key], uplink_speed, uplink_speed)) + utils.tc("class add dev %s parent 1:1 classid 1:2 " + "htb rate %skbps ceil %skbps" % (dev[int_key], debit_max, debit_max)) + + # Classe par defaut + utils.tc('class add dev %s parent 1:2 classid 1:10 ' + 'htb rate %skbps ceil %skbps prio 1' % (dev[int_key], debit_max, debit_max)) + utils.tc('qdisc add dev %s parent 1:10 ' + 'handle 10: sfq perturb 10' % dev[int_key]) + + # Classe par pour la voip + utils.tc('class add dev %s parent 1:2 classid 1:12 ' + 'htb rate %skbps ceil %skbps prio 0' % (dev[int_key], debit_max, debit_max)) + utils.tc('qdisc add dev %s parent 1:12 ' + 'handle 12: sfq perturb 10' % dev[int_key]) + + #Classe des decos upload + utils.tc('class add dev %s parent 1:2 classid 1:11 ' + 'htb rate 60kbps ceil 60kbps prio 1' % dev['out']) + utils.tc('qdisc add dev %s parent 1:11 ' + 'handle 11: sfq perturb 10' % dev['out']) + + for int_key in ['app']: + try: + utils.tc('qdisc del dev %s root' % dev[int_key]) + except TcError: + pass + utils.tc('qdisc add dev %s root handle 1: htb r2q 1' % dev[int_key]) + + utils.tc("class add dev %s parent 1: classid 1:1 " + "htb rate 128kbps ceil 128kbps" % dev[int_key]) + + # Classe pour l'upload des appartements + utils.tc("class add dev %s parent 1:1 classid 1:2 " + "htb rate 128kbps ceil 128kbps" % dev[int_key]) + utils.tc('qdisc add dev %s parent 1:2 ' + 'handle 2: sfq perturb 10' % dev[int_key]) + + # Classe pour le download des apparetments + utils.tc("class add dev %s parent 1: classid 1:3 " + "htb rate %skbps ceil %skbps" % (dev[int_key], debit_max/10, debit_max/2)) + utils.tc('qdisc add dev %s parent 1:3 ' + 'handle 3: sfq perturb 10' % dev[int_key]) + + print OK + + if apply: + self.apply(table, chain) + return chain + + diff --git a/gestion/gen_confs/firewall4/routeur.py b/gestion/gen_confs/firewall4/routeur.py new file mode 100644 index 00000000..55775c27 --- /dev/null +++ b/gestion/gen_confs/firewall4/routeur.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import utils +import base + +from utils import pretty_print, OK, anim +from base import dev + +class firewall(base.firewall): + + def __init__(self): + super(self.__class__, self).__init__() + + self.reloadable.update({ + 'portail_captif_route' : self.portail_captif_route, + 'portail_captif' : self.portail_captif, + }) + + self.use_ipset.extend([]) + self.use_tc.extend([]) + + def raw_table(self): + table = 'raw' + super(self.__class__, self).raw_table() + return + + def mangle_table(self): + table = 'mangle' + super(self.__class__, self).mangle_table() + return + + def filter_table(self): + table = 'filter' + super(self.__class__, self).filter_table() + + chain = 'FORWARD' + self.flush(table, chain) + self.add(table, chain, '-j %s' % self.portail_captif_route(table)) + return + + def nat_table(self): + table = 'nat' + super(self.__class__, self).raw_table() + + chain = 'PREROUTING' + self.add(table, chain, '-j %s' % self.portail_captif(table)) + + chain = 'POSTROUTING' + self.add(table, chain, '-j %s' % self.portail_captif_route(table)) + return + + def portail_captif_route(self, table=None, apply=False): + """PNAT les (ip,port) à laisser passer à travers le portail captif""" + chain = 'CAPTIF-ROUTE' + + if table == 'filter': + pretty_print(table, chain) + for ip in base.config.accueil_route.keys(): + for type in base.config.accueil_route[ip].keys(): + if type in ['udp', 'tcp']: + self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j ACCEPT' % (type, ip, ','.join(base.config.accueil_route[ip][type]))) + self.add(table, chain, '-p %s -s %s -m multiport --sports %s -j ACCEPT' % (type, ip, ','.join(base.config.accueil_route[ip][type]))) + self.add(table, chain, '-j REJECT') + print OK + + if table == 'nat': + pretty_print(table, chain) + #intranet et wiki pour le vlan accueil + for ip in base.config.accueil_route.keys(): + for type in base.config.accueil_route[ip].keys(): + if type in ['udp', 'tcp']: + for net in base.config.NETs['accueil']: + self.add(table, chain, '-s %s -p %s -d %s -m multiport --dports %s -j MASQUERADE' % (net, type, ip, ','.join(base.config.accueil_route[ip][type]))) + for net in base.config.NETs['isolement']: + self.add(table, chain, '-s %s -p %s -d %s -m multiport --dports %s -j MASQUERADE' % (net, type, ip, ','.join(base.config.accueil_route[ip][type]))) + for net in base.config.NETs['personnel-ens']: + self.add(table, chain, '-i %s -s %s -j MASQUERADE' % (dev['app'], net)) + print OK + + if apply: + self.apply(table, chain) + return chain + + def portail_captif(self, table=None, apply=False): + """Redirige vers le portail captif""" + chain = 'PORTAIL-CAPTIF' + + if table == 'nat': + pretty_print(table, chain) + for ip in base.config.accueil_route.keys(): + for type in base.config.accueil_route[ip].keys(): + if type in ['udp', 'tcp']: + self.add(table, chain, '-p %s -d %s -m multiport --dports %s -j RETURN' % (type, ip, ','.join(base.config.accueil_route[ip][type]))) + + for net in base.config.NETs['isolement']: + self.add(table, chain, '-p tcp -s %s --destination-port 80 -j DNAT --to-destination 10.52.0.10' % net) + + for net in base.config.NETs['accueil']: + self.add(table, chain, '-p tcp -s %s --destination-port 80 -j DNAT --to-destination 10.51.0.10' % net) + self.add(table, chain, '-p udp -s %s --dport 53 -j DNAT --to 10.51.0.10' % net) + self.add(table, chain, '-p tcp -s %s --dport 53 -j DNAT --to 10.51.0.10' % net) + print OK + + if apply: + self.apply(table, chain) + return chain + + diff --git a/gestion/gen_confs/firewall4/utils.py b/gestion/gen_confs/firewall4/utils.py new file mode 100644 index 00000000..9ef6bfaf --- /dev/null +++ b/gestion/gen_confs/firewall4/utils.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import sys + +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 + self._machines, self._adherents = self.conn.allMachinesAdherents() + return self._machines + + def adherents(self): + """Renvois la liste de tous les adhérents""" + 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_adherents(self, excepts=[]): + """Renvois la liste de tous les adhérents ayant une blackliste active""" + 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""" + anim('\tChargement des machines') + self.machines() + self.blacklisted_machines() + print OK + + 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 + diff --git a/gestion/gen_confs/firewall4/zamok.py b/gestion/gen_confs/firewall4/zamok.py new file mode 100644 index 00000000..5f2031e0 --- /dev/null +++ b/gestion/gen_confs/firewall4/zamok.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import utils +import base +import pwd + +from utils import pretty_print, OK, anim +from base import dev + +class firewall(base.firewall): + + def __init__(self): + super(self.__class__, self).__init__() + + self.reloadable.update({ + 'admin_vlan' : self.admin_vlan, + 'blacklist_output' : self.blacklist_output, + }) + + self.use_ipset.extend([]) + self.use_tc.extend([]) + + + def raw_table(self): + table = 'raw' + + super(self.__class__, self).raw_table() + + return + + def mangle_table(self): + table = 'mangle' + + super(self.__class__, self).mangle_table() + + return + + def filter_table(self): + table = 'filter' + + super(self.__class__, self).filter_table() + + chain = 'OUTPUT' + self.add(table, chain , '-d 224.0.0.0/4 -j DROP') + admin_vlan_chain = self.admin_vlan(table) + self.add(table, chain, '-m state --state RELATED,ESTABLISHED -j ACCEPT') + for net in base.config.NETs['adm']: + self.add(table, chain, '-d %s -j %s' % (net, admin_vlan_chain)) + self.add(table, chain, '-o lo -j ACCEPT') + self.add(table, chain, '-j %s' % self.blacklist_output(table)) + + return + + def nat_table(self): + table = 'nat' + + super(self.__class__, self).raw_table() + return + + def admin_vlan(self, table=None, apply=False): + chain='ADMIN-VLAN' + + if table == 'filter': + pretty_print(table, chain) + # ldap et dns toujours joinable + self.add(table, chain, '-p tcp --dport ldap -j ACCEPT') + self.add(table, chain, '-p tcp --dport domain -j ACCEPT') + self.add(table, chain, '-p udp --dport domain -j ACCEPT') + + # Pour le nfs (le paquet à laisser passer n'a pas d'owner) + self.add(table, chain, '-d nfs.adm.crans.org -j ACCEPT') + + for user in base.config.adm_users: + try: self.add(table, chain, '-m owner --uid-owner %d -j ACCEPT' % pwd.getpwnam(user)[2]) + except KeyError: print "Utilisateur %s inconnu" % user + + for adh in self.conn.search(u"(|(droits=%s)(droits=%s))" % (utils.lc_ldap.attributs.nounou, utils.lc_ldap.attributs.apprenti)): + self.add(table, chain, '-m owner --uid-owner %s -j RETURN' % adh['uidNumber'][0]) + + # Rien d'autre ne passe + self.add(table, chain, '-j REJECT --reject-with icmp-net-prohibited') + print OK + + if apply: + self.apply(table, chain) + return chain + + def blacklist_maj(self, ips): + self.blacklist_output('filter', apply=True) + self.blacklist_hard_maj(ips) + + def blacklist_output(self, table=None, apply=False): + """Empêche les gens blacklisté d'utiliser zamok comme relaie""" + chain='BLACKLIST-OUTPUT' + + if table == 'filter': + pretty_print(table, chain) + self.add(table, chain, '-d 127.0.0.1/8 -j RETURN') + for net in base.config.NETs['all']: + self.add(table, chain, '-d %s -j RETURN' % net) + for adh in self.blacklisted_adherents(['paiement']): + if 'uidNumber' in adh.attrs.keys(): + self.add(table, chain, '-m owner --uid-owner %s -j REJECT' % adh['uidNumber'][0]) + print OK + + if apply: + self.apply(table, chain) + return chain +