From ec8095492772c8da6e0b99710119a5ef090eb595 Mon Sep 17 00:00:00 2001 From: Hugo Levy-Falk Date: Tue, 12 Mar 2019 22:06:21 +0100 Subject: [PATCH] MAC-IP table --- .gitignore | 1 + .gitmodules | 3 + checkmac.nft | 6 +- config.ini.example | 9 ++ firewall.nft | 7 +- firewall.py | 309 +++++++++++++++++++++++++++++++++++++++------ global_policy.nft | 7 +- mac_ip.py | 78 ++++++++++++ nat.nft | 13 +- nat.py | 133 ++++++++++--------- re2oapi | 1 + zones/adherent.nft | 5 +- 12 files changed, 453 insertions(+), 119 deletions(-) create mode 100644 .gitmodules create mode 100644 config.ini.example create mode 100644 mac_ip.py create mode 160000 re2oapi diff --git a/.gitignore b/.gitignore index 85ed0ea..30d8b4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +config.ini Pipfile* # Created by https://www.gitignore.io/api/vim,python # Edit at https://www.gitignore.io/?templates=vim,python diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..361b0a1 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "re2oapi"] + path = re2oapi + url = https://gitlab.federez.net/re2o/re2oapi.git diff --git a/checkmac.nft b/checkmac.nft index eb0c101..b197d9f 100644 --- a/checkmac.nft +++ b/checkmac.nft @@ -1,9 +1,13 @@ # Table checkmac, à bas le spoof d'ips. -table firewall { +table inet firewall { set ip_mac { type ipv4_addr . ether_addr } + chain checkmac { + meta iifname $if_adherent ip saddr . ether saddr != @ip_mac drop + meta iifname $if_aloes ip saddr . ether saddr != @ip_mac drop + } } # Note : diff --git a/config.ini.example b/config.ini.example new file mode 100644 index 0000000..626573b --- /dev/null +++ b/config.ini.example @@ -0,0 +1,9 @@ +[NAT] +range_in_adherent=10.69.0.0/31 +range_out_adherent=193.48.225.11-193.48.225.12 +first_port_adherent=11135 +last_port_adherent=65535 +[Re2o] +hostname=re2o.rezometz.org +password=some_pass +username=service-daemon diff --git a/firewall.nft b/firewall.nft index 15fdd66..9219d2a 100755 --- a/firewall.nft +++ b/firewall.nft @@ -1,4 +1,4 @@ -#! /usr/sbin/nft -f +#! /usr/sbin/nft -I /usr/local/firewall -f # Remise à zéro des règles du pare-feu flush ruleset @@ -27,7 +27,10 @@ table inet firewall { policy drop; # Applique la politique globale - #jump global + jump global + + # Passage par le checkmac pour les concernés + jump checkmac # Filtre sur les interfaces entrantes, ne pas accepter # directement dans la chaine, mais retourner. diff --git a/firewall.py b/firewall.py index b81ed93..d8cf4f4 100755 --- a/firewall.py +++ b/firewall.py @@ -33,13 +33,14 @@ Module for nftables set management. import logging import subprocess +import re import netaddr # MAC, IPv4, IPv6 import requests from collections import Iterable -from config import Config +from configparser import ConfigParser class ExecError(Exception): @@ -95,13 +96,14 @@ class Parser: 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) + except netaddr.core.AddrFormatError: + try: + return netaddr.IPNetwork(ip, version=4) + except netaddr.core.AddrFormatError: + begin, end = ip.split('-') + return netaddr.IPRange(begin, end) @staticmethod def IPv6(ip): """Check a IPv6 validity. @@ -127,9 +129,15 @@ class Parser: @staticmethod def port_number(port): """Check a port validity.""" - port_number = int(port) - if 0 <= port_number < 65536: - return port_number + try: + port_number = int(port) + if 0 <= port_number < 65536: + return port_number + except ValueError: + begin, end = port.split('-') + begin, end = int(begin), int(end) + if 0 <= begin < end <= 65536: + return port raise ValueError('Invalid port number: "{}".'.format(port)) class NetfilterSet: @@ -145,6 +153,8 @@ class NetfilterSet: FLAGS = {'constant', 'interval', 'timeout'} + NFT_TYPE = {'set', 'map'} + def __init__(self, name, type_, # e.g.: ('MAC', 'IPv4') @@ -152,21 +162,30 @@ class NetfilterSet: use_sudo=True, address_family='inet', # Manage both IPv4 and IPv6. table_name='filter', - flags = [] + flags = [], + type_from=None ): self.name = name self.content = set() # self.type self.set_type(type_) + if type_from: + self.set_type_from(type_from) + self.nft_type = 'map' + self.key_filters = tuple(self.FILTERS[i] for i in self.type_from) + else: + self.nft_type = 'set' self.filters = tuple(self.FILTERS[i] for i in self.type) + self.set_flags(flags) # 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: + if target_content and self.nft_type == 'set': self._target_content = self.validate_set_data(target_content) + elif target_content and self.nft_type == 'map': + self._target_content = self.validate_map_data(target_content) else: self._target_content = set() @@ -181,6 +200,9 @@ class NetfilterSet: def filter(self, elements): return (self.filters[i](element) for i, element in enumerate(elements)) + def filter_key(self, elements): + return (self.key_filters[i](element) for i, element in enumerate(elements)) + def set_type(self, type_): """Check set type validity and store it along with a type checker.""" for element_type in type_: @@ -188,6 +210,13 @@ class NetfilterSet: raise ValueError('Invalid type: "{}".'.format(element_type)) self.type = type_ + def set_type_from(self, type_): + """Check set type validity and store it along with a type checker.""" + for element_type in type_: + if element_type not in self.TYPES: + raise ValueError('Invalid type: "{}".'.format(element_type)) + self.type_from = type_ + def set_address_family(self, address_family='ip'): """Set set addres_family, defaulting to "ip" like nftables.""" if address_family not in self.ADDRESS_FAMILIES: @@ -200,7 +229,7 @@ class NetfilterSet: for f in flags_: if f not in self.FLAGS: raise ValueError('Invalid flag: "{}".'.format(f)) - self.flags = _flags + self.flags = set(flags_) def create_in_kernel(self): """Create the set, removing existing set if needed.""" @@ -217,21 +246,35 @@ class NetfilterSet: """Delete the set, table and set must exist.""" CommandExec.run([ *self.nft, - 'delete set {addr_family} {table} {set_}'.format( + 'delete {nft_type} {addr_family} {table} {set_}'.format( + nft_type=self.nft_type, addr_family=self.address_family, table=self.table, set_=self.name) ]) def _create_new_set_in_kernel(self): """Create the non-existing set, creating table if needed.""" + if self.flags: + nft_command = 'add {nft_type} {addr_family} {table} {set_} {{ type {type_} ; flags {flags};}}'.format( + nft_type=self.nft_type, + addr_family=self.address_family, + table=self.table, + set_=self.name, + type_=self.format_type(), + flags=', '.join(self.flags) + ) + else: + nft_command = 'add {nft_type} {addr_family} {table} {set_} {{ type {type_} ;}}'.format( + nft_type=self.nft_type, + addr_family=self.address_family, + table=self.table, + set_=self.name, + type_=self.format_type(), + ) create_set = [ *self.nft, - '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)), - flags=', '.join(self.flags) - ] + nft_command + ] return_code = CommandExec.run(create_set, allowed_return_codes=(0, 1)) if return_code == 0: return # Set creation successful. @@ -261,7 +304,33 @@ class NetfilterSet: .format(len(errors), '",\n"'.join(map(str, errors)))) return set_ + def validate_map_data(self, dict_data): + """ + Validate data, returning it or raising a ValueError. + + For MAC-IPv4 set, data must be an iterable of (MAC, IPv4) iterables. + """ + set_ = {} + errors = [] + for key in dict_data: + try: + set_[tuple(self.filter_key(key))] = tuple(self.filter(dict_data[key])) + except Exception as err: + errors.append(err) + if errors: + raise ValueError( + 'Error parsing data, encountered the folowing {} errors.\n"{}"' + .format(len(errors), '",\n"'.join(map(str, errors)))) + return set_ + def _apply_target_content(self): + """Change netfilter content to target set.""" + if self.nft_type == 'set': + self._apply_target_content_set() + else: + self._apply_target_content_map() + + def _apply_target_content_set(self): """Change netfilter set content to target set.""" current_set = self.get_netfilter_set_content() if current_set is None: @@ -272,6 +341,23 @@ class NetfilterSet: to_add = self._target_content - current_set self._change_set_content(delete=to_delete, add=to_add) + def _apply_target_content_map(self): + """Change netfilter set content to target set.""" + current_map = self.get_netfilter_map_content() + if current_map is None: + raise ValueError('Cannot change "{}" netfilter map content: map ' + 'do not exist in "{}" "{}".'.format( + self.name, self.address_family, self.table)) + keys_to_delete = current_map.keys() - self._target_content.keys() + keys_to_add = self._target_content.keys() - current_map.keys() + keys_to_check = current_map.keys() & self._target_content.keys() + for k in keys_to_check: + if current_map[k] != self._target_content[k]: + keys_to_add.add(k) + keys_to_delete.add(k) + to_add = {k : self._target_content[k] for k in keys_to_add} + self._change_map_content(delete=keys_to_delete, add=to_add) + def _change_set_content(self, delete=None, add=None): todo = [tuple_ for tuple_ in (('add', add), ('delete', delete)) if tuple_[1]] @@ -286,6 +372,32 @@ class NetfilterSet: ] CommandExec.run(command) + def _change_map_content(self, delete=None, add=None): + if delete: + content = ', '.join(' . '.join(str(element) for element in tuple_) + for tuple_ in delete) + command = [ + *self.nft, + 'delete element {addr_family} {table} {set_} {{{content}}}' \ + .format(addr_family=self.address_family, + table=self.table, set_=self.name, content=content) + ] + CommandExec.run(command) + if add: + content = ', '.join( + ' . '.join(str(element) for element in tuple_) + + ' : ' + + ' . '.join(str(element) for element in add[tuple_]) + for tuple_ in add + ) + command = [ + *self.nft, + 'add element {addr_family} {table} {set_} {{{content}}}' \ + .format(addr_family=self.address_family, + table=self.table, set_=self.name, content=content) + ] + CommandExec.run(command) + def _get_raw_netfilter_set(self, parse_elements=True): """Return a dict describing the netfilter set matching self or None.""" _, stdout, _ = CommandExec.run_check_output( @@ -301,9 +413,25 @@ class NetfilterSet: if netfilter_set['name'] != self.name \ or netfilter_set['address_family'] != self.address_family \ or netfilter_set['table'] != self.table \ - or netfilter_set['type'] != [ - self.TYPES[type_] for type_ in self.type]: - raise ValueError('Did not get the right set, too wrong to fix.') + or not self.has_type(netfilter_set['type']) \ + or netfilter_set.get('flags', set()) != self.flags: + raise ValueError( + 'Did not get the right set, too wrong to fix. Got ' + + str(netfilter_set) + + ("\nExpected : " + "\n\tname: {name}" + "\n\taddress_family: {family}" + "\n\ttable: {table}" + "\n\tflags: {flags}" + "\n\ttypes: {types}" + ).format( + name=self.name, + family=self.address_family, + table=self.table, + flags=self.flags, + types=tuple(self.TYPES[t] for t in self.type) + ) + ) if parse_elements: if netfilter_set['raw_content']: netfilter_set['content'] = self.validate_set_data(( @@ -313,21 +441,79 @@ class NetfilterSet: netfilter_set['content'] = set() return netfilter_set + def _get_raw_netfilter_map(self, parse_elements=True): + """Return a dict describing the netfilter map matching self or None.""" + _, stdout, _ = CommandExec.run_check_output( + [*self.nft, '-nn', 'list map {addr_family} {table} {set_}'.format( + addr_family=self.address_family, table=self.table, + set_=self.name)], + allowed_return_codes=(0, 1) # In case table do not exist + ) + if not stdout: + return None + else: + netfilter_set = self._parse_netfilter_map_string(stdout) + if netfilter_set['name'] != self.name \ + or netfilter_set['address_family'] != self.address_family \ + or netfilter_set['table'] != self.table \ + or not self.has_type(netfilter_set['type']): + raise ValueError('Did not get the right map, too wrong to fix.') + if parse_elements: + if netfilter_set['raw_content']: + netfilter_set['content'] = self.validate_map_data({ + (element.strip() for element in n_uplet.split(' : ')[0].split(' . ')) : + (element.strip() for element in n_uplet.split(' : ')[1].split(' . ')) + for n_uplet in netfilter_set['raw_content'].split(',') + }) + else: + netfilter_set['content'] = {} + return netfilter_set + @staticmethod def _parse_netfilter_set_string(set_string): """ Parse netfilter set definition and return set as dict. Do not validate content type against detected set type. - Return a dict with 'name', 'address_family', 'table', 'type', + Return a dict with 'name', 'address_family', 'table', 'type', 'flags', + 'raw_content' keys (all strings, 'raw_content' can be None). + Raise ValueError in case of unexpected syntax. + """ + # A.K.A. Really, I don't hate you, so please don't hate me... + regexp = ( + "table (?P\w+)+ (?P\w+) \{\n" + "\s*set (?P\w+) \{\n" + "\s*type (?P(\w+( \. )?)+)\n" + "(\s*elements = \{ " + "(?P((\n\s*)?([\w:\.]+( \. )?)+,?)*) " + "\}\n)?" + "\s*\}\n" + "\s*\}" + ) + values = re.match(regexp, set_string).groupdict() + return { + 'address_family': values['address_family'], + 'table': values['table'], + 'name': values['name'], + 'type': values['type'].split(' . '), + 'raw_content': values['elements'], + } + + @staticmethod + def _parse_netfilter_map_string(set_string): + """ + Parse netfilter map definition and return map as dict. + + Do not validate content type against detected map type. + Return a dict with 'name', 'address_family', 'table', 'type', 'flags' 'raw_content' keys (all strings, 'raw_content' can be None). Raise ValueError in case of unexpected syntax. """ # Fragile code since using lexer / parser would be quite heavy lines = [line.lstrip('\t ') for line in set_string.strip().splitlines()] errors = [] - # 5 lines when empty, 6 with elements = { … } - if len(lines) not in (5, 6): + # 5 lines when empty, 6 with elements = { … } + one for flags + if len(lines) not in (5, 6, 7): errors.append('Error, expecting 5 or 6 lines for set definition, ' 'got "{}".'.format(set_string)) @@ -346,30 +532,54 @@ class NetfilterSet: line = next(line_iterator).split(' ') # line #2 # 'set {' - if len(line) != 3 or line[0] != 'set' or line[2] != '{': + if len(line) != 3 or line[0] != 'map' or line[2] != '{': errors.append('Cannot parse set definition, expecting "set ' '{{", got "{}".' .format(' '.join(line))) else: set_definition['name'] = line[1] - line = next(line_iterator).split(' ') # line #3 - # 'type [. ]...' - if len(line) < 2 or len(line) % 2 != 0 or line[0] != 'type' \ - or any(element != '.' for element in line[2::2]): + line, elements_type = next(line_iterator).split(' : ') # line #3 + # 'type [. ]... : [. ]...' + line = line.split(' ') + if len(line) < 2: + errors.append( + 'Cannot parse type definition, left side of \':\' is too short : %s' % line + ) + type_, keys_type = line[0], line[1:] + elements_type = elements_type.split(' ') + if type_ != 'type': + errors.append( + 'Cannot parse type definition, expected first word \'type\', got %s' % type_ + ) + elif len(elements_type) % 2 != 1 or len(keys_type) % 2 != 1 \ + or any(e != '.' for e in elements_type[1::2]) \ + or any(e != '.' for e in keys_type[1::2]): errors.append( 'Cannot parse type definition, expecting "type ' - '[. ]...", got "{}".'.format(' '.join(line))) + '[. ]... : [. ]...", got "{}".'.format(' '.join(line))) else: - set_definition['type'] = line[1::2] + set_definition['type'] = (keys_type[::2], elements_type[::2]) - if len(lines) == 6: + # here we can have the flags, if there are any + # flags , , ... + if len(lines) >= 6: + line = next(line_iterator) + if line[:5] == 'flags': # If there are actually flags + set_definition['flags'] = {f.strip() for f in line[:5].strip().split(',')} + + if len(lines) >= 6: # set is not empty, getting raw elements - line = next(line_iterator) # Unsplit line #4 - if line[:13] != 'elements = { ' or line[-1] != '}': - errors.append('Cannot parse set elements, expecting "elements ' + if 'flags' in set_definition and len(lines) == 7: # the line unsplitted previously has been used. + line = next(line_iterator) # Unsplit line #4 + print(line) + if ('flags' in set_definition and len(lines)==7) or ('flags' not in set_definition and len(lines)==6) : + if line[:13] != 'elements = { ' or line[-1] != '}': + errors.append('Cannot parse set elements, expecting "elements ' '= {{ <…>}}", got "{}".'.format(line)) + else: + set_definition['raw_content'] = line[13:-1].strip() else: - set_definition['raw_content'] = line[13:-1].strip() + set_definition['raw_content'] = None else: set_definition['raw_content'] = None @@ -393,22 +603,41 @@ class NetfilterSet: else: return netfilter_set['content'] + def get_netfilter_map_content(self): + """Return current set content from netfilter.""" + netfilter_set = self._get_raw_netfilter_map(parse_elements=True) + if netfilter_set is None: + return None + else: + return netfilter_set['content'] + def has_type(self, type_): """Check if some type match the set's one.""" - return tuple(self.TYPES[t] for t in self.type) == tuple(type_) + if self.nft_type == 'set': + return tuple(self.TYPES[t] for t in self.type) == tuple(type_) + else: + return tuple(self.TYPES[t] for t in self.type) == tuple(type_[1]) and \ + tuple(self.TYPES[t] for t in self.type_from) == tuple(type_[0]) def manage(self): """Create set if needed and populate it with target content.""" self.create_in_kernel() self._apply_target_content() + def format_type(self): + if self.nft_type == 'set': + return ' . '.join(self.TYPES[i] for i in self.type) + else: + return ' . '.join(self.TYPES[i] for i in self.type_from) + ' : ' + ' . '.join(self.TYPES[i] for i in self.type) + class Firewall: """Manages the firewall using nftables.""" @staticmethod def manage_sets(sets, address_family=None, table=None, use_sudo=None): - CONFIG = Config() + CONFIG = ConfigParser() + CONFIG.read('config.ini') address_family = address_family or CONFIG['address_family'] or 'inet' table = table or CONFIG['table'] or 'filter' sudo = use_sudo or (use_sudo is None and CONFIG['use_sudo']) diff --git a/global_policy.nft b/global_policy.nft index f6bdbf5..e44de60 100644 --- a/global_policy.nft +++ b/global_policy.nft @@ -1,9 +1,8 @@ -table firewall { +table inet firewall { chain global { - # Interdiction de l'encapsulation ipv6 dans ipv4 - ip protocol 6 drop; - + # On accepte les connexions déjà établies + ct state established,related accept # Gestion de l'ICMP : # On empêche le ping flood icmp type echo-request limit rate over 50/second drop; diff --git a/mac_ip.py b/mac_ip.py new file mode 100644 index 0000000..af67603 --- /dev/null +++ b/mac_ip.py @@ -0,0 +1,78 @@ +#! /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 + +from re2oapi import Re2oAPIClient + +from firewall import NetfilterSet + +CONFIG = ConfigParser() +CONFIG.read('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) + +api_client.list('dhcp/hostmacip') + +def gen_ip_mac_set(): + """Generates the ip_mac set in nftables. + + Returns: + A NetfilterSet object with the allowed ip - mac pairs. + """ + hosts = api_client.list('dhcp/hostmacip') + content = [ + (h['ipv4'], h['mac_address']) + for h in hosts + if h['ipv4'] and h['mac_address'] + ] + return NetfilterSet( + target_content=content, + type_=('IPv4', 'MAC'), + name='ip_mac', + table_name='firewall', + ) + + +def update_macip(): + log = logging.getLogger(__name__) + if not log.hasHandlers(): + handler = logging.StreamHandler() + formatter = logging.Formatter( + "%(asctime)s %(levelname)s %(name)s %(message)s" + ) + handler.setFormatter(formatter) + log.addHandler(handler) + log.setLevel(logging.INFO) + log.info('Updating the ip - mac set...') + ip_mac = gen_ip_mac_set() + log.info('Applying modifications...') + ip_mac.manage() + log.info('Done') + + +if __name__=='__main__': + update_macip() diff --git a/nat.nft b/nat.nft index 68f4d98..3834609 100644 --- a/nat.nft +++ b/nat.nft @@ -10,17 +10,8 @@ table ip nat { chain postrouting { type nat hook postrouting priority 100 - meta oifname != $if_supelec return - - meta iifname vmap { - $if_adherent : goto adh_nat, - $if_admin : goto adm_nat, - $if_aloes : goto aloes_nat, - $if_federez : goto federez_nat, - $if_prerezotage : goto prerezotage_nat, - } - - ip saddr 10.0.0.0/8 masquerade + # ip saddr 10.0.0.0/8 snat to 193.48.225.3 + meta oifname $if_supelec snat to 193.48.225.3 } diff --git a/nat.py b/nat.py index 856d2dc..4c18fa9 100644 --- a/nat.py +++ b/nat.py @@ -30,6 +30,18 @@ CONFIG = ConfigParser() CONFIG.read('config.ini') +def get_ip_iterable_from_str(ip): + try: + ret = netaddr.IPGlob(ip) + except netaddr.core.AddrFormatError: + try: + ret = netaddr.IPNetwork(ip) + except netaddr.core.AddrFormatError: + begin,end = ip.split('-') + ret = netaddr.IPRange(begin,end) + return ret + + 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 @@ -46,55 +58,53 @@ def create_nat(name, range_in, range_out, first_port, last_port): Returns: (_address, _port) which are NetfilterSet """ - assert last_port >= first_port, (name + ": Your first_port " + assert 0 <= first_port < last_port < 65536, (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 + 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 = [] + 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) - )) + for ip in range_in: + ports[(str(netaddr.IPAddress(ip)),)] = ("%d-%d" % (port, min(port+nb_port_by_ip, 65535)),) 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([ + ips[('-'.join([ str(netaddr.IPAddress(ip)), str(netaddr.IPAddress(ip+nb_private_by_public)) - ]), - str(ip_out) - )) + ]),)] = (str(ip_out),) ip += nb_private_by_public + 1 return ( NetfilterSet( target_content=ips, - type_=('IPv4', 'IPv4'), - name=name, + type_=('IPv4',), + name=name+'_nat_address', table_name='nat', + flags=('interval',), + type_from=('IPv4',) ), NetfilterSet( target_content=ports, - type_=('IPv4', 'port'), - name=name, + type_=('port',), + name=name+'_nat_port', table_name='nat', + flags=('interval',), + type_from=('IPv4',) ), ) 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'] + range_in = get_ip_iterable_from_str(CONFIG['NAT']['range_in_adherent']) + range_out = get_ip_iterable_from_str(CONFIG['NAT']['range_out_adherent']) + first_port = int(CONFIG['NAT']['first_port_adherent']) + last_port = int(CONFIG['NAT']['last_port_adherent']) return create_nat( 'adherent', range_in, @@ -105,10 +115,10 @@ def create_nat_adherent(): 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'] + range_in = get_ip_iterable_from_str(CONFIG['NAT']['range_in_federez']) + range_out = get_ip_iterable_from_str(CONFIG['NAT']['range_out_federez']) + first_port = CONFIG['NAT']['first_port_federez'] + last_port = CONFIG['NAT']['last_port_federez'] return create_nat( 'federez', range_in, @@ -119,10 +129,10 @@ def create_nat_federez(): 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'] + range_in = get_ip_iterable_from_str(CONFIG['NAT']['range_in_aloes']) + range_out = get_ip_iterable_from_str(CONFIG['NAT']['range_out_aloes']) + first_port = CONFIG['NAT']['first_port_aloes'] + last_port = CONFIG['NAT']['last_port_aloes'] return create_nat( 'aloes', range_in, @@ -133,10 +143,10 @@ def create_nat_aloes(): 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'] + range_in = get_ip_iterable_from_str(CONFIG['NAT']['range_in_admin']) + range_out = get_ip_iterable_from_str(CONFIG['NAT']['range_out_admin']) + first_port = CONFIG['NAT']['first_port_admin'] + last_port = CONFIG['NAT']['last_port_admin'] return create_nat( 'admin', range_in, @@ -147,10 +157,10 @@ def create_nat_admin(): 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'] + range_in = get_ip_iterable_from_str(CONFIG['NAT']['range_in_prerezotage']) + range_out = get_ip_iterable_from_str(CONFIG['NAT']['range_out_prerezotage']) + first_port = CONFIG['NAT']['first_port_prerezotage'] + last_port = CONFIG['NAT']['last_port_prerezotage'] return create_nat( 'prerezotage', range_in, @@ -166,23 +176,28 @@ def main(): 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.") + #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.") + + +if __name__=='__main__': + logging.info('Updating the NAT table.') + main() diff --git a/re2oapi b/re2oapi new file mode 160000 index 0000000..b12df74 --- /dev/null +++ b/re2oapi @@ -0,0 +1 @@ +Subproject commit b12df74fe73f351986ff51c8122089644218f8fe diff --git a/zones/adherent.nft b/zones/adherent.nft index 68c8a36..4c6f8d5 100644 --- a/zones/adherent.nft +++ b/zones/adherent.nft @@ -7,8 +7,6 @@ table inet firewall { } chain from_adh { - # On passe d'abord par le checkmac pour éviter le spoof d'ip: - #ip saddr . ether saddr != @ip_mac drop } } @@ -22,10 +20,13 @@ table ip nat { # nft add element nat adherent_nat_address {10.69.0.1-10.69.0.31 : 193.48.225.11} map adherent_nat_address { type ipv4_addr: ipv4_addr + flags interval } # exemple: 10.69.0.1 : 11135-12834 # On peut aussi ajouter dynamiquement des éléments : # nft add element nat adherent_nat_port {10.69.0.1 : 11135-12834} + # Sauf qu'on peut pas faire de maps d'intervalles (seules les clés peuvent en être) + # du coup je vois rien d'autre à faire que de modifier à la volée les règles... map adherent_nat_port { type ipv4_addr: inet_service flags interval