From d25395f8bd135d003ff8674d4e4e221e0abde924 Mon Sep 17 00:00:00 2001 From: chirac Date: Mon, 16 Jul 2018 00:40:40 +0200 Subject: [PATCH] Draft firewall --- main.py | 725 ++++++++++++++++++++++++++++++++++++++--------- templates/hp.tpl | 184 ------------ 2 files changed, 595 insertions(+), 314 deletions(-) delete mode 100644 templates/hp.tpl diff --git a/main.py b/main.py index d26f237..ae3e745 100755 --- a/main.py +++ b/main.py @@ -9,6 +9,9 @@ from jinja2 import Environment, FileSystemLoader import requests import base64 import json +import subprocess +import socket +import argparse config = ConfigParser() config.read('config.ini') @@ -23,151 +26,613 @@ client_hostname = socket.gethostname().split('.', 1)[0] all_switchs = api_client.list("switchs/ports-config/") -# Création de l'environnement Jinja -ENV = Environment(loader=FileSystemLoader('.')) - -# Création du template final avec les valeurs contenues dans le dictionnaire "valeurs" - Ces valeurs sont positionnées dans un objet "temp", qui sera utilisé par le moteur, et que l'on retrouve dans le template. - - -class Switch: +class iptables: def __init__(self): - self.additionnal = None - self.all_vlans = api_client.list("machines/vlan/") - self.settings = api_client.view("preferences/optionaltopologie/") - # Import du fichier template dans une variable "template" - self.hp_tpl = ENV.get_template("templates/hp.tpl") - self.conf = None - self.name = None - self.switch = None - self.headers = None - self.creds_dict = None + self.nat4 = "\n*nat" + self.mangle4 = "\n*mangle" + self.filter4 = "\n*filter" + self.nat6 = "\n*nat" + self.mangle6 = "\n*mangle" + self.filter6 = "\n*filter" + self.subnet_ports = api_client.list("firewall/subnet-ports/") + self.verbose = False + self.action = None + self.export = False - def get_conf_file_name(self): - return self.switch["short_name"] + ".conf" + def commit(self, chain): + self.add(chain, "COMMIT\n") - def preprocess_hp(self): - """Prérempli certains valeurs renvoyées directement à jinja, pour plus de simplicité""" - - def add_to_vlans(vlans, vlan, port, tagged=True): - if not vlan['vlan_id'] in vlans: - if not tagged: - vlans[vlan['vlan_id']] = {'ports_untagged' : [str(port['port'])], 'ports_tagged' : [], 'name' : vlan['name']} - else: - vlans[vlan['vlan_id']] = {'ports_tagged' : [str(port['port'])], 'ports_untagged' : [], 'name' : vlan['name']} - else: - if not tagged: - vlans[vlan['vlan_id']]['ports_untagged'].append(str(port['port'])) - else: - vlans[vlan['vlan_id']]['ports_tagged'].append(str(port['port'])) + def commit_filter(self): + self.add("filter4", "COMMIT\n") + self.add("filter6", "COMMIT\n") - vlans = dict() + def commit_mangle(self): + self.add("mangle4", "COMMIT\n") + self.add("mangle6", "COMMIT\n") - for port in self.switch['ports']: - if port['get_port_profil']['vlan_untagged']: - add_to_vlans(vlans, port['get_port_profil']['vlan_untagged'], port, tagged=False) - if port['get_port_profil']['vlan_tagged']: - for vlan in port['get_port_profil']['vlan_tagged']: - add_to_vlans(vlans, vlan, port) + def commit_nat(self): + self.add("nat4", "COMMIT\n") + self.add("nat6", "COMMIT\n") - #Trie les ip par vlan, et les ajoute ainsi que les subnet - for ip, subnet in self.switch["interfaces_subnet"].items(): - vlans[subnet[0]["vlan_id"]].setdefault("ipv4", {}) - vlans[subnet[0]["vlan_id"]]["ipv4"][ip] = subnet - for ipv6, subnet in self.switch["interfaces6_subnet"].items(): - vlans[subnet["vlan_id"]].setdefault("ipv6", {}) - vlans[subnet["vlan_id"]]["ipv6"][ipv6] = subnet + def add(self, chain, value): + setattr(self, chain, getattr(self, chain) + "\n" + value) - #Regroupement des options par vlans : dhcp_soop,arp, et dhcpv6, ainsi que igmp, mld , ra-guard et loop_protect - arp_protect_vlans = [vlan["vlan_id"] for vlan in self.all_vlans if vlan["arp_protect"]] - dhcp_snooping_vlans = [vlan["vlan_id"] for vlan in self.all_vlans if vlan["dhcp_snooping"]] - dhcpv6_snooping_vlans = [vlan["vlan_id"] for vlan in self.all_vlans if vlan["dhcpv6_snooping"]] - igmp_vlans = [vlan["vlan_id"] for vlan in self.all_vlans if vlan["igmp"]] - mld_vlans = [vlan["vlan_id"] for vlan in self.all_vlans if vlan["mld"]] - ra_guarded = [str(port['port']) for port in self.switch['ports'] if port['get_port_profil']['ra_guard']] - loop_protected = [str(port['port']) for port in self.switch['ports'] if port['get_port_profil']['loop_protect']] - - self.additionals = {'ra_guarded' : ra_guarded, 'loop_protected' : loop_protected, 'vlans' : vlans, 'arp_protect_vlans' : arp_protect_vlans, 'dhcp_snooping_vlans' : dhcp_snooping_vlans, 'dhcpv6_snooping_vlans' : dhcpv6_snooping_vlans, 'igmp_vlans' : igmp_vlans, 'mld_vlans': mld_vlans} - - - def gen_conf_hp(self): - """Génère la config pour ce switch hp""" - self.preprocess_hp() - self.conf = self.hp_tpl.render(switch=self.switch, settings=self.settings, additionals=self.additionals) - - def check_and_get_login(self): - """Récupère les login/mdp du switch, renvoie false si ils sont indisponibles""" - self.creds_dict = self.switch["get_management_cred_value"] - if self.creds_dict: - return True + def add_in_subtable(self, chain, subtable, value): + if '4' in chain: + self.add(chain, "-A " + subtable + " " + value) + elif '6' in chain: + self.add(chain, "-A " + subtable + " " + value) else: - return False + self.add(chain + '4', "-A " + subtable + " " + value) + self.add(chain + '6', "-A " + subtable + " " + value) - def login_hp(self): - """Login into rest interface of this switch""" - url_login = "http://" + self.switch["ipv4"] + "/rest/v3/login-sessions" - - payload_login = { - "userName" : self.creds_dict["id"], - "password" : self.creds_dict["pass"] - } - get_cookie = requests.post(url_login, data=json.dumps(payload_login)) - cookie = get_cookie.json()['cookie'] - self.headers = {"Cookie" : cookie} + def init_filter(self, subchain, decision="ACCEPT", mode='all'): + if mode == 'all' or mode == '4': + self.add("filter4", ":" + subchain + " " + decision) + if mode == 'all' or mode == '6': + self.add("filter6", ":" + subchain + " " + decision) - def apply_conf_hp(self): - """Apply config restore via rest""" - url_restore = "http://" + self.switch["ipv4"] + "/rest/v4/system/config/cfg_restore" - provision_mode = self.settings["switchs_provision"] - if provision_mode == "tftp": - data = { - "server_type": "ST_TFTP", - "file_name": self.get_conf_file_name(), - "tftp_server_address": { - "server_address": { - "ip_address": { - "version":"IAV_IP_V4", - "octets":self.settings["switchs_management_interface_ip"]}}}, - "is_forced_reboot_enabled": True, - } - elif provision_mode == "sftp": - data = { - "server_type": "ST_SFTP", - "file_name": self.get_conf_file_name(), - "tftp_server_address": { - "server_address": { - "ip_address": { - "version":"IAV_IP_V4", - "octets":self.settings["switchs_management_interface_ip"]}}, - "user_name": self.settings["switchs_management_sftp_creds"]["login"], - "password": self.settings["switchs_management_sftp_creds"]["pass"], - }, - "is_forced_reboot_enabled": True, - } - # Nous lançons la requête de type POST. - post_restore = requests.post(url_restore, data=json.dumps(data), headers=self.headers) + def init_nat(self, subchain, decision="ACCEPT", mode='all'): + if mode == 'all' or mode == '4': + self.add("nat4", ":" + subchain + " " + decision) + if mode == 'all' or mode == '6': + self.add("nat6", ":" + subchain + " " + decision) - def gen_conf_and_write(self): - """Génère la conf suivant le bon constructeur et l'écrit""" - if self.switch["model"]: - constructor = self.switch["model"]["constructor"].lower() - if "hp" in constructor or "aruba" in constructor: - self.gen_conf_hp() - self.write_conf() + def init_mangle(self, subchain, decision="ACCEPT", mode='all'): + if mode == 'all' or mode == '4': + self.add("mangle4", ":" + subchain + " " + decision) + if mode == 'all' or mode == '6': + self.add("mangle6", ":" + subchain + " " + decision) - def apply_conf(self): - if self.check_and_get_login(): - if self.switch["model"] and self.switch["automatic_provision"] == True and self.settings["provision_switchs_enabled"]: - constructor = self.switch["model"]["constructor"].lower() - if "hp" in constructor or "aruba" in constructor: - self.login_hp() - self.apply_conf_hp() + def jump(self, chain, subchainA, subchainB): + self.add(chain, "-A " + subchainA + " -j " + subchainB) - def write_conf(self): - """Ecriture de la conf du switch dans le fichier qui va bien""" - with open("generated/" + self.get_conf_file_name(), 'w+') as f: - f.write(self.conf) + def jump_all_trafic(self, chain, subchainA, subchainB, mode='all'): + if mode == 'all' or mode == '4': + self.add(chain + '4', "-A " + subchainA + " -j " + subchainB) + if mode == 'all' or mode == '6': + self.add(chain + '6', "-A " + subchainA + " -j " + subchainB) + def jump_traficfrom(self, chain, interface, subchainA, subchainB, mode='all'): + if mode == 'all' or mode == '4': + self.add(chain + '4', "-A " + subchainA + " -i " + interface + " -j " + subchainB) + if mode == 'all' or mode == '6': + self.add(chain + '6', "-A " + subchainA + " -i " + interface + " -j " + subchainB) + + def jump_traficto(self, chain, interface, subchainA, subchainB, mode='all'): + if mode == 'all' or mode == '4': + self.add(chain + '4', "-A " + subchainA + " -o " + interface + " -j " + subchainB) + if mode == 'all' or mode == '6': + self.add(chain + '6', "-A " + subchainA + " -o " + interface + " -j " + subchainB) + + def atomic_add(self, chain, subtable, command, mode='4'): + command_to_send = "-t %s -I %s 1 %s" % (chain, subtable, command) + if mode == 'all' or mode == '4': + command_to_execute = ["sudo","-n","/sbin/iptables"] + command_to_send.split() + if mode == 'all' or mode == '6': + command_to_execute = ["sudo","-n","/sbin/ip6tables"] + command_to_send.split() + process = subprocess.Popen(command_to_execute, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + def atomic_del(self, chain, subtable, command, mode='4'): + command_to_send = "-t %s -D %s %s" % (chain, subtable, command) + if mode == 'all' or mode == '4': + command_to_execute = ["sudo","-n","/sbin/iptables"] + command_to_send.split() + if mode == 'all' or mode == '6': + command_to_execute = ["sudo","-n","/sbin/ip6tables"] + command_to_send.split() + process = subprocess.Popen(command_to_execute, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + + def routeur6(self, table): + """Methode appellée spécifiquement pour le parefeu v6""" + if table == "filter": + self.base_filter() + if self.verbose: + print("Filter : interdit les machines blacklistées en forward") + self.blacklist_hard_forward() + if self.verbose: + print("Filter : filtage ports v6") + self.filtrage_ports(ip_type='6') + if self.verbose: + print("Filter : limit connexions forward") + self.limit_ssh_connexion_forward() + if self.verbose: + print("Filter : Limit connexion src") + self.limit_connexion_srcip() + elif table == "mangle": + if self.verbose: + print("Mangle : Mise en place des logs") + self.log() + if self.verbose: + print("Mangle : Réglage correct du MSS") + self.mss() + else: + pass + + def routeur4(self, table): + """Methode appellée spécifiquement pour le parefeu v4""" + if table == "filter": + self.base_filter() + if self.verbose: + print("Filter : interdit les machines blacklistées en forward") + self.blacklist_hard_forward() + if self.verbose: + print("Filter : filtrage ports 4") + self.filtrage_ports(ip_type='4') + if self.verbose: + print("Filter : limit ssh connexion forward") + self.limit_ssh_connexion_forward() + if self.verbose: + print("Filter : limit connexion src ip") + self.limit_connexion_srcip() + elif table == "mangle": + if self.verbose: + print("Mangle : Mise en place des logs") + self.log() + if self.verbose: + print("Mangle : Réglage correct du MSS") + self.mss() + elif table == "nat": + if self.verbose: + print("Nat : priv fil") + self.nat_prive_ip('fil') + if self.verbose: + print("Nat : priv wifi") + self.nat_prive_ip('wifi') + + def portail(self, table): + if table == "filter": + self.base_filter() + if self.verbose: + print("Filter : autorisation des ip en sortie") + self.captif_autorized_ip() + if table == "nat": + if self.verbose: + print("Nat : nat et captures les connexions du portail masquerade") + self.nat_connexion_portail() + self.capture_connexion_portail() + + def users(self, table): + """Securisation d'un serveur avec comptes d'utilisateurs""" + if table == 'filter': + #self.blacklist_output() + self.base_filter() + if self.verbose: + print("Filter : Forbid admin vlan for users") + self.forbid_adm() + else: + pass + + def radius(self, table): + if table == 'filter': + if self.verbose: + print("Filter : acceptation des connexions uniquement des serveurs federez") + self.accept_freerad_from_server() + else: + pass + + def base_filter(self): + if self.verbose: + print("Filter : reseaux non routables") + self.reseaux_non_routables() + if self.verbose: + print("Filter : bl hard") + self.blacklist_hard() + if self.verbose: + print("Filter : connexion input") + if self.verbose: + print("Limitation des connexions") + self.limit_ssh_connexion_input() + self.limit_connexion_dstip() + + def gen_filter(self, empty=False): + self.init_filter("INPUT") + self.init_filter("FORWARD") + self.init_filter("OUTPUT") + if not empty: + if self.verbose: + print("Filter : icmp") + self.filter_icmp() + if self.verbose: + print("Filter : icmpv6") + self.filter_icmpv6() + if self.verbose: + print("Filter : accept established") + self.accept_established() + for role in self.role: + if hasattr(self, role): + getattr(self, role)('filter') + self.commit_filter() + + def filtrage_ports(self, ip_type='4', subtable='FILTRAGE-PORTS'): + """Filtrage ports en entrée/sortie du réseau""" + if ip_type == '4': + chain = "filter4" + else: + chain = "filter6" + + + self.init_filter(subtable, decision="-") + for interface in self.interfaces['sortie']: + self.jump_traficto("filter", interface, "FORWARD", subtable, mode=ip_type) + self.jump_traficfrom("filter", interface, "FORWARD", subtable, mode=ip_type) + + for subnet in self.subnet_ports: + ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["tcp_ports_in"]) + if ports: + if ip_type == '4': + self.add_in_subtable(chain, subtable, """-m iprange --dst-range %s-%s -p tcp -m multiport --dports %s -j RETURN""" % (rule["domaine_ip_start"], rule["domaine_ip_stop"], ports)) + if ip_type == '6': + self.add_in_subtable(chain, subtable, """-s %s -p tcp -m multiport --dports %s -j RETURN""" % (ip_range, ports)) + ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["tcp_ports_out"]) + if ports: + self.add_in_subtable(chain, subtable, """-m iprange --src-range %s-%s -p tcp -m multiport --dports %s -j RETURN""" % (rule["domaine_ip_start"], rule["domaine_ip_stop"], ports)) + ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["udp_ports_in"]) + if ports: + self.add_in_subtable(chain, subtable, """-m iprange --dst-range %s-%s -p udp -m multiport --dports %s -j RETURN""" % (rule["domaine_ip_start"], rule["domaine_ip_stop"], ports)) + ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["udp_ports_out"]) + if ports: + self.add_in_subtable(chain, subtable, """-m iprange --src-range %s-%s -p udp -m multiport --dports %s -j RETURN""" % (rule["domaine_ip_start"], rule["domaine_ip_stop"], ports)) + + #Ajout des règles générales + for realm in self.config_firewall.ports_realm[ip_type]: + ports = ','.join(self.format_port(port) for port in self.config_firewall.ports_default['tcp']['output']) + if ports: + for ip_range in get_range(ip_type, realm): + self.add_in_subtable(chain, subtable, """-s %s -p tcp -m multiport --dports %s -j RETURN""" % (ip_range, ports)) + ports = ','.join(self.format_port(port) for port in self.config_firewall.ports_default['tcp']['input']) + if ports: + for ip_range in get_range(ip_type, realm): + self.add_in_subtable(chain, subtable, """-d %s -p tcp -m multiport --dports %s -j RETURN""" % (ip_range, ports)) + ports = ','.join(self.format_port(port) for port in self.config_firewall.ports_default['udp']['output']) + if ports: + for ip_range in get_range(ip_type, realm): + self.add_in_subtable(chain, subtable, """-s %s -p udp -m multiport --dports %s -j RETURN""" % (ip_range, ports)) + ports = ','.join(self.format_port(port) for port in self.config_firewall.ports_default['udp']['input']) + if ports: + for ip_range in get_range(ip_type, realm): + self.add_in_subtable(chain, subtable, """-d %s -p udp -m multiport --dports %s -j RETURN""" % (ip_range, ports)) + + + + #Ajout des machines avec ouvertures particulières + for machine in self.conn.search(u'(&(portTCPout=*)(%s=*))' % ldap_object_name): + self.add_in_subtable(chain, subtable, """-s %s -p tcp -m multiport --dports %s -j RETURN""" % (machine[ldap_object_name][0].value, ','.join(self.format_port(port) for port in machine['portTCPout']))) + for machine in self.conn.search(u'(&(portTCPin=*)(%s=*))' % ldap_object_name): + self.add_in_subtable(chain, subtable, """-d %s -p tcp -m multiport --dports %s -j RETURN""" % (machine[ldap_object_name][0].value, ','.join(self.format_port(port) for port in machine['portTCPin']))) + for machine in self.conn.search(u'(&(portUDPout=*)(%s=*))' % ldap_object_name): + self.add_in_subtable(chain, subtable, """-s %s -p udp -m multiport --dports %s -j RETURN""" % (machine[ldap_object_name][0].value, ','.join(self.format_port(port) for port in machine['portUDPout']))) + for machine in self.conn.search(u'(&(portUDPin=*)(%s=*))' % ldap_object_name): + self.add_in_subtable(chain, subtable, """-d %s -p udp -m multiport --dports %s -j RETURN""" % (machine[ldap_object_name][0].value, ','.join(self.format_port(port) for port in machine['portUDPin']))) + + #Rejet du reste + self.add_in_subtable(chain, subtable, """-j REJECT""") + + def accept_freerad_from_server(self, subtable='RADIUS-SERVER'): + """Accepte uniquement le trafique venant des serveurs radius federez""" + self.init_filter(subtable, decision="-") + for interface in self.interfaces['sortie']: + self.jump_traficfrom("filter", interface, "INPUT", subtable) + for server in self.config_firewall.radius_server: + self.add_in_subtable("filter4", subtable, """-s %s -p %s -m multiport --dports %s -j ACCEPT""" % (server['ipaddr'], server['protocol'], ','.join(server['port']))) + self.add_in_subtable("filter6", subtable, """-s %s -p %s -m multiport --dports %s -j ACCEPT""" % (server['ip6addr'], server['protocol'], ','.join(server['port']))) + self.add_in_subtable("filter", subtable, """-j REJECT""") + + def reseaux_non_routables(self, subtable='ADM-NETWORK'): + """Bloc le trafic vers les réseaux non routables""" + self.init_filter(subtable, decision="-") + for interface in self.interfaces['non-routables']: + self.jump_traficto("filter", interface, "FORWARD", subtable) + self.add_in_subtable("filter", subtable, """-j REJECT""") + + def captif_autorized_ip(self, subtable='FILTRE-IP-PORTAIL'): + """Autorise les ip whitelistées sur le portail captif accueil""" + self.init_filter(subtable, decision="-") + self.jump_all_trafic("filter", "FORWARD", subtable, mode='4') + + for ip in self.config.accueil_route.keys(): + if 'tcp' in self.config.accueil_route[ip]: + self.add_in_subtable("filter4", subtable, """-p tcp -d %s -m multiport --dports %s -j ACCEPT""" % (ip, ','.join(self.config.accueil_route[ip]['tcp']))) + if 'udp' in self.config.accueil_route[ip]: + self.add_in_subtable("filter4", subtable, """-p udp -d %s -m multiport --dports %s -j ACCEPT""" % (ip, ','.join(self.config.accueil_route[ip]['udp']))) + self.add_in_subtable("filter4", subtable, """-j REJECT""") + + + def capture_connexion_portail(self, subtable="PORTAIL-CAPTIF-REDIRECT"): + """Nat les connexions derrière l'ip de la machine du portail""" + self.init_nat(subtable, decision="-") + for interface in self.interfaces['routables']: + self.jump_traficfrom("nat", interface, "PREROUTING", subtable, mode='4') + + for ip in self.config.accueil_route.keys(): + if 'tcp' in self.config.accueil_route[ip]: + self.add_in_subtable("nat4", subtable, """-p tcp -d %s -m multiport --dports %s -j RETURN""" % (ip, ','.join(self.config.accueil_route[ip]['tcp']))) + if 'udp' in self.config.accueil_route[ip]: + self.add_in_subtable("nat4", subtable, """-p udp -d %s -m multiport --dports %s -j RETURN""" % (ip, ','.join(self.config.accueil_route[ip]['udp']))) + self.add_in_subtable("nat4", subtable, """-p udp -s %(ip)s/16 --dport 53 -j DNAT --to %(ip)s""" % {'ip' :self.config_firewall.portail['accueil']}) + self.add_in_subtable("nat4", subtable, """-p tcp -s %(ip)s/16 --dport 53 -j DNAT --to %(ip)s""" % {'ip' : self.config_firewall.portail['accueil']}) + self.add_in_subtable("nat4", subtable, """-p tcp -s %(ip)s/16 --dport 80 -j DNAT --to %(ip)s""" % {'ip' : self.config_firewall.portail['accueil']}) + self.add_in_subtable("nat4", subtable, """-p tcp -s %(ip)s/16 --dport 80 -j DNAT --to %(ip)s""" % {'ip' : self.config_firewall.portail['isolement']}) + + def nat_connexion_portail(self, subtable="PORTAIL-CAPTIF-NAT"): + """Nat les connexions derrière l'ip de la machine du portail""" + self.init_nat(subtable, decision="-") + for interface in self.interfaces['sortie']: + self.jump_traficto("nat", interface, "POSTROUTING", subtable, mode='4') + + for ip in self.config.accueil_route.keys(): + if 'tcp' in self.config.accueil_route[ip]: + self.add_in_subtable("nat4", subtable, """-p tcp -d %s -m multiport --dports %s -j MASQUERADE""" % (ip, ','.join(self.config.accueil_route[ip]['tcp']))) + if 'udp' in self.config.accueil_route[ip]: + self.add_in_subtable("nat4", subtable, """-p udp -d %s -m multiport --dports %s -j MASQUERADE""" % (ip, ','.join(self.config.accueil_route[ip]['udp']))) + + def accept_established(self, subtable='ESTABLISHED-CONN'): + """Accepte les connexions déjà établies""" + self.init_filter(subtable, decision="-") + self.jump_all_trafic("filter", "FORWARD", subtable) + self.jump_all_trafic("filter", "INPUT", subtable) + self.add_in_subtable("filter", subtable, """-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT""") + + def filter_icmpv6(self, subtable='ICMPV6'): + self.init_filter(subtable, decision="-", mode='6') + self.jump_all_trafic("filter", "INPUT", subtable, mode='6') + self.jump_all_trafic("filter", "FORWARD", subtable, mode='6') + self.jump_all_trafic("filter", "OUTPUT", subtable, mode='6') + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type echo-request -j ACCEPT""") + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type echo-reply -j ACCEPT""") + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type destination-unreachable -j ACCEPT""") + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type packet-too-big -j ACCEPT""") + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type ttl-zero-during-transit -j ACCEPT""") + self.add_in_subtable("filter6", subtable, """-p icmpv6 -m icmp6 --icmpv6-type parameter-problem -j ACCEPT""") + + def filter_icmp(self, subtable='ICMP'): + self.init_filter(subtable, decision="-", mode='4') + self.jump_all_trafic("filter", "FORWARD", subtable, mode='4') + self.jump_all_trafic("filter", "OUTPUT", subtable, mode='4') + self.jump_all_trafic("filter", "INPUT", subtable, mode='4') + self.add_in_subtable("filter4", subtable, """-p icmp -j ACCEPT""") + + def limit_ssh_connexion_input(self, subtable='LIMIT-SSH-INPUT'): + self.init_filter(subtable, decision="-") + for interface in self.interfaces['routables']: + self.jump_traficfrom("filter", interface, "INPUT", subtable) + + self.add_in_subtable("filter", subtable, """-p tcp --dport ssh -m state --state NEW -m recent --name SSH-INPUT --set""") + self.add_in_subtable("filter", subtable, """-p tcp --dport ssh -m state --state NEW -m recent --name SSH-INPUT --update --seconds 120 --hitcount 10 --rttl -j DROP""") + + def limit_ssh_connexion_forward(self, subtable='LIMIT-SSH-FORWARD'): + self.init_filter(subtable, decision="-") + for interface in self.interfaces['sortie']: + self.jump_traficfrom("filter", interface, "FORWARD", subtable) + + self.add_in_subtable("filter", subtable, """-p tcp --dport ssh -m state --state NEW -m recent --name SSH-FORWARD --set""") + self.add_in_subtable("filter", subtable, """-p tcp --dport ssh -m state --state NEW -m recent --name SSH-FORWARD --update --seconds 30 --hitcount 10 --rttl -j DROP""") + + def limit_connexion_srcip(self, subtable='LIMIT-CONNEXION-SRCIP'): + self.init_filter(subtable, decision="-") + for interface in self.interfaces['sortie']: + self.jump_traficto("filter", interface, "FORWARD", subtable) + + self.add_in_subtable("filter", subtable, """-p udp -m hashlimit --hashlimit-upto 400/sec --hashlimit-burst 800 --hashlimit-mode srcip --hashlimit-name LIMIT_UDP_SRCIP_CONNEXION -j RETURN""") + self.add_in_subtable("filter", subtable, """-p udp -m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_UDP_SRCIP_CONNEXION_LOG -j LOG --log-prefix "CONNEXION_LIMIT_UDP" """) + self.add_in_subtable("filter", subtable, """-p udp -j REJECT""") + self.add_in_subtable("filter", subtable, """-p tcp -m hashlimit --hashlimit-upto 2000/min --hashlimit-burst 4000 --hashlimit-mode srcip --hashlimit-name LIMIT_TCP_SRCIP_CONNEXION -m state --state NEW -j RETURN""") + self.add_in_subtable("filter", subtable, """-p tcp -m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_TCP_SRCIP_CONNEXION_LOG -m state --state NEW -j LOG --log-prefix "CONNEXION_LIMIT_TCP_SRCIP " """) + self.add_in_subtable("filter", subtable, """-p tcp -m state --state NEW -j REJECT""") + self.add_in_subtable("filter", subtable, """-m hashlimit --hashlimit-upto 400/sec --hashlimit-burst 800 --hashlimit-mode srcip --hashlimit-name LIMIT_OTHER_SRCIP_CONNEXION -j RETURN""") + self.add_in_subtable("filter", subtable, """-m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_OTHER_SRCIP_CONNEXION_LOG -j LOG --log-prefix "CONNEXION_LIMIT " """) + self.add_in_subtable("filter", subtable, """-j REJECT""") + + def limit_connexion_dstip(self, subtable='LIMIT-CONNEXION-DSTIP', cible='INPUT'): + self.init_filter(subtable, decision="-") + if "sortie" in self.interfaces: + for interface in self.interfaces['sortie']: + self.jump_traficfrom("filter", interface, "FORWARD", subtable) + for interface in self.interfaces['routables']: + self.jump_traficfrom("filter", interface, "INPUT", subtable) + + self.add_in_subtable("filter", subtable, """-p udp -m hashlimit --hashlimit-upto 400/sec --hashlimit-burst 800 --hashlimit-mode srcip --hashlimit-name LIMIT_UDP_DSTIP_CONNEXION -j RETURN""") + self.add_in_subtable("filter", subtable, """-p udp -m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_UDP_DSTIP_CONNEXION_LOG -j LOG --log-prefix "CONNEXION_LIMIT_UDP" """) + self.add_in_subtable("filter", subtable, """-p udp -j REJECT""") + self.add_in_subtable("filter", subtable, """-p tcp -m hashlimit --hashlimit-upto 2000/min --hashlimit-burst 4000 --hashlimit-mode srcip --hashlimit-name LIMIT_TCP_DSTIP_CONNEXION -m state --state NEW -j RETURN""") + self.add_in_subtable("filter", subtable, """-p tcp -m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_TCP_DSTIP_CONNEXION_LOG -m state --state NEW -j LOG --log-prefix "CONNEXION_LIMIT_TCP_DSTIP " """) + self.add_in_subtable("filter", subtable, """-p tcp -m state --state NEW -j REJECT""") + self.add_in_subtable("filter", subtable, """-m hashlimit --hashlimit-upto 400/sec --hashlimit-burst 800 --hashlimit-mode srcip --hashlimit-name LIMIT_OTHER_DSTIP_CONNEXION -j RETURN""") + self.add_in_subtable("filter", subtable, """-m hashlimit --hashlimit-upto 5/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name LIMIT_OTHER_DSTIP_CONNEXION_LOG -j LOG --log-prefix "CONNEXION_LIMIT " """) + self.add_in_subtable("filter", subtable, """-j REJECT""") + + def blacklist_hard_forward(self, subtable='BLACKLIST-HARD'): + """Blacklist les machines en forward, à appliquer sur les routeurs de sortie""" + for interface in self.interfaces['routables']: + self.jump_traficfrom("filter", interface, "FORWARD", subtable) + + def blacklist_hard(self, subtable='BLACKLIST-HARD'): + """Génération de la chaine blackliste hard, blackliste des mac des machines bl""" + self.init_filter(subtable, decision="-") + for interface in self.interfaces['routables']: + self.jump_traficfrom("filter", interface, "INPUT", subtable) + + for machine in self.conn.allMachines(): + if machine.blacklist_actif() and set(bl['type'] for bl in machine.blacklist_actif()).intersection(self.config.blacklist_sanctions) and machine['macAddress'] and machine['macAddress'][0].value != '': + self.add_in_subtable("filter", subtable, """-m mac --mac-source %s -j REJECT""" % machine['macAddress'][0].value) + + def blacklist_output(self, subtable='BLACKLIST-OUTPUT'): + """Génération de la chaine blackliste output, meme idée que si dessus sauf que + ici on filtre les users uid sur un serveur et non leurs ip""" + self.init_filter(subtable, decision="-") + for interface in self.interfaces['routables']: + self.jump_traficto("filter", interface, "OUTPUT", subtable) + + for user in self.conn.search(u'(&(uidNumber=*)(!(droits=nounou))(!(droits=apprenti))(|(objectClass=adherent)(objectClass=club)))', sizelimit=10000): + if user.blacklist_actif(): + self.add_in_subtable("filter", subtable, """-m owner --uid-owner %s -j REJECT""" % user['uidNumber'][0].value) + + def forbid_adm(self, subtable='ADMIN-VLAN'): + """Interdit aux users non admin de parler sur les vlans admin""" + self.init_filter(subtable, decision="-") + for interface in self.interfaces['non-routables']: + self.jump_traficto("filter", interface, "OUTPUT", subtable) + + for user in self.conn.search(u'(&(uidNumber=*)(!(droits=nounou))(!(droits=apprenti))(|(objectClass=adherent)(objectClass=club)))', sizelimit=10000): + self.add_in_subtable("filter", subtable, """-m owner --uid-owner %s -j REJECT""" % user['uidNumber'][0].value) + + def gen_nat(self, empty=False): + """Génération de la chaine nat""" + self.init_nat("PREROUTING") + self.init_nat("INPUT") + self.init_nat("OUTPUT") + self.init_nat("POSTROUTING") + if not empty: + for role in self.role: + if hasattr(self, role): + getattr(self, role)('nat') + self.commit_nat() + + def log(self, subtable='LOGALL'): + """Logage des packet sur les interfaces choisies""" + self.init_mangle(subtable, decision="-") + self.jump_all_trafic("mangle", "PREROUTING", subtable) + self.add_in_subtable("mangle", subtable, '-m state --state NEW -j LOG --log-prefix "LOG_ALL " ') + + def mss(self, subtable='MSS'): + """Reglage correct du MSS pour éviter les problèmes de MTU""" + self.init_mangle(subtable, decision="-") + self.jump_all_trafic("mangle", "POSTROUTING", subtable) + + self.add_in_subtable("mangle", subtable, '-p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu') + + def nat_prive_ip(self, nat_type): + """Nat filaire en v4""" + subtable = "CONNEXION-NAT-" + nat_type.upper() + self.init_nat(subtable, decision="-") + self.jump_all_trafic("nat", "POSTROUTING", subtable) + + nat_prive_ip_plage = self.config_firewall.nat_prive_ip_plage[nat_type] + for nat_ip_range in range(1, 26): + range_name = 'nat' + nat_prive_ip_plage.split('.')[1] + '_' + str("%02d" % nat_ip_range ) + self.init_nat(range_name, decision="-") + self.add_in_subtable("nat", subtable, '-s ' + '.'.join(nat_prive_ip_plage.split('.')[:2]) + '.' + str(nat_ip_range) + '.0/24 -j ' + range_name) + for nat_ip_range in range(1, 26): + range_name = 'nat' + nat_prive_ip_plage.split('.')[1] + '_' + str("%02d" % nat_ip_range) + for nat_ip_subrange in range(16): + subrange_name = range_name + '_' + str(hex(nat_ip_subrange)[2:]) + self.init_nat(subrange_name, decision="-") + self.add_in_subtable("nat", range_name, '-s ' + '.'.join(nat_prive_ip_plage.split('.')[:2]) + '.' + str(nat_ip_range) + '.' + str(nat_ip_subrange*16) + '/28 -j ' + subrange_name) + for nat_private_ip in range(256): + ip_src = '.'.join(nat_prive_ip_plage.split('.')[:2]) + '.' + str(nat_ip_range) + '.' + str(nat_private_ip) + '/32' + + port_low = 10000 + 2000*(nat_private_ip%26) + port_high = port_low + 1999 + + subrange_name = range_name + '_' + str(hex(nat_private_ip/16)[2:]) + + # On nat + for interface in self.config_firewall.nat_pub_ip_plage[nat_type]: + ip_nat = '.'.join(self.config_firewall.nat_pub_ip_plage[nat_type][interface].split('.')[:3]) + '.' + str(10*(nat_ip_range - 1) + nat_private_ip/26) + self.add_in_subtable("nat", subrange_name, '-s %s -o %s -p tcp -j SNAT --to-source %s' % (ip_src, self.dev[interface], ip_nat + ':' + str(port_low) + '-' + str(port_high))) + self.add_in_subtable("nat", subrange_name, '-s %s -o %s -p udp -j SNAT --to-source %s' % (ip_src, self.dev[interface], ip_nat + ':' + str(port_low) + '-' + str(port_high))) + + # On nat tout ce qui match dans les règles et qui n'est pas du tcp/udp derrière la première ip publique unused (25*10) + 1 + # Ne pas oublier de loguer ce qui sort de cette ip + for interface in self.config_firewall.nat_pub_ip_plage[nat_type]: + self.add_in_subtable("nat", subtable, '-s ' + nat_prive_ip_plage + ' -o %s -j SNAT --to-source ' % (self.dev[interface],) + '.'.join(self.config_firewall.nat_pub_ip_plage[nat_type][interface].split('.')[:3]) + '.250') + + def gen_mangle(self, empty=False): + """Génération de la chaine mangle""" + self.init_mangle("PREROUTING") + self.init_mangle("INPUT") + self.init_mangle("FORWARD") + self.init_mangle("OUTPUT") + self.init_mangle("POSTROUTING") + if not empty: + for role in self.role: + if hasattr(self, role): + getattr(self, role)('mangle') + self.commit_mangle() + + def restore_iptables(self, mode='4'): + """Restoration de l'iptable générée""" + if mode == '6': + global_chain = self.nat6 + self.filter6 + self.mangle6 + command_to_execute = ["sudo","-n","/sbin/ip6tables-restore"] + else: + global_chain = self.nat4 + self.filter4 + self.mangle4 + command_to_execute = ["sudo","-n","/sbin/iptables-restore"] + process = subprocess.Popen(command_to_execute, stdin=subprocess.PIPE, stdout=subprocess.PIPE) + process.communicate(input=global_chain.encode('utf-8')) + if self.export: + print(global_chain) + + def complete_flush_iptables(self, mode='4'): + """Insère un parefeuv6 vide, appellé par l'arrét du parefeu""" + self.restore_iptables(mode=mode) + + def do_action(self): + """Effectue l'action demandée""" + if self.action == "start" or self.action == "restart": + self.reload() + elif self.action == "stop": + self.flush() + else: + raise NotImplementedError("Action non reconnu, actions valides : start, stop ou restart") + + def reload(self): + """Recharge le parefeu""" + self.gen_mangle() + self.gen_nat() + self.gen_filter() + if any('6' in role for role in self.role): + self.restore_iptables(mode='6') + return + if any('4' in role for role in self.role): + self.restore_iptables(mode='4') + return + self.restore_iptables(mode='6') + self.restore_iptables(mode='4') + + def flush(self): + """Vide la chaine iptables, ou ip6tables suivant le role du serveur""" + self.gen_mangle(empty=True) + self.gen_nat(empty=True) + self.gen_filter(empty=True) + if any('6' in role for role in self.role): + self.complete_flush_iptables(mode='6') + if any('4' in role for role in self.role): + self.complete_flush_iptables(mode='4') + self.restore_iptables(mode='6') + self.restore_iptables(mode='4') + + def add_port(self, ports, ip_cible, sens, protocole, subtable='FILTRAGE-PORTS', mode='4'): + """Ajout atomique d'une ouverture de port + ports : liste des ports à ouvrir + sens : source ou destination + protocole : tcp ou udp + ip cible : ip où s'applique l'ouverture""" + if sens == "source": + self.atomic_add("filter", subtable, """-%s %s -p %s -m multiport --dports %s -j RETURN""" % ('s', ip_cible, protocole, ','.join(self.format_port(port) for port in ports)), mode=mode) + if sens == "destination": + self.atomic_add("filter", subtable, """-%s %s -p %s -m multiport --dports %s -j RETURN""" % ('d', ip_cible, protocole, ','.join(self.format_port(port) for port in ports)), mode=mode) + + def add_in_blacklist_hard(self, mac, subtable='BLACKLIST-HARD', mode='4'): + """Ajoute la mac à la blacklist""" + self.atomic_add("filter", subtable, """-m mac --mac-source %s -j REJECT""" % mac, mode=mode) + + def del_in_blacklist_hard(self, mac, subtable='BLACKLIST-HARD', mode='4'): + """Retire la mac de la blacklist""" + self.atomic_del("filter", subtable, """-m mac --mac-source %s -j REJECT""" % mac, mode=mode) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-v", "--verbose", help="increase output verbosity", action="store_true") + parser.add_argument("-e", "--export", help="export le contenu du parefeu", action="store_true") + parser.add_argument("action", help="Mode reconnus : start, stop ou restart") + args = parser.parse_args() + table = iptables() + if args.verbose: + table.verbose = True + table.action = args.action + table.export = args.export + table.do_action() sw = Switch() diff --git a/templates/hp.tpl b/templates/hp.tpl deleted file mode 100644 index cff6914..0000000 --- a/templates/hp.tpl +++ /dev/null @@ -1,184 +0,0 @@ -; {{ switch.model.reference }}A Configuration Editor; Created on release #{{ switch.model.firmware }} - -hostname "{{ switch.short_name }}" -; Generated on {{ date_gen }} by re2o -;--- Snmp --- -{%- if switch.switchbay.name %} -snmp-server location "{{ switch.switchbay.name }}" -{%- endif %} -;A faire à la main -snmpv3 enable -snmpv3 restricted-access -snmpv3 user "re2o" -snmpv3 group ManagerPriv user "re2o" sec-model ver3 -snmp-server community "public" Operator -;--- Heure/date -time timezone 60 -time daylight-time-rule Western-Europe -{%- for ipv4 in settings.switchs_management_utils.ntp_servers.ipv4 %} -sntp server priority {{ loop.index }} {{ ipv4 }} 4 -{%- endfor %} -{%- for ipv6 in settings.switchs_management_utils.ntp_servers.ipv6 %} -sntp server priority {{ loop.index + settings.switchs_management_utils.ntp_servers.ipv4|length }} {{ ipv6 }} 4 -{%- endfor %} -timesync sntp -sntp unicast -;--- Misc --- -console inactivity-timer 30 -;--- Logs --- -{%- for ipv4 in settings.switchs_management_utils.log_servers.ipv4 %} -logging {{ ipv4 }} -{%- endfor %} -{%- for ipv6 in settings.switchs_management_utils.log_servers.ipv6 %} -logging {{ ipv6 }} -{%- endfor %} -;--- IP du switch --- -no ip default-gateway -max-vlans 256 -{%- for id, vlan in additionals.vlans.items() %} -vlan {{ id }} - name "{{ vlan["name"]|capitalize }}" - {%- if vlan["ports_tagged"] %} - tagged {{ vlan["ports_tagged"]|join(',') }} - {%- endif %} - {%- if vlan["ports_untagged"] %} - untagged {{ vlan["ports_untagged"]|join(',') }} - {%- endif %} - {%- if id in additionals.igmp_vlans %} - ip igmp - {%- endif %} - {%- if id in additionals.mld_vlans %} - ipv6 mld version 1 - ipv6 mld enable - {%- endif %} - {%- if vlan.ipv4 %} - {%- for ipv4, subnet in vlan.ipv4.items() %} - ip address {{ ipv4 }}/{{ subnet.0.netmask_cidr }} - {%- endfor %} - {%- else %} - no ip address - {%- endif %} - {%- if vlan.ipv6 %} - {%- for ipv6, subnet6 in vlan.ipv6.items() %} - ipv6 address {{ ipv6 }}/{{ subnet6.netmask_cidr }} - {%- endfor %} - {%- if id in additionals.igmp_vlans %} - no ip igmp querier - {%- endif %} - {%- if id in additionals.mld_vlans %} - no ipv6 mld querier - {%- endif %} - {%- endif %} -exit -{%- endfor %} -;--- Accès d'administration --- -no telnet-server -{%- if switch.web_management_enabled %} -{%- if switch.web_management_enabled != "ssl" %} -web-management plaintext -{%- endif %} -{%- if switch.web_management_enabled == "ssl" %} -web-management ssl -{%- endif %} -{%- else %} -no web-management -{%- endif %} -{%- if switch.rest_enabled %} -rest-interface -{%- endif %} -aaa authentication ssh login public-key none -aaa authentication ssh enable public-key none -ip ssh -ip ssh filetransfer -{%- if settings.switchs_management_utils.subnet %} -ip authorized-managers {{ settings.switchs_management_utils.subnet.0.network }} {{ settings.switchs_management_utils.subnet.0.netmask }} access manager -{%- endif %} -{%- if settings.switchs_management_utils.subnet6 %} -ipv6 authorized-managers {{ settings.switchs_management_utils.subnet6.network }} {{ settings.switchs_management_utils.subnet6.netmask }} access manager -{%- endif %} -{%- if additionals.loop_protected %} -;--- Protection contre les boucles --- -loop-protect disable-timer 30 -loop-protect transmit-interval 3 -loop-protect {{ additionals.loop_protected|join(',') }} -{%- endif %} -;--- Serveurs Radius -radius-server dead-time 2 -{%- for ipv4 in settings.switchs_management_utils.radius_servers.ipv4 %} -radius-server host {{ ipv4 }} key "{{ switch.get_radius_key_value }}" -radius-server host {{ ipv4 }} dyn-authorization -{%- endfor %} -radius-server dyn-autz-port 3799 -;--- Filtrage mac --- -aaa port-access mac-based addr-format multi-colon -;--- Bricoles --- -no cdp run -{%- if additionals.dhcp_snooping_vlans %} -;--- DHCP Snooping --- -{%- for ipv4 in settings.switchs_management_utils.dhcp_servers.ipv4 %} -dhcp-snooping authorized-server {{ ipv4 }} -{%- endfor %} -dhcp-snooping vlan {{ additionals.dhcp_snooping_vlans|join(' ') }} -dhcp-snooping -{%- endif %} -{%- if additionals.arp_protect_vlans %} -;--- ARP Protect --- -arp-protect -arp-protect vlan {{ additionals.arp_protect_vlans|join(' ') }} -arp-protect validate src-mac dest-mac -{%- endif %} -{%- if additionals.dhcpv6_snooping_vlans %} -;--- DHCPv6 Snooping --- -dhcpv6-snooping vlan {{ additionals.dhcpv6_snooping_vlans|join(' ') }} -dhcpv6-snooping -{%- endif %} -{%- if additionals.ra_guarded %} -;--- RA guards --- -ipv6 ra-guard ports {{ additionals.ra_guarded|join(',')}} -{%- endif %} -;--- Config des prises --- -{%- for port in switch.ports %} -{%- if port.get_port_profil.radius_type == "802.1X" %} -aaa port-access authenticator {{ port.port }} -{%- if port.get_port_profil.mac_limit %} -aaa port-access authenticator {{ port.port }} client-limit {{ port.get_port_profil.mac_limit }} -{%- endif %} -aaa port-access authenticator {{ port.port }} logoff-period 3600 -{%- endif %} -{%- if port.get_port_profil.radius_type == "MAC-radius" %} -aaa port-access mac-based {{ port.port }} -{%- if port.get_port_profil.mac_limit %} -aaa port-access mac-based {{ port.port }} addr-limit {{ port.get_port_profil.mac_limit }} -{%- endif %} -aaa port-access mac-based {{ port.port }} logoff-period 3600 -aaa port-access mac-based {{ port.port }} unauth-vid 1 -{%- endif %} -interface {{ port.port }} - {%- if port.state %} - enable - {%- else %} - disable - {%- endif %} - name "{{ port.pretty_name }}" - {%- if port.get_port_profil.flow_control %} - flow-control - {%- endif %} - {%- if not port.get_port_profil.dhcp_snooping %} - dhcp-snooping trust - {%- endif %} - {%- if not port.get_port_profil.arp_protect %} - arp-protect trust - {%- endif %} - {%- if not port.get_port_profil.dhcpv6_snooping %} - dhcpv6-snooping trust - {%- endif %} - no lacp -exit -{%- endfor %} -;--- Configuration comptabilisation RADIUS --- -aaa accounting network start-stop radius -aaa accounting session-id unique -aaa accounting update periodic 240 -;--- Filtre de protocole --- -filter multicast 01005e0000fb drop all -filter multicast 3333000000fb drop all