
On peut probablement faire mieux pour ne filtrer que les gens à jour de cotiz mais j'ai la flemme …
861 lines
31 KiB
Python
861 lines
31 KiB
Python
#!/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 sys
|
|
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, rid_primaires
|
|
from config import blacklist_sanctions, blacklist_sanctions_soft, blacklist_bridage_upload, file_pickle, ann_scol, periode_transitoire
|
|
from iptools import AddrInNet
|
|
from ridtools import Rid, find_rid_plage
|
|
import subprocess
|
|
import netaddr
|
|
import ip6tools
|
|
|
|
blacklist_sanctions_ipv6 = list(blacklist_sanctions)
|
|
blacklist_sanctions_ipv6.extend(blacklist_sanctions_soft)
|
|
blacklist_sanctions_ipv6.extend(blacklist_bridage_upload)
|
|
|
|
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 = { 'adherents': 'adherents', 'fil' : 'fil', 'adherents-v6' : 'fil', 'adm' : 'adm', 'wifi' : 'wifi',
|
|
'wifi-adh-v6' : 'wifi','personnel-ens':'personnel-ens', 'serveurs':'serveurs', 'wifi-adh':'wifi',
|
|
'bornes' : 'wifi', 'adm-v6':'adm', 'serveurs-v6':'serveurs'}
|
|
|
|
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.macserveurs = Chain()
|
|
self.macadherentsv6 = Chain()
|
|
self.macwifiadhv6 = Chain()
|
|
self.extadherentsv6 = Chain()
|
|
self.extwifiadhv6 = Chain()
|
|
self.cransadherentsv6 = Chain()
|
|
self.cranswifiadhv6 = 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'''
|
|
if '<automatique>' == mac:
|
|
return
|
|
tab = {'serveurs' : 'fil' }
|
|
if type_m in tab.keys(): type_m = tab[type_m]
|
|
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', 'adherents-v6' : 'extfilv6',
|
|
'wifi' : 'extwifi',
|
|
'wifi-adh-v6' : 'extwifiv6',
|
|
'serveurs':'extfil' }
|
|
ip = ipv6_addr(mac, type_machine)
|
|
if not ip:
|
|
return
|
|
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', 'adherents-v6' :
|
|
'cransfilv6', 'wifi' : 'cranswifi',
|
|
'wifi-adh-v6' : 'cranswifiv6',
|
|
'serveurs':'cransfil' }
|
|
ip = ipv6_addr(mac, type_machine)
|
|
if not ip:
|
|
return
|
|
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.rid())
|
|
ip = ""
|
|
for type_m, plages in rid_primaires.iteritems():
|
|
if type_m in ['special']:
|
|
continue
|
|
for plage in plages:
|
|
if ident in range(plage[0], plage[1]):
|
|
ip = ipv6_addr(machine.mac(), type_m)
|
|
mac=machine.mac()
|
|
break
|
|
|
|
if ip:
|
|
self.filter.blacklist_dst('-d %s -j REJECT --reject-with icmp6-adm-prohibited' % ip)
|
|
elif mac == '<automatique>':
|
|
# else: si mac auto, c'est normal de pas avoir pu calculer l'ip
|
|
return
|
|
else:
|
|
print (u"Ipv6 de la machine %s impossible à calculer" % machine.nom()).encode('utf-8')
|
|
self.filter.blacklist_src('-m mac --mac-source %s -j REJECT --reject-with icmp6-port-unreachable' % mac)
|
|
|
|
|
|
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:
|
|
net, _ = find_rid_plage(r)
|
|
if net == "Inconnu":
|
|
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)
|
|
|
|
# On supprime les anciennes règles si elles existent.
|
|
try:
|
|
os.remove(output_file[ip_proto])
|
|
except:
|
|
pass
|
|
|
|
# On initialise un pare-feu vide
|
|
ip6tables = Ip6tables()
|
|
blacklist(ip6tables)
|
|
|
|
# On écrit les données dans un fichier
|
|
write_rules(ip6tables)
|
|
|
|
#On vide les chaines des BLACKLIST_*
|
|
flush_chain(ip_proto, ['BLACKLIST_DST', 'BLACKLIST_SRC'])
|
|
# On les applique en concervant les chaines existantes
|
|
apply_rules(ip_proto, no_flush=True)
|
|
|
|
|
|
#### BACKWARD compatibilité
|
|
ipt_p = open_pickle(ip_proto)
|
|
blacklist(ipt_p)
|
|
# On écrit et applique les règles <-- plus la peine de restorer tout, on ne traite que les chaines BLACKLIST_* ci dessus.
|
|
# On pickel toujours pour la compatibilité avec les autres fonctions de la classe Update
|
|
#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', 'adherents-v6', 'adm', 'serveurs']:
|
|
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', 'adherents-v6', 'adm', 'serveurs'])
|
|
|
|
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 '''
|
|
return ip6tools.mac_to_ipv6(prefix[dprefix[net]][0], mac)
|
|
|
|
def mac_addr(ipv6):
|
|
''' Renvoie l'adresse mac de l'ipv6 d'auto-configuration '''
|
|
ipv6_s= ipv6.split(':')[4:]
|
|
mac=''
|
|
if ipv6_s[1].endswith('ff') and ipv6_s[2].startswith('fe'):
|
|
elt = "%04x" % int(ipv6_s[0], 16)
|
|
mac += "%02x" % (int(elt[0:2],16) ^ 0x02) + ':' + elt[2:]
|
|
elt = "%04x" % int(ipv6_s[1], 16)
|
|
mac += ':' + elt[0:2]
|
|
elt = "%04x" % int(ipv6_s[2], 16)
|
|
mac += ':' + elt[2:]
|
|
elt = "%04x" % int(ipv6_s[3], 16)
|
|
mac += ':' + elt[0:2] + ':' + elt[2:]
|
|
return mac
|
|
return None
|
|
|
|
# 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 == 'adherents-v6':
|
|
net = 'fil'
|
|
if net == 'wifi-adh-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 flush_chain(ip_proto = 4, chain_list = []):
|
|
check_ip_proto(ip_proto)
|
|
f = open(output_file[ip_proto], 'r')
|
|
if ip_proto == 4:
|
|
cmd = '/sbin/iptables'
|
|
elif ip_proto == 6:
|
|
cmd = '/sbin/ip6tables'
|
|
|
|
for chain in chain_list:
|
|
run = [cmd, '-F', chain]
|
|
try:
|
|
p1 = subprocess.Popen(run, stdin=f, stdout=subprocess.PIPE)
|
|
except OSError:
|
|
syslog.syslog(syslog.LOG_ERR,"La chaine %s n'existe pas" % chain)
|
|
print "La chaine %s n'existe pas" % chain
|
|
stdout, stderr = p1.communicate()
|
|
|
|
def apply_rules(ip_proto = 4, no_flush=False):
|
|
''' 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'
|
|
if no_flush:
|
|
cmd = [cmd, '--noflush']
|
|
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') and 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('nom=*')
|
|
|
|
for target in s['adherent'] + s['club']:
|
|
sanctions = target.blacklist_actif()
|
|
if [x for x in sanctions if x in blacklist_sanctions_ipv6]:
|
|
blcklst.extend(target.machines())
|
|
|
|
s = db.search('mblacklist=*&paiement=%s' % ann_scol)
|
|
if periode_transitoire:
|
|
s['machine'].extend(db.search('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_ipv6]:
|
|
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[:] = []
|
|
|
|
print "%s machines blacklistées" % len(blcklst)
|
|
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'''
|
|
tab = {'serveurs' : 'fil' }
|
|
macips(ipt, machines, types_machines)
|
|
for type_m in types_machines:
|
|
if not '-v6' in type_m and not type_m in tab.keys():
|
|
try:
|
|
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)
|
|
except NoIface as e:
|
|
sys.stderr.write("NoIface: %s" % e)
|
|
|
|
# On active les extensions de vie privée
|
|
for net in prefix['subnet']:
|
|
ipt.filter.ieui64('-s %s -j RETURN' % net)
|
|
# Pour le lien local, on n'accepte que les eui64
|
|
ipt.filter.ieui64('-s fe80::/64 -m eui64 -j RETURN')
|
|
ipt.filter.ieui64('-j REJECT')
|
|
|
|
def macips(ipt, machines, types_machines):
|
|
''' Construit la chaîne MAC '''
|
|
tab = {'serveurs' : 'fil' }
|
|
|
|
for machine in machines:
|
|
for type_m in types_machines:
|
|
for plage in rid[type_m]:
|
|
if int(machine.rid()) in range(plage[0], plage[1]):
|
|
ipt.macip(machine.mac(), type_m)
|
|
break
|
|
|
|
for type_m in types_machines:
|
|
if not type_m in tab.keys():
|
|
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)
|