test on settings API added + improves on nfproxy code including fail-open
This commit is contained in:
@@ -15,19 +15,19 @@ RUN bun run build
|
|||||||
|
|
||||||
#Building main conteiner
|
#Building main conteiner
|
||||||
FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest
|
FROM --platform=$TARGETARCH registry.fedoraproject.org/fedora:latest
|
||||||
RUN dnf -y update && dnf install -y python3.13-devel python3-pip @development-tools gcc-c++ \
|
RUN dnf -y update && dnf install -y python3.13-devel @development-tools gcc-c++ \
|
||||||
libnetfilter_queue-devel libnfnetlink-devel libmnl-devel libcap-ng-utils nftables \
|
libnetfilter_queue-devel libnfnetlink-devel libmnl-devel libcap-ng-utils nftables \
|
||||||
vectorscan-devel libtins-devel python3-nftables libpcap-devel boost-devel
|
vectorscan-devel libtins-devel python3-nftables libpcap-devel boost-devel uv
|
||||||
|
|
||||||
RUN mkdir -p /execute/modules
|
RUN mkdir -p /execute/modules
|
||||||
WORKDIR /execute
|
WORKDIR /execute
|
||||||
|
|
||||||
ADD ./backend/requirements.txt /execute/requirements.txt
|
ADD ./backend/requirements.txt /execute/requirements.txt
|
||||||
RUN pip3 install --no-cache-dir --break-system-packages -r /execute/requirements.txt --no-warn-script-location
|
RUN uv pip install --no-cache --system -r /execute/requirements.txt
|
||||||
|
|
||||||
COPY ./backend/binsrc /execute/binsrc
|
COPY ./backend/binsrc /execute/binsrc
|
||||||
RUN g++ binsrc/nfregex.cpp -o modules/cppregex -std=c++23 -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libhs libmnl)
|
RUN g++ binsrc/nfregex.cpp -o modules/cppregex -std=c++23 -O3 -lnetfilter_queue -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libhs libmnl)
|
||||||
#RUN g++ binsrc/nfproxy.cpp -o modules/cpproxy -std=c++23 -O3 -lnetfilter_queue -lpython3.13 -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libmnl python3)
|
RUN g++ binsrc/nfproxy.cpp -o modules/cpproxy -std=c++23 -O3 -lnetfilter_queue -lpython3.13 -pthread -lnfnetlink $(pkg-config --cflags --libs libtins libmnl python3)
|
||||||
|
|
||||||
COPY ./backend/ /execute/
|
COPY ./backend/ /execute/
|
||||||
COPY --from=frontend /app/dist/ ./frontend/
|
COPY --from=frontend /app/dist/ ./frontend/
|
||||||
|
|||||||
@@ -48,7 +48,10 @@ class FiregexInterceptor:
|
|||||||
self.process = await asyncio.create_subprocess_exec(
|
self.process = await asyncio.create_subprocess_exec(
|
||||||
proxy_binary_path,
|
proxy_binary_path,
|
||||||
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE, stdin=asyncio.subprocess.PIPE,
|
||||||
env={"NTHREADS": os.getenv("NTHREADS","1")},
|
env={
|
||||||
|
"NTHREADS": os.getenv("NTHREADS","1"),
|
||||||
|
"FIREGEX_NFQUEUE_FAIL_OPEN": "1" if self.srv.fail_open else "0",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
line_fut = self.process.stdout.readuntil()
|
line_fut = self.process.stdout.readuntil()
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
|
||||||
class Service:
|
class Service:
|
||||||
def __init__(self, service_id: str, status: str, port: int, name: str, proto: str, ip_int: str, **other):
|
def __init__(self, service_id: str, status: str, port: int, name: str, proto: str, ip_int: str, fail_open: bool, **other):
|
||||||
self.id = service_id
|
self.id = service_id
|
||||||
self.status = status
|
self.status = status
|
||||||
self.port = port
|
self.port = port
|
||||||
self.name = name
|
self.name = name
|
||||||
self.proto = proto
|
self.proto = proto
|
||||||
self.ip_int = ip_int
|
self.ip_int = ip_int
|
||||||
|
self.fail_open = fail_open
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, var: dict):
|
def from_dict(cls, var: dict):
|
||||||
|
|||||||
@@ -18,10 +18,17 @@ class ServiceModel(BaseModel):
|
|||||||
n_filters: int
|
n_filters: int
|
||||||
edited_packets: int
|
edited_packets: int
|
||||||
blocked_packets: int
|
blocked_packets: int
|
||||||
|
fail_open: bool
|
||||||
|
|
||||||
class RenameForm(BaseModel):
|
class RenameForm(BaseModel):
|
||||||
name:str
|
name:str
|
||||||
|
|
||||||
|
class SettingsForm(BaseModel):
|
||||||
|
port: PortType|None = None
|
||||||
|
proto: str|None = None
|
||||||
|
ip_int: str|None = None
|
||||||
|
fail_open: bool|None = None
|
||||||
|
|
||||||
class PyFilterModel(BaseModel):
|
class PyFilterModel(BaseModel):
|
||||||
filter_id: int
|
filter_id: int
|
||||||
name: str
|
name: str
|
||||||
@@ -34,12 +41,13 @@ class ServiceAddForm(BaseModel):
|
|||||||
port: PortType
|
port: PortType
|
||||||
proto: str
|
proto: str
|
||||||
ip_int: str
|
ip_int: str
|
||||||
|
fail_open: bool = True
|
||||||
|
|
||||||
class ServiceAddResponse(BaseModel):
|
class ServiceAddResponse(BaseModel):
|
||||||
status:str
|
status:str
|
||||||
service_id: str|None = None
|
service_id: str|None = None
|
||||||
|
|
||||||
#app = APIRouter() Not released in this version
|
app = APIRouter()
|
||||||
|
|
||||||
db = SQLite('db/nft-pyfilters.db', {
|
db = SQLite('db/nft-pyfilters.db', {
|
||||||
'services': {
|
'services': {
|
||||||
@@ -49,6 +57,7 @@ db = SQLite('db/nft-pyfilters.db', {
|
|||||||
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
'name': 'VARCHAR(100) NOT NULL UNIQUE',
|
||||||
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "http"))',
|
'proto': 'VARCHAR(3) NOT NULL CHECK (proto IN ("tcp", "http"))',
|
||||||
'ip_int': 'VARCHAR(100) NOT NULL',
|
'ip_int': 'VARCHAR(100) NOT NULL',
|
||||||
|
'fail_open': 'BOOLEAN NOT NULL CHECK (fail_open IN (0, 1)) DEFAULT 1',
|
||||||
},
|
},
|
||||||
'pyfilter': {
|
'pyfilter': {
|
||||||
'filter_id': 'INTEGER PRIMARY KEY',
|
'filter_id': 'INTEGER PRIMARY KEY',
|
||||||
@@ -116,6 +125,7 @@ async def get_service_list():
|
|||||||
s.name name,
|
s.name name,
|
||||||
s.proto proto,
|
s.proto proto,
|
||||||
s.ip_int ip_int,
|
s.ip_int ip_int,
|
||||||
|
s.fail_open fail_open,
|
||||||
COUNT(f.filter_id) n_filters,
|
COUNT(f.filter_id) n_filters,
|
||||||
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
|
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
|
||||||
COALESCE(SUM(f.edited_packets),0) edited_packets
|
COALESCE(SUM(f.edited_packets),0) edited_packets
|
||||||
@@ -134,6 +144,7 @@ async def get_service_by_id(service_id: str):
|
|||||||
s.name name,
|
s.name name,
|
||||||
s.proto proto,
|
s.proto proto,
|
||||||
s.ip_int ip_int,
|
s.ip_int ip_int,
|
||||||
|
s.fail_open fail_open,
|
||||||
COUNT(f.filter_id) n_filters,
|
COUNT(f.filter_id) n_filters,
|
||||||
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
|
COALESCE(SUM(f.blocked_packets),0) blocked_packets,
|
||||||
COALESCE(SUM(f.edited_packets),0) edited_packets
|
COALESCE(SUM(f.edited_packets),0) edited_packets
|
||||||
@@ -180,6 +191,45 @@ async def service_rename(service_id: str, form: RenameForm):
|
|||||||
await refresh_frontend()
|
await refresh_frontend()
|
||||||
return {'status': 'ok'}
|
return {'status': 'ok'}
|
||||||
|
|
||||||
|
@app.put('/services/{service_id}/settings', response_model=StatusMessageModel)
|
||||||
|
async def service_settings(service_id: str, form: SettingsForm):
|
||||||
|
"""Request to change the settings of a specific service (will cause a restart)"""
|
||||||
|
|
||||||
|
if form.proto is not None and form.proto not in ["tcp", "udp"]:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid protocol")
|
||||||
|
|
||||||
|
if form.port is not None and (form.port < 1 or form.port > 65535):
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid port")
|
||||||
|
|
||||||
|
if form.ip_int is not None:
|
||||||
|
try:
|
||||||
|
form.ip_int = ip_parse(form.ip_int)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(status_code=400, detail="Invalid address")
|
||||||
|
|
||||||
|
keys = []
|
||||||
|
values = []
|
||||||
|
|
||||||
|
for key, value in form.model_dump(exclude_none=True).items():
|
||||||
|
keys.append(key)
|
||||||
|
values.append(value)
|
||||||
|
|
||||||
|
if len(keys) == 0:
|
||||||
|
raise HTTPException(status_code=400, detail="No settings to change provided")
|
||||||
|
|
||||||
|
try:
|
||||||
|
db.query(f'UPDATE services SET {", ".join([f"{key}=?" for key in keys])} WHERE service_id = ?;', *values, service_id)
|
||||||
|
except sqlite3.IntegrityError:
|
||||||
|
raise HTTPException(status_code=400, detail="A service with these settings already exists")
|
||||||
|
|
||||||
|
old_status = firewall.get(service_id).status
|
||||||
|
await firewall.remove(service_id)
|
||||||
|
await firewall.reload()
|
||||||
|
await firewall.get(service_id).next(old_status)
|
||||||
|
|
||||||
|
await refresh_frontend()
|
||||||
|
return {'status': 'ok'}
|
||||||
|
|
||||||
@app.get('/services/{service_id}/pyfilters', response_model=list[PyFilterModel])
|
@app.get('/services/{service_id}/pyfilters', response_model=list[PyFilterModel])
|
||||||
async def get_service_pyfilter_list(service_id: str):
|
async def get_service_pyfilter_list(service_id: str):
|
||||||
"""Get the list of the pyfilters of a service"""
|
"""Get the list of the pyfilters of a service"""
|
||||||
@@ -246,8 +296,8 @@ async def add_new_service(form: ServiceAddForm):
|
|||||||
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 ,name, port, status, proto, ip_int) VALUES (?, ?, ?, ?, ?, ?)",
|
db.query("INSERT INTO services (service_id ,name, port, status, proto, ip_int, fail_open) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int)
|
srv_id, refactor_name(form.name), form.port, STATUS.STOP, form.proto, form.ip_int, form.fail_open)
|
||||||
except sqlite3.IntegrityError:
|
except sqlite3.IntegrityError:
|
||||||
raise HTTPException(status_code=400, detail="This type of service already exists")
|
raise HTTPException(status_code=400, detail="This type of service already exists")
|
||||||
await firewall.reload()
|
await firewall.reload()
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ def exit_test(code):
|
|||||||
exit_test(1)
|
exit_test(1)
|
||||||
exit(code)
|
exit(code)
|
||||||
|
|
||||||
|
srvs = firegex.nf_get_services()
|
||||||
|
for ele in srvs:
|
||||||
|
if ele['name'] == args.service_name:
|
||||||
|
firegex.nf_delete_service(ele['service_id'])
|
||||||
|
|
||||||
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" )
|
||||||
if service_id:
|
if service_id:
|
||||||
puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
puts(f"Sucessfully created service {service_id} ✔", color=colors.green)
|
||||||
@@ -64,7 +69,7 @@ try:
|
|||||||
else:
|
else:
|
||||||
puts("Test Failed: Data was corrupted ", color=colors.red)
|
puts("Test Failed: Data was corrupted ", color=colors.red)
|
||||||
exit_test(1)
|
exit_test(1)
|
||||||
except Exception as e:
|
except Exception:
|
||||||
puts("Test Failed: Couldn't send data to the server ", color=colors.red)
|
puts("Test Failed: Couldn't send data to the server ", color=colors.red)
|
||||||
exit_test(1)
|
exit_test(1)
|
||||||
#Add new regex
|
#Add new regex
|
||||||
@@ -194,10 +199,24 @@ else:
|
|||||||
exit_test(1)
|
exit_test(1)
|
||||||
|
|
||||||
#Check if service was renamed correctly
|
#Check if service was renamed correctly
|
||||||
for services in firegex.nf_get_services():
|
service = firegex.nf_get_service(service_id)
|
||||||
if services["name"] == f"{args.service_name}2":
|
if service["name"] == f"{args.service_name}2":
|
||||||
puts("Checked that service was renamed correctly ✔", color=colors.green)
|
puts("Checked that service was renamed correctly ✔", color=colors.green)
|
||||||
exit_test(0)
|
else:
|
||||||
|
|
||||||
puts("Test Failed: Service wasn't renamed correctly ✗", color=colors.red)
|
puts("Test Failed: Service wasn't renamed correctly ✗", color=colors.red)
|
||||||
exit_test(1)
|
exit_test(1)
|
||||||
|
|
||||||
|
#Change settings
|
||||||
|
opposite_proto = "udp" if args.proto == "tcp" else "tcp"
|
||||||
|
if(firegex.nf_settings_service(service_id, 1338, opposite_proto, "::dead:beef" if args.ipv6 else "123.123.123.123", True)):
|
||||||
|
srv_updated = firegex.nf_get_service(service_id)
|
||||||
|
if srv_updated["port"] == 1338 and srv_updated["proto"] == opposite_proto and ("::dead:beef" if args.ipv6 else "123.123.123.123") in srv_updated["ip_int"] and srv_updated["fail_open"]:
|
||||||
|
puts("Sucessfully changed service settings ✔", color=colors.green)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Service settings weren't updated correctly ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
else:
|
||||||
|
puts("Test Failed: Coulnd't change service settings ✗", color=colors.red)
|
||||||
|
exit_test(1)
|
||||||
|
|
||||||
|
exit_test(0)
|
||||||
|
|||||||
@@ -42,6 +42,11 @@ def exit_test(code):
|
|||||||
exit_test(1)
|
exit_test(1)
|
||||||
exit(code)
|
exit(code)
|
||||||
|
|
||||||
|
srvs = firegex.ph_get_services()
|
||||||
|
for ele in srvs:
|
||||||
|
if ele['name'] == args.service_name:
|
||||||
|
firegex.ph_delete_service(ele['service_id'])
|
||||||
|
|
||||||
#Create and start serivce
|
#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")
|
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:
|
if service_id:
|
||||||
|
|||||||
@@ -102,6 +102,10 @@ class FiregexAPI:
|
|||||||
req = self.s.put(f"{self.address}api/nfregex/services/{service_id}/rename" , json={"name":newname})
|
req = self.s.put(f"{self.address}api/nfregex/services/{service_id}/rename" , json={"name":newname})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
|
def nf_settings_service(self,service_id: str, port: int, proto: str, ip_int: str, fail_open: bool):
|
||||||
|
req = self.s.put(f"{self.address}api/nfregex/services/{service_id}/settings" , json={"port":port, "proto":proto, "ip_int":ip_int, "fail_open":fail_open})
|
||||||
|
return verify(req)
|
||||||
|
|
||||||
def nf_get_service_regexes(self,service_id: str):
|
def nf_get_service_regexes(self,service_id: str):
|
||||||
req = self.s.get(f"{self.address}api/nfregex/services/{service_id}/regexes")
|
req = self.s.get(f"{self.address}api/nfregex/services/{service_id}/regexes")
|
||||||
return req.json()
|
return req.json()
|
||||||
@@ -127,9 +131,9 @@ class FiregexAPI:
|
|||||||
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_case_sensitive": is_case_sensitive})
|
json={"service_id": service_id, "regex": regex, "mode": mode, "active": active, "is_case_sensitive": is_case_sensitive})
|
||||||
return verify(req)
|
return verify(req)
|
||||||
|
|
||||||
def nf_add_service(self, name: str, port: int, proto: str, ip_int: str):
|
def nf_add_service(self, name: str, port: int, proto: str, ip_int: str, fail_open: bool = False):
|
||||||
req = self.s.post(f"{self.address}api/nfregex/services" ,
|
req = self.s.post(f"{self.address}api/nfregex/services" ,
|
||||||
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int})
|
json={"name":name,"port":port, "proto": proto, "ip_int": ip_int, "fail_open": fail_open})
|
||||||
return req.json()["service_id"] if verify(req) else False
|
return req.json()["service_id"] if verify(req) else False
|
||||||
|
|
||||||
#PortHijack
|
#PortHijack
|
||||||
|
|||||||
Reference in New Issue
Block a user