From f160b51e33611075435e98b3e51a7f97d2dd7850 Mon Sep 17 00:00:00 2001 From: Hugo LEVY-FALK Date: Wed, 16 Jan 2019 22:57:50 +0100 Subject: [PATCH] Remplissage de la table NAT. --- .gitignore | 144 +++++++++++++++++++++++++++++++++++++++ firewall.nft | 6 -- firewall.py | 50 ++++++++++++-- nat.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 375 insertions(+), 13 deletions(-) create mode 100644 .gitignore create mode 100644 nat.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..85ed0ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,144 @@ +Pipfile* +# Created by https://www.gitignore.io/api/vim,python +# Edit at https://www.gitignore.io/?templates=vim,python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +### Python Patch ### +.venv/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.gitignore.io/api/vim,python diff --git a/firewall.nft b/firewall.nft index 16eb0f3..a3d3c1b 100644 --- a/firewall.nft +++ b/firewall.nft @@ -73,10 +73,4 @@ table inet firewall { } table nat { -# TODO : on met 27 Ips internes par ip publiques -# Users : 193.48.225.10-119 -# Prerezotage 193.48.225.240-254 -# Aloes : 193.48.225.120-129 -# Federez : 193.48.225.130-199 -# Server : 193.48.225.200-209 } diff --git a/firewall.py b/firewall.py index 621362a..b81ed93 100644 --- a/firewall.py +++ b/firewall.py @@ -89,12 +89,35 @@ class Parser: return netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) @staticmethod def IPv4(ip): - """Check an IPv4 validity.""" - return netaddr.IPAddress(ip, version=4) + """Check an IPv4 validity. + + Args: + ip: can either be a tuple (in this case returns an IPRange), a + single IP address or a IP Network. + """ + if isinstance(ip, tuple): + begin, end = ip + return netaddr.IPRange(begin, end, version=4) + try: + return netaddr.IPAddress(ip, version=4) + except ValueError: + return netaddr.IPNetwork(ip, version=4) @staticmethod def IPv6(ip): - """Check a IPv6 validity.""" - return netaddr.IPAddress(ip, version=6) + """Check a IPv6 validity. + + Args: + ip: can either be a tuple (in this case returns an IPRange), a + single IP address or a IP Network. + """ + if isinstance(ip, tuple): + begin, end = ip + return netaddr.IPRange(begin, end, version=6) + try: + return netaddr.IPAddress(ip, version=6) + except ValueError: + return netaddr.IPNetwork(ip, version=6) + @staticmethod def protocol(protocol): """Check a protocol validity.""" @@ -120,13 +143,17 @@ class NetfilterSet: ADDRESS_FAMILIES = {'ip', 'ip6', 'inet', 'arp', 'bridge', 'netdev'} + FLAGS = {'constant', 'interval', 'timeout'} + def __init__(self, name, type_, # e.g.: ('MAC', 'IPv4') target_content=None, use_sudo=True, address_family='inet', # Manage both IPv4 and IPv6. - table_name='filter'): + table_name='filter', + flags = [] + ): self.name = name self.content = set() # self.type @@ -135,6 +162,7 @@ class NetfilterSet: # self.address_family self.set_address_family(address_family) self.table = table_name + self.set_flags(flags) sudo = ["/usr/bin/sudo"] * int(bool(use_sudo)) self.nft = [*sudo, "/usr/sbin/nft"] if target_content: @@ -167,6 +195,13 @@ class NetfilterSet: 'Invalid address_family: "{}".'.format(address_family)) self.address_family = address_family + def set_flags(self, flags_): + """Check set flags validity before saving them.""" + for f in flags_: + if f not in self.FLAGS: + raise ValueError('Invalid flag: "{}".'.format(f)) + self.flags = _flags + def create_in_kernel(self): """Create the set, removing existing set if needed.""" # Delete set if it exists with wrong type @@ -191,10 +226,11 @@ class NetfilterSet: """Create the non-existing set, creating table if needed.""" create_set = [ *self.nft, - 'add set {addr_family} {table} {set_} {{ type {type_} ; }}'.format( + 'add set {addr_family} {table} {set_} {{ type {type_} ; flags {flags}}}'.format( addr_family=self.address_family, table=self.table, set_=self.name, - type_=' . '.join(self.TYPES[i] for i in self.type)) + type_=' . '.join(self.TYPES[i] for i in self.type)), + flags=', '.join(self.flags) ] return_code = CommandExec.run(create_set, allowed_return_codes=(0, 1)) if return_code == 0: diff --git a/nat.py b/nat.py new file mode 100644 index 0000000..856d2dc --- /dev/null +++ b/nat.py @@ -0,0 +1,188 @@ +#! /usr/bin/python3 + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Copyright © 2019 Hugo Levy-Falk + +""" +Creates the nat set. +""" + +import logging +from configparser import ConfigParser + +import netaddr + +from firewall import NetfilterSet + +CONFIG = ConfigParser() +CONFIG.read('config.ini') + + +def create_nat(name, range_in, range_out, first_port, last_port): + """Create two nftables tables for the nat: + - _address : which link a (or a range of) local address to a + public address; + - _port : which links a local address to a + range of ports. + + Args: + name: name of the sets + range_in: an IPRange with the private IP address + range_out: an IPRange with the public IP address + first_port: the first port used for the nat + last_port: the last port used for the nat + Returns: + (_address, _port) which are NetfilterSet + """ + assert last_port >= first_port, (name + ": Your first_port " + "is lower than your last_port") + nb_private_by_public = range_in.size / range_out.size + nb_port_by_ip = (last_port - first_port + 1) / nb_private_by_public + + ports = [] + ips = [] + + port = first_port + for ip, port in range_in: + ports.append(( + str(netaddr.IPAddress(ip)), + "%d-%d" % (port, port+nb_port_by_ip) + )) + port += nb_port_by_ip + 1 + if port >= last_port: + port = first_port + ip = range_in.first + for ip_out in range_out: + ips.append(( + '-'.join([ + str(netaddr.IPAddress(ip)), + str(netaddr.IPAddress(ip+nb_private_by_public)) + ]), + str(ip_out) + )) + ip += nb_private_by_public + 1 + + return ( + NetfilterSet( + target_content=ips, + type_=('IPv4', 'IPv4'), + name=name, + table_name='nat', + ), + NetfilterSet( + target_content=ports, + type_=('IPv4', 'port'), + name=name, + table_name='nat', + ), + ) + + +def create_nat_adherent(): + range_in = netaddr.IPRange(CONFIG['range_in_adherent']) + range_out = netaddr.IPRange(CONFIG['range_out_adherent']) + first_port = CONFIG['first_port_adherent'] + last_port = CONFIG['last_port_adherent'] + return create_nat( + 'adherent', + range_in, + range_out, + first_port, + last_port + ) + + +def create_nat_federez(): + range_in = netaddr.IPRange(CONFIG['range_in_federez']) + range_out = netaddr.IPRange(CONFIG['range_out_federez']) + first_port = CONFIG['first_port_federez'] + last_port = CONFIG['last_port_federez'] + return create_nat( + 'federez', + range_in, + range_out, + first_port, + last_port + ) + + +def create_nat_aloes(): + range_in = netaddr.IPRange(CONFIG['range_in_aloes']) + range_out = netaddr.IPRange(CONFIG['range_out_aloes']) + first_port = CONFIG['first_port_aloes'] + last_port = CONFIG['last_port_aloes'] + return create_nat( + 'aloes', + range_in, + range_out, + first_port, + last_port + ) + + +def create_nat_admin(): + range_in = netaddr.IPRange(CONFIG['range_in_admin']) + range_out = netaddr.IPRange(CONFIG['range_out_admin']) + first_port = CONFIG['first_port_admin'] + last_port = CONFIG['last_port_admin'] + return create_nat( + 'admin', + range_in, + range_out, + first_port, + last_port + ) + + +def create_nat_prerezotage(): + range_in = netaddr.IPRange(CONFIG['range_in_prerezotage']) + range_out = netaddr.IPRange(CONFIG['range_out_prerezotage']) + first_port = CONFIG['first_port_prerezotage'] + last_port = CONFIG['last_port_prerezotage'] + return create_nat( + 'prerezotage', + range_in, + range_out, + first_port, + last_port + ) + + +def main(): + logging.info("Creating adherent nat...") + address, port = create_nat_adherent() + address.manage() + port.manage() + logging.info("Done.") + logging.info("Creating federez nat...") + address, port = create_nat_federez() + address.manage() + port.manage() + logging.info("Done.") + logging.info("Creating aloes nat...") + address, port = create_nat_aloes() + address.manage() + port.manage() + logging.info("Done.") + logging.info("Creating admin nat...") + address, port = create_nat_admin() + address.manage() + port.manage() + logging.info("Done.") + logging.info("Creating prerezotage nat...") + address, port = create_nat_prerezotage() + address.manage() + port.manage() + logging.info("Done.")