From 63ba0e94e7e0dde0302aae8abfbebe277c45b946 Mon Sep 17 00:00:00 2001 From: DomySh Date: Thu, 21 Jul 2022 20:25:39 +0200 Subject: [PATCH] Moduled Firegex, Merging pt1 (not finished and not working yet) --- Dockerfile | 4 +- backend/app.py | 3 +- backend/modules/__init__.py | 2 - backend/modules/firegex.py | 276 ---------- backend/modules/nfregex/__init__.py | 0 backend/modules/nfregex/firegex.py | 151 ++++++ backend/modules/{ => nfregex}/firewall.py | 11 +- backend/modules/nfregex/models.py | 30 ++ backend/modules/proxy | Bin 0 -> 360472 bytes backend/modules/regexproxy/__init__.py | 0 backend/modules/regexproxy/proxy.py | 116 ++++ backend/modules/regexproxy/utils.py | 196 +++++++ backend/nfqueue/proxy.cpp | 497 ++++++++++++++++++ backend/regextcpproxy.db | Bin 0 -> 32768 bytes backend/routers/nfregex.py | 64 ++- backend/routers/regexproxy.py | 297 +++++++++++ backend/utils/__init__.py | 9 +- backend/utils/firegextables.py | 125 +++++ backend/{modules => utils}/sqlite.py | 30 -- .../src/components/Footer/index.module.scss | 1 - frontend/src/components/Footer/index.tsx | 9 +- .../src/components/Header/index.module.scss | 17 +- frontend/src/components/Header/index.tsx | 79 ++- frontend/src/components/MainLayout.tsx | 44 +- frontend/src/components/NFRegex/utils.ts | 101 ++++ frontend/src/components/NavBar/index.tsx | 49 ++ .../src/components/RegexProxy/AddNewRegex.tsx | 124 +++++ .../components/RegexProxy/AddNewService.tsx | 120 +++++ .../RegexView/RegexView.module.scss | 13 + .../components/RegexProxy/RegexView/index.tsx | 114 ++++ .../RegexProxy/ServiceRow/ChangePortModal.tsx | 107 ++++ .../RegexProxy/ServiceRow/RenameForm.tsx | 69 +++ .../ServiceRow/ServiceRow.module.scss | 18 + .../RegexProxy/ServiceRow/index.tsx | 234 +++++++++ frontend/src/components/RegexProxy/utils.ts | 116 ++++ frontend/src/js/models.ts | 115 ++-- frontend/src/js/utils.tsx | 61 +-- .../src/pages/NFRegex.tsx/ServiceDetails.tsx | 4 +- frontend/src/pages/NFRegex.tsx/index.tsx | 44 +- frontend/src/pages/RegexProxy/HomePage.tsx | 53 ++ .../src/pages/RegexProxy/ServiceDetails.tsx | 84 +++ start.py | 22 +- 42 files changed, 2832 insertions(+), 577 deletions(-) delete mode 100644 backend/modules/firegex.py create mode 100644 backend/modules/nfregex/__init__.py create mode 100644 backend/modules/nfregex/firegex.py rename backend/modules/{ => nfregex}/firewall.py (90%) create mode 100644 backend/modules/nfregex/models.py create mode 100755 backend/modules/proxy create mode 100644 backend/modules/regexproxy/__init__.py create mode 100755 backend/modules/regexproxy/proxy.py create mode 100644 backend/modules/regexproxy/utils.py create mode 100644 backend/nfqueue/proxy.cpp create mode 100644 backend/regextcpproxy.db create mode 100644 backend/routers/regexproxy.py create mode 100644 backend/utils/firegextables.py rename backend/{modules => utils}/sqlite.py (72%) create mode 100755 frontend/src/components/NFRegex/utils.ts create mode 100644 frontend/src/components/NavBar/index.tsx create mode 100755 frontend/src/components/RegexProxy/AddNewRegex.tsx create mode 100755 frontend/src/components/RegexProxy/AddNewService.tsx create mode 100755 frontend/src/components/RegexProxy/RegexView/RegexView.module.scss create mode 100755 frontend/src/components/RegexProxy/RegexView/index.tsx create mode 100755 frontend/src/components/RegexProxy/ServiceRow/ChangePortModal.tsx create mode 100644 frontend/src/components/RegexProxy/ServiceRow/RenameForm.tsx create mode 100755 frontend/src/components/RegexProxy/ServiceRow/ServiceRow.module.scss create mode 100755 frontend/src/components/RegexProxy/ServiceRow/index.tsx create mode 100644 frontend/src/components/RegexProxy/utils.ts mode change 100755 => 100644 frontend/src/js/models.ts create mode 100755 frontend/src/pages/RegexProxy/HomePage.tsx create mode 100755 frontend/src/pages/RegexProxy/ServiceDetails.tsx diff --git a/Dockerfile b/Dockerfile index 39bd53d..7916135 100755 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:alpine RUN apk update -RUN apk add g++ git pcre2-dev libnetfilter_queue-dev libpcap-dev libcrypto1.1 libnfnetlink-dev libmnl-dev make cmake nftables +RUN apk add g++ git pcre2-dev libnetfilter_queue-dev libpcap-dev libcrypto1.1 libnfnetlink-dev libmnl-dev make cmake nftables boost-dev WORKDIR /tmp/ RUN git clone --single-branch --branch release https://github.com/jpcre2/jpcre2 @@ -18,7 +18,9 @@ WORKDIR /execute COPY ./backend/nfqueue /execute/nfqueue +ARG GCC_PARAMS RUN g++ nfqueue/nfqueue.cpp -o modules/cppqueue -std=c++20 -O3 -march=native -lnetfilter_queue -pthread -lpcre2-8 -ltins -lmnl -lnfnetlink +RUN g++ -O3 -march=native $GCC_PARAMS -o modules/proxy nfqueue/proxy.cpp -pthread -lboost_system -lboost_thread -lpcre2-8 ADD ./backend/requirements.txt /execute/requirements.txt RUN pip3 install --no-cache-dir -r /execute/requirements.txt diff --git a/backend/app.py b/backend/app.py index 1b06af7..46f8ae6 100644 --- a/backend/app.py +++ b/backend/app.py @@ -6,8 +6,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import jwt from passlib.context import CryptContext from fastapi_socketio import SocketManager -from modules import SQLite -from modules.firegex import FiregexTables +from utils.sqlite import SQLite from utils import API_VERSION, FIREGEX_PORT, JWT_ALGORITHM, get_interfaces, refresh_frontend, DEBUG from utils.loader import frontend_deploy, load_routers from utils.models import ChangePasswordModel, IpInterface, PasswordChangeForm, PasswordForm, ResetRequest, StatusModel, StatusMessageModel diff --git a/backend/modules/__init__.py b/backend/modules/__init__.py index 14d33e5..e69de29 100644 --- a/backend/modules/__init__.py +++ b/backend/modules/__init__.py @@ -1,2 +0,0 @@ -from .firewall import FirewallManager -from .sqlite import SQLite \ No newline at end of file diff --git a/backend/modules/firegex.py b/backend/modules/firegex.py deleted file mode 100644 index ba53a7a..0000000 --- a/backend/modules/firegex.py +++ /dev/null @@ -1,276 +0,0 @@ -from typing import Dict, List, Set -from utils import ip_parse, ip_family, run_func -from modules.sqlite import Service -import re, os, asyncio -import traceback, nftables - -from modules.sqlite import Regex - -QUEUE_BASE_NUM = 1000 - -class FiregexFilter(): - def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None): - self.nftables = nftables.Nftables() - self.id = int(id) if id else None - self.queue = queue - self.target = target - self.proto = proto - self.port = int(port) - self.ip_int = str(ip_int) - - def __eq__(self, o: object) -> bool: - if isinstance(o, FiregexFilter): - return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) - return False - -class FiregexTables: - - def __init__(self): - self.table_name = "firegex" - self.nft = nftables.Nftables() - - def raw_cmd(self, *cmds): - return self.nft.json_cmd({"nftables": list(cmds)}) - - def cmd(self, *cmds): - code, out, err = self.raw_cmd(*cmds) - - if code == 0: return out - else: raise Exception(err) - - def init(self): - self.reset() - code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}}) - if code == 0: - self.cmd( - {"create":{"chain":{ - "family":"inet", - "table":self.table_name, - "name":"input", - "type":"filter", - "hook":"prerouting", - "prio":-150, - "policy":"accept" - }}}, - {"create":{"chain":{ - "family":"inet", - "table":self.table_name, - "name":"output", - "type":"filter", - "hook":"postrouting", - "prio":-150, - "policy":"accept" - }}} - ) - - - def reset(self): - self.raw_cmd( - {"flush":{"table":{"name":"firegex","family":"inet"}}}, - {"delete":{"table":{"name":"firegex","family":"inet"}}}, - ) - - def list(self): - return self.cmd({"list": {"ruleset": None}})["nftables"] - - def add_output(self, queue_range, proto, port, ip_int): - init, end = queue_range - if init > end: init, end = end, init - ip_int = ip_parse(ip_int) - ip_addr = str(ip_int).split("/")[0] - ip_addr_cidr = int(str(ip_int).split("/")[1]) - self.cmd({ "insert":{ "rule": { - "family": "inet", - "table": self.table_name, - "chain": "output", - "expr": [ - {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int - {'match': {'left': {'meta': {'key': 'l4proto'}}, 'op': '==', 'right': str(proto)}}, - {'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}}, - {"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}} - ] - }}}) - - def add_input(self, queue_range, proto = None, port = None, ip_int = None): - init, end = queue_range - if init > end: init, end = end, init - ip_int = ip_parse(ip_int) - ip_addr = str(ip_int).split("/")[0] - ip_addr_cidr = int(str(ip_int).split("/")[1]) - self.cmd({"insert":{"rule":{ - "family": "inet", - "table": self.table_name, - "chain": "input", - "expr": [ - {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, #ip_int - {'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}}, - {"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}} - ] - }}}) - - def get(self) -> List[FiregexFilter]: - res = [] - for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]: - queue_str = str(filter["expr"][2]["queue"]["num"]).split("-") - queue = None - if len(queue_str) == 1: queue = int(queue_str[0]), int(queue_str[0]) - else: queue = int(queue_str[0]), int(queue_str[1]) - ip_int = None - if isinstance(filter["expr"][0]["match"]["right"],str): - ip_int = str(ip_parse(filter["expr"][0]["match"]["right"])) - else: - ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}' - res.append(FiregexFilter( - target=filter["chain"], - id=int(filter["handle"]), - queue=queue, - proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"], - port=filter["expr"][1]["match"]["right"], - ip_int=ip_int - )) - return res - - async def add(self, filter:FiregexFilter): - if filter in self.get(): return None - return await FiregexInterceptor.start( filter=filter, n_queues=int(os.getenv("N_THREADS_NFQUEUE","1"))) - - def delete_by_srv(self, srv:Service): - for filter in self.get(): - if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int): - self.cmd({"delete":{"rule": {"handle": filter.id, "table": self.table_name, "chain": filter.target, "family": "inet"}}}) - - -class RegexFilter: - def __init__( - self, regex, - is_case_sensitive=True, - is_blacklist=True, - input_mode=False, - output_mode=False, - blocked_packets=0, - id=None, - update_func = None - ): - self.regex = regex - self.is_case_sensitive = is_case_sensitive - self.is_blacklist = is_blacklist - if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True) - self.input_mode = input_mode - self.output_mode = output_mode - self.blocked = blocked_packets - self.id = id - self.update_func = update_func - self.compiled_regex = self.compile() - - @classmethod - def from_regex(cls, regex:Regex, update_func = None): - return cls( - id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive, - is_blacklist=regex.is_blacklist, blocked_packets=regex.blocked_packets, - input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"], - update_func = update_func - ) - def compile(self): - if isinstance(self.regex, str): self.regex = self.regex.encode() - if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether") - re.compile(self.regex) # raise re.error if it's invalid! - case_sensitive = "1" if self.is_case_sensitive else "0" - if self.input_mode: - yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex() - if self.output_mode: - yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex() - - async def update(self): - if self.update_func: - await run_func(self.update_func, self) - -class FiregexInterceptor: - - def __init__(self): - self.filter:FiregexFilter - self.filter_map_lock:asyncio.Lock - self.filter_map: Dict[str, RegexFilter] - self.regex_filters: Set[RegexFilter] - self.update_config_lock:asyncio.Lock - self.process:asyncio.subprocess.Process - self.n_queues:int - self.update_task: asyncio.Task - - @classmethod - async def start(cls, filter: FiregexFilter, n_queues:int = 1): - self = cls() - self.filter = filter - self.n_queues = n_queues - self.filter_map_lock = asyncio.Lock() - self.update_config_lock = asyncio.Lock() - input_range, output_range = await self._start_binary() - self.update_task = asyncio.create_task(self.update_blocked()) - FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) - FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) - return self - - async def _start_binary(self): - proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"./cppqueue") - self.process = await asyncio.create_subprocess_exec( - proxy_binary_path, str(self.n_queues), - stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE - ) - line_fut = self.process.stdout.readuntil() - try: - line_fut = await asyncio.wait_for(line_fut, timeout=3) - except asyncio.TimeoutError: - self.process.kill() - raise Exception("Invalid binary output") - line = line_fut.decode() - if line.startswith("QUEUES "): - params = line.split() - return (int(params[2]), int(params[3])), (int(params[5]), int(params[6])) - else: - self.process.kill() - raise Exception("Invalid binary output") - - async def update_blocked(self): - try: - while True: - line = (await self.process.stdout.readuntil()).decode() - if line.startswith("BLOCKED"): - regex_id = line.split()[1] - async with self.filter_map_lock: - if regex_id in self.filter_map: - self.filter_map[regex_id].blocked+=1 - await self.filter_map[regex_id].update() - except asyncio.CancelledError: pass - except asyncio.IncompleteReadError: pass - except Exception: - traceback.print_exc() - - async def stop(self): - self.update_task.cancel() - if self.process and self.process.returncode is None: - self.process.kill() - - async def _update_config(self, filters_codes): - async with self.update_config_lock: - self.process.stdin.write((" ".join(filters_codes)+"\n").encode()) - await self.process.stdin.drain() - - async def reload(self, filters:List[RegexFilter]): - async with self.filter_map_lock: - self.filter_map = self.compile_filters(filters) - filters_codes = self.get_filter_codes() - await self._update_config(filters_codes) - - def get_filter_codes(self): - filters_codes = list(self.filter_map.keys()) - filters_codes.sort(key=lambda a: self.filter_map[a].blocked, reverse=True) - return filters_codes - - def compile_filters(self, filters:List[RegexFilter]): - res = {} - for filter_obj in filters: - try: - raw_filters = filter_obj.compile() - for filter in raw_filters: - res[filter] = filter_obj - except Exception: pass - return res \ No newline at end of file diff --git a/backend/modules/nfregex/__init__.py b/backend/modules/nfregex/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/modules/nfregex/firegex.py b/backend/modules/nfregex/firegex.py new file mode 100644 index 0000000..445c404 --- /dev/null +++ b/backend/modules/nfregex/firegex.py @@ -0,0 +1,151 @@ +from typing import Dict, List, Set +from utils.firegextables import FiregexFilter, FiregexTables +from utils import ip_parse, ip_family, run_func +from modules.nfregex.models import Service, Regex +import re, os, asyncio +import traceback + +QUEUE_BASE_NUM = 1000 + + +class RegexFilter: + def __init__( + self, regex, + is_case_sensitive=True, + is_blacklist=True, + input_mode=False, + output_mode=False, + blocked_packets=0, + id=None, + update_func = None + ): + self.regex = regex + self.is_case_sensitive = is_case_sensitive + self.is_blacklist = is_blacklist + if input_mode == output_mode: input_mode = output_mode = True # (False, False) == (True, True) + self.input_mode = input_mode + self.output_mode = output_mode + self.blocked = blocked_packets + self.id = id + self.update_func = update_func + self.compiled_regex = self.compile() + + @classmethod + def from_regex(cls, regex:Regex, update_func = None): + return cls( + id=regex.id, regex=regex.regex, is_case_sensitive=regex.is_case_sensitive, + is_blacklist=regex.is_blacklist, blocked_packets=regex.blocked_packets, + input_mode = regex.mode in ["C","B"], output_mode=regex.mode in ["S","B"], + update_func = update_func + ) + def compile(self): + if isinstance(self.regex, str): self.regex = self.regex.encode() + if not isinstance(self.regex, bytes): raise Exception("Invalid Regex Paramether") + re.compile(self.regex) # raise re.error if it's invalid! + case_sensitive = "1" if self.is_case_sensitive else "0" + if self.input_mode: + yield case_sensitive + "C" + self.regex.hex() if self.is_blacklist else case_sensitive + "c"+ self.regex.hex() + if self.output_mode: + yield case_sensitive + "S" + self.regex.hex() if self.is_blacklist else case_sensitive + "s"+ self.regex.hex() + + async def update(self): + if self.update_func: + await run_func(self.update_func, self) + +class FiregexInterceptor: + + def __init__(self): + self.filter:FiregexFilter + self.filter_map_lock:asyncio.Lock + self.filter_map: Dict[str, RegexFilter] + self.regex_filters: Set[RegexFilter] + self.update_config_lock:asyncio.Lock + self.process:asyncio.subprocess.Process + self.n_queues:int + self.update_task: asyncio.Task + + @classmethod + async def start(cls, filter: FiregexFilter, n_queues:int = int(os.getenv("NTHREADS","1"))): + self = cls() + self.filter = filter + self.n_queues = n_queues + self.filter_map_lock = asyncio.Lock() + self.update_config_lock = asyncio.Lock() + input_range, output_range = await self._start_binary() + self.update_task = asyncio.create_task(self.update_blocked()) + if not filter in FiregexTables().get(): + FiregexTables().add_input(queue_range=input_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) + FiregexTables().add_output(queue_range=output_range, proto=self.filter.proto, port=self.filter.port, ip_int=self.filter.ip_int) + return self + + async def _start_binary(self): + proxy_binary_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),"../cppqueue") + self.process = await asyncio.create_subprocess_exec( + proxy_binary_path, str(self.n_queues), + stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE + ) + line_fut = self.process.stdout.readuntil() + try: + line_fut = await asyncio.wait_for(line_fut, timeout=3) + except asyncio.TimeoutError: + self.process.kill() + raise Exception("Invalid binary output") + line = line_fut.decode() + if line.startswith("QUEUES "): + params = line.split() + return (int(params[2]), int(params[3])), (int(params[5]), int(params[6])) + else: + self.process.kill() + raise Exception("Invalid binary output") + + async def update_blocked(self): + try: + while True: + line = (await self.process.stdout.readuntil()).decode() + if line.startswith("BLOCKED"): + regex_id = line.split()[1] + async with self.filter_map_lock: + if regex_id in self.filter_map: + self.filter_map[regex_id].blocked+=1 + await self.filter_map[regex_id].update() + except asyncio.CancelledError: pass + except asyncio.IncompleteReadError: pass + except Exception: + traceback.print_exc() + + async def stop(self): + self.update_task.cancel() + if self.process and self.process.returncode is None: + self.process.kill() + + async def _update_config(self, filters_codes): + async with self.update_config_lock: + self.process.stdin.write((" ".join(filters_codes)+"\n").encode()) + await self.process.stdin.drain() + + async def reload(self, filters:List[RegexFilter]): + async with self.filter_map_lock: + self.filter_map = self.compile_filters(filters) + filters_codes = self.get_filter_codes() + await self._update_config(filters_codes) + + def get_filter_codes(self): + filters_codes = list(self.filter_map.keys()) + filters_codes.sort(key=lambda a: self.filter_map[a].blocked, reverse=True) + return filters_codes + + def compile_filters(self, filters:List[RegexFilter]): + res = {} + for filter_obj in filters: + try: + raw_filters = filter_obj.compile() + for filter in raw_filters: + res[filter] = filter_obj + except Exception: pass + return res + +def delete_by_srv(srv:Service): + nft = FiregexTables() + for filter in nft.get(): + if filter.port == srv.port and filter.proto == srv.proto and ip_parse(filter.ip_int) == ip_parse(srv.ip_int): + nft.cmd({"delete":{"rule": {"handle": filter.id, "table": nft.table_name, "chain": filter.target, "family": "inet"}}}) \ No newline at end of file diff --git a/backend/modules/firewall.py b/backend/modules/nfregex/firewall.py similarity index 90% rename from backend/modules/firewall.py rename to backend/modules/nfregex/firewall.py index 213e42b..03ba62b 100644 --- a/backend/modules/firewall.py +++ b/backend/modules/nfregex/firewall.py @@ -1,7 +1,8 @@ import asyncio from typing import Dict -from modules.firegex import FiregexFilter, FiregexTables, RegexFilter -from modules.sqlite import Regex, SQLite, Service +from modules.nfregex.firegex import FiregexFilter, FiregexInterceptor, FiregexTables, RegexFilter, delete_by_srv +from modules.nfregex.models import Regex, Service +from utils.sqlite import SQLite class STATUS: STOP = "stop" @@ -93,13 +94,13 @@ class ServiceManager: async def start(self): if not self.interceptor: - FiregexTables().delete_by_srv(self.srv) - self.interceptor = await FiregexTables().add(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) + delete_by_srv(self.srv) + self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int)) await self._update_filters_from_db() self._set_status(STATUS.ACTIVE) async def stop(self): - FiregexTables().delete_by_srv(self.srv) + delete_by_srv(self.srv) if self.interceptor: await self.interceptor.stop() self.interceptor = None diff --git a/backend/modules/nfregex/models.py b/backend/modules/nfregex/models.py new file mode 100644 index 0000000..d365412 --- /dev/null +++ b/backend/modules/nfregex/models.py @@ -0,0 +1,30 @@ +import base64 + +class Service: + def __init__(self, id: str, status: str, port: int, name: str, proto: str, ip_int: str): + self.id = id + self.status = status + self.port = port + self.name = name + self.proto = proto + self.ip_int = ip_int + + @classmethod + def from_dict(cls, var: dict): + return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"]) + + +class Regex: + def __init__(self, id: int, regex: bytes, mode: str, service_id: str, is_blacklist: bool, blocked_packets: int, is_case_sensitive: bool, active: bool): + self.regex = regex + self.mode = mode + self.service_id = service_id + self.is_blacklist = is_blacklist + self.blocked_packets = blocked_packets + self.id = id + self.is_case_sensitive = is_case_sensitive + self.active = active + + @classmethod + def from_dict(cls, var: dict): + return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"]) \ No newline at end of file diff --git a/backend/modules/proxy b/backend/modules/proxy new file mode 100755 index 0000000000000000000000000000000000000000..64b5f592bdbfa12a8916a811daaee6b0b4c2c0c8 GIT binary patch literal 360472 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 PAUSE or PAUSE -> ACTIVE + if (self.status, to) in [(STATUS.ACTIVE, STATUS.PAUSE)]: + await self.proxy.pause() + self._set_status(to) + + elif (self.status, to) in [(STATUS.PAUSE, STATUS.ACTIVE)]: + await self.proxy.reload() + self._set_status(to) + + # ACTIVE -> STOP + elif (self.status,to) in [(STATUS.ACTIVE, STATUS.STOP), (STATUS.WAIT, STATUS.STOP), (STATUS.PAUSE, STATUS.STOP)]: #Stop proxy + if self.starter: self.starter.cancel() + await self.proxy.stop() + self._set_status(to) + + # STOP -> ACTIVE or STOP -> PAUSE + elif (self.status, to) in [(STATUS.STOP, STATUS.ACTIVE), (STATUS.STOP, STATUS.PAUSE)]: + self.wanted_status = to + self._set_status(STATUS.WAIT) + self.__proxy_starter(to) + + + def _stats_updater(self,filter:Filter): + self.db.query("UPDATE regexes SET blocked_packets = ? WHERE regex_id = ?;", filter.blocked, filter.code) + + async def update_port(self): + async with self.lock: + self._update_port_from_db() + if self.status in [STATUS.PAUSE, STATUS.ACTIVE]: + next_status = self.status if self.status != STATUS.WAIT else self.wanted_status + await self._next(STATUS.STOP) + await self._next(next_status) + + def _set_status(self,status): + self.status = status + self.__update_status_db(status) + + + async def update_filters(self): + async with self.lock: + self._update_filters_from_db() + if self.status in [STATUS.PAUSE, STATUS.ACTIVE]: + await self.proxy.reload() + + def __proxy_starter(self,to): + async def func(): + try: + while True: + if check_port_is_open(self.proxy.public_port): + self._set_status(to) + await self.proxy.start(in_pause=(to==STATUS.PAUSE)) + self._set_status(STATUS.STOP) + return + else: + await asyncio.sleep(.5) + except asyncio.CancelledError: + self._set_status(STATUS.STOP) + await self.proxy.stop() + self.starter = asyncio.create_task(func()) + +class ProxyManager: + def __init__(self, db:SQLite): + self.db = db + self.proxy_table:dict = {} + self.lock = asyncio.Lock() + + async def close(self): + for key in list(self.proxy_table.keys()): + await self.remove(key) + + async def remove(self,id): + async with self.lock: + if id in self.proxy_table: + await self.proxy_table[id].next(STATUS.STOP) + del self.proxy_table[id] + + async def reload(self): + async with self.lock: + for srv in self.db.query('SELECT service_id, status FROM services;'): + srv_id, req_status = srv["service_id"], srv["status"] + if srv_id in self.proxy_table: + continue + + self.proxy_table[srv_id] = ServiceManager(srv_id,self.db) + await self.proxy_table[srv_id].next(req_status) + + def get(self,id): + if id in self.proxy_table: + return self.proxy_table[id] + else: + raise ServiceNotFoundException() + +def check_port_is_open(port): + try: + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0',port)) + sock.close() + return True + except Exception: + return False + +def gen_service_id(db): + while True: + res = secrets.token_hex(8) + if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0: + break + return res + +def gen_internal_port(db): + while True: + res = random.randint(30000, 45000) + if len(db.query('SELECT 1 FROM services WHERE internal_port = ?;', res)) == 0: + break + return res \ No newline at end of file diff --git a/backend/nfqueue/proxy.cpp b/backend/nfqueue/proxy.cpp new file mode 100644 index 0000000..4a6cac9 --- /dev/null +++ b/backend/nfqueue/proxy.cpp @@ -0,0 +1,497 @@ +/* + Copyright (c) 2007 Arash Partow (http://www.partow.net) + URL: http://www.partow.net/programming/tcpproxy/index.html + Modified and adapted by Pwnzer0tt1 +*/ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +typedef jpcre2::select jp; +using namespace std; + +bool unhexlify(string const &hex, string &newString) { + try{ + int len = hex.length(); + for(int i=0; i< len; i+=2) + { + std::string byte = hex.substr(i,2); + char chr = (char) (int)strtol(byte.c_str(), NULL, 16); + newString.push_back(chr); + } + return true; + } + catch (...){ + return false; + } +} + +typedef pair regex_rule_pair; +typedef vector regex_rule_vector; +struct regex_rules{ + regex_rule_vector regex_s_c_w, regex_c_s_w, regex_s_c_b, regex_c_s_b; + + regex_rule_vector* getByCode(char code){ + switch(code){ + case 'C': // Client to server Blacklist + return ®ex_c_s_b; break; + case 'c': // Client to server Whitelist + return ®ex_c_s_w; break; + case 'S': // Server to client Blacklist + return ®ex_s_c_b; break; + case 's': // Server to client Whitelist + return ®ex_s_c_w; break; + } + throw invalid_argument( "Expected 'C' 'c' 'S' or 's'" ); + } + + void add(const char* arg){ + + //Integrity checks + size_t arg_len = strlen(arg); + if (arg_len < 2 || arg_len%2 != 0) return; + if (arg[0] != '0' && arg[0] != '1') return; + if (arg[1] != 'C' && arg[1] != 'c' && arg[1] != 'S' && arg[1] != 's') return; + string hex(arg+2), expr; + if (!unhexlify(hex, expr)) return; + //Push regex + jp::Regex regex(expr,arg[0] == '1'?"gS":"giS"); + if (regex){ + #ifdef DEBUG + cerr << "Added regex " << expr << " " << arg << endl; + #endif + getByCode(arg[1])->push_back(make_pair(string(arg), regex)); + } else { + cerr << "Regex " << arg << " was not compiled successfully" << endl; + } + } + +}; +shared_ptr regex_config; + +mutex update_mutex; +#ifdef MULTI_THREAD +mutex stdout_mutex; +#endif + +bool filter_data(unsigned char* data, const size_t& bytes_transferred, regex_rule_vector const &blacklist, regex_rule_vector const &whitelist){ + #ifdef DEBUG_PACKET + cerr << "---------------- Packet ----------------" << endl; + for(int i=0;i lck(stdout_mutex); + #endif + cout << "BLOCKED " << ele.first << endl; + return false; + } + } catch(...){ + cerr << "Error while matching regex: " << ele.first << endl; + } + } + for (regex_rule_pair ele:whitelist){ + try{ + if(!ele.second.match(str_data)){ + #ifdef MULTI_THREAD + std::unique_lock lck(stdout_mutex); + #endif + cout << "BLOCKED " << ele.first << endl; + return false; + } + } catch(...){ + cerr << "Error while matching regex: " << ele.first << endl; + } + } + #ifdef DEBUG + cerr << "Packet Accepted!" << endl; + #endif + return true; +} + +namespace tcp_proxy +{ + namespace ip = boost::asio::ip; + + class bridge : public boost::enable_shared_from_this + { + public: + + typedef ip::tcp::socket socket_type; + typedef boost::shared_ptr ptr_type; + + bridge(boost::asio::io_context& ios) + : downstream_socket_(ios), + upstream_socket_ (ios), + thread_safety(ios) + {} + + socket_type& downstream_socket() + { + // Client socket + return downstream_socket_; + } + + socket_type& upstream_socket() + { + // Remote server socket + return upstream_socket_; + } + + void start(const string& upstream_host, unsigned short upstream_port) + { + // Attempt connection to remote server (upstream side) + upstream_socket_.async_connect( + ip::tcp::endpoint( + boost::asio::ip::address::from_string(upstream_host), + upstream_port), + boost::asio::bind_executor(thread_safety, + boost::bind( + &bridge::handle_upstream_connect, + shared_from_this(), + boost::asio::placeholders::error))); + } + + void handle_upstream_connect(const boost::system::error_code& error) + { + if (!error) + { + // Setup async read from remote server (upstream) + + upstream_socket_.async_read_some( + boost::asio::buffer(upstream_data_,max_data_length), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_upstream_read, + shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + + // Setup async read from client (downstream) + downstream_socket_.async_read_some( + boost::asio::buffer(downstream_data_,max_data_length), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_downstream_read, + shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + close(); + } + + private: + + /* + Section A: Remote Server --> Proxy --> Client + Process data recieved from remote sever then send to client. + */ + + // Read from remote server complete, now send data to client + void handle_upstream_read(const boost::system::error_code& error, + const size_t& bytes_transferred) // Da Server a Client + { + if (!error) + { + shared_ptr regex_old_config = regex_config; + if (filter_data(upstream_data_, bytes_transferred, regex_old_config->regex_s_c_b, regex_old_config->regex_s_c_w)){ + async_write(downstream_socket_, + boost::asio::buffer(upstream_data_,bytes_transferred), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_downstream_write, + shared_from_this(), + boost::asio::placeholders::error))); + }else{ + close(); + } + } + else + close(); + } + + // Write to client complete, Async read from remote server + void handle_downstream_write(const boost::system::error_code& error) + { + if (!error) + { + + upstream_socket_.async_read_some( + boost::asio::buffer(upstream_data_,max_data_length), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_upstream_read, + shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + close(); + } + // *** End Of Section A *** + + + /* + Section B: Client --> Proxy --> Remove Server + Process data recieved from client then write to remove server. + */ + + // Read from client complete, now send data to remote server + void handle_downstream_read(const boost::system::error_code& error, + const size_t& bytes_transferred) // Da Client a Server + { + if (!error) + { + shared_ptr regex_old_config = regex_config; + if (filter_data(downstream_data_, bytes_transferred, regex_old_config->regex_c_s_b, regex_old_config->regex_c_s_w)){ + async_write(upstream_socket_, + boost::asio::buffer(downstream_data_,bytes_transferred), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_upstream_write, + shared_from_this(), + boost::asio::placeholders::error))); + }else{ + close(); + } + } + else + close(); + } + + // Write to remote server complete, Async read from client + void handle_upstream_write(const boost::system::error_code& error) + { + if (!error) + { + downstream_socket_.async_read_some( + boost::asio::buffer(downstream_data_,max_data_length), + boost::asio::bind_executor(thread_safety, + boost::bind(&bridge::handle_downstream_read, + shared_from_this(), + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + close(); + } + // *** End Of Section B *** + + void close() + { + boost::mutex::scoped_lock lock(mutex_); + + if (downstream_socket_.is_open()) + { + downstream_socket_.close(); + } + + if (upstream_socket_.is_open()) + { + upstream_socket_.close(); + } + } + + socket_type downstream_socket_; + socket_type upstream_socket_; + + enum { max_data_length = 8192 }; //8KB + unsigned char downstream_data_[max_data_length]; + unsigned char upstream_data_ [max_data_length]; + boost::asio::io_context::strand thread_safety; + boost::mutex mutex_; + public: + + class acceptor + { + public: + + acceptor(boost::asio::io_context& io_context, + const string& local_host, unsigned short local_port, + const string& upstream_host, unsigned short upstream_port) + : io_context_(io_context), + localhost_address(boost::asio::ip::address_v4::from_string(local_host)), + acceptor_(io_context_,ip::tcp::endpoint(localhost_address,local_port)), + upstream_port_(upstream_port), + upstream_host_(upstream_host) + {} + + bool accept_connections() + { + try + { + session_ = boost::shared_ptr(new bridge(io_context_)); + + acceptor_.async_accept(session_->downstream_socket(), + boost::asio::bind_executor(session_->thread_safety, + boost::bind(&acceptor::handle_accept, + this, + boost::asio::placeholders::error))); + } + catch(exception& e) + { + cerr << "acceptor exception: " << e.what() << endl; + return false; + } + + return true; + } + + private: + + void handle_accept(const boost::system::error_code& error) + { + if (!error) + { + session_->start(upstream_host_,upstream_port_); + + if (!accept_connections()) + { + cerr << "Failure during call to accept." << endl; + } + } + else + { + cerr << "Error: " << error.message() << endl; + } + } + + boost::asio::io_context& io_context_; + ip::address_v4 localhost_address; + ip::tcp::acceptor acceptor_; + ptr_type session_; + unsigned short upstream_port_; + string upstream_host_; + }; + + }; +} + +void update_config (boost::asio::streambuf &input_buffer){ + #ifdef DEBUG + cerr << "Updating configuration" << endl; + #endif + std::istream config_stream(&input_buffer); + std::unique_lock lck(update_mutex); + regex_rules *regex_new_config = new regex_rules(); + string data; + while(true){ + config_stream >> data; + if (config_stream.eof()) break; + regex_new_config->add(data.c_str()); + } + regex_config.reset(regex_new_config); +} + +class async_updater +{ +public: + async_updater(boost::asio::io_context& io_context) : input_(io_context, ::dup(STDIN_FILENO)), thread_safety(io_context) + { + + boost::asio::async_read_until(input_, input_buffer_, '\n', + boost::asio::bind_executor(thread_safety, + boost::bind(&async_updater::on_update, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + + void on_update(const boost::system::error_code& error, std::size_t length) + { + if (!error) + { + update_config(input_buffer_); + boost::asio::async_read_until(input_, input_buffer_, '\n', + boost::asio::bind_executor(thread_safety, + boost::bind(&async_updater::on_update, this, + boost::asio::placeholders::error, + boost::asio::placeholders::bytes_transferred))); + } + else + { + close(); + } + } + + void close() + { + input_.close(); + } + +private: + boost::asio::posix::stream_descriptor input_; + boost::asio::io_context::strand thread_safety; + boost::asio::streambuf input_buffer_; +}; + + +int main(int argc, char* argv[]) +{ + if (argc < 5) + { + cerr << "usage: tcpproxy_server " << endl; + return 1; + } + + const unsigned short local_port = static_cast(::atoi(argv[2])); + const unsigned short forward_port = static_cast(::atoi(argv[4])); + const string local_host = argv[1]; + const string forward_host = argv[3]; + + boost::asio::io_context ios; + + boost::asio::streambuf buf; + boost::asio::posix::stream_descriptor cin_in(ios, ::dup(STDIN_FILENO)); + boost::asio::read_until(cin_in, buf,'\n'); + update_config(buf); + + async_updater updater(ios); + + #ifdef DEBUG + cerr << "Starting Proxy" << endl; + #endif + try + { + tcp_proxy::bridge::acceptor acceptor(ios, + local_host, local_port, + forward_host, forward_port); + + acceptor.accept_connections(); + #ifdef MULTI_THREAD + boost::thread_group tg; + #ifdef THREAD_NUM + for (unsigned i = 0; i < THREAD_NUM; ++i) + #else + for (unsigned i = 0; i < thread::hardware_concurrency(); ++i) + #endif + tg.create_thread(boost::bind(&boost::asio::io_context::run, &ios)); + + tg.join_all(); + #else + ios.run(); + #endif + } + catch(exception& e) + { + cerr << "Error: " << e.what() << endl; + return 1; + } + #ifdef DEBUG + cerr << "Proxy stopped!" << endl; + #endif + + return 0; +} diff --git a/backend/regextcpproxy.db b/backend/regextcpproxy.db new file mode 100644 index 0000000000000000000000000000000000000000..bfe8b99628092e28222dd911d4734bfa58b44648 GIT binary patch literal 32768 zcmeI&Z%@-e90%~0u?;12#wRl&BtOEFEvZ78Lx_LJcEG~uq}vF@2TivgreSQbb_+g4 zO?*4P1z&QUb-#G zvhf3H7k84$OkqyGFzv3z&j(%mY`~2^f5p!YhxdneoBKQ5nJMY4*kIJu zDtk>v6|Kw;=y&C*rqQSlW&MmYYRRxI#deHV$7~;WY{ykjdKUMNv`vSL%XJ;wwTC?S z_GKcOnVXZ}r(Cnu2|6%pGdb!ZR?Xo>g<9 zGvf}8&o3{BON9GL8?{=M6}8I-sI<*WJCuzSs%Vs5R!Gg|6l!&P?PG2kr(&esb%a)f zZ82To)hk;XE00?$v&~|&+8|NWAD}S0Ed4^S>=yMy+F^$Z{lauzucDx$!Ex8jwrBiT zl_}_~s?cUlXJRN`-zYog%#qGEna;EltJCPP6Dg2;^B|VY%+Jg39{JG?3I*HgNPL5% zS;sZqfpb-~M#Vh5%XoY7j0$#L-tU?n+o|{CH;VNP(xcOlH@j=+VctS}5dK zMT}1et&ZLPCykA6Duue{NodA~XJl?Ep2(EuWy!x!owJU3<~7WL+w=E>d(|;k0 z^dD7Fg9`DXoXjjP$`^S*F30?}V+_sCfIFe=R3ti~6IqeJ#@=rzdOo;MbA_oyrm}d| z$U@gJ)X+{${LuQ(_9>B()1M@ z>GeuYvn;jHZaq9&Zx;%DZN2H!)kRSj72tWV=5P$## zAOHafKmY;|m>7XnY~jZE{{ip)zxe1M2?7v+00bZa0SG_<0uX=z1Rwx`2^R3)|Ks_8 zf&+}9K>z{}fB*y_009U<00Izz00e{pp8qioAOHafKmY;|fB*y_009U<00NUQfbah& zKgJj#1Rwwb2tWV=5P$##AOHafKmhOmF$W+30SG_<0uX=z1Rwwb2tWV=lP~ZC?{lO3 literal 0 HcmV?d00001 diff --git a/backend/routers/nfregex.py b/backend/routers/nfregex.py index 9780ce5..48c6e5e 100644 --- a/backend/routers/nfregex.py +++ b/backend/routers/nfregex.py @@ -1,13 +1,14 @@ from base64 import b64decode import re +import secrets import sqlite3 from typing import List, Union from fastapi import APIRouter, HTTPException from pydantic import BaseModel -from modules.firegex import FiregexTables -from modules.firewall import STATUS, FirewallManager -from modules.sqlite import SQLite -from utils import gen_service_id, ip_parse, refactor_name, refresh_frontend +from modules.nfregex.firegex import FiregexTables +from modules.nfregex.firewall import STATUS, FirewallManager +from utils.sqlite import SQLite +from utils import ip_parse, refactor_name, refresh_frontend from utils.models import ResetRequest, StatusMessageModel class GeneralStatModel(BaseModel): @@ -58,29 +59,6 @@ class ServiceAddResponse(BaseModel): app = APIRouter() -async def reset(params: ResetRequest): - if not params.delete: - db.backup() - await firewall.close() - FiregexTables().reset() - if params.delete: - db.delete() - db.init() - else: - db.restore() - await firewall.init() - - -async def startup(): - db.init() - await firewall.init() - -async def shutdown(): - db.backup() - await firewall.close() - db.disconnect() - db.restore() - db = SQLite('db/nft-regex.db', { 'services': { 'service_id': 'VARCHAR(100) PRIMARY KEY', @@ -107,6 +85,36 @@ db = SQLite('db/nft-regex.db', { ] }) +async def reset(params: ResetRequest): + if not params.delete: + db.backup() + await firewall.close() + FiregexTables().reset() + if params.delete: + db.delete() + db.init() + else: + db.restore() + await firewall.init() + + +async def startup(): + db.init() + await firewall.init() + +async def shutdown(): + db.backup() + await firewall.close() + db.disconnect() + db.restore() + +def gen_service_id(): + while True: + res = secrets.token_hex(8) + if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0: + break + return res + firewall = FirewallManager(db) @app.get('/stats', response_model=GeneralStatModel) @@ -271,7 +279,7 @@ async def add_new_service(form: ServiceAddForm, ): return {"status":"Invalid protocol"} srv_id = None try: - srv_id = gen_service_id(db) + srv_id = gen_service_id() db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)", srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int) except sqlite3.IntegrityError: diff --git a/backend/routers/regexproxy.py b/backend/routers/regexproxy.py new file mode 100644 index 0000000..e5d6fb8 --- /dev/null +++ b/backend/routers/regexproxy.py @@ -0,0 +1,297 @@ +from base64 import b64decode +import sqlite3, re +from typing import List, Union +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from modules.regexproxy.utils import STATUS, ProxyManager, gen_internal_port, gen_service_id +from utils.sqlite import SQLite +from utils.models import ResetRequest, StatusMessageModel +from utils.firegextables import FiregexTables + +app = APIRouter() +db = SQLite("regextcpproxy.db",{ + 'services': { + 'status': 'VARCHAR(100) NOT NULL', + 'service_id': 'VARCHAR(100) PRIMARY KEY', + 'internal_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536)', + 'public_port': 'INT NOT NULL CHECK(internal_port > 0 and internal_port < 65536) UNIQUE', + 'name': 'VARCHAR(100) NOT NULL' + }, + 'regexes': { + 'regex': 'TEXT NOT NULL', + 'mode': 'VARCHAR(1) NOT NULL', + 'service_id': 'VARCHAR(100) NOT NULL', + 'is_blacklist': 'BOOLEAN NOT NULL CHECK (is_blacklist IN (0, 1))', + 'blocked_packets': 'INTEGER UNSIGNED NOT NULL DEFAULT 0', + 'regex_id': 'INTEGER PRIMARY KEY', + 'is_case_sensitive' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1))', + 'active' : 'BOOLEAN NOT NULL CHECK (is_case_sensitive IN (0, 1)) DEFAULT 1', + 'FOREIGN KEY (service_id)':'REFERENCES services (service_id)', + }, + 'QUERY':[ + "CREATE UNIQUE INDEX IF NOT EXISTS unique_regex_service ON regexes (regex,service_id,is_blacklist,mode,is_case_sensitive);" + ] + }) + +firewall = ProxyManager(db) + +async def reset(params: ResetRequest): + if not params.delete: + db.backup() + await firewall.close() + FiregexTables().reset() + if params.delete: + db.delete() + db.init() + else: + db.restore() + await firewall.reload() + + +async def startup(): + db.init() + await firewall.reload() + +async def shutdown(): + db.backup() + await firewall.close() + db.disconnect() + db.restore() + + + +class GeneralStatModel(BaseModel): + closed:int + regexes: int + services: int + +@app.get('/stats', response_model=GeneralStatModel) +async def get_general_stats(): + """Get firegex general status about services""" + return db.query(""" + SELECT + (SELECT COALESCE(SUM(blocked_packets),0) FROM regexes) closed, + (SELECT COUNT(*) FROM regexes) regexes, + (SELECT COUNT(*) FROM services) services + """)[0] + +class ServiceModel(BaseModel): + id:str + status: str + public_port: int + internal_port: int + name: str + n_regex: int + n_packets: int + +@app.get('/services', response_model=List[ServiceModel]) +async def get_service_list(): + """Get the list of existent firegex services""" + return db.query(""" + SELECT + s.service_id `id`, + s.status status, + s.public_port public_port, + s.internal_port internal_port, + s.name name, + COUNT(r.regex_id) n_regex, + COALESCE(SUM(r.blocked_packets),0) n_packets + FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id + GROUP BY s.service_id; + """) + +@app.get('/service/{service_id}', response_model=ServiceModel) +async def get_service_by_id(service_id: str, ): + """Get info about a specific service using his id""" + res = db.query(""" + SELECT + s.service_id `id`, + s.status status, + s.public_port public_port, + s.internal_port internal_port, + s.name name, + COUNT(r.regex_id) n_regex, + COALESCE(SUM(r.blocked_packets),0) n_packets + FROM services s LEFT JOIN regexes r ON r.service_id = s.service_id WHERE s.service_id = ? + GROUP BY s.service_id; + """, service_id) + if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!") + return res[0] + +@app.get('/service/{service_id}/stop', response_model=StatusMessageModel) +async def service_stop(service_id: str, ): + """Request the stop of a specific service""" + await firewall.get(service_id).next(STATUS.STOP) + return {'status': 'ok'} + +@app.get('/service/{service_id}/pause', response_model=StatusMessageModel) +async def service_pause(service_id: str, ): + """Request the pause of a specific service""" + await firewall.get(service_id).next(STATUS.PAUSE) + return {'status': 'ok'} + +@app.get('/service/{service_id}/start', response_model=StatusMessageModel) +async def service_start(service_id: str, ): + """Request the start of a specific service""" + await firewall.get(service_id).next(STATUS.ACTIVE) + return {'status': 'ok'} + +@app.get('/service/{service_id}/delete', response_model=StatusMessageModel) +async def service_delete(service_id: str, ): + """Request the deletion of a specific service""" + db.query('DELETE FROM services WHERE service_id = ?;', service_id) + db.query('DELETE FROM regexes WHERE service_id = ?;', service_id) + await firewall.remove(service_id) + return {'status': 'ok'} + + +@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel) +async def regen_service_port(service_id: str, ): + """Request the regeneration of a the internal proxy port of a specific service""" + db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id) + await firewall.get(service_id).update_port() + return {'status': 'ok'} + +class ChangePortForm(BaseModel): + port: Union[int, None] + internalPort: Union[int, None] + +@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel) +async def change_service_ports(service_id: str, change_port:ChangePortForm ): + """Choose and change the ports of the service""" + if change_port.port is None and change_port.internalPort is None: + return {'status': 'Invalid Request!'} + try: + sql_inj = "" + query:List[Union[str,int]] = [] + if not change_port.port is None: + sql_inj+=" public_port = ? " + query.append(change_port.port) + if not change_port.port is None and not change_port.internalPort is None: + sql_inj += "," + if not change_port.internalPort is None: + sql_inj+=" internal_port = ? " + query.append(change_port.internalPort) + query.append(service_id) + db.query(f'UPDATE services SET {sql_inj} WHERE service_id = ?;', *query) + except sqlite3.IntegrityError: + return {'status': 'Name or/and port of the service has been already assigned to another service'} + await firewall.get(service_id).update_port() + return {'status': 'ok'} + +class RegexModel(BaseModel): + regex:str + mode:str + id:int + service_id:str + is_blacklist: bool + n_packets:int + is_case_sensitive:bool + active:bool + +@app.get('/service/{service_id}/regexes', response_model=List[RegexModel]) +async def get_service_regexe_list(service_id: str, ): + """Get the list of the regexes of a service""" + return db.query(""" + SELECT + regex, mode, regex_id `id`, service_id, is_blacklist, + blocked_packets n_packets, is_case_sensitive, active + FROM regexes WHERE service_id = ?; + """, service_id) + +@app.get('/regex/{regex_id}', response_model=RegexModel) +async def get_regex_by_id(regex_id: int, ): + """Get regex info using his id""" + res = db.query(""" + SELECT + regex, mode, regex_id `id`, service_id, is_blacklist, + blocked_packets n_packets, is_case_sensitive, active + FROM regexes WHERE `id` = ?; + """, regex_id) + if len(res) == 0: raise HTTPException(status_code=400, detail="This regex does not exists!") + return res[0] + +@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel) +async def regex_delete(regex_id: int, ): + """Delete a regex using his id""" + res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) + if len(res) != 0: + db.query('DELETE FROM regexes WHERE regex_id = ?;', regex_id) + await firewall.get(res[0]["service_id"]).update_filters() + + return {'status': 'ok'} + +@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel) +async def regex_enable(regex_id: int, ): + """Request the enabling of a regex""" + res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) + if len(res) != 0: + db.query('UPDATE regexes SET active=1 WHERE regex_id = ?;', regex_id) + await firewall.get(res[0]["service_id"]).update_filters() + return {'status': 'ok'} + +@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel) +async def regex_disable(regex_id: int, ): + """Request the deactivation of a regex""" + res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id) + if len(res) != 0: + db.query('UPDATE regexes SET active=0 WHERE regex_id = ?;', regex_id) + await firewall.get(res[0]["service_id"]).update_filters() + return {'status': 'ok'} + +class RegexAddForm(BaseModel): + service_id: str + regex: str + mode: str + active: Union[bool,None] + is_blacklist: bool + is_case_sensitive: bool + +@app.post('/regexes/add', response_model=StatusMessageModel) +async def add_new_regex(form: RegexAddForm, ): + """Add a new regex""" + try: + re.compile(b64decode(form.regex)) + except Exception: + return {"status":"Invalid regex"} + try: + db.query("INSERT INTO regexes (service_id, regex, is_blacklist, mode, is_case_sensitive, active ) VALUES (?, ?, ?, ?, ?, ?);", + form.service_id, form.regex, form.is_blacklist, form.mode, form.is_case_sensitive, True if form.active is None else form.active ) + except sqlite3.IntegrityError: + return {'status': 'An identical regex already exists'} + + await firewall.get(form.service_id).update_filters() + return {'status': 'ok'} + +class ServiceAddForm(BaseModel): + name: str + port: int + internalPort: Union[int, None] + +class ServiceAddStatus(BaseModel): + status:str + id: Union[str,None] + +class RenameForm(BaseModel): + name:str + +@app.post('/service/{service_id}/rename', response_model=StatusMessageModel) +async def service_rename(service_id: str, form: RenameForm, ): + """Request to change the name of a specific service""" + if not form.name: return {'status': 'The name cannot be empty!'} + db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id) + return {'status': 'ok'} + +@app.post('/services/add', response_model=ServiceAddStatus) +async def add_new_service(form: ServiceAddForm, ): + """Add a new service""" + serv_id = gen_service_id(db) + try: + internal_port = form.internalPort if form.internalPort else gen_internal_port(db) + db.query("INSERT INTO services (name, service_id, internal_port, public_port, status) VALUES (?, ?, ?, ?, ?)", + form.name, serv_id, internal_port, form.port, 'stop') + except sqlite3.IntegrityError: + return {'status': 'Name or/and ports of the service has been already assigned to another service'} + await firewall.reload() + + return {'status': 'ok', "id": serv_id } \ No newline at end of file diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index 726d45b..ed23156 100755 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -1,6 +1,6 @@ import asyncio from ipaddress import ip_interface -import os, socket, secrets, psutil +import os, socket, psutil import sys from fastapi_socketio import SocketManager @@ -30,13 +30,6 @@ def refactor_name(name:str): while " " in name: name = name.replace(" "," ") return name -def gen_service_id(db): - while True: - res = secrets.token_hex(8) - if len(db.query('SELECT 1 FROM services WHERE service_id = ?;', res)) == 0: - break - return res - def list_files(mypath): from os import listdir from os.path import isfile, join diff --git a/backend/utils/firegextables.py b/backend/utils/firegextables.py new file mode 100644 index 0000000..2fcf177 --- /dev/null +++ b/backend/utils/firegextables.py @@ -0,0 +1,125 @@ +from typing import List +import nftables +from utils import ip_parse, ip_family + +class FiregexFilter(): + def __init__(self, proto:str, port:int, ip_int:str, queue=None, target:str=None, id=None): + self.nftables = nftables.Nftables() + self.id = int(id) if id else None + self.queue = queue + self.target = target + self.proto = proto + self.port = int(port) + self.ip_int = str(ip_int) + + def __eq__(self, o: object) -> bool: + if isinstance(o, FiregexFilter): + return self.port == o.port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int) + return False + +class FiregexTables: + + def __init__(self): + self.table_name = "firegex" + self.nft = nftables.Nftables() + + def raw_cmd(self, *cmds): + return self.nft.json_cmd({"nftables": list(cmds)}) + + def cmd(self, *cmds): + code, out, err = self.raw_cmd(*cmds) + + if code == 0: return out + else: raise Exception(err) + + def init(self): + self.reset() + code, out, err = self.raw_cmd({"create":{"table":{"name":self.table_name,"family":"inet"}}}) + if code == 0: + self.cmd( + {"create":{"chain":{ + "family":"inet", + "table":self.table_name, + "name":"input", + "type":"filter", + "hook":"prerouting", + "prio":-150, + "policy":"accept" + }}}, + {"create":{"chain":{ + "family":"inet", + "table":self.table_name, + "name":"output", + "type":"filter", + "hook":"postrouting", + "prio":-150, + "policy":"accept" + }}} + ) + + + def reset(self): + self.raw_cmd( + {"flush":{"table":{"name":"firegex","family":"inet"}}}, + {"delete":{"table":{"name":"firegex","family":"inet"}}}, + ) + + def list(self): + return self.cmd({"list": {"ruleset": None}})["nftables"] + + def add_output(self, queue_range, proto, port, ip_int): + init, end = queue_range + if init > end: init, end = end, init + ip_int = ip_parse(ip_int) + ip_addr = str(ip_int).split("/")[0] + ip_addr_cidr = int(str(ip_int).split("/")[1]) + self.cmd({ "insert":{ "rule": { + "family": "inet", + "table": self.table_name, + "chain": "output", + "expr": [ + {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'saddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, + {'match': {"left": { "payload": {"protocol": str(proto), "field": "sport"}}, "op": "==", "right": int(port)}}, + {"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}} + ] + }}}) + + def add_input(self, queue_range, proto = None, port = None, ip_int = None): + init, end = queue_range + if init > end: init, end = end, init + ip_int = ip_parse(ip_int) + ip_addr = str(ip_int).split("/")[0] + ip_addr_cidr = int(str(ip_int).split("/")[1]) + self.cmd({"insert":{"rule":{ + "family": "inet", + "table": self.table_name, + "chain": "input", + "expr": [ + {'match': {'left': {'payload': {'protocol': ip_family(ip_int), 'field': 'daddr'}}, 'op': '==', 'right': {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}}}, + {'match': {"left": { "payload": {"protocol": str(proto), "field": "dport"}}, "op": "==", "right": int(port)}}, + {"queue": {"num": str(init) if init == end else f"{init}-{end}", "flags": ["bypass"]}} + ] + }}}) + + def get(self) -> List[FiregexFilter]: + res = [] + for filter in [ele["rule"] for ele in self.list() if "rule" in ele and ele["rule"]["table"] == self.table_name]: + queue_str = str(filter["expr"][2]["queue"]["num"]).split("-") + queue = None + if len(queue_str) == 1: queue = int(queue_str[0]), int(queue_str[0]) + else: queue = int(queue_str[0]), int(queue_str[1]) + ip_int = None + if isinstance(filter["expr"][0]["match"]["right"],str): + ip_int = str(ip_parse(filter["expr"][0]["match"]["right"])) + else: + ip_int = f'{filter["expr"][0]["match"]["right"]["prefix"]["addr"]}/{filter["expr"][0]["match"]["right"]["prefix"]["len"]}' + res.append(FiregexFilter( + target=filter["chain"], + id=int(filter["handle"]), + queue=queue, + proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"], + port=filter["expr"][1]["match"]["right"], + ip_int=ip_int + )) + return res + \ No newline at end of file diff --git a/backend/modules/sqlite.py b/backend/utils/sqlite.py similarity index 72% rename from backend/modules/sqlite.py rename to backend/utils/sqlite.py index 4bf38a3..12ced61 100644 --- a/backend/modules/sqlite.py +++ b/backend/utils/sqlite.py @@ -93,33 +93,3 @@ class SQLite(): self.query('INSERT INTO keys_values (key, value) VALUES (?, ?);', key, str(value)) else: self.query('UPDATE keys_values SET value=? WHERE key = ?;', str(value), key) - - -class Service: - def __init__(self, id: str, status: str, port: int, name: str, proto: str, ip_int: str): - self.id = id - self.status = status - self.port = port - self.name = name - self.proto = proto - self.ip_int = ip_int - - @classmethod - def from_dict(cls, var: dict): - return cls(id=var["service_id"], status=var["status"], port=var["port"], name=var["name"], proto=var["proto"], ip_int=var["ip_int"]) - - -class Regex: - def __init__(self, id: int, regex: bytes, mode: str, service_id: str, is_blacklist: bool, blocked_packets: int, is_case_sensitive: bool, active: bool): - self.regex = regex - self.mode = mode - self.service_id = service_id - self.is_blacklist = is_blacklist - self.blocked_packets = blocked_packets - self.id = id - self.is_case_sensitive = is_case_sensitive - self.active = active - - @classmethod - def from_dict(cls, var: dict): - return cls(id=var["regex_id"], regex=base64.b64decode(var["regex"]), mode=var["mode"], service_id=var["service_id"], is_blacklist=var["is_blacklist"], blocked_packets=var["blocked_packets"], is_case_sensitive=var["is_case_sensitive"], active=var["active"]) \ No newline at end of file diff --git a/frontend/src/components/Footer/index.module.scss b/frontend/src/components/Footer/index.module.scss index 607bd15..be3716c 100755 --- a/frontend/src/components/Footer/index.module.scss +++ b/frontend/src/components/Footer/index.module.scss @@ -2,7 +2,6 @@ @use "../../index.scss" as *; .footer{ - height: 150px; margin-top: 50px; background-color: $primary_color; @extend .center-flex; diff --git a/frontend/src/components/Footer/index.tsx b/frontend/src/components/Footer/index.tsx index 05e140d..7f61c54 100755 --- a/frontend/src/components/Footer/index.tsx +++ b/frontend/src/components/Footer/index.tsx @@ -1,12 +1,13 @@ +import { Footer } from '@mantine/core'; import React from 'react'; import style from "./index.module.scss"; -function Footer() { - return + } -export default Footer; +export default FooterPage; diff --git a/frontend/src/components/Header/index.module.scss b/frontend/src/components/Header/index.module.scss index b1bd452..369c4ef 100755 --- a/frontend/src/components/Header/index.module.scss +++ b/frontend/src/components/Header/index.module.scss @@ -1,17 +1,24 @@ @use "../../vars" as *; +@use "../../index.scss" as *; .header{ width: 100%; - height: 140px; background-color: $primary_color; display: flex; align-items: center; justify-content: center; } -.logo{ - width: 200px; - margin-left: 40px; - height: 70%; +.divlogo{ + width: 110px; + height: 100%; + cursor: pointer; + @extend .center-flex; +} + +.navbtn{ + @extend .center-flex; + width: 30px; + margin:0; } \ No newline at end of file diff --git a/frontend/src/components/Header/index.tsx b/frontend/src/components/Header/index.tsx index 5fbdf35..93eb6b8 100755 --- a/frontend/src/components/Header/index.tsx +++ b/frontend/src/components/Header/index.tsx @@ -1,12 +1,9 @@ import React, { useState } from 'react'; -import { ActionIcon, Divider, Image, Menu, Tooltip, FloatingTooltip } from '@mantine/core'; +import { ActionIcon, Divider, Image, Menu, Tooltip, FloatingTooltip, MediaQuery, Burger, Space, Header } from '@mantine/core'; import style from "./index.module.scss"; -import { errorNotify, logout } from '../../js/utils'; -import { BsPlusLg } from "react-icons/bs" +import { errorNotify, gatmainpath, logout } from '../../js/utils'; import { AiFillHome } from "react-icons/ai" -import { useNavigate, useParams } from 'react-router-dom'; -import AddNewRegex from '../NFRegex/AddNewRegex'; -import AddNewService from '../NFRegex/AddNewService'; +import { useNavigate } from 'react-router-dom'; import { FaLock } from 'react-icons/fa'; import { MdOutlineSettingsBackupRestore } from 'react-icons/md'; import { ImExit } from 'react-icons/im'; @@ -14,7 +11,7 @@ import ResetPasswordModal from './ResetPasswordModal'; import ResetModal from './ResetModal'; -function Header() { +function HeaderPage({navOpen, setNav, ...other}: { navOpen: boolean, setNav:React.Dispatch>}) { const navigator = useNavigate() @@ -26,19 +23,27 @@ function Header() { }) } + const go_to_home = () => { + navigator(`/${gatmainpath()}`) + } + const [changePasswordModal, setChangePasswordModal] = useState(false); const [resetFiregexModal, setResetFiregexModal] = useState(false); - const [tooltipAddOpened, setTooltipAddOpened] = useState(false); const [tooltipHomeOpened, setTooltipHomeOpened] = useState(false); + const [tooltipLogoutOpened,setTooltipLogoutOpened] = useState(false); - const {srv} = useParams() - - const [open, setOpen] = useState(false); - const closeModal = () => {setOpen(false);} - - return
- -
+ return
+ +
+ setNav((o) => !o)} + size="sm" + mr="xl" + /> +
+
Firegex logonavigator("/")}/> @@ -46,50 +51,34 @@ function Header() {
-
+ Firewall Access - } onClick={logout_action}>Logout - } onClick={() => setChangePasswordModal(true)}>Change Password + } onClick={() => setChangePasswordModal(true)}>Change Password Actions } onClick={() => setResetFiregexModal(true)}>Reset Firegex -
- + + navigator("/")} - aria-describedby="tooltip-home-id" + onClick={go_to_home} onFocus={() => setTooltipHomeOpened(false)} onBlur={() => setTooltipHomeOpened(false)} onMouseEnter={() => setTooltipHomeOpened(true)} onMouseLeave={() => setTooltipHomeOpened(false)}> - { srv? - - setOpen(true)} size="xl" radius="md" variant="filled" - aria-describedby="tooltip-add-id" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> - - : - setOpen(true)} size="xl" radius="md" variant="filled" - aria-describedby="tooltip-add-id" - onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)} - onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}> - - } - - {srv? - : - - } + + setTooltipLogoutOpened(false)} onBlur={() => setTooltipLogoutOpened(false)} + onMouseEnter={() => setTooltipLogoutOpened(true)} onMouseLeave={() => setTooltipLogoutOpened(false)}> + setChangePasswordModal(false)} /> setResetFiregexModal(false)} /> -
-
+ +
} -export default Header; +export default HeaderPage; diff --git a/frontend/src/components/MainLayout.tsx b/frontend/src/components/MainLayout.tsx index e94f3bf..148a7b7 100755 --- a/frontend/src/components/MainLayout.tsx +++ b/frontend/src/components/MainLayout.tsx @@ -1,23 +1,33 @@ -import React from 'react'; -import { Container, Space, Tabs } from '@mantine/core'; -import Footer from './Footer'; -import Header from './Header'; +import React, { useState } from 'react'; +import { Container, Space } from '@mantine/core'; +import { AppShell } from '@mantine/core'; +import NavBar from './NavBar'; +import FooterPage from './Footer'; +import HeaderPage from './Header'; + + + function MainLayout({ children }:{ children:any }) { + const [opened, setOpened] = useState(false); return <> -
- - - - - - - - {children} - - -