
Ignore-this: 199e9ff5f09e1fe600c1066179f4e47b Ce patch est un test, il ne restera en prod que si ça fonctionne. L'idée est qu'on souhaiterait conserver les vieilles machines comme les vieux adhérents, sauf demande explicite de suppression, par ailleurs, l'association mid <=> ip est très utile pour pas mal de choses. Pour la conserver, on crée un identifiant rid, qui supplante le mid, qui est lui choisi comme l'aid ou le fid, en incrémentant. Ce patch vise à implémenter cela. S'il génère des bugs, il subira un rollback. darcs-hash:20130123021650-b6762-347428d75f066f7f4821ca067d8c9bb6a4396bf5.gz
777 lines
28 KiB
Python
Executable file
777 lines
28 KiB
Python
Executable file
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
#
|
|
# IPT.PY -- Gestion du firewall
|
|
#
|
|
# Copyright (C) 2010 Olivier Huber
|
|
# Authors: Olivier Huber <huber@crans.org>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
import os, re, syslog, cPickle, socket
|
|
|
|
from ldap_crans import crans_ldap, hostname
|
|
from commands import getstatusoutput
|
|
from config import NETs, role, prefix, rid, output_file, filter_policy
|
|
from config import blacklist_sanctions, blacklist_sanctions_soft, file_pickle, ann_scol, periode_transitoire
|
|
from iptools import AddrInNet
|
|
from ridtools import Rid
|
|
import subprocess
|
|
import netaddr
|
|
|
|
blacklist_sanctions.extend(blacklist_sanctions_soft)
|
|
|
|
Mangle_policy = """
|
|
*mangle
|
|
:PREROUTING ACCEPT [0:0]
|
|
:INPUT ACCEPT [0:0]
|
|
:FORWARD ACCEPT [0:0]
|
|
:OUTPUT ACCEPT [0:0]
|
|
:POSTROUTING ACCEPT [0:0]
|
|
"""
|
|
|
|
Raw_policy = """
|
|
*raw
|
|
:PREROUTING ACCEPT [0:0]
|
|
:OUTPUT ACCEPT [0:0]
|
|
"""
|
|
|
|
Filter_policy_template = """
|
|
*filter
|
|
:INPUT %(policy_input)s [0:0]
|
|
:FORWARD %(policy_forward)s [0:0]
|
|
:OUTPUT %(policy_output)s [0:0]
|
|
"""
|
|
|
|
dprefix = { 'fil' : 'fil', 'fil-v6' : 'fil', 'adm' : 'adm', 'wifi' : 'wifi',
|
|
'wifi-v6' : 'wifi','personnel-ens':'personnel-ens' }
|
|
|
|
default_chains = [ 'PREROUTING', 'INPUT', 'FORWARD', 'OUTPUT', 'POSTROUTING' ]
|
|
|
|
# On ouvre une connexion avec la base
|
|
db = crans_ldap()
|
|
|
|
##############################################################################
|
|
#
|
|
# Déclaration des classes pour les règles du firewall
|
|
#
|
|
##############################################################################
|
|
|
|
class Chain(object):
|
|
''' Classe regroupant toutes les règles du firewall '''
|
|
def __init__(self):
|
|
self.items = []
|
|
def __call__(self, cmd):
|
|
self.items.append(cmd)
|
|
|
|
class Table(object):
|
|
''' Classe contenant toutes les chaînes du firewall '''
|
|
# TODO voir si on peut pas créer dynamiquement les méthodes
|
|
def __init__(self):
|
|
self.prerouting = Chain()
|
|
self.input = Chain()
|
|
self.forward = Chain()
|
|
self.output = Chain()
|
|
self.postrouting = Chain()
|
|
self.mac = Chain()
|
|
self.macfil = Chain()
|
|
self.macfilv6 = Chain()
|
|
self.macadm = Chain()
|
|
self.macwifi = Chain()
|
|
self.macwifiv6 = Chain()
|
|
self.extfil = Chain()
|
|
self.extfilv6 = Chain()
|
|
self.extwifi = Chain()
|
|
self.extwifiv6 = Chain()
|
|
self.cransfil = Chain()
|
|
self.cransfilv6 = Chain()
|
|
self.cranswifi = Chain()
|
|
self.cranswifiv6 = Chain()
|
|
self.ieui64 = Chain()
|
|
self.feui64 = Chain()
|
|
self.blacklist_src = Chain()
|
|
self.blacklist_dst = Chain()
|
|
self.srv_out_adm = Chain()
|
|
self.ingress_filtering = Chain()
|
|
self.tracker_torrent = Chain()
|
|
|
|
class Ip6tables(object):
|
|
''' Classe pour '''
|
|
def __init__(self):
|
|
self.filter = Table()
|
|
self.mangle = Table()
|
|
self.raw = Table()
|
|
|
|
def macip(self, mac, type_m):
|
|
'''Fait la correspondance MAC-IP'''
|
|
type_mm = re.sub('-', '', type_m)
|
|
getattr(self.filter,'mac' + type_mm)(" ".join(['-m mac --mac-source', mac,
|
|
'-j RETURN']))
|
|
# self.filter.mac(" ".join(['-m mac --mac-source', mac,
|
|
# '-j RETURN']))
|
|
|
|
def extcrans(self, type_machine, ports, mac, dev):
|
|
'''Ouverture des ports de l'extérieur vers la zone crans'''
|
|
tab = { 'fil' : 'extfil', 'fil-v6' : 'extfilv6',
|
|
'wifi' : 'extwifi',
|
|
'wifi-v6' : 'extwifiv6' }
|
|
ip = ipv6_addr(mac, type_machine)
|
|
for proto in ['tcp', 'udp']:
|
|
for port in ports[proto]:
|
|
if port != ':':
|
|
getattr(self.filter,tab[type_machine])('-i %s -p %s -d %s --dport %s -j \
|
|
ACCEPT' % (dev, proto, ip, port))
|
|
else:
|
|
getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s -j ACCEPT' %
|
|
(dev, proto, ip))
|
|
|
|
def cransext(self, type_machine, ports, mac, dev):
|
|
'''Ouverture des ports de la zone crans vers l'extérieur'''
|
|
tab = { 'fil' : 'cransfil', 'fil-v6' :
|
|
'cransfilv6', 'wifi' : 'cranswifi',
|
|
'wifi-v6' : 'cranswifiv6' }
|
|
ip = ipv6_addr(mac, type_machine)
|
|
for proto in ['tcp', 'udp']:
|
|
for port in ports[proto]:
|
|
if port != ':':
|
|
getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s --sport %s -j \
|
|
ACCEPT' % (dev, proto, ip, port))
|
|
else:
|
|
getattr(self.filter,tab[type_machine])('-i %s -p %s -s %s -j ACCEPT' %
|
|
(dev, proto, ip))
|
|
|
|
def blacklist(self, machine):
|
|
''' Met des règles empêchant toute communication
|
|
vers et à partir de la machine considérée '''
|
|
ident = int(machine.id())
|
|
ip = ""
|
|
for type_m in rid.keys():
|
|
if ident in range(rid[type_m][0], rid[type_m][1]):
|
|
ip = ipv6_addr(machine.mac(), type_m)
|
|
mac=machine.mac()
|
|
break
|
|
|
|
if ip:
|
|
self.filter.blacklist_src('-m mac --mac-source %s -j REJECT --reject-with icmp6-port-unreachable' % mac)
|
|
#~ self.filter.blacklist_src('-s %s -j REJECT --reject-with \
|
|
#~ icmp6-adm-prohibited' % ip)
|
|
self.filter.blacklist_dst('-d %s -j REJECT --reject-with \
|
|
icmp6-adm-prohibited' % ip)
|
|
|
|
|
|
def version(self):
|
|
''' methode retournant la version du protocole ip
|
|
pour l'instance de la classe '''
|
|
return 6
|
|
|
|
class Update(object):
|
|
''' Cette classe comprend toutes les méthodes nécessaires
|
|
à une mise-à-jour ou un régénération partielle du firewall'''
|
|
def ports(self, rids, ip_proto = 4):
|
|
''' Met à jour les autorisations par machine sur l'ouverture des ports.
|
|
Prend en argument une liste de rid (entier(s)) de machines à changer'''
|
|
check_ip_proto(ip_proto)
|
|
ipt_p = open_pickle(ip_proto)
|
|
dev_crans = iface6('fil')
|
|
if ip_proto == 4:
|
|
dev_ext = iface('ens')
|
|
elif ip_proto == 6:
|
|
dev_ext = iface6('sixxs2')
|
|
net = ""
|
|
for r in rids:
|
|
for type_m in rid.keys():
|
|
if int(r) in range(rid[type_m][0], rid[type_m][1]):
|
|
net = type_m
|
|
if net == "":
|
|
raise RidError("Il n'y a pas de réseau associé au rid %i" % m)
|
|
if '-v6' in net and ip_proto == 4:
|
|
raise MismatchRidIpProto(r, ip_proto, net)
|
|
machine = db.search('rid=%i' % r)['machine']
|
|
if len(machine) != 1:
|
|
msg = "Il y a %i machines associée(s) au rid %i" % (
|
|
len(machine), r)
|
|
raise RidError(msg)
|
|
else:
|
|
if ip_proto == 4:
|
|
ip = Rid(int(m)).to_ipv4()
|
|
elif ip_proto == 6:
|
|
ip = ipv6_addr(machine[0].mac(), net)
|
|
# On vérifie si la machine a déjà des entrées dans les chaînes
|
|
# On est un peu sous optimal ici
|
|
for sens in ['crans', 'ext']:
|
|
items = getattr(ipt_p.filter,sens + re.sub('-', '',net)).items
|
|
i = 0
|
|
while i < len(items):
|
|
if ip in items[i] or 'REJECT' in items[i]:
|
|
del items[i]
|
|
else:
|
|
i = i + 1
|
|
ports_io(ipt_p, machine[0], net, dev_ext, dev_crans)
|
|
getattr(ipt_p.filter,'ext' + re.sub('-', '', net))('-j \
|
|
REJECT --reject-with icmp6-port-unreachable')
|
|
getattr(ipt_p.filter,'crans' + re.sub('-', '', net))('-j \
|
|
REJECT --reject-with icmp6-port-unreachable')
|
|
# On écrit et applique les règles
|
|
write_rules(ipt_p)
|
|
apply_rules(ip_proto)
|
|
|
|
os.remove(file_pickle[ip_proto])
|
|
|
|
# On sauve les chaînes
|
|
|
|
save_pickle(ipt_p)
|
|
return 0
|
|
|
|
def blacklist(self, ip_proto = 4):
|
|
''' Permet de regénérer les diverses blacklistes '''
|
|
#TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
|
|
check_ip_proto(ip_proto)
|
|
ipt_p = open_pickle(ip_proto)
|
|
blacklist(ipt_p)
|
|
# On écrit et applique les règles
|
|
write_rules(ipt_p)
|
|
apply_rules(ip_proto)
|
|
|
|
os.remove(file_pickle[ip_proto])
|
|
|
|
# On sauve les chaînes
|
|
save_pickle(ipt_p)
|
|
return 0
|
|
|
|
def macs(self, macs, ip_proto = 4):
|
|
''' Méthode temporaire le temps de trouver une solution plus élégante
|
|
l'interface devrait être la même que pour la fonction finale'''
|
|
check_ip_proto(ip_proto)
|
|
ipt_p = open_pickle(ip_proto)
|
|
for type_m in ['fil', 'fil-v6', 'adm']:
|
|
type_mm = re.sub('-', '', type_m)
|
|
getattr(ipt_p.filter,'mac%s' % type_mm).items[:] = []
|
|
machines = db.all_machines(graphic = True)
|
|
macips(ipt_p, machines, ['fil', 'fil-v6', 'adm'])
|
|
|
|
write_rules(ipt_p)
|
|
apply_rules(ip_proto)
|
|
os.remove(file_pickle[ip_proto])
|
|
save_pickle(ipt_p)
|
|
return 0
|
|
# Il faut voir comment passer les mac en argument
|
|
# def macs(self, macs, ip_proto = 4):
|
|
# ''' Fonction pour mettre à jour les adresses mac acceptées sur le réseau.
|
|
# Prend comme argument un dictionnaire ayant pour clé 'remove' et 'add',
|
|
# contenant respectivement les adresses mac a ôter et celles à ajouter'''
|
|
# #TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
|
|
# ipt_p = open_pickle(ip_proto)
|
|
# # On modifie la chaîne mac
|
|
# nb = len(ipt_p.filter.mac.items)
|
|
# for mac in macs['remove']:
|
|
# for i in range(1, nb):
|
|
# if mac in ipt_p.filter.mac.items[i]:
|
|
# ipt_p.filter.mac.items.remove[i]
|
|
# break
|
|
# print "Erreur, la mac " + mac + " n'est pas dans la chaîne."
|
|
#
|
|
# for mac in macs['add']:
|
|
# ipt_p.macip(mac)
|
|
#
|
|
# # On écrit et applique les règles
|
|
# write_rules(ipt_p)
|
|
# apply_rules(6)
|
|
#
|
|
# os.remove(file_pickle[ip_proto])
|
|
#
|
|
# # On sauve les chaînes
|
|
# save_pickle(ipt_p)
|
|
# return 0
|
|
#
|
|
##############################################################################
|
|
#
|
|
# Déclaration des exceptions
|
|
#
|
|
##############################################################################
|
|
|
|
class NoIface(Exception):
|
|
''' Exception invoquée lorsqu'il n'y a pas
|
|
d'interface associé à un réseau'''
|
|
def __init__(self, net, msg):
|
|
self.net = net
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"Pas d'interface associé à %s"
|
|
% self.net)
|
|
syslog.syslog(syslog.LOG_ERR,"Résultat de la commande : \n %s"
|
|
% self.msg)
|
|
def __str__(self):
|
|
txt = "Pas d'interface associé à %s \n \
|
|
Résultat de la commande : \n %s" % (self.net, self.msg)
|
|
return txt
|
|
|
|
class Iproute2Error(Exception):
|
|
''' Exception invoquée lorsqu'un appel à iproute2 renvoie une erreur'''
|
|
def __init__(self, cmd, code, msg):
|
|
self.cmd = cmd
|
|
self.code = code
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
|
|
self.msg))
|
|
def __str__(self):
|
|
return "%s: %s %s" % ( self.cmd, self.code, self.msg)
|
|
|
|
class NoRtTable(Exception):
|
|
''' Exception invoquée lorsqu'une table de routage n'existe pas'''
|
|
def __init__(self, msg):
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"La table donnée n'existe pas : %s" %
|
|
self.msg)
|
|
def __str__(self):
|
|
return "La table donnée n'existe pas : %s" % self.msg
|
|
|
|
class ApplyRulesError(Exception):
|
|
''' Exception invoquée lorsque l'application des règles par
|
|
ip(6|)tables-restore échoue '''
|
|
def __init__(self, cmd, code, msg):
|
|
self.cmd = cmd
|
|
self.code = code
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"%s : %s \n %s" %
|
|
(self.cmd, self.code, self.msg))
|
|
def __str__(self):
|
|
return "%s : %s \n %s" % (self.cmd, self.code, self.msg)
|
|
|
|
class SysctlError(Exception):
|
|
''' Exception invoquée lorsque la modification d'un état dans /proc/sys
|
|
échoue '''
|
|
def __init__(self, cmd, code, msg):
|
|
self.cmd = cmd
|
|
self.code = code
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
|
|
self.msg))
|
|
def __str__(self):
|
|
return "%s: %s %s" % ( self.cmd, self.code, self.msg)
|
|
|
|
class Ip6tablesError(Exception):
|
|
def __init__(self, cmd, code, msg):
|
|
self.cmd = cmd
|
|
self.code = code
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"%s : %s %s" % (self.cmd, self.code,
|
|
self.msg))
|
|
def __str__(self):
|
|
return "%s: %s %s" % ( self.cmd, self.code, self.msg)
|
|
|
|
class RidError(Exception):
|
|
''' Exception invoquée lorsqu'il y a un soucis avec un rid fourni '''
|
|
def __init__(self, msg):
|
|
self.msg = msg
|
|
syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
|
|
def __str__(self):
|
|
return "%s" % self.msg
|
|
|
|
class MismatchRidIpProto(Exception):
|
|
''' Exception invoquée lorsqu'il y a un défaut d'appariement entre le rid
|
|
et le protocole ip concerné'''
|
|
def __init__(self, mrid, ip_proto, net):
|
|
self.mrid = mrid
|
|
self.ip_proto = ip_proto
|
|
self.net = net
|
|
self.msg = "Défaut d'appariement entre le rid %i (%s) \
|
|
et le protocole ip %i" % (self.mrid, self.net, self.ip_proto)
|
|
syslog.syslog(syslog.LOG_ERR, self.msg)
|
|
def __str__(self):
|
|
return self.msg
|
|
|
|
class IpProtoError(Exception):
|
|
''' Exception quand le protocole ip fourni est inconnu '''
|
|
def __init__(self, proto):
|
|
self.msg = "Le protocole ip %i est inconnu" % proto
|
|
syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
|
|
def __str__(self):
|
|
return "%s" % self.msg
|
|
|
|
class UnknowUserError(Exception):
|
|
''' Exception quand l'utilisateur demandé n'existe pas sur la machine '''
|
|
def __init__(self, user):
|
|
self.msg = "L'utilisateur %s est inconnu" % user
|
|
syslog.syslog(syslog.LOG_ERR,"%s" % self.msg)
|
|
def __str__(self):
|
|
return "%s" % self.msg
|
|
|
|
|
|
##############################################################################
|
|
#
|
|
# Déclaration des fonctions
|
|
#
|
|
##############################################################################
|
|
|
|
def gethostbyname(hostname):
|
|
hosts4=[]
|
|
hosts6=[]
|
|
try :
|
|
for host in socket.getaddrinfo(hostname,None,socket.AF_INET,socket.IPPROTO_IP,socket.AI_CANONNAME):
|
|
hosts4.append(host[4][0])
|
|
except(socket.gaierror): pass
|
|
try :
|
|
for host in socket.getaddrinfo(hostname,None,socket.AF_INET6,socket.IPPROTO_IP,socket.AI_CANONNAME):
|
|
hosts6.append(host[4][0])
|
|
except(socket.gaierror): pass
|
|
return (hosts4,hosts6)
|
|
|
|
def check_ip_proto(ip_proto):
|
|
''' Vérifie que le protocole ip fourni est valide '''
|
|
if ip_proto != 4 and ip_proto != 6:
|
|
raise IpProtoError(ip_proto)
|
|
|
|
def ipv6_addr(mac, net):
|
|
''' Renvoie l'adresse ipv6 d'auto-configuration de la mac sur le réseau '''
|
|
mac_s = mac.split(':')
|
|
eui = '2'+':'.join(mac_s[1:3])+'ff:fe'+':'.join(mac_s[3:5])+mac_s[5]
|
|
return re.sub(':/64', eui , prefix[dprefix[net]][0])
|
|
|
|
# TODO Fusionner les deux fonctions.
|
|
def iface(net):
|
|
'''Retourne l'interface réseau associée à un certain type de réseau
|
|
Pour l'instant on se base sur l'ipv4 pour identifier l'interface'''
|
|
if net == 'fil-v6':
|
|
net = 'fil'
|
|
if net == 'wifi-v6':
|
|
net = 'wifi'
|
|
cmd = "ip a show| egrep 'inet[^6]'"
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
output = msg.splitlines()
|
|
for line in output:
|
|
if AddrInNet(line.split()[1].split('/')[0], NETs[net]):
|
|
return line.split()[-1]
|
|
|
|
raise NoIface(net, msg)
|
|
|
|
def iface6(net):
|
|
'''Retourne l'interface réseau associée à
|
|
un certain type de réseau ipv6'''
|
|
cmd = "ip -6 a show"
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
output = msg.splitlines()
|
|
subnet = netaddr.IPNetwork(prefix[net][0])
|
|
i = 0
|
|
while i < len(output):
|
|
if re.match('^[0-9]+:', output[i]):
|
|
dev = re.sub('(@.*|:)', '', output[i].split()[1])
|
|
i = i + 1
|
|
while (i < len(output)) and ('inet6' in output[i]):
|
|
if netaddr.IPNetwork(output[i].split()[1]) == subnet:
|
|
return dev
|
|
else:
|
|
i = i + 2
|
|
else:
|
|
i = i + 1
|
|
|
|
raise NoIface(net, msg)
|
|
|
|
|
|
def check_table(table):
|
|
''' Vérifie que la table existe bien '''
|
|
ctables = open('/etc/iproute2/rt_tables', 'r')
|
|
rt_tables = ctables.readlines()
|
|
tables = [item for item in rt_tables if not re.match('#', item)]
|
|
if any(re.search(table, elt) for elt in tables):
|
|
return 0
|
|
else:
|
|
raise NoRtTable(table)
|
|
|
|
def custom_default_route(table, default, net, ip_proto = 4):
|
|
''' Permet de créer une route par default personnalisée'''
|
|
check_ip_proto(ip_proto)
|
|
check_table(table)
|
|
dev = iface(net)
|
|
cmd = "ip -%i route add table %s default via %s dev %s" % (ip_proto, table,
|
|
default, dev)
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
|
|
def custom_subnet_route(table, subnet, net, ip_proto = 4):
|
|
''' Permet de créer une route personnalisée'''
|
|
check_ip_proto(ip_proto)
|
|
check_table(table)
|
|
dev = iface(net)
|
|
cmd = "ip -%i route add table %s from %s dev %s" % (ip_proto, table, subnet,
|
|
dev)
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
|
|
def custom_subnet_via_route(table, subnet, net, via, ip_proto = 4):
|
|
''' Permet de créer une route personnalisée'''
|
|
check_ip_proto(ip_proto)
|
|
check_table(table)
|
|
dev = iface(net)
|
|
cmd = "ip -%i route add table %s from %s via %s dev %s" % (ip_proto, table,
|
|
subnet, via, dev)
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
|
|
def custom_ip_rule(table, subnet, ip_proto = 4):
|
|
''' Ajout d'un règle de routage personnalisée basée
|
|
sur un sous-reseaux de provenance'''
|
|
check_ip_proto(ip_proto)
|
|
check_table(table)
|
|
cmd = "ip -%i rule add from %s table %s" % (ip_proto, subnet, table)
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
|
|
|
|
def custom_mark_rule(table, fw_mark, ip_proto = 4):
|
|
''' Ajout d'une règle de routage personnalisée basée sur une marque'''
|
|
check_ip_proto(ip_proto)
|
|
check_table(table)
|
|
cmd = "ip -%i rule add fwmark %s table %s" % (ip_proto, fw_mark, table)
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise Iproute2Error(cmd, code, msg)
|
|
|
|
def srv(attribute):
|
|
''' Renvoie le (ou les) serveur(s) qui possède l'attribut '''
|
|
return [ server for server, attr in role.iteritems() if (attribute in attr)]
|
|
|
|
def ip_attribut(attribute, cl, ip_proto = 4):
|
|
''' Renvoie l'ip du serveur qui possède l'attribut '''
|
|
check_ip_proto(ip_proto)
|
|
host = srv(attribute)[0]
|
|
#Gestion erreur ?
|
|
# Autre net que fil ?
|
|
if ip_proto == 4:
|
|
return cl.search('host=%s.crans.org' % host)['machine'][0].ip()
|
|
elif ip_proto == 6:
|
|
mac = cl.search('host=%s.crans.org' % host)['machine'][0].mac()
|
|
if mac:
|
|
return ipv6_addr(mac, 'fil')
|
|
else:
|
|
print "Error" + " host=%s.crans.org" % name
|
|
|
|
def apply_rules(ip_proto = 4):
|
|
''' Applique les règles fournies dans le fichier passé en arguement '''
|
|
check_ip_proto(ip_proto)
|
|
f = open(output_file[ip_proto], 'r')
|
|
if ip_proto == 4:
|
|
cmd = '/sbin/iptables-restore'
|
|
elif ip_proto == 6:
|
|
cmd = '/sbin/ip6tables-restore'
|
|
try:
|
|
p1 = subprocess.Popen(cmd, stdin=f, stdout=subprocess.PIPE)
|
|
except OSError:
|
|
syslog.syslog(syslog.LOG_ERR,"Le fichier %s n'existe pas" % f.name)
|
|
print "Le fichier %s n'existe pas" % f.name
|
|
stdout, stderr = p1.communicate()
|
|
if p1.returncode < 0:
|
|
raise ApplyRulesError(cmd, -p1.returncode, 'STDOUT\n' + stdout +
|
|
'STDERR\n' + stderr )
|
|
|
|
def enable_forwarding(ip_proto = 4):
|
|
''' Autorise le forwarding sur la machine considérée '''
|
|
check_ip_proto(ip_proto)
|
|
cmd = 'echo 1 > /proc/sys/net/ipv%i/conf/all/forwarding' % ip_proto
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise SysctlError(cmd, code, msg)
|
|
|
|
def disable_forwarding(ip_proto = 4):
|
|
''' Désactive le forwarding sur la machine considérée '''
|
|
check_ip_proto(ip_proto)
|
|
cmd = 'echo 0 > /proc/sys/net/ipv%i/conf/all/forwarding' % ip_proto
|
|
code, msg = getstatusoutput(cmd)
|
|
if code:
|
|
raise SysctlError(cmd, code, msg)
|
|
|
|
|
|
def not_private(arg):
|
|
''' Retourne un boolén suivant que la fonction passé en argument
|
|
est privée ou non'''
|
|
if re.match('^_.*', str(arg)):
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
def open_pickle(ip_proto = 4):
|
|
''' Cette fonction ouvre un fichier contenant un dump d'une classe
|
|
Ip(|6)tables'''
|
|
check_ip_proto(ip_proto)
|
|
try:
|
|
f = open(file_pickle[ip_proto], 'r')
|
|
except OSError:
|
|
syslog.syslog(syslog.LOG_ERR,"Le fichier %s n'existe pas" %
|
|
file_pickle[ip_proto])
|
|
print "Le fichier %s n'existe pas" % file_pickle[ip_proto]
|
|
#XXX gestion des erreurs
|
|
return cPickle.load(f)
|
|
|
|
def save_pickle(ipt):
|
|
''' Cette fonction sauvegarde une instance de Ip(|6)tables'''
|
|
fout = open(file_pickle[ipt.version()], 'w')
|
|
cPickle.dump(ipt, fout)
|
|
fout.close()
|
|
|
|
def write_rules(ipt):
|
|
''' Cette fonction écrit les règles dans un fichier '''
|
|
# TODO vérifier que la fonctionn est _vraiement_ ipv4 aware
|
|
ip_proto = ipt.version()
|
|
fout = open(output_file[ip_proto], 'w')
|
|
|
|
# On recherche si la machine a des politiques particulières
|
|
if hostname in filter_policy.keys():
|
|
key = hostname
|
|
else:
|
|
key = 'default'
|
|
|
|
# On applique les politiques par défaut sur les chaînes canoniques
|
|
output = { 'filter' : Filter_policy_template % filter_policy[key],
|
|
'mangle' : Mangle_policy,
|
|
'raw' : Raw_policy
|
|
}
|
|
|
|
# On parcours une première fois l'instance pour initialiser correctement
|
|
# nos chaînes
|
|
for itables in ['filter', 'mangle', 'raw']:
|
|
for ichain in filter(not_private,
|
|
dir(ipt.__getattribute__(itables))):
|
|
if ichain.upper() not in default_chains:
|
|
if hasattr(ipt.__getattribute__(itables).__getattribute__(ichain),'items'):
|
|
output[itables] += ":%s - [0:0]\n" % ichain.upper()
|
|
|
|
# On ajoute maintenant les règles
|
|
for itables in ['filter', 'mangle', 'raw']:
|
|
for ichain in filter(not_private,
|
|
dir(ipt.__getattribute__(itables))):
|
|
for rule in ipt.__getattribute__(itables).__getattribute__(ichain).items:
|
|
output[itables] += '-A ' + ichain.upper() + ' ' + rule + '\n'
|
|
output['filter'] += '-A FORWARD -j REJECT\n'
|
|
|
|
# Ecriture dans le fichier
|
|
fout.writelines(output['filter'])
|
|
fout.write('COMMIT\n')
|
|
fout.write('')
|
|
fout.writelines(output['mangle'])
|
|
fout.write('COMMIT\n')
|
|
fout.write('')
|
|
fout.writelines(output['raw'])
|
|
fout.write('COMMIT\n')
|
|
|
|
fout.close()
|
|
|
|
|
|
def blacklist(ipt):
|
|
''' Permet de peupler les chaînes concernant les diverses blacklistes '''
|
|
|
|
blcklst = []
|
|
|
|
s = db.search('paiement=%s' % ann_scol)
|
|
if periode_transitoire:
|
|
tmp=db.search('paiement=%s' % ann_scol-1)
|
|
s['adherent'].entend(tmp['adherent'])
|
|
s['club'].entend(tmp['club'])
|
|
del tmp
|
|
|
|
for target in s['adherent'] + s['club']:
|
|
sanctions = target.blacklist_actif()
|
|
if [x for x in sanctions if x in blacklist_sanctions]:
|
|
blcklst.extend(target.machines())
|
|
|
|
s = db.search('mblacklist=*&paiement=%s' % ann_scol)
|
|
if periode_transitoire:
|
|
s['machine'].entend(db.s('mblacklist=*&paiement=%s' % ann_scol-1)['machine'])
|
|
|
|
for target in s['machine']:
|
|
sanctions = target.blacklist_actif()
|
|
if [x for x in sanctions if x in blacklist_sanctions]:
|
|
blcklst.append(target)
|
|
|
|
if ipt.filter.blacklist_src.items:
|
|
ipt.filter.blacklist_src.items[:] = []
|
|
if ipt.filter.blacklist_dst.items:
|
|
ipt.filter.blacklist_dst.items[:] = []
|
|
|
|
for machine in blcklst:
|
|
ipt.blacklist(machine)
|
|
|
|
return 0
|
|
|
|
def ports_io(ipt, machine, type_machine, dev_ext, dev_crans):
|
|
''' Fonction ouvrant les ports d'une machine selon le contenu de la base
|
|
LDAP'''
|
|
ports_in = { 'tcp' : machine.portTCPin(),
|
|
'udp' : machine.portUDPin() }
|
|
ports_out = { 'tcp' : machine.portTCPout(),
|
|
'udp' : machine.portUDPout() }
|
|
if ports_in.values() != [[], []]:
|
|
ipt.extcrans(type_machine, ports_in, machine.mac(),
|
|
dev_ext)
|
|
if ports_out.values() != [[], []]:
|
|
ipt.cransext(type_machine, ports_out, machine.mac(),
|
|
dev_crans)
|
|
|
|
def mac_ip(ipt, machines, types_machines):
|
|
'''Réalise une correspondance MAC-IP pour un réseau considéré
|
|
On vérifie d'abord que la MAC est connue et ensuite on n'accepte que les
|
|
adresses en eui64'''
|
|
macips(ipt, machines, types_machines)
|
|
# TODO Il faut raffiner avant de rajouter le wifi
|
|
for type_m in types_machines:
|
|
if not '-v6' in type_m:
|
|
dev = iface6(type_m)
|
|
ipt.filter.input('-i %s -s %s -j %s' % (dev, prefix[type_m][0],
|
|
'MAC' + type_m.upper()))
|
|
ipt.filter.input('-i %s -j IEUI64' % dev)
|
|
ipt.filter.ieui64('-i %s -s %s -m eui64 -j RETURN' % (dev,
|
|
prefix[type_m][0]))
|
|
|
|
ipt.filter.ieui64('-s fe80::/64 -m eui64 -j RETURN')
|
|
ipt.filter.ieui64('-j REJECT')
|
|
#ipt.filter.ieui64('-j REJECT')
|
|
|
|
def macips(ipt, machines, types_machines):
|
|
''' Construit la chaîne MAC '''
|
|
for machine in machines:
|
|
for type_m in types_machines:
|
|
if int(machine.rid()) in range(rid[type_m][0], rid[type_m][1]):
|
|
ipt.macip(machine.mac(), type_m)
|
|
break
|
|
for type_m in types_machines:
|
|
type_mm = re.sub('-', '', type_m)
|
|
getattr(ipt.filter,'mac' + type_mm)('-j REJECT')
|
|
#eval('ipt.filter.mac' + type_mm)('-j REJECT')
|
|
return 0
|
|
|
|
def ingress_filtering(ipt):
|
|
''' Réalise un filtre sur les plages d'IP susceptibles d'être routées '''
|
|
ip_proto = ipt.version()
|
|
if ip_proto == 6:
|
|
dev_ext = iface6('sixxs2')
|
|
# d'abord sur l'interface sur le réseau Cr@ns, on ne route que les
|
|
# paquet dans le bon subnet.
|
|
ipt.filter.ingress_filtering('-o %s -s %s -j RETURN' % (dev_ext,
|
|
prefix['subnet'][0]))
|
|
ipt.filter.ingress_filtering('-o %s -j LOG --log-prefix "BAD ROUTE "' %
|
|
dev_ext)
|
|
ipt.filter.ingress_filtering('-o %s -j REJECT' % dev_ext)
|
|
# de l'extérieur, on ne veut que des paquet ne provenant pas de notre
|
|
# réseau à destination de notre réseau
|
|
ipt.filter.ingress_filtering('-i %s ! -s %s -d %s -j RETURN' %
|
|
(dev_ext, prefix['subnet'][0], prefix['subnet'][0]))
|
|
ipt.filter.ingress_filtering('-i %s -j LOG --log-prefix "BAD SRC "' %
|
|
dev_ext)
|
|
ipt.filter.ingress_filtering('-i %s -j REJECT' % dev_ext)
|