scripts/gestion/gen_confs/firewall6.py
Vincent Le Gallic 2ce185720e Éclatement de config.py en plusieurs sous-module de config. L'API reste à peu près la même, il faut juste penser à import config.submodule avant d'utilisr config.submodule (confid.dns, config.upload par exemple)
Tous les autres fichiers modifiés le sont pour compatibilité avec ce changement.

Ce commit implique des commits du même genre dans l'intranet2, lc_ldap et bcfg2.
2013-03-26 16:24:31 +01:00

336 lines
13 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, p2p
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 tracker_torrent(ip6tables):
for tracker in p2p.udp_torrent_tracker:
for dest in gethostbyname(tracker)[1]:
ip6tables.filter.tracker_torrent('-p udp -d %s -j LOG --log-level notice --log-prefix "TRACKER:%s "' % (dest,(tracker[:20]) if len(tracker) > 20 else tracker))
ip6tables.filter.tracker_torrent('-p udp -d %s -j REJECT --reject-with icmp6-adm-prohibited' % dest)
ip6tables.filter.tracker_torrent('-p udp -j RETURN')
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')
def ports(dev_ip6, dev_list):
''' Ouvre les ports '''
for machine in machines :
for type_machine in ['fil', 'fil-v6', 'wifi', 'wifi-v6', 'serveurs']:
if int(machine.rid()) in range(rid[type_machine][0],
rid[type_machine][1]):
for dev in dev_list:
ports_io(ip6tables, machine, type_machine, dev_ip6, dev)
#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
for dev in dev_list:
ip6tables.filter.forward('-i %s -p udp -m multiport --dports 0:136,140:65535 -j ACCEPT' % dev)
# 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)
for type_machine in ['fil', 'fil-v6', 'wifi', 'wifi-v6']:
ip6tables.filter.forward('-i %s -s %s -j %s' % (iface6(type_machine),
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 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')
# On ne vérifie rien sur les ip qui ne sont pas dans notre prefix
for net in prefix['subnet']:
ip6tables.filter.ieui64('! -s %s -j RETURN' % net)
# Correspondance MAC-IP
mac_ip(ip6tables, machines, ['fil', 'fil-v6', 'adm', 'wifi', 'wifi-v6', 'serveurs'])
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
dev_crans = iface6('fil')
dev_wifi = iface6('wifi')
dev_ip6 = iface6('sixxs2')
ip6tables.mangle.forward("-o %s -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu" % dev_ip6)
ip6tables.mangle.forward("-o %s -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu" % dev_wifi)
ip6tables.mangle.forward("-o %s -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu" % dev_crans)
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_MAC_IP "' % dev_crans)
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_MAC_IP "' % dev_wifi)
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_wifi)
ip6tables.mangle.prerouting('-i %s -m state --state NEW -j LOG --log-prefix "LOG_ALL "' % dev_ip6 )
# Ipv6 sur évènementiel, on ne laisse sortir que si ça vient de la mac d'ytrap-llatsni
ip6tables.filter.forward('-o %s -d 2a01:240:fe3d:d2::/64 -j ACCEPT' % dev_crans)
ip6tables.filter.forward('-o %s -m mac --mac-source 00:00:6c:69:69:01 -s 2a01:240:fe3d:d2::/64 -j ACCEPT' % 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('-i %s -j BLACKLIST_SRC' % dev_wifi)
ip6tables.filter.forward('-i %s -j BLACKLIST_DST' % dev_ip6)
#tracker_torrent(ip6tables)
#ip6tables.filter.forward('-o %s -p udp -j TRACKER_TORRENT' % dev_ip6 )
#ip6tables.filter.forward('-o %s -p tcp -m string --algo kmp --string "GET /" -j TRACKER_TORRENT' % dev_ip6)
#ip6tables.filter.forward('-o %s -p tcp -m string --algo kmp --string "get /" -j TRACKER_TORRENT' % dev_ip6)
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', 'wifi', 'wifi-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 MAC' % dev_wifi)
# 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]))
# 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, dev_wifi])
# On met en place le forwarding
enable_forwarding(6)
def wifi_router():
''' Firewall pour le router du wifi '''
# Le wifi est maintenant routé directement sur komaz.
# On utilise donc directement main_router
pass
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 remplace les règles en place
start()
return 0
def blacklist_main():
fw6 = Update()
fw6.blacklist(6)
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])()
elif sys.argv[1] == 'blacklist':
blacklist_main()
else:
aide()
sys.exit(-1)