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
6 years ago
import subprocess
import socket
import argparse
import firewall_config
import sys
6 years ago
import os
path =(os.path.dirname(os.path.abspath(__file__)))
config = ConfigParser()
6 years ago
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]
6 years ago
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
6 years ago
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/")
6 years ago
self.verbose = False
self.action = None
self.dry = False
6 years ago
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', [])
6 years ago
def commit(self, chain):
self.add(chain, "COMMIT\n")
6 years ago
def commit_filter(self):
self.add("filter4", "COMMIT\n")
self.add("filter6", "COMMIT\n")
6 years ago
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'):
6 years ago
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)
6 years ago
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)
6 years ago
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()
6 years ago
if self.verbose:
print("Filter : Limit connection src")
self.limit_connection_srcip()
6 years ago
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()
6 years ago
if self.verbose:
print("Filter : limit connection src ip")
self.limit_connection_srcip()
6 years ago
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)
6 years ago
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)
6 years ago
def portail(self, table):
if table == "filter":
self.base_filter()
if self.verbose:
print("Filter : autorisation des ip en sortie")
self.captive_authorized_ip()
6 years ago
if table == "nat":
if self.verbose:
print("Nat : nat et captures les connexions du portail masquerade")
self.nat_connection_portail()
self.capture_connection_portail()
6 years ago
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")
6 years ago
if self.verbose:
print("Limitation des connexions")
self.limit_ssh_connection_input()
self.limit_connection_dstip()
self.external_forward()
6 years ago
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']:
6 years ago
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
6 years ago
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"])
4 years ago
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')
6 years ago
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"]])
6 years ago
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"]])
6 years ago
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"]])
6 years ago
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"]])
6 years ago
if ports:
add_specific_rule(ports, ip_type, chain, interface, subnet, 'udp', 'src')
6 years ago
#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']:
6 years ago
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']:
6 years ago
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"""
6 years ago
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)))
6 years ago
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))
6 years ago
def capture_connection_portail(self, subtable="PORTAIL-CAPTIF-REDIRECT"):
"""Redirige les connexions 80 et 443 vers l'ip cible"""
6 years ago
self.init_nat(subtable, decision="-")
for interface in self.interfaces_settings['routable-portail']:
6 years ago
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'])
6 years ago
def nat_connection_portail(self, subtable="PORTAIL-CAPTIF-NAT"):
6 years ago
"""Nat les connexions derrière l'ip de la machine du portail"""
self.init_nat(subtable, decision="-")
for interface in self.interfaces_settings['sortie']:
6 years ago
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)))
6 years ago
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'):
6 years ago
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['routable']:
6 years ago
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'):
6 years ago
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['sortie']:
6 years ago
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'):
6 years ago
self.init_filter(subtable, decision="-")
for interface in self.interfaces_settings['sortie']:
6 years ago
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'):
6 years ago
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']:
6 years ago
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")
6 years ago
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']:
6 years ago
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'])
6 years ago
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 "')
6 years ago
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)
6 years ago
def nat_prive_ip(self, nat_type):
"""Nat filaire en v4"""
subtable = "CONNEXION-NAT-" + nat_type['name'].upper()
6 years ago
self.init_nat(subtable, decision="-")
self.jump_all_trafic("nat", "POSTROUTING", subtable, mode='4')
6 years ago
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)
6 years ago
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)
6 years ago
def restore_iptables(self, mode='4'):
"""Restoration de l'iptable générée"""
if mode == '6':
global_chain = self.global_chain6
6 years ago
command_to_execute = ["sudo","-n","/sbin/ip6tables-restore"]
else:
global_chain = self.global_chain4
6 years ago
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'))
6 years ago
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()
6 years ago
elif self.action == "stop":
self.gen_firewall(empty=True)
if not self.dry:
self.flush()
6 years ago
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
6 years ago
self.complete_flush_iptables(mode='6')
if any('4' in role for role in self.role):
self.complete_flush_iptables(mode='4')
return
6 years ago
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 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)
6 years ago
def run(args):
6 years ago
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
6 years ago
table.do_action()
6 years ago
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")
6 years ago
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")
6 years ago
args = parser.parse_args()
6 years ago
if args.force:
run(args)
6 years ago
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})