@@ -38,6 +38,7 @@ RUN g++ binsrc/proxy.cpp -o modules/proxy -O3 -pthread -lboost_system -lboost_th
|
|||||||
|
|
||||||
COPY ./backend/ /execute/
|
COPY ./backend/ /execute/
|
||||||
COPY --from=frontend /app/build/ ./frontend/
|
COPY --from=frontend /app/build/ ./frontend/
|
||||||
ENTRYPOINT ["/bin/sh", "/execute/docker-entrypoint.sh"]
|
|
||||||
|
CMD ["/bin/sh", "/execute/docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ Initiially the project was based only on regex filters, and also now the main fu
|
|||||||
|
|
||||||
## Next points
|
## Next points
|
||||||
|
|
||||||
- Create hijacking port to proxy
|
|
||||||
- Explanation about tools in the dedicated pages making them more user-friendly
|
- Explanation about tools in the dedicated pages making them more user-friendly
|
||||||
- buffering the TCP and(/or) the UDP stream to avoid to bypass the proxy dividing the information in more packets
|
- buffering the TCP and(/or) the UDP stream to avoid to bypass the proxy dividing the information in more packets
|
||||||
- Adding new section with "general firewall rules" to manage "simple" TCP traffic rules graphically and through nftables
|
- Adding new section with "general firewall rules" to manage "simple" TCP traffic rules graphically and through nftables
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
chown nobody:nobody -R /execute/
|
chown nobody:nobody -R /execute/
|
||||||
|
|
||||||
capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
|
exec capsh --caps="cap_net_admin+eip cap_setpcap,cap_setuid,cap_setgid+ep" \
|
||||||
--keep=1 --user=nobody --addamb=cap_net_admin -- \
|
--keep=1 --user=nobody --addamb=cap_net_admin -- -c "python3 /execute/app.py DOCKER"
|
||||||
-c "python3 /execute/app.py DOCKER"
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from typing import Dict, List, Set
|
from typing import Dict, List, Set
|
||||||
from utils.firegextables import FiregexFilter, FiregexTables
|
from modules.nfregex.nftables import FiregexTables
|
||||||
from utils import ip_parse, ip_family, run_func
|
from utils import ip_parse, run_func
|
||||||
from modules.nfregex.models import Service, Regex
|
from modules.nfregex.models import Service, Regex
|
||||||
import re, os, asyncio
|
import re, os, asyncio
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
nft = FiregexTables()
|
||||||
|
|
||||||
class RegexFilter:
|
class RegexFilter:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, regex,
|
self, regex,
|
||||||
@@ -52,7 +54,7 @@ class RegexFilter:
|
|||||||
class FiregexInterceptor:
|
class FiregexInterceptor:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.filter:FiregexFilter
|
self.srv:Service
|
||||||
self.filter_map_lock:asyncio.Lock
|
self.filter_map_lock:asyncio.Lock
|
||||||
self.filter_map: Dict[str, RegexFilter]
|
self.filter_map: Dict[str, RegexFilter]
|
||||||
self.regex_filters: Set[RegexFilter]
|
self.regex_filters: Set[RegexFilter]
|
||||||
@@ -61,16 +63,14 @@ class FiregexInterceptor:
|
|||||||
self.update_task: asyncio.Task
|
self.update_task: asyncio.Task
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def start(cls, filter: FiregexFilter):
|
async def start(cls, srv: Service):
|
||||||
self = cls()
|
self = cls()
|
||||||
self.filter = filter
|
self.srv = srv
|
||||||
self.filter_map_lock = asyncio.Lock()
|
self.filter_map_lock = asyncio.Lock()
|
||||||
self.update_config_lock = asyncio.Lock()
|
self.update_config_lock = asyncio.Lock()
|
||||||
input_range, output_range = await self._start_binary()
|
input_range, output_range = await self._start_binary()
|
||||||
self.update_task = asyncio.create_task(self.update_blocked())
|
self.update_task = asyncio.create_task(self.update_blocked())
|
||||||
if not filter in FiregexTables().get():
|
nft.add(self.srv, input_range, output_range)
|
||||||
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
|
return self
|
||||||
|
|
||||||
async def _start_binary(self):
|
async def _start_binary(self):
|
||||||
@@ -139,8 +139,3 @@ class FiregexInterceptor:
|
|||||||
except Exception: pass
|
except Exception: pass
|
||||||
return res
|
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"}}})
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from modules.nfregex.firegex import FiregexFilter, FiregexInterceptor, FiregexTables, RegexFilter, delete_by_srv
|
from modules.nfregex.firegex import FiregexInterceptor, RegexFilter
|
||||||
|
from modules.nfregex.nftables import FiregexTables, FiregexFilter
|
||||||
from modules.nfregex.models import Regex, Service
|
from modules.nfregex.models import Regex, Service
|
||||||
from utils.sqlite import SQLite
|
from utils.sqlite import SQLite
|
||||||
|
|
||||||
@@ -8,38 +9,40 @@ class STATUS:
|
|||||||
STOP = "stop"
|
STOP = "stop"
|
||||||
ACTIVE = "active"
|
ACTIVE = "active"
|
||||||
|
|
||||||
|
nft = FiregexTables()
|
||||||
|
|
||||||
class FirewallManager:
|
class FirewallManager:
|
||||||
def __init__(self, db:SQLite):
|
def __init__(self, db:SQLite):
|
||||||
self.db = db
|
self.db = db
|
||||||
self.proxy_table: Dict[str, ServiceManager] = {}
|
self.service_table: Dict[str, ServiceManager] = {}
|
||||||
self.lock = asyncio.Lock()
|
self.lock = asyncio.Lock()
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
for key in list(self.proxy_table.keys()):
|
for key in list(self.service_table.keys()):
|
||||||
await self.remove(key)
|
await self.remove(key)
|
||||||
|
|
||||||
async def remove(self,srv_id):
|
async def remove(self,srv_id):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
if srv_id in self.proxy_table:
|
if srv_id in self.service_table:
|
||||||
await self.proxy_table[srv_id].next(STATUS.STOP)
|
await self.service_table[srv_id].next(STATUS.STOP)
|
||||||
del self.proxy_table[srv_id]
|
del self.service_table[srv_id]
|
||||||
|
|
||||||
async def init(self):
|
async def init(self):
|
||||||
FiregexTables().init()
|
nft.init()
|
||||||
await self.reload()
|
await self.reload()
|
||||||
|
|
||||||
async def reload(self):
|
async def reload(self):
|
||||||
async with self.lock:
|
async with self.lock:
|
||||||
for srv in self.db.query('SELECT * FROM services;'):
|
for srv in self.db.query('SELECT * FROM services;'):
|
||||||
srv = Service.from_dict(srv)
|
srv = Service.from_dict(srv)
|
||||||
if srv.id in self.proxy_table:
|
if srv.id in self.service_table:
|
||||||
continue
|
continue
|
||||||
self.proxy_table[srv.id] = ServiceManager(srv, self.db)
|
self.service_table[srv.id] = ServiceManager(srv, self.db)
|
||||||
await self.proxy_table[srv.id].next(srv.status)
|
await self.service_table[srv.id].next(srv.status)
|
||||||
|
|
||||||
def get(self,srv_id):
|
def get(self,srv_id):
|
||||||
if srv_id in self.proxy_table:
|
if srv_id in self.service_table:
|
||||||
return self.proxy_table[srv_id]
|
return self.service_table[srv_id]
|
||||||
else:
|
else:
|
||||||
raise ServiceNotFoundException()
|
raise ServiceNotFoundException()
|
||||||
|
|
||||||
@@ -94,13 +97,13 @@ class ServiceManager:
|
|||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
if not self.interceptor:
|
if not self.interceptor:
|
||||||
delete_by_srv(self.srv)
|
nft.delete(self.srv)
|
||||||
self.interceptor = await FiregexInterceptor.start(FiregexFilter(self.srv.proto,self.srv.port, self.srv.ip_int))
|
self.interceptor = await FiregexInterceptor.start(self.srv)
|
||||||
await self._update_filters_from_db()
|
await self._update_filters_from_db()
|
||||||
self._set_status(STATUS.ACTIVE)
|
self._set_status(STATUS.ACTIVE)
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
delete_by_srv(self.srv)
|
nft.delete(self.srv)
|
||||||
if self.interceptor:
|
if self.interceptor:
|
||||||
await self.interceptor.stop()
|
await self.interceptor.stop()
|
||||||
self.interceptor = None
|
self.interceptor = None
|
||||||
|
|||||||
@@ -11,7 +11,14 @@ class Service:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, var: dict):
|
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"])
|
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:
|
class Regex:
|
||||||
@@ -27,4 +34,13 @@ class Regex:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, var: dict):
|
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"])
|
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"]
|
||||||
|
)
|
||||||
109
backend/modules/nfregex/nftables.py
Normal file
109
backend/modules/nfregex/nftables.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
from typing import List
|
||||||
|
from modules.nfregex.models import Service
|
||||||
|
from utils import ip_parse, ip_family, NFTableManager, nftables_int_to_json
|
||||||
|
|
||||||
|
class FiregexFilter:
|
||||||
|
def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
|
||||||
|
self.id = id
|
||||||
|
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)
|
||||||
|
elif isinstance(o, Service):
|
||||||
|
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(NFTableManager):
|
||||||
|
input_chain = "nfregex_input"
|
||||||
|
output_chain = "nfregex_output"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__([
|
||||||
|
{"add":{"chain":{
|
||||||
|
"family":"inet",
|
||||||
|
"table":self.table_name,
|
||||||
|
"name":self.input_chain,
|
||||||
|
"type":"filter",
|
||||||
|
"hook":"prerouting",
|
||||||
|
"prio":-150,
|
||||||
|
"policy":"accept"
|
||||||
|
}}},
|
||||||
|
{"add":{"chain":{
|
||||||
|
"family":"inet",
|
||||||
|
"table":self.table_name,
|
||||||
|
"name":self.output_chain,
|
||||||
|
"type":"filter",
|
||||||
|
"hook":"postrouting",
|
||||||
|
"prio":-150,
|
||||||
|
"policy":"accept"
|
||||||
|
}}}
|
||||||
|
],[
|
||||||
|
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
|
||||||
|
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.input_chain}}},
|
||||||
|
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
|
||||||
|
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.output_chain}}},
|
||||||
|
])
|
||||||
|
|
||||||
|
def add(self, srv:Service, queue_range_input, queue_range_output):
|
||||||
|
|
||||||
|
for ele in self.get():
|
||||||
|
if ele.__eq__(srv): return
|
||||||
|
|
||||||
|
init, end = queue_range_output
|
||||||
|
if init > end: init, end = end, init
|
||||||
|
self.cmd({ "insert":{ "rule": {
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": self.output_chain,
|
||||||
|
"expr": [
|
||||||
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'saddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||||
|
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.port)}},
|
||||||
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
|
]
|
||||||
|
}}})
|
||||||
|
|
||||||
|
init, end = queue_range_input
|
||||||
|
if init > end: init, end = end, init
|
||||||
|
self.cmd({"insert":{"rule":{
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": self.input_chain,
|
||||||
|
"expr": [
|
||||||
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_int), 'field': 'daddr'}}, 'op': '==', 'right': nftables_int_to_json(srv.ip_int)}},
|
||||||
|
{'match': {"left": { "payload": {"protocol": str(srv.proto), "field": "dport"}}, "op": "==", "right": int(srv.port)}},
|
||||||
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
|
]
|
||||||
|
}}})
|
||||||
|
|
||||||
|
|
||||||
|
def get(self) -> List[FiregexFilter]:
|
||||||
|
res = []
|
||||||
|
for filter in self.list_rules(tables=[self.table_name], chains=[self.input_chain,self.output_chain]):
|
||||||
|
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"]),
|
||||||
|
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
||||||
|
port=filter["expr"][1]["match"]["right"],
|
||||||
|
ip_int=ip_int
|
||||||
|
))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def delete(self, srv:Service):
|
||||||
|
for filter in self.get():
|
||||||
|
if filter.__eq__(srv):
|
||||||
|
self.cmd({ "delete":{ "rule": {
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": filter.target,
|
||||||
|
"handle": filter.id
|
||||||
|
}}})
|
||||||
|
|
||||||
0
backend/modules/porthijack/__init__.py
Normal file
0
backend/modules/porthijack/__init__.py
Normal file
77
backend/modules/porthijack/firewall.py
Normal file
77
backend/modules/porthijack/firewall.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import asyncio
|
||||||
|
from typing import Dict
|
||||||
|
from modules.porthijack.nftables import FiregexTables
|
||||||
|
from modules.porthijack.models import Service
|
||||||
|
from utils.sqlite import SQLite
|
||||||
|
|
||||||
|
nft = FiregexTables()
|
||||||
|
|
||||||
|
class FirewallManager:
|
||||||
|
def __init__(self, db:SQLite):
|
||||||
|
self.db = db
|
||||||
|
self.service_table: Dict[str, ServiceManager] = {}
|
||||||
|
self.lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
for key in list(self.service_table.keys()):
|
||||||
|
await self.remove(key)
|
||||||
|
|
||||||
|
async def remove(self,srv_id):
|
||||||
|
async with self.lock:
|
||||||
|
if srv_id in self.service_table:
|
||||||
|
await self.service_table[srv_id].disable()
|
||||||
|
del self.service_table[srv_id]
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
FiregexTables().init()
|
||||||
|
await self.reload()
|
||||||
|
|
||||||
|
async def reload(self):
|
||||||
|
async with self.lock:
|
||||||
|
for srv in self.db.query('SELECT * FROM services;'):
|
||||||
|
srv = Service.from_dict(srv)
|
||||||
|
if srv.service_id in self.service_table:
|
||||||
|
continue
|
||||||
|
self.service_table[srv.service_id] = ServiceManager(srv, self.db)
|
||||||
|
if srv.active:
|
||||||
|
await self.service_table[srv.service_id].enable()
|
||||||
|
|
||||||
|
def get(self,srv_id):
|
||||||
|
if srv_id in self.service_table:
|
||||||
|
return self.service_table[srv_id]
|
||||||
|
else:
|
||||||
|
raise ServiceNotFoundException()
|
||||||
|
|
||||||
|
class ServiceNotFoundException(Exception): pass
|
||||||
|
|
||||||
|
class ServiceManager:
|
||||||
|
def __init__(self, srv: Service, db):
|
||||||
|
self.srv = srv
|
||||||
|
self.db = db
|
||||||
|
self.active = False
|
||||||
|
self.lock = asyncio.Lock()
|
||||||
|
|
||||||
|
async def enable(self):
|
||||||
|
if not self.active:
|
||||||
|
async with self.lock:
|
||||||
|
nft.delete(self.srv)
|
||||||
|
nft.add(self.srv)
|
||||||
|
self._set_status(True)
|
||||||
|
|
||||||
|
async def disable(self):
|
||||||
|
if self.active:
|
||||||
|
async with self.lock:
|
||||||
|
nft.delete(self.srv)
|
||||||
|
self._set_status(False)
|
||||||
|
|
||||||
|
async def refresh(self, srv:Service):
|
||||||
|
self.srv = srv
|
||||||
|
if self.active: await self.restart()
|
||||||
|
|
||||||
|
def _set_status(self,active):
|
||||||
|
self.active = active
|
||||||
|
self.db.query("UPDATE services SET active = ? WHERE service_id = ?;", active, self.srv.service_id)
|
||||||
|
|
||||||
|
async def restart(self):
|
||||||
|
await self.disable()
|
||||||
|
await self.enable()
|
||||||
23
backend/modules/porthijack/models.py
Normal file
23
backend/modules/porthijack/models.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
class Service:
|
||||||
|
def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_src: str, ip_dst:str):
|
||||||
|
self.service_id = service_id
|
||||||
|
self.active = active
|
||||||
|
self.public_port = public_port
|
||||||
|
self.proxy_port = proxy_port
|
||||||
|
self.name = name
|
||||||
|
self.proto = proto
|
||||||
|
self.ip_src = ip_src
|
||||||
|
self.ip_dst = ip_dst
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, var: dict):
|
||||||
|
return cls(
|
||||||
|
service_id=var["service_id"],
|
||||||
|
active=var["active"],
|
||||||
|
public_port=var["public_port"],
|
||||||
|
proxy_port=var["proxy_port"],
|
||||||
|
name=var["name"],
|
||||||
|
proto=var["proto"],
|
||||||
|
ip_src=var["ip_src"],
|
||||||
|
ip_dst=var["ip_dst"]
|
||||||
|
)
|
||||||
105
backend/modules/porthijack/nftables.py
Normal file
105
backend/modules/porthijack/nftables.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
from typing import List
|
||||||
|
from modules.porthijack.models import Service
|
||||||
|
from utils import addr_parse, ip_parse, ip_family, NFTableManager, nftables_json_to_int
|
||||||
|
|
||||||
|
class FiregexHijackRule():
|
||||||
|
def __init__(self, proto:str, public_port:int,proxy_port:int, ip_src:str, ip_dst:str, target:str, id:int):
|
||||||
|
self.id = id
|
||||||
|
self.target = target
|
||||||
|
self.proto = proto
|
||||||
|
self.public_port = public_port
|
||||||
|
self.proxy_port = proxy_port
|
||||||
|
self.ip_src = str(ip_src)
|
||||||
|
self.ip_dst = str(ip_dst)
|
||||||
|
|
||||||
|
def __eq__(self, o: object) -> bool:
|
||||||
|
if isinstance(o, FiregexHijackRule):
|
||||||
|
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
|
||||||
|
elif isinstance(o, Service):
|
||||||
|
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_src) == ip_parse(o.ip_src)
|
||||||
|
return False
|
||||||
|
|
||||||
|
class FiregexTables(NFTableManager):
|
||||||
|
prerouting_porthijack = "prerouting_porthijack"
|
||||||
|
postrouting_porthijack = "postrouting_porthijack"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__([
|
||||||
|
{"add":{"chain":{
|
||||||
|
"family":"inet",
|
||||||
|
"table":self.table_name,
|
||||||
|
"name":self.prerouting_porthijack,
|
||||||
|
"type":"filter",
|
||||||
|
"hook":"prerouting",
|
||||||
|
"prio":-300,
|
||||||
|
"policy":"accept"
|
||||||
|
}}},
|
||||||
|
{"add":{"chain":{
|
||||||
|
"family":"inet",
|
||||||
|
"table":self.table_name,
|
||||||
|
"name":self.postrouting_porthijack,
|
||||||
|
"type":"filter",
|
||||||
|
"hook":"postrouting",
|
||||||
|
"prio":-300,
|
||||||
|
"policy":"accept"
|
||||||
|
}}}
|
||||||
|
],[
|
||||||
|
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
|
||||||
|
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.prerouting_porthijack}}},
|
||||||
|
{"flush":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}},
|
||||||
|
{"delete":{"chain":{"table":self.table_name,"family":"inet", "name":self.postrouting_porthijack}}}
|
||||||
|
])
|
||||||
|
|
||||||
|
def add(self, srv:Service):
|
||||||
|
|
||||||
|
for ele in self.get():
|
||||||
|
if ele.__eq__(srv): return
|
||||||
|
|
||||||
|
self.cmd({ "insert":{ "rule": {
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": self.prerouting_porthijack,
|
||||||
|
"expr": [
|
||||||
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'op': '==', 'right': addr_parse(srv.ip_src)}},
|
||||||
|
{'match': {'left': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'op': '==', 'right': int(srv.public_port)}},
|
||||||
|
{'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'dport'}}, 'value': int(srv.proxy_port)}},
|
||||||
|
{'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_src), 'field': 'daddr'}}, 'value': addr_parse(srv.ip_dst)}}
|
||||||
|
]
|
||||||
|
}}})
|
||||||
|
self.cmd({ "insert":{ "rule": {
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": self.postrouting_porthijack,
|
||||||
|
"expr": [
|
||||||
|
{'match': {'left': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'op': '==', 'right': addr_parse(srv.ip_dst)}},
|
||||||
|
{'match': {'left': { "payload": {"protocol": str(srv.proto), "field": "sport"}}, "op": "==", "right": int(srv.proxy_port)}},
|
||||||
|
{'mangle': {'key': {'payload': {'protocol': str(srv.proto), 'field': 'sport'}}, 'value': int(srv.public_port)}},
|
||||||
|
{'mangle': {'key': {'payload': {'protocol': ip_family(srv.ip_dst), 'field': 'saddr'}}, 'value': addr_parse(srv.ip_src)}}
|
||||||
|
]
|
||||||
|
}}})
|
||||||
|
|
||||||
|
|
||||||
|
def get(self) -> List[FiregexHijackRule]:
|
||||||
|
res = []
|
||||||
|
for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
|
||||||
|
filter["expr"][0]["match"]["right"]
|
||||||
|
res.append(FiregexHijackRule(
|
||||||
|
target=filter["chain"],
|
||||||
|
id=int(filter["handle"]),
|
||||||
|
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
||||||
|
public_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.prerouting_porthijack else filter["expr"][2]["mangle"]["value"],
|
||||||
|
proxy_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"],
|
||||||
|
ip_src=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.prerouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
|
||||||
|
ip_dst=nftables_json_to_int(filter["expr"][0]["match"]["right"]) if filter["chain"] == self.postrouting_porthijack else nftables_json_to_int(filter["expr"][3]["mangle"]["value"]),
|
||||||
|
))
|
||||||
|
return res
|
||||||
|
|
||||||
|
def delete(self, srv:Service):
|
||||||
|
for filter in self.get():
|
||||||
|
if filter.__eq__(srv):
|
||||||
|
self.cmd({ "delete":{ "rule": {
|
||||||
|
"family": "inet",
|
||||||
|
"table": self.table_name,
|
||||||
|
"chain": filter.target,
|
||||||
|
"handle": filter.id
|
||||||
|
}}})
|
||||||
@@ -5,7 +5,7 @@ import sqlite3
|
|||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from modules.nfregex.firegex import FiregexTables
|
from modules.nfregex.nftables import FiregexTables
|
||||||
from modules.nfregex.firewall import STATUS, FirewallManager
|
from modules.nfregex.firewall import STATUS, FirewallManager
|
||||||
from utils.sqlite import SQLite
|
from utils.sqlite import SQLite
|
||||||
from utils import ip_parse, refactor_name, refresh_frontend
|
from utils import ip_parse, refactor_name, refresh_frontend
|
||||||
@@ -145,7 +145,7 @@ async def get_service_list():
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
@app.get('/service/{service_id}', response_model=ServiceModel)
|
@app.get('/service/{service_id}', response_model=ServiceModel)
|
||||||
async def get_service_by_id(service_id: str, ):
|
async def get_service_by_id(service_id: str):
|
||||||
"""Get info about a specific service using his id"""
|
"""Get info about a specific service using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -164,21 +164,21 @@ async def get_service_by_id(service_id: str, ):
|
|||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||||
async def service_stop(service_id: str, ):
|
async def service_stop(service_id: str):
|
||||||
"""Request the stop of a specific service"""
|
"""Request the stop of a specific service"""
|
||||||
await firewall.get(service_id).next(STATUS.STOP)
|
await firewall.get(service_id).next(STATUS.STOP)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
||||||
async def service_start(service_id: str, ):
|
async def service_start(service_id: str):
|
||||||
"""Request the start of a specific service"""
|
"""Request the start of a specific service"""
|
||||||
await firewall.get(service_id).next(STATUS.ACTIVE)
|
await firewall.get(service_id).next(STATUS.ACTIVE)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||||
async def service_delete(service_id: str, ):
|
async def service_delete(service_id: str):
|
||||||
"""Request the deletion of a specific service"""
|
"""Request the deletion of a specific service"""
|
||||||
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||||
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
||||||
@@ -187,7 +187,7 @@ async def service_delete(service_id: str, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||||
async def service_rename(service_id: str, form: RenameForm, ):
|
async def service_rename(service_id: str, form: RenameForm):
|
||||||
"""Request to change the name of a specific service"""
|
"""Request to change the name of a specific service"""
|
||||||
form.name = refactor_name(form.name)
|
form.name = refactor_name(form.name)
|
||||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||||
@@ -199,7 +199,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||||
async def get_service_regexe_list(service_id: str, ):
|
async def get_service_regexe_list(service_id: str):
|
||||||
"""Get the list of the regexes of a service"""
|
"""Get the list of the regexes of a service"""
|
||||||
return db.query("""
|
return db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -209,7 +209,7 @@ async def get_service_regexe_list(service_id: str, ):
|
|||||||
""", service_id)
|
""", service_id)
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
||||||
async def get_regex_by_id(regex_id: int, ):
|
async def get_regex_by_id(regex_id: int):
|
||||||
"""Get regex info using his id"""
|
"""Get regex info using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -221,7 +221,7 @@ async def get_regex_by_id(regex_id: int, ):
|
|||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
||||||
async def regex_delete(regex_id: int, ):
|
async def regex_delete(regex_id: int):
|
||||||
"""Delete a regex using his id"""
|
"""Delete a regex using his id"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -232,7 +232,7 @@ async def regex_delete(regex_id: int, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
||||||
async def regex_enable(regex_id: int, ):
|
async def regex_enable(regex_id: int):
|
||||||
"""Request the enabling of a regex"""
|
"""Request the enabling of a regex"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -242,7 +242,7 @@ async def regex_enable(regex_id: int, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
||||||
async def regex_disable(regex_id: int, ):
|
async def regex_disable(regex_id: int):
|
||||||
"""Request the deactivation of a regex"""
|
"""Request the deactivation of a regex"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -252,7 +252,7 @@ async def regex_disable(regex_id: int, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.post('/regexes/add', response_model=StatusMessageModel)
|
@app.post('/regexes/add', response_model=StatusMessageModel)
|
||||||
async def add_new_regex(form: RegexAddForm, ):
|
async def add_new_regex(form: RegexAddForm):
|
||||||
"""Add a new regex"""
|
"""Add a new regex"""
|
||||||
try:
|
try:
|
||||||
re.compile(b64decode(form.regex))
|
re.compile(b64decode(form.regex))
|
||||||
@@ -269,7 +269,7 @@ async def add_new_regex(form: RegexAddForm, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.post('/services/add', response_model=ServiceAddResponse)
|
@app.post('/services/add', response_model=ServiceAddResponse)
|
||||||
async def add_new_service(form: ServiceAddForm, ):
|
async def add_new_service(form: ServiceAddForm):
|
||||||
"""Add a new service"""
|
"""Add a new service"""
|
||||||
try:
|
try:
|
||||||
form.ip_int = ip_parse(form.ip_int)
|
form.ip_int = ip_parse(form.ip_int)
|
||||||
|
|||||||
196
backend/routers/porthijack.py
Normal file
196
backend/routers/porthijack.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import secrets
|
||||||
|
import sqlite3
|
||||||
|
from typing import List, Union
|
||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from modules.porthijack.models import Service
|
||||||
|
from utils.sqlite import SQLite
|
||||||
|
from utils import addr_parse, ip_family, refactor_name, refresh_frontend
|
||||||
|
from utils.models import ResetRequest, StatusMessageModel
|
||||||
|
from modules.porthijack.nftables import FiregexTables
|
||||||
|
from modules.porthijack.firewall import FirewallManager
|
||||||
|
|
||||||
|
class ServiceModel(BaseModel):
|
||||||
|
service_id: str
|
||||||
|
active: bool
|
||||||
|
public_port: int
|
||||||
|
proxy_port: int
|
||||||
|
name: str
|
||||||
|
proto: str
|
||||||
|
ip_src: str
|
||||||
|
ip_dst: str
|
||||||
|
|
||||||
|
class RenameForm(BaseModel):
|
||||||
|
name:str
|
||||||
|
|
||||||
|
class ServiceAddForm(BaseModel):
|
||||||
|
name: str
|
||||||
|
public_port: int
|
||||||
|
proxy_port: int
|
||||||
|
proto: str
|
||||||
|
ip_src: str
|
||||||
|
ip_dst: str
|
||||||
|
|
||||||
|
class ServiceAddResponse(BaseModel):
|
||||||
|
status:str
|
||||||
|
service_id: Union[None,str]
|
||||||
|
|
||||||
|
class GeneralStatModel(BaseModel):
|
||||||
|
services: int
|
||||||
|
|
||||||
|
app = APIRouter()
|
||||||
|
|
||||||
|
db = SQLite('db/port-hijacking.db', {
|
||||||
|
'services': {
|
||||||
|
'service_id': 'VARCHAR(100) PRIMARY KEY',
|
||||||
|
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
||||||
|
'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536)',
|
||||||
|
'proxy_port': 'INT NOT NULL CHECK(proxy_port > 0 and proxy_port < 65536 and proxy_port != public_port)',
|
||||||
|
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
||||||
|
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
|
||||||
|
'ip_src': 'VARCHAR(100) NOT NULL',
|
||||||
|
'ip_dst': 'VARCHAR(100) NOT NULL',
|
||||||
|
},
|
||||||
|
'QUERY':[
|
||||||
|
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_src, proto);"
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
async def get_general_stats():
|
||||||
|
"""Get firegex general status about services"""
|
||||||
|
return db.query("""
|
||||||
|
SELECT
|
||||||
|
(SELECT COUNT(*) FROM services) services
|
||||||
|
""")[0]
|
||||||
|
|
||||||
|
@app.get('/services', response_model=List[ServiceModel])
|
||||||
|
async def get_service_list():
|
||||||
|
"""Get the list of existent firegex services"""
|
||||||
|
return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services;")
|
||||||
|
|
||||||
|
@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 service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst FROM services WHERE 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).disable()
|
||||||
|
await refresh_frontend()
|
||||||
|
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).enable()
|
||||||
|
await refresh_frontend()
|
||||||
|
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)
|
||||||
|
await firewall.remove(service_id)
|
||||||
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
@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"""
|
||||||
|
form.name = refactor_name(form.name)
|
||||||
|
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||||
|
try:
|
||||||
|
db.query('UPDATE services SET name=? WHERE service_id = ?;', form.name, service_id)
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
return {'status': 'This name is already used'}
|
||||||
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
class ChangeDestination(BaseModel):
|
||||||
|
ip_dst: str
|
||||||
|
proxy_port: int
|
||||||
|
|
||||||
|
@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
|
||||||
|
async def service_change_destination(service_id: str, form: ChangeDestination):
|
||||||
|
"""Request to change the proxy destination of the service"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
form.ip_dst = addr_parse(form.ip_dst)
|
||||||
|
except ValueError:
|
||||||
|
return {"status":"Invalid address"}
|
||||||
|
srv = Service.from_dict(db.query('SELECT * FROM services WHERE service_id = ?;', service_id)[0])
|
||||||
|
if ip_family(form.ip_dst) != ip_family(srv.ip_src):
|
||||||
|
return {'status': 'The destination ip is not of the same family as the source ip'}
|
||||||
|
try:
|
||||||
|
db.query('UPDATE services SET proxy_port=?, ip_dst=? WHERE service_id = ?;', form.proxy_port, form.ip_dst, service_id)
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
return {'status': 'Invalid proxy port or service'}
|
||||||
|
|
||||||
|
srv.ip_dst = form.ip_dst
|
||||||
|
srv.proxy_port = form.proxy_port
|
||||||
|
await firewall.get(service_id).refresh(srv)
|
||||||
|
|
||||||
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
@app.post('/services/add', response_model=ServiceAddResponse)
|
||||||
|
async def add_new_service(form: ServiceAddForm):
|
||||||
|
"""Add a new service"""
|
||||||
|
try:
|
||||||
|
form.ip_src = addr_parse(form.ip_src)
|
||||||
|
form.ip_dst = addr_parse(form.ip_dst)
|
||||||
|
except ValueError:
|
||||||
|
return {"status":"Invalid address"}
|
||||||
|
|
||||||
|
if ip_family(form.ip_dst) != ip_family(form.ip_src):
|
||||||
|
return {"status":"Destination and source addresses must be of the same family"}
|
||||||
|
if form.proto not in ["tcp", "udp"]:
|
||||||
|
return {"status":"Invalid protocol"}
|
||||||
|
|
||||||
|
srv_id = None
|
||||||
|
try:
|
||||||
|
srv_id = gen_service_id()
|
||||||
|
db.query("INSERT INTO services (service_id, active, public_port, proxy_port, name, proto, ip_src, ip_dst) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_src, form.ip_dst)
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
return {'status': 'This type of service already exists'}
|
||||||
|
|
||||||
|
await firewall.reload()
|
||||||
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok', 'service_id': srv_id}
|
||||||
@@ -100,7 +100,7 @@ async def get_service_list():
|
|||||||
""")
|
""")
|
||||||
|
|
||||||
@app.get('/service/{service_id}', response_model=ServiceModel)
|
@app.get('/service/{service_id}', response_model=ServiceModel)
|
||||||
async def get_service_by_id(service_id: str, ):
|
async def get_service_by_id(service_id: str):
|
||||||
"""Get info about a specific service using his id"""
|
"""Get info about a specific service using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -118,28 +118,28 @@ async def get_service_by_id(service_id: str, ):
|
|||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/stop', response_model=StatusMessageModel)
|
||||||
async def service_stop(service_id: str, ):
|
async def service_stop(service_id: str):
|
||||||
"""Request the stop of a specific service"""
|
"""Request the stop of a specific service"""
|
||||||
await firewall.get(service_id).next(STATUS.STOP)
|
await firewall.get(service_id).next(STATUS.STOP)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/pause', response_model=StatusMessageModel)
|
||||||
async def service_pause(service_id: str, ):
|
async def service_pause(service_id: str):
|
||||||
"""Request the pause of a specific service"""
|
"""Request the pause of a specific service"""
|
||||||
await firewall.get(service_id).next(STATUS.PAUSE)
|
await firewall.get(service_id).next(STATUS.PAUSE)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/start', response_model=StatusMessageModel)
|
||||||
async def service_start(service_id: str, ):
|
async def service_start(service_id: str):
|
||||||
"""Request the start of a specific service"""
|
"""Request the start of a specific service"""
|
||||||
await firewall.get(service_id).next(STATUS.ACTIVE)
|
await firewall.get(service_id).next(STATUS.ACTIVE)
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/delete', response_model=StatusMessageModel)
|
||||||
async def service_delete(service_id: str, ):
|
async def service_delete(service_id: str):
|
||||||
"""Request the deletion of a specific service"""
|
"""Request the deletion of a specific service"""
|
||||||
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
db.query('DELETE FROM services WHERE service_id = ?;', service_id)
|
||||||
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
db.query('DELETE FROM regexes WHERE service_id = ?;', service_id)
|
||||||
@@ -149,7 +149,7 @@ async def service_delete(service_id: str, ):
|
|||||||
|
|
||||||
|
|
||||||
@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel)
|
@app.get('/service/{service_id}/regen-port', response_model=StatusMessageModel)
|
||||||
async def regen_service_port(service_id: str, ):
|
async def regen_service_port(service_id: str):
|
||||||
"""Request the regeneration of a the internal proxy port of a specific service"""
|
"""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)
|
db.query('UPDATE services SET internal_port = ? WHERE service_id = ?;', gen_internal_port(db), service_id)
|
||||||
await firewall.get(service_id).update_port()
|
await firewall.get(service_id).update_port()
|
||||||
@@ -161,7 +161,7 @@ class ChangePortForm(BaseModel):
|
|||||||
internalPort: Union[int, None]
|
internalPort: Union[int, None]
|
||||||
|
|
||||||
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
|
@app.post('/service/{service_id}/change-ports', response_model=StatusMessageModel)
|
||||||
async def change_service_ports(service_id: str, change_port:ChangePortForm ):
|
async def change_service_ports(service_id: str, change_port:ChangePortForm):
|
||||||
"""Choose and change the ports of the service"""
|
"""Choose and change the ports of the service"""
|
||||||
if change_port.port is None and change_port.internalPort is None:
|
if change_port.port is None and change_port.internalPort is None:
|
||||||
return {'status': 'Invalid Request!'}
|
return {'status': 'Invalid Request!'}
|
||||||
@@ -195,7 +195,7 @@ class RegexModel(BaseModel):
|
|||||||
active:bool
|
active:bool
|
||||||
|
|
||||||
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
@app.get('/service/{service_id}/regexes', response_model=List[RegexModel])
|
||||||
async def get_service_regexe_list(service_id: str, ):
|
async def get_service_regexe_list(service_id: str):
|
||||||
"""Get the list of the regexes of a service"""
|
"""Get the list of the regexes of a service"""
|
||||||
return db.query("""
|
return db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -205,7 +205,7 @@ async def get_service_regexe_list(service_id: str, ):
|
|||||||
""", service_id)
|
""", service_id)
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
@app.get('/regex/{regex_id}', response_model=RegexModel)
|
||||||
async def get_regex_by_id(regex_id: int, ):
|
async def get_regex_by_id(regex_id: int):
|
||||||
"""Get regex info using his id"""
|
"""Get regex info using his id"""
|
||||||
res = db.query("""
|
res = db.query("""
|
||||||
SELECT
|
SELECT
|
||||||
@@ -217,7 +217,7 @@ async def get_regex_by_id(regex_id: int, ):
|
|||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/delete', response_model=StatusMessageModel)
|
||||||
async def regex_delete(regex_id: int, ):
|
async def regex_delete(regex_id: int):
|
||||||
"""Delete a regex using his id"""
|
"""Delete a regex using his id"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -227,7 +227,7 @@ async def regex_delete(regex_id: int, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/enable', response_model=StatusMessageModel)
|
||||||
async def regex_enable(regex_id: int, ):
|
async def regex_enable(regex_id: int):
|
||||||
"""Request the enabling of a regex"""
|
"""Request the enabling of a regex"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -237,7 +237,7 @@ async def regex_enable(regex_id: int, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
@app.get('/regex/{regex_id}/disable', response_model=StatusMessageModel)
|
||||||
async def regex_disable(regex_id: int, ):
|
async def regex_disable(regex_id: int):
|
||||||
"""Request the deactivation of a regex"""
|
"""Request the deactivation of a regex"""
|
||||||
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
res = db.query('SELECT * FROM regexes WHERE regex_id = ?;', regex_id)
|
||||||
if len(res) != 0:
|
if len(res) != 0:
|
||||||
@@ -255,7 +255,7 @@ class RegexAddForm(BaseModel):
|
|||||||
is_case_sensitive: bool
|
is_case_sensitive: bool
|
||||||
|
|
||||||
@app.post('/regexes/add', response_model=StatusMessageModel)
|
@app.post('/regexes/add', response_model=StatusMessageModel)
|
||||||
async def add_new_regex(form: RegexAddForm, ):
|
async def add_new_regex(form: RegexAddForm):
|
||||||
"""Add a new regex"""
|
"""Add a new regex"""
|
||||||
try:
|
try:
|
||||||
re.compile(b64decode(form.regex))
|
re.compile(b64decode(form.regex))
|
||||||
@@ -283,7 +283,7 @@ class RenameForm(BaseModel):
|
|||||||
name:str
|
name:str
|
||||||
|
|
||||||
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
@app.post('/service/{service_id}/rename', response_model=StatusMessageModel)
|
||||||
async def service_rename(service_id: str, form: RenameForm, ):
|
async def service_rename(service_id: str, form: RenameForm):
|
||||||
"""Request to change the name of a specific service"""
|
"""Request to change the name of a specific service"""
|
||||||
form.name = refactor_name(form.name)
|
form.name = refactor_name(form.name)
|
||||||
if not form.name: return {'status': 'The name cannot be empty!'}
|
if not form.name: return {'status': 'The name cannot be empty!'}
|
||||||
@@ -295,7 +295,7 @@ async def service_rename(service_id: str, form: RenameForm, ):
|
|||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.post('/services/add', response_model=ServiceAddStatus)
|
@app.post('/services/add', response_model=ServiceAddStatus)
|
||||||
async def add_new_service(form: ServiceAddForm, ):
|
async def add_new_service(form: ServiceAddForm):
|
||||||
"""Add a new service"""
|
"""Add a new service"""
|
||||||
serv_id = gen_service_id(db)
|
serv_id = gen_service_id(db)
|
||||||
form.name = refactor_name(form.name)
|
form.name = refactor_name(form.name)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from ipaddress import ip_interface
|
from ipaddress import ip_address, ip_interface
|
||||||
import os, socket, psutil
|
import os, socket, psutil, sys, nftables
|
||||||
import sys
|
|
||||||
from fastapi_socketio import SocketManager
|
from fastapi_socketio import SocketManager
|
||||||
|
|
||||||
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
|
LOCALHOST_IP = socket.gethostbyname(os.getenv("LOCALHOST_IP","127.0.0.1"))
|
||||||
@@ -38,6 +37,9 @@ def list_files(mypath):
|
|||||||
def ip_parse(ip:str):
|
def ip_parse(ip:str):
|
||||||
return str(ip_interface(ip).network)
|
return str(ip_interface(ip).network)
|
||||||
|
|
||||||
|
def addr_parse(ip:str):
|
||||||
|
return str(ip_address(ip))
|
||||||
|
|
||||||
def ip_family(ip:str):
|
def ip_family(ip:str):
|
||||||
return "ip6" if ip_interface(ip).version == 6 else "ip"
|
return "ip6" if ip_interface(ip).version == 6 else "ip"
|
||||||
|
|
||||||
@@ -47,4 +49,60 @@ def get_interfaces():
|
|||||||
for interf in interfs:
|
for interf in interfs:
|
||||||
if interf.family in [socket.AF_INET, socket.AF_INET6]:
|
if interf.family in [socket.AF_INET, socket.AF_INET6]:
|
||||||
yield {"name": int_name, "addr":interf.address}
|
yield {"name": int_name, "addr":interf.address}
|
||||||
return list(_get_interfaces())
|
return list(_get_interfaces())
|
||||||
|
|
||||||
|
def nftables_int_to_json(ip_int):
|
||||||
|
ip_int = ip_parse(ip_int)
|
||||||
|
ip_addr = str(ip_int).split("/")[0]
|
||||||
|
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
||||||
|
return {"prefix": {"addr": ip_addr, "len": ip_addr_cidr}}
|
||||||
|
|
||||||
|
def nftables_json_to_int(ip_json_int):
|
||||||
|
if isinstance(ip_json_int,str):
|
||||||
|
return str(ip_parse(ip_json_int))
|
||||||
|
else:
|
||||||
|
return f'{ip_json_int["prefix"]["addr"]}/{ip_json_int["prefix"]["len"]}'
|
||||||
|
|
||||||
|
class Singleton(object):
|
||||||
|
__instance = None
|
||||||
|
def __new__(class_, *args, **kwargs):
|
||||||
|
if not isinstance(class_.__instance, class_):
|
||||||
|
class_.__instance = object.__new__(class_, *args, **kwargs)
|
||||||
|
return class_.__instance
|
||||||
|
|
||||||
|
class NFTableManager(Singleton):
|
||||||
|
|
||||||
|
table_name = "firegex"
|
||||||
|
|
||||||
|
def __init__(self, init_cmd, reset_cmd):
|
||||||
|
self.__init_cmds = init_cmd
|
||||||
|
self.__reset_cmds = reset_cmd
|
||||||
|
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()
|
||||||
|
self.raw_cmd({"add":{"table":{"name":self.table_name,"family":"inet"}}})
|
||||||
|
self.cmd(*self.__init_cmds)
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
self.raw_cmd(*self.__reset_cmds)
|
||||||
|
|
||||||
|
def list_rules(self, tables = None, chains = None):
|
||||||
|
for filter in [ele["rule"] for ele in self.raw_list() if "rule" in ele ]:
|
||||||
|
if tables and filter["table"] not in tables: continue
|
||||||
|
if chains and filter["chain"] not in chains: continue
|
||||||
|
yield filter
|
||||||
|
|
||||||
|
def raw_list(self):
|
||||||
|
return self.cmd({"list": {"ruleset": None}})["nftables"]
|
||||||
|
|
||||||
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
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 {"range":[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 {"range":[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 = filter["expr"][2]["queue"]["num"]
|
|
||||||
queue = None
|
|
||||||
if isinstance(queue_str,dict): queue = int(queue_str["range"][0]), int(queue_str["range"][1])
|
|
||||||
else: queue = int(queue_str), int(queue_str)
|
|
||||||
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
|
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ def load_routers(app):
|
|||||||
resets, startups, shutdowns = [], [], []
|
resets, startups, shutdowns = [], [], []
|
||||||
for router in get_router_modules():
|
for router in get_router_modules():
|
||||||
if router.router:
|
if router.router:
|
||||||
app.include_router(router.router, prefix=f"/{router.name}")
|
app.include_router(router.router, prefix=f"/{router.name}", tags=[router.name])
|
||||||
if router.reset:
|
if router.reset:
|
||||||
resets.append(router.reset)
|
resets.append(router.reset)
|
||||||
if router.startup:
|
if router.startup:
|
||||||
|
|||||||
@@ -6,11 +6,12 @@ import { Outlet, Route, Routes } from 'react-router-dom';
|
|||||||
import MainLayout from './components/MainLayout';
|
import MainLayout from './components/MainLayout';
|
||||||
import { PasswordSend, ServerStatusResponse } from './js/models';
|
import { PasswordSend, ServerStatusResponse } from './js/models';
|
||||||
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, login, setpassword } from './js/utils';
|
import { errorNotify, fireUpdateRequest, getstatus, HomeRedirector, login, setpassword } from './js/utils';
|
||||||
import NFRegex from './pages/NFRegex.tsx';
|
import NFRegex from './pages/NFRegex';
|
||||||
import io from 'socket.io-client';
|
import io from 'socket.io-client';
|
||||||
import RegexProxy from './pages/RegexProxy';
|
import RegexProxy from './pages/RegexProxy';
|
||||||
import ServiceDetailsNFRegex from './pages/NFRegex.tsx/ServiceDetails';
|
import ServiceDetailsNFRegex from './pages/NFRegex/ServiceDetails';
|
||||||
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
import ServiceDetailsProxyRegex from './pages/RegexProxy/ServiceDetails';
|
||||||
|
import PortHijack from './pages/PortHijack';
|
||||||
|
|
||||||
const socket = io({transports: ["websocket", "polling"], path:"/sock" });
|
const socket = io({transports: ["websocket", "polling"], path:"/sock" });
|
||||||
|
|
||||||
@@ -153,6 +154,7 @@ function App() {
|
|||||||
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
<Route path="regexproxy" element={<RegexProxy><Outlet /></RegexProxy>} >
|
||||||
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
<Route path=":srv" element={<ServiceDetailsProxyRegex />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
<Route path="porthijack" element={<PortHijack />} />
|
||||||
<Route path="*" element={<HomeRedirector />} />
|
<Route path="*" element={<HomeRedirector />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
$primary_color: #242a33;
|
$primary_color: #242a33;
|
||||||
$second_color: #1A1B1E;
|
$secondary_color: #1A1B1E;
|
||||||
$third_color:#25262b;
|
$third_color:#25262b;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import React from 'react';
|
|||||||
|
|
||||||
import style from "./index.module.scss";
|
import style from "./index.module.scss";
|
||||||
|
|
||||||
|
|
||||||
function FooterPage() {
|
function FooterPage() {
|
||||||
return <Footer id="footer" height={70} className={style.footer}>
|
return <Footer id="footer" height={70} className={style.footer}>
|
||||||
Made by Pwnzer0tt1
|
Made by Pwnzer0tt1
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Autocomplete, AutocompleteItem } from '@mantine/core';
|
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/hooks';
|
import { useForm } from '@mantine/hooks';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { okNotify, regex_ipv4, regex_ipv6, getipinterfaces } from '../../js/utils';
|
import { okNotify, regex_ipv4, regex_ipv6 } from '../../js/utils';
|
||||||
import { ImCross } from "react-icons/im"
|
import { ImCross } from "react-icons/im"
|
||||||
import { nfregex } from './utils';
|
import { nfregex } from './utils';
|
||||||
|
import PortAndInterface from '../PortAndInterface';
|
||||||
|
|
||||||
type ServiceAddForm = {
|
type ServiceAddForm = {
|
||||||
name:string,
|
name:string,
|
||||||
@@ -13,16 +14,6 @@ type ServiceAddForm = {
|
|||||||
autostart: boolean,
|
autostart: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ItemProps extends AutocompleteItem {
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
|
||||||
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
|
|
||||||
( <b>{label}</b> ) -{">"} <b>{value}</b>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
|
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
@@ -41,14 +32,6 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
|
||||||
|
|
||||||
useEffect(()=>{
|
|
||||||
getipinterfaces().then(data => {
|
|
||||||
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
|
|
||||||
})
|
|
||||||
},[])
|
|
||||||
|
|
||||||
const close = () =>{
|
const close = () =>{
|
||||||
onClose()
|
onClose()
|
||||||
form.reset()
|
form.reset()
|
||||||
@@ -85,28 +68,9 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
{...form.getInputProps('name')}
|
{...form.getInputProps('name')}
|
||||||
/>
|
/>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
<PortAndInterface form={form} int_name="ip_int" port_name="port" label={"Public IP Interface and port (ipv4/ipv6 + CIDR allowed)"} />
|
||||||
<Autocomplete
|
|
||||||
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
|
|
||||||
placeholder="10.1.1.0/24"
|
|
||||||
itemComponent={AutoCompleteItem}
|
|
||||||
data={ipInterfaces}
|
|
||||||
{...form.getInputProps('ip_int')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<NumberInput
|
|
||||||
placeholder="8080"
|
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
label="Public Service port"
|
|
||||||
{...form.getInputProps('port')}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Space h="md" />
|
|
||||||
|
|
||||||
|
|
||||||
<div className='center-flex'>
|
<div className='center-flex'>
|
||||||
<Switch
|
<Switch
|
||||||
label="Auto-Start Service"
|
label="Auto-Start Service"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export default function NavBar({ closeNav, opened }: {closeNav: () => void, open
|
|||||||
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
|
<Navbar.Section grow component={ScrollArea} px="xs" mt="xs">
|
||||||
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} />
|
<NavBarButton navigate="nfregex" closeNav={closeNav} name="Netfilter Regex" color="blue" icon={<IoMdGitNetwork />} />
|
||||||
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="lime" icon={<MdTransform />} />
|
<NavBarButton navigate="regexproxy" closeNav={closeNav} name="TCP Proxy Regex Filter" color="lime" icon={<MdTransform />} />
|
||||||
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} disabled/>
|
<NavBarButton navigate="porthijack" closeNav={closeNav} name="Hijack Port to Proxy" color="red" icon={<GrDirections />} />
|
||||||
</Navbar.Section>
|
</Navbar.Section>
|
||||||
|
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|||||||
46
frontend/src/components/PortAndInterface.tsx
Normal file
46
frontend/src/components/PortAndInterface.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { Autocomplete, AutocompleteItem, Space, Title } from "@mantine/core"
|
||||||
|
import { UseForm } from "@mantine/hooks/lib/use-form/use-form";
|
||||||
|
import React, { useEffect, useState } from "react"
|
||||||
|
import { getipinterfaces } from "../js/utils";
|
||||||
|
import PortInput from "./PortInput";
|
||||||
|
|
||||||
|
|
||||||
|
interface ItemProps extends AutocompleteItem {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AutoCompleteItem = React.forwardRef<HTMLDivElement, ItemProps>(
|
||||||
|
({ label, value, ...props }: ItemProps, ref) => <div ref={ref} {...props}>
|
||||||
|
( <b>{label}</b> ) -{">"} <b>{value}</b>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export default function PortAndInterface({ form, int_name, port_name, label }:{ form:UseForm<any>, int_name:string, port_name:string, label?:string }) {
|
||||||
|
|
||||||
|
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
getipinterfaces().then(data => {
|
||||||
|
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
|
||||||
|
})
|
||||||
|
},[])
|
||||||
|
|
||||||
|
return <>
|
||||||
|
{label?<>
|
||||||
|
<Title order={6}>{label}</Title>
|
||||||
|
<Space h="xs" /></> :null}
|
||||||
|
|
||||||
|
<div className='center-flex' style={{width:"100%"}}>
|
||||||
|
<Autocomplete
|
||||||
|
placeholder="10.1.1.1"
|
||||||
|
itemComponent={AutoCompleteItem}
|
||||||
|
data={ipInterfaces}
|
||||||
|
{...form.getInputProps(int_name)}
|
||||||
|
style={{width:"100%"}}
|
||||||
|
/>
|
||||||
|
<Space w="sm" /><span style={{marginTop:"-3px", fontSize:"1.5em"}}>:</span><Space w="sm" />
|
||||||
|
<PortInput {...form.getInputProps(port_name)} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
113
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
113
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
import { Button, Group, Space, TextInput, Notification, Modal, Switch, SegmentedControl } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { okNotify, regex_ipv6_no_cidr, regex_ipv4_no_cidr } from '../../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
import { porthijack } from './utils';
|
||||||
|
import PortAndInterface from '../PortAndInterface';
|
||||||
|
|
||||||
|
type ServiceAddForm = {
|
||||||
|
name:string,
|
||||||
|
public_port:number,
|
||||||
|
proxy_port:number,
|
||||||
|
proto:string,
|
||||||
|
ip_src:string,
|
||||||
|
ip_dst:string,
|
||||||
|
autostart: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void }) {
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
name:"",
|
||||||
|
public_port:80,
|
||||||
|
proxy_port:8080,
|
||||||
|
proto:"tcp",
|
||||||
|
ip_src:"",
|
||||||
|
ip_dst:"127.0.0.1",
|
||||||
|
autostart: false,
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
name: (value) => value !== ""?true:false,
|
||||||
|
public_port: (value) => value>0 && value<65536,
|
||||||
|
proxy_port: (value) => value>0 && value<65536,
|
||||||
|
proto: (value) => ["tcp","udp"].includes(value),
|
||||||
|
ip_src: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false,
|
||||||
|
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () =>{
|
||||||
|
onClose()
|
||||||
|
form.reset()
|
||||||
|
setError(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
|
const submitRequest = ({ name, proxy_port, public_port, autostart, proto, ip_src, ip_dst }:ServiceAddForm) =>{
|
||||||
|
setSubmitLoading(true)
|
||||||
|
porthijack.servicesadd({name, proxy_port, public_port, proto, ip_src, ip_dst }).then( res => {
|
||||||
|
if (res.status === "ok" && res.service_id){
|
||||||
|
setSubmitLoading(false)
|
||||||
|
close();
|
||||||
|
if (autostart) porthijack.servicestart(res.service_id)
|
||||||
|
okNotify(`Service ${name} has been added`, `Successfully added service from port ${public_port} to ${proxy_port}`)
|
||||||
|
}else{
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Invalid request! [ "+res.status+" ]")
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Request Failed! [ "+err+" ]")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <Modal size="xl" title="Add a new service" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<TextInput
|
||||||
|
label="Service name"
|
||||||
|
placeholder="Challenge 01"
|
||||||
|
{...form.getInputProps('name')}
|
||||||
|
/>
|
||||||
|
<Space h="md" />
|
||||||
|
<PortAndInterface form={form} int_name="ip_src" port_name="public_port" label="Public IP Address and port (ipv4/ipv6)" />
|
||||||
|
<Space h="md" />
|
||||||
|
<PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" label="Proxy/Internal IP Address and port (ipv4/ipv6)" />
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<div className='center-flex'>
|
||||||
|
<Switch
|
||||||
|
label="Auto-Start Service"
|
||||||
|
{...form.getInputProps('autostart', { type: 'checkbox' })}
|
||||||
|
/>
|
||||||
|
<div className="flex-spacer"></div>
|
||||||
|
<SegmentedControl
|
||||||
|
data={[
|
||||||
|
{ label: 'TCP', value: 'tcp' },
|
||||||
|
{ label: 'UDP', value: 'udp' },
|
||||||
|
]}
|
||||||
|
{...form.getInputProps('proto')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={submitLoading} type="submit">Add Service</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddNewService;
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { Button, Group, Space, Notification, Modal } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { okNotify, regex_ipv4_no_cidr, regex_ipv6_no_cidr } from '../../../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
import { porthijack, Service } from '../utils';
|
||||||
|
import PortAndInterface from '../../PortAndInterface';
|
||||||
|
|
||||||
|
function ChangeDestination({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
ip_dst:service.ip_dst,
|
||||||
|
proxy_port:service.proxy_port
|
||||||
|
},
|
||||||
|
validationRules:{
|
||||||
|
proxy_port: (value) => value>0 && value<65536,
|
||||||
|
ip_dst: (value) => value.match(regex_ipv6_no_cidr)?true:false || value.match(regex_ipv4_no_cidr)?true:false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () =>{
|
||||||
|
onClose()
|
||||||
|
form.reset()
|
||||||
|
setError(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => form.setValues({ip_dst:service.ip_dst, proxy_port: service.proxy_port}),[opened])
|
||||||
|
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
|
const submitRequest = ({ ip_dst, proxy_port }:{ ip_dst:string, proxy_port:number }) => {
|
||||||
|
setSubmitLoading(true)
|
||||||
|
porthijack.changedestination(service.service_id, ip_dst, proxy_port).then( res => {
|
||||||
|
if (res.status === "ok"){
|
||||||
|
setSubmitLoading(false)
|
||||||
|
close();
|
||||||
|
okNotify(`Service ${service.name} has changed destination in ${ ip_dst }:${ proxy_port }`, `Successfully changed destination of service on port ${service.public_port}`)
|
||||||
|
}else{
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError(res.status)
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Request Failed! [ "+err+" ]")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <Modal size="xl" title={`Change destination of '${service.name}' [${service.ip_src}]:${service.public_port}`} opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
|
||||||
|
<PortAndInterface form={form} int_name="ip_dst" port_name="proxy_port" />
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={submitLoading} type="submit">Change</Button>
|
||||||
|
</Group>
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChangeDestination;
|
||||||
68
frontend/src/components/PortHijack/ServiceRow/RenameForm.tsx
Normal file
68
frontend/src/components/PortHijack/ServiceRow/RenameForm.tsx
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { Button, Group, Space, TextInput, Notification, Modal } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { okNotify } from '../../../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
import { porthijack, Service } from '../utils';
|
||||||
|
|
||||||
|
function RenameForm({ opened, onClose, service }:{ opened:boolean, onClose:()=>void, service:Service }) {
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: { name:service.name },
|
||||||
|
validationRules:{ name: (value) => value !== "" }
|
||||||
|
})
|
||||||
|
|
||||||
|
const close = () =>{
|
||||||
|
onClose()
|
||||||
|
form.reset()
|
||||||
|
setError(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(()=> form.setFieldValue("name", service.name),[opened])
|
||||||
|
|
||||||
|
const [submitLoading, setSubmitLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string|null>(null)
|
||||||
|
|
||||||
|
const submitRequest = ({ name }:{ name:string }) => {
|
||||||
|
setSubmitLoading(true)
|
||||||
|
porthijack.servicerename(service.service_id, name).then( res => {
|
||||||
|
if (!res){
|
||||||
|
setSubmitLoading(false)
|
||||||
|
close();
|
||||||
|
okNotify(`Service ${service.name} has been renamed in ${ name }`, `Successfully renamed service on port ${service.public_port}`)
|
||||||
|
}else{
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Error: [ "+res+" ]")
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
setError("Request Failed! [ "+err+" ]")
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return <Modal size="xl" title={`Rename '${service.name}' service on port ${service.public_port}`} opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||||
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
<TextInput
|
||||||
|
label="Service Name"
|
||||||
|
placeholder="Awesome Service Name!"
|
||||||
|
{...form.getInputProps('name')}
|
||||||
|
/>
|
||||||
|
<Group position="right" mt="md">
|
||||||
|
<Button loading={submitLoading} type="submit">Rename</Button>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
{error?<>
|
||||||
|
<Notification icon={<ImCross size={14} />} color="red" onClose={()=>{setError(null)}}>
|
||||||
|
Error: {error}
|
||||||
|
</Notification><Space h="md" /></>:null}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RenameForm;
|
||||||
26
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
26
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
|
||||||
|
@use "../../../index.scss" as *;
|
||||||
|
|
||||||
|
.row{
|
||||||
|
width: 95%;
|
||||||
|
padding: 15px 0px;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
@extend .center-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name{
|
||||||
|
font-size: 2.3em;
|
||||||
|
font-weight: bolder;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-bottom: 13px;
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portInput *{
|
||||||
|
color: #FFF;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-top: -1px;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
187
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
187
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
@@ -0,0 +1,187 @@
|
|||||||
|
import { ActionIcon, Badge, Divider, Menu, Space, Title, Tooltip } from '@mantine/core';
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { FaPlay, FaStop } from 'react-icons/fa';
|
||||||
|
import { porthijack, Service } from '../utils';
|
||||||
|
import style from "./index.module.scss";
|
||||||
|
import YesNoModal from '../../YesNoModal';
|
||||||
|
import { errorNotify, okNotify } from '../../../js/utils';
|
||||||
|
import { BsArrowRepeat, BsTrashFill } from 'react-icons/bs';
|
||||||
|
import { BiRename } from 'react-icons/bi'
|
||||||
|
import RenameForm from './RenameForm';
|
||||||
|
import ChangeDestination from './ChangeDestination';
|
||||||
|
import PortInput from '../../PortInput';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
|
||||||
|
function ServiceRow({ service }:{ service:Service }) {
|
||||||
|
|
||||||
|
let status_color = service.active ? "teal": "red"
|
||||||
|
|
||||||
|
const [buttonLoading, setButtonLoading] = useState(false)
|
||||||
|
const [tooltipStopOpened, setTooltipStopOpened] = useState(false);
|
||||||
|
const [deleteModal, setDeleteModal] = useState(false)
|
||||||
|
const [renameModal, setRenameModal] = useState(false)
|
||||||
|
const [changeDestModal, setChangeDestModal] = useState(false)
|
||||||
|
const portInputRef = React.createRef<HTMLInputElement>()
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: { proxy_port:service.proxy_port },
|
||||||
|
validationRules:{ proxy_port: (value) => value > 0 && value < 65536 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const onChangeProxyPort = ({proxy_port}:{proxy_port:number}) => {
|
||||||
|
if (proxy_port === service.proxy_port) return
|
||||||
|
if (proxy_port > 0 && proxy_port < 65536 && proxy_port !== service.public_port){
|
||||||
|
porthijack.changedestination(service.service_id, service.ip_dst, proxy_port).then( res => {
|
||||||
|
if (res.status === "ok"){
|
||||||
|
okNotify(`Service ${service.name} destination port has changed in ${ proxy_port }`, `Successfully changed destination port`)
|
||||||
|
}else{
|
||||||
|
errorNotify(`Error while changing the destination port of ${service.name}`,`Error: ${res.status}`)
|
||||||
|
}
|
||||||
|
}).catch( err => {
|
||||||
|
errorNotify("Request for changing port failed!",`Error: [ ${err} ]`)
|
||||||
|
})
|
||||||
|
}else{
|
||||||
|
form.setFieldValue("proxy_port", service.proxy_port)
|
||||||
|
errorNotify(`Error while changing the destination port of ${service.name}`,`Insert a valid port number`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopService = async () => {
|
||||||
|
setButtonLoading(true)
|
||||||
|
|
||||||
|
await porthijack.servicestop(service.service_id).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.public_port} has been stopped!`)
|
||||||
|
}else{
|
||||||
|
errorNotify(`An error as occurred during the stopping of the service ${service.public_port}`,`Error: ${res}`)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify(`An error as occurred during the stopping of the service ${service.public_port}`,`Error: ${err}`)
|
||||||
|
})
|
||||||
|
setButtonLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startService = async () => {
|
||||||
|
setButtonLoading(true)
|
||||||
|
await porthijack.servicestart(service.service_id).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify(`Service ${service.name} started successfully!`,`The service on ${service.public_port} has been started!`)
|
||||||
|
}else{
|
||||||
|
errorNotify(`An error as occurred during the starting of the service ${service.public_port}`,`Error: ${res}`)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify(`An error as occurred during the starting of the service ${service.public_port}`,`Error: ${err}`)
|
||||||
|
})
|
||||||
|
setButtonLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteService = () => {
|
||||||
|
porthijack.servicedelete(service.service_id).then(res => {
|
||||||
|
if (!res){
|
||||||
|
okNotify("Service delete complete!",`The service ${service.name} has been deleted!`)
|
||||||
|
}else
|
||||||
|
errorNotify("An error occurred while deleting a service",`Error: ${res}`)
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify("An error occurred while deleting a service",`Error: ${err}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<div className={style.row} style={{width:"100%"}}>
|
||||||
|
<Space w="xl" /><Space w="xl" />
|
||||||
|
<div>
|
||||||
|
<div className="center-flex-row">
|
||||||
|
<div className="center-flex"><Title order={4} className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.public_port}</Badge></div>
|
||||||
|
<div className="center-flex">
|
||||||
|
<Badge color={status_color} radius="sm" size="md" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
|
||||||
|
<Space w="sm" />
|
||||||
|
<Badge color={service.proto === "tcp"?"cyan":"orange"} radius="sm" size="md" variant="filled">
|
||||||
|
{service.proto}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
<div className="center-flex-row">
|
||||||
|
<Badge color="lime" radius="sm" size="md" variant="filled">
|
||||||
|
FROM {service.ip_src} : {service.public_port}
|
||||||
|
</Badge>
|
||||||
|
<Space h="sm" />
|
||||||
|
<Badge color="blue" radius="sm" size="md" variant="filled">
|
||||||
|
<div className="center-flex">
|
||||||
|
TO {service.ip_dst} :
|
||||||
|
<form onSubmit={form.onSubmit((v)=>portInputRef.current?.blur())}>
|
||||||
|
<PortInput
|
||||||
|
defaultValue={service.proxy_port}
|
||||||
|
size="xs"
|
||||||
|
variant="unstyled"
|
||||||
|
style={{
|
||||||
|
width: (6+form.values.proxy_port.toString().length*6.2) +"px"
|
||||||
|
}}
|
||||||
|
className={style.portInput}
|
||||||
|
onBlur={(e)=>{onChangeProxyPort({proxy_port:parseInt(e.target.value)})}}
|
||||||
|
ref={portInputRef}
|
||||||
|
{...form.getInputProps("proxy_port")}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space w="xl" /><Space w="xl" />
|
||||||
|
<div className="center-flex">
|
||||||
|
<Menu>
|
||||||
|
<Menu.Label><b>Rename service</b></Menu.Label>
|
||||||
|
<Menu.Item icon={<BiRename size={18} />} onClick={()=>setRenameModal(true)}>Change service name</Menu.Item>
|
||||||
|
<Menu.Label><b>Change destination</b></Menu.Label>
|
||||||
|
<Menu.Item icon={<BsArrowRepeat size={18} />} onClick={()=>setChangeDestModal(true)}>Change hijacking destination</Menu.Item>
|
||||||
|
<Divider />
|
||||||
|
<Menu.Label><b>Danger zone</b></Menu.Label>
|
||||||
|
<Menu.Item color="red" icon={<BsTrashFill size={18} />} onClick={()=>setDeleteModal(true)}>Delete Service</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
<Space w="md"/>
|
||||||
|
<Tooltip label="Stop service" zIndex={0} transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="red" opened={tooltipStopOpened} tooltipId="tooltip-stop-id">
|
||||||
|
<ActionIcon color="red" loading={buttonLoading}
|
||||||
|
onClick={stopService} size="xl" radius="md" variant="filled"
|
||||||
|
disabled={!service.active}
|
||||||
|
aria-describedby="tooltip-stop-id"
|
||||||
|
onFocus={() => setTooltipStopOpened(false)} onBlur={() => setTooltipStopOpened(false)}
|
||||||
|
onMouseEnter={() => setTooltipStopOpened(true)} onMouseLeave={() => setTooltipStopOpened(false)}>
|
||||||
|
<FaStop size="20px" />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
<Space w="md"/>
|
||||||
|
<Tooltip label="Start service" transition="pop" zIndex={0} transitionDuration={200} transitionTimingFunction="ease" color="teal">
|
||||||
|
<ActionIcon color="teal" size="xl" radius="md" onClick={startService} loading={buttonLoading}
|
||||||
|
variant="filled" disabled={service.active}>
|
||||||
|
<FaPlay size="20px" />
|
||||||
|
</ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<Space w="xl" /><Space w="xl" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<hr style={{width:"100%"}}/>
|
||||||
|
<YesNoModal
|
||||||
|
title='Are you sure to delete this service?'
|
||||||
|
description={`You are going to delete the service '${service.public_port}', causing the stopping of the firewall and deleting all the regex associated. This will cause the shutdown of your service! ⚠️`}
|
||||||
|
onClose={()=>setDeleteModal(false) }
|
||||||
|
action={deleteService}
|
||||||
|
opened={deleteModal}
|
||||||
|
/>
|
||||||
|
<RenameForm
|
||||||
|
onClose={()=>setRenameModal(false)}
|
||||||
|
opened={renameModal}
|
||||||
|
service={service}
|
||||||
|
/>
|
||||||
|
<ChangeDestination
|
||||||
|
onClose={()=>setChangeDestModal(false)}
|
||||||
|
opened={changeDestModal}
|
||||||
|
service={service}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceRow;
|
||||||
65
frontend/src/components/PortHijack/utils.ts
Executable file
65
frontend/src/components/PortHijack/utils.ts
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
import { ServerResponse } from "../../js/models"
|
||||||
|
import { getapi, postapi } from "../../js/utils"
|
||||||
|
|
||||||
|
export type GeneralStats = {
|
||||||
|
services:number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Service = {
|
||||||
|
name:string,
|
||||||
|
service_id:string,
|
||||||
|
active:boolean,
|
||||||
|
proto: string,
|
||||||
|
ip_src: string,
|
||||||
|
ip_dst: string,
|
||||||
|
proxy_port: number,
|
||||||
|
public_port: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceAddForm = {
|
||||||
|
name:string,
|
||||||
|
public_port:number,
|
||||||
|
proxy_port:number,
|
||||||
|
proto:string,
|
||||||
|
ip_src: string,
|
||||||
|
ip_dst: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceAddResponse = {
|
||||||
|
status: string,
|
||||||
|
service_id?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const porthijack = {
|
||||||
|
stats: async () => {
|
||||||
|
return await getapi("porthijack/stats") as GeneralStats;
|
||||||
|
},
|
||||||
|
services: async () => {
|
||||||
|
return await getapi("porthijack/services") as Service[];
|
||||||
|
},
|
||||||
|
serviceinfo: async (service_id:string) => {
|
||||||
|
return await getapi(`porthijack/service/${service_id}`) as Service;
|
||||||
|
},
|
||||||
|
servicestart: async (service_id:string) => {
|
||||||
|
const { status } = await getapi(`porthijack/service/${service_id}/start`) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
},
|
||||||
|
servicerename: async (service_id:string, name: string) => {
|
||||||
|
const { status } = await postapi(`porthijack/service/${service_id}/rename`,{ name }) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
},
|
||||||
|
servicestop: async (service_id:string) => {
|
||||||
|
const { status } = await getapi(`porthijack/service/${service_id}/stop`) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
},
|
||||||
|
servicesadd: async (data:ServiceAddForm) => {
|
||||||
|
return await postapi("porthijack/services/add",data) as ServiceAddResponse;
|
||||||
|
},
|
||||||
|
servicedelete: async (service_id:string) => {
|
||||||
|
const { status } = await getapi(`porthijack/service/${service_id}/delete`) as ServerResponse;
|
||||||
|
return status === "ok"?undefined:status
|
||||||
|
},
|
||||||
|
changedestination: async (service_id:string, ip_dst:string, proxy_port:number) => {
|
||||||
|
return await postapi(`porthijack/service/${service_id}/change-destination`, {proxy_port, ip_dst}) as ServerResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
36
frontend/src/components/PortInput.tsx
Normal file
36
frontend/src/components/PortInput.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { NumberInput, NumberInputProps } from "@mantine/core"
|
||||||
|
import React, { useState } from "react"
|
||||||
|
|
||||||
|
interface PortInputProps extends NumberInputProps {
|
||||||
|
fullWidth?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const PortInput = React.forwardRef<HTMLInputElement, PortInputProps>( (props, ref) => {
|
||||||
|
|
||||||
|
const [oldValue, setOldValue] = useState<string>(props.defaultValue?props.defaultValue.toString():"")
|
||||||
|
return <NumberInput
|
||||||
|
variant={props.variant?props.variant:"filled"}
|
||||||
|
hideControls
|
||||||
|
placeholder="80"
|
||||||
|
min={props.min?props.min:1}
|
||||||
|
max={props.max?props.min:65535}
|
||||||
|
style={props.fullWidth?props.style:{ width: "75px", ...props.style }}
|
||||||
|
onInput={(e) => {
|
||||||
|
const value = parseInt((e.target as HTMLInputElement).value)
|
||||||
|
if (value > 65535) {
|
||||||
|
(e.target as HTMLInputElement).value = oldValue
|
||||||
|
} else if (value < 1) {
|
||||||
|
(e.target as HTMLInputElement).value = oldValue
|
||||||
|
}else{
|
||||||
|
(e.target as HTMLInputElement).value = value.toString()
|
||||||
|
}
|
||||||
|
setOldValue((e.target as HTMLInputElement).value)
|
||||||
|
props.onInput?.(e)
|
||||||
|
}}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export default PortInput
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
|
import { Button, Group, Space, TextInput, Notification, Modal, Switch } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/hooks';
|
import { useForm } from '@mantine/hooks';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { okNotify } from '../../js/utils';
|
import { okNotify } from '../../js/utils';
|
||||||
import { ImCross } from "react-icons/im"
|
import { ImCross } from "react-icons/im"
|
||||||
import { regexproxy } from './utils';
|
import { regexproxy } from './utils';
|
||||||
|
import PortInput from '../PortInput';
|
||||||
|
|
||||||
type ServiceAddForm = {
|
type ServiceAddForm = {
|
||||||
name:string,
|
name:string,
|
||||||
@@ -67,20 +68,16 @@ function AddNewService({ opened, onClose }:{ opened:boolean, onClose:()=>void })
|
|||||||
/>
|
/>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
|
|
||||||
<NumberInput
|
<PortInput
|
||||||
placeholder="8080"
|
fullWidth
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
label="Public Service port"
|
label="Public Service port"
|
||||||
{...form.getInputProps('port')}
|
{...form.getInputProps('port')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{form.values.chosenInternalPort?<>
|
{form.values.chosenInternalPort?<>
|
||||||
<Space h="md" />
|
<Space h="md" />
|
||||||
<NumberInput
|
<PortInput
|
||||||
placeholder="8080"
|
fullWidth
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
label="Internal Proxy Port"
|
label="Internal Proxy Port"
|
||||||
{...form.getInputProps('internalPort')}
|
{...form.getInputProps('internalPort')}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Button, Group, NumberInput, Space, Notification, Modal, Center, Title } from '@mantine/core';
|
import { Button, Group, Space, Notification, Modal, Center, Title } from '@mantine/core';
|
||||||
import { useForm } from '@mantine/hooks';
|
import { useForm } from '@mantine/hooks';
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { ImCross } from "react-icons/im"
|
import { ImCross } from "react-icons/im"
|
||||||
import { FaLongArrowAltDown } from 'react-icons/fa';
|
import { FaLongArrowAltDown } from 'react-icons/fa';
|
||||||
import { regexproxy, Service } from '../utils';
|
import { regexproxy, Service } from '../utils';
|
||||||
import { okNotify } from '../../../js/utils';
|
import { okNotify } from '../../../js/utils';
|
||||||
|
import PortInput from '../../PortInput';
|
||||||
|
|
||||||
type InputForm = {
|
type InputForm = {
|
||||||
internalPort:number,
|
internalPort:number,
|
||||||
@@ -58,27 +59,21 @@ function ChangePortModal({ service, opened, onClose }:{ service:Service, opened:
|
|||||||
return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
return <Modal size="xl" title="Change Ports" opened={opened} onClose={close} closeOnClickOutside={false} centered>
|
||||||
<form onSubmit={form.onSubmit(submitRequest)}>
|
<form onSubmit={form.onSubmit(submitRequest)}>
|
||||||
|
|
||||||
|
<PortInput
|
||||||
|
fullWidth
|
||||||
<NumberInput
|
|
||||||
placeholder="30001"
|
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
label="Internal Proxy Port"
|
label="Internal Proxy Port"
|
||||||
{...form.getInputProps('internalPort')}
|
{...form.getInputProps('internalPort')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
<Center><FaLongArrowAltDown size={50}/></Center>
|
<Center><FaLongArrowAltDown size={50}/></Center>
|
||||||
|
|
||||||
<NumberInput
|
<PortInput
|
||||||
placeholder="8080"
|
fullWidth
|
||||||
min={1}
|
|
||||||
max={65535}
|
|
||||||
label="Public Service Port"
|
label="Public Service Port"
|
||||||
{...form.getInputProps('port')}
|
{...form.getInputProps('port')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Space h="xl" />
|
<Space h="xl" />
|
||||||
|
|
||||||
<Center><Title order={5}>The change of the ports will cause a temporarily shutdown of the service! ⚠️</Title></Center>
|
<Center><Title order={5}>The change of the ports will cause a temporarily shutdown of the service! ⚠️</Title></Center>
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ var Buffer = require('buffer').Buffer
|
|||||||
export const eventUpdateName = "update-info"
|
export const eventUpdateName = "update-info"
|
||||||
|
|
||||||
export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
|
export const regex_ipv6 = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$";
|
||||||
export const regex_ipv4 = "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$"
|
export const regex_ipv6_no_cidr = "^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*$";
|
||||||
|
export const regex_ipv4 = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(3[0-2]|[1-2][0-9]|[0-9]))?$"
|
||||||
|
export const regex_ipv4_no_cidr = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
|
||||||
|
|
||||||
export async function getapi(path:string):Promise<any>{
|
export async function getapi(path:string):Promise<any>{
|
||||||
|
|
||||||
|
|||||||
73
frontend/src/pages/PortHijack/index.tsx
Executable file
73
frontend/src/pages/PortHijack/index.tsx
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { BsPlusLg } from "react-icons/bs";
|
||||||
|
import ServiceRow from '../../components/PortHijack/ServiceRow';
|
||||||
|
import { GeneralStats, porthijack, Service } from '../../components/PortHijack/utils';
|
||||||
|
import { errorNotify, eventUpdateName, fireUpdateRequest } from '../../js/utils';
|
||||||
|
import AddNewService from '../../components/PortHijack/AddNewService';
|
||||||
|
import { useWindowEvent } from '@mantine/hooks';
|
||||||
|
|
||||||
|
|
||||||
|
function PortHijack() {
|
||||||
|
|
||||||
|
const [services, setServices] = useState<Service[]>([]);
|
||||||
|
const [loader, setLoader] = useState(true);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [tooltipAddServOpened, setTooltipAddServOpened] = useState(false);
|
||||||
|
const [tooltipAddOpened, setTooltipAddOpened] = useState(false);
|
||||||
|
|
||||||
|
const [generalStats, setGeneralStats] = useState<GeneralStats>({services:0});
|
||||||
|
const updateInfo = async () => {
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
porthijack.stats().then(res => {
|
||||||
|
setGeneralStats(res)
|
||||||
|
}).catch(
|
||||||
|
err => errorNotify("General Info Auto-Update failed!", err.toString())
|
||||||
|
),
|
||||||
|
porthijack.services().then(res => {
|
||||||
|
setServices(res)
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify("Home Page Auto-Update failed!", err.toString())
|
||||||
|
})
|
||||||
|
])
|
||||||
|
setLoader(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
useWindowEvent(eventUpdateName, updateInfo)
|
||||||
|
useEffect(fireUpdateRequest,[])
|
||||||
|
|
||||||
|
const closeModal = () => {setOpen(false);}
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<Space h="sm" />
|
||||||
|
<div className='center-flex'>
|
||||||
|
<Title order={4}>Hijack port to proxy</Title>
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
<Badge size="sm" color="yellow" variant="filled">Services: {generalStats.services}</Badge>
|
||||||
|
<Space w="xs" />
|
||||||
|
<Tooltip label="Add a new service" position='bottom' transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="blue" opened={tooltipAddOpened}>
|
||||||
|
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="lg" radius="md" variant="filled"
|
||||||
|
onFocus={() => setTooltipAddOpened(false)} onBlur={() => setTooltipAddOpened(false)}
|
||||||
|
onMouseEnter={() => setTooltipAddOpened(true)} onMouseLeave={() => setTooltipAddOpened(false)}><BsPlusLg size={18} /></ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
<div id="service-list" className="center-flex-row">
|
||||||
|
<LoadingOverlay visible={loader} />
|
||||||
|
{services.length > 0?services.map( srv => <ServiceRow service={srv} key={srv.service_id} />):<><Space h="xl"/> <Title className='center-flex' align='center' order={3}>No services found! Add one clicking the "+" buttons</Title>
|
||||||
|
<Space h="xl" /> <Space h="xl" /> <Space h="xl" /> <Space h="xl" />
|
||||||
|
<div className='center-flex'>
|
||||||
|
<Tooltip label="Add a new service" transition="pop" transitionDuration={200} transitionTimingFunction="ease" color="blue" opened={tooltipAddServOpened}>
|
||||||
|
<ActionIcon color="blue" onClick={()=>setOpen(true)} size="xl" radius="md" variant="filled"
|
||||||
|
onFocus={() => setTooltipAddServOpened(false)} onBlur={() => setTooltipAddServOpened(false)}
|
||||||
|
onMouseEnter={() => setTooltipAddServOpened(true)} onMouseLeave={() => setTooltipAddServOpened(false)}><BsPlusLg size="20px" /></ActionIcon>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</>}
|
||||||
|
<AddNewService opened={open} onClose={closeModal} />
|
||||||
|
</div>
|
||||||
|
<AddNewService opened={open} onClose={closeModal} />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PortHijack;
|
||||||
15
start.py
15
start.py
@@ -27,12 +27,16 @@ parser.add_argument('--no-autostart', "-n", required=False, action="store_true",
|
|||||||
parser.add_argument('--keep','-k', required=False, action="store_true", help='Keep the docker-compose file generated', default=False)
|
parser.add_argument('--keep','-k', required=False, action="store_true", help='Keep the docker-compose file generated', default=False)
|
||||||
parser.add_argument('--build', "-b", required=False, action="store_true", help='Build the container locally', default=False)
|
parser.add_argument('--build', "-b", required=False, action="store_true", help='Build the container locally', default=False)
|
||||||
parser.add_argument('--stop', '-s', required=False, action="store_true", help='Stop firegex execution', default=False)
|
parser.add_argument('--stop', '-s', required=False, action="store_true", help='Stop firegex execution', default=False)
|
||||||
|
parser.add_argument('--restart', '-r', required=False, action="store_true", help='Restart firegex', default=False)
|
||||||
parser.add_argument('--psw-no-interactive',type=str, required=False, help='Password for no-interactive mode', default=None)
|
parser.add_argument('--psw-no-interactive',type=str, required=False, help='Password for no-interactive mode', default=None)
|
||||||
parser.add_argument('--startup-psw', required=False, action="store_true", help='Insert password in the startup screen of firegex', default=False)
|
parser.add_argument('--startup-psw','-P', required=False, action="store_true", help='Insert password in the startup screen of firegex', default=False)
|
||||||
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
os.chdir(os.path.dirname(os.path.realpath(__file__)))
|
||||||
|
|
||||||
|
start_operation = not (args.stop or args.restart)
|
||||||
|
|
||||||
if args.build and not os.path.isfile("./Dockerfile"):
|
if args.build and not os.path.isfile("./Dockerfile"):
|
||||||
puts("This is not a clone of firegex, to build firegex the clone of the repository is needed!", color=colors.red)
|
puts("This is not a clone of firegex, to build firegex the clone of the repository is needed!", color=colors.red)
|
||||||
exit()
|
exit()
|
||||||
@@ -40,14 +44,14 @@ if args.build and not os.path.isfile("./Dockerfile"):
|
|||||||
if args.threads < 1:
|
if args.threads < 1:
|
||||||
args.threads = multiprocessing.cpu_count()
|
args.threads = multiprocessing.cpu_count()
|
||||||
|
|
||||||
if not args.stop:
|
if start_operation:
|
||||||
sep()
|
sep()
|
||||||
puts(f"Firegex", color=colors.yellow, end="")
|
puts(f"Firegex", color=colors.yellow, end="")
|
||||||
puts(" will start on port ", end="")
|
puts(" will start on port ", end="")
|
||||||
puts(f"{args.port}", color=colors.cyan)
|
puts(f"{args.port}", color=colors.cyan)
|
||||||
|
|
||||||
psw_set = None
|
psw_set = None
|
||||||
if not args.stop:
|
if start_operation:
|
||||||
if args.psw_no_interactive:
|
if args.psw_no_interactive:
|
||||||
psw_set = args.psw_no_interactive
|
psw_set = args.psw_no_interactive
|
||||||
elif not args.startup_psw:
|
elif not args.startup_psw:
|
||||||
@@ -100,7 +104,10 @@ services:
|
|||||||
sep()
|
sep()
|
||||||
if not args.no_autostart:
|
if not args.no_autostart:
|
||||||
try:
|
try:
|
||||||
if args.stop:
|
if args.restart:
|
||||||
|
puts("Running 'docker-compose restart'\n", color=colors.green)
|
||||||
|
os.system("docker-compose -p firegex restart")
|
||||||
|
elif args.stop:
|
||||||
puts("Running 'docker-compose down'\n", color=colors.green)
|
puts("Running 'docker-compose down'\n", color=colors.green)
|
||||||
os.system("docker-compose -p firegex down")
|
os.system("docker-compose -p firegex down")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colo
|
|||||||
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
||||||
|
|
||||||
#Create server
|
#Create server
|
||||||
server = TcpServer(args.port,ipv6=args.ipv6) if args.proto == "tcp" else UdpServer(args.port,ipv6=args.ipv6)
|
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port,ipv6=args.ipv6)
|
||||||
|
|
||||||
def exit_test(code):
|
def exit_test(code):
|
||||||
if service_id:
|
if service_id:
|
||||||
server.stop()
|
server.stop()
|
||||||
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
if(firegex.nf_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
||||||
else: puts(f"Test Failed: Coulnd't deleted serivce ✗", color=colors.red); exit_test(1)
|
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
||||||
exit(code)
|
exit(code)
|
||||||
|
|
||||||
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" )
|
service_id = firegex.nf_add_service(args.service_name, args.port, args.proto , "::1" if args.ipv6 else "127.0.0.1" )
|
||||||
|
|||||||
99
tests/ph_test.py
Executable file
99
tests/ph_test.py
Executable file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from utils.colors import *
|
||||||
|
from utils.firegexapi import *
|
||||||
|
from utils.tcpserver import TcpServer
|
||||||
|
from utils.udpserver import UdpServer
|
||||||
|
import argparse, secrets, base64,time
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--address", "-a", type=str , required=False, help='Address of firegex backend', default="http://127.0.0.1:4444/")
|
||||||
|
parser.add_argument("--password", "-p", type=str, required=True, help='Firegex password')
|
||||||
|
parser.add_argument("--service_name", "-n", type=str , required=False, help='Name of the test service', default="Test Service")
|
||||||
|
parser.add_argument("--port", "-P", type=int , required=False, help='Port of the test service', default=1337)
|
||||||
|
parser.add_argument("--ipv6", "-6" , action="store_true", help='Test Ipv6', default=False)
|
||||||
|
parser.add_argument("--proto", "-m" , type=str, required=False, choices=["tcp","udp"], help='Select the protocol', default="tcp")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
sep()
|
||||||
|
puts(f"Testing will start on ", color=colors.cyan, end="")
|
||||||
|
puts(f"{args.address}", color=colors.yellow)
|
||||||
|
|
||||||
|
firegex = FiregexAPI(args.address)
|
||||||
|
|
||||||
|
#Login
|
||||||
|
if (firegex.login(args.password)): puts(f"Sucessfully logged in ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Unknown response or wrong passowrd ✗", color=colors.red); exit(1)
|
||||||
|
|
||||||
|
#Create server
|
||||||
|
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+1,ipv6=args.ipv6,proxy_port=args.port)
|
||||||
|
|
||||||
|
def exit_test(code):
|
||||||
|
if service_id:
|
||||||
|
server.stop()
|
||||||
|
if(firegex.ph_delete_service(service_id)): puts(f"Sucessfully deleted service ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Coulnd't delete serivce ✗", color=colors.red); exit_test(1)
|
||||||
|
exit(code)
|
||||||
|
|
||||||
|
#Create and start serivce
|
||||||
|
service_id = firegex.ph_add_service(args.service_name, args.port, args.port+1, args.proto , "::1" if args.ipv6 else "127.0.0.1", "::1" if args.ipv6 else "127.0.0.1")
|
||||||
|
if service_id: puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Failed to create service ✗", color=colors.red); exit(1)
|
||||||
|
|
||||||
|
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Failed to start service ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
|
server.start()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
#Check if it started
|
||||||
|
def checkData(should_work):
|
||||||
|
res = None
|
||||||
|
try: res = server.sendCheckData(secrets.token_bytes(432))
|
||||||
|
except ConnectionRefusedError: res = False
|
||||||
|
if res:
|
||||||
|
if should_work: puts(f"Successfully received data ✔", color=colors.green)
|
||||||
|
else: puts("Test Failed: Connection wasn't blocked ✗", color=colors.red); exit_test(1)
|
||||||
|
else:
|
||||||
|
if should_work: puts(f"Test Failed: Data wans't received ✗", color=colors.red); exit_test(1)
|
||||||
|
else: puts(f"Successfully blcoked connection ✔", color=colors.green)
|
||||||
|
|
||||||
|
checkData(True)
|
||||||
|
|
||||||
|
#Pause the proxy
|
||||||
|
if(firegex.ph_stop_service(service_id)): puts(f"Sucessfully paused service with id {service_id} ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Coulnd't pause the service ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
|
checkData(False)
|
||||||
|
|
||||||
|
#Start firewall
|
||||||
|
if(firegex.ph_start_service(service_id)): puts(f"Sucessfully started service with id {service_id} ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Coulnd't start the service ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
|
checkData(True)
|
||||||
|
|
||||||
|
#Change port
|
||||||
|
if(firegex.ph_change_destination(service_id, "::1" if args.ipv6 else "127.0.0.1", args.port+2)):
|
||||||
|
puts(f"Sucessfully changed port ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Coulnd't change destination ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
|
checkData(False)
|
||||||
|
|
||||||
|
server.stop()
|
||||||
|
server = (TcpServer if args.proto == "tcp" else UdpServer)(args.port+2,ipv6=args.ipv6,proxy_port=args.port)
|
||||||
|
server.start()
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
checkData(True)
|
||||||
|
|
||||||
|
#Rename service
|
||||||
|
if(firegex.ph_rename_service(service_id,f"{args.service_name}2")): puts(f"Sucessfully renamed service to {args.service_name}2 ✔", color=colors.green)
|
||||||
|
else: puts(f"Test Failed: Coulnd't rename service ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
|
#Check if service was renamed correctly
|
||||||
|
for services in firegex.ph_get_services():
|
||||||
|
if services["name"] == f"{args.service_name}2":
|
||||||
|
puts(f"Checked that service was renamed correctly ✔", color=colors.green)
|
||||||
|
exit_test(0)
|
||||||
|
|
||||||
|
puts(f"Test Failed: Service wasn't renamed correctly ✗", color=colors.red); exit_test(1)
|
||||||
|
exit_test(1)
|
||||||
@@ -82,6 +82,7 @@ def checkRegex(regex, should_work=True, upper=False):
|
|||||||
if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
|
if not server.sendCheckData(secrets.token_bytes(200) + s + secrets.token_bytes(200)):
|
||||||
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
|
puts(f"The malicious request was successfully blocked ✔", color=colors.green)
|
||||||
n_blocked += 1
|
n_blocked += 1
|
||||||
|
time.sleep(0.5)
|
||||||
if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked:
|
if firegex.px_get_regex(r["id"])["n_packets"] == n_blocked:
|
||||||
puts(f"The packed was reported as blocked ✔", color=colors.green)
|
puts(f"The packed was reported as blocked ✔", color=colors.green)
|
||||||
else:
|
else:
|
||||||
@@ -245,4 +246,4 @@ new_internal_port = firegex.px_get_service(service_id)["internal_port"]
|
|||||||
if (internal_port != new_internal_port): puts(f"Sucessfully got regenerated port {new_internal_port} ✔", color=colors.green)
|
if (internal_port != new_internal_port): puts(f"Sucessfully got regenerated port {new_internal_port} ✔", color=colors.green)
|
||||||
else: puts(f"Test Failed: Coundn't get internal port, or it was the same as previous ✗", color=colors.red); exit_test(1)
|
else: puts(f"Test Failed: Coundn't get internal port, or it was the same as previous ✗", color=colors.red); exit_test(1)
|
||||||
|
|
||||||
exit_test(0)
|
exit_test(0)
|
||||||
|
|||||||
@@ -11,4 +11,12 @@ python3 nf_test.py -p testpassword -m udp
|
|||||||
echo "Running Netfilter Regex UDP ipv6"
|
echo "Running Netfilter Regex UDP ipv6"
|
||||||
python3 nf_test.py -p testpassword -m udp -6
|
python3 nf_test.py -p testpassword -m udp -6
|
||||||
echo "Running Proxy Regex"
|
echo "Running Proxy Regex"
|
||||||
python3 px_test.py -p testpassword
|
python3 px_test.py -p testpassword
|
||||||
|
echo "Running Port Hijack TCP ipv4"
|
||||||
|
python3 ph_test.py -p testpassword -m tcp
|
||||||
|
echo "Running Port Hijack TCP ipv6"
|
||||||
|
python3 ph_test.py -p testpassword -m tcp -6
|
||||||
|
echo "Running Port Hijack UDP ipv4"
|
||||||
|
python3 ph_test.py -p testpassword -m udp
|
||||||
|
echo "Running Port Hijack UDP ipv6"
|
||||||
|
python3 ph_test.py -p testpassword -m udp -6
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import string
|
||||||
from requests import Session
|
from requests import Session
|
||||||
|
|
||||||
def verify(req):
|
def verify(req):
|
||||||
@@ -36,7 +37,7 @@ class FiregexAPI:
|
|||||||
def status(self):
|
def status(self):
|
||||||
return self.s.get(f"{self.address}api/status").json()
|
return self.s.get(f"{self.address}api/status").json()
|
||||||
|
|
||||||
def login(self,password):
|
def login(self,password: str):
|
||||||
req = self.s.post(f"{self.address}api/login", data=f"username=login&password={password}")
|
req = self.s.post(f"{self.address}api/login", data=f"username=login&password={password}")
|
||||||
try :
|
try :
|
||||||
self.s.set_token(req.json()["access_token"])
|
self.s.set_token(req.json()["access_token"])
|
||||||
@@ -48,7 +49,7 @@ class FiregexAPI:
|
|||||||
self.s.unset_token()
|
self.s.unset_token()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def set_password(self,password):
|
def set_password(self,password: str):
|
||||||
req = self.s.post(f"{self.address}api/set-password", json={"password":password})
|
req = self.s.post(f"{self.address}api/set-password", json={"password":password})
|
||||||
if verify(req):
|
if verify(req):
|
||||||
self.s.set_token(req.json()["access_token"])
|
self.s.set_token(req.json()["access_token"])
|
||||||
@@ -56,7 +57,7 @@ class FiregexAPI:
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def change_password(self,password,expire):
|
def change_password(self, password: str, expire: bool):
|
||||||
req = self.s.post(f"{self.address}api/change-password", json={"password":password, "expire":expire})
|
req = self.s.post(f"{self.address}api/change-password", json={"password":password, "expire":expire})
|
||||||
if verify(req):
|
if verify(req):
|
||||||
self.s.set_token(req.json()["access_token"])
|
self.s.set_token(req.json()["access_token"])
|
||||||
@@ -68,11 +69,11 @@ class FiregexAPI:
|
|||||||
req = self.s.get(f"{self.address}api/interfaces")
|
req = self.s.get(f"{self.address}api/interfaces")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def reset(self, delete):
|
def reset(self, delete: bool):
|
||||||
req = self.s.post(f"{self.address}api/reset", json={"delete":delete})
|
req = self.s.post(f"{self.address}api/reset", json={"delete":delete})
|
||||||
|
|
||||||
#Netfilter regex
|
#Netfilter regex
|
||||||
def nf_get_stats():
|
def nf_get_stats(self):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/stats")
|
req = self.s.get(f"{self.address}api/nfregex/stats")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
@@ -80,43 +81,43 @@ class FiregexAPI:
|
|||||||
req = self.s.get(f"{self.address}api/nfregex/services")
|
req = self.s.get(f"{self.address}api/nfregex/services")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def nf_get_service(self,service_id):
|
def nf_get_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}")
|
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def nf_stop_service(self,service_id):
|
def nf_stop_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/stop")
|
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/stop")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_start_service(self,service_id):
|
def nf_start_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/start")
|
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/start")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_delete_service(self,service_id):
|
def nf_delete_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/delete")
|
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/delete")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_rename_service(self,service_id,newname):
|
def nf_rename_service(self,service_id: str, newname: str):
|
||||||
req = self.s.post(f"{self.address}api/nfregex/service/{service_id}/rename" , json={"name":newname})
|
req = self.s.post(f"{self.address}api/nfregex/service/{service_id}/rename" , json={"name":newname})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_get_service_regexes(self,service_id):
|
def nf_get_service_regexes(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/regexes")
|
req = self.s.get(f"{self.address}api/nfregex/service/{service_id}/regexes")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def nf_get_regex(self,regex_id):
|
def nf_get_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}")
|
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def nf_delete_regex(self,regex_id):
|
def nf_delete_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/delete")
|
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/delete")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_enable_regex(self,regex_id):
|
def nf_enable_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/enable")
|
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/enable")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_disable_regex(self,regex_id):
|
def nf_disable_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
|
req = self.s.get(f"{self.address}api/nfregex/regex/{regex_id}/disable")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
@@ -131,7 +132,7 @@ class FiregexAPI:
|
|||||||
return req.json()["service_id"] if verify(req) else False
|
return req.json()["service_id"] if verify(req) else False
|
||||||
|
|
||||||
#Proxy regex
|
#Proxy regex
|
||||||
def px_get_stats():
|
def px_get_stats(self):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/stats")
|
req = self.s.get(f"{self.address}api/regexproxy/stats")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
@@ -139,54 +140,54 @@ class FiregexAPI:
|
|||||||
req = self.s.get(f"{self.address}api/regexproxy/services")
|
req = self.s.get(f"{self.address}api/regexproxy/services")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def px_get_service(self,service_id):
|
def px_get_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def px_stop_service(self,service_id):
|
def px_stop_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/stop")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/stop")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_pause_service(self,service_id):
|
def px_pause_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/pause")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/pause")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_start_service(self,service_id):
|
def px_start_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/start")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/start")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_delete_service(self,service_id):
|
def px_delete_service(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/delete")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/delete")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_regen_service_port(self,service_id):
|
def px_regen_service_port(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regen-port")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regen-port")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_change_service_port(self,service_id, port=None, internalPort=None):
|
def px_change_service_port(self,service_id: str, port:int =None, internalPort:int =None):
|
||||||
payload = {}
|
payload = {}
|
||||||
if port: payload["port"] = port
|
if port: payload["port"] = port
|
||||||
if internalPort: payload["internalPort"] = internalPort
|
if internalPort: payload["internalPort"] = internalPort
|
||||||
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload)
|
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/change-ports", json=payload)
|
||||||
return req.json() if verify(req) else False
|
return req.json() if verify(req) else False
|
||||||
|
|
||||||
def px_get_service_regexes(self,service_id):
|
def px_get_service_regexes(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regexes")
|
req = self.s.get(f"{self.address}api/regexproxy/service/{service_id}/regexes")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def px_get_regex(self,regex_id):
|
def px_get_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}")
|
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}")
|
||||||
return req.json()
|
return req.json()
|
||||||
|
|
||||||
def px_delete_regex(self,regex_id):
|
def px_delete_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/delete")
|
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/delete")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_enable_regex(self,regex_id):
|
def px_enable_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/enable")
|
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/enable")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_disable_regex(self,regex_id):
|
def px_disable_regex(self,regex_id: str):
|
||||||
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/disable")
|
req = self.s.get(f"{self.address}api/regexproxy/regex/{regex_id}/disable")
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
@@ -195,15 +196,49 @@ class FiregexAPI:
|
|||||||
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
|
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_blacklist": is_blacklist, "is_case_sensitive": is_case_sensitive})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_rename_service(self,service_id,newname):
|
def px_rename_service(self,service_id: str, newname: str):
|
||||||
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/rename" , json={"name":newname})
|
req = self.s.post(f"{self.address}api/regexproxy/service/{service_id}/rename" , json={"name":newname})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def px_add_service(self, name: str, port: int, internalPort = None):
|
def px_add_service(self, name: str, port: int, internalPort:int = None):
|
||||||
payload = {}
|
payload = {}
|
||||||
payload["name"] = name
|
payload["name"] = name
|
||||||
payload["port"] = port
|
payload["port"] = port
|
||||||
if internalPort:
|
if internalPort:
|
||||||
payload["internalPort"] = internalPort
|
payload["internalPort"] = internalPort
|
||||||
req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload)
|
req = self.s.post(f"{self.address}api/regexproxy/services/add" , json=payload)
|
||||||
return req.json()["id"] if verify(req) else False
|
return req.json()["id"] if verify(req) else False
|
||||||
|
|
||||||
|
#PortHijack
|
||||||
|
def ph_get_services(self):
|
||||||
|
req = self.s.get(f"{self.address}api/porthijack/services")
|
||||||
|
return req.json()
|
||||||
|
|
||||||
|
def ph_get_service(self,service_id: str):
|
||||||
|
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}")
|
||||||
|
return req.json()
|
||||||
|
|
||||||
|
def ph_stop_service(self,service_id: str):
|
||||||
|
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/stop")
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
|
def ph_start_service(self,service_id: str):
|
||||||
|
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/start")
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
|
def ph_delete_service(self,service_id: str):
|
||||||
|
req = self.s.get(f"{self.address}api/porthijack/service/{service_id}/delete")
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
|
def ph_rename_service(self,service_id: str,newname: str):
|
||||||
|
req = self.s.post(f"{self.address}api/porthijack/service/{service_id}/rename" , json={"name":newname})
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
|
def ph_change_destination(self,service_id: str, ip_dst:string , proxy_port: int):
|
||||||
|
req = self.s.post(f"{self.address}api/porthijack/service/{service_id}/change-destination", json={"ip_dst": ip_dst, "proxy_port": proxy_port})
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
|
def ph_add_service(self, name: str, public_port: int, proxy_port: int, proto: str, ip_src: str, ip_dst: str):
|
||||||
|
req = self.s.post(f"{self.address}api/porthijack/services/add" ,
|
||||||
|
json={"name":name, "public_port": public_port, "proxy_port":proxy_port, "proto": proto, "ip_src": ip_src, "ip_dst": ip_dst})
|
||||||
|
return req.json()["service_id"] if verify(req) else False
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from multiprocessing import Process
|
|||||||
import socket
|
import socket
|
||||||
|
|
||||||
class UdpServer:
|
class UdpServer:
|
||||||
def __init__(self,port,ipv6):
|
def __init__(self,port,ipv6, proxy_port = None):
|
||||||
def _startServer(port):
|
def _startServer(port):
|
||||||
sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
sock = socket.socket(socket.AF_INET6 if ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
@@ -15,6 +15,7 @@ class UdpServer:
|
|||||||
|
|
||||||
self.ipv6 = ipv6
|
self.ipv6 = ipv6
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.proxy_port = proxy_port
|
||||||
self.server = Process(target=_startServer,args=[port])
|
self.server = Process(target=_startServer,args=[port])
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
@@ -26,7 +27,7 @@ class UdpServer:
|
|||||||
def sendCheckData(self,data):
|
def sendCheckData(self,data):
|
||||||
s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
s.settimeout(2)
|
s.settimeout(2)
|
||||||
s.sendto(data, ('::1' if self.ipv6 else '127.0.0.1', self.port))
|
s.sendto(data, ('::1' if self.ipv6 else '127.0.0.1', self.proxy_port if self.proxy_port else self.port))
|
||||||
try:
|
try:
|
||||||
received_data = s.recvfrom(432)
|
received_data = s.recvfrom(432)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|||||||
Reference in New Issue
Block a user