#!/usr/bin/python # -*- coding: utf-8 -*- # # FIREWALL6.PY -- Gestion du firewall pour l'ipv6 # # Copyright (C) 2010 Olivier Huber # Authors: Olivier Huber # # 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 . 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 tracker_torrent(ip6tables): for tracker in 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 ôte toutes les règles stop() # On met 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)