From 375aac46f51e8ef2be751953e8384790d5fa92b2 Mon Sep 17 00:00:00 2001 From: Andrea Leardini Date: Fri, 8 Nov 2024 15:04:43 +0100 Subject: [PATCH] fix: validation of domain sets domains Ensure the total number of characters a domain set uses in the dnsmasq configuration file does not exceed 1024 characters. https://github.com/NethServer/nethsecurity/issues/877 --- src/nethsec/objects/__init__.py | 42 +++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/nethsec/objects/__init__.py b/src/nethsec/objects/__init__.py index 7753569a..8628a331 100644 --- a/src/nethsec/objects/__init__.py +++ b/src/nethsec/objects/__init__.py @@ -218,6 +218,28 @@ def get_domain_set_ipsets(uci, id): break return ipsets +def get_domain_set_total_chars(name: str, domains: list[str]): + """ + Calculate the total number of characters the given domain set will use in the dnsmasq configuration file. + This file supports a maximum of 1024 characters per line. + + Example syntax of a domain set in dnsmasq configuration file: + nftset=/example1.org/example2.org/example3.org/.../4#inet#fw4#domainSetName + + Args: + name: name of domain set + domains: a list of valid DNS names + """ + # remove duplicated domains + unique_domains = list(dict.fromkeys(domains)) + total_chars = 8 + 11 # leading "nftset=/" and trailing "4#inet#fw4#" + + for domain in unique_domains: + total_chars += len(domain) + 1 # +1 for trailing "/" + + total_chars += len(name) # domain set name + return total_chars + def add_domain_set(uci, name: str, family: str, domains: list[str], timeout: int = 660) -> str: """ Add domain set to objects config. @@ -241,19 +263,25 @@ def add_domain_set(uci, name: str, family: str, domains: list[str], timeout: int raise utils.ValidationError('family', 'invalid_family', family) if timeout < 0: raise utils.ValidationError('timeout', 'invalid_timeout', timeout) + if get_domain_set_total_chars(name, domains) > 1024 - 10: # -10 chars for safety + raise utils.ValidationError('domain', 'total_chars_exceeded') + + # remove duplicated domains + unique_domains = list(dict.fromkeys(domains)) + id = utils.get_random_id() uci.set('objects', id, 'domain') uci.set('objects', id, 'name', name) uci.set('objects', id, 'family', family) uci.set('objects', id, 'timeout', timeout) - uci.set('objects', id, 'domain', domains) + uci.set('objects', id, 'domain', unique_domains) uci.save('objects') # create ipset inside dhcp config ipset = utils.get_random_id() uci.set('dhcp', ipset, 'ipset') uci.set('dhcp', ipset, 'name', [name]) - uci.set('dhcp', ipset, 'domain', domains) + uci.set('dhcp', ipset, 'domain', unique_domains) uci.set('dhcp', ipset, 'table_family', 'inet') uci.set('dhcp', ipset, 'ns_link', f'objects/{id}') uci.save('dhcp') @@ -293,17 +321,23 @@ def edit_domain_set(uci, id: str, name: str, family: str, domains: list[str], ti raise utils.ValidationError('family', 'invalid_family', family) if timeout < 0: raise utils.ValidationError('timeout', 'invalid_timeout', timeout) + if get_domain_set_total_chars(name, domains) > 1024 - 10: # -10 chars for safety + raise utils.ValidationError('domain', 'total_chars_exceeded') + + # remove duplicated domains + unique_domains = list(dict.fromkeys(domains)) + uci.set('objects', id, 'name', name) uci.set('objects', id, 'family', family) uci.set('objects', id, 'timeout', timeout) - uci.set('objects', id, 'domain', domains) + uci.set('objects', id, 'domain', unique_domains) uci.save('objects') # update ipset inside dhcp config for section in uci.get_all("dhcp"): if uci.get('dhcp', section, 'ns_link', default=None) == f'objects/{id}': uci.set('dhcp', section, 'name', [name]) - uci.set('dhcp', section, 'domain', domains) + uci.set('dhcp', section, 'domain', unique_domains) uci.set('dhcp', section, 'ns_tag', ['automated']) uci.save('dhcp') break