
Pour firewall, en fait on s'en fout parce qu'il fait pas d'écriture, mais on laisse le binding en admin parce que les transactions sont plus rapides et qu'il en fait mâss.
1250 lines
50 KiB
Python
Executable file
1250 lines
50 KiB
Python
Executable file
#!/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 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 = 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 = 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
|
|
if self._machines:
|
|
self._blacklisted_machines = [ machine for machine in self._machines if machine.blacklist_actif() ]
|
|
return self._blacklisted_machines
|
|
blacklisted = [ machine for machine in conn.search("blacklist=*",sizelimit=4096) if machine.blacklist_actif() ]
|
|
self._blacklisted_machines = set()
|
|
for item in blacklisted:
|
|
if isinstance(item, lc_ldap.objets.proprio):
|
|
self._blacklisted_machines = self._blacklisted_machines.union(item.machines())
|
|
elif isinstance(item, lc_ldap.objets.machine):
|
|
self._blacklisted_machines.add(item)
|
|
else:
|
|
print >> sys.stderr, 'Objet %s inconnu blacklisté' % a.__class__.__name__
|
|
return self._blacklisted_machines
|
|
|
|
def blacklisted_adherents(self):
|
|
"""Renvois la liste de tous les adhérents ayant une blackliste active"""
|
|
if self._blacklisted_adherents:
|
|
return self._blacklisted_adherents
|
|
self._blacklisted_adherents = filter(lambda adh: adh.blacklist_actif(), self.adherents())
|
|
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):
|
|
global conn
|
|
#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
|
|
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()
|
|
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)
|
|
self.reload('blacklist_hard')
|
|
|
|
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`"""
|
|
for ip in ip_list:
|
|
machine = conn.search("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 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 = conn.search("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"""
|
|
for ip in ip_list:
|
|
machine = conn.search("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
|
|
|
|
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,
|
|
})
|
|
|
|
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)
|
|
self.reload('blacklist_hard')
|
|
self.reload('blacklist_soft')
|
|
self.reload('blacklist_upload')
|
|
|
|
def raw_table(self):
|
|
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.blacklist_soft(table, fill_ipset=True))
|
|
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()
|
|
|
|
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, '-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.reseaux_non_routable(table, fill_ipset=True))
|
|
self.add(table, chain, '-j %s' % self.blacklist_soft(table))
|
|
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, '-j %s' % 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 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):
|
|
for ip in ip_list:
|
|
machine = conn.search("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 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 == 'mangle':
|
|
pretty_print(table, chain)
|
|
self.add(table, chain, '-p tcp ! --dport 80 -j RETURN')
|
|
self.add(table, chain, '! -p tcp -j RETURN')
|
|
for net in NETs['all']:
|
|
self.add(table, chain, '-d %s -j RETURN' % net)
|
|
self.add(table, chain, '-m set --match-set %s src -j MARK --set-mark %s'
|
|
% (self.ipset['blacklist']['soft'], config.firewall.mark['proxy']))
|
|
|
|
print OK
|
|
|
|
if table == 'filter':
|
|
pretty_print(table, chain)
|
|
self.add(table, chain, '-m mark --mark %s -j ACCEPT' % config.firewall.mark['proxy'])
|
|
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:3128' % config.firewall.mark['proxy'] )
|
|
print OK
|
|
|
|
if apply:
|
|
self.apply(table, chain)
|
|
return chain
|
|
|
|
def blacklist_upload_maj(self, ip_list):
|
|
for ip in ip_list:
|
|
machine = conn.search("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):
|
|
"""Redirige les gens blacklisté vers le portail captif"""
|
|
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, 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['portTCP%s' % 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,'tcp','out')
|
|
if 'portUDPout' in machine.attrs.keys():
|
|
add_ports(ip,'udp','out')
|
|
if 'portTCPin' in machine.attrs.keys():
|
|
add_ports(ip,'tcp','in')
|
|
if 'portUDPin' in machine.attrs.keys():
|
|
add_ports(ip,'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 30kbps ceil 30kbps 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 nounou in conn.search("droits=%s" % lc_ldap.attributs.nounou):
|
|
self.add(table, chain, '-m owner --uid-owner %s -j RETURN' % nounou['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):
|
|
anim('\tMise à jour des blacklists')
|
|
self.blacklist_output('filter', apply=True)
|
|
#self.blacklist_hard_maj(ips)
|
|
self.reload('blacklist_hard')
|
|
print OK
|
|
|
|
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():
|
|
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 <noms de chaînes> : reconstruit les chaînes
|
|
Les chaînes pouvant être reconstruites sont :
|
|
%(chaines)s
|
|
Pour reconfiguration d'IPs particulières, utiliser generate. """ % \
|
|
{ 'p' : sys.argv[0].split('/')[-1] , 'chaines' : '\n '.join(chaines) }
|
|
sys.exit(-1)
|
|
|
|
# Bons arguments ?
|
|
if len(sys.argv) == 1 :
|
|
__usage()
|
|
for arg in sys.argv[1:] :
|
|
if arg in [ 'stop', 'restart', 'start' ] and len(sys.argv) != 2 :
|
|
__usage("L'argument %s ne peut être employé que seul." % arg)
|
|
|
|
if arg not in [ 'stop', 'restart', 'start' ] + chaines :
|
|
__usage("L'argument %s est inconnu." % arg)
|
|
|
|
for arg in sys.argv[1:] :
|
|
if arg == 'stop':
|
|
fw.stop()
|
|
elif arg == 'start':
|
|
fw.start()
|
|
elif arg == 'restart':
|
|
fw.restart()
|
|
else:
|
|
fw.reload(arg)
|