From 67f563cb43a3409db725776763a13d9cfe514d6b Mon Sep 17 00:00:00 2001 From: Domingo Dirutigliano Date: Tue, 26 Sep 2023 17:24:04 +0200 Subject: [PATCH] fix: setting implmented + fixed and optimized rules adding --- backend/modules/firewall/firewall.py | 31 ++++++-- backend/modules/firewall/nftables.py | 113 +++++++++++++++------------ backend/routers/firewall.py | 10 +-- 3 files changed, 93 insertions(+), 61 deletions(-) diff --git a/backend/modules/firewall/firewall.py b/backend/modules/firewall/firewall.py index 243ee37..aaa6e9c 100644 --- a/backend/modules/firewall/firewall.py +++ b/backend/modules/firewall/firewall.py @@ -20,17 +20,32 @@ class FirewallManager: async def reload(self): async with self.lock: - if self.db.get("ENABLED", "0") == "1": - additional_rules = [] - if self.allow_loopback: - pass #TODO complete rule - if self.allow_established: - pass #TODO complete rule - rules = list(map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), policy=self.db.get('POLICY', 'accept')) - nft.set(additional_rules + rules) + if self.enabled: + nft.set( + map(Rule.from_dict, self.db.query('SELECT * FROM rules WHERE active = 1 ORDER BY rule_id;')), + policy=self.policy, + allow_loopback=self.allow_loopback, + allow_established=self.allow_established + ) else: nft.reset() + @property + def policy(self): + return self.db.get("POLICY", "accept") + + @policy.setter + def policy(self, value): + self.db.set("POLICY", value) + + @property + def enabled(self): + return self.db.get("ENABLED", "0") == "1" + + @enabled.setter + def enabled(self, value): + self.db.set("ENABLED", "1" if value else "0") + @property def keep_rules(self): return self.db.get("keep_rules", "0") == "1" diff --git a/backend/modules/firewall/nftables.py b/backend/modules/firewall/nftables.py index 3a01ef9..234858a 100644 --- a/backend/modules/firewall/nftables.py +++ b/backend/modules/firewall/nftables.py @@ -28,7 +28,7 @@ class FiregexTables(NFTableManager): rules_chain_in = "firewall_rules_in" rules_chain_out = "firewall_rules_out" - def init_comands(self, policy:str="accept", policy_out:str="accept"): + def init_comands(self, policy:str="accept", policy_out:str="accept", allow_loopback=False, allow_established=False): return [ {"add":{"chain":{ "family":"inet", @@ -36,7 +36,7 @@ class FiregexTables(NFTableManager): "name":self.rules_chain_in, "type":"filter", "hook":"prerouting", - "prio":-300, + "prio":-150, "policy":policy }}}, {"add":{"chain":{ @@ -45,10 +45,24 @@ class FiregexTables(NFTableManager): "name":self.rules_chain_out, "type":"filter", "hook":"postrouting", - "prio":-300, + "prio":-150, "policy":policy_out }}}, - ] + ] + ([ + { "add":{ "rule": { + "family": "inet", "table": self.table_name, "chain": self.rules_chain_out, + "expr": [{ "match": { "op": "==", "left": { "meta": { "key": "iif"}}, "right": "lo"}},{"accept": None}] + }}}, + { "add":{ "rule": { + "family": "inet", "table": self.table_name, "chain": self.rules_chain_in, + "expr": [{ "match": { "op": "==", "left": { "meta": { "key": "iif"}}, "right": "lo"}},{"accept": None}] + }}} + ] if allow_loopback else []) + ([ + { "add":{ "rule": { + "family": "inet", "table": self.table_name, "chain": self.rules_chain_in, + "expr": [{ "match": {"op": "in", "left": { "ct": { "key": "state" }},"right": ["established"]} }, { "accept": None }] + }}} + ] if allow_established else []) def __init__(self): super().__init__(self.init_comands(),[ @@ -58,52 +72,55 @@ class FiregexTables(NFTableManager): {"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.rules_chain_out}}}, ]) - def set(self, srvs:list[Rule], policy:str="accept"): + def set(self, srvs:list[Rule], policy:str="accept", allow_loopback=False, allow_established=False): srvs = list(srvs) self.reset() if policy == "reject": policy = "drop" - srvs.extend([ - Rule( - proto="any", - ip_src=iprule, - ip_dst=iprule, - port_src_from=1, - port_dst_from=1, - port_src_to=65535, - port_dst_to=65535, - action="reject", - mode="I" - ) for iprule in ["0.0.0.0/0", "::/0"] - ]) - self.cmd(*self.init_comands(policy)) - for ele in srvs[::-1]: self.add(ele) + srvs.append(Rule( + proto="any", + ip_src="any", + ip_dst="any", + port_src_from=1, + port_dst_from=1, + port_src_to=65535, + port_dst_to=65535, + action="reject", + mode="I" + )) + rules = self.init_comands(policy, allow_loopback=allow_loopback, allow_established=allow_established) + self.get_rules(*srvs) + self.cmd(*rules) - def add(self, srv:Rule): - ip_filters = [] - if srv.ip_src.lower() != "any" and srv.ip_dst.lower() != "any": - ip_filters = [ - {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_src)}}, - {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_dst)}}, - ] - - port_filters = [] - if srv.proto != "any": - if srv.port_src_from != 1 or srv.port_src_to != 65535: #Any Port - port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '>=', 'right': int(srv.port_src_from)}}) - port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '<=', 'right': int(srv.port_src_to)}}) - if srv.port_dst_from != 1 or srv.port_dst_to != 65535: #Any Port - port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '>=', 'right': int(srv.port_dst_from)}}) - port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '<=', 'right': int(srv.port_dst_to)}}) - if len(port_filters) == 0: - port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '!=', 'right': 0}}) #filter the protocol if no port is specified - - end_rules = [{'accept': None} if srv.action == "accept" else {'reject': {}} if (srv.action == "reject" and not srv.output_mode) else {'drop': None}] - - self.cmd({ "insert":{ "rule": { - "family": "inet", - "table": self.table_name, - "chain": self.rules_chain_out if srv.output_mode else self.rules_chain_in, - "expr": ip_filters + port_filters + end_rules - #If srv.output_mode is True, then the rule is in the output chain, so the reject action is not allowed - }}}) \ No newline at end of file + def get_rules(self,*srvs:Rule): + rules = [] + for srv in srvs: + ip_filters = [] + if srv.ip_src.lower() != "any" and srv.ip_dst.lower() != "any": + ip_filters = [ + {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_src)}}, + {'match': {'left': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_dst)}}, + ] + + port_filters = [] + if srv.proto != "any": + if srv.port_src_from != 1 or srv.port_src_to != 65535: #Any Port + port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '>=', 'right': int(srv.port_src_from)}}) + port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '<=', 'right': int(srv.port_src_to)}}) + if srv.port_dst_from != 1 or srv.port_dst_to != 65535: #Any Port + port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '>=', 'right': int(srv.port_dst_from)}}) + port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '<=', 'right': int(srv.port_dst_to)}}) + if len(port_filters) == 0: + port_filters.append({'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'op': '!=', 'right': 0}}) #filter the protocol if no port is specified + + end_rules = [{'accept': None} if srv.action == "accept" else {'reject': {}} if (srv.action == "reject" and not srv.output_mode) else {'drop': None}] + rules.append({ "add":{ "rule": { + "family": "inet", + "table": self.table_name, + "chain": self.rules_chain_out if srv.output_mode else self.rules_chain_in, + "expr": ip_filters + port_filters + end_rules + #If srv.output_mode is True, then the rule is in the output chain, so the reject action is not allowed + }}}) + return rules + + def add(self, *srvs:Rule): + self.cmd(*self.get_rules(*srvs)) \ No newline at end of file diff --git a/backend/routers/firewall.py b/backend/routers/firewall.py index 8fd3099..5a4a6f2 100644 --- a/backend/routers/firewall.py +++ b/backend/routers/firewall.py @@ -119,21 +119,21 @@ async def set_settings(form: FirewallSettings): async def get_rule_list(): """Get the list of existent firegex rules""" return { - "policy": db.get("POLICY", "accept"), + "policy": firewall.policy, "rules": db.query("SELECT active, name, proto, ip_src, ip_dst, port_src_from, port_dst_from, port_src_to, port_dst_to, action, mode FROM rules ORDER BY rule_id;"), - "enabled": db.get("ENABLED", "0") == "1" + "enabled": firewall.enabled } @app.get('/enable', response_model=StatusMessageModel) async def enable_firewall(): """Request enabling the firewall""" - db.set("ENABLED", "1") + firewall.enabled = True return await apply_changes() @app.get('/disable', response_model=StatusMessageModel) async def disable_firewall(): """Request disabling the firewall""" - db.set("ENABLED", "0") + firewall.enabled = False return await apply_changes() def parse_and_check_rule(rule:RuleModel): @@ -186,7 +186,7 @@ async def add_new_service(form: RuleFormAdd): ele.action, ele.mode ) for rid, ele in enumerate(rules)] ) - db.set("POLICY", form.policy) + firewall.policy = form.policy except sqlite3.IntegrityError: raise HTTPException(status_code=400, detail="Error saving the rules: maybe there are duplicated rules") return await apply_changes()