Port hijack changes and start writing frontend
This commit is contained in:
@@ -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"]
|
||||||
|
)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from modules.nfregex.models import Service
|
from modules.nfregex.models import Service
|
||||||
from utils import ip_parse, ip_family, NFTableManager
|
from utils import ip_parse, ip_family, NFTableManager, nftables_int_to_json
|
||||||
|
|
||||||
class FiregexFilter:
|
class FiregexFilter:
|
||||||
def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
|
def __init__(self, proto:str, port:int, ip_int:str, target:str, id:int):
|
||||||
@@ -52,10 +52,6 @@ class FiregexTables(NFTableManager):
|
|||||||
|
|
||||||
for ele in self.get():
|
for ele in self.get():
|
||||||
if ele.__eq__(srv): return
|
if ele.__eq__(srv): return
|
||||||
|
|
||||||
ip_int = ip_parse(srv.ip_int)
|
|
||||||
ip_addr = str(ip_int).split("/")[0]
|
|
||||||
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
|
||||||
|
|
||||||
init, end = queue_range_output
|
init, end = queue_range_output
|
||||||
if init > end: init, end = end, init
|
if init > end: init, end = end, init
|
||||||
@@ -64,7 +60,7 @@ class FiregexTables(NFTableManager):
|
|||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.output_chain,
|
"chain": self.output_chain,
|
||||||
"expr": [
|
"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': 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)}},
|
{'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"]}}
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
]
|
]
|
||||||
@@ -77,7 +73,7 @@ class FiregexTables(NFTableManager):
|
|||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.input_chain,
|
"chain": self.input_chain,
|
||||||
"expr": [
|
"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': 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)}},
|
{'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"]}}
|
{"queue": {"num": str(init) if init == end else {"range":[init, end] }, "flags": ["bypass"]}}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -64,8 +64,8 @@ class ServiceManager:
|
|||||||
nft.delete(self.srv)
|
nft.delete(self.srv)
|
||||||
self._set_status(False)
|
self._set_status(False)
|
||||||
|
|
||||||
async def change_port(self, new_port):
|
async def refresh(self, srv:Service):
|
||||||
self.srv.proxy_port = new_port
|
self.srv = srv
|
||||||
if self.active: await self.restart()
|
if self.active: await self.restart()
|
||||||
|
|
||||||
def _set_status(self,active):
|
def _set_status(self,active):
|
||||||
|
|||||||
@@ -1,13 +1,23 @@
|
|||||||
class Service:
|
class Service:
|
||||||
def __init__(self, service_id: str, active: bool, public_port: int, proxy_port: int, name: str, proto: str, ip_int: str):
|
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.service_id = service_id
|
||||||
self.active = active
|
self.active = active
|
||||||
self.public_port = public_port
|
self.public_port = public_port
|
||||||
self.proxy_port = proxy_port
|
self.proxy_port = proxy_port
|
||||||
self.name = name
|
self.name = name
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.ip_int = ip_int
|
self.ip_src = ip_src
|
||||||
|
self.ip_dst = ip_dst
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, var: dict):
|
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_int=var["ip_int"])
|
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"]
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
from modules.porthijack.models import Service
|
from modules.porthijack.models import Service
|
||||||
from utils import ip_parse, ip_family, NFTableManager
|
from utils import addr_parse, ip_parse, ip_family, NFTableManager, nftables_json_to_int
|
||||||
|
|
||||||
class FiregexHijackRule():
|
class FiregexHijackRule():
|
||||||
def __init__(self, proto:str, public_port:int,proxy_port:int, ip_int:str, target:str, id:int):
|
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.id = id
|
||||||
self.target = target
|
self.target = target
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.public_port = public_port
|
self.public_port = public_port
|
||||||
self.proxy_port = proxy_port
|
self.proxy_port = proxy_port
|
||||||
self.ip_int = str(ip_int)
|
self.ip_src = str(ip_src)
|
||||||
|
self.ip_dst = str(ip_dst)
|
||||||
|
|
||||||
def __eq__(self, o: object) -> bool:
|
def __eq__(self, o: object) -> bool:
|
||||||
if isinstance(o, FiregexHijackRule):
|
if isinstance(o, FiregexHijackRule):
|
||||||
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
|
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):
|
elif isinstance(o, Service):
|
||||||
return self.public_port == o.public_port and self.proto == o.proto and ip_parse(self.ip_int) == ip_parse(o.ip_int)
|
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
|
return False
|
||||||
|
|
||||||
class FiregexTables(NFTableManager):
|
class FiregexTables(NFTableManager):
|
||||||
@@ -54,17 +55,15 @@ class FiregexTables(NFTableManager):
|
|||||||
for ele in self.get():
|
for ele in self.get():
|
||||||
if ele.__eq__(srv): return
|
if ele.__eq__(srv): return
|
||||||
|
|
||||||
ip_int = ip_parse(srv.ip_int)
|
|
||||||
ip_addr = str(ip_int).split("/")[0]
|
|
||||||
ip_addr_cidr = int(str(ip_int).split("/")[1])
|
|
||||||
self.cmd({ "insert":{ "rule": {
|
self.cmd({ "insert":{ "rule": {
|
||||||
"family": "inet",
|
"family": "inet",
|
||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.prerouting_porthijack,
|
"chain": self.prerouting_porthijack,
|
||||||
"expr": [
|
"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': 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)}},
|
{'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': 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": {
|
self.cmd({ "insert":{ "rule": {
|
||||||
@@ -72,9 +71,10 @@ class FiregexTables(NFTableManager):
|
|||||||
"table": self.table_name,
|
"table": self.table_name,
|
||||||
"chain": self.postrouting_porthijack,
|
"chain": self.postrouting_porthijack,
|
||||||
"expr": [
|
"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': 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)}},
|
{'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': 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)}}
|
||||||
]
|
]
|
||||||
}}})
|
}}})
|
||||||
|
|
||||||
@@ -82,18 +82,15 @@ class FiregexTables(NFTableManager):
|
|||||||
def get(self) -> List[FiregexHijackRule]:
|
def get(self) -> List[FiregexHijackRule]:
|
||||||
res = []
|
res = []
|
||||||
for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
|
for filter in self.list_rules(tables=[self.table_name], chains=[self.prerouting_porthijack,self.postrouting_porthijack]):
|
||||||
ip_int = None
|
filter["expr"][0]["match"]["right"]
|
||||||
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(FiregexHijackRule(
|
res.append(FiregexHijackRule(
|
||||||
target=filter["chain"],
|
target=filter["chain"],
|
||||||
id=int(filter["handle"]),
|
id=int(filter["handle"]),
|
||||||
proto=filter["expr"][1]["match"]["left"]["payload"]["protocol"],
|
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"],
|
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"],
|
proxy_port=filter["expr"][1]["match"]["right"] if filter["chain"] == self.postrouting_porthijack else filter["expr"][2]["mangle"]["value"],
|
||||||
ip_int=ip_int
|
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
|
return res
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ 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.porthijack.models import Service
|
||||||
from utils.sqlite import SQLite
|
from utils.sqlite import SQLite
|
||||||
from utils import ip_parse, refactor_name, refresh_frontend
|
from utils import addr_parse, ip_family, refactor_name, refresh_frontend
|
||||||
from utils.models import ResetRequest, StatusMessageModel
|
from utils.models import ResetRequest, StatusMessageModel
|
||||||
from modules.porthijack.nftables import FiregexTables
|
from modules.porthijack.nftables import FiregexTables
|
||||||
from modules.porthijack.firewall import FirewallManager
|
from modules.porthijack.firewall import FirewallManager
|
||||||
@@ -16,7 +17,8 @@ class ServiceModel(BaseModel):
|
|||||||
proxy_port: int
|
proxy_port: int
|
||||||
name: str
|
name: str
|
||||||
proto: str
|
proto: str
|
||||||
ip_int: str
|
ip_src: str
|
||||||
|
ip_dst: str
|
||||||
|
|
||||||
class RenameForm(BaseModel):
|
class RenameForm(BaseModel):
|
||||||
name:str
|
name:str
|
||||||
@@ -26,7 +28,8 @@ class ServiceAddForm(BaseModel):
|
|||||||
public_port: int
|
public_port: int
|
||||||
proxy_port: int
|
proxy_port: int
|
||||||
proto: str
|
proto: str
|
||||||
ip_int: str
|
ip_src: str
|
||||||
|
ip_dst: str
|
||||||
|
|
||||||
class ServiceAddResponse(BaseModel):
|
class ServiceAddResponse(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
@@ -41,15 +44,15 @@ db = SQLite('db/port-hijacking.db', {
|
|||||||
'services': {
|
'services': {
|
||||||
'service_id': 'VARCHAR(100) PRIMARY KEY',
|
'service_id': 'VARCHAR(100) PRIMARY KEY',
|
||||||
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
'active' : 'BOOLEAN NOT NULL CHECK (active IN (0, 1))',
|
||||||
'public_port': 'INT NOT NULL CHECK(public_port > 0 and public_port < 65536) UNIQUE',
|
'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)',
|
'proxy_port': 'INT NOT NULL CHECK(proxy_port > 0 and proxy_port < 65536 and proxy_port != public_port)',
|
||||||
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
||||||
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
|
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "udp"))',
|
||||||
'ip_int': 'VARCHAR(100) NOT NULL',
|
'ip_src': 'VARCHAR(100) NOT NULL',
|
||||||
|
'ip_dst': 'VARCHAR(100) NOT NULL',
|
||||||
},
|
},
|
||||||
'QUERY':[
|
'QUERY':[
|
||||||
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_int, proto);",
|
"CREATE UNIQUE INDEX IF NOT EXISTS unique_services ON services (public_port, ip_src, proto);"
|
||||||
""
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -96,12 +99,12 @@ async def get_general_stats():
|
|||||||
@app.get('/services', response_model=List[ServiceModel])
|
@app.get('/services', response_model=List[ServiceModel])
|
||||||
async def get_service_list():
|
async def get_service_list():
|
||||||
"""Get the list of existent firegex services"""
|
"""Get the list of existent firegex services"""
|
||||||
return db.query("SELECT service_id, active, public_port, proxy_port, name, proto, ip_int FROM 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)
|
@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("SELECT service_id, active, public_port, proxy_port, name, proto, ip_int FROM services WHERE service_id = ?;", service_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!")
|
if len(res) == 0: raise HTTPException(status_code=400, detail="This service does not exists!")
|
||||||
return res[0]
|
return res[0]
|
||||||
|
|
||||||
@@ -139,17 +142,30 @@ async def service_rename(service_id: str, form: RenameForm):
|
|||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
class ChangePortRequest(BaseModel):
|
class ChangeDestination(BaseModel):
|
||||||
|
ip_dst: str
|
||||||
proxy_port: int
|
proxy_port: int
|
||||||
|
|
||||||
@app.post('/service/{service_id}/changeport', response_model=StatusMessageModel)
|
@app.post('/service/{service_id}/change-destination', response_model=StatusMessageModel)
|
||||||
async def service_changeport(service_id: str, form: ChangePortRequest):
|
async def service_change_destination(service_id: str, form: ChangeDestination):
|
||||||
"""Request to change the proxy port of a specific service"""
|
"""Request to change the proxy destination of the service"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
db.query('UPDATE services SET proxy_port=? WHERE service_id = ?;', form.proxy_port, service_id)
|
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:
|
except sqlite3.IntegrityError:
|
||||||
return {'status': 'Invalid proxy port or service'}
|
return {'status': 'Invalid proxy port or service'}
|
||||||
await firewall.get(service_id).change_port(form.proxy_port)
|
|
||||||
|
srv.ip_dst = form.ip_dst
|
||||||
|
srv.proxy_port = form.proxy_port
|
||||||
|
await firewall.get(service_id).refresh(srv)
|
||||||
|
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@@ -157,18 +173,24 @@ async def service_changeport(service_id: str, form: ChangePortRequest):
|
|||||||
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_src = addr_parse(form.ip_src)
|
||||||
|
form.ip_dst = addr_parse(form.ip_dst)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return {"status":"Invalid address"}
|
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"]:
|
if form.proto not in ["tcp", "udp"]:
|
||||||
return {"status":"Invalid protocol"}
|
return {"status":"Invalid protocol"}
|
||||||
|
|
||||||
srv_id = None
|
srv_id = None
|
||||||
try:
|
try:
|
||||||
srv_id = gen_service_id()
|
srv_id = gen_service_id()
|
||||||
db.query("INSERT INTO services (service_id, active, public_port, proxy_port, name, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
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_int)
|
srv_id, False, form.public_port, form.proxy_port , form.name, form.proto, form.ip_src, form.ip_dst)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
return {'status': 'This type of service already exists'}
|
return {'status': 'This type of service already exists'}
|
||||||
|
|
||||||
await firewall.reload()
|
await firewall.reload()
|
||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok', 'service_id': srv_id}
|
return {'status': 'ok', 'service_id': srv_id}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from ipaddress import ip_interface
|
from ipaddress import ip_address, ip_interface
|
||||||
import os, socket, psutil, sys, nftables
|
import os, socket, psutil, sys, nftables
|
||||||
from fastapi_socketio import SocketManager
|
from fastapi_socketio import SocketManager
|
||||||
|
|
||||||
@@ -37,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"
|
||||||
|
|
||||||
@@ -48,6 +51,18 @@ def get_interfaces():
|
|||||||
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):
|
class Singleton(object):
|
||||||
__instance = None
|
__instance = None
|
||||||
def __new__(class_, *args, **kwargs):
|
def __new__(class_, *args, **kwargs):
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
155
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
155
frontend/src/components/PortHijack/AddNewService.tsx
Executable file
@@ -0,0 +1,155 @@
|
|||||||
|
import { Button, Group, NumberInput, Space, TextInput, Notification, Modal, Switch, SegmentedControl, Autocomplete, AutocompleteItem } from '@mantine/core';
|
||||||
|
import { useForm } from '@mantine/hooks';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { okNotify, regex_ipv4, regex_ipv6, getipinterfaces } from '../../js/utils';
|
||||||
|
import { ImCross } from "react-icons/im"
|
||||||
|
import { porthijack } from './utils';
|
||||||
|
|
||||||
|
type ServiceAddForm = {
|
||||||
|
name:string,
|
||||||
|
public_port:number,
|
||||||
|
proxy_port:number,
|
||||||
|
proto:string,
|
||||||
|
ip_int:string,
|
||||||
|
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 }) {
|
||||||
|
|
||||||
|
const form = useForm({
|
||||||
|
initialValues: {
|
||||||
|
name:"",
|
||||||
|
public_port:80,
|
||||||
|
proxy_port:8080,
|
||||||
|
proto:"tcp",
|
||||||
|
ip_int:"",
|
||||||
|
autostart: true,
|
||||||
|
},
|
||||||
|
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_int: (value) => value.match(regex_ipv6)?true:false || value.match(regex_ipv4)?true:false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const [ipInterfaces, setIpInterfaces] = useState<AutocompleteItem[]>([]);
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
getipinterfaces().then(data => {
|
||||||
|
setIpInterfaces(data.map(item => ({label:item.name, value:item.addr})));
|
||||||
|
})
|
||||||
|
},[])
|
||||||
|
|
||||||
|
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_int }:ServiceAddForm) =>{
|
||||||
|
setSubmitLoading(true)
|
||||||
|
porthijack.servicesadd({name, proxy_port, public_port, proto, ip_int }).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" />
|
||||||
|
<div className='center-flex' style={{width:"100%"}}>
|
||||||
|
<Autocomplete
|
||||||
|
label="Public IP Interface (ipv4/ipv6 + CIDR allowed)"
|
||||||
|
placeholder="10.1.1.0/24"
|
||||||
|
itemComponent={AutoCompleteItem}
|
||||||
|
data={ipInterfaces}
|
||||||
|
{...form.getInputProps('ip_int')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Space w="sm" />:<Space w="sm" />
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
placeholder="80"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
label="Public Service port"
|
||||||
|
{...form.getInputProps('public_port')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Space h="md" />
|
||||||
|
|
||||||
|
<NumberInput
|
||||||
|
placeholder="8080"
|
||||||
|
min={1}
|
||||||
|
max={65535}
|
||||||
|
label="Redirect to port"
|
||||||
|
{...form.getInputProps('proxy_port')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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;
|
||||||
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.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.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;
|
||||||
18
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
18
frontend/src/components/PortHijack/ServiceRow/index.module.scss
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
|
||||||
|
@use "../../../index.scss" as *;
|
||||||
|
|
||||||
|
.row{
|
||||||
|
width: 95%;
|
||||||
|
padding: 30px 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;
|
||||||
|
}
|
||||||
156
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
156
frontend/src/components/PortHijack/ServiceRow/index.tsx
Executable file
@@ -0,0 +1,156 @@
|
|||||||
|
import { ActionIcon, Badge, Divider, Grid, MediaQuery, 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 { MdOutlineArrowForwardIos } from "react-icons/md"
|
||||||
|
import style from "./index.module.scss";
|
||||||
|
import YesNoModal from '../../YesNoModal';
|
||||||
|
import { errorNotify, okNotify, regex_ipv4 } from '../../../js/utils';
|
||||||
|
import { BsTrashFill } from 'react-icons/bs';
|
||||||
|
import { BiRename } from 'react-icons/bi'
|
||||||
|
import RenameForm from './RenameForm';
|
||||||
|
|
||||||
|
function ServiceRow({ service, onClick }:{ service:Service, onClick?:()=>void }) {
|
||||||
|
|
||||||
|
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 stopService = async () => {
|
||||||
|
setButtonLoading(true)
|
||||||
|
|
||||||
|
await porthijack.servicestop(service.service_id).then(res => {
|
||||||
|
if(!res){
|
||||||
|
okNotify(`Service ${service.name} stopped successfully!`,`The service on ${service.port} has been stopped!`)
|
||||||
|
}else{
|
||||||
|
errorNotify(`An error as occurred during the stopping of the service ${service.port}`,`Error: ${res}`)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify(`An error as occurred during the stopping of the service ${service.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.port} has been started!`)
|
||||||
|
}else{
|
||||||
|
errorNotify(`An error as occurred during the starting of the service ${service.port}`,`Error: ${res}`)
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
errorNotify(`An error as occurred during the starting of the service ${service.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 <>
|
||||||
|
<Grid className={style.row} justify="flex-end" style={{width:"100%"}}>
|
||||||
|
<Grid.Col md={4} xs={12}>
|
||||||
|
<MediaQuery smallerThan="md" styles={{ display: 'none' }}><div>
|
||||||
|
<div className="center-flex-row">
|
||||||
|
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.port}</Badge></div>
|
||||||
|
<Badge color={status_color} radius="sm" size="lg" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
|
||||||
|
</div>
|
||||||
|
</div></MediaQuery>
|
||||||
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}><div>
|
||||||
|
<div className="center-flex">
|
||||||
|
<div className="center-flex"><Title className={style.name}>{service.name}</Title> <Badge size="xl" gradient={{ from: 'indigo', to: 'cyan' }} variant="gradient">:{service.port}</Badge></div>
|
||||||
|
<Badge style={{marginLeft:"20px"}} color={status_color} radius="sm" size="lg" variant="filled">Status: <u>{service.active?"ENABLED":"DISABLED"}</u></Badge>
|
||||||
|
<Space w="xl" />
|
||||||
|
</div>
|
||||||
|
</div></MediaQuery>
|
||||||
|
|
||||||
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<Space h="xl" />
|
||||||
|
</MediaQuery>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
<Grid.Col className="center-flex" md={8} xs={12}>
|
||||||
|
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<><Space w="xl" /><Space w="xl" /></>
|
||||||
|
</MediaQuery>
|
||||||
|
|
||||||
|
<div className="center-flex-row">
|
||||||
|
<Badge color={service.ip_int.match(regex_ipv4)?"cyan":"pink"} radius="sm" size="md" variant="filled">{service.ip_int} on {service.proto}</Badge>
|
||||||
|
</div>
|
||||||
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<div className='flex-spacer' />
|
||||||
|
</MediaQuery>
|
||||||
|
<MediaQuery smallerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<><Space w="xl" /><Space w="xl" /></>
|
||||||
|
</MediaQuery>
|
||||||
|
<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>
|
||||||
|
<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" />
|
||||||
|
{onClick?<div>
|
||||||
|
<MdOutlineArrowForwardIos onClick={onClick} style={{cursor:"pointer"}} size={45} />
|
||||||
|
<Space w="xl" />
|
||||||
|
</div>:null}
|
||||||
|
<MediaQuery largerThan="md" styles={{ display: 'none' }}>
|
||||||
|
<><Space w="xl" /><Space w="xl" /></>
|
||||||
|
</MediaQuery>
|
||||||
|
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
<hr style={{width:"100%"}}/>
|
||||||
|
<YesNoModal
|
||||||
|
title='Are you sure to delete this service?'
|
||||||
|
description={`You are going to delete the service '${service.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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServiceRow;
|
||||||
64
frontend/src/components/PortHijack/utils.ts
Executable file
64
frontend/src/components/PortHijack/utils.ts
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
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,
|
||||||
|
port:number,
|
||||||
|
proto: string,
|
||||||
|
ip_int: string,
|
||||||
|
proxy_port: number,
|
||||||
|
public_port: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ServiceAddForm = {
|
||||||
|
name:string,
|
||||||
|
public_port:number,
|
||||||
|
proxy_port:number,
|
||||||
|
proto:string,
|
||||||
|
ip_int: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
|
||||||
|
},
|
||||||
|
changeport: async (service_id:string, proxy_port:number) => {
|
||||||
|
return await postapi(`porthijack/service/${service_id}/changeport`, {proxy_port}) as ServerResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
78
frontend/src/pages/PortHijack/index.tsx
Executable file
78
frontend/src/pages/PortHijack/index.tsx
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
import { ActionIcon, Badge, LoadingOverlay, Space, Title, Tooltip } from '@mantine/core';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { BsPlusLg } from "react-icons/bs";
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
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 navigator = useNavigate()
|
||||||
|
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} onClick={()=>{
|
||||||
|
navigator("/nfregex/"+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;
|
||||||
Reference in New Issue
Block a user