scripts/gestion/gen_confs/firewall6.py
Valentin Samir 871cf91d7b [firewall6] s/DROP/REJECT/g
Ignore-this: 1831804fe846ba8186466adadba594c5

darcs-hash:20121004021220-3a55a-0c84daf10f4a414e8e7548dbfcfa0f22ef731c4b.gz
2012-10-04 04:12:20 +02:00

354 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, mid, prefix, role, file_pickle, open_ports
from config import authorized_icmpv6, mac_wifi, adm_only, adm_users
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.id()) in range(mid[type_machine][0],
mid[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 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 )
udp_torrent_tracker = {
'tracker.ccc.de':[['2001:67c:20a0:7::2',80]],
'tracker.istole.it':[['2a00:1a28:1151:6:230:48ff:fed4:ee8c',80]],
}
# 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('-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.values():
for dest in tracker:
ip6tables.filter.forward('-p udp -d %s --dport %s -j LOG --log-level notice --log-prefix "TRACKER_TORRENT: "' % (dest[0],dest[1]))
ip6tables.filter.forward('-p udp -d %s --dport %s -j REJECT --reject-with icmp6-adm-prohibited' % (dest[0],dest[1]))
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 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)