634 lines
32 KiB
Python
Executable file
634 lines
32 KiB
Python
Executable file
#!/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=False)
|
|
|
|
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)
|
|
|
|
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):
|
|
if '4' in chain:
|
|
self.add(chain, "-A " + subtable + " " + value)
|
|
elif '6' in chain:
|
|
self.add(chain, "-A " + subtable + " " + value)
|
|
else:
|
|
self.add(chain + '4', "-A " + subtable + " " + value)
|
|
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_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 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 : 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":
|
|
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.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.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 : 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_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':
|
|
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, subnt, '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 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 protocol in self.portail_settings['autorized_hosts']:
|
|
for ip, ports in self.portail_settings['autorized_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 capture_connexion_portail(self, subtable="PORTAIL-CAPTIF-REDIRECT"):
|
|
"""Redirige les connexion 80 et 443 vers l'ip cible"""
|
|
self.init_nat(subtable, decision="-")
|
|
for interface in self.interfaces_settings['routable']:
|
|
self.jump_traficfrom("nat", interface, "PREROUTING", subtable, mode='4')
|
|
|
|
for protocol in self.portail_settings['autorized_hosts']:
|
|
for ip, ports in self.portail_settings['autorized_hosts'][protocol].items():
|
|
self.add_in_subtable("nat4", subtable, """-p %s -d %s -m multiport --dports %s -j RETURN""" % (protocol, ip, ','.join(ports)))
|
|
for ip_range, destination in self.portail_settings['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 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_settings['sortie']:
|
|
self.jump_traficto("nat", interface, "POSTROUTING", subtable, mode='4')
|
|
|
|
for protocol in self.portail_settings['autorized_hosts']:
|
|
for ip, ports in self.portail_settings['autorized_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_connexion_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_connexion_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_connexion_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_connexion_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 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)
|
|
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['name'].upper()
|
|
self.init_nat(subtable, decision="-")
|
|
self.jump_all_trafic("nat", "POSTROUTING", subtable, mode='4')
|
|
|
|
nat_prive_ip_plage = nat_type['ip_sources']
|
|
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("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, 26):
|
|
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 = 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, pub_ip_range in nat_type['interfaces_ip_to_nat'].items():
|
|
ip_nat = '.'.join(pub_ip_range.split('.')[:3]) + '.' + str(10*(nat_ip_range - 1) + nat_private_ip//26)
|
|
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]) + '.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 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})
|
|
|