You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

783 lines
39 KiB
Python

#!/usr/bin/env python3
from configparser import ConfigParser
import socket
from re2oapi import Re2oAPIClient
from jinja2 import Environment, FileSystemLoader
import requests
import base64
import json
import subprocess
import socket
import argparse
import firewall_config
import sys
import os
path =(os.path.dirname(os.path.abspath(__file__)))
config = ConfigParser()
config.read(path+'/config.ini')
api_hostname = config.get('Re2o', 'hostname')
api_password = config.get('Re2o', 'password')
api_username = config.get('Re2o', 'username')
api_client = Re2oAPIClient(api_hostname, api_username, api_password, use_tls=True)
client_hostname = socket.gethostname().split('.', 1)[0]
class iptables:
def __init__(self):
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.global_chain4 = None
self.global_chain6 = None
self.subnet_ports = api_client.list("firewall/subnet-ports/")
self.interface_ports = api_client.list("firewall/interface-ports/")
self.normal_users = api_client.list("users/normaluser/")
self.verbose = False
self.action = None
self.dry = False
self.export = False
self.export4 = False
self.export6 = False
self.role = getattr(firewall_config, 'role', None)
self.interfaces_settings = getattr(firewall_config, 'interfaces_type', None)
self.nat_settings = getattr(firewall_config, 'nat', None)
self.portail_settings = getattr(firewall_config, 'portail', None)
self.accueils = getattr(firewall_config, 'accueils', [])
self.log_ignore_v4 = getattr(firewall_config, 'log_ignore_v4', [])
self.log_ignore_v6 = getattr(firewall_config, 'log_ignore_v6', [])
self.external_forward_settings = getattr(firewall_config, 'external_forward', [])
def commit(self, chain):
self.add(chain, "COMMIT\n")
def commit_filter(self):
self.add("filter4", "COMMIT\n")
self.add("filter6", "COMMIT\n")
def commit_mangle(self):
self.add("mangle4", "COMMIT\n")
self.add("mangle6", "COMMIT\n")
def commit_nat(self):
self.add("nat4", "COMMIT\n")
self.add("nat6", "COMMIT\n")
def add(self, chain, value):
setattr(self, chain, getattr(self, chain) + "\n" + value)
def add_in_subtable(self, chain, subtable, value, mode='all'):
if '4' in chain:
self.add(chain, "-A " + subtable + " " + value)
elif '6' in chain:
self.add(chain, "-A " + subtable + " " + value)
else:
if mode in ('4', 'all'):
self.add(chain + '4', "-A " + subtable + " " + value)
if mode in ('6', 'all'):
self.add(chain + '6', "-A " + subtable + " " + value)
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 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 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 jump(self, chain, subchainA, subchainB):
self.add(chain, "-A " + subchainA + " -j " + subchainB)
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_trafic_from_source(self, chain, ip_source, subchainA, subchainB, mode='all'):
if mode == 'all' or mode == '4':
self.add(chain + '4', "-A " + subchainA + " -s " + ip_source + " -j " + subchainB)
if mode == 'all' or mode == '6':
self.add(chain + '6', "-A " + subchainA + " -i " + ip_source + " -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 : filtage ports v6")
self.filtrage_ports(ip_type='6')
if self.verbose:
print("Filter : limit connections forward")
self.limit_ssh_connection_forward()
if self.verbose:
print("Filter : Limit connection src")
self.limit_connection_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 : filtrage ports 4")
self.filtrage_ports(ip_type='4')
if self.verbose:
print("Filter : limit ssh connection forward")
self.limit_ssh_connection_forward()
if self.verbose:
print("Filter : limit connection src ip")
self.limit_connection_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":
for nat_to_do in self.nat_settings:
if self.verbose:
print("Nat : priv" + nat_to_do['name'])
self.nat_prive_ip(nat_to_do)
def routeur(self, table):
"""Methode appellée spécifiquement pour le parefeu v4/v6"""
if table == "filter":
self.base_filter()
if self.verbose:
print("Filter : filtrage ports 4")
self.filtrage_ports(ip_type='4')
if self.verbose:
print("Filter : filtage ports v6")
self.filtrage_ports(ip_type='6')
if self.verbose:
print("Filter : limit ssh connection forward")
self.limit_ssh_connection_forward()
if self.verbose:
print("Filter : limit connection src ip")
self.limit_connection_srcip()
elif table == "mangle":
if self.verbose:
print("Mangle : Mise en place des logs")
self.log()
if self.verbose:
print("Mangle : Ajout des accueils")
self.add_accueils()
if self.verbose:
print("Mangle : Réglage correct du MSS")
self.mss()
elif table == "nat":
for nat_to_do in self.nat_settings:
if self.verbose:
print("Nat : priv" + nat_to_do['name'])
self.nat_prive_ip(nat_to_do)
def portail(self, table):
if table == "filter":
self.base_filter()
if self.verbose:
print("Filter : autorisation des ip en sortie")
self.captive_authorized_ip()
if table == "nat":
if self.verbose:
print("Nat : nat et captures les connexions du portail masquerade")
self.nat_connection_portail()
self.capture_connection_portail()
def users(self, table):
"""Securisation d'un serveur avec comptes d'utilisateurs"""
if table == 'filter':
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 : connection input")
if self.verbose:
print("Limitation des connexions")
self.limit_ssh_connection_input()
self.limit_connection_dstip()
self.external_forward()
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="-", mode=ip_type)
for interface in self.interfaces_settings['sortie']:
self.jump_traficto("filter", interface, "FORWARD", subtable, mode=ip_type)
self.jump_traficfrom("filter", interface, "FORWARD", subtable, mode=ip_type)
def add_general_rule(ports, ip_type, chain, subtable, subnet, protocol, direction):
"""Règles générales, fonction de factorisation"""
if ip_type == '4':
self.add_in_subtable(chain, subtable, """-m iprange --%s-range %s-%s -p %s -m multiport --dports %s -j RETURN""" % (direction, subnet["domaine_ip_start"], subnet["domaine_ip_stop"], protocol, ports))
if ip_type == '6':
if "None" in subnet["complete_prefixv6"]:
return
self.add_in_subtable(chain, subtable, """-%s %s -p %s -m multiport --dports %s -j RETURN""" % (direction[0], subnet["complete_prefixv6"], protocol, ports))
#Ajout des règles générales
for subnet in self.subnet_ports:
if subnet["ouverture_ports"]:
if subnet["ouverture_ports"]["tcp_ports_in"]:
ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["tcp_ports_in"])
add_general_rule(ports, ip_type, chain, subtable, subnet, 'tcp', 'dst')
if subnet["ouverture_ports"]["tcp_ports_out"]:
ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["tcp_ports_out"])
add_general_rule(ports, ip_type, chain, subtable, subnet, 'tcp', 'src')
if subnet["ouverture_ports"]["udp_ports_in"]:
ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["udp_ports_in"])
add_general_rule(ports, ip_type, chain, subtable, subnet, 'udp', 'dst')
if subnet["ouverture_ports"]["udp_ports_out"]:
ports = ','.join(rule["show_port"] for rule in subnet["ouverture_ports"]["udp_ports_out"])
add_general_rule(ports, ip_type, chain, subtable, subnet, 'udp', 'src')
def add_specific_rule(ports, ip_type, chain, interface, subnet, protocol, direction):
"""Règles spécifique, fonction de factorisation"""
if ip_type == '4':
if interface['ipv4']:
self.add_in_subtable(chain, subtable, """-%s %s -p %s -m multiport --dports %s -j RETURN""" % (direction[0], interface['ipv4'], protocol, ports))
if ip_type == '6':
for ipv6_addr in interface['ipv6']:
self.add_in_subtable(chain, subtable, """-%s %s -p %s -m multiport --dports %s -j RETURN""" % (direction[0], ipv6_addr['ipv6'], protocol, ports))
for interface in self.interface_ports:
ports = ','.join([port_list['show_port'] for dict_ports in interface["port_lists"] for port_list in dict_ports["tcp_ports_in"]])
if ports:
add_specific_rule(ports, ip_type, chain, interface, subnet, 'tcp', 'dst')
ports = ','.join([port_list['show_port'] for dict_ports in interface["port_lists"] for port_list in dict_ports["tcp_ports_out"]])
if ports:
add_specific_rule(ports, ip_type, chain, interface, subnet, 'tcp', 'src')
ports = ','.join([port_list['show_port'] for dict_ports in interface["port_lists"] for port_list in dict_ports["udp_ports_in"]])
if ports:
add_specific_rule(ports, ip_type, chain, interface, subnet, 'udp', 'dst')
ports = ','.join([port_list['show_port'] for dict_ports in interface["port_lists"] for port_list in dict_ports["udp_ports_out"]])
if ports:
add_specific_rule(ports, ip_type, chain, interface, subnet, 'udp', 'src')
#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_settings['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_settings['admin']:
self.jump_traficto("filter", interface, "FORWARD", subtable)
self.add_in_subtable("filter", subtable, """-j REJECT""")
def captive_authorized_ip(self, subtable='FILTRE-IP-PORTAIL'):
"""Autorise les ip whitelistées sur le portail captive accueil"""
self.init_filter(subtable, decision="-")
self.jump_all_trafic("filter", "FORWARD", subtable, mode='4')
for protocol in self.portail_settings['authorized_hosts']:
for ip, ports in self.portail_settings['authorized_hosts'][protocol].items():
self.add_in_subtable("filter4", subtable, """-p %s -d %s -m multiport --dports %s -j ACCEPT""" % (protocol, ip, ','.join(ports)))
self.add_in_subtable("filter4", subtable, """-j REJECT""")
def ip_redirect(self, subtable, ip_redirect):
for ip_range, destination in ip_redirect.items():
for protocol, ip in destination.items():
for ip_dest, ports in ip.items():
self.add_in_subtable("nat4", subtable, """-p %s -s %s -m multiport --dports %s -j DNAT --to %s""" % (protocol, ip_range, ','.join(ports), ip_dest))
def capture_connection_portail(self, subtable="PORTAIL-CAPTIF-REDIRECT"):
"""Redirige les connexions 80 et 443 vers l'ip cible"""
self.init_nat(subtable, decision="-")
for interface in self.interfaces_settings['routable-portail']:
self.jump_traficfrom("nat", interface, "PREROUTING", subtable, mode='4')
for protocol in self.portail_settings['authorized_hosts']:
for ip, ports in self.portail_settings['authorized_hosts'][protocol].items():
self.add_in_subtable("nat4", subtable, """-p %s -d %s -m multiport --dports %s -j RETURN""" % (protocol, ip, ','.join(ports)))
self.ip_redirect(subtable, self.portail_settings['ip_redirect'])
def nat_connection_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_settings['sortie']:
self.jump_traficto("nat", interface, "POSTROUTING", subtable, mode='4')
for protocol in self.portail_settings['authorized_hosts']:
for ip, ports in self.portail_settings['authorized_hosts'][protocol].items():
self.add_in_subtable("nat4", subtable, """-p %s -d %s -m multiport --dports %s -j MASQUERADE""" % (protocol, ip, ','.join(ports)))
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_connection_input(self, subtable='LIMIT-SSH-INPUT'):
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['routable']:
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_connection_forward(self, subtable='LIMIT-SSH-FORWARD'):
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['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_connection_srcip(self, subtable='LIMIT-CONNEXION-SRCIP'):
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['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_connection_dstip(self, subtable='LIMIT-CONNEXION-DSTIP', cible='INPUT'):
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['sortie']:
self.jump_traficfrom("filter", interface, "FORWARD", subtable)
for interface in self.interfaces_settings['routable']:
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 external_forward(self):
for ip_type in [4, 6]:
for if1, if2 in self.external_forward_settings:
self.add(f"filter{ip_type}", f"-I FORWARD -i {if1} -o {if2} -j ACCEPT")
self.add(f"filter{ip_type}", f"-I FORWARD -i {if2} -o {if1} -j ACCEPT")
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_settings['admin']:
self.jump_traficto("filter", interface, "OUTPUT", subtable)
for user in self.normal_users:
self.add_in_subtable("filter", subtable, """-m owner --uid-owner %s -j REJECT""" % user['uid'])
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)
for net in self.log_ignore_v4:
self.add_in_subtable("mangle4", subtable, f'-d {net} -j RETURN')
for net in self.log_ignore_v6:
self.add_in_subtable("mangle6", subtable, f'-d {net} -j RETURN')
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 run_ipset(self, *args):
command = ["sudo", "-n", "/usr/sbin/ipset"] + list(args)
return subprocess.run(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
def ipset_create(self, name, set_type, timeout):
self.run_ipset("create", name, set_type, "timeout", str(timeout))
def ipset_swap(self, first, second):
self.run_ipset("swap", first, second)
def ipset_destroy(self, name):
self.run_ipset("destroy", name)
def ipset_exists(self, name):
ret = self.run_ipset("list", name)
return ret.returncode == 0
def add_mac_ipset(self, name, timeout):
if self.ipset_exists(name):
tmp_name = f"{name}__tmp"
self.ipset_create(tmp_name, "hash:mac", timeout)
self.ipset_swap(tmp_name, name)
self.ipset_destroy(tmp_name)
else:
self.ipset_create(name, "hash:mac", timeout)
def add_accueils(self):
for accueil in self.accueils:
iface = accueil["iface"]
triggered = f"accueil_{iface}_triggered"
allowed = f"accueil_{iface}_allowed"
triggers = accueil["triggers"]
ip_sources = accueil.get("ip_sources", [])
ip_redirect = accueil.get("ip_redirect", {})
self.add_mac_ipset(allowed, accueil.get("grace_period", 120))
self.add_mac_ipset(triggered, accueil.get("retry_period", 240))
self.add_accueil(iface, allowed, triggered, triggers, ip_sources, ip_redirect)
def add_accueil(self, iface, allowed_set, triggered_set, triggers, ip_sources, ip_redirect):
subtable = f"ACCUEIL-{iface}"
self.init_mangle(subtable, decision="-")
# on laisse passer les machines qui sont temporairement
# autorisées
self.add_in_subtable("mangle", subtable, f"-m set --match-set {allowed_set} src -j RETURN")
# on interdit les machines qui ont accédé à un "trigger" récemment
# mais qui ne sont plus autorisées (règle précédente)
self.add_in_subtable("mangle", subtable, f"-m set --match-set {triggered_set} src -j DROP")
# on autorise l'accès aux "triggers" pour les machines qui n'y ont
# pas accédé récemment, et on les ajoute aux ensembles pour signaler
# l'accès récent
for iptype, proto, addr, port in triggers:
match = f"-p {proto} -d {addr} --dport {port}"
for s in (allowed_set, triggered_set):
self.add_in_subtable("mangle", subtable, f"{match} -j SET --add-set {s} src", iptype)
self.add_in_subtable("mangle", subtable, f"{match} -j RETURN", iptype)
# ATTENTION: on n'interdit toutes les autres connexions, car c'est géré
# via le profil de ports de re2o + par la redirection configurée
subtable_redir = f"ACCUEIL-REDIR-{iface}"
self.init_nat(subtable_redir, decision="-")
# on redirige les machines non temporairement autorisées vers les
# portails captifs
self.add_in_subtable("nat", subtable_redir, f"-m set --match-set {allowed_set} src -j RETURN")
for ip_source in ip_sources:
self.ip_redirect(subtable_redir, {ip_source: ip_redirect})
self.jump_traficfrom("mangle", iface, "PREROUTING", subtable)
self.jump_traficfrom("nat", iface, "PREROUTING", subtable_redir)
def nat_prive_ip(self, nat_type):
"""Nat filaire en v4"""
subtable = "CONNEXION-NAT-" + nat_type['name'].upper()
self.init_nat(subtable, decision="-")
self.jump_all_trafic("nat", "POSTROUTING", subtable, mode='4')
if 'interfaces_ip_to_nat' in nat_type and 'ip_sources' in nat_type:
nat_prive_ip_plage = nat_type['ip_sources']
for nat_ip_range in range(1, 11):
range_name = 'nat' + nat_prive_ip_plage.split('.')[1] + '_' + str("%02d" % nat_ip_range )
self.init_nat(range_name, decision="-")
self.add_in_subtable("nat4", 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, 11):
range_name = 'nat' + nat_prive_ip_plage.split('.')[1] + '_' + str("%02d" % nat_ip_range)
nat_rule_tcp = ""
nat_rule_udp = ""
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("nat4", 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 = 1000 + 1000*(nat_private_ip%64)
port_high = port_low + 999
subrange_name = range_name + '_' + str(hex(nat_private_ip//16)[2:])
# On nat
for interface, pub_ip_range in nat_type['interfaces_ip_to_nat'].items():
ip_nat = '.'.join(pub_ip_range.split('.')[:3]) + '.' + str((int(nat_prive_ip_plage.split('.')[1][0]) - 1)*40 + 4*(nat_ip_range - 1) + nat_private_ip//64)
nat_rule_tcp += '\n-A %s -s %s -o %s -p tcp -j SNAT --to-source %s' % (subrange_name, ip_src, interface, ip_nat + ':' + str(port_low) + '-' + str(port_high))
nat_rule_udp += '\n-A %s -s %s -o %s -p udp -j SNAT --to-source %s' % (subrange_name, ip_src, interface, ip_nat + ':' + str(port_low) + '-' + str(port_high))
self.add("nat4", nat_rule_tcp)
self.add("nat4", nat_rule_udp)
# 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, pub_ip_range in nat_type['interfaces_ip_to_nat'].items():
self.add_in_subtable("nat4", subtable, '-s ' + nat_prive_ip_plage + ' -o %s -j SNAT --to-source ' % (interface,) + '.'.join(pub_ip_range.split('.')[:3]) + '.' + str(250 + int(nat_prive_ip_plage.split('.')[1][0])))
if 'extra_nat' in nat_type:
### Extra-nat (ex : Pour que le routeur ait accès à internet)
for interface, rules in nat_type['extra_nat'].items():
for ip_source, ip_to_nat in rules.items():
rule = ''
if 'extra_nat_group' in nat_type and interface in nat_type['extra_nat_group']:
rule = "-m set --match-set " + nat_type['extra_nat_group'][interface] + " src "
rule += '-s ' + ip_source + ' -o ' + interface + ' -j SNAT --to-source ' + ip_to_nat
self.add_in_subtable("nat4", subtable, rule)
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 gen_firewall(self, empty=False):
"""Assemblage des chaines et export si il y a lieu"""
self.gen_mangle(empty=empty)
self.gen_nat(empty=empty)
self.gen_filter(empty=empty)
self.global_chain4 = self.nat4 + self.filter4 + self.mangle4
self.global_chain6 = self.nat6 + self.filter6 + self.mangle6
if self.export or self.export4:
print(self.global_chain4)
if self.export or self.export6:
print(self.global_chain6)
def restore_iptables(self, mode='4'):
"""Restoration de l'iptable générée"""
if mode == '6':
global_chain = self.global_chain6
command_to_execute = ["sudo","-n","/sbin/ip6tables-restore"]
else:
global_chain = self.global_chain4
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'))
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.gen_firewall()
if not self.dry:
self.reload()
elif self.action == "stop":
self.gen_firewall(empty=True)
if not self.dry:
self.flush()
else:
raise NotImplementedError("Action non reconnu, actions valides : start, stop ou restart")
def reload(self):
"""Recharge le parefeu"""
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"""
if any('6' in role for role in self.role):
return
self.complete_flush_iptables(mode='6')
if any('4' in role for role in self.role):
self.complete_flush_iptables(mode='4')
return
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 run(args):
table = iptables()
if args.verbose:
table.verbose = True
table.action = args.action
table.export = args.export
table.dry = args.dry
table.export4 = args.export4
table.export6 = args.export6
table.do_action()
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 des parefeus", action="store_true")
parser.add_argument("-e4", "--export4", help="export le contenu du parefeu v4", action="store_true")
parser.add_argument("-e6", "--export6", help="export le contenu du parefeu v6", action="store_true")
parser.add_argument("action", help="Mode reconnus : start, stop ou restart", default="restart", nargs="?")
parser.add_argument("--force", help="Force l'action", action="store_true")
parser.add_argument("--dry", help="Ne pas effectuer de modification sur le pare-feu", default=False, action="store_true")
args = parser.parse_args()
if args.force:
run(args)
for service in api_client.list("services/regen/"):
if service['hostname'] == client_hostname and \
service['service_name'] == 'firewall' and \
service['need_regen']:
run(args)
if not args.dry:
api_client.patch(service['api_url'], data={'need_regen': False})