356 lines
13 KiB
Python
Executable file
356 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 rid, prefix, role, file_pickle, open_ports, p2p
|
|
from config import authorized_icmpv6, 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_list):
|
|
''' Ouvre les ports '''
|
|
for machine in machines :
|
|
for type_machine in ['fil', 'adherents-v6', 'wifi', 'wifi-adh-v6', 'serveurs']:
|
|
for plage in rid[type_machine]:
|
|
if int(machine.rid()) in range(plage[0], plage[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', 'adherents-v6', 'wifi', 'wifi-adh-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', 'adherents-v6', 'wifi', 'wifi-adh-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', 'adherents-v6', 'adm', 'wifi', 'wifi-adh-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 )
|
|
|
|
# On force le /32 de google à passer en ipv4 pour tester si ça soulage le tunnel ipv6
|
|
ip6tables.filter.forward('-o %s -p tcp -d 2a00:1450:4006::/32 -j REJECT' % 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', 'adherents-v6', 'wifi', 'wifi-adh-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 routeur_nat64():
|
|
''' Firewall pour le nat64 '''
|
|
|
|
dev_crans = iface6('fil')
|
|
dev_adm = iface6('adm')
|
|
dev_v6only = iface6('v6only')
|
|
|
|
# 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_v6only)
|
|
ip6tables.filter.forward('-i %s -j BLACKLIST_DST' % dev_crans)
|
|
|
|
|
|
ip6tables.filter.forward('-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT')
|
|
|
|
# Pour les autres connections
|
|
for type_m in [i for i in ['fil', 'adherents-v6', 'wifi', 'wifi-adh-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)
|
|
|
|
# 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 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)
|