scripts/gestion/gen_confs/firewall6.py
Pierre-Elliott Bécue 2a7dd72069 [Scripts] On rajoute un champ rid qui fait le lien avec les plages d'ip, et on rend le mid strictement croissant.
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
2013-01-23 03:16:50 +01:00

359 lines
14 KiB
Python
Executable file

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# FIREWALL6.PY -- Gestion du firewall pour l'ipv6
#
# 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, re, os, pwd
sys.path.append('/usr/scripts/gestion')
from ldap_crans import hostname
from config import conf_fw, rid, prefix, role, file_pickle, open_ports
from config import authorized_icmpv6, mac_wifi, adm_only, adm_users
from config import udp_torrent_tracker
from ipt import *
# On invoque Ip6tables
ip6tables = Ip6tables()
def aide():
""" Affiche l'aide pour utiliser le script"""
print """
Usage:
%(script)s start : Démarrage du firewall
%(script)s stop : Arrêt du firewall
%(script)s restart : Redémarrage du firewall
""" % { 'script' : sys.argv[0].split('/')[-1] }
def ports(dev_ip6, dev_crans):
''' Ouvre les ports '''
for machine in machines :
for type_machine in ['fil', 'fil-v6', 'wifi', 'wifi-v6']:
if int(machine.rid()) in range(rid[type_machine][0],
rid[type_machine][1]):
ports_io(ip6tables, machine, type_machine, dev_ip6, dev_crans)
#Protection contre les attaques brute-force
# XXX FIXIT !!!
# Il semble qu'il faille un kernel >= .29 et iptables >= 1.4.3
# http://netfilter.org/projects/iptables/files/changes-iptables-1.4.3.txt
# ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m \
#recent --name SSH --set ' % dev_ip6)
# ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW -m \
#recent --name SSH --update --seconds 60 --hitcount 4 --rttl -j DROP' %
# dev_ip6)
# ip6tables.filter.forward('-i %s -p tcp --dport ssh -m state --state NEW \
#-j ACCEPT' % dev_ip6)
for proto in open_ports.keys():
ip6tables.filter.forward('-i %s -p %s -m multiport --dports %s -j ACCEPT' % (dev_ip6, proto, open_ports[proto]))
for type_machine in ['fil', 'fil-v6', 'wifi', 'wifi-v6']:
ip6tables.filter.forward('-i %s -d %s -j %s' % (dev_ip6,
prefix[dprefix[type_machine]][0], 'EXT' + re.sub('-', '',
type_machine.upper())))
eval('ip6tables.filter.ext' + re.sub('-', '', type_machine))('-j REJECT --reject-with icmp6-port-unreachable')
# Port ouvert CRANS->EXT
ip6tables.filter.forward('-i %s -p udp -m multiport --dports 0:136,140:65535 -j ACCEPT' % dev_crans)
# FIXME: proxy transparent -> port 80
ip6tables.filter.forward('-i %s -p tcp -m multiport --dports 0:24,26:79,80,81:134,136,140:444,446:65535 -j ACCEPT' % dev_crans)
for type_machine in ['fil', 'fil-v6', 'wifi', 'wifi-v6']:
ip6tables.filter.forward('-i %s -s %s -j %s' % (dev_crans,
prefix[dprefix[type_machine]][0], 'CRANS' + re.sub('-', '',
type_machine.upper())))
eval('ip6tables.filter.crans' + re.sub('-', '', type_machine))('-j REJECT --reject-with icmp6-port-unreachable')
def basic_fw():
''' Met en place un firewall de base commun à tous les serveurs'''
# On rejete les ra.
ip6tables.filter.input('-p icmpv6 -m icmp6 --icmpv6-type router-advertisement -j REJECT')
# On accepte NDP sauf les RA, sinon, les REJECT ne fonctionnent pas
for icmpv6 in ['neighbour-solicitation','neighbour-advertisement','redirect','router-solicitation']:
ip6tables.filter.input('-p icmpv6 -m icmp6 --icmpv6-type %s -j ACCEPT' % icmpv6)
ip6tables.filter.output('-p icmpv6 -m icmp6 --icmpv6-type %s -j ACCEPT' % icmpv6)
# On ne vérifie rien sur les ip qui ne sont pas dans notre prefix
ip6tables.filter.ieui64('! -s %s -j RETURN' % prefix['subnet'][0])
# XXX Code spécifique pour le wifi, peut être pas la manière la plus
# élégante, mais peut être la plus pratique
if hostname in role.keys() and ('wifi-router' not in role[hostname]):
ip6tables.filter.ieui64('-i %s -s %s -m mac --mac-source %s -j RETURN'
% (iface6('fil'), prefix['wifi'][0], mac_wifi))
# Correspondance MAC-IP
mac_ip(ip6tables, machines, ['fil', 'fil-v6', 'adm'])
#wifi ?
def main_router():
''' Firewall pour le router principal '''
#TODO : réseaux non routable, interaction avec generate
# il faut aussi voir les conditions pour passer la ctstate avant MAC-IP
# (normalement, il n'y a pas de problèmes.
# et peut être aussi avant blackliste (il faut prévoir un script qui
# enlève les entrées dans la conntract lors de la mise en place de la
# blackliste
# route pour le proxy transparent
# Pour plus tard
# custom_default_route('proxy', ip_attribut('main-proxy', cl, 6), 'fil', 6)
# custom_mark_rule('proxy', conf_fw.mark['proxy'], 6)
# ip6tables.mangle.prerouting("-p tcp -d ! %s --dport 80 -j MARK --set-mark\
# %s" % (prefix['fil'][0], conf_fw.mark['proxy']))
#
dev_crans = iface6('fil')
dev_ip6 = iface6('sixxs2')
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % dev_crans)
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % dev_ip6 )
# Les blacklistes
# Si on les met après la règle conntrack, une connexion existante ne sera
# pas sevrée et dinc avec un tunnel ssh idoine, la blacklist aurait aucun
# effet.
# Alternative : flusher la table conntrack des entrées concernant cette
# machine.
blacklist(ip6tables)
ip6tables.filter.forward('-i %s -j BLACKLIST_SRC' % dev_crans)
#~ ip6tables.filter.forward('-o %s -j BLACKLIST_SRC' % dev_ip6)
ip6tables.filter.forward('-i %s -j BLACKLIST_DST' % dev_ip6)
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "info_hash=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-m string --algo kmp --string "/scrape?" -j LOG --log-level notice --log-prefix "TRACKER_TORRENT: "')
ip6tables.filter.tracker_torrent('-m string --algo kmp --string "/scrape?" -j REJECT --reject-with icmp6-adm-prohibited')
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "peer_id=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "port=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "uploaded=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "downloaded=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-m string --algo kmp ! --string "left=" -j ACCEPT')
ip6tables.filter.tracker_torrent('-j LOG --log-level notice --log-prefix "TRACKER_TORRENT: "')
ip6tables.filter.tracker_torrent('-j REJECT --reject-with icmp6-adm-prohibited')
ip6tables.filter.forward('-p tcp -m string --algo kmp --string "GET /" -j TRACKER_TORRENT')
ip6tables.filter.forward('-p tcp -m string --algo kmp --string "get /" -j TRACKER_TORRENT')
for tracker in udp_torrent_tracker:
for dest in gethostbyname(tracker)[1]:
ip6tables.filter.forward('-p udp -d %s -j LOG --log-level notice --log-prefix "TRACKER:%s "' % (dest,(tracker[:20]) if len(tracker) > 20 else tracker))
ip6tables.filter.forward('-p udp -d %s -j REJECT --reject-with icmp6-adm-prohibited' % dest)
ip6tables.filter.forward('-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT')
# On filtre les réseaux non routable et aussi on accepte en entrée
# que les paquets dont la source n'est pas notre plage, pour éviter
# http://travaux.ovh.net/?do=details&id=5183
ingress_filtering(ip6tables)
ip6tables.filter.forward('-j INGRESS_FILTERING')
# Pour les autres connections
for type_m in [i for i in ['fil', 'fil-v6'] if not 'v6' in i]:
ip6tables.filter.mac('-s %s -j %s' % (prefix[type_m][0], 'MAC' +
type_m.upper()))
ip6tables.filter.forward('-i %s -j MAC' % dev_crans)
ip6tables.filter.forward('-i %s -j FEUI64' % dev_crans)
ip6tables.filter.feui64('-s %s -m mac --mac-source %s -j RETURN' %
(prefix['wifi'][0], mac_wifi))
ip6tables.filter.feui64('-s %s -m eui64 -j RETURN' % prefix['fil'][0])
ip6tables.filter.feui64('-j REJECT')
# Rien ne passe vers adm
# est ce que du local est gêné par le règle ?
ip6tables.filter.forward('-d %s -j REJECT --reject-with icmp6-addr-unreachable' % (prefix['adm'][0]))
# on accepte les ping
for icmpv6 in authorized_icmpv6:
ip6tables.filter.forward('-p icmpv6 -m icmp6 --icmpv6-type %s -j ACCEPT' % icmpv6)
ip6tables.filter.forward('-p icmpv6 -j REJECT')
# cf https://www.sixxs.net/faq/connectivity/?faq=filters
ip6tables.filter.forward('-m rt --rt-type 0 -j REJECT')
# Ouverture des ports
ports(dev_ip6, dev_crans)
# On met en place le forwarding
enable_forwarding(6)
def wifi_router():
''' Firewall pour le router du wifi '''
# pour plus tard
# custom_default_route('proxy', ip_attribut('main-proxy', cl, 6), 'wifi', 6)
# custom_mark_rule('proxy', conf_fw.mark['proxy'], 6)
# ip6tables.mangle.prerouting("-p tcp -d ! %s --dport 80 -j MARK --set-mark \
#%s" % (prefix['fil'][0], conf_fw.mark['proxy']))
dev_crans = iface6('fil')
dev_wifi = iface6('wifi')
# Stop aux RA
ip6tables.filter.forward('-p icmpv6 -m icmp6 --icmpv6-type router-advertisement -j REJECT')
# Un peu moche, mais il faut supprimer les dernière règles dans IEUI64
# avant de rajouter la règle pour les machines wifi.
l = len(ip6tables.filter.ieui64.items)
del ip6tables.filter.ieui64.items[l-1]
del ip6tables.filter.ieui64.items[l-2]
mac_ip(ip6tables, machines, ['wifi', 'wifi-v6'])
liste_net = ['fil', 'fil-v6', 'wifi', 'wifi-v6']
for type_m in [i for i in liste_net if not 'v6' in i]:
ip6tables.filter.mac('-s %s -j %s' % (prefix[type_m][0], 'MAC' +
type_m.upper()))
ip6tables.filter.feui64('-s %s -m eui64 -j RETURN'
% prefix[type_m][0])
ip6tables.filter.feui64('-j REJECT')
ip6tables.filter.forward('-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT')
ip6tables.filter.forward('-j MAC')
ip6tables.filter.forward('-j FEUI64')
# On met en place le forwarding
enable_forwarding(6)
#def main_proxy():
def adherents_server():
''' Firewall pour le serveur des adhérents '''
dev_adm = iface6('adm')
# On fait attention à ce qui sort
ip6tables.filter.output('-o lo -j ACCEPT')
ip6tables.filter.output('-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT')
ip6tables.filter.output('-o %s -j SRV_OUT_ADM' % dev_adm)
# Chaîne SRV_OUT_ADM
# Seul certains users ont le droit de communiquer sur le vlan adm
for user in adm_users:
try:
u_uid = pwd.getpwnam(user)[2]
except KeyError:
# raise UnknowUserError(user)
continue
ip6tables.filter.srv_out_adm('-m owner --uid-owner %d -j ACCEPT' %
pwd.getpwnam(user)[2])
# LDAP et DNS toujours joignable
ip6tables.filter.srv_out_adm('-p tcp --dport ldap -j ACCEPT')
ip6tables.filter.srv_out_adm('-p tcp --dport domain -j ACCEPT')
ip6tables.filter.srv_out_adm('-p udp --dport domain -j ACCEPT')
# Pour le nfs (le paquet à laisser passer n'a pas d'owner)
# ip6tables.filter.srv_out_adm('-d nfs.adm.crans.org -m owner ! \
#--uid-owner 0 -j REJECT --reject-with icmp6-adm-prohibited')
# ip6tables.filter.srv_out_adm('-d nfs.adm.crans.org -j ACCEPT')
## A corriger, le nfs a pas l'air de faire de l'ipv6 de toute façon.
# On arrête tout
ip6tables.filter.srv_out_adm('-j REJECT --reject-with icmp6-adm-prohibited')
def appt_proxy():
pass
def start():
''' Démarre le firewall sur la machine considérée '''
# On véifie si le serveur n'est pas que sur le vlan adm. Dans ce cas on ne
# fait rien
if hostname in adm_only:
print "Rien n'a faire, ce serveur est adm-only"
exit(0)
# On rempli machines
global machines
machines = db.all_machines(graphic = True)
print hostname
# On supprime les anciennes règles si elles existent.
try:
os.remove(output_file[6])
os.remove(file_pickle[6])
except:
pass
# On recherche si la machine a des attributs particuliers.
if hostname in role.keys():
if 'external' in role[hostname]:
print "Il faut encore implémenter un firewall pour les serveurs à \
l'extérieur du réseau Cr@ns"
exit(0)
if 'sniffer' not in role[hostname]:
basic_fw()
for func in role[hostname]:
if func != 'sniffer':
eval(re.sub('-', '_', func))()
else:
basic_fw()
# On écrit les données dans un fichier
write_rules(ip6tables)
# On les applique
apply_rules(6)
# Sauvegarde de l'instance
save_pickle(ip6tables)
return 0
def stop():
''' Vide les tables '''
# TODO
# il manque une gestion des règles de routage spéciales réalisées à
# l'aide de ip rule et ip route
# idée faire une classe et la stocker en pickle pour savoir ce qui a été
# ajouté
# On fixe comme règle juste les déclarations de police par défaut
write_rules(ip6tables)
# On les applique
apply_rules(6)
disable_forwarding(6)
return 0
def restart():
''' Redémarre le firewall '''
# On ôte toutes les règles
stop()
# On met les règles en place
start()
return 0
if __name__ == '__main__':
if len(sys.argv) != 2:
aide()
sys.exit(-1)
if sys.argv[1] in [ 'start', 'stop', 'restart' ]:
eval(sys.argv[1])()
else:
aide()
sys.exit(-1)